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>
