You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metamodel.apache.org by ka...@apache.org on 2016/06/14 05:20:45 UTC

[01/11] metamodel git commit: Added scaffolding for 'metamodel-as-a-service' module

Repository: metamodel
Updated Branches:
  refs/heads/feature/5.x/swagger-docs [created] 0a82d13b1


Added scaffolding for 'metamodel-as-a-service' module

Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/c9f56aa7
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/c9f56aa7
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/c9f56aa7

Branch: refs/heads/feature/5.x/swagger-docs
Commit: c9f56aa753334b571ab868912c47d0e629e351ed
Parents: dd0cff7
Author: Kasper S�rensen <i....@gmail.com>
Authored: Mon May 23 21:36:33 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Mon May 23 21:55:51 2016 -0700

----------------------------------------------------------------------
 pom.xml                                         | 30 +++++++
 service-webapp/pom.xml                          | 84 +++++++++++++++++
 .../controllers/RootInformationController.java  | 95 ++++++++++++++++++++
 .../resources/context/application-context.xml   | 32 +++++++
 service-webapp/src/main/resources/logback.xml   | 33 +++++++
 .../main/webapp/WEB-INF/dispatcher-servlet.xml  | 57 ++++++++++++
 service-webapp/src/main/webapp/WEB-INF/web.xml  | 66 ++++++++++++++
 .../RootInformationControllerTest.java          | 61 +++++++++++++
 spring/pom.xml                                  | 12 ---
 9 files changed, 458 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/c9f56aa7/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 6b4b2b3..0e9d696 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,6 +29,7 @@ under the License.
 		<hadoop.version>2.6.0</hadoop.version>
 		<jackson.version>2.6.3</jackson.version>
 		<easymock.version>3.2</easymock.version>
+		<spring.version>4.2.6.RELEASE</spring.version>
 		<httpcomponents.version>4.4.1</httpcomponents.version>
 		<checksum-maven-plugin.version>1.2</checksum-maven-plugin.version>
 		<skipTests>false</skipTests>
@@ -77,6 +78,7 @@ under the License.
 		<module>full</module>
 		<module>spring</module>
 		<module>neo4j</module>
+		<module>service-webapp</module>
 	</modules>
 	<issueManagement>
 		<system>Jira</system>
@@ -534,6 +536,34 @@ under the License.
 				<artifactId>hsqldb</artifactId>
 				<version>1.8.0.10</version>
 			</dependency>
