You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metamodel.apache.org by ka...@apache.org on 2016/08/05 06:02:43 UTC
[1/2] metamodel git commit: RESTful MetaModel service-webapp.
Repository: metamodel
Updated Branches:
refs/heads/feature/5.x/rest [created] 6d197c360
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RootInformationController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RootInformationController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RootInformationController.java
new file mode 100644
index 0000000..f2e95d5
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/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.service.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");
+ 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";
+ final String artifactId = "MetaModel-service-webapp";
+ final String resourcePath = "/META-INF/maven/" + groupId + "/" + artifactId + "/pom.properties";
+ final Properties properties = new Properties();
+ try (final InputStream inputStream = servletContext.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/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java
new file mode 100644
index 0000000..993c1d3
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/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.service.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.schema.Schema;
+import org.apache.metamodel.service.app.DataContextTraverser;
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+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.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/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SwaggerSpecController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SwaggerSpecController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SwaggerSpecController.java
new file mode 100644
index 0000000..1081519
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/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.service.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;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java
new file mode 100644
index 0000000..c914289
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.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.schema.Table;
+import org.apache.metamodel.service.app.DataContextTraverser;
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+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.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = { "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}",
+ "/{tenant}/{dataContext}/s/{schema}/t/{table}" }, produces = MediaType.APPLICATION_JSON_VALUE)
+public class TableController {
+
+ private final TenantRegistry tenantRegistry;
+
+ @Autowired
+ public TableController(TenantRegistry tenantRegistry) {
+ this.tenantRegistry = tenantRegistry;
+ }
+
+ @RequestMapping(method = RequestMethod.GET)
+ @ResponseBody
+ public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+ @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId,
+ @PathVariable("table") String tableId) {
+ final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+ final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+ final DataContextTraverser traverser = new DataContextTraverser(dataContext);
+
+ final Table table = traverser.getTable(schemaId, tableId);
+
+ final String tenantName = tenantContext.getTenantName();
+ final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}/t/{table}/c/{column}");
+
+ final String tableName = table.getName();
+ final String schemaName = table.getSchema().getName();
+ final List<RestLink> columnsLinks = Arrays.stream(table.getColumnNames()).map(c -> new RestLink(String.valueOf(
+ c), uriBuilder.build(tenantName, dataSourceName, schemaName, tableName, c))).collect(Collectors
+ .toList());
+
+ final Map<String, Object> map = new LinkedHashMap<>();
+ map.put("type", "table");
+ map.put("name", tableName);
+ map.put("schema", schemaName);
+ map.put("datasource", dataSourceName);
+ map.put("tenant", tenantName);
+ map.put("columns", columnsLinks);
+ return map;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java
new file mode 100644
index 0000000..bae9923
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.java
@@ -0,0 +1,115 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.UpdateCallback;
+import org.apache.metamodel.UpdateScript;
+import org.apache.metamodel.UpdateSummary;
+import org.apache.metamodel.UpdateableDataContext;
+import org.apache.metamodel.insert.RowInsertionBuilder;
+import org.apache.metamodel.query.Query;
+import org.apache.metamodel.schema.Table;
+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.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = { "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}/data",
+ "/{tenant}/{dataContext}/s/{schema}/t/{table}/d" }, produces = MediaType.APPLICATION_JSON_VALUE)
+public class TableDataController {
+
+ private final TenantRegistry tenantRegistry;
+
+ @Autowired
+ public TableDataController(TenantRegistry tenantRegistry) {
+ this.tenantRegistry = tenantRegistry;
+ }
+
+ @RequestMapping(method = RequestMethod.GET)
+ @ResponseBody
+ public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+ @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId,
+ @PathVariable("table") String tableId, @RequestParam(value = "offset", required = false) Integer offset,
+ @RequestParam(value = "limit", required = false) Integer limit) {
+ final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+ final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+ final DataContextTraverser traverser = new DataContextTraverser(dataContext);
+
+ final Table table = traverser.getTable(schemaId, tableId);
+
+ final Query query = dataContext.query().from(table).selectAll().toQuery();
+
+ return QueryController.executeQuery(dataContext, query, offset, limit);
+ }
+
+ @RequestMapping(method = RequestMethod.POST)
+ @ResponseBody
+ public Map<String, Object> post(@PathVariable("tenant") String tenantId,
+ @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId,
+ @PathVariable("table") String tableId, @RequestBody final List<Map<String, Object>> inputRecords) {
+
+ final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+ final UpdateableDataContext dataContext = tenantContext.getDataSourceRegistry().openDataContextForUpdate(dataSourceName);
+
+ final DataContextTraverser traverser = new DataContextTraverser(dataContext);
+
+ final Table table = traverser.getTable(schemaId, tableId);
+
+ final UpdateSummary result = dataContext.executeUpdate(new UpdateScript() {
+ @Override
+ public void run(UpdateCallback callback) {
+ for (Map<String, Object> inputMap : inputRecords) {
+ final RowInsertionBuilder insert = callback.insertInto(table);
+ for (Entry<String, Object> entry : inputMap.entrySet()) {
+ insert.value(entry.getKey(), entry.getValue());
+ }
+ insert.execute();
+ }
+ }
+ });
+
+ final Map<String, Object> response = new LinkedHashMap<>();
+ response.put("status", "ok");
+
+ if (result.getInsertedRows().isPresent()) {
+ response.put("inserted-rows", result.getInsertedRows().get());
+ }
+ if (result.getGeneratedKeys().isPresent()) {
+ response.put("generated-keys", result.getGeneratedKeys().get());
+ }
+
+ return response;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java
new file mode 100644
index 0000000..9582bbe
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java
@@ -0,0 +1,94 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+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.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = "/{tenant}", produces = MediaType.APPLICATION_JSON_VALUE)
+public class TenantController {
+
+ private final TenantRegistry tenantRegistry;
+
+ @Autowired
+ public TenantController(TenantRegistry tenantRegistry) {
+ this.tenantRegistry = tenantRegistry;
+ }
+
+ @RequestMapping(method = RequestMethod.GET)
+ @ResponseBody
+ public Map<String, Object> getTenant(@PathVariable("tenant") String tenantName) {
+ final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantName);
+ final String tenantNameNormalized = tenantContext.getTenantName();
+
+ final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{datasource}");
+
+ final List<String> dataContextIdentifiers = tenantContext.getDataSourceRegistry().getDataSourceNames();
+ final List<RestLink> dataSourceLinks = dataContextIdentifiers.stream().map(s -> new RestLink(s, uriBuilder
+ .build(tenantNameNormalized, s))).collect(Collectors.toList());
+
+ final Map<String, Object> map = new LinkedHashMap<>();
+ map.put("type", "tenant");
+ map.put("name", tenantNameNormalized);
+ map.put("datasources", dataSourceLinks);
+ return map;
+ }
+
+ @RequestMapping(method = RequestMethod.PUT)
+ @ResponseBody
+ public Map<String, Object> putTenant(@PathVariable("tenant") String tenantName) {
+ final TenantContext tenantContext = tenantRegistry.createTenantContext(tenantName);
+ final String tenantIdentifier = tenantContext.getTenantName();
+
+ final Map<String, Object> map = new LinkedHashMap<>();
+ map.put("type", "tenant");
+ map.put("name", tenantIdentifier);
+
+ return map;
+ }
+
+ @RequestMapping(method = RequestMethod.DELETE)
+ @ResponseBody
+ public Map<String, Object> deleteTenant(@PathVariable("tenant") String tenantName) {
+ tenantRegistry.deleteTenantContext(tenantName);
+
+ final Map<String, Object> map = new LinkedHashMap<>();
+ map.put("type", "tenant");
+ map.put("name", tenantName);
+ map.put("deleted", true);
+
+ return map;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java
new file mode 100644
index 0000000..b6fdb28
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.validation.constraints.NotNull;
+
+import org.apache.metamodel.service.app.DataSourceDefinition;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RestDataSourceDefinition implements DataSourceDefinition {
+
+ private final Map<String, Object> properties = new HashMap<>();
+
+ @JsonProperty(value = "type", required = true)
+ @NotNull
+ private String type;
+
+ @Override
+ public String getType() {
+ return type;
+ }
+
+ @JsonAnyGetter
+ @Override
+ public Map<String, Object> getProperties() {
+ return properties;
+ }
+
+ @JsonAnySetter
+ public void set(String name, Object value) {
+ properties.put(name, value);
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestErrorResponse.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestErrorResponse.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestErrorResponse.java
new file mode 100644
index 0000000..ed27dfe
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestErrorResponse.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers.model;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Represents the JSON object that is returned when an error occurs
+ */
+public class RestErrorResponse {
+
+ @JsonProperty("code")
+ private int code;
+
+ @JsonProperty("message")
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private String message;
+
+ @JsonProperty("additional_details")
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private Map<String, Object> additionalDetails;
+
+ public RestErrorResponse(int code, String message) {
+ this(code, message, null);
+ }
+
+ public RestErrorResponse(int code, String message, Map<String, Object> additionalDetails) {
+ this.code = code;
+ this.message = message;
+ this.additionalDetails = additionalDetails;
+ }
+
+ public RestErrorResponse() {
+ this(-1, null, null);
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public void setAdditionalDetails(Map<String, Object> additionalDetails) {
+ this.additionalDetails = additionalDetails;
+ }
+
+ public Map<String, Object> getAdditionalDetails() {
+ return additionalDetails;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestLink.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestLink.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestLink.java
new file mode 100644
index 0000000..2830389
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestLink.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers.model;
+
+import java.io.Serializable;
+import java.net.URI;
+
+/**
+ * Represents a hyper-link to a service (typically provided in the REST
+ * responses)
+ */
+public class RestLink implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private String name;
+
+ private URI uri;
+
+ public RestLink() {
+ }
+
+ public RestLink(String name, URI uri) {
+ this();
+ this.name = name;
+ this.uri = uri;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public URI getUri() {
+ return uri;
+ }
+
+ public void setUri(URI uri) {
+ this.uri = uri;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/resources/context/application-context.xml
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/resources/context/application-context.xml b/service-webapp/src/main/resources/context/application-context.xml
new file mode 100644
index 0000000..e31feb1
--- /dev/null
+++ b/service-webapp/src/main/resources/context/application-context.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:security="http://www.springframework.org/schema/security"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:mvc="http://www.springframework.org/schema/mvc"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
+ http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
+
+ <context:component-scan base-package="org.apache.metamodel.service.app" />
+
+ <bean id="tenantRegistry" class="org.apache.metamodel.service.app.InMemoryTenantRegistry" />
+
+</beans>
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/resources/logback.xml
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/resources/logback.xml b/service-webapp/src/main/resources/logback.xml
new file mode 100644
index 0000000..8ba8596
--- /dev/null
+++ b/service-webapp/src/main/resources/logback.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<configuration>
+
+ <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <logger name="org.apache.metamodel" level="info" />
+
+ <root level="warn">
+ <appender-ref ref="consoleAppender" />
+ </root>
+</configuration>
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/resources/swagger.yaml
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/resources/swagger.yaml b/service-webapp/src/main/resources/swagger.yaml
new file mode 100644
index 0000000..b75ad4d
--- /dev/null
+++ b/service-webapp/src/main/resources/swagger.yaml
@@ -0,0 +1,538 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+swagger: '2.0'
+info:
+ title: Apache MetaModel RESTful API
+ description: Delivers 'MetaModel-as-a-Service' for unified data federation.
+ version: "5.0.0"
+ contact:
+ name: Apache MetaModel
+ url: http://metamodel.apache.org
+ license:
+ name: Apache License, version 2.0
+ url: http://www.apache.org/licenses/LICENSE-2.0
+consumes:
+ - application/json
+produces:
+ - application/json
+paths:
+ /:
+ get:
+ summary: Hello MetaModel
+ description: An endpoint that provides a confirmation that the system is operational
+ responses:
+ 200:
+ description: The system is operational
+ schema:
+ type: object
+ properties:
+ ping:
+ type: string
+ description: Should return 'pong!' when the system is operational
+ application:
+ type: string
+ description: The name of the application running (Apache MetaModel)
+ version:
+ type: string
+ description: The version of the application running
+ server-time:
+ type: object
+ properties:
+ timestamp:
+ type: integer
+ format: int64
+ description: The server-time in timestamp format (millis since 1st of January 1970)
+ iso8601:
+ type: string
+ description: The server-time in ISO-8601 format
+ canonical-hostname:
+ type: string
+ description: The canonical hostname of the server
+ /{tenant}:
+ parameters:
+ - name: tenant
+ in: path
+ type: string
+ description: The tenant name
+ required: true
+ put:
+ summary: Create new tenant
+ responses:
+ 200:
+ description: Tenant created
+ schema:
+ properties:
+ type:
+ type: string
+ description: The type of entity (tenant)
+ name:
+ type: string
+ description: The tenant name/identifier
+ 409:
+ description: Tenant already exist
+ schema:
+ $ref: "#/definitions/error"
+ get:
+ summary: Get tenant information
+ description: Provides basic information about a tenant of the system
+ responses:
+ 404:
+ description: Tenant not found
+ schema:
+ $ref: "#/definitions/error"
+ 200:
+ description: Tenant found
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ description: The type of entity (tenant)
+ name:
+ type: string
+ description: The tenant name/identifier
+ datasources:
+ type: array
+ items:
+ type: object
+ properties:
+ name:
+ type: string
+ description: The name of the datasource
+ uri:
+ type: string
+ format: uri
+ description: A link to the datasource information
+ delete:
+ summary: Delete tenant
+ description: Deletes a tenant from the system
+ responses:
+ 200:
+ description: Tenant deleted
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ description: The type of entity (tenant)
+ name:
+ type: string
+ description: The tenant name/identifier
+ 404:
+ description: Tenant not found
+ schema:
+ $ref: "#/definitions/error"
+ /{tenant}/{datasource}:
+ parameters:
+ - name: tenant
+ in: path
+ type: string
+ description: The tenant name
+ required: true
+ - name: datasource
+ in: path
+ type: string
+ description: The datasource name
+ required: true
+ get:
+ responses:
+ 200:
+ description: Datasource found
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ description: The type of entity (datasource)
+ name:
+ type: string
+ description: The datasource name
+ tenant:
+ type: string
+ description: The tenant name
+ updateable:
+ type: boolean
+ description: Is this datasource updateable?
+ query_uri:
+ type: string
+ description: A link to the query endpoint for this datasource
+ format: uri
+ schemas:
+ type: array
+ description: The schemas of this datasource
+ items:
+ type: object
+ properties:
+ name:
+ type: string
+ description: The schema name
+ uri:
+ type: string
+ description: A link to the schema information
+ format: uri
+ 404:
+ description: Datasource not found
+ schema:
+ $ref: "#/definitions/error"
+ put:
+ parameters:
+ - name: inputData
+ in: body
+ description: The definition of the datasource using properties. The same properties as normally applied in MetaModel factories (e.g. 'type', 'resource', 'url', 'driver-class' ,'hostname', 'port', 'catalog', 'database', 'username', 'port', 'table-defs') are used here.
+ required: true
+ schema:
+ type: object
+ responses:
+ 200:
+ description: Datasource created
+ /{tenant}/{datasource}/q:
+ parameters:
+ - name: tenant
+ in: path
+ type: string
+ description: The tenant name
+ required: true
+ - name: datasource
+ in: path
+ type: string
+ description: The datasource name
+ required: true
+ get:
+ description: Executes a query on the datasource
+ parameters:
+ - name: sql
+ in: query
+ type: string
+ description: The MetaModel-flavoured SQL query to execute
+ required: true
+ - name: offset
+ in: query
+ type: string
+ description: An offset / first-row flag to set on the query
+ required: false
+ - name: limit
+ in: query
+ type: string
+ description: A limit / max-rows flag to set on the query
+ required: false
+ responses:
+ 200:
+ description: Query executed
+ schema:
+ $ref: "#/definitions/queryResponse"
+ 400:
+ description: Failure while parsing query
+ schema:
+ $ref: "#/definitions/error"
+ 404:
+ description: Datasource not found
+ schema:
+ $ref: "#/definitions/error"
+ 500:
+ description: Failure while executing query
+ schema:
+ $ref: "#/definitions/error"
+ /{tenant}/{datasource}/s/{schema}:
+ parameters:
+ - name: tenant
+ in: path
+ type: string
+ description: The tenant name
+ required: true
+ - name: datasource
+ in: path
+ type: string
+ description: The datasource name
+ required: true
+ - name: schema
+ in: path
+ type: string
+ description: The schema name
+ required: true
+ get:
+ responses:
+ 200:
+ description: Schema found
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ description: The type of entity (schema)
+ name:
+ type: string
+ description: The schema name
+ datasource:
+ type: string
+ description: The datasource name
+ tables:
+ type: array
+ description: The names of the schema's tables
+ items:
+ type: object
+ properties:
+ name:
+ type: string
+ description: The table name
+ uri:
+ type: string
+ description: A link to the table information
+ format: uri
+ 404:
+ description: Schema not found
+ schema:
+ $ref: "#/definitions/error"
+ /{tenant}/{datasource}/s/{schema}/t/{table}:
+ parameters:
+ - name: tenant
+ in: path
+ type: string
+ description: The tenant name
+ required: true
+ - name: datasource
+ in: path
+ type: string
+ description: The datasource name
+ required: true
+ - name: schema
+ in: path
+ type: string
+ description: The schema name
+ required: true
+ - name: table
+ in: path
+ type: string
+ description: The table name
+ required: true
+ get:
+ responses:
+ 200:
+ description: Table found
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ description: The type of entity (table)
+ name:
+ type: string
+ description: The table name
+ schema:
+ type: string
+ description: The schema name
+ datasource:
+ type: string
+ description: The datasource name
+ tenant:
+ type: string
+ description: The tenant name
+ columns:
+ type: array
+ description: The names of the table's columns
+ items:
+ type: object
+ properties:
+ name:
+ type: string
+ description: The column name
+ uri:
+ type: string
+ description: A link to the column information
+ format: uri
+ 404:
+ description: Table not found
+ schema:
+ $ref: "#/definitions/error"
+ /{tenant}/{datasource}/s/{schema}/t/{table}/d:
+ parameters:
+ - name: tenant
+ in: path
+ type: string
+ description: The tenant name
+ required: true
+ - name: datasource
+ in: path
+ type: string
+ description: The datasource name
+ required: true
+ - name: schema
+ in: path
+ type: string
+ description: The schema name
+ required: true
+ - name: table
+ in: path
+ type: string
+ description: The table name
+ required: true
+ get:
+ description: Gets the data of the table
+ responses:
+ 200:
+ description: Query executed
+ schema:
+ $ref: "#/definitions/queryResponse"
+ 400:
+ description: Failure while parsing query
+ schema:
+ $ref: "#/definitions/error"
+ 404:
+ description: Table not found
+ schema:
+ $ref: "#/definitions/error"
+ 500:
+ description: Failure while executing query
+ schema:
+ $ref: "#/definitions/error"
+ post:
+ description: Inserts data to the table
+ parameters:
+ - name: inputData
+ in: body
+ description: The data to insert
+ required: true
+ schema:
+ type: array
+ items:
+ description: A record to insert where each key is expected to match a column name and each value is the value to put.
+ type: object
+ responses:
+ 200:
+ description: Data inserted
+ #TODO
+ 404:
+ description: Table not found
+ schema:
+ $ref: "#/definitions/error"
+ /{tenant}/{datasource}/s/{schema}/t/{table}/c/{column}:
+ parameters:
+ - name: tenant
+ in: path
+ type: string
+ description: The tenant name
+ required: true
+ - name: datasource
+ in: path
+ type: string
+ description: The datasource name
+ required: true
+ - name: schema
+ in: path
+ type: string
+ description: The schema name
+ required: true
+ - name: table
+ in: path
+ type: string
+ description: The table name
+ required: true
+ - name: column
+ in: path
+ type: string
+ description: The column name
+ required: true
+ get:
+ description: Gets information about a column
+ responses:
+ 200:
+ description: Query executed
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ description: The type of entity (column)
+ name:
+ type: string
+ description: The column name
+ table:
+ type: string
+ description: The table name
+ schema:
+ type: string
+ description: The schema name
+ datasource:
+ type: string
+ description: The datasource name
+ tenant:
+ type: string
+ description: The tenant name
+ metadata:
+ type: object
+ description: Metadata about the column
+ properties:
+ number:
+ type: integer
+ description: The column number (0-based)
+ size:
+ type: integer
+ description: The column size
+ nullable:
+ type: boolean
+ description: Is the column nullable?
+ primary-key:
+ type: boolean
+ description: Is the column a primary key?
+ indexed:
+ type: boolean
+ description: Is the column indexed?
+ column-type:
+ type: string
+ description: The column type (as interpreted/adapted by Apache MetaModel)
+ native-type:
+ type: string
+ description: The native column type (as defined by the datasource itself)
+ remarks:
+ type: string
+ description: Any remarks on the column
+ 404:
+ description: Column not found
+ schema:
+ $ref: "#/definitions/error"
+definitions:
+ queryResponse:
+ description: Represents the result of a query - a dataset
+ type: object
+ properties:
+ type:
+ type: string
+ description: The type of entity (dataset)
+ headers:
+ type: array
+ description: The dataset header names
+ items:
+ type: string
+ data:
+ type: array
+ description: The actual data returned by the query
+ items:
+ type: array
+ items:
+ type: object
+ error:
+ description: Elaborates the error that occurred
+ type: object
+ properties:
+ code:
+ type: integer
+ description: The HTTP status code
+ message:
+ type: string
+ description: A humanly readable error message
+ additional_details:
+ type: object
+ description: Any auxilary details to further elaborate the error
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml b/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml
new file mode 100644
index 0000000..7407d2c
--- /dev/null
+++ b/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:security="http://www.springframework.org/schema/security"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:mvc="http://www.springframework.org/schema/mvc"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
+ http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
+
+ <context:component-scan base-package="org.apache.metamodel.service.controllers" />
+
+ <mvc:annotation-driven>
+ <mvc:message-converters>
+ <ref bean="jsonMessageConverter" />
+ </mvc:message-converters>
+ </mvc:annotation-driven>
+
+ <mvc:resources location="/swagger-ui/" mapping="/swagger-ui/**" order="-1001" />
+
+ <!-- Message converters -->
+ <bean id="jsonMessageConverter"
+ class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
+ <property name="supportedMediaTypes">
+ <list>
+ <value>application/json;charset=UTF-8</value>
+ <value>application/json</value>
+ </list>
+ </property>
+ </bean>
+
+ <bean
+ class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
+ <property name="messageConverters">
+ <list>
+ <ref bean="jsonMessageConverter" />
+ </list>
+ </property>
+ </bean>
+</beans>
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/webapp/WEB-INF/web.xml b/service-webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..31de07f
--- /dev/null
+++ b/service-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ version="3.1" xmlns="http://java.sun.com/xml/ns/javaee">
+
+ <!-- Spring listener -->
+ <listener>
+ <listener-class>org.springframework.web.context.ContextLoaderListener
+ </listener-class>
+ </listener>
+ <listener>
+ <listener-class>org.springframework.web.context.request.RequestContextListener
+ </listener-class>
+ </listener>
+
+ <!-- Spring config -->
+ <context-param>
+ <param-name>contextConfigLocation</param-name>
+ <param-value>classpath:context/application-context.xml</param-value>
+ </context-param>
+
+ <!-- Spring servlet -->
+ <servlet>
+ <servlet-name>dispatcher</servlet-name>
+ <servlet-class>org.springframework.web.servlet.DispatcherServlet
+ </servlet-class>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>dispatcher</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+ <filter>
+ <filter-name>CharacterEncodingFilter</filter-name>
+ <filter-class>org.springframework.web.filter.CharacterEncodingFilter
+ </filter-class>
+ <init-param>
+ <param-name>encoding</param-name>
+ <param-value>UTF-8</param-value>
+ </init-param>
+ <init-param>
+ <param-name>forceEncoding</param-name>
+ <param-value>true</param-value>
+ </init-param>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>CharacterEncodingFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+</web-app>
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java
new file mode 100644
index 0000000..8e3dab3
--- /dev/null
+++ b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/RootInformationControllerTest.java
@@ -0,0 +1,61 @@
+/**
+ * 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 static org.junit.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class RootInformationControllerTest {
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void init() {
+ final RootInformationController controller = new RootInformationController();
+ controller.servletContext = new MockServletContext();
+ mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+ }
+
+ @Test
+ public void testGet() throws Exception {
+ final MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/").contentType(
+ MediaType.APPLICATION_JSON);
+
+ final MvcResult result = mockMvc.perform(request).andExpect(MockMvcResultMatchers.status().is(200)).andReturn();
+
+ final String content = result.getResponse().getContentAsString();
+
+ final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+ assertEquals("pong!", map.get("ping"));
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java
new file mode 100644
index 0000000..ca38bc6
--- /dev/null
+++ b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java
@@ -0,0 +1,195 @@
+/**
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Map;
+
+import org.apache.metamodel.service.app.InMemoryTenantRegistry;
+import org.apache.metamodel.service.app.TenantRegistry;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class TenantInteractionScenarioTest {
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void init() {
+ TenantRegistry tenantRegistry = new InMemoryTenantRegistry();
+ TenantController tenantController = new TenantController(tenantRegistry);
+ DataSourceController dataContextController = new DataSourceController(tenantRegistry);
+ SchemaController schemaController = new SchemaController(tenantRegistry);
+ TableController tableController = new TableController(tenantRegistry);
+ ColumnController columnController = new ColumnController(tenantRegistry);
+ QueryController queryController = new QueryController(tenantRegistry);
+ TableDataController tableDataController = new TableDataController(tenantRegistry);
+
+ mockMvc = MockMvcBuilders.standaloneSetup(tenantController, dataContextController, schemaController,
+ tableController, columnController, queryController, tableDataController).build();
+ }
+
+ @Test
+ public void testScenario() throws Exception {
+ // create tenant
+ {
+ final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put("/tenant1").contentType(
+ MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
+
+ final String content = result.getResponse().getContentAsString();
+ final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+ assertEquals("tenant", map.get("type"));
+ assertEquals("tenant1", map.get("name"));
+ assertNull(map.get("datasources"));
+ }
+
+ // create datasource
+ {
+ final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put("/tenant1/mydata").content(
+ "{'type':'pojo','table-defs':'hello_world (greeting VARCHAR, who VARCHAR); foo (bar INTEGER, baz DATE);'}"
+ .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect(
+ MockMvcResultMatchers.status().isOk()).andReturn();
+
+ final String content = result.getResponse().getContentAsString();
+ final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+ assertEquals("datasource", map.get("type"));
+ assertEquals("mydata", map.get("name"));
+ assertEquals(
+ "[{name=information_schema, uri=/tenant1/mydata/s/information_schema}, {name=mydata, uri=/tenant1/mydata/s/mydata}]",
+ map.get("schemas").toString());
+ }
+
+ // explore tenant - now with a datasource
+ {
+ final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1")).andExpect(
+ MockMvcResultMatchers.status().isOk()).andReturn();
+
+ final String content = result.getResponse().getContentAsString();
+ final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+ assertEquals("tenant", map.get("type"));
+ assertEquals("tenant1", map.get("name"));
+ assertEquals("[{name=mydata, uri=/tenant1/mydata}]", map.get("datasources").toString());
+ }
+
+ // explore schema
+ {
+ final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/s/mydata")).andExpect(
+ MockMvcResultMatchers.status().isOk()).andReturn();
+
+ final String content = result.getResponse().getContentAsString();
+ final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+ assertEquals("schema", map.get("type"));
+ assertEquals("mydata", map.get("name"));
+
+ assertEquals(
+ "[{name=foo, uri=/tenant1/mydata/s/mydata/t/foo}, {name=hello_world, uri=/tenant1/mydata/s/mydata/t/hello_world}]",
+ map.get("tables").toString());
+ }
+
+ // explore table
+ {
+ final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/s/mydata/t/foo"))
+ .andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
+
+ final String content = result.getResponse().getContentAsString();
+ final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+ assertEquals("table", map.get("type"));
+ assertEquals("foo", map.get("name"));
+
+ assertEquals("[{name=bar, uri=/tenant1/mydata/s/mydata/t/foo/c/bar}, "
+ + "{name=baz, uri=/tenant1/mydata/s/mydata/t/foo/c/baz}]", map.get("columns").toString());
+ }
+
+ // explore column
+ {
+ final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/s/mydata/t/foo/c/bar"))
+ .andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
+
+ final String content = result.getResponse().getContentAsString();
+ final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+ assertEquals("column", map.get("type"));
+ assertEquals("bar", map.get("name"));
+
+ assertEquals(
+ "{number=0, size=null, nullable=true, primary-key=false, indexed=false, column-type=INTEGER, native-type=null, remarks=null}",
+ map.get("metadata").toString());
+ }
+
+ // query metadata from information_schema
+ {
+ final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/q?sql={sql}",
+ "SELECT name, table FROM information_schema.columns")).andExpect(MockMvcResultMatchers.status()
+ .isOk()).andReturn();
+
+ final String content = result.getResponse().getContentAsString();
+ final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+ assertEquals("dataset", map.get("type"));
+ assertEquals("[columns.name, columns.table]", map.get("header").toString());
+ assertEquals("[[bar, foo], [baz, foo], [greeting, hello_world], [who, hello_world]]", map.get("data")
+ .toString());
+ }
+
+ // insert into table (x2)
+ {
+ final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(
+ "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Howdy','who':'MetaModel'}]"
+ .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect(
+ MockMvcResultMatchers.status().isOk()).andReturn();
+
+ final String content = result.getResponse().getContentAsString();
+ final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+ assertEquals("{status=ok}", map.toString());
+ }
+ {
+ final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(
+ "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Hi','who':'Apache'}]"
+ .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect(
+ MockMvcResultMatchers.status().isOk()).andReturn();
+
+ final String content = result.getResponse().getContentAsString();
+ final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+ assertEquals("{status=ok}", map.toString());
+ }
+
+ // query the actual data
+ // query metadata from information_schema
+ {
+ final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/q?sql={sql}",
+ "SELECT greeting, who AS who_is_it FROM hello_world")).andExpect(MockMvcResultMatchers.status()
+ .isOk()).andReturn();
+
+ final String content = result.getResponse().getContentAsString();
+ final Map<?, ?> map = new ObjectMapper().readValue(content, Map.class);
+ assertEquals("dataset", map.get("type"));
+ assertEquals("[hello_world.greeting, hello_world.who AS who_is_it]", map.get("header").toString());
+ assertEquals("[[Howdy, MetaModel], [Hi, Apache]]", map.get("data")
+ .toString());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/spring/pom.xml
----------------------------------------------------------------------
diff --git a/spring/pom.xml b/spring/pom.xml
index 6792582..b6d09b4 100644
--- a/spring/pom.xml
+++ b/spring/pom.xml
@@ -27,10 +27,6 @@ under the License.
<artifactId>MetaModel-spring</artifactId>
<name>MetaModel module for Spring enabled configuration</name>
- <properties>
- <spring.version>3.0.7.RELEASE</spring.version>
- </properties>
-
<dependencies>
<dependency>
<groupId>org.apache.metamodel</groupId>
@@ -46,14 +42,7 @@ under the License.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
- <version>${spring.version}</version>
<scope>provided</scope>
- <exclusions>
- <exclusion>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- </exclusion>
- </exclusions>
</dependency>
<!-- test -->
@@ -70,7 +59,6 @@ under the License.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
- <version>${spring.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
[2/2] metamodel git commit: RESTful MetaModel service-webapp.
Posted by ka...@apache.org.
RESTful MetaModel service-webapp.
Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/6d197c36
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/6d197c36
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/6d197c36
Branch: refs/heads/feature/5.x/rest
Commit: 6d197c360398438e7ac3b894cd7a24b4ec3df4ce
Parents: 02397db
Author: Kasper S�rensen <i....@gmail.com>
Authored: Thu Aug 4 23:02:34 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Thu Aug 4 23:02:34 2016 -0700
----------------------------------------------------------------------
.../factory/DataContextPropertiesImpl.java | 3 +
.../apache/metamodel/pojo/PojoDataContext.java | 4 +-
.../metamodel/pojo/PojoDataContextFactory.java | 71 +++
....apache.metamodel.factory.DataContextFactory | 1 +
pom.xml | 38 +-
service-webapp/Dockerfile | 32 ++
service-webapp/README.md | 12 +
service-webapp/pom.xml | 98 ++++
.../src/main/docker/swagger-ui/index.html | 94 ++++
.../app/CachedDataSourceRegistryWrapper.java | 109 ++++
.../service/app/DataContextSupplier.java | 48 ++
.../service/app/DataContextTraverser.java | 67 +++
.../service/app/DataSourceDefinition.java | 28 +
.../service/app/DataSourceRegistry.java | 48 ++
.../service/app/InMemoryDataSourceRegistry.java | 65 +++
.../service/app/InMemoryTenantContext.java | 41 ++
.../service/app/InMemoryTenantRegistry.java | 74 +++
.../metamodel/service/app/TenantContext.java | 31 ++
.../metamodel/service/app/TenantRegistry.java | 38 ++
.../AbstractIdentifierNamingException.java | 40 ++
.../DataSourceAlreadyExistException.java | 28 +
.../DataSourceNotUpdateableException.java | 37 ++
.../app/exceptions/NoSuchColumnException.java | 29 +
.../exceptions/NoSuchDataSourceException.java | 29 +
.../app/exceptions/NoSuchSchemaException.java | 29 +
.../app/exceptions/NoSuchTableException.java | 29 +
.../app/exceptions/NoSuchTenantException.java | 28 +
.../exceptions/TenantAlreadyExistException.java | 28 +
.../service/controllers/ColumnController.java | 86 +++
.../controllers/DataSourceController.java | 104 ++++
.../service/controllers/QueryController.java | 85 +++
.../service/controllers/RestErrorHandler.java | 160 ++++++
.../controllers/RootInformationController.java | 102 ++++
.../service/controllers/SchemaController.java | 80 +++
.../controllers/SwaggerSpecController.java | 64 +++
.../service/controllers/TableController.java | 85 +++
.../controllers/TableDataController.java | 115 ++++
.../service/controllers/TenantController.java | 94 ++++
.../model/RestDataSourceDefinition.java | 55 ++
.../controllers/model/RestErrorResponse.java | 80 +++
.../service/controllers/model/RestLink.java | 60 +++
.../resources/context/application-context.xml | 34 ++
service-webapp/src/main/resources/logback.xml | 33 ++
service-webapp/src/main/resources/swagger.yaml | 538 +++++++++++++++++++
.../main/webapp/WEB-INF/dispatcher-servlet.xml | 59 ++
service-webapp/src/main/webapp/WEB-INF/web.xml | 70 +++
.../RootInformationControllerTest.java | 61 +++
.../TenantInteractionScenarioTest.java | 195 +++++++
spring/pom.xml | 12 -
49 files changed, 3337 insertions(+), 14 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java b/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java
index e66a811..13ce9c1 100644
--- a/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java
+++ b/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java
@@ -288,6 +288,9 @@ public class DataContextPropertiesImpl implements DataContextProperties {
@Override
public SimpleTableDef[] getTableDefs() {
final Object obj = get(PROPERTY_TABLE_DEFS);
+ if (obj == null) {
+ return null;
+ }
if (obj instanceof SimpleTableDef[]) {
return (SimpleTableDef[]) obj;
}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java
----------------------------------------------------------------------
diff --git a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java
index 9369e96..340228f 100644
--- a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java
+++ b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java
@@ -49,6 +49,8 @@ import org.apache.metamodel.util.SimpleTableDef;
public class PojoDataContext extends QueryPostprocessDataContext implements UpdateableDataContext, Serializable {
private static final long serialVersionUID = 1L;
+
+ public static final String DEFAULT_SCHEMA_NAME = "Schema";
private final Map<String, TableDataProvider<?>> _tables;
private final String _schemaName;
@@ -68,7 +70,7 @@ public class PojoDataContext extends QueryPostprocessDataContext implements Upda
* @param tables
*/
public PojoDataContext(List<TableDataProvider<?>> tables) {
- this("Schema", tables);
+ this(DEFAULT_SCHEMA_NAME, tables);
}
/**
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java
----------------------------------------------------------------------
diff --git a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java
new file mode 100644
index 0000000..35842bf
--- /dev/null
+++ b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.pojo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.metamodel.ConnectionException;
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.factory.DataContextFactory;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.factory.ResourceFactoryRegistry;
+import org.apache.metamodel.factory.UnsupportedDataContextPropertiesException;
+import org.apache.metamodel.util.SimpleTableDef;
+
+public class PojoDataContextFactory implements DataContextFactory {
+
+ public static final String PROPERTY_TYPE = "pojo";
+
+ @Override
+ public boolean accepts(DataContextProperties properties, ResourceFactoryRegistry resourceFactoryRegistry) {
+ return PROPERTY_TYPE.equals(properties.getDataContextType());
+ }
+
+ @Override
+ public DataContext create(DataContextProperties properties, ResourceFactoryRegistry resourceFactoryRegistry)
+ throws UnsupportedDataContextPropertiesException, ConnectionException {
+
+ assert accepts(properties, resourceFactoryRegistry);
+
+ final String schemaName;
+ if (properties.getDatabaseName() != null) {
+ schemaName = properties.getDatabaseName();
+ } else {
+ schemaName = "Schema";
+ }
+
+ final List<TableDataProvider<?>> tableDataProviders;
+
+ final SimpleTableDef[] tableDefs = properties.getTableDefs();
+ if (tableDefs == null) {
+ tableDataProviders = new ArrayList<>();
+ } else {
+ tableDataProviders = new ArrayList<>(tableDefs.length);
+ for (int i = 0; i < tableDefs.length; i++) {
+ final TableDataProvider<?> tableDataProvider = new ArrayTableDataProvider(tableDefs[i],
+ new ArrayList<Object[]>());
+ tableDataProviders.add(tableDataProvider);
+ }
+ }
+
+ return new PojoDataContext(schemaName, tableDataProviders);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory
----------------------------------------------------------------------
diff --git a/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory b/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory
new file mode 100644
index 0000000..76f808d
--- /dev/null
+++ b/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory
@@ -0,0 +1 @@
+org.apache.metamodel.pojo.PojoDataContextFactory
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 1df4cc0..51f8b67 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,6 +29,7 @@ under the License.
<hadoop.version>2.6.0</hadoop.version>
<jackson.version>2.6.3</jackson.version>
<easymock.version>3.2</easymock.version>
+ <spring.version>4.2.6.RELEASE</spring.version>
<httpcomponents.version>4.4.1</httpcomponents.version>
<checksum-maven-plugin.version>1.2</checksum-maven-plugin.version>
<skipTests>false</skipTests>
@@ -77,6 +78,7 @@ under the License.
<module>full</module>
<module>spring</module>
<module>neo4j</module>
+ <module>service-webapp</module>
</modules>
<issueManagement>
<system>Jira</system>
@@ -370,7 +372,7 @@ under the License.
<excludeSubProjects>false</excludeSubProjects>
<excludes>
<exclude>KEYS</exclude>
- <exclude>*.md</exclude>
+ <exclude>**/*.md</exclude>
<exclude>example-metamodel-integrationtest-configuration.properties</exclude>
<exclude>travis-metamodel-integrationtest-configuration.properties</exclude>
<exclude>**/src/assembly/metamodel-packaged-assembly-descriptor.xml</exclude>
@@ -389,6 +391,7 @@ under the License.
<exclude>**/*.ipr/**</exclude>
<exclude>**/.idea/**</exclude>
<exclude>**/tattletale-filters.properties</exclude>
+ <exclude>**/swagger-ui/**</exclude>
<exclude>DEPENDENCIES</exclude>
<exclude>DISCLAIMER</exclude>
<exclude>neo4j-community-*/**</exclude>
@@ -511,6 +514,11 @@ under the License.
<version>${jackson.version}</version>
</dependency>
<dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+ <dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
@@ -535,6 +543,34 @@ under the License.
<artifactId>hsqldb</artifactId>
<version>1.8.0.10</version>
</dependency>
+
+ <!-- Spring -->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-core</artifactId>
+ <version>${spring.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-test</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
<!-- Hadoop -->
<dependency>
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/Dockerfile
----------------------------------------------------------------------
diff --git a/service-webapp/Dockerfile b/service-webapp/Dockerfile
new file mode 100644
index 0000000..4734f19
--- /dev/null
+++ b/service-webapp/Dockerfile
@@ -0,0 +1,32 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+FROM tomcat:8.0-jre8
+
+RUN rm -rf $CATALINA_HOME/webapps \
+ mkdir $CATALINA_HOME/webapps
+COPY target/MetaModel.war $CATALINA_HOME/webapps/ROOT.war
+
+EXPOSE 8080
+
+ENV SWAGGER_UI_VERSION 2.1.5
+RUN wget -q -O - https://github.com/swagger-api/swagger-ui/archive/v$SWAGGER_UI_VERSION.tar.gz | tar -xzf - -C /tmp \
+ && mv /tmp/swagger-ui-$SWAGGER_UI_VERSION/dist $CATALINA_HOME/webapps/swagger-ui \
+ && rm -r /tmp/swagger-ui-$SWAGGER_UI_VERSION
+ADD src/main/docker/swagger-ui/index.html $CATALINA_HOME/webapps/swagger-ui
+
+CMD ["catalina.sh", "run"]
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/README.md
----------------------------------------------------------------------
diff --git a/service-webapp/README.md b/service-webapp/README.md
new file mode 100644
index 0000000..20bca99
--- /dev/null
+++ b/service-webapp/README.md
@@ -0,0 +1,12 @@
+# MetaModel-as-a-service
+
+This is a web application that allows you to access MetaModel's unified API for datastore exploration and querying - using a set of RESTful services.
+
+## Docker building and running
+
+```
+docker build -t metamodel-service .
+docker run --rm -p 8080:8080 metamodel-service
+```
+
+And then go to http://localhost:8080 (assuming localhost is your docker machine).
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/pom.xml
----------------------------------------------------------------------
diff --git a/service-webapp/pom.xml b/service-webapp/pom.xml
new file mode 100644
index 0000000..fa56340
--- /dev/null
+++ b/service-webapp/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>MetaModel</artifactId>
+ <groupId>org.apache.metamodel</groupId>
+ <version>5.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>MetaModel-service-webapp</artifactId>
+ <name>MetaModel-as\u2013a-service web application</name>
+ <packaging>war</packaging>
+
+ <build>
+ <finalName>MetaModel</finalName>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.metamodel</groupId>
+ <artifactId>MetaModel-spring</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.metamodel</groupId>
+ <artifactId>MetaModel-full</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-yaml</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>1.1.7</version>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-validator</artifactId>
+ <version>5.2.4.Final</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ </dependency>
+
+ <!-- Provided -->
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>3.1.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Test -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/docker/swagger-ui/index.html
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/docker/swagger-ui/index.html b/service-webapp/src/main/docker/swagger-ui/index.html
new file mode 100755
index 0000000..16df339
--- /dev/null
+++ b/service-webapp/src/main/docker/swagger-ui/index.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Swagger UI</title>
+ <link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32" />
+ <link rel="icon" type="image/png" href="images/favicon-16x16.png" sizes="16x16" />
+ <link href='css/typography.css' media='screen' rel='stylesheet' type='text/css'/>
+ <link href='css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
+ <link href='css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
+ <link href='css/reset.css' media='print' rel='stylesheet' type='text/css'/>
+ <link href='css/print.css' media='print' rel='stylesheet' type='text/css'/>
+
+ <script src='lib/object-assign-pollyfill.js' type='text/javascript'></script>
+ <script src='lib/jquery-1.8.0.min.js' type='text/javascript'></script>
+ <script src='lib/jquery.slideto.min.js' type='text/javascript'></script>
+ <script src='lib/jquery.wiggle.min.js' type='text/javascript'></script>
+ <script src='lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
+ <script src='lib/handlebars-2.0.0.js' type='text/javascript'></script>
+ <script src='lib/lodash.min.js' type='text/javascript'></script>
+ <script src='lib/backbone-min.js' type='text/javascript'></script>
+ <script src='swagger-ui.js' type='text/javascript'></script>
+ <script src='lib/highlight.9.1.0.pack.js' type='text/javascript'></script>
+ <script src='lib/highlight.9.1.0.pack_extended.js' type='text/javascript'></script>
+ <script src='lib/jsoneditor.min.js' type='text/javascript'></script>
+ <script src='lib/marked.js' type='text/javascript'></script>
+ <script src='lib/swagger-oauth.js' type='text/javascript'></script>
+
+ <!-- Some basic translations -->
+ <!-- <script src='lang/translator.js' type='text/javascript'></script> -->
+ <!-- <script src='lang/ru.js' type='text/javascript'></script> -->
+ <!-- <script src='lang/en.js' type='text/javascript'></script> -->
+
+ <script type="text/javascript">
+ $(function () {
+ var url = window.location.search.match(/url=([^&]+)/);
+ if (url && url.length > 1) {
+ url = decodeURIComponent(url[1]);
+ } else {
+ url = "../../swagger.json";
+ }
+
+ hljs.configure({
+ highlightSizeThreshold: 5000
+ });
+
+ // Pre load translate...
+ if(window.SwaggerTranslator) {
+ window.SwaggerTranslator.translate();
+ }
+ window.swaggerUi = new SwaggerUi({
+ url: url,
+ dom_id: "swagger-ui-container",
+ supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
+ onComplete: function(swaggerApi, swaggerUi){
+ if(typeof initOAuth == "function") {
+ initOAuth({
+ clientId: "your-client-id",
+ clientSecret: "your-client-secret-if-required",
+ realm: "your-realms",
+ appName: "your-app-name",
+ scopeSeparator: ",",
+ additionalQueryStringParams: {}
+ });
+ }
+
+ if(window.SwaggerTranslator) {
+ window.SwaggerTranslator.translate();
+ }
+ },
+ onFailure: function(data) {
+ log("Unable to Load SwaggerUI");
+ },
+ docExpansion: "none",
+ jsonEditor: false,
+ defaultModelRendering: 'schema',
+ showRequestHeaders: false
+ });
+
+ window.swaggerUi.load();
+
+ function log() {
+ if ('console' in window) {
+ console.log.apply(console, arguments);
+ }
+ }
+ });
+ </script>
+</head>
+
+<body class="swagger-section">
+<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
+</body>
+</html>
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java
new file mode 100644
index 0000000..fffe83d
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/CachedDataSourceRegistryWrapper.java
@@ -0,0 +1,109 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.MetaModelException;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
+import org.apache.metamodel.util.FileHelper;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.RemovalListener;
+import com.google.common.cache.RemovalNotification;
+
+/**
+ * A wrapper that adds a cache around a {@link DataSourceRegistry} in order to
+ * prevent re-connecting all the time to the same data source.
+ */
+public class CachedDataSourceRegistryWrapper implements DataSourceRegistry {
+
+ /**
+ * The default timeout (in seconds) before the cache evicts and closes the
+ * created {@link DataContext}s.
+ */
+ public static final int DEFAULT_TIMEOUT_SECONDS = 60;
+
+ private final DataSourceRegistry delegate;
+ private final LoadingCache<String, DataContext> loadingCache;
+
+ public CachedDataSourceRegistryWrapper(DataSourceRegistry delegate) {
+ this(delegate, DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ }
+
+ public CachedDataSourceRegistryWrapper(DataSourceRegistry delegate, long cacheTimeout, TimeUnit cacheTimeoutUnit) {
+ this.delegate = delegate;
+ this.loadingCache = CacheBuilder.newBuilder().expireAfterAccess(cacheTimeout, cacheTimeoutUnit).removalListener(
+ createRemovalListener()).build(createCacheLoader());
+ }
+
+ private RemovalListener<String, DataContext> createRemovalListener() {
+ return new RemovalListener<String, DataContext>() {
+ @Override
+ public void onRemoval(RemovalNotification<String, DataContext> notification) {
+ final DataContext dataContext = notification.getValue();
+ // some DataContexts are closeable - attempt closing it here
+ FileHelper.safeClose(dataContext);
+ }
+ };
+ }
+
+ private CacheLoader<String, DataContext> createCacheLoader() {
+ return new CacheLoader<String, DataContext>() {
+ @Override
+ public DataContext load(String key) throws Exception {
+ return delegate.openDataContext(key);
+ }
+ };
+ }
+
+ @Override
+ public List<String> getDataSourceNames() {
+ return delegate.getDataSourceNames();
+ }
+
+ @Override
+ public String registerDataSource(String dataContextName, DataContextProperties dataContextProperties)
+ throws DataSourceAlreadyExistException {
+ loadingCache.invalidate(dataContextName);
+ return delegate.registerDataSource(dataContextName, dataContextProperties);
+ }
+
+ @Override
+ public DataContext openDataContext(String dataSourceName) throws NoSuchDataSourceException {
+ try {
+ return loadingCache.get(dataSourceName);
+ } catch (ExecutionException e) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ }
+ throw new MetaModelException("Unexpected error happened while getting DataContext '" + dataSourceName
+ + "' from cache", e);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java
new file mode 100644
index 0000000..2f42c6f
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.function.Supplier;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.factory.DataContextFactoryRegistryImpl;
+import org.apache.metamodel.factory.DataContextProperties;
+
+public class DataContextSupplier implements Supplier<DataContext> {
+
+ private final String dataSourceName;
+ private final DataContextProperties dataContextProperties;
+
+ public DataContextSupplier(String dataSourceName, DataContextProperties dataContextProperties) {
+ this.dataSourceName = dataSourceName;
+ this.dataContextProperties = dataContextProperties;
+ }
+
+ @Override
+ public DataContext get() {
+ final DataContext dataContext = DataContextFactoryRegistryImpl.getDefaultInstance().createDataContext(
+ dataContextProperties);
+ return dataContext;
+ }
+
+ @Override
+ public String toString() {
+ return "DataContextSupplier[" + dataSourceName + "]";
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java
new file mode 100644
index 0000000..6ec7122
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.schema.Schema;
+import org.apache.metamodel.schema.Table;
+import org.apache.metamodel.service.app.exceptions.NoSuchColumnException;
+import org.apache.metamodel.service.app.exceptions.NoSuchSchemaException;
+import org.apache.metamodel.service.app.exceptions.NoSuchTableException;
+
+/**
+ * Utility object responsible for traversing the schema/table/column structures
+ * of a {@link DataContext} based on String identifiers and names.
+ *
+ * This class will throw the appropriate exceptions if needed which is more
+ * communicative than the usual NPEs that would otherwise be thrown.
+ */
+public class DataContextTraverser {
+
+ private final DataContext dataContext;
+
+ public DataContextTraverser(DataContext dataContext) {
+ this.dataContext = dataContext;
+ }
+
+ public Schema getSchema(String schemaName) {
+ final Schema schema = dataContext.getSchemaByName(schemaName);
+ if (schema == null) {
+ throw new NoSuchSchemaException(schemaName);
+ }
+ return schema;
+ }
+
+ public Table getTable(String schemaName, String tableName) {
+ final Table table = getSchema(schemaName).getTableByName(tableName);
+ if (table == null) {
+ throw new NoSuchTableException(tableName);
+ }
+ return table;
+ }
+
+ public Column getColumn(String schemaName, String tableName, String columnName) {
+ final Column column = getTable(schemaName, tableName).getColumnByName(columnName);
+ if (column == null) {
+ throw new NoSuchColumnException(columnName);
+ }
+ return column;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
new file mode 100644
index 0000000..2aaa8e5
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.Map;
+
+public interface DataSourceDefinition {
+
+ public String getType();
+
+ public Map<String, Object> getProperties();
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
new file mode 100644
index 0000000..e0c8697
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.List;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.UpdateableDataContext;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException;
+import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
+
+/**
+ * Represents a user's/tenant's registry of {@link DataContext}s.
+ */
+public interface DataSourceRegistry {
+
+ public List<String> getDataSourceNames();
+
+ public String registerDataSource(String dataContextName, DataContextProperties dataContextProperties) throws DataSourceAlreadyExistException;
+
+ public DataContext openDataContext(String dataSourceName) throws NoSuchDataSourceException;
+
+ public default UpdateableDataContext openDataContextForUpdate(String dataSourceName) {
+ final DataContext dataContext = openDataContext(dataSourceName);
+ if (dataContext instanceof UpdateableDataContext) {
+ return (UpdateableDataContext) dataContext;
+ }
+ throw new DataSourceNotUpdateableException(dataSourceName);
+ };
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
new file mode 100644
index 0000000..386232a
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
+
+public class InMemoryDataSourceRegistry implements DataSourceRegistry {
+
+ private final Map<String, Supplier<DataContext>> dataSources;
+
+ public InMemoryDataSourceRegistry() {
+ dataSources = new LinkedHashMap<>();
+ }
+
+ @Override
+ public String registerDataSource(final String name, final DataContextProperties dataContextProperties)
+ throws DataSourceAlreadyExistException {
+ if (dataSources.containsKey(name)) {
+ throw new DataSourceAlreadyExistException(name);
+ }
+
+ dataSources.put(name, new DataContextSupplier(name, dataContextProperties));
+ return name;
+ }
+
+ @Override
+ public List<String> getDataSourceNames() {
+ return dataSources.keySet().stream().collect(Collectors.toList());
+ }
+
+ @Override
+ public DataContext openDataContext(String name) {
+ final Supplier<DataContext> supplier = dataSources.get(name);
+ if (supplier == null) {
+ throw new NoSuchDataSourceException(name);
+ }
+ return supplier.get();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
new file mode 100644
index 0000000..022ab28
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+public class InMemoryTenantContext implements TenantContext {
+
+ private final String tenantIdentifier;
+ private final DataSourceRegistry dataContextRegistry;
+
+ public InMemoryTenantContext(String tenantIdentifier) {
+ this.tenantIdentifier = tenantIdentifier;
+ this.dataContextRegistry = new CachedDataSourceRegistryWrapper(new InMemoryDataSourceRegistry());
+ }
+
+ @Override
+ public String getTenantName() {
+ return tenantIdentifier;
+ }
+
+ @Override
+ public DataSourceRegistry getDataSourceRegistry() {
+ return dataContextRegistry;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
new file mode 100644
index 0000000..6ac5bec
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
@@ -0,0 +1,74 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.service.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException;
+
+/**
+ * In-memory {@link TenantRegistry}. This is not particularly
+ * production-friendly as it is non-persistent, but it is useful for demo
+ * purposes.
+ */
+public class InMemoryTenantRegistry implements TenantRegistry {
+
+ private final Map<String, TenantContext> tenants;
+
+ public InMemoryTenantRegistry() {
+ tenants = new LinkedHashMap<>();
+ }
+
+ @Override
+ public List<String> getTenantIdentifiers() {
+ return tenants.keySet().stream().collect(Collectors.toList());
+ }
+
+ @Override
+ public TenantContext getTenantContext(String tenantIdentifier) {
+ final TenantContext tenant = tenants.get(tenantIdentifier);
+ if (tenant == null) {
+ throw new NoSuchTenantException(tenantIdentifier);
+ }
+ return tenant;
+ }
+
+ @Override
+ public TenantContext createTenantContext(String tenantIdentifier) {
+ if (tenants.containsKey(tenantIdentifier)) {
+ throw new TenantAlreadyExistException(tenantIdentifier);
+ }
+ final InMemoryTenantContext tenantContext = new InMemoryTenantContext(tenantIdentifier);
+ tenants.put(tenantIdentifier, tenantContext);
+ return tenantContext;
+ }
+
+ @Override
+ public void deleteTenantContext(String tenantIdentifier) {
+ final TenantContext removedTenant = tenants.remove(tenantIdentifier);
+ if (removedTenant == null) {
+ throw new NoSuchTenantException(tenantIdentifier);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
new file mode 100644
index 0000000..c550a60
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+/**
+ * Represents a context-object containing all the information and services
+ * related to a particular tenant.
+ */
+public interface TenantContext {
+
+ public String getTenantName();
+
+ public DataSourceRegistry getDataSourceRegistry();
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
new file mode 100644
index 0000000..5c02821
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
@@ -0,0 +1,38 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.List;
+
+import org.apache.metamodel.service.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException;
+
+/**
+ * Represents the application's central registry of tenants
+ */
+public interface TenantRegistry {
+
+ public List<String> getTenantIdentifiers();
+
+ public TenantContext getTenantContext(String tenantIdentifier) throws NoSuchTenantException;
+
+ public TenantContext createTenantContext(String tenantIdentifier) throws TenantAlreadyExistException;
+
+ public void deleteTenantContext(String tenantIdentifier) throws NoSuchTenantException;
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java
new file mode 100644
index 0000000..98f5892
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+import org.apache.metamodel.MetaModelException;
+
+/**
+ * Exception super class for any exception that arises because an identifier
+ * (name, ID or such) is invalid for a specific context.
+ */
+public class AbstractIdentifierNamingException extends MetaModelException {
+
+ private static final long serialVersionUID = 1L;
+ private final String identifier;
+
+ public AbstractIdentifierNamingException(String identifier) {
+ super("Illegal value: " + identifier);
+ this.identifier = identifier;
+ }
+
+ public String getIdentifier() {
+ return identifier;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java
new file mode 100644
index 0000000..3d5d650
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class DataSourceAlreadyExistException extends AbstractIdentifierNamingException {
+
+ private static final long serialVersionUID = 1L;
+
+ public DataSourceAlreadyExistException(String name) {
+ super(name);
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java
new file mode 100644
index 0000000..eb828cc
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+import org.apache.metamodel.MetaModelException;
+
+public class DataSourceNotUpdateableException extends MetaModelException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String dataSourceName;
+
+ public DataSourceNotUpdateableException(String dataSourceName) {
+ super(dataSourceName);
+ this.dataSourceName = dataSourceName;
+ }
+
+ public String getDataSourceName() {
+ return dataSourceName;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java
new file mode 100644
index 0000000..1902af1
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchColumnException extends AbstractIdentifierNamingException {
+
+ private static final long serialVersionUID = 1L;
+
+ public NoSuchColumnException(String name) {
+ super(name);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java
new file mode 100644
index 0000000..b59f016
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchDataSourceException extends AbstractIdentifierNamingException {
+
+ private static final long serialVersionUID = 1L;
+
+ public NoSuchDataSourceException(String name) {
+ super(name);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java
new file mode 100644
index 0000000..5b238a8
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchSchemaException extends AbstractIdentifierNamingException {
+
+ private static final long serialVersionUID = 1L;
+
+ public NoSuchSchemaException(String name) {
+ super(name);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java
new file mode 100644
index 0000000..73141cd
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchTableException extends AbstractIdentifierNamingException {
+
+ private static final long serialVersionUID = 1L;
+
+ public NoSuchTableException(String name) {
+ super(name);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java
new file mode 100644
index 0000000..b123ae2
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchTenantException extends AbstractIdentifierNamingException {
+
+ private static final long serialVersionUID = 1L;
+
+ public NoSuchTenantException(String tenantIdentifier) {
+ super(tenantIdentifier);
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java
new file mode 100644
index 0000000..f24c114
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class TenantAlreadyExistException extends AbstractIdentifierNamingException {
+
+ private static final long serialVersionUID = 1L;
+
+ public TenantAlreadyExistException(String tenantIdentifier) {
+ super(tenantIdentifier);
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
new file mode 100644
index 0000000..c811def
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
@@ -0,0 +1,86 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.service.app.DataContextTraverser;
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = { "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}/columns/{column}",
+ "/{tenant}/{dataContext}/s/{schema}/t/{table}/c/{column}" }, produces = MediaType.APPLICATION_JSON_VALUE)
+public class ColumnController {
+
+ private final TenantRegistry tenantRegistry;
+
+ @Autowired
+ public ColumnController(TenantRegistry tenantRegistry) {
+ this.tenantRegistry = tenantRegistry;
+ }
+
+ @RequestMapping(method = RequestMethod.GET)
+ @ResponseBody
+ public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+ @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId,
+ @PathVariable("table") String tableId, @PathVariable("column") String columnId) {
+ final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+ final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+ final DataContextTraverser traverser = new DataContextTraverser(dataContext);
+
+ final Column column = traverser.getColumn(schemaId, tableId, columnId);
+
+ final String tenantName = tenantContext.getTenantName();
+ final String tableName = column.getTable().getName();
+ final String schemaName = column.getTable().getSchema().getName();
+
+ final Map<String, Object> metadata = new LinkedHashMap<>();
+ metadata.put("number", column.getColumnNumber());
+ metadata.put("size", column.getColumnSize());
+ metadata.put("nullable", column.isNullable());
+ metadata.put("primary-key", column.isPrimaryKey());
+ metadata.put("indexed", column.isIndexed());
+ metadata.put("column-type", column.getType() == null ? null : column.getType().getName());
+ metadata.put("native-type", column.getNativeType());
+ metadata.put("remarks", column.getRemarks());
+
+ final Map<String, Object> map = new LinkedHashMap<>();
+ map.put("type", "column");
+ map.put("name", column.getName());
+ map.put("table", tableName);
+ map.put("schema", schemaName);
+ map.put("datasource", dataSourceName);
+ map.put("tenant", tenantName);
+ map.put("metadata", metadata);
+
+ return map;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
new file mode 100644
index 0000000..6dfe2d0
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
@@ -0,0 +1,104 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.validation.Valid;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.UpdateableDataContext;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.factory.DataContextPropertiesImpl;
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+import org.apache.metamodel.service.controllers.model.RestDataSourceDefinition;
+import org.apache.metamodel.service.controllers.model.RestLink;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = "/{tenant}/{datasource}", produces = MediaType.APPLICATION_JSON_VALUE)
+public class DataSourceController {
+
+ private final TenantRegistry tenantRegistry;
+
+ @Autowired
+ public DataSourceController(TenantRegistry tenantRegistry) {
+ this.tenantRegistry = tenantRegistry;
+ }
+
+ @RequestMapping(method = RequestMethod.PUT)
+ @ResponseBody
+ public Map<String, Object> put(@PathVariable("tenant") String tenantId,
+ @PathVariable("datasource") String dataSourceId,
+ @Valid @RequestBody RestDataSourceDefinition dataContextDefinition) {
+
+ final Map<String, Object> map = new HashMap<>();
+ map.putAll(dataContextDefinition.getProperties());
+ map.put(DataContextPropertiesImpl.PROPERTY_DATA_CONTEXT_TYPE, dataContextDefinition.getType());
+
+ if (!map.containsKey(DataContextPropertiesImpl.PROPERTY_DATABASE)) {
+ // add the data source ID as database name if it is not already set.
+ map.put(DataContextPropertiesImpl.PROPERTY_DATABASE, dataSourceId);
+ }
+
+ final DataContextProperties properties = new DataContextPropertiesImpl(map);
+
+ final String dataContextIdentifier = tenantRegistry.getTenantContext(tenantId).getDataSourceRegistry()
+ .registerDataSource(dataSourceId, properties);
+
+ return get(tenantId, dataContextIdentifier);
+ }
+
+ @RequestMapping(method = RequestMethod.GET)
+ @ResponseBody
+ public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+ @PathVariable("datasource") String dataSourceName) {
+ final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+ final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+ final String tenantName = tenantContext.getTenantName();
+ final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}");
+
+ final List<RestLink> schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s,
+ uriBuilder.build(tenantName, dataSourceName, s))).collect(Collectors.toList());
+
+ final Map<String, Object> map = new LinkedHashMap<>();
+ map.put("type", "datasource");
+ map.put("name", dataSourceName);
+ map.put("tenant", tenantName);
+ map.put("updateable", dataContext instanceof UpdateableDataContext);
+ map.put("query_uri", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantName, dataSourceName));
+ map.put("schemas", schemaLinks);
+ return map;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java
new file mode 100644
index 0000000..693bc4c
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.data.DataSet;
+import org.apache.metamodel.query.Query;
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = { "/{tenant}/{dataContext}/query",
+ "/{tenant}/{dataContext}/q" }, produces = MediaType.APPLICATION_JSON_VALUE)
+public class QueryController {
+
+ private final TenantRegistry tenantRegistry;
+
+ @Autowired
+ public QueryController(TenantRegistry tenantRegistry) {
+ this.tenantRegistry = tenantRegistry;
+ }
+
+ @RequestMapping(method = RequestMethod.GET)
+ @ResponseBody
+ public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+ @PathVariable("dataContext") String dataSourceName,
+ @RequestParam(value = "sql", required = true) String queryString,
+ @RequestParam(value = "offset", required = false) Integer offset,
+ @RequestParam(value = "limit", required = false) Integer limit) {
+ final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+ final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+ final Query query = dataContext.parseQuery(queryString);
+
+ return executeQuery(dataContext, query, offset, limit);
+ }
+
+ public static Map<String, Object> executeQuery(DataContext dataContext, Query query, Integer offset, Integer limit) {
+
+ if (offset != null) {
+ query.setFirstRow(offset);
+ }
+ if (limit != null) {
+ query.setMaxRows(limit);
+ }
+
+ final DataSet dataSet = dataContext.executeQuery(query);
+
+ final Map<String, Object> map = new LinkedHashMap<>();
+ map.put("type", "dataset");
+ map.put("header", Arrays.stream(dataSet.getSelectItems()).map((si) -> si.toString()).collect(Collectors
+ .toList()));
+ map.put("data", dataSet.toObjectArrays());
+ return map;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/6d197c36/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
new file mode 100644
index 0000000..8a08151
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
@@ -0,0 +1,160 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.metamodel.query.parser.QueryParserException;
+import org.apache.metamodel.service.app.exceptions.AbstractIdentifierNamingException;
+import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException;
+import org.apache.metamodel.service.app.exceptions.NoSuchColumnException;
+import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
+import org.apache.metamodel.service.app.exceptions.NoSuchSchemaException;
+import org.apache.metamodel.service.app.exceptions.NoSuchTableException;
+import org.apache.metamodel.service.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException;
+import org.apache.metamodel.service.controllers.model.RestErrorResponse;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ControllerAdvice
+public class RestErrorHandler {
+
+ /**
+ * Method binding issues (raised by Spring framework) - mapped to
+ * BAD_REQUEST.
+ *
+ * @param ex
+ * @return
+ */
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ResponseBody
+ public RestErrorResponse processValidationError(MethodArgumentNotValidException ex) {
+ final BindingResult result = ex.getBindingResult();
+
+ final Map<String, Object> globalErrorsMap = new LinkedHashMap<>();
+ final List<ObjectError> globalErrors = result.getGlobalErrors();
+ for (ObjectError objectError : globalErrors) {
+ globalErrorsMap.put(objectError.getObjectName(), objectError.getDefaultMessage());
+ }
+
+ final List<FieldError> fieldErrors = result.getFieldErrors();
+ final Map<String, Object> fieldErrorsMap = new LinkedHashMap<>();
+ for (FieldError fieldError : fieldErrors) {
+ fieldErrorsMap.put(fieldError.getObjectName() + '.' + fieldError.getField(), fieldError
+ .getDefaultMessage());
+ }
+
+ final Map<String, Object> additionalDetails = new LinkedHashMap<>();
+ if (!globalErrorsMap.isEmpty()) {
+ additionalDetails.put("global-errors", globalErrorsMap);
+ }
+ if (!fieldErrorsMap.isEmpty()) {
+ additionalDetails.put("field-errors", fieldErrorsMap);
+ }
+ final RestErrorResponse errorResponse = new RestErrorResponse(HttpStatus.BAD_REQUEST.value(),
+ "Failed to validate request");
+ if (!additionalDetails.isEmpty()) {
+ errorResponse.setAdditionalDetails(additionalDetails);
+ }
+ return errorResponse;
+ }
+
+ /**
+ * No such [Entity] exception handler method - mapped to NOT_FOUND.
+ *
+ * @param ex
+ * @return
+ */
+ @ExceptionHandler({ NoSuchTenantException.class, NoSuchDataSourceException.class, NoSuchSchemaException.class,
+ NoSuchTableException.class, NoSuchColumnException.class })
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ @ResponseBody
+ public RestErrorResponse processNoSuchEntity(AbstractIdentifierNamingException ex) {
+ return new RestErrorResponse(HttpStatus.NOT_FOUND.value(), "Not found: " + ex.getIdentifier());
+ }
+
+ /**
+ * [Entity] already exist exception handler method - mapped to CONFLICT.
+ *
+ * @param ex
+ * @return
+ */
+ @ExceptionHandler({ TenantAlreadyExistException.class, DataSourceAlreadyExistException.class })
+ @ResponseStatus(HttpStatus.CONFLICT)
+ @ResponseBody
+ public RestErrorResponse processEntityAlreadyExist(AbstractIdentifierNamingException ex) {
+ return new RestErrorResponse(HttpStatus.CONFLICT.value(), "Already exist: " + ex.getIdentifier());
+ }
+
+ /**
+ * DataSource not updateable exception handler method - mapped to
+ * BAD_REQUEST.
+ *
+ * @param ex
+ * @return
+ */
+ @ExceptionHandler(DataSourceNotUpdateableException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ResponseBody
+ public RestErrorResponse processDataSourceNotUpdateable(DataSourceNotUpdateableException ex) {
+ return new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), "DataSource not updateable: " + ex
+ .getDataSourceName());
+ }
+
+ /**
+ * Query parsing exception - mapped to BAD_REQUEST.
+ *
+ * @param ex
+ * @return
+ */
+ @ExceptionHandler(QueryParserException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ResponseBody
+ public RestErrorResponse processQueryParsingError(QueryParserException ex) {
+ return new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
+ }
+
+ /**
+ * Catch-all exception handler method - mapped to INTERNAL_SERVER_ERROR.
+ *
+ * @param ex
+ * @return
+ */
+ @ExceptionHandler(Exception.class)
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ @ResponseBody
+ public RestErrorResponse processAnyException(Exception ex) {
+ final Map<String, Object> additionalDetails = new HashMap<>();
+ additionalDetails.put("exception_type", ex.getClass().getName());
+ return new RestErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage(), additionalDetails);
+ }
+}