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/23 05:41:39 UTC
[2/2] metamodel-membrane git commit: Added initial codebase,
adapted from MetaModel REST service branch
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/initial-codebase
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;
+ }
+}