Added a rudimentary table data controller Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/76091fe8 Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/76091fe8 Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/76091fe8
Branch: refs/heads/feature/5.x/swagger-docs Commit: 76091fe80fc9a525df19a183a5a9047d13677510 Parents: 8d4d035 Author: Kasper Sørensen <[email protected]> Authored: Wed Jun 8 02:58:41 2016 -0700 Committer: Kasper Sørensen <[email protected]> Committed: Wed Jun 8 02:58:41 2016 -0700 ---------------------------------------------------------------------- .../service/app/DataContextSupplier.java | 15 ++- .../service/controllers/QueryController.java | 6 + .../controllers/TableDataController.java | 109 +++++++++++++++++++ .../TenantInteractionScenarioTest.java | 46 +++++++- 4 files changed, 171 insertions(+), 5 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/metamodel/blob/76091fe8/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 index 3b36e2b..a0fb6ec 100644 --- 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 @@ -35,14 +35,23 @@ public class DataContextSupplier implements Supplier<DataContext> { private final String dataSourceName; private final DataSourceDefinition dataSourceDefinition; + private final DataContext eagerLoadedDataContext; public DataContextSupplier(String dataSourceName, DataSourceDefinition dataSourceDefinition) { this.dataSourceName = dataSourceName; this.dataSourceDefinition = dataSourceDefinition; + this.eagerLoadedDataContext = createDataContext(true); } @Override public DataContext get() { + if (eagerLoadedDataContext != null) { + return eagerLoadedDataContext; + } + return createDataContext(false); + } + + private DataContext createDataContext(boolean eager) { final String type = dataSourceDefinition.getType(); switch (type.toLowerCase()) { case "pojo": @@ -64,7 +73,11 @@ public class DataContextSupplier implements Supplier<DataContext> { : dataSourceDefinition.getSchemaName(); return new PojoDataContext(schemaName, tableDataProviders); - default: + } + + if (eager) { + return null; + } else { throw new UnsupportedOperationException("Unsupported data source type: " + type); } } http://git-wip-us.apache.org/repos/asf/metamodel/blob/76091fe8/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 index e5c1e72..693bc4c 100644 --- 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 @@ -60,6 +60,12 @@ public class QueryController { 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); } http://git-wip-us.apache.org/repos/asf/metamodel/blob/76091fe8/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..a12bbac --- /dev/null +++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableDataController.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.controllers; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.metamodel.DataContext; +import org.apache.metamodel.UpdateSummary; +import org.apache.metamodel.UpdateableDataContext; +import org.apache.metamodel.insert.InsertInto; +import org.apache.metamodel.query.Query; +import org.apache.metamodel.schema.Schema; +import org.apache.metamodel.schema.Table; +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 Schema schema = dataContext.getSchemaByName(schemaId); + final Table table = schema.getTableByName(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 Map<String, Object> inputMap) { + + final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); + final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName); + if (!(dataContext instanceof UpdateableDataContext)) { + throw new UnsupportedOperationException("Cannot perform updates on read-only datasource: " + + dataSourceName); + } + + final Schema schema = dataContext.getSchemaByName(schemaId); + final Table table = schema.getTableByName(tableId); + + final InsertInto insert = new InsertInto(table); + for (Entry<String, Object> entry : inputMap.entrySet()) { + insert.value(entry.getKey(), entry.getValue()); + } + + final UpdateableDataContext updateableDataContext = (UpdateableDataContext) dataContext; + final UpdateSummary result = updateableDataContext.executeUpdate(insert); + + 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/76091fe8/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 index 35c4695..793f88c 100644 --- 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 @@ -49,9 +49,10 @@ public class TenantInteractionScenarioTest { 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).build(); + tableController, columnController, queryController, tableDataController).build(); } @Test @@ -71,7 +72,7 @@ public class TenantInteractionScenarioTest { // create datasource { final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put("/tenant1/mydata").content( - "{'type':'pojo','table-definitions':'hello world (greeting VARCHAR, who VARCHAR); foo (bar INTEGER, baz DATE);'}" + "{'type':'pojo','table-definitions':'hello_world (greeting VARCHAR, who VARCHAR); foo (bar INTEGER, baz DATE);'}" .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect( MockMvcResultMatchers.status().isOk()).andReturn(); @@ -107,7 +108,7 @@ public class TenantInteractionScenarioTest { 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%20world}]", + "[{name=foo, uri=/tenant1/mydata/s/mydata/t/foo}, {name=hello_world, uri=/tenant1/mydata/s/mydata/t/hello_world}]", map.get("tables").toString()); } @@ -150,7 +151,44 @@ public class TenantInteractionScenarioTest { 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") + 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()); } }