+			
+			<!-- Spring -->
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-core</artifactId>
+				<version>${spring.version}</version>
+			    <exclusions>
+			    	<exclusion>
+			    		<groupId>commons-logging</groupId>
+			    		<artifactId>commons-logging</artifactId>
+			    	</exclusion>
+			    </exclusions>
+			</dependency>
+			<dependency>
+			    <groupId>org.springframework</groupId>
+			    <artifactId>spring-context</artifactId>
+			    <version>${spring.version}</version>
+			</dependency>
+			<dependency>
+			    <groupId>org.springframework</groupId>
+			    <artifactId>spring-test</artifactId>
+			    <version>${spring.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-webmvc</artifactId>
+				<version>${spring.version}</version>
+			</dependency>
 
 			<!-- Hadoop -->
 			<dependency>

http://git-wip-us.apache.org/repos/asf/metamodel/blob/c9f56aa7/service-webapp/pom.xml
----------------------------------------------------------------------
diff --git a/service-webapp/pom.xml b/service-webapp/pom.xml
new file mode 100644
index 0000000..f3836dd
--- /dev/null
+++ b/service-webapp/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<parent>
+		<artifactId>MetaModel</artifactId>
+		<groupId>org.apache.metamodel</groupId>
+		<version>5.0-SNAPSHOT</version>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+	<artifactId>MetaModel-service-webapp</artifactId>
+	<name>MetaModel-as\u2013a-service web application</name>
+	<packaging>war</packaging>
+
+	<build>
+		<finalName>MetaModel</finalName>
+	</build>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-webmvc</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-databind</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-classic</artifactId>
+			<version>1.1.7</version>
+		</dependency>
+		<dependency>
+			<groupId>org.hibernate</groupId>
+			<artifactId>hibernate-validator</artifactId>
+			<version>5.2.4.Final</version>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>jcl-over-slf4j</artifactId>
+		</dependency>
+
+		<!-- Provided -->
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+			<version>3.1.0</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<!-- Test -->
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metamodel/blob/c9f56aa7/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..794f85a
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RootInformationController.java
@@ -0,0 +1,95 @@
+/**
+ * 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);
+        }
+        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/c9f56aa7/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..b0c945d
--- /dev/null
+++ b/service-webapp/src/main/resources/context/application-context.xml
@@ -0,0 +1,32 @@
+<?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" />
+
+</beans>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metamodel/blob/c9f56aa7/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..de862de
--- /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>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metamodel/blob/c9f56aa7/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..12af16f
--- /dev/null
+++ b/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml
@@ -0,0 +1,57 @@
+<?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>
+
+	<!-- 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>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/metamodel/blob/c9f56aa7/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..eb3ef51
--- /dev/null
+++ b/service-webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,66 @@
+<?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/c9f56aa7/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..58a5ca5
--- /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 testGenericMessageSuccess() 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/c9f56aa7/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>


[09/11] metamodel git commit: Added error handling component

Posted by ka...@apache.org.
Added error handling component

Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/704790b1
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/704790b1
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/704790b1

Branch: refs/heads/feature/5.x/swagger-docs
Commit: 704790b16ae8754c693251149127ef12b530df7a
Parents: 76091fe
Author: Kasper S�rensen <i....@gmail.com>
Authored: Sun Jun 12 21:33:17 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Sun Jun 12 21:58:28 2016 -0700

----------------------------------------------------------------------
 pom.xml                                         |   2 +-
 .../service/app/DataSourceRegistry.java         |  16 +-
 .../service/app/InMemoryDataSourceRegistry.java |   8 +-
 .../service/app/InMemoryTenantRegistry.java     |  18 ++-
 .../metamodel/service/app/TenantRegistry.java   |  13 +-
 .../AbstractIdentifierNamingException.java      |  40 +++++
 .../DataSourceAlreadyExistException.java        |  28 ++++
 .../DataSourceNotUpdateableException.java       |  37 +++++
 .../exceptions/NoSuchDataSourceException.java   |  29 ++++
 .../app/exceptions/NoSuchTenantException.java   |  28 ++++
 .../exceptions/TenantAlreadyExistException.java |  28 ++++
 .../controllers/DataSourceController.java       |   8 +-
 .../service/controllers/RestErrorHandler.java   | 156 +++++++++++++++++++
 .../controllers/TableDataController.java        |   6 +-
 .../service/controllers/TenantController.java   |  12 +-
 .../controllers/model/RestErrorResponse.java    |  80 ++++++++++
 16 files changed, 476 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 0e9d696..678dd35 100644
--- a/pom.xml
+++ b/pom.xml
@@ -372,7 +372,7 @@ under the License.
 						<excludeSubProjects>false</excludeSubProjects>
 						<excludes>
 							<exclude>KEYS</exclude>
-							<exclude>*.md</exclude>
+							<exclude>**/*.md</exclude>
 							<exclude>example-metamodel-integrationtest-configuration.properties</exclude>
 							<exclude>travis-metamodel-integrationtest-configuration.properties</exclude>
 							<exclude>**/src/assembly/metamodel-packaged-assembly-descriptor.xml</exclude>

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
index 1930354..3a9ba43 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
@@ -21,6 +21,10 @@ package org.apache.metamodel.service.app;
 import java.util.List;
 
 import org.apache.metamodel.DataContext;
+import org.apache.metamodel.UpdateableDataContext;
+import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException;
+import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
 
 /**
  * Represents a user's/tenant's registry of {@link DataContext}s.
@@ -29,7 +33,15 @@ public interface DataSourceRegistry {
 
     public List<String> getDataSourceNames();
 
-    public String registerDataSource(String dataContextName, DataSourceDefinition dataSourceDef) throws IllegalArgumentException;
+    public String registerDataSource(String dataContextName, DataSourceDefinition dataSourceDef) throws DataSourceAlreadyExistException;
 
-    public DataContext openDataContext(String dataSourceName) throws IllegalArgumentException;
+    public DataContext openDataContext(String dataSourceName) throws NoSuchDataSourceException;
+
+    public default UpdateableDataContext openDataContextForUpdate(String dataSourceName) {
+        final DataContext dataContext = openDataContext(dataSourceName);
+        if (dataContext instanceof UpdateableDataContext) {
+            return (UpdateableDataContext) dataContext;
+        }
+        throw new DataSourceNotUpdateableException(dataSourceName);
+    };
 }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
index 3a6ecee..b99c6e7 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
@@ -25,6 +25,8 @@ import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 import org.apache.metamodel.DataContext;
+import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
 
 public class InMemoryDataSourceRegistry implements DataSourceRegistry {
 
@@ -36,9 +38,9 @@ public class InMemoryDataSourceRegistry implements DataSourceRegistry {
 
     @Override
     public String registerDataSource(final String name, final DataSourceDefinition dataSourceDef)
-            throws IllegalArgumentException {
+            throws DataSourceAlreadyExistException {
         if (dataSources.containsKey(name)) {
-            throw new IllegalArgumentException("DataContext already exist: " + name);
+            throw new DataSourceAlreadyExistException(name);
         }
 
         dataSources.put(name, new DataContextSupplier(name, dataSourceDef));
@@ -54,7 +56,7 @@ public class InMemoryDataSourceRegistry implements DataSourceRegistry {
     public DataContext openDataContext(String name) {
         final Supplier<DataContext> supplier = dataSources.get(name);
         if (supplier == null) {
-            throw new IllegalArgumentException("No such DataContext: " + name);
+            throw new NoSuchDataSourceException(name);
         }
         return supplier.get();
     }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
index c74fb22..6ac5bec 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
@@ -23,6 +23,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import org.apache.metamodel.service.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException;
+
 /**
  * In-memory {@link TenantRegistry}. This is not particularly
  * production-friendly as it is non-persistent, but it is useful for demo
@@ -43,13 +46,17 @@ public class InMemoryTenantRegistry implements TenantRegistry {
 
     @Override
     public TenantContext getTenantContext(String tenantIdentifier) {
-        return tenants.get(tenantIdentifier);
+        final TenantContext tenant = tenants.get(tenantIdentifier);
+        if (tenant == null) {
+            throw new NoSuchTenantException(tenantIdentifier);
+        }
+        return tenant;
     }
 
     @Override
     public TenantContext createTenantContext(String tenantIdentifier) {
         if (tenants.containsKey(tenantIdentifier)) {
-            throw new IllegalArgumentException("Tenant already exist: " + tenantIdentifier);
+            throw new TenantAlreadyExistException(tenantIdentifier);
         }
         final InMemoryTenantContext tenantContext = new InMemoryTenantContext(tenantIdentifier);
         tenants.put(tenantIdentifier, tenantContext);
@@ -57,8 +64,11 @@ public class InMemoryTenantRegistry implements TenantRegistry {
     }
 
     @Override
-    public boolean deleteTenantContext(String tenantIdentifier) {
-        return tenants.remove(tenantIdentifier) != null;
+    public void deleteTenantContext(String tenantIdentifier) {
+        final TenantContext removedTenant = tenants.remove(tenantIdentifier);
+        if (removedTenant == null) {
+            throw new NoSuchTenantException(tenantIdentifier);
+        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
index 450db2d..5c02821 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
@@ -20,6 +20,9 @@ package org.apache.metamodel.service.app;
 
 import java.util.List;
 
+import org.apache.metamodel.service.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException;
+
 /**
  * Represents the application's central registry of tenants
  */
@@ -27,9 +30,9 @@ public interface TenantRegistry {
 
     public List<String> getTenantIdentifiers();
 
-    public TenantContext getTenantContext(String tenantIdentifier);
-    
-    public TenantContext createTenantContext(String tenantIdentifier) throws IllegalArgumentException;
-    
-    public boolean deleteTenantContext(String tenantIdentifier);
+    public TenantContext getTenantContext(String tenantIdentifier) throws NoSuchTenantException;
+
+    public TenantContext createTenantContext(String tenantIdentifier) throws TenantAlreadyExistException;
+
+    public void deleteTenantContext(String tenantIdentifier) throws NoSuchTenantException;
 }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java
new file mode 100644
index 0000000..98f5892
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/AbstractIdentifierNamingException.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+import org.apache.metamodel.MetaModelException;
+
+/**
+ * Exception super class for any exception that arises because an identifier
+ * (name, ID or such) is invalid for a specific context.
+ */
+public class AbstractIdentifierNamingException extends MetaModelException {
+
+    private static final long serialVersionUID = 1L;
+    private final String identifier;
+
+    public AbstractIdentifierNamingException(String identifier) {
+        super("Illegal value: " + identifier);
+        this.identifier = identifier;
+    }
+
+    public String getIdentifier() {
+        return identifier;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java
new file mode 100644
index 0000000..3d5d650
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceAlreadyExistException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class DataSourceAlreadyExistException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public DataSourceAlreadyExistException(String name) {
+        super(name);
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java
new file mode 100644
index 0000000..eb828cc
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/DataSourceNotUpdateableException.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+import org.apache.metamodel.MetaModelException;
+
+public class DataSourceNotUpdateableException extends MetaModelException {
+
+    private static final long serialVersionUID = 1L;
+
+    private final String dataSourceName;
+
+    public DataSourceNotUpdateableException(String dataSourceName) {
+        super(dataSourceName);
+        this.dataSourceName = dataSourceName;
+    }
+
+    public String getDataSourceName() {
+        return dataSourceName;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java
new file mode 100644
index 0000000..b59f016
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchDataSourceException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchDataSourceException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public NoSuchDataSourceException(String name) {
+        super(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java
new file mode 100644
index 0000000..b123ae2
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTenantException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchTenantException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public NoSuchTenantException(String tenantIdentifier) {
+        super(tenantIdentifier);
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java
new file mode 100644
index 0000000..f24c114
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/TenantAlreadyExistException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class TenantAlreadyExistException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public TenantAlreadyExistException(String tenantIdentifier) {
+        super(tenantIdentifier);
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
index 8be66b1..9c678ff 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
@@ -28,10 +28,11 @@ import javax.validation.Valid;
 import javax.ws.rs.core.UriBuilder;
 
 import org.apache.metamodel.DataContext;
+import org.apache.metamodel.UpdateableDataContext;
 import org.apache.metamodel.service.app.TenantContext;
 import org.apache.metamodel.service.app.TenantRegistry;
-import org.apache.metamodel.service.controllers.model.RestLink;
 import org.apache.metamodel.service.controllers.model.RestDataSourceDefinition;
+import org.apache.metamodel.service.controllers.model.RestLink;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -73,13 +74,14 @@ public class DataSourceController {
         final String tenantName = tenantContext.getTenantName();
         final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}");
 
-        final List<RestLink> schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s, uriBuilder
-                .build(tenantName, dataSourceName, s))).collect(Collectors.toList());
+        final List<RestLink> schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s,
+                uriBuilder.build(tenantName, dataSourceName, s))).collect(Collectors.toList());
 
         final Map<String, Object> map = new LinkedHashMap<>();
         map.put("type", "datasource");
         map.put("name", dataSourceName);
         map.put("tenant", tenantName);
+        map.put("updateable", dataContext instanceof UpdateableDataContext);
         map.put("query", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantName, dataSourceName));
         map.put("schemas", schemaLinks);
         return map;

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
new file mode 100644
index 0000000..690ee0e
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
@@ -0,0 +1,156 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.metamodel.query.parser.QueryParserException;
+import org.apache.metamodel.service.app.exceptions.AbstractIdentifierNamingException;
+import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException;
+import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
+import org.apache.metamodel.service.app.exceptions.NoSuchTenantException;
+import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException;
+import org.apache.metamodel.service.controllers.model.RestErrorResponse;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ControllerAdvice
+public class RestErrorHandler {
+
+    /**
+     * Method binding issues (raised by Spring framework) - mapped to
+     * BAD_REQUEST.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ResponseBody
+    public RestErrorResponse processValidationError(MethodArgumentNotValidException ex) {
+        final BindingResult result = ex.getBindingResult();
+
+        final Map<String, Object> globalErrorsMap = new LinkedHashMap<>();
+        final List<ObjectError> globalErrors = result.getGlobalErrors();
+        for (ObjectError objectError : globalErrors) {
+            globalErrorsMap.put(objectError.getObjectName(), objectError.getDefaultMessage());
+        }
+
+        final List<FieldError> fieldErrors = result.getFieldErrors();
+        final Map<String, Object> fieldErrorsMap = new LinkedHashMap<>();
+        for (FieldError fieldError : fieldErrors) {
+            fieldErrorsMap.put(fieldError.getObjectName() + '.' + fieldError.getField(), fieldError
+                    .getDefaultMessage());
+        }
+
+        final Map<String, Object> additionalDetails = new LinkedHashMap<>();
+        if (!globalErrorsMap.isEmpty()) {
+            additionalDetails.put("global-errors", globalErrorsMap);
+        }
+        if (!fieldErrorsMap.isEmpty()) {
+            additionalDetails.put("field-errors", fieldErrorsMap);
+        }
+        final RestErrorResponse errorResponse = new RestErrorResponse(HttpStatus.BAD_REQUEST.value(),
+                "Failed to validate request");
+        if (!additionalDetails.isEmpty()) {
+            errorResponse.setAdditionalDetails(additionalDetails);
+        }
+        return errorResponse;
+    }
+
+    /**
+     * No such [Entity] exception handler method - mapped to NOT_FOUND.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler({ NoSuchTenantException.class, NoSuchDataSourceException.class })
+    @ResponseStatus(HttpStatus.NOT_FOUND)
+    @ResponseBody
+    public RestErrorResponse processNoSuchEntity(AbstractIdentifierNamingException ex) {
+        return new RestErrorResponse(HttpStatus.NOT_FOUND.value(), "Not found: " + ex.getIdentifier());
+    }
+
+    /**
+     * [Entity] already exist exception handler method - mapped to CONFLICT.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler({ TenantAlreadyExistException.class, DataSourceAlreadyExistException.class })
+    @ResponseStatus(HttpStatus.CONFLICT)
+    @ResponseBody
+    public RestErrorResponse processEntityAlreadyExist(AbstractIdentifierNamingException ex) {
+        return new RestErrorResponse(HttpStatus.CONFLICT.value(), "Already exist: " + ex.getIdentifier());
+    }
+
+    /**
+     * DataSource not updateable exception handler method - mapped to
+     * BAD_REQUEST.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(DataSourceNotUpdateableException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ResponseBody
+    public RestErrorResponse processDataSourceNotUpdateable(DataSourceNotUpdateableException ex) {
+        return new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), "DataSource not updateable: " + ex
+                .getDataSourceName());
+    }
+
+    /**
+     * Query parsing exception - mapped to BAD_REQUEST.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(QueryParserException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ResponseBody
+    public RestErrorResponse processQueryParsingError(QueryParserException ex) {
+        return new RestErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
+    }
+
+    /**
+     * Catch-all exception handler method - mapped to INTERNAL_SERVER_ERROR.
+     * 
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(Exception.class)
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    @ResponseBody
+    public RestErrorResponse processAnyException(Exception ex) {
+        final Map<String, Object> additionalDetails = new HashMap<>();
+        additionalDetails.put("exception_type", ex.getClass().getName());
+        return new RestErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage(), additionalDetails);
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/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
index a12bbac..e25258f 100644
--- 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
@@ -77,11 +77,7 @@ public class TableDataController {
             @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 UpdateableDataContext dataContext = tenantContext.getDataSourceRegistry().openDataContextForUpdate(dataSourceName);
 
         final Schema schema = dataContext.getSchemaByName(schemaId);
         final Table table = schema.getTableByName(tableId);

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/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
index ef3011f..9582bbe 100644
--- 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
@@ -51,10 +51,6 @@ public class TenantController {
     @ResponseBody
     public Map<String, Object> getTenant(@PathVariable("tenant") String tenantName) {
         final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantName);
-        if (tenantContext == null) {
-            throw new IllegalArgumentException("No such tenant: " + tenantName);
-        }
-
         final String tenantNameNormalized = tenantContext.getTenantName();
 
         final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{datasource}");
@@ -86,16 +82,12 @@ public class TenantController {
     @RequestMapping(method = RequestMethod.DELETE)
     @ResponseBody
     public Map<String, Object> deleteTenant(@PathVariable("tenant") String tenantName) {
-        final boolean deleted = tenantRegistry.deleteTenantContext(tenantName);
-
-        if (!deleted) {
-            throw new IllegalArgumentException("No such tenant: " + tenantName);
-        }
+        tenantRegistry.deleteTenantContext(tenantName);
 
         final Map<String, Object> map = new LinkedHashMap<>();
         map.put("type", "tenant");
         map.put("name", tenantName);
-        map.put("deleted", deleted);
+        map.put("deleted", true);
 
         return map;
     }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/704790b1/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;
+    }
+
+}


[11/11] metamodel git commit: Updated interface definitions as per new Swagger yaml file

Posted by ka...@apache.org.
Updated interface definitions as per new Swagger yaml file

Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/0a82d13b
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/0a82d13b
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/0a82d13b

Branch: refs/heads/feature/5.x/swagger-docs
Commit: 0a82d13b19ea0f8aa3aabcd140caaf3274a8e5b4
Parents: 79af7dd
Author: Kasper S�rensen <i....@gmail.com>
Authored: Mon Jun 13 22:15:34 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Mon Jun 13 22:15:34 2016 -0700

----------------------------------------------------------------------
 .../service/controllers/ColumnController.java   |   2 +-
 .../controllers/DataSourceController.java       |   2 +-
 .../service/controllers/SchemaController.java   |   2 +-
 .../service/controllers/TableController.java    |   2 +-
 .../controllers/TableDataController.java        |  26 +-
 .../TenantInteractionScenarioTest.java          |   4 +-
 service-webapp/swagger.yaml                     | 503 +++++++++++++++++++
 7 files changed, 526 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/0a82d13b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
index 7d6d5ff..c811def 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
@@ -77,7 +77,7 @@ public class ColumnController {
         map.put("name", column.getName());
         map.put("table", tableName);
         map.put("schema", schemaName);
-        map.put("data-context", dataSourceName);
+        map.put("datasource", dataSourceName);
         map.put("tenant", tenantName);
         map.put("metadata", metadata);
 

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0a82d13b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
index 9c678ff..524cc09 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
@@ -82,7 +82,7 @@ public class DataSourceController {
         map.put("name", dataSourceName);
         map.put("tenant", tenantName);
         map.put("updateable", dataContext instanceof UpdateableDataContext);
-        map.put("query", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantName, dataSourceName));
+        map.put("query_uri", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantName, dataSourceName));
         map.put("schemas", schemaLinks);
         return map;
     }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0a82d13b/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
index 6208d49..993c1d3 100644
--- 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
@@ -72,7 +72,7 @@ public class SchemaController {
         final Map<String, Object> map = new LinkedHashMap<>();
         map.put("type", "schema");
         map.put("name", schemaName);
-        map.put("data-context", dataSourceName);
+        map.put("datasource", dataSourceName);
         map.put("tenant", tenantName);
         map.put("tables", tableLinks);
         return map;

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0a82d13b/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
index 4157dac..c914289 100644
--- 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
@@ -77,7 +77,7 @@ public class TableController {
         map.put("type", "table");
         map.put("name", tableName);
         map.put("schema", schemaName);
-        map.put("data-context", dataSourceName);
+        map.put("datasource", dataSourceName);
         map.put("tenant", tenantName);
         map.put("columns", columnsLinks);
         return map;

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0a82d13b/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
index c48d74f..bae9923 100644
--- 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
@@ -19,13 +19,16 @@
 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.InsertInto;
+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;
@@ -75,7 +78,7 @@ public class TableDataController {
     @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) {
+            @PathVariable("table") String tableId, @RequestBody final List<Map<String, Object>> inputRecords) {
 
         final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
         final UpdateableDataContext dataContext = tenantContext.getDataSourceRegistry().openDataContextForUpdate(dataSourceName);
@@ -84,13 +87,18 @@ public class TableDataController {
 
         final Table table = traverser.getTable(schemaId, 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 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");

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0a82d13b/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 793f88c..5706153 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
@@ -158,7 +158,7 @@ public class TenantInteractionScenarioTest {
         // insert into table (x2)
         {
             final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(
-                    "/tenant1/mydata/s/mydata/t/hello_world/d").content("{'greeting':'Howdy','who':'MetaModel'}"
+                    "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Howdy','who':'MetaModel'}]"
                             .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect(
                                     MockMvcResultMatchers.status().isOk()).andReturn();
 
@@ -168,7 +168,7 @@ public class TenantInteractionScenarioTest {
         }
         {
             final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(
-                    "/tenant1/mydata/s/mydata/t/hello_world/d").content("{'greeting':'Hi','who':'Apache'}"
+                    "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Hi','who':'Apache'}]"
                             .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect(
                                     MockMvcResultMatchers.status().isOk()).andReturn();
 

http://git-wip-us.apache.org/repos/asf/metamodel/blob/0a82d13b/service-webapp/swagger.yaml
----------------------------------------------------------------------
diff --git a/service-webapp/swagger.yaml b/service-webapp/swagger.yaml
new file mode 100644
index 0000000..1edd47a
--- /dev/null
+++ b/service-webapp/swagger.yaml
@@ -0,0 +1,503 @@
+swagger: '2.0'
+info:
+  title: Apache MetaModel RESTful API
+  description: Delivers 'MetaModel-as-a-Service' for unified data federation.
+  version: "5.0.0"
+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"
+  /{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


[07/11] metamodel git commit: Added ability to query data and to specify POJO table defs.

Posted by ka...@apache.org.
Added ability to query data and to specify POJO table defs.

Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/8d4d0351
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/8d4d0351
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/8d4d0351

Branch: refs/heads/feature/5.x/swagger-docs
Commit: 8d4d03519d56ce482139ea42fb178504e288c892
Parents: 8722c19
Author: Kasper S�rensen <i....@gmail.com>
Authored: Wed Jun 8 02:35:20 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Wed Jun 8 02:39:27 2016 -0700

----------------------------------------------------------------------
 .../service/app/DataContextSupplier.java        | 72 ++++++++++++++++++
 .../service/app/DataSourceDefinition.java       |  6 ++
 .../service/app/InMemoryDataSourceRegistry.java | 24 +++---
 .../service/controllers/QueryController.java    | 79 ++++++++++++++++++++
 .../model/RestDataSourceDefinition.java         | 25 ++++++-
 .../TenantInteractionScenarioTest.java          | 66 ++++++++--------
 6 files changed, 226 insertions(+), 46 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/8d4d0351/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java
new file mode 100644
index 0000000..3b36e2b
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextSupplier.java
@@ -0,0 +1,72 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.pojo.ArrayTableDataProvider;
+import org.apache.metamodel.pojo.PojoDataContext;
+import org.apache.metamodel.pojo.TableDataProvider;
+import org.apache.metamodel.util.SimpleTableDef;
+import org.apache.metamodel.util.SimpleTableDefParser;
+
+public class DataContextSupplier implements Supplier<DataContext> {
+
+    private final String dataSourceName;
+    private final DataSourceDefinition dataSourceDefinition;
+
+    public DataContextSupplier(String dataSourceName, DataSourceDefinition dataSourceDefinition) {
+        this.dataSourceName = dataSourceName;
+        this.dataSourceDefinition = dataSourceDefinition;
+    }
+
+    @Override
+    public DataContext get() {
+        final String type = dataSourceDefinition.getType();
+        switch (type.toLowerCase()) {
+        case "pojo":
+            final List<TableDataProvider<?>> tableDataProviders;
+
+            final Object tableDefinitions = dataSourceDefinition.getTableDefinitions();
+            if (tableDefinitions == null) {
+                tableDataProviders = new ArrayList<>(0);
+            } else if (tableDefinitions instanceof String) {
+                final SimpleTableDef[] tableDefs = SimpleTableDefParser.parseTableDefs((String) tableDefinitions);
+                tableDataProviders = Arrays.stream(tableDefs).map((tableDef) -> {
+                    return new ArrayTableDataProvider(tableDef, new ArrayList<Object[]>());
+                }).collect(Collectors.toList());
+            } else {
+                throw new UnsupportedOperationException("Unsupported table definition type: " + tableDefinitions);
+            }
+
+            final String schemaName = dataSourceDefinition.getSchemaName() == null ? dataSourceName
+                    : dataSourceDefinition.getSchemaName();
+
+            return new PojoDataContext(schemaName, tableDataProviders);
+        default:
+            throw new UnsupportedOperationException("Unsupported data source type: " + type);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8d4d0351/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
index 943222a..11140a7 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
@@ -20,5 +20,11 @@ package org.apache.metamodel.service.app;
 
 public interface DataSourceDefinition {
 
+    public String getType();
+    
+    public Object getTableDefinitions();
+    
+    public String getSchemaName();
+    
     // TODO
 }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8d4d0351/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
index f3c45e8..3a6ecee 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
@@ -25,7 +25,6 @@ import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 import org.apache.metamodel.DataContext;
-import org.apache.metamodel.pojo.PojoDataContext;
 
 public class InMemoryDataSourceRegistry implements DataSourceRegistry {
 
@@ -36,19 +35,14 @@ public class InMemoryDataSourceRegistry implements DataSourceRegistry {
     }
 
     @Override
-    public String registerDataSource(final String dataContextIdentifier, final DataSourceDefinition dataContextDef)
+    public String registerDataSource(final String name, final DataSourceDefinition dataSourceDef)
             throws IllegalArgumentException {
-        if (dataSources.containsKey(dataContextIdentifier)) {
-            throw new IllegalArgumentException("DataContext already exist: " + dataContextIdentifier);
+        if (dataSources.containsKey(name)) {
+            throw new IllegalArgumentException("DataContext already exist: " + name);
         }
-        dataSources.put(dataContextIdentifier, new Supplier<DataContext>() {
-            @Override
-            public DataContext get() {
-                // TODO: Do a proper transformation from definition to instance
-                return new PojoDataContext();
-            }
-        });
-        return dataContextIdentifier;
+
+        dataSources.put(name, new DataContextSupplier(name, dataSourceDef));
+        return name;
     }
 
     @Override
@@ -57,10 +51,10 @@ public class InMemoryDataSourceRegistry implements DataSourceRegistry {
     }
 
     @Override
-    public DataContext openDataContext(String dataContextIdentifier) {
-        final Supplier<DataContext> supplier = dataSources.get(dataContextIdentifier);
+    public DataContext openDataContext(String name) {
+        final Supplier<DataContext> supplier = dataSources.get(name);
         if (supplier == null) {
-            throw new IllegalArgumentException("No such DataContext: " + dataContextIdentifier);
+            throw new IllegalArgumentException("No such DataContext: " + name);
         }
         return supplier.get();
     }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8d4d0351/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java
new file mode 100644
index 0000000..e5c1e72
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/QueryController.java
@@ -0,0 +1,79 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.data.DataSet;
+import org.apache.metamodel.query.Query;
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = { "/{tenant}/{dataContext}/query",
+        "/{tenant}/{dataContext}/q" }, produces = MediaType.APPLICATION_JSON_VALUE)
+public class QueryController {
+
+    private final TenantRegistry tenantRegistry;
+
+    @Autowired
+    public QueryController(TenantRegistry tenantRegistry) {
+        this.tenantRegistry = tenantRegistry;
+    }
+
+    @RequestMapping(method = RequestMethod.GET)
+    @ResponseBody
+    public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+            @PathVariable("dataContext") String dataSourceName,
+            @RequestParam(value = "sql", required = true) String queryString,
+            @RequestParam(value = "offset", required = false) Integer offset,
+            @RequestParam(value = "limit", required = false) Integer limit) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+        final Query query = dataContext.parseQuery(queryString);
+        if (offset != null) {
+            query.setFirstRow(offset);
+        }
+        if (limit != null) {
+            query.setMaxRows(limit);
+        }
+
+        final DataSet dataSet = dataContext.executeQuery(query);
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "dataset");
+        map.put("header", Arrays.stream(dataSet.getSelectItems()).map((si) -> si.toString()).collect(Collectors
+                .toList()));
+        map.put("data", dataSet.toObjectArrays());
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8d4d0351/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
index ad5f5ba..48c5926 100644
--- 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
@@ -22,12 +22,35 @@ import javax.validation.constraints.NotNull;
 
 import org.apache.metamodel.service.app.DataSourceDefinition;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
 public class RestDataSourceDefinition implements DataSourceDefinition {
 
     @JsonProperty(value = "type", required = true)
     @NotNull
-    public String type;
+    private String type;
 
+    @JsonProperty(value = "table-definitions", required = false)
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private Object tableDefinitions;
+
+    @JsonProperty(value = "schema-name", required = false)
+    @JsonInclude(JsonInclude.Include.NON_NULL)
+    private String schemaName;
+
+    @Override
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public Object getTableDefinitions() {
+        return tableDefinitions;
+    }
+
+    @Override
+    public String getSchemaName() {
+        return schemaName;
+    }
 }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8d4d0351/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 acdbb15..35c4695 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
@@ -48,9 +48,10 @@ public class TenantInteractionScenarioTest {
         SchemaController schemaController = new SchemaController(tenantRegistry);
         TableController tableController = new TableController(tenantRegistry);
         ColumnController columnController = new ColumnController(tenantRegistry);
+        QueryController queryController = new QueryController(tenantRegistry);
 
         mockMvc = MockMvcBuilders.standaloneSetup(tenantController, dataContextController, schemaController,
-                tableController, columnController).build();
+                tableController, columnController, queryController).build();
     }
 
     @Test
@@ -70,15 +71,16 @@ public class TenantInteractionScenarioTest {
         // create datasource
         {
             final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.put("/tenant1/mydata").content(
-                    "{'type':'pojo'}".replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect(
-                            MockMvcResultMatchers.status().isOk()).andReturn();
+                    "{'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();
 
             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=Schema, uri=/tenant1/mydata/s/Schema}]",
+                    "[{name=information_schema, uri=/tenant1/mydata/s/information_schema}, {name=mydata, uri=/tenant1/mydata/s/mydata}]",
                     map.get("schemas").toString());
         }
 
@@ -96,56 +98,60 @@ public class TenantInteractionScenarioTest {
 
         // explore schema
         {
-            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/tenant1/mydata/s/information_schema"))
-                    .andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
+            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("information_schema", map.get("name"));
+            assertEquals("mydata", map.get("name"));
 
-            assertEquals("[{name=tables, uri=/tenant1/mydata/s/information_schema/t/tables}, "
-                    + "{name=columns, uri=/tenant1/mydata/s/information_schema/t/columns}, "
-                    + "{name=relationships, uri=/tenant1/mydata/s/information_schema/t/relationships}]", map.get(
-                            "tables").toString());
+            assertEquals(
+                    "[{name=foo, uri=/tenant1/mydata/s/mydata/t/foo}, {name=hello world, uri=/tenant1/mydata/s/mydata/t/hello%20world}]",
+                    map.get("tables").toString());
         }
 
         // explore table
         {
-            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(
-                    "/tenant1/mydata/s/information_schema/t/columns")).andExpect(MockMvcResultMatchers.status().isOk())
-                    .andReturn();
+            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("columns", map.get("name"));
-
-            assertEquals("[{name=name, uri=/tenant1/mydata/s/information_schema/t/columns/c/name}, "
-                    + "{name=type, uri=/tenant1/mydata/s/information_schema/t/columns/c/type}, "
-                    + "{name=native_type, uri=/tenant1/mydata/s/information_schema/t/columns/c/native_type}, "
-                    + "{name=size, uri=/tenant1/mydata/s/information_schema/t/columns/c/size}, "
-                    + "{name=nullable, uri=/tenant1/mydata/s/information_schema/t/columns/c/nullable}, "
-                    + "{name=indexed, uri=/tenant1/mydata/s/information_schema/t/columns/c/indexed}, "
-                    + "{name=table, uri=/tenant1/mydata/s/information_schema/t/columns/c/table}, "
-                    + "{name=remarks, uri=/tenant1/mydata/s/information_schema/t/columns/c/remarks}]", map.get(
-                            "columns").toString());
+            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/information_schema/t/columns/c/nullable")).andExpect(MockMvcResultMatchers
-                            .status().isOk()).andReturn();
+            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("nullable", map.get("name"));
+            assertEquals("bar", map.get("name"));
 
             assertEquals(
-                    "{number=4, size=null, nullable=true, primary-key=false, indexed=false, column-type=BOOLEAN, native-type=null, remarks=null}",
+                    "{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());
+        }
     }
 }


[05/11] metamodel git commit: Fixed cosmetic review remarks (EOLs and Docker EXPOSE redundancy)

Posted by ka...@apache.org.
Fixed cosmetic review remarks (EOLs and Docker EXPOSE redundancy)

Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/6f6f776b
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/6f6f776b
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/6f6f776b

Branch: refs/heads/feature/5.x/swagger-docs
Commit: 6f6f776b1372f7dc6a24e427ced1c6046d696004
Parents: 972ea4b
Author: Kasper S�rensen <i....@gmail.com>
Authored: Mon Jun 6 20:14:03 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Mon Jun 6 20:14:03 2016 -0700

----------------------------------------------------------------------
 service-webapp/Dockerfile                                         | 1 -
 service-webapp/src/main/resources/context/application-context.xml | 2 +-
 service-webapp/src/main/resources/logback.xml                     | 2 +-
 service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml     | 2 +-
 4 files changed, 3 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/6f6f776b/service-webapp/Dockerfile
----------------------------------------------------------------------
diff --git a/service-webapp/Dockerfile b/service-webapp/Dockerfile
index 033d6e3..d154aca 100644
--- a/service-webapp/Dockerfile
+++ b/service-webapp/Dockerfile
@@ -21,5 +21,4 @@ RUN rm -rf $CATALINA_HOME/webapps \
  mkdir $CATALINA_HOME/webapps
 COPY target/MetaModel.war $CATALINA_HOME/webapps/ROOT.war
 
-EXPOSE 8080
 CMD ["catalina.sh", "run"]

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6f6f776b/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
index 89a1b28..e31feb1 100644
--- a/service-webapp/src/main/resources/context/application-context.xml
+++ b/service-webapp/src/main/resources/context/application-context.xml
@@ -31,4 +31,4 @@ under the License.
 
 	<bean id="tenantRegistry" class="org.apache.metamodel.service.app.InMemoryTenantRegistry" />
 
-</beans>
\ No newline at end of file
+</beans>

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6f6f776b/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
index de862de..8ba8596 100644
--- a/service-webapp/src/main/resources/logback.xml
+++ b/service-webapp/src/main/resources/logback.xml
@@ -30,4 +30,4 @@ under the License.
 	<root level="warn">
 		<appender-ref ref="consoleAppender" />
 	</root>
-</configuration>
\ No newline at end of file
+</configuration>

http://git-wip-us.apache.org/repos/asf/metamodel/blob/6f6f776b/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
index 12af16f..41cf38c 100644
--- a/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml
+++ b/service-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml
@@ -54,4 +54,4 @@ under the License.
 			</list>
 		</property>
 	</bean>
-</beans>
\ No newline at end of file
+</beans>


[10/11] metamodel git commit: Improved throwing of exceptions from controllers.

Posted by ka...@apache.org.
Improved throwing of exceptions from controllers.

Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/79af7dde
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/79af7dde
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/79af7dde

Branch: refs/heads/feature/5.x/swagger-docs
Commit: 79af7dded8df48e829a70b1d8c2defdd7076cb60
Parents: 704790b
Author: Kasper S�rensen <i....@gmail.com>
Authored: Sun Jun 12 22:11:06 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Sun Jun 12 22:11:06 2016 -0700

----------------------------------------------------------------------
 .../service/app/DataContextTraverser.java       | 67 ++++++++++++++++++++
 .../app/exceptions/NoSuchColumnException.java   | 29 +++++++++
 .../app/exceptions/NoSuchSchemaException.java   | 29 +++++++++
 .../app/exceptions/NoSuchTableException.java    | 29 +++++++++
 .../service/controllers/ColumnController.java   | 13 ++--
 .../service/controllers/RestErrorHandler.java   |  6 +-
 .../service/controllers/SchemaController.java   |  8 ++-
 .../service/controllers/TableController.java    |  9 +--
 .../controllers/TableDataController.java        | 12 ++--
 9 files changed, 183 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/79af7dde/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java
new file mode 100644
index 0000000..6ec7122
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextTraverser.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.schema.Schema;
+import org.apache.metamodel.schema.Table;
+import org.apache.metamodel.service.app.exceptions.NoSuchColumnException;
+import org.apache.metamodel.service.app.exceptions.NoSuchSchemaException;
+import org.apache.metamodel.service.app.exceptions.NoSuchTableException;
+
+/**
+ * Utility object responsible for traversing the schema/table/column structures
+ * of a {@link DataContext} based on String identifiers and names.
+ * 
+ * This class will throw the appropriate exceptions if needed which is more
+ * communicative than the usual NPEs that would otherwise be thrown.
+ */
+public class DataContextTraverser {
+
+    private final DataContext dataContext;
+
+    public DataContextTraverser(DataContext dataContext) {
+        this.dataContext = dataContext;
+    }
+
+    public Schema getSchema(String schemaName) {
+        final Schema schema = dataContext.getSchemaByName(schemaName);
+        if (schema == null) {
+            throw new NoSuchSchemaException(schemaName);
+        }
+        return schema;
+    }
+
+    public Table getTable(String schemaName, String tableName) {
+        final Table table = getSchema(schemaName).getTableByName(tableName);
+        if (table == null) {
+            throw new NoSuchTableException(tableName);
+        }
+        return table;
+    }
+
+    public Column getColumn(String schemaName, String tableName, String columnName) {
+        final Column column = getTable(schemaName, tableName).getColumnByName(columnName);
+        if (column == null) {
+            throw new NoSuchColumnException(columnName);
+        }
+        return column;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/79af7dde/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java
new file mode 100644
index 0000000..1902af1
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchColumnException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchColumnException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public NoSuchColumnException(String name) {
+        super(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/79af7dde/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java
new file mode 100644
index 0000000..5b238a8
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchSchemaException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchSchemaException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public NoSuchSchemaException(String name) {
+        super(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/79af7dde/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java
new file mode 100644
index 0000000..73141cd
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/exceptions/NoSuchTableException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app.exceptions;
+
+public class NoSuchTableException extends AbstractIdentifierNamingException {
+
+    private static final long serialVersionUID = 1L;
+
+    public NoSuchTableException(String name) {
+        super(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/79af7dde/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
index 1eb1a13..7d6d5ff 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
@@ -23,8 +23,7 @@ import java.util.Map;
 
 import org.apache.metamodel.DataContext;
 import org.apache.metamodel.schema.Column;
-import org.apache.metamodel.schema.Schema;
-import org.apache.metamodel.schema.Table;
+import org.apache.metamodel.service.app.DataContextTraverser;
 import org.apache.metamodel.service.app.TenantContext;
 import org.apache.metamodel.service.app.TenantRegistry;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -55,13 +54,13 @@ public class ColumnController {
         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 Column column = table.getColumnByName(columnId);
+        final DataContextTraverser traverser = new DataContextTraverser(dataContext);
+
+        final Column column = traverser.getColumn(schemaId, tableId, columnId);
 
         final String tenantName = tenantContext.getTenantName();
-        final String tableName = table.getName();
-        final String schemaName = schema.getName();
+        final String tableName = column.getTable().getName();
+        final String schemaName = column.getTable().getSchema().getName();
 
         final Map<String, Object> metadata = new LinkedHashMap<>();
         metadata.put("number", column.getColumnNumber());

http://git-wip-us.apache.org/repos/asf/metamodel/blob/79af7dde/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
index 690ee0e..8a08151 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/RestErrorHandler.java
@@ -27,7 +27,10 @@ import org.apache.metamodel.query.parser.QueryParserException;
 import org.apache.metamodel.service.app.exceptions.AbstractIdentifierNamingException;
 import org.apache.metamodel.service.app.exceptions.DataSourceAlreadyExistException;
 import org.apache.metamodel.service.app.exceptions.DataSourceNotUpdateableException;
+import org.apache.metamodel.service.app.exceptions.NoSuchColumnException;
 import org.apache.metamodel.service.app.exceptions.NoSuchDataSourceException;
+import org.apache.metamodel.service.app.exceptions.NoSuchSchemaException;
+import org.apache.metamodel.service.app.exceptions.NoSuchTableException;
 import org.apache.metamodel.service.app.exceptions.NoSuchTenantException;
 import org.apache.metamodel.service.app.exceptions.TenantAlreadyExistException;
 import org.apache.metamodel.service.controllers.model.RestErrorResponse;
@@ -91,7 +94,8 @@ public class RestErrorHandler {
      * @param ex
      * @return
      */
-    @ExceptionHandler({ NoSuchTenantException.class, NoSuchDataSourceException.class })
+    @ExceptionHandler({ NoSuchTenantException.class, NoSuchDataSourceException.class, NoSuchSchemaException.class,
+            NoSuchTableException.class, NoSuchColumnException.class })
     @ResponseStatus(HttpStatus.NOT_FOUND)
     @ResponseBody
     public RestErrorResponse processNoSuchEntity(AbstractIdentifierNamingException ex) {

http://git-wip-us.apache.org/repos/asf/metamodel/blob/79af7dde/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
index bfdbe5f..6208d49 100644
--- 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
@@ -28,6 +28,7 @@ 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;
@@ -40,7 +41,8 @@ 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)
+@RequestMapping(value = { "/{tenant}/{dataContext}/schemas/{schema}",
+        "/{tenant}/{dataContext}/s/{schema}" }, produces = MediaType.APPLICATION_JSON_VALUE)
 public class SchemaController {
 
     private final TenantRegistry tenantRegistry;
@@ -57,7 +59,9 @@ public class SchemaController {
         final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
         final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
 
-        final Schema schema = dataContext.getSchemaByName(schemaId);
+        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}");
 

http://git-wip-us.apache.org/repos/asf/metamodel/blob/79af7dde/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
index cca0edc..4157dac 100644
--- 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
@@ -27,8 +27,8 @@ 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.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;
@@ -59,15 +59,16 @@ public class TableController {
             @PathVariable("table") String tableId) {
         final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
         final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+        
+        final DataContextTraverser traverser = new DataContextTraverser(dataContext);
 
-        final Schema schema = dataContext.getSchemaByName(schemaId);
-        final Table table = schema.getTableByName(tableId);
+        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 = schema.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());

http://git-wip-us.apache.org/repos/asf/metamodel/blob/79af7dde/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
index e25258f..c48d74f 100644
--- 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
@@ -27,8 +27,8 @@ 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.DataContextTraverser;
 import org.apache.metamodel.service.app.TenantContext;
 import org.apache.metamodel.service.app.TenantRegistry;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -61,9 +61,10 @@ public class TableDataController {
             @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 Schema schema = dataContext.getSchemaByName(schemaId);
-        final Table table = schema.getTableByName(tableId);
+        final Table table = traverser.getTable(schemaId, tableId);
 
         final Query query = dataContext.query().from(table).selectAll().toQuery();
 
@@ -79,8 +80,9 @@ public class TableDataController {
         final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
         final UpdateableDataContext dataContext = tenantContext.getDataSourceRegistry().openDataContextForUpdate(dataSourceName);
 
-        final Schema schema = dataContext.getSchemaByName(schemaId);
-        final Table table = schema.getTableByName(tableId);
+        final DataContextTraverser traverser = new DataContextTraverser(dataContext);
+
+        final Table table = traverser.getTable(schemaId, tableId);
 
         final InsertInto insert = new InsertInto(table);
         for (Entry<String, Object> entry : inputMap.entrySet()) {


[08/11] metamodel git commit: Added a rudimentary table data controller

Posted by ka...@apache.org.
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 <i....@gmail.com>
Authored: Wed Jun 8 02:58:41 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
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());
         }
     }


[06/11] metamodel git commit: Built out an extensive unittest to document a full usage scenario.

Posted by ka...@apache.org.
Built out an extensive unittest to document a full usage scenario.

Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/8722c196
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/8722c196
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/8722c196

Branch: refs/heads/feature/5.x/swagger-docs
Commit: 8722c196673713501edbc232ffe5ed3a213fbc5a
Parents: 6f6f776
Author: Kasper S�rensen <i....@gmail.com>
Authored: Mon Jun 6 20:55:15 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Mon Jun 6 20:55:15 2016 -0700

----------------------------------------------------------------------
 .../service/app/DataContextDefinition.java      |  24 ---
 .../service/app/DataContextRegistry.java        |  35 -----
 .../service/app/DataSourceDefinition.java       |  24 +++
 .../service/app/DataSourceRegistry.java         |  35 +++++
 .../app/InMemoryDataContextRegistry.java        |  68 ---------
 .../service/app/InMemoryDataSourceRegistry.java |  68 +++++++++
 .../service/app/InMemoryTenantContext.java      |   8 +-
 .../metamodel/service/app/TenantContext.java    |   4 +-
 .../service/controllers/ColumnController.java   |  87 +++++++++++
 .../controllers/DataContextController.java      |  87 -----------
 .../controllers/DataSourceController.java       |  87 +++++++++++
 .../service/controllers/SchemaController.java   |  16 +-
 .../service/controllers/TableController.java    |  20 +--
 .../service/controllers/TenantController.java   |  16 +-
 .../model/RestDataContextDefinition.java        |  33 ----
 .../model/RestDataSourceDefinition.java         |  33 ++++
 .../TenantInteractionScenarioTest.java          | 151 +++++++++++++++++++
 17 files changed, 517 insertions(+), 279 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java
deleted file mode 100644
index 955ff6c..0000000
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.metamodel.service.app;
-
-public interface DataContextDefinition {
-
-    // TODO
-}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java
deleted file mode 100644
index d2a7526..0000000
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.metamodel.service.app;
-
-import java.util.List;
-
-import org.apache.metamodel.DataContext;
-
-/**
- * Represents a user's/tenant's registry of {@link DataContext}s.
- */
-public interface DataContextRegistry {
-
-    public List<String> getDataContextIdentifiers();
-
-    public String registerDataContext(String dataContextIdentifier, DataContextDefinition dataContextDef) throws IllegalArgumentException;
-
-    public DataContext openDataContext(String dataContextIdentifier) throws IllegalArgumentException;
-}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
new file mode 100644
index 0000000..943222a
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceDefinition.java
@@ -0,0 +1,24 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+public interface DataSourceDefinition {
+
+    // TODO
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
new file mode 100644
index 0000000..1930354
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataSourceRegistry.java
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.List;
+
+import org.apache.metamodel.DataContext;
+
+/**
+ * Represents a user's/tenant's registry of {@link DataContext}s.
+ */
+public interface DataSourceRegistry {
+
+    public List<String> getDataSourceNames();
+
+    public String registerDataSource(String dataContextName, DataSourceDefinition dataSourceDef) throws IllegalArgumentException;
+
+    public DataContext openDataContext(String dataSourceName) throws IllegalArgumentException;
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java
deleted file mode 100644
index f9b6aa0..0000000
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.metamodel.service.app;
-
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
-import org.apache.metamodel.DataContext;
-import org.apache.metamodel.pojo.PojoDataContext;
-
-public class InMemoryDataContextRegistry implements DataContextRegistry {
-
-    private final Map<String, Supplier<DataContext>> dataContextIdentifiers;
-
-    public InMemoryDataContextRegistry() {
-        dataContextIdentifiers = new LinkedHashMap<>();
-    }
-
-    @Override
-    public String registerDataContext(final String dataContextIdentifier, final DataContextDefinition dataContextDef)
-            throws IllegalArgumentException {
-        if (dataContextIdentifiers.containsKey(dataContextIdentifier)) {
-            throw new IllegalArgumentException("DataContext already exist: " + dataContextIdentifier);
-        }
-        dataContextIdentifiers.put(dataContextIdentifier, new Supplier<DataContext>() {
-            @Override
-            public DataContext get() {
-                // TODO: Do a proper transformation from definition to instance
-                return new PojoDataContext();
-            }
-        });
-        return dataContextIdentifier;
-    }
-
-    @Override
-    public List<String> getDataContextIdentifiers() {
-        return dataContextIdentifiers.keySet().stream().collect(Collectors.toList());
-    }
-
-    @Override
-    public DataContext openDataContext(String dataContextIdentifier) {
-        final Supplier<DataContext> supplier = dataContextIdentifiers.get(dataContextIdentifier);
-        if (supplier == null) {
-            throw new IllegalArgumentException("No such DataContext: " + dataContextIdentifier);
-        }
-        return supplier.get();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
new file mode 100644
index 0000000..f3c45e8
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataSourceRegistry.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.pojo.PojoDataContext;
+
+public class InMemoryDataSourceRegistry implements DataSourceRegistry {
+
+    private final Map<String, Supplier<DataContext>> dataSources;
+
+    public InMemoryDataSourceRegistry() {
+        dataSources = new LinkedHashMap<>();
+    }
+
+    @Override
+    public String registerDataSource(final String dataContextIdentifier, final DataSourceDefinition dataContextDef)
+            throws IllegalArgumentException {
+        if (dataSources.containsKey(dataContextIdentifier)) {
+            throw new IllegalArgumentException("DataContext already exist: " + dataContextIdentifier);
+        }
+        dataSources.put(dataContextIdentifier, new Supplier<DataContext>() {
+            @Override
+            public DataContext get() {
+                // TODO: Do a proper transformation from definition to instance
+                return new PojoDataContext();
+            }
+        });
+        return dataContextIdentifier;
+    }
+
+    @Override
+    public List<String> getDataSourceNames() {
+        return dataSources.keySet().stream().collect(Collectors.toList());
+    }
+
+    @Override
+    public DataContext openDataContext(String dataContextIdentifier) {
+        final Supplier<DataContext> supplier = dataSources.get(dataContextIdentifier);
+        if (supplier == null) {
+            throw new IllegalArgumentException("No such DataContext: " + dataContextIdentifier);
+        }
+        return supplier.get();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
index 99a3e4e..04fb708 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
@@ -21,20 +21,20 @@ package org.apache.metamodel.service.app;
 public class InMemoryTenantContext implements TenantContext {
 
     private final String tenantIdentifier;
-    private final DataContextRegistry dataContextRegistry;
+    private final DataSourceRegistry dataContextRegistry;
 
     public InMemoryTenantContext(String tenantIdentifier) {
         this.tenantIdentifier = tenantIdentifier;
-        this.dataContextRegistry = new InMemoryDataContextRegistry();
+        this.dataContextRegistry = new InMemoryDataSourceRegistry();
     }
 
     @Override
-    public String getTenantIdentifier() {
+    public String getTenantName() {
         return tenantIdentifier;
     }
 
     @Override
-    public DataContextRegistry getDataContextRegistry() {
+    public DataSourceRegistry getDataSourceRegistry() {
         return dataContextRegistry;
     }
 

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
index 4e27922..c550a60 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
@@ -24,8 +24,8 @@ package org.apache.metamodel.service.app;
  */
 public interface TenantContext {
 
-    public String getTenantIdentifier();
+    public String getTenantName();
 
-    public DataContextRegistry getDataContextRegistry();
+    public DataSourceRegistry getDataSourceRegistry();
 
 }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
new file mode 100644
index 0000000..1eb1a13
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/ColumnController.java
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.controllers;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.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.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = { "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}/columns/{column}",
+        "/{tenant}/{dataContext}/s/{schema}/t/{table}/c/{column}" }, produces = MediaType.APPLICATION_JSON_VALUE)
+public class ColumnController {
+
+    private final TenantRegistry tenantRegistry;
+
+    @Autowired
+    public ColumnController(TenantRegistry tenantRegistry) {
+        this.tenantRegistry = tenantRegistry;
+    }
+
+    @RequestMapping(method = RequestMethod.GET)
+    @ResponseBody
+    public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+            @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId,
+            @PathVariable("table") String tableId, @PathVariable("column") String columnId) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+        final Schema schema = dataContext.getSchemaByName(schemaId);
+        final Table table = schema.getTableByName(tableId);
+        final Column column = table.getColumnByName(columnId);
+
+        final String tenantName = tenantContext.getTenantName();
+        final String tableName = table.getName();
+        final String schemaName = schema.getName();
+
+        final Map<String, Object> metadata = new LinkedHashMap<>();
+        metadata.put("number", column.getColumnNumber());
+        metadata.put("size", column.getColumnSize());
+        metadata.put("nullable", column.isNullable());
+        metadata.put("primary-key", column.isPrimaryKey());
+        metadata.put("indexed", column.isIndexed());
+        metadata.put("column-type", column.getType() == null ? null : column.getType().getName());
+        metadata.put("native-type", column.getNativeType());
+        metadata.put("remarks", column.getRemarks());
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "column");
+        map.put("name", column.getName());
+        map.put("table", tableName);
+        map.put("schema", schemaName);
+        map.put("data-context", dataSourceName);
+        map.put("tenant", tenantName);
+        map.put("metadata", metadata);
+
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java
deleted file mode 100644
index 441c74b..0000000
--- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * 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.validation.Valid;
-import javax.ws.rs.core.UriBuilder;
-
-import org.apache.metamodel.DataContext;
-import org.apache.metamodel.service.app.TenantContext;
-import org.apache.metamodel.service.app.TenantRegistry;
-import org.apache.metamodel.service.controllers.model.RestLink;
-import org.apache.metamodel.service.controllers.model.RestDataContextDefinition;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping(value = "/{tenant}/{dataContext}", produces = MediaType.APPLICATION_JSON_VALUE)
-public class DataContextController {
-
-    private final TenantRegistry tenantRegistry;
-
-    @Autowired
-    public DataContextController(TenantRegistry tenantRegistry) {
-        this.tenantRegistry = tenantRegistry;
-    }
-
-    @RequestMapping(method = RequestMethod.PUT)
-    @ResponseBody
-    public Map<String, Object> put(@PathVariable("tenant") String tenantId,
-            @PathVariable("dataContext") String dataContextId,
-            @Valid @RequestBody RestDataContextDefinition dataContextDefinition) {
-        final String dataContextIdentifier = tenantRegistry.getTenantContext(tenantId).getDataContextRegistry()
-                .registerDataContext(dataContextId, dataContextDefinition);
-
-        return get(tenantId, dataContextIdentifier);
-    }
-
-    @RequestMapping(method = RequestMethod.GET)
-    @ResponseBody
-    public Map<String, Object> get(@PathVariable("tenant") String tenantId,
-            @PathVariable("dataContext") String dataContextId) {
-        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
-        final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId);
-
-        final String tenantIdentifier = tenantContext.getTenantIdentifier();
-        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/schemas/{schema}");
-
-        final List<RestLink> schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s, uriBuilder
-                .build(tenantIdentifier, dataContextId, s))).collect(Collectors.toList());
-
-        final Map<String, Object> map = new LinkedHashMap<>();
-        map.put("type", "data-context");
-        map.put("name", dataContextId);
-        map.put("tenant", tenantIdentifier);
-        map.put("query", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantIdentifier, dataContextId));
-        map.put("schemas", schemaLinks);
-        return map;
-    }
-}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
new file mode 100644
index 0000000..8be66b1
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataSourceController.java
@@ -0,0 +1,87 @@
+/**
+ * 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.validation.Valid;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+import org.apache.metamodel.service.controllers.model.RestLink;
+import org.apache.metamodel.service.controllers.model.RestDataSourceDefinition;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = "/{tenant}/{datasource}", produces = MediaType.APPLICATION_JSON_VALUE)
+public class DataSourceController {
+
+    private final TenantRegistry tenantRegistry;
+
+    @Autowired
+    public DataSourceController(TenantRegistry tenantRegistry) {
+        this.tenantRegistry = tenantRegistry;
+    }
+
+    @RequestMapping(method = RequestMethod.PUT)
+    @ResponseBody
+    public Map<String, Object> put(@PathVariable("tenant") String tenantId,
+            @PathVariable("datasource") String dataSourceId,
+            @Valid @RequestBody RestDataSourceDefinition dataContextDefinition) {
+        final String dataContextIdentifier = tenantRegistry.getTenantContext(tenantId).getDataSourceRegistry()
+                .registerDataSource(dataSourceId, dataContextDefinition);
+
+        return get(tenantId, dataContextIdentifier);
+    }
+
+    @RequestMapping(method = RequestMethod.GET)
+    @ResponseBody
+    public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+            @PathVariable("datasource") String dataSourceName) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
+
+        final String tenantName = tenantContext.getTenantName();
+        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/s/{schema}");
+
+        final List<RestLink> schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s, uriBuilder
+                .build(tenantName, dataSourceName, s))).collect(Collectors.toList());
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "datasource");
+        map.put("name", dataSourceName);
+        map.put("tenant", tenantName);
+        map.put("query", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantName, dataSourceName));
+        map.put("schemas", schemaLinks);
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/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
index 9c18c3b..bfdbe5f 100644
--- 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
@@ -40,7 +40,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.RestController;
 
 @RestController
-@RequestMapping(value = "/{tenant}/{dataContext}/schemas/{schema}", produces = MediaType.APPLICATION_JSON_VALUE)
+@RequestMapping(value = {"/{tenant}/{dataContext}/schemas/{schema}", "/{tenant}/{dataContext}/s/{schema}"}, produces = MediaType.APPLICATION_JSON_VALUE)
 public class SchemaController {
 
     private final TenantRegistry tenantRegistry;
@@ -53,23 +53,23 @@ public class SchemaController {
     @RequestMapping(method = RequestMethod.GET)
     @ResponseBody
     public Map<String, Object> get(@PathVariable("tenant") String tenantId,
-            @PathVariable("dataContext") String dataContextId, @PathVariable("schema") String schemaId) {
+            @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId) {
         final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
-        final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId);
+        final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
 
         final Schema schema = dataContext.getSchemaByName(schemaId);
-        final String tenantIdentifier = tenantContext.getTenantIdentifier();
-        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/schemas/{schema}/tables/{table}");
+        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(tenantIdentifier, dataContextId, schemaName, t))).collect(Collectors.toList());
+                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("data-context", dataContextId);
-        map.put("tenant", tenantIdentifier);
+        map.put("data-context", dataSourceName);
+        map.put("tenant", tenantName);
         map.put("tables", tableLinks);
         return map;
     }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/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
index 373c1db..cca0edc 100644
--- 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
@@ -41,7 +41,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.RestController;
 
 @RestController
-@RequestMapping(value = "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}", produces = MediaType.APPLICATION_JSON_VALUE)
+@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;
@@ -54,30 +55,29 @@ public class TableController {
     @RequestMapping(method = RequestMethod.GET)
     @ResponseBody
     public Map<String, Object> get(@PathVariable("tenant") String tenantId,
-            @PathVariable("dataContext") String dataContextId, @PathVariable("schema") String schemaId,
+            @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId,
             @PathVariable("table") String tableId) {
         final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
-        final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId);
+        final DataContext dataContext = tenantContext.getDataSourceRegistry().openDataContext(dataSourceName);
 
         final Schema schema = dataContext.getSchemaByName(schemaId);
         final Table table = schema.getTableByName(tableId);
 
-        final String tenantIdentifier = tenantContext.getTenantIdentifier();
-        final UriBuilder uriBuilder = UriBuilder.fromPath(
-                "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}/columns/{column}");
+        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 = schema.getName();
-        final List<RestLink> columnsLinks = Arrays.stream(table.getColumnNames()).map(c -> new RestLink(String.valueOf(c),
-                uriBuilder.build(tenantIdentifier, dataContextId, schemaName, tableName, c))).collect(Collectors
+        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("data-context", dataContextId);
-        map.put("tenant", tenantIdentifier);
+        map.put("data-context", dataSourceName);
+        map.put("tenant", tenantName);
         map.put("columns", columnsLinks);
         return map;
     }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/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
index 48a0ed1..ef3011f 100644
--- 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
@@ -55,18 +55,18 @@ public class TenantController {
             throw new IllegalArgumentException("No such tenant: " + tenantName);
         }
 
-        final String tenantIdentifier = tenantContext.getTenantIdentifier();
+        final String tenantNameNormalized = tenantContext.getTenantName();
 
-        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}");
+        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{datasource}");
 
-        final List<String> dataContextIdentifiers = tenantContext.getDataContextRegistry().getDataContextIdentifiers();
-        final List<RestLink> dataContextLinks = dataContextIdentifiers.stream().map(s -> new RestLink(s, uriBuilder.build(
-                tenantIdentifier, s))).collect(Collectors.toList());
+        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", tenantIdentifier);
-        map.put("data-contexts", dataContextLinks);
+        map.put("name", tenantNameNormalized);
+        map.put("datasources", dataSourceLinks);
         return map;
     }
 
@@ -74,7 +74,7 @@ public class TenantController {
     @ResponseBody
     public Map<String, Object> putTenant(@PathVariable("tenant") String tenantName) {
         final TenantContext tenantContext = tenantRegistry.createTenantContext(tenantName);
-        final String tenantIdentifier = tenantContext.getTenantIdentifier();
+        final String tenantIdentifier = tenantContext.getTenantName();
 
         final Map<String, Object> map = new LinkedHashMap<>();
         map.put("type", "tenant");

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java
deleted file mode 100644
index ba2002e..0000000
--- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * 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 javax.validation.constraints.NotNull;
-
-import org.apache.metamodel.service.app.DataContextDefinition;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-public class RestDataContextDefinition implements DataContextDefinition {
-
-    @JsonProperty(value = "type", required = true)
-    @NotNull
-    public String type;
-
-}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/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..ad5f5ba
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataSourceDefinition.java
@@ -0,0 +1,33 @@
+/**
+ * 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 javax.validation.constraints.NotNull;
+
+import org.apache.metamodel.service.app.DataSourceDefinition;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RestDataSourceDefinition implements DataSourceDefinition {
+
+    @JsonProperty(value = "type", required = true)
+    @NotNull
+    public String type;
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8722c196/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..acdbb15
--- /dev/null
+++ b/service-webapp/src/test/java/org/apache/metamodel/service/controllers/TenantInteractionScenarioTest.java
@@ -0,0 +1,151 @@
+/**
+ * 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);
+
+        mockMvc = MockMvcBuilders.standaloneSetup(tenantController, dataContextController, schemaController,
+                tableController, columnController).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'}".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=Schema, uri=/tenant1/mydata/s/Schema}]",
+                    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/information_schema"))
+                    .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("information_schema", map.get("name"));
+
+            assertEquals("[{name=tables, uri=/tenant1/mydata/s/information_schema/t/tables}, "
+                    + "{name=columns, uri=/tenant1/mydata/s/information_schema/t/columns}, "
+                    + "{name=relationships, uri=/tenant1/mydata/s/information_schema/t/relationships}]", map.get(
+                            "tables").toString());
+        }
+
+        // explore table
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(
+                    "/tenant1/mydata/s/information_schema/t/columns")).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("columns", map.get("name"));
+
+            assertEquals("[{name=name, uri=/tenant1/mydata/s/information_schema/t/columns/c/name}, "
+                    + "{name=type, uri=/tenant1/mydata/s/information_schema/t/columns/c/type}, "
+                    + "{name=native_type, uri=/tenant1/mydata/s/information_schema/t/columns/c/native_type}, "
+                    + "{name=size, uri=/tenant1/mydata/s/information_schema/t/columns/c/size}, "
+                    + "{name=nullable, uri=/tenant1/mydata/s/information_schema/t/columns/c/nullable}, "
+                    + "{name=indexed, uri=/tenant1/mydata/s/information_schema/t/columns/c/indexed}, "
+                    + "{name=table, uri=/tenant1/mydata/s/information_schema/t/columns/c/table}, "
+                    + "{name=remarks, uri=/tenant1/mydata/s/information_schema/t/columns/c/remarks}]", map.get(
+                            "columns").toString());
+        }
+
+        // explore column
+        {
+            final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(
+                    "/tenant1/mydata/s/information_schema/t/columns/c/nullable")).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("nullable", map.get("name"));
+
+            assertEquals(
+                    "{number=4, size=null, nullable=true, primary-key=false, indexed=false, column-type=BOOLEAN, native-type=null, remarks=null}",
+                    map.get("metadata").toString());
+        }
+    }
+}


[04/11] metamodel git commit: Added DataContextController. Removed half-baked error handling for now.

Posted by ka...@apache.org.
Added DataContextController. Removed half-baked error handling for now.

Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/972ea4b1
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/972ea4b1
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/972ea4b1

Branch: refs/heads/feature/5.x/swagger-docs
Commit: 972ea4b1549db94ab595956a043e86fa80dfceed
Parents: fabd1b9
Author: Kasper S�rensen <i....@gmail.com>
Authored: Sun Jun 5 23:00:03 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Sun Jun 5 23:36:42 2016 -0700

----------------------------------------------------------------------
 .../service/app/DataContextDefinition.java      | 24 ++++++
 .../service/app/DataContextRegistry.java        |  2 +
 .../app/InMemoryDataContextRegistry.java        | 17 ++++
 .../controllers/DataContextController.java      | 87 ++++++++++++++++++++
 .../service/controllers/SchemaController.java   | 76 +++++++++++++++++
 .../service/controllers/TableController.java    | 84 +++++++++++++++++++
 .../service/controllers/TenantController.java   | 77 ++++++++---------
 .../service/controllers/model/Link.java         | 59 -------------
 .../model/RestDataContextDefinition.java        | 33 ++++++++
 .../service/controllers/model/RestLink.java     | 60 ++++++++++++++
 .../RootInformationControllerTest.java          |  2 +-
 11 files changed, 417 insertions(+), 104 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/972ea4b1/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java
new file mode 100644
index 0000000..955ff6c
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextDefinition.java
@@ -0,0 +1,24 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+public interface DataContextDefinition {
+
+    // TODO
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/972ea4b1/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java
index dd18a04..d2a7526 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java
@@ -29,5 +29,7 @@ public interface DataContextRegistry {
 
     public List<String> getDataContextIdentifiers();
 
+    public String registerDataContext(String dataContextIdentifier, DataContextDefinition dataContextDef) throws IllegalArgumentException;
+
     public DataContext openDataContext(String dataContextIdentifier) throws IllegalArgumentException;
 }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/972ea4b1/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java
index 89d835d..f9b6aa0 100644
--- a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java
@@ -25,6 +25,7 @@ import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 import org.apache.metamodel.DataContext;
+import org.apache.metamodel.pojo.PojoDataContext;
 
 public class InMemoryDataContextRegistry implements DataContextRegistry {
 
@@ -35,6 +36,22 @@ public class InMemoryDataContextRegistry implements DataContextRegistry {
     }
 
     @Override
+    public String registerDataContext(final String dataContextIdentifier, final DataContextDefinition dataContextDef)
+            throws IllegalArgumentException {
+        if (dataContextIdentifiers.containsKey(dataContextIdentifier)) {
+            throw new IllegalArgumentException("DataContext already exist: " + dataContextIdentifier);
+        }
+        dataContextIdentifiers.put(dataContextIdentifier, new Supplier<DataContext>() {
+            @Override
+            public DataContext get() {
+                // TODO: Do a proper transformation from definition to instance
+                return new PojoDataContext();
+            }
+        });
+        return dataContextIdentifier;
+    }
+
+    @Override
     public List<String> getDataContextIdentifiers() {
         return dataContextIdentifiers.keySet().stream().collect(Collectors.toList());
     }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/972ea4b1/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java
new file mode 100644
index 0000000..441c74b
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/DataContextController.java
@@ -0,0 +1,87 @@
+/**
+ * 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.validation.Valid;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.service.app.TenantContext;
+import org.apache.metamodel.service.app.TenantRegistry;
+import org.apache.metamodel.service.controllers.model.RestLink;
+import org.apache.metamodel.service.controllers.model.RestDataContextDefinition;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(value = "/{tenant}/{dataContext}", produces = MediaType.APPLICATION_JSON_VALUE)
+public class DataContextController {
+
+    private final TenantRegistry tenantRegistry;
+
+    @Autowired
+    public DataContextController(TenantRegistry tenantRegistry) {
+        this.tenantRegistry = tenantRegistry;
+    }
+
+    @RequestMapping(method = RequestMethod.PUT)
+    @ResponseBody
+    public Map<String, Object> put(@PathVariable("tenant") String tenantId,
+            @PathVariable("dataContext") String dataContextId,
+            @Valid @RequestBody RestDataContextDefinition dataContextDefinition) {
+        final String dataContextIdentifier = tenantRegistry.getTenantContext(tenantId).getDataContextRegistry()
+                .registerDataContext(dataContextId, dataContextDefinition);
+
+        return get(tenantId, dataContextIdentifier);
+    }
+
+    @RequestMapping(method = RequestMethod.GET)
+    @ResponseBody
+    public Map<String, Object> get(@PathVariable("tenant") String tenantId,
+            @PathVariable("dataContext") String dataContextId) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId);
+
+        final String tenantIdentifier = tenantContext.getTenantIdentifier();
+        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/schemas/{schema}");
+
+        final List<RestLink> schemaLinks = Arrays.stream(dataContext.getSchemaNames()).map(s -> new RestLink(s, uriBuilder
+                .build(tenantIdentifier, dataContextId, s))).collect(Collectors.toList());
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "data-context");
+        map.put("name", dataContextId);
+        map.put("tenant", tenantIdentifier);
+        map.put("query", UriBuilder.fromPath("/{tenant}/{dataContext}/query").build(tenantIdentifier, dataContextId));
+        map.put("schemas", schemaLinks);
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/972ea4b1/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..9c18c3b
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/SchemaController.java
@@ -0,0 +1,76 @@
+/**
+ * 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.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}", 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 dataContextId, @PathVariable("schema") String schemaId) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId);
+
+        final Schema schema = dataContext.getSchemaByName(schemaId);
+        final String tenantIdentifier = tenantContext.getTenantIdentifier();
+        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}/schemas/{schema}/tables/{table}");
+
+        final String schemaName = schema.getName();
+        final List<RestLink> tableLinks = Arrays.stream(schema.getTableNames()).map(t -> new RestLink(String.valueOf(t),
+                uriBuilder.build(tenantIdentifier, dataContextId, schemaName, t))).collect(Collectors.toList());
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "schema");
+        map.put("name", schemaName);
+        map.put("data-context", dataContextId);
+        map.put("tenant", tenantIdentifier);
+        map.put("tables", tableLinks);
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/972ea4b1/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..373c1db
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TableController.java
@@ -0,0 +1,84 @@
+/**
+ * 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.schema.Table;
+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}", 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 dataContextId, @PathVariable("schema") String schemaId,
+            @PathVariable("table") String tableId) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId);
+        final DataContext dataContext = tenantContext.getDataContextRegistry().openDataContext(dataContextId);
+
+        final Schema schema = dataContext.getSchemaByName(schemaId);
+        final Table table = schema.getTableByName(tableId);
+
+        final String tenantIdentifier = tenantContext.getTenantIdentifier();
+        final UriBuilder uriBuilder = UriBuilder.fromPath(
+                "/{tenant}/{dataContext}/schemas/{schema}/tables/{table}/columns/{column}");
+
+        final String tableName = table.getName();
+        final String schemaName = schema.getName();
+        final List<RestLink> columnsLinks = Arrays.stream(table.getColumnNames()).map(c -> new RestLink(String.valueOf(c),
+                uriBuilder.build(tenantIdentifier, dataContextId, 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("data-context", dataContextId);
+        map.put("tenant", tenantIdentifier);
+        map.put("columns", columnsLinks);
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/972ea4b1/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
index afefb9e..48a0ed1 100644
--- 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
@@ -18,50 +18,67 @@
  */
 package org.apache.metamodel.service.controllers;
 
-import java.io.IOException;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
-import javax.servlet.http.HttpServletResponse;
 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.Link;
+import org.apache.metamodel.service.controllers.model.RestLink;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.ExceptionHandler;
 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;
-import org.springframework.web.client.HttpServerErrorException;
 
 @RestController
 @RequestMapping(value = "/{tenant}", produces = MediaType.APPLICATION_JSON_VALUE)
 public class TenantController {
 
+    private final TenantRegistry tenantRegistry;
+
     @Autowired
-    TenantRegistry tenantRegistry;
+    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);
+        if (tenantContext == null) {
+            throw new IllegalArgumentException("No such tenant: " + tenantName);
+        }
+
+        final String tenantIdentifier = tenantContext.getTenantIdentifier();
+
+        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}");
+
+        final List<String> dataContextIdentifiers = tenantContext.getDataContextRegistry().getDataContextIdentifiers();
+        final List<RestLink> dataContextLinks = dataContextIdentifiers.stream().map(s -> new RestLink(s, uriBuilder.build(
+                tenantIdentifier, s))).collect(Collectors.toList());
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "tenant");
+        map.put("name", tenantIdentifier);
+        map.put("data-contexts", dataContextLinks);
+        return map;
+    }
 
     @RequestMapping(method = RequestMethod.PUT)
     @ResponseBody
     public Map<String, Object> putTenant(@PathVariable("tenant") String tenantName) {
-        final TenantContext tenantContext;
-        try {
-            tenantContext = tenantRegistry.createTenantContext(tenantName);
-        } catch (IllegalArgumentException e) {
-            throw new HttpServerErrorException(HttpStatus.CONFLICT, e.getMessage());
-        }
+        final TenantContext tenantContext = tenantRegistry.createTenantContext(tenantName);
         final String tenantIdentifier = tenantContext.getTenantIdentifier();
 
         final Map<String, Object> map = new LinkedHashMap<>();
         map.put("type", "tenant");
-        map.put("id", tenantIdentifier);
+        map.put("name", tenantIdentifier);
 
         return map;
     }
@@ -72,42 +89,14 @@ public class TenantController {
         final boolean deleted = tenantRegistry.deleteTenantContext(tenantName);
 
         if (!deleted) {
-            throw new HttpServerErrorException(HttpStatus.NOT_FOUND, "No such tenant: " + tenantName);
+            throw new IllegalArgumentException("No such tenant: " + tenantName);
         }
 
         final Map<String, Object> map = new LinkedHashMap<>();
         map.put("type", "tenant");
-        map.put("id", tenantName);
+        map.put("name", tenantName);
         map.put("deleted", deleted);
 
         return map;
     }
-
-    @RequestMapping(method = RequestMethod.GET)
-    @ResponseBody
-    public Map<String, Object> getTenant(@PathVariable("tenant") String tenantName) {
-        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantName);
-        if (tenantContext == null) {
-            throw new HttpServerErrorException(HttpStatus.NOT_FOUND, "No such tenant: " + tenantName);
-        }
-
-        final String tenantIdentifier = tenantContext.getTenantIdentifier();
-
-        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}");
-
-        final List<String> dataContextIdentifiers = tenantContext.getDataContextRegistry().getDataContextIdentifiers();
-        final List<Link> dataContextLinks = dataContextIdentifiers.stream().map(s -> new Link(s, uriBuilder.build(
-                tenantIdentifier, s))).collect(Collectors.toList());
-
-        final Map<String, Object> map = new LinkedHashMap<>();
-        map.put("type", "tenant");
-        map.put("id", tenantIdentifier);
-        map.put("data-contexts", dataContextLinks);
-        return map;
-    }
-
-    @ExceptionHandler(HttpServerErrorException.class)
-    public void handleException(HttpServerErrorException e, HttpServletResponse resp) throws IOException {
-        resp.sendError(e.getStatusCode().value(), e.getStatusText());
-    }
 }

http://git-wip-us.apache.org/repos/asf/metamodel/blob/972ea4b1/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java
deleted file mode 100644
index 4871553..0000000
--- a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * 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 Link implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    private String name;
-    private URI uri;
-
-    public Link() {
-    }
-
-    public Link(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/972ea4b1/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java
new file mode 100644
index 0000000..ba2002e
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/RestDataContextDefinition.java
@@ -0,0 +1,33 @@
+/**
+ * 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 javax.validation.constraints.NotNull;
+
+import org.apache.metamodel.service.app.DataContextDefinition;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RestDataContextDefinition implements DataContextDefinition {
+
+    @JsonProperty(value = "type", required = true)
+    @NotNull
+    public String type;
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/972ea4b1/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/972ea4b1/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
index 58a5ca5..8e3dab3 100644
--- 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
@@ -47,7 +47,7 @@ public class RootInformationControllerTest {
     }
 
     @Test
-    public void testGenericMessageSuccess() throws Exception {
+    public void testGet() throws Exception {
         final MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/").contentType(
                 MediaType.APPLICATION_JSON);
 


[03/11] metamodel git commit: Added Docker capabilities: A Dockerfile and README.md

Posted by ka...@apache.org.
Added Docker capabilities: A Dockerfile and README.md

Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/fabd1b97
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/fabd1b97
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/fabd1b97

Branch: refs/heads/feature/5.x/swagger-docs
Commit: fabd1b9784a7ec6512bf716fe8e6fa39e8cbf15b
Parents: 8e2ff71
Author: Kasper S�rensen <i....@gmail.com>
Authored: Mon May 23 23:11:43 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Mon May 23 23:13:18 2016 -0700

----------------------------------------------------------------------
 service-webapp/Dockerfile | 25 +++++++++++++++++++++++++
 service-webapp/README.md  | 12 ++++++++++++
 2 files changed, 37 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/fabd1b97/service-webapp/Dockerfile
----------------------------------------------------------------------
diff --git a/service-webapp/Dockerfile b/service-webapp/Dockerfile
new file mode 100644
index 0000000..033d6e3
--- /dev/null
+++ b/service-webapp/Dockerfile
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+FROM tomcat:8.0-jre8
+
+RUN rm -rf $CATALINA_HOME/webapps \
+ mkdir $CATALINA_HOME/webapps
+COPY target/MetaModel.war $CATALINA_HOME/webapps/ROOT.war
+
+EXPOSE 8080
+CMD ["catalina.sh", "run"]

http://git-wip-us.apache.org/repos/asf/metamodel/blob/fabd1b97/service-webapp/README.md
----------------------------------------------------------------------
diff --git a/service-webapp/README.md b/service-webapp/README.md
new file mode 100644
index 0000000..20bca99
--- /dev/null
+++ b/service-webapp/README.md
@@ -0,0 +1,12 @@
+# MetaModel-as-a-service
+
+This is a web application that allows you to access MetaModel's unified API for datastore exploration and querying - using a set of RESTful services.
+
+## Docker building and running
+
+```
+docker build -t metamodel-service .
+docker run --rm -p 8080:8080 metamodel-service
+```
+
+And then go to http://localhost:8080 (assuming localhost is your docker machine).


[02/11] metamodel git commit: Made a very simple tenant controller

Posted by ka...@apache.org.
Made a very simple tenant controller

Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/8e2ff716
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/8e2ff716
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/8e2ff716

Branch: refs/heads/feature/5.x/swagger-docs
Commit: 8e2ff7167eeae456a1e307426407e262dd7297f8
Parents: c9f56aa
Author: Kasper S�rensen <i....@gmail.com>
Authored: Mon May 23 23:05:21 2016 -0700
Committer: Kasper S�rensen <i....@gmail.com>
Committed: Mon May 23 23:05:21 2016 -0700

----------------------------------------------------------------------
 service-webapp/pom.xml                          |  10 ++
 .../service/app/DataContextRegistry.java        |  33 ++++++
 .../app/InMemoryDataContextRegistry.java        |  51 +++++++++
 .../service/app/InMemoryTenantContext.java      |  41 +++++++
 .../service/app/InMemoryTenantRegistry.java     |  64 +++++++++++
 .../metamodel/service/app/TenantContext.java    |  31 +++++
 .../metamodel/service/app/TenantRegistry.java   |  35 ++++++
 .../service/controllers/TenantController.java   | 113 +++++++++++++++++++
 .../service/controllers/model/Link.java         |  59 ++++++++++
 .../resources/context/application-context.xml   |   2 +
 10 files changed, 439 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel/blob/8e2ff716/service-webapp/pom.xml
----------------------------------------------------------------------
diff --git a/service-webapp/pom.xml b/service-webapp/pom.xml
index f3836dd..b452f55 100644
--- a/service-webapp/pom.xml
+++ b/service-webapp/pom.xml
@@ -35,6 +35,16 @@ under the License.
 
 	<dependencies>
 		<dependency>
+			<groupId>org.apache.metamodel</groupId>
+			<artifactId>MetaModel-spring</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.metamodel</groupId>
+			<artifactId>MetaModel-full</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
 			<groupId>org.springframework</groupId>
 			<artifactId>spring-webmvc</artifactId>
 		</dependency>

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8e2ff716/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java
new file mode 100644
index 0000000..dd18a04
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/DataContextRegistry.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.List;
+
+import org.apache.metamodel.DataContext;
+
+/**
+ * Represents a user's/tenant's registry of {@link DataContext}s.
+ */
+public interface DataContextRegistry {
+
+    public List<String> getDataContextIdentifiers();
+
+    public DataContext openDataContext(String dataContextIdentifier) throws IllegalArgumentException;
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8e2ff716/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java
new file mode 100644
index 0000000..89d835d
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryDataContextRegistry.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.DataContext;
+
+public class InMemoryDataContextRegistry implements DataContextRegistry {
+
+    private final Map<String, Supplier<DataContext>> dataContextIdentifiers;
+
+    public InMemoryDataContextRegistry() {
+        dataContextIdentifiers = new LinkedHashMap<>();
+    }
+
+    @Override
+    public List<String> getDataContextIdentifiers() {
+        return dataContextIdentifiers.keySet().stream().collect(Collectors.toList());
+    }
+
+    @Override
+    public DataContext openDataContext(String dataContextIdentifier) {
+        final Supplier<DataContext> supplier = dataContextIdentifiers.get(dataContextIdentifier);
+        if (supplier == null) {
+            throw new IllegalArgumentException("No such DataContext: " + dataContextIdentifier);
+        }
+        return supplier.get();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8e2ff716/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
new file mode 100644
index 0000000..99a3e4e
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantContext.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+public class InMemoryTenantContext implements TenantContext {
+
+    private final String tenantIdentifier;
+    private final DataContextRegistry dataContextRegistry;
+
+    public InMemoryTenantContext(String tenantIdentifier) {
+        this.tenantIdentifier = tenantIdentifier;
+        this.dataContextRegistry = new InMemoryDataContextRegistry();
+    }
+
+    @Override
+    public String getTenantIdentifier() {
+        return tenantIdentifier;
+    }
+
+    @Override
+    public DataContextRegistry getDataContextRegistry() {
+        return dataContextRegistry;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8e2ff716/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.java
new file mode 100644
index 0000000..c74fb22
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/InMemoryTenantRegistry.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.app;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * In-memory {@link TenantRegistry}. This is not particularly
+ * production-friendly as it is non-persistent, but it is useful for demo
+ * purposes.
+ */
+public class InMemoryTenantRegistry implements TenantRegistry {
+
+    private final Map<String, TenantContext> tenants;
+
+    public InMemoryTenantRegistry() {
+        tenants = new LinkedHashMap<>();
+    }
+
+    @Override
+    public List<String> getTenantIdentifiers() {
+        return tenants.keySet().stream().collect(Collectors.toList());
+    }
+
+    @Override
+    public TenantContext getTenantContext(String tenantIdentifier) {
+        return tenants.get(tenantIdentifier);
+    }
+
+    @Override
+    public TenantContext createTenantContext(String tenantIdentifier) {
+        if (tenants.containsKey(tenantIdentifier)) {
+            throw new IllegalArgumentException("Tenant already exist: " + tenantIdentifier);
+        }
+        final InMemoryTenantContext tenantContext = new InMemoryTenantContext(tenantIdentifier);
+        tenants.put(tenantIdentifier, tenantContext);
+        return tenantContext;
+    }
+
+    @Override
+    public boolean deleteTenantContext(String tenantIdentifier) {
+        return tenants.remove(tenantIdentifier) != null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8e2ff716/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
new file mode 100644
index 0000000..4e27922
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantContext.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+/**
+ * Represents a context-object containing all the information and services
+ * related to a particular tenant.
+ */
+public interface TenantContext {
+
+    public String getTenantIdentifier();
+
+    public DataContextRegistry getDataContextRegistry();
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8e2ff716/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
new file mode 100644
index 0000000..450db2d
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/app/TenantRegistry.java
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.service.app;
+
+import java.util.List;
+
+/**
+ * Represents the application's central registry of tenants
+ */
+public interface TenantRegistry {
+
+    public List<String> getTenantIdentifiers();
+
+    public TenantContext getTenantContext(String tenantIdentifier);
+    
+    public TenantContext createTenantContext(String tenantIdentifier) throws IllegalArgumentException;
+    
+    public boolean deleteTenantContext(String tenantIdentifier);
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8e2ff716/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..afefb9e
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/TenantController.java
@@ -0,0 +1,113 @@
+/**
+ * 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.IOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.servlet.http.HttpServletResponse;
+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.Link;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+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;
+import org.springframework.web.client.HttpServerErrorException;
+
+@RestController
+@RequestMapping(value = "/{tenant}", produces = MediaType.APPLICATION_JSON_VALUE)
+public class TenantController {
+
+    @Autowired
+    TenantRegistry tenantRegistry;
+
+    @RequestMapping(method = RequestMethod.PUT)
+    @ResponseBody
+    public Map<String, Object> putTenant(@PathVariable("tenant") String tenantName) {
+        final TenantContext tenantContext;
+        try {
+            tenantContext = tenantRegistry.createTenantContext(tenantName);
+        } catch (IllegalArgumentException e) {
+            throw new HttpServerErrorException(HttpStatus.CONFLICT, e.getMessage());
+        }
+        final String tenantIdentifier = tenantContext.getTenantIdentifier();
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "tenant");
+        map.put("id", tenantIdentifier);
+
+        return map;
+    }
+
+    @RequestMapping(method = RequestMethod.DELETE)
+    @ResponseBody
+    public Map<String, Object> deleteTenant(@PathVariable("tenant") String tenantName) {
+        final boolean deleted = tenantRegistry.deleteTenantContext(tenantName);
+
+        if (!deleted) {
+            throw new HttpServerErrorException(HttpStatus.NOT_FOUND, "No such tenant: " + tenantName);
+        }
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "tenant");
+        map.put("id", tenantName);
+        map.put("deleted", deleted);
+
+        return map;
+    }
+
+    @RequestMapping(method = RequestMethod.GET)
+    @ResponseBody
+    public Map<String, Object> getTenant(@PathVariable("tenant") String tenantName) {
+        final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantName);
+        if (tenantContext == null) {
+            throw new HttpServerErrorException(HttpStatus.NOT_FOUND, "No such tenant: " + tenantName);
+        }
+
+        final String tenantIdentifier = tenantContext.getTenantIdentifier();
+
+        final UriBuilder uriBuilder = UriBuilder.fromPath("/{tenant}/{dataContext}");
+
+        final List<String> dataContextIdentifiers = tenantContext.getDataContextRegistry().getDataContextIdentifiers();
+        final List<Link> dataContextLinks = dataContextIdentifiers.stream().map(s -> new Link(s, uriBuilder.build(
+                tenantIdentifier, s))).collect(Collectors.toList());
+
+        final Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", "tenant");
+        map.put("id", tenantIdentifier);
+        map.put("data-contexts", dataContextLinks);
+        return map;
+    }
+
+    @ExceptionHandler(HttpServerErrorException.class)
+    public void handleException(HttpServerErrorException e, HttpServletResponse resp) throws IOException {
+        resp.sendError(e.getStatusCode().value(), e.getStatusText());
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel/blob/8e2ff716/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java
----------------------------------------------------------------------
diff --git a/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java
new file mode 100644
index 0000000..4871553
--- /dev/null
+++ b/service-webapp/src/main/java/org/apache/metamodel/service/controllers/model/Link.java
@@ -0,0 +1,59 @@
+/**
+ * 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 Link implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String name;
+    private URI uri;
+
+    public Link() {
+    }
+
+    public Link(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/8e2ff716/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
index b0c945d..89a1b28 100644
--- a/service-webapp/src/main/resources/context/application-context.xml
+++ b/service-webapp/src/main/resources/context/application-context.xml
@@ -29,4 +29,6 @@ under the License.
 
 	<context:component-scan base-package="org.apache.metamodel.service.app" />
 
+	<bean id="tenantRegistry" class="org.apache.metamodel.service.app.InMemoryTenantRegistry" />
+
 </beans>
\ No newline at end of file