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:50 UTC

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

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());
+        }
+    }
+}