RESTful MetaModel service-webapp. Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/6d197c36 Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/6d197c36 Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/6d197c36
Branch: refs/heads/feature/5.x/rest Commit: 6d197c360398438e7ac3b894cd7a24b4ec3df4ce Parents: 02397db Author: Kasper Sørensen <[email protected]> Authored: Thu Aug 4 23:02:34 2016 -0700 Committer: Kasper Sørensen <[email protected]> Committed: Thu Aug 4 23:02:34 2016 -0700 ---------------------------------------------------------------------- .../factory/DataContextPropertiesImpl.java | 3 + .../apache/metamodel/pojo/PojoDataContext.java | 4 +- .../metamodel/pojo/PojoDataContextFactory.java | 71 +++ ....apache.metamodel.factory.DataContextFactory | 1 + pom.xml | 38 +- service-webapp/Dockerfile | 32 ++ service-webapp/README.md | 12 + service-webapp/pom.xml | 98 ++++ .../src/main/docker/swagger-ui/index.html | 94 ++++ .../app/CachedDataSourceRegistryWrapper.java | 109 ++++ .../service/app/DataContextSupplier.java | 48 ++ .../service/app/DataContextTraverser.java | 67 +++ .../service/app/DataSourceDefinition.java | 28 + .../service/app/DataSourceRegistry.java | 48 ++ .../service/app/InMemoryDataSourceRegistry.java | 65 +++ .../service/app/InMemoryTenantContext.java | 41 ++ .../service/app/InMemoryTenantRegistry.java | 74 +++ .../metamodel/service/app/TenantContext.java | 31 ++ .../metamodel/service/app/TenantRegistry.java | 38 ++ .../AbstractIdentifierNamingException.java | 40 ++ .../DataSourceAlreadyExistException.java | 28 + .../DataSourceNotUpdateableException.java | 37 ++ .../app/exceptions/NoSuchColumnException.java | 29 + .../exceptions/NoSuchDataSourceException.java | 29 + .../app/exceptions/NoSuchSchemaException.java | 29 + .../app/exceptions/NoSuchTableException.java | 29 + .../app/exceptions/NoSuchTenantException.java | 28 + .../exceptions/TenantAlreadyExistException.java | 28 + .../service/controllers/ColumnController.java | 86 +++ .../controllers/DataSourceController.java | 104 ++++ .../service/controllers/QueryController.java | 85 +++ .../service/controllers/RestErrorHandler.java | 160 ++++++ .../controllers/RootInformationController.java | 102 ++++ .../service/controllers/SchemaController.java | 80 +++ .../controllers/SwaggerSpecController.java | 64 +++ .../service/controllers/TableController.java | 85 +++ .../controllers/TableDataController.java | 115 ++++ .../service/controllers/TenantController.java | 94 ++++ .../model/RestDataSourceDefinition.java | 55 ++ .../controllers/model/RestErrorResponse.java | 80 +++ .../service/controllers/model/RestLink.java | 60 +++ .../resources/context/application-context.xml | 34 ++ service-webapp/src/main/resources/logback.xml | 33 ++ service-webapp/src/main/resources/swagger.yaml | 538 +++++++++++++++++++ .../main/webapp/WEB-INF/dispatcher-servlet.xml | 59 ++ service-webapp/src/main/webapp/WEB-INF/web.xml | 70 +++ .../RootInformationControllerTest.java | 61 +++ .../TenantInteractionScenarioTest.java | 195 +++++++ spring/pom.xml | 12 - 49 files changed, 3337 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java b/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java index e66a811..13ce9c1 100644 --- a/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java +++ b/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java @@ -288,6 +288,9 @@ public class DataContextPropertiesImpl implements DataContextProperties { @Override public SimpleTableDef[] getTableDefs() { final Object obj = get(PROPERTY_TABLE_DEFS); + if (obj == null) { + return null; + } if (obj instanceof SimpleTableDef[]) { return (SimpleTableDef[]) obj; } http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java ---------------------------------------------------------------------- diff --git a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java index 9369e96..340228f 100644 --- a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java +++ b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java @@ -49,6 +49,8 @@ import org.apache.metamodel.util.SimpleTableDef; public class PojoDataContext extends QueryPostprocessDataContext implements UpdateableDataContext, Serializable { private static final long serialVersionUID = 1L; + + public static final String DEFAULT_SCHEMA_NAME = "Schema"; private final Map<String, TableDataProvider<?>> _tables; private final String _schemaName; @@ -68,7 +70,7 @@ public class PojoDataContext extends QueryPostprocessDataContext implements Upda * @param tables */ public PojoDataContext(List<TableDataProvider<?>> tables) { - this("Schema", tables); + this(DEFAULT_SCHEMA_NAME, tables); } /** http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java ---------------------------------------------------------------------- diff --git a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java new file mode 100644 index 0000000..35842bf --- /dev/null +++ b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.pojo; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.metamodel.ConnectionException; +import org.apache.metamodel.DataContext; +import org.apache.metamodel.factory.DataContextFactory; +import org.apache.metamodel.factory.DataContextProperties; +import org.apache.metamodel.factory.ResourceFactoryRegistry; +import org.apache.metamodel.factory.UnsupportedDataContextPropertiesException; +import org.apache.metamodel.util.SimpleTableDef; + +public class PojoDataContextFactory implements DataContextFactory { + + public static final String PROPERTY_TYPE = "pojo"; + + @Override + public boolean accepts(DataContextProperties properties, ResourceFactoryRegistry resourceFactoryRegistry) { + return PROPERTY_TYPE.equals(properties.getDataContextType()); + } + + @Override + public DataContext create(DataContextProperties properties, ResourceFactoryRegistry resourceFactoryRegistry) + throws UnsupportedDataContextPropertiesException, ConnectionException { + + assert accepts(properties, resourceFactoryRegistry); + + final String schemaName; + if (properties.getDatabaseName() != null) { + schemaName = properties.getDatabaseName(); + } else { + schemaName = "Schema"; + } + + final List<TableDataProvider<?>> tableDataProviders; + + final SimpleTableDef[] tableDefs = properties.getTableDefs(); + if (tableDefs == null) { + tableDataProviders = new ArrayList<>(); + } else { + tableDataProviders = new ArrayList<>(tableDefs.length); + for (int i = 0; i < tableDefs.length; i++) { + final TableDataProvider<?> tableDataProvider = new ArrayTableDataProvider(tableDefs[i], + new ArrayList<Object[]>()); + tableDataProviders.add(tableDataProvider); + } + } + + return new PojoDataContext(schemaName, tableDataProviders); + } + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory ---------------------------------------------------------------------- diff --git a/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory b/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory new file mode 100644 index 0000000..76f808d --- /dev/null +++ b/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory @@ -0,0 +1 @@ +org.apache.metamodel.pojo.PojoDataContextFactory \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 1df4cc0..51f8b67 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,7 @@ under the License. <hadoop.version>2.6.0</hadoop.version> <jackson.version>2.6.3</jackson.version> <easymock.version>3.2</easymock.version> + <spring.version>4.2.6.RELEASE</spring.version> <httpcomponents.version>4.4.1</httpcomponents.version> <checksum-maven-plugin.version>1.2</checksum-maven-plugin.version> <skipTests>false</skipTests> @@ -77,6 +78,7 @@ under the License. <module>full</module> <module>spring</module> <module>neo4j</module> + <module>service-webapp</module> </modules> <issueManagement> <system>Jira</system> @@ -370,7 +372,7 @@ under the License. <excludeSubProjects>false</excludeSubProjects> <excludes> <exclude>KEYS</exclude> - <exclude>*.md</exclude> + <exclude>**/*.md</exclude> <exclude>example-metamodel-integrationtest-configuration.properties</exclude> <exclude>travis-metamodel-integrationtest-configuration.properties</exclude> <exclude>**/src/assembly/metamodel-packaged-assembly-descriptor.xml</exclude> @@ -389,6 +391,7 @@ under the License. <exclude>**/*.ipr/**</exclude> <exclude>**/.idea/**</exclude> <exclude>**/tattletale-filters.properties</exclude> + <exclude>**/swagger-ui/**</exclude> <exclude>DEPENDENCIES</exclude> <exclude>DISCLAIMER</exclude> <exclude>neo4j-community-*/**</exclude> @@ -511,6 +514,11 @@ under the License. <version>${jackson.version}</version> </dependency> <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-yaml</artifactId> + <version>${jackson.version}</version> + </dependency> + <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>${jackson.version}</version> @@ -535,6 +543,34 @@ under the License. <artifactId>hsqldb</artifactId> <version>1.8.0.10</version> </dependency> + + <!-- Spring --> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-core</artifactId> + <version>${spring.version}</version> + <exclusions> + <exclusion> + <groupId>commons-logging</groupId> + <artifactId>commons-logging</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-context</artifactId> + <version>${spring.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-test</artifactId> + <version>${spring.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-webmvc</artifactId> + <version>${spring.version}</version> + </dependency> <!-- Hadoop --> <dependency> http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/Dockerfile ---------------------------------------------------------------------- diff --git a/service-webapp/Dockerfile b/service-webapp/Dockerfile new file mode 100644 index 0000000..4734f19 --- /dev/null +++ b/service-webapp/Dockerfile @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +FROM tomcat:8.0-jre8 + +RUN rm -rf $CATALINA_HOME/webapps \ + mkdir $CATALINA_HOME/webapps +COPY target/MetaModel.war $CATALINA_HOME/webapps/ROOT.war + +EXPOSE 8080 + +ENV SWAGGER_UI_VERSION 2.1.5 +RUN wget -q -O - https://github.com/swagger-api/swagger-ui/archive/v$SWAGGER_UI_VERSION.tar.gz | tar -xzf - -C /tmp \ + && mv /tmp/swagger-ui-$SWAGGER_UI_VERSION/dist $CATALINA_HOME/webapps/swagger-ui \ + && rm -r /tmp/swagger-ui-$SWAGGER_UI_VERSION +ADD src/main/docker/swagger-ui/index.html $CATALINA_HOME/webapps/swagger-ui + +CMD ["catalina.sh", "run"] http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/README.md ---------------------------------------------------------------------- diff --git a/service-webapp/README.md b/service-webapp/README.md new file mode 100644 index 0000000..20bca99 --- /dev/null +++ b/service-webapp/README.md @@ -0,0 +1,12 @@ +# MetaModel-as-a-service + +This is a web application that allows you to access MetaModel's unified API for datastore exploration and querying - using a set of RESTful services. + +## Docker building and running + +``` +docker build -t metamodel-service . +docker run --rm -p 8080:8080 metamodel-service +``` + +And then go to http://localhost:8080 (assuming localhost is your docker machine). http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/pom.xml ---------------------------------------------------------------------- diff --git a/service-webapp/pom.xml b/service-webapp/pom.xml new file mode 100644 index 0000000..fa56340 --- /dev/null +++ b/service-webapp/pom.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>MetaModel</artifactId> + <groupId>org.apache.metamodel</groupId> + <version>5.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <artifactId>MetaModel-service-webapp</artifactId> + <name>MetaModel-asâa-service web application</name> + <packaging>war</packaging> + + <build> + <finalName>MetaModel</finalName> + </build> + + <dependencies> + <dependency> + <groupId>org.apache.metamodel</groupId> + <artifactId>MetaModel-spring</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.metamodel</groupId> + <artifactId>MetaModel-full</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-webmvc</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-yaml</artifactId> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <version>1.1.7</version> + </dependency> + <dependency> + <groupId>org.hibernate</groupId> + <artifactId>hibernate-validator</artifactId> + <version>5.2.4.Final</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>jcl-over-slf4j</artifactId> + </dependency> + + <!-- Provided --> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <version>3.1.0</version> + <scope>provided</scope> + </dependency> + + <!-- Test --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-test</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/docker/swagger-ui/index.html ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/docker/swagger-ui/index.html b/service-webapp/src/main/docker/swagger-ui/index.html new file mode 100755 index 0000000..16df339 --- /dev/null +++ b/service-webapp/src/main/docker/swagger-ui/index.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>Swagger UI</title> + <link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32" /> + <link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16" /> + <link href='css/typography.css' media='screen' rel='stylesheet' type='text/css'/> + <link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/> + <link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/> + <link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/> + <link href='css/print.css' media='print' rel='stylesheet' type='text/css'/> + + <script src='lib/object-assign-pollyfill.js' type='text/javascript'></script> + <script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script> + <script src='lib/jquery.slideto.min.js' type='text/javascript'></script> + <script src='lib/jquery.wiggle.min.js' type='text/javascript'></script> + <script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script> + <script src='lib/handlebars-2.0.0.js' type='text/javascript'></script> + <script src='lib/lodash.min.js' type='text/javascript'></script> + <script src='lib/backbone-min.js' type='text/javascript'></script> + <script src='swagger-ui.js' type='text/javascript'></script> + <script src='lib/highlight.9.1.0.pack.js' type='text/javascript'></script> + <script src='lib/highlight.9.1.0.pack_extended.js' type='text/javascript'></script> + <script src='lib/jsoneditor.min.js' type='text/javascript'></script> + <script src='lib/marked.js' type='text/javascript'></script> + <script src='lib/swagger-oauth.js' type='text/javascript'></script> + + <!-- Some basic translations --> + <!-- <script src='lang/translator.js' type='text/javascript'></script> --> + <!-- <script src='lang/ru.js' type='text/javascript'></script> --> + <!-- <script src='lang/en.js' type='text/javascript'></script> --> + + <script type="text/javascript"> + $(function () { + var url = window.location.search.match(/url=([^&]+)/); + if (url && url.length > 1) { + url = decodeURIComponent(url[1]); + } else { + url = "../../swagger.json"; + } + + hljs.configure({ + highlightSizeThreshold: 5000 + }); + + // Pre load translate... + if(window.SwaggerTranslator) { + window.SwaggerTranslator.translate(); + } + window.swaggerUi = new SwaggerUi({ + url: url, + dom_id: "swagger-ui-container", + supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'], + onComplete: function(swaggerApi, swaggerUi){ + if(typeof initOAuth == "function") { + initOAuth({ + clientId: "your-client-id", + clientSecret: "your-client-secret-if-required", + realm: "your-realms", + appName: "your-app-name", + scopeSeparator: ",", + additionalQueryStringParams: {} + }); + } + + if(window.SwaggerTranslator) { + window.SwaggerTranslator.translate(); + } + }, + onFailure: function(data) { + log("Unable to Load SwaggerUI"); + }, + docExpansion: "none", + jsonEditor: false, + defaultModelRendering: 'schema', + showRequestHeaders: false + }); + + window.swaggerUi.load(); + + function log() { + if ('console' in window) { + console.log.apply(console, arguments); + } + } + }); + </script> +</head> + +<body class="swagger-section"> +<div id="swagger-ui-container" class="swagger-ui-wrap"></div> +</body> +</html> http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java new file mode 100644 index 0000000..fffe83d --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java @@ -0,0 +1,109 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.MetaModelException; +import org.apache.metamodel.factory.DataContextProperties; +import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException; +import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException; +import org.apache.metamodel.util.FileHelper; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.cache.RemovalListener; +import com.google.common.cache.RemovalNotification; + +/** + * A wrapper that adds a cache around a {@link DataSourceRegistry} in order to + * prevent re-connecting all the time to the same data source. + */ +public class CachedDataSourceRegistryWrapper implements DataSourceRegistry { + + /** + * The default timeout (in seconds) before the cache evicts and closes the + * created {@link DataContext}s. + */ + public static final int DEFAULT_TIMEOUT_SECONDS = 60; + + private final DataSourceRegistry delegate; + private final LoadingCache<String, DataContext> loadingCache; + + public CachedDataSourceRegistryWrapper(DataSourceRegistry delegate) { + this(delegate, DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + + public CachedDataSourceRegistryWrapper(DataSourceRegistry delegate, long cacheTimeout, TimeUnit cacheTimeoutUnit) { + this.delegate = delegate; + this.loadingCache = CacheBuilder.newBuilder().expireAfterAccess(cacheTimeout, cacheTimeoutUnit).removalListener( + createRemovalListener()).build(createCacheLoader()); + } + + private RemovalListener<String, DataContext> createRemovalListener() { + return new RemovalListener<String, DataContext>() { + @Override + public void onRemoval(RemovalNotification<String, DataContext> notification) { + final DataContext dataContext = notification.getValue(); + // some DataContexts are closeable - attempt closing it here + FileHelper.safeClose(dataContext); + } + }; + } + + private CacheLoader<String, DataContext> createCacheLoader() { + return new CacheLoader<String, DataContext>() { + @Override + public DataContext load(String key) throws Exception { + return delegate.openDataContext(key); + } + }; + } + + @Override + public List<String> getDataSourceNames() { + return delegate.getDataSourceNames(); + } + + @Override + public String registerDataSource(String dataContextName, DataContextProperties dataContextProperties) + throws DataSourceAlreadyExistException { + loadingCache.invalidate(dataContextName); + return delegate.registerDataSource(dataContextName, dataContextProperties); + } + + @Override + public DataContext openDataContext(String dataSourceName) throws NoSuchDataSourceException { + try { + return loadingCache.get(dataSourceName); + } catch (ExecutionException e) { + final Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + throw new MetaModelException("Unexpected error happened while getting DataContext '" + dataSourceName + + "' from cache", e); + } + } + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java new file mode 100644 index 0000000..2f42c6f --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app; + +import java.util.function.Supplier; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.factory.DataContextFactoryRegistryImpl; +import org.apache.metamodel.factory.DataContextProperties; + +public class DataContextSupplier implements Supplier<DataContext> { + + private final String dataSourceName; + private final DataContextProperties dataContextProperties; + + public DataContextSupplier(String dataSourceName, DataContextProperties dataContextProperties) { + this.dataSourceName = dataSourceName; + this.dataContextProperties = dataContextProperties; + } + + @Override + public DataContext get() { + final DataContext dataContext = DataContextFactoryRegistryImpl.getDefaultInstance().createDataContext( + dataContextProperties); + return dataContext; + } + + @Override + public String toString() { + return "DataContextSupplier[" + dataSourceName + "]"; + } +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java new file mode 100644 index 0000000..6ec7122 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.schema.Column; +import org.apache.metamodel.schema.Schema; +import org.apache.metamodel.schema.Table; +import org.apache.metamodel.service.app.exceptions.NoSuchColumnException; +import org.apache.metamodel.service.app.exceptions.NoSuchSchemaException; +import org.apache.metamodel.service.app.exceptions.NoSuchTableException; + +/** + * Utility object responsible for traversing the schema/table/column structures + * of a {@link DataContext} based on String identifiers and names. + * + * This class will throw the appropriate exceptions if needed which is more + * communicative than the usual NPEs that would otherwise be thrown. + */ +public class DataContextTraverser { + + private final DataContext dataContext; + + public DataContextTraverser(DataContext dataContext) { + this.dataContext = dataContext; + } + + public Schema getSchema(String schemaName) { + final Schema schema = dataContext.getSchemaByName(schemaName); + if (schema == null) { + throw new NoSuchSchemaException(schemaName); + } + return schema; + } + + public Table getTable(String schemaName, String tableName) { + final Table table = getSchema(schemaName).getTableByName(tableName); + if (table == null) { + throw new NoSuchTableException(tableName); + } + return table; + } + + public Column getColumn(String schemaName, String tableName, String columnName) { + final Column column = getTable(schemaName, tableName).getColumnByName(columnName); + if (column == null) { + throw new NoSuchColumnException(columnName); + } + return column; + } +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java new file mode 100644 index 0000000..2aaa8e5 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app; + +import java.util.Map; + +public interface DataSourceDefinition { + + public String getType(); + + public Map<String, Object> getProperties(); +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java new file mode 100644 index 0000000..e0c8697 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app; + +import java.util.List; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.UpdateableDataContext; +import org.apache.metamodel.factory.DataContextProperties; +import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException; +import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException; +import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException; + +/** + * Represents a user's/tenant's registry of {@link DataContext}s. + */ +public interface DataSourceRegistry { + + public List<String> getDataSourceNames(); + + public String registerDataSource(String dataContextName, DataContextProperties dataContextProperties) throws DataSourceAlreadyExistException; + + public DataContext openDataContext(String dataSourceName) throws NoSuchDataSourceException; + + public default UpdateableDataContext openDataContextForUpdate(String dataSourceName) { + final DataContext dataContext = openDataContext(dataSourceName); + if (dataContext instanceof UpdateableDataContext) { + return (UpdateableDataContext) dataContext; + } + throw new DataSourceNotUpdateableException(dataSourceName); + }; +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java new file mode 100644 index 0000000..386232a --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.factory.DataContextProperties; +import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException; +import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException; + +public class InMemoryDataSourceRegistry implements DataSourceRegistry { + + private final Map<String, Supplier<DataContext>> dataSources; + + public InMemoryDataSourceRegistry() { + dataSources = new LinkedHashMap<>(); + } + + @Override + public String registerDataSource(final String name, final DataContextProperties dataContextProperties) + throws DataSourceAlreadyExistException { + if (dataSources.containsKey(name)) { + throw new DataSourceAlreadyExistException(name); + } + + dataSources.put(name, new DataContextSupplier(name, dataContextProperties)); + return name; + } + + @Override + public List<String> getDataSourceNames() { + return dataSources.keySet().stream().collect(Collectors.toList()); + } + + @Override + public DataContext openDataContext(String name) { + final Supplier<DataContext> supplier = dataSources.get(name); + if (supplier == null) { + throw new NoSuchDataSourceException(name); + } + return supplier.get(); + } + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java new file mode 100644 index 0000000..022ab28 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app; + +public class InMemoryTenantContext implements TenantContext { + + private final String tenantIdentifier; + private final DataSourceRegistry dataContextRegistry; + + public InMemoryTenantContext(String tenantIdentifier) { + this.tenantIdentifier = tenantIdentifier; + this.dataContextRegistry = new CachedDataSourceRegistryWrapper(new InMemoryDataSourceRegistry()); + } + + @Override + public String getTenantName() { + return tenantIdentifier; + } + + @Override + public DataSourceRegistry getDataSourceRegistry() { + return dataContextRegistry; + } + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java new file mode 100644 index 0000000..6ac5bec --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.metamodel.service.app.exceptions.NoSuchTenantException; +import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException; + +/** + * In-memory {@link TenantRegistry}. This is not particularly + * production-friendly as it is non-persistent, but it is useful for demo + * purposes. + */ +public class InMemoryTenantRegistry implements TenantRegistry { + + private final Map<String, TenantContext> tenants; + + public InMemoryTenantRegistry() { + tenants = new LinkedHashMap<>(); + } + + @Override + public List<String> getTenantIdentifiers() { + return tenants.keySet().stream().collect(Collectors.toList()); + } + + @Override + public TenantContext getTenantContext(String tenantIdentifier) { + final TenantContext tenant = tenants.get(tenantIdentifier); + if (tenant == null) { + throw new NoSuchTenantException(tenantIdentifier); + } + return tenant; + } + + @Override + public TenantContext createTenantContext(String tenantIdentifier) { + if (tenants.containsKey(tenantIdentifier)) { + throw new TenantAlreadyExistException(tenantIdentifier); + } + final InMemoryTenantContext tenantContext = new InMemoryTenantContext(tenantIdentifier); + tenants.put(tenantIdentifier, tenantContext); + return tenantContext; + } + + @Override + public void deleteTenantContext(String tenantIdentifier) { + final TenantContext removedTenant = tenants.remove(tenantIdentifier); + if (removedTenant == null) { + throw new NoSuchTenantException(tenantIdentifier); + } + } + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java new file mode 100644 index 0000000..c550a60 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app; + +/** + * Represents a context-object containing all the information and services + * related to a particular tenant. + */ +public interface TenantContext { + + public String getTenantName(); + + public DataSourceRegistry getDataSourceRegistry(); + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java new file mode 100644 index 0000000..5c02821 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app; + +import java.util.List; + +import org.apache.metamodel.service.app.exceptions.NoSuchTenantException; +import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException; + +/** + * Represents the application's central registry of tenants + */ +public interface TenantRegistry { + + public List<String> getTenantIdentifiers(); + + public TenantContext getTenantContext(String tenantIdentifier) throws NoSuchTenantException; + + public TenantContext createTenantContext(String tenantIdentifier) throws TenantAlreadyExistException; + + public void deleteTenantContext(String tenantIdentifier) throws NoSuchTenantException; +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java new file mode 100644 index 0000000..98f5892 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app.exceptions; + +import org.apache.metamodel.MetaModelException; + +/** + * Exception super class for any exception that arises because an identifier + * (name, ID or such) is invalid for a specific context. + */ +public class AbstractIdentifierNamingException extends MetaModelException { + + private static final long serialVersionUID = 1L; + private final String identifier; + + public AbstractIdentifierNamingException(String identifier) { + super("Illegal value: " + identifier); + this.identifier = identifier; + } + + public String getIdentifier() { + return identifier; + } +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java new file mode 100644 index 0000000..3d5d650 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app.exceptions; + +public class DataSourceAlreadyExistException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public DataSourceAlreadyExistException(String name) { + super(name); + } +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java new file mode 100644 index 0000000..eb828cc --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java @@ -0,0 +1,37 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app.exceptions; + +import org.apache.metamodel.MetaModelException; + +public class DataSourceNotUpdateableException extends MetaModelException { + + private static final long serialVersionUID = 1L; + + private final String dataSourceName; + + public DataSourceNotUpdateableException(String dataSourceName) { + super(dataSourceName); + this.dataSourceName = dataSourceName; + } + + public String getDataSourceName() { + return dataSourceName; + } +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java new file mode 100644 index 0000000..1902af1 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app.exceptions; + +public class NoSuchColumnException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public NoSuchColumnException(String name) { + super(name); + } + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java new file mode 100644 index 0000000..b59f016 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app.exceptions; + +public class NoSuchDataSourceException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public NoSuchDataSourceException(String name) { + super(name); + } + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java new file mode 100644 index 0000000..5b238a8 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app.exceptions; + +public class NoSuchSchemaException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public NoSuchSchemaException(String name) { + super(name); + } + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java new file mode 100644 index 0000000..73141cd --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app.exceptions; + +public class NoSuchTableException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public NoSuchTableException(String name) { + super(name); + } + +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java new file mode 100644 index 0000000..b123ae2 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app.exceptions; + +public class NoSuchTenantException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public NoSuchTenantException(String tenantIdentifier) { + super(tenantIdentifier); + } +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java new file mode 100644 index 0000000..f24c114 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.app.exceptions; + +public class TenantAlreadyExistException extends AbstractIdentifierNamingException { + + private static final long serialVersionUID = 1L; + + public TenantAlreadyExistException(String tenantIdentifier) { + super(tenantIdentifier); + } +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java new file mode 100644 index 0000000..c811def --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java @@ -0,0 +1,86 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.controllers; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.schema.Column; +import org.apache.metamodel.service.app.DataContextTraverser; +import org.apache.metamodel.service.app.TenantContext; +import org.apache.metamodel.service.app.TenantRegistry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = { "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}/columns/{column}", + "/{tenant}/{dataContext}/s/{schema}/t/{table}/c/{column}" }, produces = MediaType.APPLICATION_JSON_VALUE) +public class ColumnController { + + private final TenantRegistry tenantRegistry; + + @Autowired + public ColumnController(TenantRegistry tenantRegistry) { + this.tenantRegistry = tenantRegistry; + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map<String, Object> get(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId, + @PathVariable("table") String tableId, @PathVariable("column") String columnId) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); + + final DataContextTraverser traverser = new DataContextTraverser(dataContext); + + final Column column = traverser.getColumn(schemaId, tableId, columnId); + + final String tenantName = tenantContext.getTenantName(); + final String tableName = column.getTable().getName(); + final String schemaName = column.getTable().getSchema().getName(); + + final Map<String, Object> metadata = new LinkedHashMap<>(); + metadata.put("number", column.getColumnNumber()); + metadata.put("size", column.getColumnSize()); + metadata.put("nullable", column.isNullable()); + metadata.put("primary-key", column.isPrimaryKey()); + metadata.put("indexed", column.isIndexed()); + metadata.put("column-type", column.getType() == null ? null : column.getType().getName()); + metadata.put("native-type", column.getNativeType()); + metadata.put("remarks", column.getRemarks()); + + final Map<String, Object> map = new LinkedHashMap<>(); + map.put("type", "column"); + map.put("name", column.getName()); + map.put("table", tableName); + map.put("schema", schemaName); + map.put("datasource", dataSourceName); + map.put("tenant", tenantName); + map.put("metadata", metadata); + + return map; + } +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java new file mode 100644 index 0000000..6dfe2d0 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java @@ -0,0 +1,104 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.controllers; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.validation.Valid; +import javax.ws.rs.core.UriBuilder; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.UpdateableDataContext; +import org.apache.metamodel.factory.DataContextProperties; +import org.apache.metamodel.factory.DataContextPropertiesImpl; +import org.apache.metamodel.service.app.TenantContext; +import org.apache.metamodel.service.app.TenantRegistry; +import org.apache.metamodel.service.controllers.model.RestDataSourceDefinition; +import org.apache.metamodel.service.controllers.model.RestLink; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/{tenant}/{datasource}", produces = MediaType.APPLICATION_JSON_VALUE) +public class DataSourceController { + + private final TenantRegistry tenantRegistry; + + @Autowired + public DataSourceController(TenantRegistry tenantRegistry) { + this.tenantRegistry = tenantRegistry; + } + + @RequestMapping(method = RequestMethod.PUT) + @ResponseBody + public Map<String, Object> put(@PathVariable("tenant") String tenantId, + @PathVariable("datasource") String dataSourceId, + @Valid @RequestBody RestDataSourceDefinition dataContextDefinition) { + + final Map<String, Object> map = new HashMap<>(); + map.putAll(dataContextDefinition.getProperties()); + map.put(DataContextPropertiesImpl.PROPERTY_DATA_CONTEXT_TYPE, dataContextDefinition.getType()); + + if (!map.containsKey(DataContextPropertiesImpl.PROPERTY_DATABASE)) { + // add the data source ID as database name if it is not already set. + map.put(DataContextPropertiesImpl.PROPERTY_DATABASE, dataSourceId); + } + + final DataContextProperties properties = new DataContextPropertiesImpl(map); + + final String dataContextIdentifier = tenantRegistry.getTenantContext(tenantId).getDataSourceRegistry() + .registerDataSource(dataSourceId, properties); + + return get(tenantId, dataContextIdentifier); + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map<String, Object> get(@PathVariable("tenant") String tenantId, + @PathVariable("datasource") String dataSourceName) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); + + final String tenantName = tenantContext.getTenantName(); + final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}"); + + final List<RestLink> schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s, + uriBuilder.build(tenantName, dataSourceName, s))).collect(Collectors.toList()); + + final Map<String, Object> map = new LinkedHashMap<>(); + map.put("type", "datasource"); + map.put("name", dataSourceName); + map.put("tenant", tenantName); + map.put("updateable", dataContext instanceof UpdateableDataContext); + map.put("query_uri", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantName, dataSourceName)); + map.put("schemas", schemaLinks); + return map; + } +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java new file mode 100644 index 0000000..693bc4c --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java @@ -0,0 +1,85 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.controllers; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.data.DataSet; +import org.apache.metamodel.query.Query; +import org.apache.metamodel.service.app.TenantContext; +import org.apache.metamodel.service.app.TenantRegistry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = { "/{tenant}/{dataContext}/query", + "/{tenant}/{dataContext}/q" }, produces = MediaType.APPLICATION_JSON_VALUE) +public class QueryController { + + private final TenantRegistry tenantRegistry; + + @Autowired + public QueryController(TenantRegistry tenantRegistry) { + this.tenantRegistry = tenantRegistry; + } + + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public Map<String, Object> get(@PathVariable("tenant") String tenantId, + @PathVariable("dataContext") String dataSourceName, + @RequestParam(value = "sql", required = true) String queryString, + @RequestParam(value = "offset", required = false) Integer offset, + @RequestParam(value = "limit", required = false) Integer limit) { + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); + + final Query query = dataContext.parseQuery(queryString); + + return executeQuery(dataContext, query, offset, limit); + } + + public static Map<String, Object> executeQuery(DataContext dataContext, Query query, Integer offset, Integer limit) { + + if (offset != null) { + query.setFirstRow(offset); + } + if (limit != null) { + query.setMaxRows(limit); + } + + final DataSet dataSet = dataContext.executeQuery(query); + + final Map<String, Object> map = new LinkedHashMap<>(); + map.put("type", "dataset"); + map.put("header", Arrays.stream(dataSet.getSelectItems()).map((si) -> si.toString()).collect(Collectors + .toList())); + map.put("data", dataSet.toObjectArrays()); + return map; + } +} http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java ---------------------------------------------------------------------- diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java new file mode 100644 index 0000000..8a08151 --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java @@ -0,0 +1,160 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.metamodel.service.controllers; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.metamodel.query.parser.QueryParserException; +import org.apache.metamodel.service.app.exceptions.AbstractIdentifierNamingException; +import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException; +import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException; +import org.apache.metamodel.service.app.exceptions.NoSuchColumnException; +import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException; +import org.apache.metamodel.service.app.exceptions.NoSuchSchemaException; +import org.apache.metamodel.service.app.exceptions.NoSuchTableException; +import org.apache.metamodel.service.app.exceptions.NoSuchTenantException; +import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException; +import org.apache.metamodel.service.controllers.model.RestErrorResponse; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ControllerAdvice +public class RestErrorHandler { + + /** + * Method binding issues (raised by Spring framework) - mapped to + * BAD_REQUEST. + * + * @param ex + * @return + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public RestErrorResponse processValidationError(MethodArgumentNotValidException ex) { + final BindingResult result = ex.getBindingResult(); + + final Map<String, Object> globalErrorsMap = new LinkedHashMap<>(); + final List<ObjectError> globalErrors = result.getGlobalErrors(); + for (ObjectError objectError : globalErrors) { + globalErrorsMap.put(objectError.getObjectName(), objectError.getDefaultMessage()); + } + + final List<FieldError> fieldErrors = result.getFieldErrors(); + final Map<String, Object> fieldErrorsMap = new LinkedHashMap<>(); + for (FieldError fieldError : fieldErrors) { + fieldErrorsMap.put(fieldError.getObjectName() + '.' + fieldError.getField(), fieldError + .getDefaultMessage()); + } + + final Map<String, Object> additionalDetails = new LinkedHashMap<>(); + if (!globalErrorsMap.isEmpty()) { + additionalDetails.put("global-errors", globalErrorsMap); + } + if (!fieldErrorsMap.isEmpty()) { + additionalDetails.put("field-errors", fieldErrorsMap); + } + final RestErrorResponse errorResponse = new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), + "Failed to validate request"); + if (!additionalDetails.isEmpty()) { + errorResponse.setAdditionalDetails(additionalDetails); + } + return errorResponse; + } + + /** + * No such [Entity] exception handler method - mapped to NOT_FOUND. + * + * @param ex + * @return + */ + @ExceptionHandler({ NoSuchTenantException.class, NoSuchDataSourceException.class, NoSuchSchemaException.class, + NoSuchTableException.class, NoSuchColumnException.class }) + @ResponseStatus(HttpStatus.NOT_FOUND) + @ResponseBody + public RestErrorResponse processNoSuchEntity(AbstractIdentifierNamingException ex) { + return new RestErrorResponse(HttpStatus.NOT_FOUND.value(), "Not found: " + ex.getIdentifier()); + } + + /** + * [Entity] already exist exception handler method - mapped to CONFLICT. + * + * @param ex + * @return + */ + @ExceptionHandler({ TenantAlreadyExistException.class, DataSourceAlreadyExistException.class }) + @ResponseStatus(HttpStatus.CONFLICT) + @ResponseBody + public RestErrorResponse processEntityAlreadyExist(AbstractIdentifierNamingException ex) { + return new RestErrorResponse(HttpStatus.CONFLICT.value(), "Already exist: " + ex.getIdentifier()); + } + + /** + * DataSource not updateable exception handler method - mapped to + * BAD_REQUEST. + * + * @param ex + * @return + */ + @ExceptionHandler(DataSourceNotUpdateableException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public RestErrorResponse processDataSourceNotUpdateable(DataSourceNotUpdateableException ex) { + return new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), "DataSource not updateable: " + ex + .getDataSourceName()); + } + + /** + * Query parsing exception - mapped to BAD_REQUEST. + * + * @param ex + * @return + */ + @ExceptionHandler(QueryParserException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public RestErrorResponse processQueryParsingError(QueryParserException ex) { + return new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); + } + + /** + * Catch-all exception handler method - mapped to INTERNAL_SERVER_ERROR. + * + * @param ex + * @return + */ + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ResponseBody + public RestErrorResponse processAnyException(Exception ex) { + final Map<String, Object> additionalDetails = new HashMap<>(); + additionalDetails.put("exception_type", ex.getClass().getName()); + return new RestErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage(), additionalDetails); + } +}
