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 <[email protected]> Authored: Sat Jul 22 22:22:41 2017 -0700 Committer: Kasper Sørensen <[email protected]> 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; + } +}
