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>

Reply via email to