You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metamodel.apache.org by ka...@apache.org on 2016/08/05 06:02:44 UTC

[2/2] metamodel git commit: RESTful MetaModel service-webapp.

RESTful MetaModel service-webapp.

Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/6d197c36
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/6d197c36
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/6d197c36

Branch: refs/heads/feature/5.x/rest
Commit: 6d197c360398438e7ac3b894cd7a24b4ec3df4ce
Parents: 02397db
Author: Kasper S�rensen <i....@gmail.com>
Authored: Thu Aug 4 23:02:34 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Thu Aug 4 23:02:34 2016 -0700

----------------------------------------------------------------------
 .../factory/DataContextPropertiesImpl.java      |   3 +
 .../apache/metamodel/pojo/PojoDataContext.java  |   4 +-
 .../metamodel/pojo/PojoDataContextFactory.java  |  71 +++
 ....apache.metamodel.factory.DataContextFactory |   1 +
 pom.xml                                         |  38 +-
 service-webapp/Dockerfile                       |  32 ++
 service-webapp/README.md                        |  12 +
 service-webapp/pom.xml                          |  98 ++++
 .../src/main/docker/swagger-ui/index.html       |  94 ++++
 .../app/CachedDataSourceRegistryWrapper.java    | 109 ++++
 .../service/app/DataContextSupplier.java        |  48 ++
 .../service/app/DataContextTraverser.java       |  67 +++
 .../service/app/DataSourceDefinition.java       |  28 +
 .../service/app/DataSourceRegistry.java         |  48 ++
 .../service/app/InMemoryDataSourceRegistry.java |  65 +++
 .../service/app/InMemoryTenantContext.java      |  41 ++
 .../service/app/InMemoryTenantRegistry.java     |  74 +++
 .../metamodel/service/app/TenantContext.java    |  31 ++
 .../metamodel/service/app/TenantRegistry.java   |  38 ++
 .../AbstractIdentifierNamingException.java      |  40 ++
 .../DataSourceAlreadyExistException.java        |  28 +
 .../DataSourceNotUpdateableException.java       |  37 ++
 .../app/exceptions/NoSuchColumnException.java   |  29 +
 .../exceptions/NoSuchDataSourceException.java   |  29 +
 .../app/exceptions/NoSuchSchemaException.java   |  29 +
 .../app/exceptions/NoSuchTableException.java    |  29 +
 .../app/exceptions/NoSuchTenantException.java   |  28 +
 .../exceptions/TenantAlreadyExistException.java |  28 +
 .../service/controllers/ColumnController.java   |  86 +++
 .../controllers/DataSourceController.java       | 104 ++++
 .../service/controllers/QueryController.java    |  85 +++
 .../service/controllers/RestErrorHandler.java   | 160 ++++++
 .../controllers/RootInformationController.java  | 102 ++++
 .../service/controllers/SchemaController.java   |  80 +++
 .../controllers/SwaggerSpecController.java      |  64 +++
 .../service/controllers/TableController.java    |  85 +++
 .../controllers/TableDataController.java        | 115 ++++
 .../service/controllers/TenantController.java   |  94 ++++
 .../model/RestDataSourceDefinition.java         |  55 ++
 .../controllers/model/RestErrorResponse.java    |  80 +++
 .../service/controllers/model/RestLink.java     |  60 +++
 .../resources/context/application-context.xml   |  34 ++
 service-webapp/src/main/resources/logback.xml   |  33 ++
 service-webapp/src/main/resources/swagger.yaml  | 538 +++++++++++++++++++
 .../main/webapp/WEB-INF/dispatcher-servlet.xml  |  59 ++
 service-webapp/src/main/webapp/WEB-INF/web.xml  |  70 +++
 .../RootInformationControllerTest.java          |  61 +++
 .../TenantInteractionScenarioTest.java          | 195 +++++++
 spring/pom.xml                                  |  12 -
 49 files changed, 3337 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java b/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java
