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 2017/07/31 20:23:09 UTC

[1/6] metamodel-membrane git commit: Added initial codebase, adapted from MetaModel REST service branch

Repository: metamodel-membrane
Updated Branches:
  refs/heads/master 8d6636a89 -> d5419cb34


http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/TableController.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/TableController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/TableController.java
new file mode 100644
index 0000000..82f568d
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/TableController.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.membrane.controllers;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.membrane.app.DataContextTraverser;
+import org.apache.metamodel.membrane.app.TenantContext;
+import org.apache.metamodel.membrane.app.TenantRegistry;
+import org.apache.metamodel.membrane.controllers.model.RestLink;
+import org.apache.metamodel.schema.Table;
+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}",
+        "/{tenant}/{dataContext}/s/{schema}/t/{table}" }, produces = MediaType.APPLICATION_JSON_VALUE)
+public class TableController {
+
+    private final TenantRegistry tenantRegistry;
+
+    @Autowired
+    public TableController(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) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+        
+        final DataContextTraverser traverser = new DataContextTraverser(dataContext);
+
+        final Table table = traverser.getTable(schemaId, tableId);
+
+        final String tenantName = tenantContext.getTenantName();
+        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}/t/{table}/c/{column}");
+
+        final String tableName = table.getName();
+        final String schemaName = table.getSchema().getName();
+        final List<RestLink> columnsLinks = Arrays.stream(table.getColumnNames()).map(c -> new RestLink(String.valueOf(
+                c), uriBuilder.build(tenantName, dataSourceName, schemaName, tableName, c))).collect(Collectors
+                        .toList());
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "table");
+        map.put("name", tableName);
+        map.put("schema", schemaName);
+        map.put("datasource", dataSourceName);
+        map.put("tenant", tenantName);
+        map.put("columns", columnsLinks);
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java
new file mode 100644
index 0000000..e906556
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java
@@ -0,0 +1,115 @@
+/**
+ * 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.membrane.controllers;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.UpdateCallback;
+import org.apache.metamodel.UpdateScript;
+import org.apache.metamodel.UpdateSummary;
+import org.apache.metamodel.UpdateableDataContext;
+import org.apache.metamodel.insert.RowInsertionBuilder;
+import org.apache.metamodel.membrane.app.DataContextTraverser;
+import org.apache.metamodel.membrane.app.TenantContext;
+import org.apache.metamodel.membrane.app.TenantRegistry;
+import org.apache.metamodel.query.Query;
+import org.apache.metamodel.schema.Table;
+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.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = { "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}/data",
+        "/{tenant}/{dataContext}/s/{schema}/t/{table}/d" }, produces = MediaType.APPLICATION_JSON_VALUE)
+public class TableDataController {
+
+    private final TenantRegistry tenantRegistry;
+
+    @Autowired
+    public TableDataController(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, @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 DataContextTraverser traverser = new DataContextTraverser(dataContext);
+
+        final Table table = traverser.getTable(schemaId, tableId);
+
+        final Query query = dataContext.query().from(table).selectAll().toQuery();
+
+        return QueryController.executeQuery(dataContext, query, offset, limit);
+    }
+
+    @RequestMapping(method = RequestMethod.POST)
+    @ResponseBody
+    public Map<String, Object> post(@PathVariable("tenant") String tenantId,
+            @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId,
+            @PathVariable("table") String tableId, @RequestBody final List<Map<String, Object>> inputRecords) {
+
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final UpdateableDataContext dataContext = tenantContext.getDataSourceRegistry().openDataContextForUpdate(dataSourceName);
+
+        final DataContextTraverser traverser = new DataContextTraverser(dataContext);
+
+        final Table table = traverser.getTable(schemaId, tableId);
+
+        final UpdateSummary result = dataContext.executeUpdate(new UpdateScript() {
+            @Override
+            public void run(UpdateCallback callback) {
+                for (Map<String, Object> inputMap : inputRecords) {
+                    final RowInsertionBuilder insert = callback.insertInto(table);
+                    for (Entry<String, Object> entry : inputMap.entrySet()) {
+                        insert.value(entry.getKey(), entry.getValue());
+                    }
+                    insert.execute();
+                }
+            }
+        });
+
+        final Map<String, Object> response = new LinkedHashMap<>();
+        response.put("status", "ok");
+
+        if (result.getInsertedRows().isPresent()) {
+            response.put("inserted-rows", result.getInsertedRows().get());
+        }
+        if (result.getGeneratedKeys().isPresent()) {
+            response.put("generated-keys", result.getGeneratedKeys().get());
+        }
+
+        return response;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/TenantController.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/TenantController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/TenantController.java
new file mode 100644
index 0000000..c4af271
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/TenantController.java
@@ -0,0 +1,94 @@
+/**
+ * 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.membrane.controllers;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.metamodel.membrane.app.TenantContext;
+import org.apache.metamodel.membrane.app.TenantRegistry;
+import org.apache.metamodel.membrane.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.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}", produces = MediaType.APPLICATION_JSON_VALUE)
+public class TenantController {
+
+    private final TenantRegistry tenantRegistry;
+
+    @Autowired
+    public TenantController(TenantRegistry tenantRegistry) {
+        this.tenantRegistry = tenantRegistry;
+    }
+
+    @RequestMapping(method = RequestMethod.GET)
+    @ResponseBody
+    public Map<String, Object> getTenant(@PathVariable("tenant") String tenantName) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantName);
+        final String tenantNameNormalized = tenantContext.getTenantName();
+
+        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{datasource}");
+
+        final List<String> dataContextIdentifiers = tenantContext.getDataSourceRegistry().getDataSourceNames();
+        final List<RestLink> dataSourceLinks = dataContextIdentifiers.stream().map(s -> new RestLink(s, uriBuilder
+                .build(tenantNameNormalized, s))).collect(Collectors.toList());
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "tenant");
+        map.put("name", tenantNameNormalized);
+        map.put("datasources", dataSourceLinks);
+        return map;
+    }
+
+    @RequestMapping(method = RequestMethod.PUT)
+    @ResponseBody
+    public Map<String, Object> putTenant(@PathVariable("tenant") String tenantName) {
+        final TenantContext tenantContext = tenantRegistry.createTenantContext(tenantName);
+        final String tenantIdentifier = tenantContext.getTenantName();
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "tenant");
+        map.put("name", tenantIdentifier);
+
+        return map;
+    }
+
+    @RequestMapping(method = RequestMethod.DELETE)
+    @ResponseBody
+    public Map<String, Object> deleteTenant(@PathVariable("tenant") String tenantName) {
+        tenantRegistry.deleteTenantContext(tenantName);
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "tenant");
+        map.put("name", tenantName);
+        map.put("deleted", true);
+
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
new file mode 100644
index 0000000..500427e
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
@@ -0,0 +1,55 @@
+/**
+ * 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.membrane.controllers.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.validation.constraints.NotNull;
+
+import org.apache.metamodel.membrane.app.DataSourceDefinition;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RestDataSourceDefinition implements DataSourceDefinition {
+
+    private final Map<String, Object> properties = new HashMap<>();
+
+    @JsonProperty(value = "type", required = true)
+    @NotNull
+    private String type;
+
+    @Override
+    public String getType() {
+        return type;
+    }
+
+    @JsonAnyGetter
+    @Override
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+
+    @JsonAnySetter
+    public void set(String name, Object value) {
+        properties.put(name, value);
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java
new file mode 100644
index 0000000..ad2ce2a
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java
@@ -0,0 +1,80 @@
+/**
+ * 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.membrane.controllers.model;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Represents the JSON object that is returned when an error occurs
+ */
+public class RestErrorResponse {
+
+    @JsonProperty("code")
+    private int code;
+
+    @JsonProperty("message")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private String message;
+
+    @JsonProperty("additional_details")
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private Map<String, Object> additionalDetails;
+
+    public RestErrorResponse(int code, String message) {
+        this(code, message, null);
+    }
+
+    public RestErrorResponse(int code, String message, Map<String, Object> additionalDetails) {
+        this.code = code;
+        this.message = message;
+        this.additionalDetails = additionalDetails;
+    }
+
+    public RestErrorResponse() {
+        this(-1, null, null);
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public void setAdditionalDetails(Map<String, Object> additionalDetails) {
+        this.additionalDetails = additionalDetails;
+    }
+
+    public Map<String, Object> getAdditionalDetails() {
+        return additionalDetails;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java
new file mode 100644
index 0000000..01fb76d
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java
@@ -0,0 +1,60 @@
+/**
+ * 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.membrane.controllers.model;
+
+import java.io.Serializable;
+import java.net.URI;
+
+/**
+ * Represents a hyper-link to a service (typically provided in the REST
+ * responses)
+ */
+public class RestLink implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String name;
+
+    private URI uri;
+
+    public RestLink() {
+    }
+
+    public RestLink(String name, URI uri) {
+        this();
+        this.name = name;
+        this.uri = uri;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public URI getUri() {
+        return uri;
+    }
+
+    public void setUri(URI uri) {
+        this.uri = uri;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/resources/context/application-context.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/context/application-context.xml b/core/src/main/resources/context/application-context.xml
new file mode 100644
index 0000000..e28e2b0
--- /dev/null
+++ b/core/src/main/resources/context/application-context.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:security="http://www.springframework.org/schema/security"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
+	xmlns:context="http://www.springframework.org/schema/context"
+	xmlns:mvc="http://www.springframework.org/schema/mvc"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+	http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
+	http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
+				http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
+
+	<context:component-scan base-package="org.apache.metamodel.membrane.app" />
+
+	<bean id="tenantRegistry" class="org.apache.metamodel.membrane.app.InMemoryTenantRegistry" />
+
+</beans>

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/resources/context/dispatcher-servlet.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/context/dispatcher-servlet.xml b/core/src/main/resources/context/dispatcher-servlet.xml
new file mode 100644
index 0000000..17a4be7
--- /dev/null
+++ b/core/src/main/resources/context/dispatcher-servlet.xml
@@ -0,0 +1,59 @@
+<?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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:security="http://www.springframework.org/schema/security"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
+	xmlns:context="http://www.springframework.org/schema/context"
+	xmlns:mvc="http://www.springframework.org/schema/mvc"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+	http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
+	http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
+				http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
+
+	<context:component-scan base-package="org.apache.metamodel.membrane.controllers" />
+
+	<mvc:annotation-driven>
+		<mvc:message-converters>
+			<ref bean="jsonMessageConverter" />
+		</mvc:message-converters>
+	</mvc:annotation-driven>
+
+	<mvc:resources location="/swagger-ui/" mapping="/swagger-ui/**" order="-1001" />
+	
+	<!-- Message converters -->
+	<bean id="jsonMessageConverter"
+		class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
+		<property name="supportedMediaTypes">
+			<list>
+				<value>application/json;charset=UTF-8</value>
+				<value>application/json</value>
+			</list>
+		</property>
+	</bean>
+
+	<bean
+		class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
+		<property name="messageConverters">
+			<list>
+				<ref bean="jsonMessageConverter" />
+			</list>
+		</property>
+	</bean>
+</beans>

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/resources/logback.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/logback.xml b/core/src/main/resources/logback.xml
new file mode 100644
index 0000000..8ba8596
--- /dev/null
+++ b/core/src/main/resources/logback.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+<configuration>
+
+	<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+		</encoder>
+	</appender>
+
+	<logger name="org.apache.metamodel" level="info" />
+
+	<root level="warn">
+		<appender-ref ref="consoleAppender" />
+	</root>
+</configuration>

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/resources/swagger.yaml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/swagger.yaml b/core/src/main/resources/swagger.yaml
new file mode 100644
index 0000000..dc5ef93
--- /dev/null
+++ b/core/src/main/resources/swagger.yaml
@@ -0,0 +1,538 @@
+# 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.
+swagger: '2.0'
+info:
+  title: Apache MetaModel Membrane
+  description: Data Federation as a RESTful service.
+  version: "1.0"
+  contact:
+    name: Apache MetaModel
+    url: http://metamodel.apache.org
+  license:
+    name: Apache License, version 2.0
+    url: http://www.apache.org/licenses/LICENSE-2.0
+consumes:
+  - application/json
+produces:
+  - application/json
+paths:
+  /:
+    get:
+      summary: Hello Membrane
+      description: An endpoint that provides a confirmation that the system is operational
+      responses:
+        200:
+          description: The system is operational
+          schema:
+            type: object
+            properties:
+              ping:
+                type: string
+                description: Should return 'pong!' when the system is operational
+              application:
+                type: string
+                description: The name of the application running (Apache MetaModel Membrane)
+              version:
+                type: string
+                description: The version of the application running
+              server-time:
+                type: object
+                properties:
+                  timestamp:
+                    type: integer
+                    format: int64
+                    description: The server-time in timestamp format (millis since 1st of January 1970)
+                  iso8601:
+                    type: string
+                    description: The server-time in ISO-8601 format
+              canonical-hostname:
+                type: string
+                description: The canonical hostname of the server
+  /{tenant}:
+    parameters:
+      - name: tenant
+        in: path
+        type: string
+        description: The tenant name
+        required: true
+    put:
+      summary: Create new tenant
+      responses:
+        200:
+          description: Tenant created
+          schema:
+            properties:
+              type:
+                type: string
+                description: The type of entity (tenant)
+              name:
+                type: string
+                description: The tenant name/identifier
+        409:
+          description: Tenant already exist
+          schema:
+            $ref: "#/definitions/error"
+    get:
+      summary: Get tenant information
+      description: Provides basic information about a tenant of the system
+      responses:
+        404:
+          description: Tenant not found
+          schema:
+            $ref: "#/definitions/error"
+        200:
+          description: Tenant found
+          schema:
+            type: object
+            properties:
+              type:
+                type: string
+                description: The type of entity (tenant)
+              name:
+                type: string
+                description: The tenant name/identifier
+              datasources:
+                type: array
+                items:
+                  type: object
+                  properties:
+                    name:
+                      type: string
+                      description: The name of the datasource
+                    uri:
+                      type: string
+                      format: uri
+                      description: A link to the datasource information
+    delete:
+      summary: Delete tenant
+      description: Deletes a tenant from the system
+      responses:
+        200:
+          description: Tenant deleted
+          schema:
+            type: object
+            properties:
+              type:
+                type: string
+                description: The type of entity (tenant)
+              name:
+                type: string
+                description: The tenant name/identifier
+        404:
+          description: Tenant not found
+          schema:
+            $ref: "#/definitions/error"
+  /{tenant}/{datasource}:
+    parameters:
+      - name: tenant
+        in: path
+        type: string
+        description: The tenant name
+        required: true
+      - name: datasource
+        in: path
+        type: string
+        description: The datasource name
+        required: true
+    get:
+      responses:
+        200:
+          description: Datasource found
+          schema:
+            type: object
+            properties:
+              type:
+                type: string
+                description: The type of entity (datasource)
+              name:
+                type: string
+                description: The datasource name
+              tenant:
+                type: string
+                description: The tenant name
+              updateable:
+                type: boolean
+                description: Is this datasource updateable?
+              query_uri:
+                type: string
+                description: A link to the query endpoint for this datasource
+                format: uri
+              schemas:
+                type: array
+                description: The schemas of this datasource
+                items:
+                  type: object
+                  properties:
+                    name:
+                      type: string
+                      description: The schema name
+                    uri:
+                      type: string
+                      description: A link to the schema information
+                      format: uri
+        404:
+          description: Datasource not found
+          schema:
+            $ref: "#/definitions/error"
+    put:
+      parameters:
+        - name: inputData
+          in: body
+          description: The definition of the datasource using properties. The same properties as normally applied in MetaModel factories (e.g. 'type', 'resource', 'url', 'driver-class' ,'hostname', 'port', 'catalog', 'database', 'username', 'port', 'table-defs') are used here.
+          required: true
+          schema:
+            type: object
+      responses:
+        200:
+          description: Datasource created
+  /{tenant}/{datasource}/q:
+    parameters:
+      - name: tenant
+        in: path
+        type: string
+        description: The tenant name
+        required: true
+      - name: datasource
+        in: path
+        type: string
+        description: The datasource name
+        required: true
+    get:
+      description: Executes a query on the datasource
+      parameters:
+      - name: sql
+        in: query
+        type: string
+        description: The MetaModel-flavoured SQL query to execute
+        required: true
+      - name: offset
+        in: query
+        type: string
+        description: An offset / first-row flag to set on the query
+        required: false
+      - name: limit
+        in: query
+        type: string
+        description: A limit / max-rows flag to set on the query
+        required: false
+      responses:
+        200:
+          description: Query executed
+          schema:
+            $ref: "#/definitions/queryResponse"
+        400:
+          description: Failure while parsing query
+          schema:
+            $ref: "#/definitions/error"
+        404:
+          description: Datasource not found
+          schema:
+            $ref: "#/definitions/error"
+        500:
+          description: Failure while executing query
+          schema:
+            $ref: "#/definitions/error"
+  /{tenant}/{datasource}/s/{schema}:
+    parameters:
+      - name: tenant
+        in: path
+        type: string
+        description: The tenant name
+        required: true
+      - name: datasource
+        in: path
+        type: string
+        description: The datasource name
+        required: true
+      - name: schema
+        in: path
+        type: string
+        description: The schema name
+        required: true
+    get:
+      responses:
+        200:
+          description: Schema found
+          schema:
+            type: object
+            properties:
+              type:
+                type: string
+                description: The type of entity (schema)
+              name:
+                type: string
+                description: The schema name
+              datasource:
+                type: string
+                description: The datasource name
+              tables:
+                type: array
+                description: The names of the schema's tables
+                items:
+                  type: object
+                  properties:
+                    name:
+                      type: string
+                      description: The table name
+                    uri:
+                      type: string
+                      description: A link to the table information
+                      format: uri
+        404:
+          description: Schema not found
+          schema:
+            $ref: "#/definitions/error"
+  /{tenant}/{datasource}/s/{schema}/t/{table}:
+    parameters:
+      - name: tenant
+        in: path
+        type: string
+        description: The tenant name
+        required: true
+      - name: datasource
+        in: path
+        type: string
+        description: The datasource name
+        required: true
+      - name: schema
+        in: path
+        type: string
+        description: The schema name
+        required: true
+      - name: table
+        in: path
+        type: string
+        description: The table name
+        required: true
+    get:
+      responses:
+        200:
+          description: Table found
+          schema:
+            type: object
+            properties:
+              type:
+                type: string
+                description: The type of entity (table)
+              name:
+                type: string
+                description: The table name
+              schema:
+                type: string
+                description: The schema name
+              datasource:
+                type: string
+                description: The datasource name
+              tenant:
+                type: string
+                description: The tenant name
+              columns:
+                type: array
+                description: The names of the table's columns
+                items:
+                  type: object
+                  properties:
+                    name:
+                      type: string
+                      description: The column name
+                    uri:
+                      type: string
+                      description: A link to the column information
+                      format: uri
+        404:
+          description: Table not found
+          schema:
+            $ref: "#/definitions/error"
+  /{tenant}/{datasource}/s/{schema}/t/{table}/d:
+    parameters:
+      - name: tenant
+        in: path
+        type: string
+        description: The tenant name
+        required: true
+      - name: datasource
+        in: path
+        type: string
+        description: The datasource name
+        required: true
+      - name: schema
+        in: path
+        type: string
+        description: The schema name
+        required: true
+      - name: table
+        in: path
+        type: string
+        description: The table name
+        required: true
+    get:
+      description: Gets the data of the table
+      responses:
+        200:
+          description: Query executed
+          schema:
+            $ref: "#/definitions/queryResponse"
+        400:
+          description: Failure while parsing query
+          schema:
+            $ref: "#/definitions/error"
+        404:
+          description: Table not found
+          schema:
+            $ref: "#/definitions/error"
+        500:
+          description: Failure while executing query
+          schema:
+            $ref: "#/definitions/error"
+    post:
+      description: Inserts data to the table
+      parameters:
+        - name: inputData
+          in: body
+          description: The data to insert
+          required: true
+          schema:
+            type: array
+            items:
+              description: A record to insert where each key is expected to match a column name and each value is the value to put.
+              type: object
+      responses:
+        200:
+          description: Data inserted
+          #TODO
+        404:
+          description: Table not found
+          schema:
+            $ref: "#/definitions/error"
+  /{tenant}/{datasource}/s/{schema}/t/{table}/c/{column}:
+    parameters:
+      - name: tenant
+        in: path
+        type: string
+        description: The tenant name
+        required: true
+      - name: datasource
+        in: path
+        type: string
+        description: The datasource name
+        required: true
+      - name: schema
+        in: path
+        type: string
+        description: The schema name
+        required: true
+      - name: table
+        in: path
+        type: string
+        description: The table name
+        required: true
+      - name: column
+        in: path
+        type: string
+        description: The column name
+        required: true
+    get:
+      description: Gets information about a column
+      responses:
+        200:
+          description: Query executed
+          schema:
+            type: object
+            properties:
+              type:
+                type: string
+                description: The type of entity (column)
+              name:
+                type: string
+                description: The column name
+              table:
+                type: string
+                description: The table name
+              schema:
+                type: string
+                description: The schema name
+              datasource:
+                type: string
+                description: The datasource name
+              tenant:
+                type: string
+                description: The tenant name
+              metadata:
+                type: object
+                description: Metadata about the column
+                properties:
+                  number:
+                    type: integer
+                    description: The column number (0-based)
+                  size:
+                    type: integer
+                    description: The column size
+                  nullable:
+                    type: boolean
+                    description: Is the column nullable?
+                  primary-key:
+                    type: boolean
+                    description: Is the column a primary key?
+                  indexed:
+                    type: boolean
+                    description: Is the column indexed?
+                  column-type:
+                    type: string
+                    description: The column type (as interpreted/adapted by Apache MetaModel)
+                  native-type:
+                    type: string
+                    description: The native column type (as defined by the datasource itself)
+                  remarks:
+                    type: string
+                    description: Any remarks on the column
+        404:
+          description: Column not found
+          schema:
+            $ref: "#/definitions/error"
+definitions:
+  queryResponse:
+    description: Represents the result of a query - a dataset
+    type: object
+    properties:
+      type:
+        type: string
+        description: The type of entity (dataset)
+      headers:
+        type: array
+        description: The dataset header names
+        items:
+          type: string
+      data:
+        type: array
+        description: The actual data returned by the query
+        items:
+          type: array
+          items:
+            type: object
+  error:
+    description: Elaborates the error that occurred
+    type: object
+    properties:
+      code:
+        type: integer
+        description: The HTTP status code
+      message:
+        type: string
+        description: A humanly readable error message
+      additional_details:
+        type: object
+        description: Any auxilary details to further elaborate the error

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/test/java/org/apache/metamodel/membrane/controllers/RootInformationControllerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/metamodel/membrane/controllers/RootInformationControllerTest.java b/core/src/test/java/org/apache/metamodel/membrane/controllers/RootInformationControllerTest.java
new file mode 100644
index 0000000..1b5fbbf
--- /dev/null
+++ b/core/src/test/java/org/apache/metamodel/membrane/controllers/RootInformationControllerTest.java
@@ -0,0 +1,62 @@
+/**
+ * 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.membrane.controllers;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.apache.metamodel.membrane.controllers.RootInformationController;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class RootInformationControllerTest {
+
+    private MockMvc mockMvc;
+
+    @Before
+    public void init() {
+        final RootInformationController controller = new RootInformationController();
+        controller.servletContext = new MockServletContext();
+        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+    }
+
+    @Test
+    public void testGet() throws Exception {
+        final MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/").contentType(
+                MediaType.APPLICATION_JSON);
+
+        final MvcResult result = mockMvc.perform(request).andExpect(MockMvcResultMatchers.status().is(200)).andReturn();
+
+        final String content = result.getResponse().getContentAsString();
+
+        final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+        assertEquals("pong!", map.get("ping"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java b/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java
new file mode 100644
index 0000000..4c01e53
--- /dev/null
+++ b/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java
@@ -0,0 +1,202 @@
+/**
+ * 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.membrane.controllers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Map;
+
+import org.apache.metamodel.membrane.app.InMemoryTenantRegistry;
+import org.apache.metamodel.membrane.app.TenantRegistry;
+import org.apache.metamodel.membrane.controllers.ColumnController;
+import org.apache.metamodel.membrane.controllers.DataSourceController;
+import org.apache.metamodel.membrane.controllers.QueryController;
+import org.apache.metamodel.membrane.controllers.SchemaController;
+import org.apache.metamodel.membrane.controllers.TableController;
+import org.apache.metamodel.membrane.controllers.TableDataController;
+import org.apache.metamodel.membrane.controllers.TenantController;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class TenantInteractionScenarioTest {
+
+    private MockMvc mockMvc;
+
+    @Before
+    public void init() {
+        TenantRegistry tenantRegistry = new InMemoryTenantRegistry();
+        TenantController tenantController = new TenantController(tenantRegistry);
+        DataSourceController dataContextController = new DataSourceController(tenantRegistry);
+        SchemaController schemaController = new SchemaController(tenantRegistry);
+        TableController tableController = new TableController(tenantRegistry);
+        ColumnController columnController = new ColumnController(tenantRegistry);
+        QueryController queryController = new QueryController(tenantRegistry);
+        TableDataController tableDataController = new TableDataController(tenantRegistry);
+
+        mockMvc = MockMvcBuilders.standaloneSetup(tenantController, dataContextController, schemaController,
+                tableController, columnController, queryController, tableDataController).build();
+    }
+
+    @Test
+    public void testScenario() throws Exception {
+        // create tenant
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put("/tenant1").contentType(
+                    MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
+
+            final String content = result.getResponse().getContentAsString();
+            final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+            assertEquals("tenant", map.get("type"));
+            assertEquals("tenant1", map.get("name"));
+            assertNull(map.get("datasources"));
+        }
+
+        // create datasource
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put("/tenant1/mydata").content(
+                    "{'type':'pojo','table-defs':'hello_world (greeting VARCHAR, who VARCHAR); foo (bar INTEGER, baz DATE);'}"
+                            .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect(
+                                    MockMvcResultMatchers.status().isOk()).andReturn();
+
+            final String content = result.getResponse().getContentAsString();
+            final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+            assertEquals("datasource", map.get("type"));
+            assertEquals("mydata", map.get("name"));
+            assertEquals(
+                    "[{name=information_schema, uri=/tenant1/mydata/s/information_schema}, {name=mydata, uri=/tenant1/mydata/s/mydata}]",
+                    map.get("schemas").toString());
+        }
+
+        // explore tenant - now with a datasource
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1")).andExpect(
+                    MockMvcResultMatchers.status().isOk()).andReturn();
+
+            final String content = result.getResponse().getContentAsString();
+            final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+            assertEquals("tenant", map.get("type"));
+            assertEquals("tenant1", map.get("name"));
+            assertEquals("[{name=mydata, uri=/tenant1/mydata}]", map.get("datasources").toString());
+        }
+
+        // explore schema
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/s/mydata")).andExpect(
+                    MockMvcResultMatchers.status().isOk()).andReturn();
+
+            final String content = result.getResponse().getContentAsString();
+            final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+            assertEquals("schema", map.get("type"));
+            assertEquals("mydata", map.get("name"));
+
+            assertEquals(
+                    "[{name=foo, uri=/tenant1/mydata/s/mydata/t/foo}, {name=hello_world, uri=/tenant1/mydata/s/mydata/t/hello_world}]",
+                    map.get("tables").toString());
+        }
+
+        // explore table
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/s/mydata/t/foo"))
+                    .andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
+
+            final String content = result.getResponse().getContentAsString();
+            final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+            assertEquals("table", map.get("type"));
+            assertEquals("foo", map.get("name"));
+
+            assertEquals("[{name=bar, uri=/tenant1/mydata/s/mydata/t/foo/c/bar}, "
+                    + "{name=baz, uri=/tenant1/mydata/s/mydata/t/foo/c/baz}]", map.get("columns").toString());
+        }
+
+        // explore column
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/s/mydata/t/foo/c/bar"))
+                    .andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
+
+            final String content = result.getResponse().getContentAsString();
+            final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+            assertEquals("column", map.get("type"));
+            assertEquals("bar", map.get("name"));
+
+            assertEquals(
+                    "{number=0, size=null, nullable=true, primary-key=false, indexed=false, column-type=INTEGER, native-type=null, remarks=null}",
+                    map.get("metadata").toString());
+        }
+
+        // query metadata from information_schema
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/q?sql={sql}",
+                    "SELECT name, table FROM information_schema.columns")).andExpect(MockMvcResultMatchers.status()
+                            .isOk()).andReturn();
+
+            final String content = result.getResponse().getContentAsString();
+            final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+            assertEquals("dataset", map.get("type"));
+            assertEquals("[columns.name, columns.table]", map.get("header").toString());
+            assertEquals("[[bar, foo], [baz, foo], [greeting, hello_world], [who, hello_world]]", map.get("data")
+                    .toString());
+        }
+
+        // insert into table (x2)
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(
+                    "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Howdy','who':'MetaModel'}]"
+                            .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect(
+                                    MockMvcResultMatchers.status().isOk()).andReturn();
+
+            final String content = result.getResponse().getContentAsString();
+            final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+            assertEquals("{status=ok}", map.toString());
+        }
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(
+                    "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Hi','who':'Apache'}]"
+                            .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect(
+                                    MockMvcResultMatchers.status().isOk()).andReturn();
+
+            final String content = result.getResponse().getContentAsString();
+            final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+            assertEquals("{status=ok}", map.toString());
+        }
+        
+        // query the actual data
+        // query metadata from information_schema
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/q?sql={sql}",
+                    "SELECT greeting, who AS who_is_it FROM hello_world")).andExpect(MockMvcResultMatchers.status()
+                            .isOk()).andReturn();
+
+            final String content = result.getResponse().getContentAsString();
+            final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+            assertEquals("dataset", map.get("type"));
+            assertEquals("[hello_world.greeting, hello_world.who AS who_is_it]", map.get("header").toString());
+            assertEquals("[[Howdy, MetaModel], [Hi, Apache]]", map.get("data")
+                    .toString());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/docker-compose.yml
----------------------------------------------------------------------
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..c87a2bb
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,26 @@
+# 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.
+version: '2'
+services:
+  metamodel-membrane:
+    container_name: metamodel-membrane
+    image: metamodel-membrane
+    build: undertow
+    ports:
+    - "8080:8080"
+    environment:
+    - MEMBRANE_HTTP_PORT=8080

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index b8dc59f..b5d51d5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,25 +1,32 @@
 <?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. -->
+<!--
+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/maven-v4_0_0.xsd">
 	<modelVersion>4.0.0</modelVersion>
 	<properties>
-		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-		<metamodel.version>5.0-RC2</metamodel.version>
+		<metamodel.version>5.0-SNAPSHOT</metamodel.version>
 	</properties>
 	<parent>
 		<groupId>org.apache.metamodel</groupId>
 		<artifactId>MetaModel</artifactId>
-		<version>5.0-RC2</version>
+		<version>5.0-SNAPSHOT</version>
 	</parent>
 	<scm>
 		<url>https://git-wip-us.apache.org/repos/asf?p=metamodel-membrane.git</url>
@@ -36,25 +43,34 @@
 	<inceptionYear>2017</inceptionYear>
 	<packaging>pom</packaging>
 	<modules>
+		<module>core</module>
+		<module>war</module>
+		<module>undertow</module>
 	</modules>
 
 	<dependencyManagement>
 		<dependencies>
 			<dependency>
-				<groupId>org.apache.metanmodel</groupId>
+				<groupId>org.apache.metamodel</groupId>
 				<artifactId>MetaModel-core</artifactId>
 				<version>${metamodel.version}</version>
 			</dependency>
 			<dependency>
-				<groupId>org.apache.metanmodel</groupId>
+				<groupId>org.apache.metamodel</groupId>
 				<artifactId>MetaModel-spring</artifactId>
 				<version>${metamodel.version}</version>
 			</dependency>
 			<dependency>
-				<groupId>org.apache.metanmodel</groupId>
+				<groupId>org.apache.metamodel</groupId>
 				<artifactId>MetaModel-full</artifactId>
 				<version>${metamodel.version}</version>
 			</dependency>
+			<dependency>
+				<groupId>javax.servlet</groupId>
+				<artifactId>javax.servlet-api</artifactId>
+				<version>3.1.0</version>
+				<scope>provided</scope>
+			</dependency>
 		</dependencies>
 	</dependencyManagement>
 </project>

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/undertow/Dockerfile
----------------------------------------------------------------------
diff --git a/undertow/Dockerfile b/undertow/Dockerfile
new file mode 100644
index 0000000..3e14546
--- /dev/null
+++ b/undertow/Dockerfile
@@ -0,0 +1,22 @@
+# 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 openjdk:8-jre-alpine
+
+COPY target/membrane-undertow-server.jar membrane-undertow-server.jar
+
+CMD java -server -jar membrane-undertow-server.jar

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/undertow/pom.xml
----------------------------------------------------------------------
diff --git a/undertow/pom.xml b/undertow/pom.xml
new file mode 100644
index 0000000..81c2600
--- /dev/null
+++ b/undertow/pom.xml
@@ -0,0 +1,80 @@
+<?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>
+		<groupId>org.apache.metamodel.membrane</groupId>
+		<artifactId>Membrane-parent</artifactId>
+		<version>0.1-SNAPSHOT</version>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+	<artifactId>Membrane-undertow</artifactId>
+	<packaging>jar</packaging>
+
+	<build>
+		<finalName>membrane-undertow-server</finalName>
+
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-shade-plugin</artifactId>
+				<version>2.4.3</version>
+				<executions>
+					<execution>
+						<phase>package</phase>
+						<goals>
+							<goal>shade</goal>
+						</goals>
+						<configuration>
+							<createDependencyReducedPom>false</createDependencyReducedPom>
+							<transformers>
+								<transformer
+									implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+									<mainClass>org.apache.metamodel.membrane.server.WebServer</mainClass>
+								</transformer>
+								<transformer
+									implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
+									<resource>META-INF/spring.handlers</resource>
+								</transformer>
+								<transformer
+									implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
+									<resource>META-INF/spring.schemas</resource>
+								</transformer>
+							</transformers>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.metamodel.membrane</groupId>
+			<artifactId>Membrane-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>io.undertow</groupId>
+			<artifactId>undertow-servlet</artifactId>
+			<version>1.3.23.Final</version>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/undertow/src/main/docker/swagger-ui/index.html
----------------------------------------------------------------------
diff --git a/undertow/src/main/docker/swagger-ui/index.html b/undertow/src/main/docker/swagger-ui/index.html
new file mode 100644
index 0000000..fae3de9
--- /dev/null
+++ b/undertow/src/main/docker/swagger-ui/index.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<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-membrane/blob/6492107d/undertow/src/main/java/org/apache/metamodel/membrane/server/WebServer.java
----------------------------------------------------------------------
diff --git a/undertow/src/main/java/org/apache/metamodel/membrane/server/WebServer.java b/undertow/src/main/java/org/apache/metamodel/membrane/server/WebServer.java
new file mode 100644
index 0000000..1643bd8
--- /dev/null
+++ b/undertow/src/main/java/org/apache/metamodel/membrane/server/WebServer.java
@@ -0,0 +1,92 @@
+/**
+ * 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.membrane.server;
+
+import java.io.File;
+
+import javax.servlet.ServletException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.context.ContextLoaderListener;
+import org.springframework.web.context.request.RequestContextListener;
+import org.springframework.web.filter.CharacterEncodingFilter;
+import org.springframework.web.servlet.DispatcherServlet;
+
+import com.google.common.base.Strings;
+
+import io.undertow.Undertow;
+import io.undertow.server.handlers.resource.FileResourceManager;
+import io.undertow.servlet.Servlets;
+import io.undertow.servlet.api.DeploymentInfo;
+import io.undertow.servlet.api.DeploymentManager;
+
+/**
+ * Defines the main class that starts the Undertow-based web server.
+ */
+public class WebServer {
+
+    private static final Logger logger = LoggerFactory.getLogger(WebServer.class);
+
+    private static final int DEFAULT_PORT = 8080;
+
+    public static void main(final String[] args) throws Exception {
+        final String portEnv = System.getenv("MEMBRANE_HTTP_PORT");
+        final int port = Strings.isNullOrEmpty(portEnv) ? DEFAULT_PORT : Integer.parseInt(portEnv);
+
+        logger.info("Apache MetaModel Membrane server initiating on port {}", port);
+
+        startServer(port);
+
+        logger.info("Apache MetaModel Membrane server started on port {}", port);
+    }
+
+    public static void startServer(int port) throws Exception {
+        final DeploymentInfo deployment = Servlets.deployment().setClassLoader(WebServer.class.getClassLoader());
+        deployment.setContextPath("");
+        deployment.setDeploymentName("membrane");
+        deployment.addInitParameter("contextConfigLocation", "classpath:context/application-context.xml");
+        deployment.setResourceManager(new FileResourceManager(new File("."), 0));
+        deployment.addListener(Servlets.listener(ContextLoaderListener.class));
+        deployment.addListener(Servlets.listener(RequestContextListener.class));
+        deployment.addServlet(Servlets.servlet("dispatcher", DispatcherServlet.class).addMapping("/*")
+                .addInitParam("contextConfigLocation", "classpath:context/dispatcher-servlet.xml"));
+        deployment.addFilter(Servlets.filter(CharacterEncodingFilter.class).addInitParam("forceEncoding", "true")
+                .addInitParam("encoding", "UTF-8"));
+
+        final DeploymentManager manager = Servlets.defaultContainer().addDeployment(deployment);
+        manager.deploy();
+
+        final Undertow server = Undertow.builder().addHttpListener(port, "0.0.0.0").setHandler(manager.start()).build();
+        server.start();
+
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                // graceful shutdown of everything
+                server.stop();
+                try {
+                    manager.stop();
+                } catch (ServletException e) {
+                }
+                manager.undeploy();
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/war/pom.xml
----------------------------------------------------------------------
diff --git a/war/pom.xml b/war/pom.xml
new file mode 100644
index 0000000..0c35c43
--- /dev/null
+++ b/war/pom.xml
@@ -0,0 +1,42 @@
+<?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>
+		<groupId>org.apache.metamodel.membrane</groupId>
+		<artifactId>Membrane-parent</artifactId>
+		<version>0.1-SNAPSHOT</version>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+	<artifactId>Membrane-war</artifactId>
+	<packaging>war</packaging>
+
+	<build>
+		<finalName>Membrane</finalName>
+	</build>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.metamodel.membrane</groupId>
+			<artifactId>Membrane-core</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/war/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/war/src/main/webapp/WEB-INF/web.xml b/war/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..5a313ca
--- /dev/null
+++ b/war/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,74 @@
+<?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.
+-->
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	version="3.1" xmlns="http://java.sun.com/xml/ns/javaee">
+	
+	<!-- Spring listener -->
+	<listener>
+		<listener-class>org.springframework.web.context.ContextLoaderListener
+		</listener-class>
+	</listener>
+	<listener>
+		<listener-class>org.springframework.web.context.request.RequestContextListener
+		</listener-class>
+	</listener>
+
+	<!-- Spring config -->
+	<context-param>
+		<param-name>contextConfigLocation</param-name>
+		<param-value>classpath:context/application-context.xml</param-value>
+	</context-param>
+
+	<!-- Spring servlet -->
+	<servlet>
+		<servlet-name>dispatcher</servlet-name>
+		<servlet-class>org.springframework.web.servlet.DispatcherServlet
+		</servlet-class>
+		<load-on-startup>1</load-on-startup>
+		<init-param>
+			<param-name>contextConfigLocation</param-name>
+			<param-value>classpath:context/dispatcher-servlet.xml</param-value>
+		</init-param>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>dispatcher</servlet-name>
+		<url-pattern>/*</url-pattern>
+	</servlet-mapping>
+
+	<filter>
+		<filter-name>CharacterEncodingFilter</filter-name>
+		<filter-class>org.springframework.web.filter.CharacterEncodingFilter
+		</filter-class>
+		<init-param>
+			<param-name>encoding</param-name>
+			<param-value>UTF-8</param-value>
+		</init-param>
+		<init-param>
+			<param-name>forceEncoding</param-name>
+			<param-value>true</param-value>
+		</init-param>
+	</filter>
+
+	<filter-mapping>
+		<filter-name>CharacterEncodingFilter</filter-name>
+		<url-pattern>/*</url-pattern>
+	</filter-mapping>
+
+</web-app>


[5/6] metamodel-membrane git commit: Updated MM dependency to 5.0-RC3

Posted by ka...@apache.org.
Updated MM dependency to 5.0-RC3

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

Branch: refs/heads/master
Commit: e02d105a6bafa500727354c9eb2a3bedc9bcee7c
Parents: 9bbce3a
Author: Kasper Sørensen <i....@gmail.com>
Authored: Sun Jul 30 19:22:27 2017 -0700
Committer: Kasper Sørensen <i....@gmail.com>
Committed: Sun Jul 30 19:22:27 2017 -0700

----------------------------------------------------------------------
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/e02d105a/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index b5d51d5..ebac406 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,12 +21,12 @@ under the License.
 	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<modelVersion>4.0.0</modelVersion>
 	<properties>
-		<metamodel.version>5.0-SNAPSHOT</metamodel.version>
+		<metamodel.version>5.0-RC3</metamodel.version>
 	</properties>
 	<parent>
 		<groupId>org.apache.metamodel</groupId>
 		<artifactId>MetaModel</artifactId>
-		<version>5.0-SNAPSHOT</version>
+		<version>5.0-RC3</version>
 	</parent>
 	<scm>
 		<url>https://git-wip-us.apache.org/repos/asf?p=metamodel-membrane.git</url>


[4/6] metamodel-membrane git commit: Fixed EOL and some final statements as per review

Posted by ka...@apache.org.
Fixed EOL and some final statements as per review

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

Branch: refs/heads/master
Commit: 65320cc33c3a25b024929358670552a2cdffa296
Parents: 9bbce3a
Author: Kasper Sørensen <i....@gmail.com>
Authored: Wed Jul 26 18:13:35 2017 -0700
Committer: Kasper Sørensen <i....@gmail.com>
Committed: Wed Jul 26 18:13:35 2017 -0700

----------------------------------------------------------------------
 core/pom.xml                                            |  2 +-
 .../membrane/app/CachedDataSourceRegistryWrapper.java   | 12 ++++++------
 .../controllers/model/RestDataSourceDefinition.java     |  2 +-
 .../membrane/controllers/model/RestErrorResponse.java   | 10 +++++-----
 .../metamodel/membrane/controllers/model/RestLink.java  |  7 +++----
 undertow/pom.xml                                        |  2 +-
 war/pom.xml                                             |  2 +-
 7 files changed, 18 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/65320cc3/core/pom.xml
----------------------------------------------------------------------
diff --git a/core/pom.xml b/core/pom.xml
index ad0ccb3..7042a08 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -101,4 +101,4 @@ under the License.
 			<scope>test</scope>
 		</dependency>
 	</dependencies>
-</project>
\ No newline at end of file
+</project>

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/65320cc3/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java b/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java
index f09fbca..ed7902a 100644
--- a/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java
+++ b/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java
@@ -50,11 +50,11 @@ public class CachedDataSourceRegistryWrapper implements DataSourceRegistry {
     private final DataSourceRegistry delegate;
     private final LoadingCache<String, DataContext> loadingCache;
 
-    public CachedDataSourceRegistryWrapper(DataSourceRegistry delegate) {
+    public CachedDataSourceRegistryWrapper(final DataSourceRegistry delegate) {
         this(delegate, DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
     }
 
-    public CachedDataSourceRegistryWrapper(DataSourceRegistry delegate, long cacheTimeout, TimeUnit cacheTimeoutUnit) {
+    public CachedDataSourceRegistryWrapper(final DataSourceRegistry delegate, final long cacheTimeout, final TimeUnit cacheTimeoutUnit) {
         this.delegate = delegate;
         this.loadingCache = CacheBuilder.newBuilder().expireAfterAccess(cacheTimeout, cacheTimeoutUnit).removalListener(
                 createRemovalListener()).build(createCacheLoader());
@@ -63,7 +63,7 @@ public class CachedDataSourceRegistryWrapper implements DataSourceRegistry {
     private RemovalListener<String, DataContext> createRemovalListener() {
         return new RemovalListener<String, DataContext>() {
             @Override
-            public void onRemoval(RemovalNotification<String, DataContext> notification) {
+            public void onRemoval(final RemovalNotification<String, DataContext> notification) {
                 final DataContext dataContext = notification.getValue();
                 // some DataContexts are closeable - attempt closing it here
                 FileHelper.safeClose(dataContext);
@@ -74,7 +74,7 @@ public class CachedDataSourceRegistryWrapper implements DataSourceRegistry {
     private CacheLoader<String, DataContext> createCacheLoader() {
         return new CacheLoader<String, DataContext>() {
             @Override
-            public DataContext load(String key) throws Exception {
+            public DataContext load(final String key) throws Exception {
                 return delegate.openDataContext(key);
             }
         };
@@ -86,14 +86,14 @@ public class CachedDataSourceRegistryWrapper implements DataSourceRegistry {
     }
 
     @Override
-    public String registerDataSource(String dataContextName, DataContextProperties dataContextProperties)
+    public String registerDataSource(final String dataContextName, final DataContextProperties dataContextProperties)
             throws DataSourceAlreadyExistException {
         loadingCache.invalidate(dataContextName);
         return delegate.registerDataSource(dataContextName, dataContextProperties);
     }
 
     @Override
-    public DataContext openDataContext(String dataSourceName) throws NoSuchDataSourceException {
+    public DataContext openDataContext(final String dataSourceName) throws NoSuchDataSourceException {
         try {
             return loadingCache.get(dataSourceName);
         } catch (ExecutionException e) {

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/65320cc3/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
index 500427e..3feef3c 100644
--- a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
@@ -49,7 +49,7 @@ public class RestDataSourceDefinition implements DataSourceDefinition {
     }
 
     @JsonAnySetter
-    public void set(String name, Object value) {
+    public void set(final String name, final Object value) {
         properties.put(name, value);
     }
 }

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/65320cc3/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java
index ad2ce2a..8504ea6 100644
--- a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestErrorResponse.java
@@ -39,11 +39,11 @@ public class RestErrorResponse {
     @JsonInclude(JsonInclude.Include.NON_NULL)
     private Map<String, Object> additionalDetails;
 
-    public RestErrorResponse(int code, String message) {
+    public RestErrorResponse(final int code, final String message) {
         this(code, message, null);
     }
 
-    public RestErrorResponse(int code, String message, Map<String, Object> additionalDetails) {
+    public RestErrorResponse(final int code, final String message, final Map<String, Object> additionalDetails) {
         this.code = code;
         this.message = message;
         this.additionalDetails = additionalDetails;
@@ -57,7 +57,7 @@ public class RestErrorResponse {
         return code;
     }
 
-    public void setCode(int code) {
+    public void setCode(final int code) {
         this.code = code;
     }
 
@@ -65,11 +65,11 @@ public class RestErrorResponse {
         return message;
     }
 
-    public void setMessage(String message) {
+    public void setMessage(final String message) {
         this.message = message;
     }
 
-    public void setAdditionalDetails(Map<String, Object> additionalDetails) {
+    public void setAdditionalDetails(final Map<String, Object> additionalDetails) {
         this.additionalDetails = additionalDetails;
     }
 

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/65320cc3/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java
index 01fb76d..cde135c 100644
--- a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestLink.java
@@ -30,13 +30,12 @@ public class RestLink implements Serializable {
     private static final long serialVersionUID = 1L;
 
     private String name;
-
     private URI uri;
 
     public RestLink() {
     }
 
-    public RestLink(String name, URI uri) {
+    public RestLink(final String name, final URI uri) {
         this();
         this.name = name;
         this.uri = uri;
@@ -46,7 +45,7 @@ public class RestLink implements Serializable {
         return name;
     }
 
-    public void setName(String name) {
+    public void setName(final String name) {
         this.name = name;
     }
 
@@ -54,7 +53,7 @@ public class RestLink implements Serializable {
         return uri;
     }
 
-    public void setUri(URI uri) {
+    public void setUri(final URI uri) {
         this.uri = uri;
     }
 }

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/65320cc3/undertow/pom.xml
----------------------------------------------------------------------
diff --git a/undertow/pom.xml b/undertow/pom.xml
index 81c2600..d1b989a 100644
--- a/undertow/pom.xml
+++ b/undertow/pom.xml
@@ -77,4 +77,4 @@ under the License.
 			<version>1.3.23.Final</version>
 		</dependency>
 	</dependencies>
-</project>
\ No newline at end of file
+</project>

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/65320cc3/war/pom.xml
----------------------------------------------------------------------
diff --git a/war/pom.xml b/war/pom.xml
index 0c35c43..c4ab505 100644
--- a/war/pom.xml
+++ b/war/pom.xml
@@ -39,4 +39,4 @@ under the License.
 			<version>${project.version}</version>
 		</dependency>
 	</dependencies>
-</project>
\ No newline at end of file
+</project>


[3/6] metamodel-membrane git commit: Removed incomplete swagger-ui setup

Posted by ka...@apache.org.
Removed incomplete swagger-ui setup

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

Branch: refs/heads/master
Commit: 9bbce3a1e944f41178d14e1e3de90fd309a5083e
Parents: 6492107
Author: Kasper Sørensen <i....@gmail.com>
Authored: Sat Jul 22 22:56:55 2017 -0700
Committer: Kasper Sørensen <i....@gmail.com>
Committed: Sat Jul 22 22:56:55 2017 -0700

----------------------------------------------------------------------
 .../resources/context/dispatcher-servlet.xml    |   2 -
 undertow/src/main/docker/swagger-ui/index.html  | 112 -------------------
 2 files changed, 114 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/9bbce3a1/core/src/main/resources/context/dispatcher-servlet.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/context/dispatcher-servlet.xml b/core/src/main/resources/context/dispatcher-servlet.xml
index 17a4be7..437d2b1 100644
--- a/core/src/main/resources/context/dispatcher-servlet.xml
+++ b/core/src/main/resources/context/dispatcher-servlet.xml
@@ -35,8 +35,6 @@ under the License.
 		</mvc:message-converters>
 	</mvc:annotation-driven>
 
-	<mvc:resources location="/swagger-ui/" mapping="/swagger-ui/**" order="-1001" />
-	
 	<!-- Message converters -->
 	<bean id="jsonMessageConverter"
 		class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/9bbce3a1/undertow/src/main/docker/swagger-ui/index.html
----------------------------------------------------------------------
diff --git a/undertow/src/main/docker/swagger-ui/index.html b/undertow/src/main/docker/swagger-ui/index.html
deleted file mode 100644
index fae3de9..0000000
--- a/undertow/src/main/docker/swagger-ui/index.html
+++ /dev/null
@@ -1,112 +0,0 @@
-<!DOCTYPE html>
-<!--
-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.
--->
-<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>


[2/6] metamodel-membrane git commit: Added initial codebase, adapted from MetaModel REST service branch

Posted by ka...@apache.org.
Added initial codebase, adapted from MetaModel REST service branch

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

Branch: refs/heads/master
Commit: 6492107d501b475c873cdf31a6a70b312b2fa198
Parents: 8d6636a
Author: Kasper Sørensen <i....@gmail.com>
Authored: Sat Jul 22 22:22:41 2017 -0700
Committer: Kasper Sørensen <i....@gmail.com>
Committed: Sat Jul 22 22:41:23 2017 -0700

----------------------------------------------------------------------
 README.md                                       |  18 +-
 core/pom.xml                                    | 104 ++++
 .../app/CachedDataSourceRegistryWrapper.java    | 109 ++++
 .../membrane/app/DataContextSupplier.java       |  48 ++
 .../membrane/app/DataContextTraverser.java      |  67 +++
 .../membrane/app/DataSourceDefinition.java      |  28 +
 .../membrane/app/DataSourceRegistry.java        |  48 ++
 .../app/InMemoryDataSourceRegistry.java         |  65 +++
 .../membrane/app/InMemoryTenantContext.java     |  45 ++
 .../membrane/app/InMemoryTenantRegistry.java    |  79 +++
 .../metamodel/membrane/app/TenantContext.java   |  31 ++
 .../metamodel/membrane/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 +
 .../membrane/controllers/ColumnController.java  |  86 +++
 .../controllers/DataSourceController.java       | 104 ++++
 .../membrane/controllers/QueryController.java   |  85 +++
 .../membrane/controllers/RestErrorHandler.java  | 160 ++++++
 .../controllers/RootInformationController.java  | 102 ++++
 .../membrane/controllers/SchemaController.java  |  80 +++
 .../controllers/SwaggerSpecController.java      |  64 +++
 .../membrane/controllers/TableController.java   |  85 +++
 .../controllers/TableDataController.java        | 115 ++++
 .../membrane/controllers/TenantController.java  |  94 ++++
 .../model/RestDataSourceDefinition.java         |  55 ++
 .../controllers/model/RestErrorResponse.java    |  80 +++
 .../membrane/controllers/model/RestLink.java    |  60 +++
 .../resources/context/application-context.xml   |  34 ++
 .../resources/context/dispatcher-servlet.xml    |  59 ++
 core/src/main/resources/logback.xml             |  33 ++
 core/src/main/resources/swagger.yaml            | 538 +++++++++++++++++++
 .../RootInformationControllerTest.java          |  62 +++
 .../TenantInteractionScenarioTest.java          | 202 +++++++
 docker-compose.yml                              |  26 +
 pom.xml                                         |  48 +-
 undertow/Dockerfile                             |  22 +
 undertow/pom.xml                                |  80 +++
 undertow/src/main/docker/swagger-ui/index.html  | 112 ++++
 .../metamodel/membrane/server/WebServer.java    |  92 ++++
 war/pom.xml                                     |  42 ++
 war/src/main/webapp/WEB-INF/web.xml             |  74 +++
 48 files changed, 3534 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index b3586ae..8af263a 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,22 @@ Data Federation as a RESTful service. A subproject of [Apache MetaModel](http://
 <img src="http://metamodel.apache.org/img/logo.png" style="float: right; margin-left: 20px;" alt="MetaModel logo" />
 </div>
 
+### Building and running
+
+Make sure you have [Apache Maven](http://maven.apache.org/), then build by invoking:
+
+```
+mvn clean install
+```
+
+After building the Java archives and executables, you can use [Docker](https://www.docker.com/) and [Docker compose](https://docs.docker.com/compose/) to run Membrane easily, like this:
+
+```
+docker-compose up --build
+```
+
+Now Membrane should be running on port `8080` of your Docker host. Typically that's either http://localhost:8080 (if you have a native Docker install) or http://192.168.99.100:8080 (if you use Docker toolbox).
+
 ### Mailing lists
 
 Membrane uses the same development infrastructure as the main Apache MetaModel project:
@@ -16,4 +32,4 @@ Membrane uses the same development infrastructure as the main Apache MetaModel p
 
 ### Contributing
 
-Please see [CONTRIBUTE.md from Apache MetaModel](https://github.com/apache/metamodel/blob/master/CONTRIBUTE.md)
+Please see [CONTRIBUTE.md from Apache MetaModel](https://github.com/apache/metamodel/blob/master/CONTRIBUTE.md) which also apply to the Membrane contribution guidelines.

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/pom.xml
----------------------------------------------------------------------
diff --git a/core/pom.xml b/core/pom.xml
new file mode 100644
index 0000000..ad0ccb3
--- /dev/null
+++ b/core/pom.xml
@@ -0,0 +1,104 @@
+<?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>
+		<groupId>org.apache.metamodel.membrane</groupId>
+		<artifactId>Membrane-parent</artifactId>
+		<version>0.1-SNAPSHOT</version>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+	<artifactId>Membrane-core</artifactId>
+	<packaging>jar</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.metamodel</groupId>
+			<artifactId>MetaModel-spring</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.metamodel</groupId>
+			<artifactId>MetaModel-full</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>org.slf4j</groupId>
+					<artifactId>slf4j-log4j12</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>javax.servlet</groupId>
+					<artifactId>servlet-api</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>log4j</groupId>
+					<artifactId>log4j</artifactId>
+				</exclusion>
+			</exclusions>
+		</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>
+			<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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java b/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java
new file mode 100644
index 0000000..f09fbca
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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.membrane.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/DataContextSupplier.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/DataContextSupplier.java b/core/src/main/java/org/apache/metamodel/membrane/app/DataContextSupplier.java
new file mode 100644
index 0000000..7ce0cc0
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/DataContextTraverser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/DataContextTraverser.java b/core/src/main/java/org/apache/metamodel/membrane/app/DataContextTraverser.java
new file mode 100644
index 0000000..ee22171
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.app;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.membrane.app.exceptions.NoSuchColumnException;
+import org.apache.metamodel.membrane.app.exceptions.NoSuchSchemaException;
+import org.apache.metamodel.membrane.app.exceptions.NoSuchTableException;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.schema.Schema;
+import org.apache.metamodel.schema.Table;
+
+/**
+ * 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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/DataSourceDefinition.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/DataSourceDefinition.java b/core/src/main/java/org/apache/metamodel/membrane/app/DataSourceDefinition.java
new file mode 100644
index 0000000..d8df5f4
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/DataSourceRegistry.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/DataSourceRegistry.java b/core/src/main/java/org/apache/metamodel/membrane/app/DataSourceRegistry.java
new file mode 100644
index 0000000..4aa6f01
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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.membrane.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.membrane.app.exceptions.DataSourceNotUpdateableException;
+import org.apache.metamodel.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/InMemoryDataSourceRegistry.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/InMemoryDataSourceRegistry.java b/core/src/main/java/org/apache/metamodel/membrane/app/InMemoryDataSourceRegistry.java
new file mode 100644
index 0000000..1272cee
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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.membrane.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/InMemoryTenantContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/InMemoryTenantContext.java b/core/src/main/java/org/apache/metamodel/membrane/app/InMemoryTenantContext.java
new file mode 100644
index 0000000..3ecb7fe
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/app/InMemoryTenantContext.java
@@ -0,0 +1,45 @@
+/**
+ * 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.membrane.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;
+    }
+
+    @Override
+    public String toString() {
+        return "InMemoryTenantContext[" + tenantIdentifier + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/InMemoryTenantRegistry.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/InMemoryTenantRegistry.java b/core/src/main/java/org/apache/metamodel/membrane/app/InMemoryTenantRegistry.java
new file mode 100644
index 0000000..8665819
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/app/InMemoryTenantRegistry.java
@@ -0,0 +1,79 @@
+/**
+ * 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.membrane.app;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.membrane.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.membrane.app.exceptions.TenantAlreadyExistException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 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 static final Logger logger = LoggerFactory.getLogger(InMemoryTenantRegistry.class);
+    private final Map<String, TenantContext> tenants;
+
+    public InMemoryTenantRegistry() {
+        tenants = new LinkedHashMap<>();
+        logger.info("Initialized!");
+    }
+
+    @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);
+        logger.info("Created new tenant: {}", 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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/TenantContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/TenantContext.java b/core/src/main/java/org/apache/metamodel/membrane/app/TenantContext.java
new file mode 100644
index 0000000..491859f
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/TenantRegistry.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/TenantRegistry.java b/core/src/main/java/org/apache/metamodel/membrane/app/TenantRegistry.java
new file mode 100644
index 0000000..625adb8
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.app;
+
+import java.util.List;
+
+import org.apache.metamodel.membrane.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/AbstractIdentifierNamingException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/AbstractIdentifierNamingException.java b/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/AbstractIdentifierNamingException.java
new file mode 100644
index 0000000..06a58e4
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/DataSourceAlreadyExistException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/DataSourceAlreadyExistException.java b/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/DataSourceAlreadyExistException.java
new file mode 100644
index 0000000..e284cd7
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/DataSourceNotUpdateableException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/DataSourceNotUpdateableException.java b/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/DataSourceNotUpdateableException.java
new file mode 100644
index 0000000..09003f9
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchColumnException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchColumnException.java b/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchColumnException.java
new file mode 100644
index 0000000..a161973
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchDataSourceException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchDataSourceException.java b/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchDataSourceException.java
new file mode 100644
index 0000000..38421b0
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchSchemaException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchSchemaException.java b/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchSchemaException.java
new file mode 100644
index 0000000..1810981
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchTableException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchTableException.java b/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchTableException.java
new file mode 100644
index 0000000..42bf93c
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchTenantException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchTenantException.java b/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/NoSuchTenantException.java
new file mode 100644
index 0000000..45cfa7a
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/TenantAlreadyExistException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/TenantAlreadyExistException.java b/core/src/main/java/org/apache/metamodel/membrane/app/exceptions/TenantAlreadyExistException.java
new file mode 100644
index 0000000..364e166
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/ColumnController.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/ColumnController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/ColumnController.java
new file mode 100644
index 0000000..6e99371
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.controllers;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.membrane.app.DataContextTraverser;
+import org.apache.metamodel.membrane.app.TenantContext;
+import org.apache.metamodel.membrane.app.TenantRegistry;
+import org.apache.metamodel.schema.Column;
+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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/DataSourceController.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/DataSourceController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/DataSourceController.java
new file mode 100644
index 0000000..c9df027
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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.membrane.app.TenantContext;
+import org.apache.metamodel.membrane.app.TenantRegistry;
+import org.apache.metamodel.membrane.controllers.model.RestDataSourceDefinition;
+import org.apache.metamodel.membrane.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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/QueryController.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/QueryController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/QueryController.java
new file mode 100644
index 0000000..1cd6ed6
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.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.membrane.app.TenantContext;
+import org.apache.metamodel.membrane.app.TenantRegistry;
+import org.apache.metamodel.query.Query;
+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-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/RestErrorHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/RestErrorHandler.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/RestErrorHandler.java
new file mode 100644
index 0000000..5d79f56
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/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.membrane.controllers;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.metamodel.membrane.app.exceptions.AbstractIdentifierNamingException;
+import org.apache.metamodel.membrane.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.membrane.app.exceptions.DataSourceNotUpdateableException;
+import org.apache.metamodel.membrane.app.exceptions.NoSuchColumnException;
+import org.apache.metamodel.membrane.app.exceptions.NoSuchDataSourceException;
+import org.apache.metamodel.membrane.app.exceptions.NoSuchSchemaException;
+import org.apache.metamodel.membrane.app.exceptions.NoSuchTableException;
+import org.apache.metamodel.membrane.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.membrane.app.exceptions.TenantAlreadyExistException;
+import org.apache.metamodel.membrane.controllers.model.RestErrorResponse;
+import org.apache.metamodel.query.parser.QueryParserException;
+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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/RootInformationController.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/RootInformationController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/RootInformationController.java
new file mode 100644
index 0000000..0a624aa
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/RootInformationController.java
@@ -0,0 +1,102 @@
+/**
+ * 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.membrane.controllers;
+
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.servlet.ServletContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+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
+public class RootInformationController {
+
+    private static final Logger logger = LoggerFactory.getLogger(RootInformationController.class);
+
+    @Autowired
+    ServletContext servletContext;
+
+    @RequestMapping(method = RequestMethod.GET, value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseBody
+    public Map<String, Object> index() {
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("ping", "pong!");
+        map.put("application", "Apache MetaModel Membrane");
+        map.put("version", getVersion());
+        map.put("server-time", getServerTime());
+        try {
+            map.put("canonical-hostname", InetAddress.getLocalHost().getCanonicalHostName());
+        } catch (Exception e) {
+            logger.info("Failed to get canonical-hostname", e);
+        }
+        map.put("open-api", getOpenApi());
+        return map;
+    }
+
+    private Map<String, Object> getOpenApi() {
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("spec", servletContext.getContextPath() + "/swagger.json");
+        return map;
+    }
+
+    private Map<String, Object> getServerTime() {
+        final ZonedDateTime now = ZonedDateTime.now();
+        final String dateFormatted = now.format(DateTimeFormatter.ISO_INSTANT);
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("timestamp", new Date().getTime());
+        map.put("iso8601", dateFormatted);
+        return map;
+    }
+
+    /**
+     * Does the slightly tedious task of reading the software version from
+     * META-INF based on maven metadata.
+     * 
+     * @return
+     */
+    private String getVersion() {
+        final String groupId = "org.apache.metamodel.membrane";
+        final String artifactId = "Membrane-core";
+        final String resourcePath = "/META-INF/maven/" + groupId + "/" + artifactId + "/pom.properties";
+        final Properties properties = new Properties();
+        try (final InputStream inputStream = RootInformationController.class.getResourceAsStream(resourcePath)) {
+            properties.load(inputStream);
+        } catch (Exception e) {
+            logger.error("Failed to load version from manifest: " + e.getMessage());
+        }
+
+        final String version = properties.getProperty("version", "UNKNOWN");
+        return version;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/SchemaController.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/SchemaController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/SchemaController.java
new file mode 100644
index 0000000..0332138
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/SchemaController.java
@@ -0,0 +1,80 @@
+/**
+ * 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.membrane.controllers;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.membrane.app.DataContextTraverser;
+import org.apache.metamodel.membrane.app.TenantContext;
+import org.apache.metamodel.membrane.app.TenantRegistry;
+import org.apache.metamodel.membrane.controllers.model.RestLink;
+import org.apache.metamodel.schema.Schema;
+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}",
+        "/{tenant}/{dataContext}/s/{schema}" }, produces = MediaType.APPLICATION_JSON_VALUE)
+public class SchemaController {
+
+    private final TenantRegistry tenantRegistry;
+
+    @Autowired
+    public SchemaController(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) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+        final DataContextTraverser traverser = new DataContextTraverser(dataContext);
+
+        final Schema schema = traverser.getSchema(schemaId);
+        final String tenantName = tenantContext.getTenantName();
+        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}/t/{table}");
+
+        final String schemaName = schema.getName();
+        final List<RestLink> tableLinks = Arrays.stream(schema.getTableNames()).map(t -> new RestLink(String.valueOf(t),
+                uriBuilder.build(tenantName, dataSourceName, schemaName, t))).collect(Collectors.toList());
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "schema");
+        map.put("name", schemaName);
+        map.put("datasource", dataSourceName);
+        map.put("tenant", tenantName);
+        map.put("tables", tableLinks);
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/6492107d/core/src/main/java/org/apache/metamodel/membrane/controllers/SwaggerSpecController.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/SwaggerSpecController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/SwaggerSpecController.java
new file mode 100644
index 0000000..a01ef2f
--- /dev/null
+++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/SwaggerSpecController.java
@@ -0,0 +1,64 @@
+/**
+ * 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.membrane.controllers;
+
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.metamodel.util.FileHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+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;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+
+@RestController
+public class SwaggerSpecController {
+
+    @Autowired
+    ServletContext servletContext;
+    
+    @RequestMapping(method = RequestMethod.GET, value = "/swagger.json", produces = MediaType.APPLICATION_JSON_VALUE)
+    @ResponseBody
+    public Map<String, Object> getSwaggerJson(HttpServletRequest req) throws Exception {
+        final String yaml;
+        try (final InputStream resource = getClass().getResourceAsStream("/swagger.yaml")) {
+            yaml = FileHelper.readInputStreamAsString(resource, FileHelper.UTF_8_ENCODING);
+        }
+
+        final ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
+        @SuppressWarnings("unchecked")
+        final Map<String, Object> map = yamlReader.readValue(yaml, Map.class);
+
+        // add the base path, scheme and host
+        map.put("basePath", servletContext.getContextPath());
+        map.put("host", req.getServerName() + ":" + req.getServerPort());
+        map.put("schemes", Arrays.asList(req.getScheme()));
+
+        return map;
+    }
+}


[6/6] metamodel-membrane git commit: Merge branch 'initial-codebase' of https://git-wip-us.apache.org/repos/asf/metamodel-membrane.git into initial-codebase

Posted by ka...@apache.org.
Merge branch 'initial-codebase' of https://git-wip-us.apache.org/repos/asf/metamodel-membrane.git into initial-codebase

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

Branch: refs/heads/master
Commit: d5419cb34f2cc4e2bc0cfca7be3b80ddc0b7bd48
Parents: e02d105 65320cc
Author: Kasper Sørensen <i....@gmail.com>
Authored: Sun Jul 30 19:22:37 2017 -0700
Committer: Kasper Sørensen <i....@gmail.com>
Committed: Sun Jul 30 19:22:37 2017 -0700

----------------------------------------------------------------------
 core/pom.xml                                            |  2 +-
 .../membrane/app/CachedDataSourceRegistryWrapper.java   | 12 ++++++------
 .../controllers/model/RestDataSourceDefinition.java     |  2 +-
 .../membrane/controllers/model/RestErrorResponse.java   | 10 +++++-----
 .../metamodel/membrane/controllers/model/RestLink.java  |  7 +++----
 undertow/pom.xml                                        |  2 +-
 war/pom.xml                                             |  2 +-
 7 files changed, 18 insertions(+), 19 deletions(-)
----------------------------------------------------------------------