index e66a811..13ce9c1 100644
--- a/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java
+++ b/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java
@@ -288,6 +288,9 @@ public class DataContextPropertiesImpl implements DataContextProperties {
     @Override
     public SimpleTableDef[] getTableDefs() {
         final Object obj = get(PROPERTY_TABLE_DEFS);
+        if (obj == null) {
+            return null;
+        }
         if (obj instanceof SimpleTableDef[]) {
             return (SimpleTableDef[]) obj;
         }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java
----------------------------------------------------------------------
diff --git a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java
index 9369e96..340228f 100644
--- a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java
+++ b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java
@@ -49,6 +49,8 @@ import org.apache.metamodel.util.SimpleTableDef;
 public class PojoDataContext extends QueryPostprocessDataContext implements UpdateableDataContext, Serializable {
 
     private static final long serialVersionUID = 1L;
+    
+    public static final String DEFAULT_SCHEMA_NAME = "Schema";
 
     private final Map<String, TableDataProvider<?>> _tables;
     private final String _schemaName;
@@ -68,7 +70,7 @@ public class PojoDataContext extends QueryPostprocessDataContext implements Upda
      * @param tables
      */
     public PojoDataContext(List<TableDataProvider<?>> tables) {
-        this("Schema", tables);
+        this(DEFAULT_SCHEMA_NAME, tables);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java
----------------------------------------------------------------------
diff --git a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java
new file mode 100644
index 0000000..35842bf
--- /dev/null
+++ b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.pojo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.metamodel.ConnectionException;
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.factory.DataContextFactory;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.factory.ResourceFactoryRegistry;
+import org.apache.metamodel.factory.UnsupportedDataContextPropertiesException;
+import org.apache.metamodel.util.SimpleTableDef;
+
+public class PojoDataContextFactory implements DataContextFactory {
+
+    public static final String PROPERTY_TYPE = "pojo";
+
+    @Override
+    public boolean accepts(DataContextProperties properties, ResourceFactoryRegistry resourceFactoryRegistry) {
+        return PROPERTY_TYPE.equals(properties.getDataContextType());
+    }
+
+    @Override
+    public DataContext create(DataContextProperties properties, ResourceFactoryRegistry resourceFactoryRegistry)
+            throws UnsupportedDataContextPropertiesException, ConnectionException {
+
+        assert accepts(properties, resourceFactoryRegistry);
+
+        final String schemaName;
+        if (properties.getDatabaseName() != null) {
+            schemaName = properties.getDatabaseName();
+        } else {
+            schemaName = "Schema";
+        }
+
+        final List<TableDataProvider<?>> tableDataProviders;
+
+        final SimpleTableDef[] tableDefs = properties.getTableDefs();
+        if (tableDefs == null) {
+            tableDataProviders = new ArrayList<>();
+        } else {
+            tableDataProviders = new ArrayList<>(tableDefs.length);
+            for (int i = 0; i < tableDefs.length; i++) {
+                final TableDataProvider<?> tableDataProvider = new ArrayTableDataProvider(tableDefs[i],
+                        new ArrayList<Object[]>());
+                tableDataProviders.add(tableDataProvider);
+            }
+        }
+
+        return new PojoDataContext(schemaName, tableDataProviders);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory
----------------------------------------------------------------------
diff --git a/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory b/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory
new file mode 100644
index 0000000..76f808d
--- /dev/null
+++ b/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory
@@ -0,0 +1 @@
+org.apache.metamodel.pojo.PojoDataContextFactory
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 1df4cc0..51f8b67 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,6 +29,7 @@ under the License.
 		<hadoop.version>2.6.0</hadoop.version>
 		<jackson.version>2.6.3</jackson.version>
 		<easymock.version>3.2</easymock.version>
+		<spring.version>4.2.6.RELEASE</spring.version>
 		<httpcomponents.version>4.4.1</httpcomponents.version>
 		<checksum-maven-plugin.version>1.2</checksum-maven-plugin.version>
 		<skipTests>false</skipTests>
@@ -77,6 +78,7 @@ under the License.
 		<module>full</module>
 		<module>spring</module>
 		<module>neo4j</module>
+		<module>service-webapp</module>
 	</modules>
 	<issueManagement>
 		<system>Jira</system>
@@ -370,7 +372,7 @@ under the License.
 						<excludeSubProjects>false</excludeSubProjects>
 						<excludes>
 							<exclude>KEYS</exclude>
-							<exclude>*.md</exclude>
+							<exclude>**/*.md</exclude>
 							<exclude>example-metamodel-integrationtest-configuration.properties</exclude>
 							<exclude>travis-metamodel-integrationtest-configuration.properties</exclude>
 							<exclude>**/src/assembly/metamodel-packaged-assembly-descriptor.xml</exclude>
@@ -389,6 +391,7 @@ under the License.
 							<exclude>**/*.ipr/**</exclude>
 							<exclude>**/.idea/**</exclude>
 							<exclude>**/tattletale-filters.properties</exclude>
+							<exclude>**/swagger-ui/**</exclude>
 							<exclude>DEPENDENCIES</exclude>
 							<exclude>DISCLAIMER</exclude>
 							<exclude>neo4j-community-*/**</exclude>
@@ -511,6 +514,11 @@ under the License.
 				<version>${jackson.version}</version>
 			</dependency>
 			<dependency>
+				<groupId>com.fasterxml.jackson.dataformat</groupId>
+				<artifactId>jackson-dataformat-yaml</artifactId>
+				<version>${jackson.version}</version>
+			</dependency>
+			<dependency>
 				<groupId>com.fasterxml.jackson.core</groupId>
 				<artifactId>jackson-annotations</artifactId>
 				<version>${jackson.version}</version>
@@ -535,6 +543,34 @@ under the License.
 				<artifactId>hsqldb</artifactId>
 				<version>1.8.0.10</version>
 			</dependency>
+			
+			<!-- Spring -->
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-core</artifactId>
+				<version>${spring.version}</version>
+			    <exclusions>
+			    	<exclusion>
+			    		<groupId>commons-logging</groupId>
+			    		<artifactId>commons-logging</artifactId>
+			    	</exclusion>
+			    </exclusions>
+			</dependency>
+			<dependency>
+			    <groupId>org.springframework</groupId>
+			    <artifactId>spring-context</artifactId>
+			    <version>${spring.version}</version>
+			</dependency>
+			<dependency>
+			    <groupId>org.springframework</groupId>
+			    <artifactId>spring-test</artifactId>
+			    <version>${spring.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-webmvc</artifactId>
+				<version>${spring.version}</version>
+			</dependency>
 
 			<!-- Hadoop -->
 			<dependency>

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/Dockerfile
----------------------------------------------------------------------
diff --git a/service-webapp/Dockerfile b/service-webapp/Dockerfile
new file mode 100644
index 0000000..4734f19
--- /dev/null
+++ b/service-webapp/Dockerfile
@@ -0,0 +1,32 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+FROM tomcat:8.0-jre8
+
+RUN rm -rf $CATALINA_HOME/webapps \
+ mkdir $CATALINA_HOME/webapps
+COPY target/MetaModel.war $CATALINA_HOME/webapps/ROOT.war
+
+EXPOSE 8080
+
+ENV SWAGGER_UI_VERSION 2.1.5
+RUN  wget -q -O - https://github.com/swagger-api/swagger-ui/archive/v$SWAGGER_UI_VERSION.tar.gz | tar -xzf - -C /tmp \
+    && mv /tmp/swagger-ui-$SWAGGER_UI_VERSION/dist $CATALINA_HOME/webapps/swagger-ui \
+    && rm -r /tmp/swagger-ui-$SWAGGER_UI_VERSION
+ADD src/main/docker/swagger-ui/index.html $CATALINA_HOME/webapps/swagger-ui
+
+CMD ["catalina.sh", "run"]

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/README.md
----------------------------------------------------------------------
diff --git a/service-webapp/README.md b/service-webapp/README.md
new file mode 100644
index 0000000..20bca99
--- /dev/null
+++ b/service-webapp/README.md
@@ -0,0 +1,12 @@
+# MetaModel-as-a-service
+
+This is a web application that allows you to access MetaModel's unified API for datastore exploration and querying - using a set of RESTful services.
+
+## Docker building and running
+
+```
+docker build -t metamodel-service .
+docker run --rm -p 8080:8080 metamodel-service
+```
+
+And then go to http://localhost:8080 (assuming localhost is your docker machine).

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/pom.xml
----------------------------------------------------------------------
diff --git a/service-webapp/pom.xml b/service-webapp/pom.xml
new file mode 100644
index 0000000..fa56340
--- /dev/null
+++ b/service-webapp/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<parent>
+		<artifactId>MetaModel</artifactId>
+		<groupId>org.apache.metamodel</groupId>
+		<version>5.0-SNAPSHOT</version>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+	<artifactId>MetaModel-service-webapp</artifactId>
+	<name>MetaModel-as\u2013a-service web application</name>
+	<packaging>war</packaging>
+
+	<build>
+		<finalName>MetaModel</finalName>
+	</build>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.metamodel</groupId>
+			<artifactId>MetaModel-spring</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.metamodel</groupId>
+			<artifactId>MetaModel-full</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-webmvc</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-databind</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.dataformat</groupId>
+			<artifactId>jackson-dataformat-yaml</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-classic</artifactId>
+			<version>1.1.7</version>
+		</dependency>
+		<dependency>
+			<groupId>org.hibernate</groupId>
+			<artifactId>hibernate-validator</artifactId>
+			<version>5.2.4.Final</version>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>jcl-over-slf4j</artifactId>
+		</dependency>
+
+		<!-- Provided -->
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+			<version>3.1.0</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<!-- Test -->
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/docker/swagger-ui/index.html
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/docker/swagger-ui/index.html b/service-webapp/src/main/docker/swagger-ui/index.html
new file mode 100755
index 0000000..16df339
--- /dev/null
+++ b/service-webapp/src/main/docker/swagger-ui/index.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Swagger UI</title>
+  <link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32" />
+  <link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16" />
+  <link href='css/typography.css' media='screen' rel='stylesheet' type='text/css'/>
+  <link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
+  <link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
+  <link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/>
+  <link href='css/print.css' media='print' rel='stylesheet' type='text/css'/>
+
+  <script src='lib/object-assign-pollyfill.js' type='text/javascript'></script>
+  <script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script>
+  <script src='lib/jquery.slideto.min.js' type='text/javascript'></script>
+  <script src='lib/jquery.wiggle.min.js' type='text/javascript'></script>
+  <script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
+  <script src='lib/handlebars-2.0.0.js' type='text/javascript'></script>
+  <script src='lib/lodash.min.js' type='text/javascript'></script>
+  <script src='lib/backbone-min.js' type='text/javascript'></script>
+  <script src='swagger-ui.js' type='text/javascript'></script>
+  <script src='lib/highlight.9.1.0.pack.js' type='text/javascript'></script>
+  <script src='lib/highlight.9.1.0.pack_extended.js' type='text/javascript'></script>
+  <script src='lib/jsoneditor.min.js' type='text/javascript'></script>
+  <script src='lib/marked.js' type='text/javascript'></script>
+  <script src='lib/swagger-oauth.js' type='text/javascript'></script>
+
+  <!-- Some basic translations -->
+  <!-- <script src='lang/translator.js' type='text/javascript'></script> -->
+  <!-- <script src='lang/ru.js' type='text/javascript'></script> -->
+  <!-- <script src='lang/en.js' type='text/javascript'></script> -->
+
+  <script type="text/javascript">
+    $(function () {
+      var url = window.location.search.match(/url=([^&]+)/);
+      if (url && url.length > 1) {
+        url = decodeURIComponent(url[1]);
+      } else {
+        url = "../../swagger.json";
+      }
+
+      hljs.configure({
+        highlightSizeThreshold: 5000
+      });
+
+      // Pre load translate...
+      if(window.SwaggerTranslator) {
+        window.SwaggerTranslator.translate();
+      }
+      window.swaggerUi = new SwaggerUi({
+        url: url,
+        dom_id: "swagger-ui-container",
+        supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
+        onComplete: function(swaggerApi, swaggerUi){
+          if(typeof initOAuth == "function") {
+            initOAuth({
+              clientId: "your-client-id",
+              clientSecret: "your-client-secret-if-required",
+              realm: "your-realms",
+              appName: "your-app-name",
+              scopeSeparator: ",",
+              additionalQueryStringParams: {}
+            });
+          }
+
+          if(window.SwaggerTranslator) {
+            window.SwaggerTranslator.translate();
+          }
+        },
+        onFailure: function(data) {
+          log("Unable to Load SwaggerUI");
+        },
+        docExpansion: "none",
+        jsonEditor: false,
+        defaultModelRendering: 'schema',
+        showRequestHeaders: false
+      });
+
+      window.swaggerUi.load();
+
+      function log() {
+        if ('console' in window) {
+          console.log.apply(console, arguments);
+        }
+      }
+  });
+  </script>
+</head>
+
+<body class="swagger-section">
+<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java
new file mode 100644
index 0000000..fffe83d
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java
@@ -0,0 +1,109 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.MetaModelException;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
+import org.apache.metamodel.util.FileHelper;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.RemovalListener;
+import com.google.common.cache.RemovalNotification;
+
+/**
+ * A wrapper that adds a cache around a {@link DataSourceRegistry} in order to
+ * prevent re-connecting all the time to the same data source.
+ */
+public class CachedDataSourceRegistryWrapper implements DataSourceRegistry {
+
+    /**
+     * The default timeout (in seconds) before the cache evicts and closes the
+     * created {@link DataContext}s.
+     */
+    public static final int DEFAULT_TIMEOUT_SECONDS = 60;
+
+    private final DataSourceRegistry delegate;
+    private final LoadingCache<String, DataContext> loadingCache;
+
+    public CachedDataSourceRegistryWrapper(DataSourceRegistry delegate) {
+        this(delegate, DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+    }
+
+    public CachedDataSourceRegistryWrapper(DataSourceRegistry delegate, long cacheTimeout, TimeUnit cacheTimeoutUnit) {
+        this.delegate = delegate;
+        this.loadingCache = CacheBuilder.newBuilder().expireAfterAccess(cacheTimeout, cacheTimeoutUnit).removalListener(
+                createRemovalListener()).build(createCacheLoader());
+    }
+
+    private RemovalListener<String, DataContext> createRemovalListener() {
+        return new RemovalListener<String, DataContext>() {
+            @Override
+            public void onRemoval(RemovalNotification<String, DataContext> notification) {
+                final DataContext dataContext = notification.getValue();
+                // some DataContexts are closeable - attempt closing it here
+                FileHelper.safeClose(dataContext);
+            }
+        };
+    }
+
+    private CacheLoader<String, DataContext> createCacheLoader() {
+        return new CacheLoader<String, DataContext>() {
+            @Override
+            public DataContext load(String key) throws Exception {
+                return delegate.openDataContext(key);
+            }
+        };
+    }
+
+    @Override
+    public List<String> getDataSourceNames() {
+        return delegate.getDataSourceNames();
+    }
+
+    @Override
+    public String registerDataSource(String dataContextName, DataContextProperties dataContextProperties)
+            throws DataSourceAlreadyExistException {
+        loadingCache.invalidate(dataContextName);
+        return delegate.registerDataSource(dataContextName, dataContextProperties);
+    }
+
+    @Override
+    public DataContext openDataContext(String dataSourceName) throws NoSuchDataSourceException {
+        try {
+            return loadingCache.get(dataSourceName);
+        } catch (ExecutionException e) {
+            final Throwable cause = e.getCause();
+            if (cause instanceof RuntimeException) {
+                throw (RuntimeException) cause;
+            }
+            throw new MetaModelException("Unexpected error happened while getting DataContext '" + dataSourceName
+                    + "' from cache", e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java
new file mode 100644
index 0000000..2f42c6f
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.function.Supplier;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.factory.DataContextFactoryRegistryImpl;
+import org.apache.metamodel.factory.DataContextProperties;
+
+public class DataContextSupplier implements Supplier<DataContext> {
+
+    private final String dataSourceName;
+    private final DataContextProperties dataContextProperties;
+
+    public DataContextSupplier(String dataSourceName, DataContextProperties dataContextProperties) {
+        this.dataSourceName = dataSourceName;
+        this.dataContextProperties = dataContextProperties;
+    }
+
+    @Override
+    public DataContext get() {
+        final DataContext dataContext = DataContextFactoryRegistryImpl.getDefaultInstance().createDataContext(
+                dataContextProperties);
+        return dataContext;
+    }
+
+    @Override
+    public String toString() {
+        return "DataContextSupplier[" + dataSourceName + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java
new file mode 100644
index 0000000..6ec7122
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.schema.Schema;
+import org.apache.metamodel.schema.Table;
+import org.apache.metamodel.service.app.exceptions.NoSuchColumnException;
+import org.apache.metamodel.service.app.exceptions.NoSuchSchemaException;
+import org.apache.metamodel.service.app.exceptions.NoSuchTableException;
+
+/**
+ * Utility object responsible for traversing the schema/table/column structures
+ * of a {@link DataContext} based on String identifiers and names.
+ * 
+ * This class will throw the appropriate exceptions if needed which is more
+ * communicative than the usual NPEs that would otherwise be thrown.
+ */
+public class DataContextTraverser {
+
+    private final DataContext dataContext;
+
+    public DataContextTraverser(DataContext dataContext) {
+        this.dataContext = dataContext;
+    }
+
+    public Schema getSchema(String schemaName) {
+        final Schema schema = dataContext.getSchemaByName(schemaName);
+        if (schema == null) {
+            throw new NoSuchSchemaException(schemaName);
+        }
+        return schema;
+    }
+
+    public Table getTable(String schemaName, String tableName) {
+        final Table table = getSchema(schemaName).getTableByName(tableName);
+        if (table == null) {
+            throw new NoSuchTableException(tableName);
+        }
+        return table;
+    }
+
+    public Column getColumn(String schemaName, String tableName, String columnName) {
+        final Column column = getTable(schemaName, tableName).getColumnByName(columnName);
+        if (column == null) {
+            throw new NoSuchColumnException(columnName);
+        }
+        return column;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
new file mode 100644
index 0000000..2aaa8e5
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.Map;
+
+public interface DataSourceDefinition {
+
+    public String getType();
+    
+    public Map<String, Object> getProperties();
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
new file mode 100644
index 0000000..e0c8697
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.List;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.UpdateableDataContext;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException;
+import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
+
+/**
+ * Represents a user's/tenant's registry of {@link DataContext}s.
+ */
+public interface DataSourceRegistry {
+
+    public List<String> getDataSourceNames();
+
+    public String registerDataSource(String dataContextName, DataContextProperties dataContextProperties) throws DataSourceAlreadyExistException;
+
+    public DataContext openDataContext(String dataSourceName) throws NoSuchDataSourceException;
+
+    public default UpdateableDataContext openDataContextForUpdate(String dataSourceName) {
+        final DataContext dataContext = openDataContext(dataSourceName);
+        if (dataContext instanceof UpdateableDataContext) {
+            return (UpdateableDataContext) dataContext;
+        }
+        throw new DataSourceNotUpdateableException(dataSourceName);
+    };
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
new file mode 100644
index 0000000..386232a
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
+
+public class InMemoryDataSourceRegistry implements DataSourceRegistry {
+
+    private final Map<String, Supplier<DataContext>> dataSources;
+
+    public InMemoryDataSourceRegistry() {
+        dataSources = new LinkedHashMap<>();
+    }
+
+    @Override
+    public String registerDataSource(final String name, final DataContextProperties dataContextProperties)
+            throws DataSourceAlreadyExistException {
+        if (dataSources.containsKey(name)) {
+            throw new DataSourceAlreadyExistException(name);
+        }
+
+        dataSources.put(name, new DataContextSupplier(name, dataContextProperties));
+        return name;
+    }
+
+    @Override
+    public List<String> getDataSourceNames() {
+        return dataSources.keySet().stream().collect(Collectors.toList());
+    }
+
+    @Override
+    public DataContext openDataContext(String name) {
+        final Supplier<DataContext> supplier = dataSources.get(name);
+        if (supplier == null) {
+            throw new NoSuchDataSourceException(name);
+        }
+        return supplier.get();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
new file mode 100644
index 0000000..022ab28
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+public class InMemoryTenantContext implements TenantContext {
+
+    private final String tenantIdentifier;
+    private final DataSourceRegistry dataContextRegistry;
+
+    public InMemoryTenantContext(String tenantIdentifier) {
+        this.tenantIdentifier = tenantIdentifier;
+        this.dataContextRegistry = new CachedDataSourceRegistryWrapper(new InMemoryDataSourceRegistry());
+    }
+
+    @Override
+    public String getTenantName() {
+        return tenantIdentifier;
+    }
+
+    @Override
+    public DataSourceRegistry getDataSourceRegistry() {
+        return dataContextRegistry;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
new file mode 100644
index 0000000..6ac5bec
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
@@ -0,0 +1,74 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.service.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException;
+
+/**
+ * In-memory {@link TenantRegistry}. This is not particularly
+ * production-friendly as it is non-persistent, but it is useful for demo
+ * purposes.
+ */
+public class InMemoryTenantRegistry implements TenantRegistry {
+
+    private final Map<String, TenantContext> tenants;
+
+    public InMemoryTenantRegistry() {
+        tenants = new LinkedHashMap<>();
+    }
+
+    @Override
+    public List<String> getTenantIdentifiers() {
+        return tenants.keySet().stream().collect(Collectors.toList());
+    }
+
+    @Override
+    public TenantContext getTenantContext(String tenantIdentifier) {
+        final TenantContext tenant = tenants.get(tenantIdentifier);
+        if (tenant == null) {
+            throw new NoSuchTenantException(tenantIdentifier);
+        }
+        return tenant;
+    }
+
+    @Override
+    public TenantContext createTenantContext(String tenantIdentifier) {
+        if (tenants.containsKey(tenantIdentifier)) {
+            throw new TenantAlreadyExistException(tenantIdentifier);
+        }
+        final InMemoryTenantContext tenantContext = new InMemoryTenantContext(tenantIdentifier);
+        tenants.put(tenantIdentifier, tenantContext);
+        return tenantContext;
+    }
+
+    @Override
+    public void deleteTenantContext(String tenantIdentifier) {
+        final TenantContext removedTenant = tenants.remove(tenantIdentifier);
+        if (removedTenant == null) {
+            throw new NoSuchTenantException(tenantIdentifier);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
new file mode 100644
index 0000000..c550a60
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+/**
+ * Represents a context-object containing all the information and services
+ * related to a particular tenant.
+ */
+public interface TenantContext {
+
+    public String getTenantName();
+
+    public DataSourceRegistry getDataSourceRegistry();
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
new file mode 100644
index 0000000..5c02821
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
@@ -0,0 +1,38 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.List;
+
+import org.apache.metamodel.service.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException;
+
+/**
+ * Represents the application's central registry of tenants
+ */
+public interface TenantRegistry {
+
+    public List<String> getTenantIdentifiers();
+
+    public TenantContext getTenantContext(String tenantIdentifier) throws NoSuchTenantException;
+
+    public TenantContext createTenantContext(String tenantIdentifier) throws TenantAlreadyExistException;
+
+    public void deleteTenantContext(String tenantIdentifier) throws NoSuchTenantException;
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java
new file mode 100644
index 0000000..98f5892
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+import org.apache.metamodel.MetaModelException;
+
+/**
+ * Exception super class for any exception that arises because an identifier
+ * (name, ID or such) is invalid for a specific context.
+ */
+public class AbstractIdentifierNamingException extends MetaModelException {
+
+    private static final long serialVersionUID = 1L;
+    private final String identifier;
+
+    public AbstractIdentifierNamingException(String identifier) {
+        super("Illegal value: " + identifier);
+        this.identifier = identifier;
+    }
+
+    public String getIdentifier() {
+        return identifier;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java
new file mode 100644
index 0000000..3d5d650
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class DataSourceAlreadyExistException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public DataSourceAlreadyExistException(String name) {
+        super(name);
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java
new file mode 100644
index 0000000..eb828cc
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+import org.apache.metamodel.MetaModelException;
+
+public class DataSourceNotUpdateableException extends MetaModelException {
+
+    private static final long serialVersionUID = 1L;
+
+    private final String dataSourceName;
+
+    public DataSourceNotUpdateableException(String dataSourceName) {
+        super(dataSourceName);
+        this.dataSourceName = dataSourceName;
+    }
+
+    public String getDataSourceName() {
+        return dataSourceName;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java
new file mode 100644
index 0000000..1902af1
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchColumnException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public NoSuchColumnException(String name) {
+        super(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java
new file mode 100644
index 0000000..b59f016
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchDataSourceException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public NoSuchDataSourceException(String name) {
+        super(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java
new file mode 100644
index 0000000..5b238a8
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchSchemaException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public NoSuchSchemaException(String name) {
+        super(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java
new file mode 100644
index 0000000..73141cd
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchTableException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public NoSuchTableException(String name) {
+        super(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java
new file mode 100644
index 0000000..b123ae2
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchTenantException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public NoSuchTenantException(String tenantIdentifier) {
+        super(tenantIdentifier);
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java
new file mode 100644
index 0000000..f24c114
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class TenantAlreadyExistException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public TenantAlreadyExistException(String tenantIdentifier) {
+        super(tenantIdentifier);
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
new file mode 100644
index 0000000..c811def
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
@@ -0,0 +1,86 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.service.app.DataContextTraverser;
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = { "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}/columns/{column}",
+        "/{tenant}/{dataContext}/s/{schema}/t/{table}/c/{column}" }, produces = MediaType.APPLICATION_JSON_VALUE)
+public class ColumnController {
+
+    private final TenantRegistry tenantRegistry;
+
+    @Autowired
+    public ColumnController(TenantRegistry tenantRegistry) {
+        this.tenantRegistry = tenantRegistry;
+    }
+
+    @RequestMapping(method = RequestMethod.GET)
+    @ResponseBody
+    public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+            @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId,
+            @PathVariable("table") String tableId, @PathVariable("column") String columnId) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+        final DataContextTraverser traverser = new DataContextTraverser(dataContext);
+
+        final Column column = traverser.getColumn(schemaId, tableId, columnId);
+
+        final String tenantName = tenantContext.getTenantName();
+        final String tableName = column.getTable().getName();
+        final String schemaName = column.getTable().getSchema().getName();
+
+        final Map<String, Object> metadata = new LinkedHashMap<>();
+        metadata.put("number", column.getColumnNumber());
+        metadata.put("size", column.getColumnSize());
+        metadata.put("nullable", column.isNullable());
+        metadata.put("primary-key", column.isPrimaryKey());
+        metadata.put("indexed", column.isIndexed());
+        metadata.put("column-type", column.getType() == null ? null : column.getType().getName());
+        metadata.put("native-type", column.getNativeType());
+        metadata.put("remarks", column.getRemarks());
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "column");
+        map.put("name", column.getName());
+        map.put("table", tableName);
+        map.put("schema", schemaName);
+        map.put("datasource", dataSourceName);
+        map.put("tenant", tenantName);
+        map.put("metadata", metadata);
+
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
new file mode 100644
index 0000000..6dfe2d0
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
@@ -0,0 +1,104 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.validation.Valid;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.UpdateableDataContext;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.factory.DataContextPropertiesImpl;
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+import org.apache.metamodel.service.controllers.model.RestDataSourceDefinition;
+import org.apache.metamodel.service.controllers.model.RestLink;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = "/{tenant}/{datasource}", produces = MediaType.APPLICATION_JSON_VALUE)
+public class DataSourceController {
+
+    private final TenantRegistry tenantRegistry;
+
+    @Autowired
+    public DataSourceController(TenantRegistry tenantRegistry) {
+        this.tenantRegistry = tenantRegistry;
+    }
+
+    @RequestMapping(method = RequestMethod.PUT)
+    @ResponseBody
+    public Map<String, Object> put(@PathVariable("tenant") String tenantId,
+            @PathVariable("datasource") String dataSourceId,
+            @Valid @RequestBody RestDataSourceDefinition dataContextDefinition) {
+
+        final Map<String, Object> map = new HashMap<>();
+        map.putAll(dataContextDefinition.getProperties());
+        map.put(DataContextPropertiesImpl.PROPERTY_DATA_CONTEXT_TYPE, dataContextDefinition.getType());
+
+        if (!map.containsKey(DataContextPropertiesImpl.PROPERTY_DATABASE)) {
+            // add the data source ID as database name if it is not already set.
+            map.put(DataContextPropertiesImpl.PROPERTY_DATABASE, dataSourceId);
+        }
+
+        final DataContextProperties properties = new DataContextPropertiesImpl(map);
+
+        final String dataContextIdentifier = tenantRegistry.getTenantContext(tenantId).getDataSourceRegistry()
+                .registerDataSource(dataSourceId, properties);
+
+        return get(tenantId, dataContextIdentifier);
+    }
+
+    @RequestMapping(method = RequestMethod.GET)
+    @ResponseBody
+    public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+            @PathVariable("datasource") String dataSourceName) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+        final String tenantName = tenantContext.getTenantName();
+        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}");
+
+        final List<RestLink> schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s,
+                uriBuilder.build(tenantName, dataSourceName, s))).collect(Collectors.toList());
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "datasource");
+        map.put("name", dataSourceName);
+        map.put("tenant", tenantName);
+        map.put("updateable", dataContext instanceof UpdateableDataContext);
+        map.put("query_uri", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantName, dataSourceName));
+        map.put("schemas", schemaLinks);
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java
new file mode 100644
index 0000000..693bc4c
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.data.DataSet;
+import org.apache.metamodel.query.Query;
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = { "/{tenant}/{dataContext}/query",
+        "/{tenant}/{dataContext}/q" }, produces = MediaType.APPLICATION_JSON_VALUE)
+public class QueryController {
+
+    private final TenantRegistry tenantRegistry;
+
+    @Autowired
+    public QueryController(TenantRegistry tenantRegistry) {
+        this.tenantRegistry = tenantRegistry;
+    }
+
+    @RequestMapping(method = RequestMethod.GET)
+    @ResponseBody
+    public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+            @PathVariable("dataContext") String dataSourceName,
+            @RequestParam(value = "sql", required = true) String queryString,
+            @RequestParam(value = "offset", required = false) Integer offset,
+            @RequestParam(value = "limit", required = false) Integer limit) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+        final Query query = dataContext.parseQuery(queryString);
+        
+        return executeQuery(dataContext, query, offset, limit);
+    }
+
+    public static Map<String, Object> executeQuery(DataContext dataContext, Query query, Integer offset, Integer limit) {
+
+        if (offset != null) {
+            query.setFirstRow(offset);
+        }
+        if (limit != null) {
+            query.setMaxRows(limit);
+        }
+
+        final DataSet dataSet = dataContext.executeQuery(query);
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "dataset");
+        map.put("header", Arrays.stream(dataSet.getSelectItems()).map((si) -> si.toString()).collect(Collectors
+                .toList()));
+        map.put("data", dataSet.toObjectArrays());
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
new file mode 100644
index 0000000..8a08151
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
@@ -0,0 +1,160 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.metamodel.query.parser.QueryParserException;
+import org.apache.metamodel.service.app.exceptions.AbstractIdentifierNamingException;
+import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException;
+import org.apache.metamodel.service.app.exceptions.NoSuchColumnException;
+import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
+import org.apache.metamodel.service.app.exceptions.NoSuchSchemaException;
+import org.apache.metamodel.service.app.exceptions.NoSuchTableException;
+import org.apache.metamodel.service.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException;
+import org.apache.metamodel.service.controllers.model.RestErrorResponse;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ControllerAdvice
+public class RestErrorHandler {
+
+    /**
+     * Method binding issues (raised by Spring framework) - mapped to
+     * BAD_REQUEST.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ResponseBody
+    public RestErrorResponse processValidationError(MethodArgumentNotValidException ex) {
+        final BindingResult result = ex.getBindingResult();
+
+        final Map<String, Object> globalErrorsMap = new LinkedHashMap<>();
+        final List<ObjectError> globalErrors = result.getGlobalErrors();
+        for (ObjectError objectError : globalErrors) {
+            globalErrorsMap.put(objectError.getObjectName(), objectError.getDefaultMessage());
+        }
+
+        final List<FieldError> fieldErrors = result.getFieldErrors();
+        final Map<String, Object> fieldErrorsMap = new LinkedHashMap<>();
+        for (FieldError fieldError : fieldErrors) {
+            fieldErrorsMap.put(fieldError.getObjectName() + '.' + fieldError.getField(), fieldError
+                    .getDefaultMessage());
+        }
+
+        final Map<String, Object> additionalDetails = new LinkedHashMap<>();
+        if (!globalErrorsMap.isEmpty()) {
+            additionalDetails.put("global-errors", globalErrorsMap);
+        }
+        if (!fieldErrorsMap.isEmpty()) {
+            additionalDetails.put("field-errors", fieldErrorsMap);
+        }
+        final RestErrorResponse errorResponse = new RestErrorResponse(HttpStatus.BAD_REQUEST.value(),
+                "Failed to validate request");
+        if (!additionalDetails.isEmpty()) {
+            errorResponse.setAdditionalDetails(additionalDetails);
+        }
+        return errorResponse;
+    }
+
+    /**
+     * No such [Entity] exception handler method - mapped to NOT_FOUND.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler({ NoSuchTenantException.class, NoSuchDataSourceException.class, NoSuchSchemaException.class,
+            NoSuchTableException.class, NoSuchColumnException.class })
+    @ResponseStatus(HttpStatus.NOT_FOUND)
+    @ResponseBody
+    public RestErrorResponse processNoSuchEntity(AbstractIdentifierNamingException ex) {
+        return new RestErrorResponse(HttpStatus.NOT_FOUND.value(), "Not found: " + ex.getIdentifier());
+    }
+
+    /**
+     * [Entity] already exist exception handler method - mapped to CONFLICT.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler({ TenantAlreadyExistException.class, DataSourceAlreadyExistException.class })
+    @ResponseStatus(HttpStatus.CONFLICT)
+    @ResponseBody
+    public RestErrorResponse processEntityAlreadyExist(AbstractIdentifierNamingException ex) {
+        return new RestErrorResponse(HttpStatus.CONFLICT.value(), "Already exist: " + ex.getIdentifier());
+    }
+
+    /**
+     * DataSource not updateable exception handler method - mapped to
+     * BAD_REQUEST.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(DataSourceNotUpdateableException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ResponseBody
+    public RestErrorResponse processDataSourceNotUpdateable(DataSourceNotUpdateableException ex) {
+        return new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), "DataSource not updateable: " + ex
+                .getDataSourceName());
+    }
+
+    /**
+     * Query parsing exception - mapped to BAD_REQUEST.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(QueryParserException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ResponseBody
+    public RestErrorResponse processQueryParsingError(QueryParserException ex) {
+        return new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
+    }
+
+    /**
+     * Catch-all exception handler method - mapped to INTERNAL_SERVER_ERROR.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(Exception.class)
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    @ResponseBody
+    public RestErrorResponse processAnyException(Exception ex) {
+        final Map<String, Object> additionalDetails = new HashMap<>();
+        additionalDetails.put("exception_type", ex.getClass().getName());
+        return new RestErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage(), additionalDetails);
+    }
+}