Repository: metamodel-membrane
Updated Branches:
  refs/heads/master d9bde2a90 -> 5e7dcac0a


METAMODEL-1149: Created a file-based tenant registry and docker setup

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

Branch: refs/heads/master
Commit: a91b35e33f891155d302db6ba4fe7e19c2173a05
Parents: d9bde2a
Author: Kasper Sørensen <i.am.kasper.soren...@gmail.com>
Authored: Sat Aug 5 19:15:05 2017 -0700
Committer: Kasper Sørensen <i.am.kasper.soren...@gmail.com>
Committed: Sat Aug 5 19:20:59 2017 -0700

----------------------------------------------------------------------
 .../app/CachedDataSourceRegistryWrapper.java    |   6 +-
 .../membrane/app/DataSourceRegistry.java        |   2 +-
 .../metamodel/membrane/app/TenantRegistry.java  |   3 +-
 .../file/FileBasedDataSourceRegistry.java       | 122 ++++++++++++++++++
 .../registry/file/FileBasedTenantContext.java   |  51 ++++++++
 .../registry/file/FileBasedTenantRegistry.java  | 125 +++++++++++++++++++
 .../model/RestDataSourceDefinition.java         |  18 +++
 .../resources/context/application-context.xml   |   8 +-
 undertow/Dockerfile                             |   4 +
 9 files changed, 333 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/a91b35e3/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java
 
b/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java
index ed7902a..04a0872 100644
--- 
a/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java
+++ 
b/core/src/main/java/org/apache/metamodel/membrane/app/CachedDataSourceRegistryWrapper.java
@@ -86,10 +86,10 @@ public class CachedDataSourceRegistryWrapper implements 
DataSourceRegistry {
     }
 
     @Override
-    public String registerDataSource(final String dataContextName, final 
DataContextProperties dataContextProperties)
+    public String registerDataSource(final String dataSourceName, final 
DataContextProperties dataContextProperties)
             throws DataSourceAlreadyExistException {
-        loadingCache.invalidate(dataContextName);
-        return delegate.registerDataSource(dataContextName, 
dataContextProperties);
+        loadingCache.invalidate(dataSourceName);
+        return delegate.registerDataSource(dataSourceName, 
dataContextProperties);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/a91b35e3/core/src/main/java/org/apache/metamodel/membrane/app/DataSourceRegistry.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/metamodel/membrane/app/DataSourceRegistry.java 
b/core/src/main/java/org/apache/metamodel/membrane/app/DataSourceRegistry.java
index 4aa6f01..b4678c0 100644
--- 
a/core/src/main/java/org/apache/metamodel/membrane/app/DataSourceRegistry.java
+++ 
b/core/src/main/java/org/apache/metamodel/membrane/app/DataSourceRegistry.java
@@ -34,7 +34,7 @@ public interface DataSourceRegistry {
 
     public List<String> getDataSourceNames();
 
-    public String registerDataSource(String dataContextName, 
DataContextProperties dataContextProperties) throws 
DataSourceAlreadyExistException;
+    public String registerDataSource(String dataSourceName, 
DataContextProperties dataContextProperties) throws 
DataSourceAlreadyExistException;
 
     public DataContext openDataContext(String dataSourceName) throws 
NoSuchDataSourceException;
 

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/a91b35e3/core/src/main/java/org/apache/metamodel/membrane/app/TenantRegistry.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/metamodel/membrane/app/TenantRegistry.java 
b/core/src/main/java/org/apache/metamodel/membrane/app/TenantRegistry.java
index 625adb8..6a32800 100644
--- a/core/src/main/java/org/apache/metamodel/membrane/app/TenantRegistry.java
+++ b/core/src/main/java/org/apache/metamodel/membrane/app/TenantRegistry.java
@@ -32,7 +32,8 @@ public interface TenantRegistry {
 
     public TenantContext getTenantContext(String tenantIdentifier) throws 
NoSuchTenantException;
 
-    public TenantContext createTenantContext(String tenantIdentifier) throws 
TenantAlreadyExistException;
+    public TenantContext createTenantContext(String tenantIdentifier) throws 
IllegalArgumentException,
+            TenantAlreadyExistException;
 
     public void deleteTenantContext(String tenantIdentifier) throws 
NoSuchTenantException;
 }

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/a91b35e3/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedDataSourceRegistry.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedDataSourceRegistry.java
 
b/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedDataSourceRegistry.java
new file mode 100644
index 0000000..6659292
--- /dev/null
+++ 
b/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedDataSourceRegistry.java
@@ -0,0 +1,122 @@
+/**
+ * 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.membrane.app.registry.file;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.membrane.app.DataContextSupplier;
+import org.apache.metamodel.membrane.app.DataSourceRegistry;
+import 
org.apache.metamodel.membrane.app.exceptions.DataSourceAlreadyExistException;
+import org.apache.metamodel.membrane.app.exceptions.NoSuchDataSourceException;
+import 
org.apache.metamodel.membrane.controllers.model.RestDataSourceDefinition;
+import org.apache.metamodel.membrane.swagger.invoker.JSON;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Strings;
+
+public class FileBasedDataSourceRegistry implements DataSourceRegistry {
+
+    private static final ObjectMapper OBJECT_MAPPER = new 
JSON().getContext(Object.class);
+    private static final String DATASOURCE_FILE_SUFFIX = ".json";
+    private static final String DATASOURCE_FILE_PREFIX = "ds_";
+
+    private final File directory;
+
+    public FileBasedDataSourceRegistry(File directory) {
+        this.directory = directory;
+    }
+
+    @Override
+    public List<String> getDataSourceNames() {
+        final File[] files = directory.listFiles(new FileFilter() {
+            @Override
+            public boolean accept(File file) {
+                if (file.isDirectory()) {
+                    final String filename = file.getName();
+                    if (filename.startsWith(DATASOURCE_FILE_PREFIX) && 
filename.endsWith(DATASOURCE_FILE_SUFFIX)) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        });
+        return Arrays.stream(files).map(f -> 
getDataSourceName(f)).collect(Collectors.toList());
+    }
+
+    private String getDataSourceName(File file) {
+        final String filename = file.getName();
+        return filename.substring(DATASOURCE_FILE_PREFIX.length(), 
filename.length() - DATASOURCE_FILE_SUFFIX.length());
+    }
+
+    private File getDataSourceFile(String name) {
+        final String filename = DATASOURCE_FILE_PREFIX + name + 
DATASOURCE_FILE_SUFFIX;
+        return new File(directory, filename);
+    }
+
+    @Override
+    public String registerDataSource(String dataSourceName, 
DataContextProperties dataContextProperties)
+            throws DataSourceAlreadyExistException {
+        if (Strings.isNullOrEmpty(dataSourceName)) {
+            throw new IllegalArgumentException("DataSource name cannot be null 
or empty");
+        }
+        final File file = getDataSourceFile(dataSourceName);
+        if (file.exists()) {
+            throw new DataSourceAlreadyExistException(dataSourceName);
+        }
+
+        final RestDataSourceDefinition dataSource = new 
RestDataSourceDefinition(dataContextProperties);
+        try {
+            OBJECT_MAPPER.writeValue(file, dataSource);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        return dataSourceName;
+    }
+
+    @Override
+    public DataContext openDataContext(String dataSourceName) throws 
NoSuchDataSourceException {
+        if (Strings.isNullOrEmpty(dataSourceName)) {
+            throw new IllegalArgumentException("DataSource name cannot be null 
or empty");
+        }
+        final File file = getDataSourceFile(dataSourceName);
+        if (!file.exists()) {
+            throw new NoSuchDataSourceException(dataSourceName);
+        }
+
+        final RestDataSourceDefinition dataSource;
+        try {
+            dataSource = OBJECT_MAPPER.readValue(file, 
RestDataSourceDefinition.class);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+
+        final DataContextSupplier supplier = new 
DataContextSupplier(dataSourceName, dataSource
+                .toDataContextProperties());
+        return supplier.get();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/a91b35e3/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedTenantContext.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedTenantContext.java
 
b/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedTenantContext.java
new file mode 100644
index 0000000..b1bc48c
--- /dev/null
+++ 
b/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedTenantContext.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.membrane.app.registry.file;
+
+import java.io.File;
+
+import org.apache.metamodel.membrane.app.CachedDataSourceRegistryWrapper;
+import org.apache.metamodel.membrane.app.DataSourceRegistry;
+import org.apache.metamodel.membrane.app.TenantContext;
+
+class FileBasedTenantContext implements TenantContext {
+
+    private final File directory;
+    private final DataSourceRegistry dataContextRegistry;
+
+    public FileBasedTenantContext(File directory) {
+        this.directory = directory;
+        this.dataContextRegistry = new CachedDataSourceRegistryWrapper(new 
FileBasedDataSourceRegistry(directory));
+    }
+
+    @Override
+    public String getTenantName() {
+        return directory.getName();
+    }
+
+    @Override
+    public DataSourceRegistry getDataSourceRegistry() {
+        return dataContextRegistry;
+    }
+
+    @Override
+    public String toString() {
+        return "FileBasedTenantContext[" + directory.getName() + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/a91b35e3/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedTenantRegistry.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedTenantRegistry.java
 
b/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedTenantRegistry.java
new file mode 100644
index 0000000..6242038
--- /dev/null
+++ 
b/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedTenantRegistry.java
@@ -0,0 +1,125 @@
+/**
+ * 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.membrane.app.registry.file;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.metamodel.membrane.app.TenantContext;
+import org.apache.metamodel.membrane.app.TenantRegistry;
+import org.apache.metamodel.membrane.app.exceptions.NoSuchTenantException;
+import 
org.apache.metamodel.membrane.app.exceptions.TenantAlreadyExistException;
+
+import com.google.common.base.Strings;
+
+/**
+ * A {@link TenantRegistry} that persists tenant and datasource information in
+ * directories and files.
+ */
+public class FileBasedTenantRegistry implements TenantRegistry {
+
+    private final File directory;
+
+    public FileBasedTenantRegistry(File directory) {
+        this.directory = directory;
+        if (!directory.exists()) {
+            directory.mkdirs();
+        }
+    }
+
+    @Override
+    public List<String> getTenantIdentifiers() {
+        final File[] files = directory.listFiles(new FileFilter() {
+            @Override
+            public boolean accept(File file) {
+                if (file.isDirectory() && !file.isHidden() && 
!file.getName().startsWith(".")) {
+                    return true;
+                }
+                return false;
+            }
+        });
+        if (files == null) {
+            return Collections.emptyList();
+        }
+        return 
Arrays.stream(files).map(File::getName).collect(Collectors.toList());
+    }
+
+    @Override
+    public TenantContext getTenantContext(String tenantIdentifier) throws 
NoSuchTenantException {
+        final File file = new File(directory, tenantIdentifier);
+        if (!file.exists() || !file.isDirectory()) {
+            throw new NoSuchTenantException(tenantIdentifier);
+        }
+        return new FileBasedTenantContext(file);
+    }
+
+    @Override
+    public TenantContext createTenantContext(String tenantIdentifier) throws 
IllegalArgumentException,
+            TenantAlreadyExistException {
+        validateTenantIdentifier(tenantIdentifier);
+        final File file = new File(directory, tenantIdentifier);
+        if (file.exists()) {
+            if (file.isDirectory()) {
+                throw new TenantAlreadyExistException(tenantIdentifier);
+            } else {
+                throw new IllegalArgumentException("Illegal tenant identifier 
string: " + tenantIdentifier
+                        + ". String is reserved.");
+            }
+        }
+        file.mkdirs();
+        return getTenantContext(tenantIdentifier);
+    }
+
+    @Override
+    public void deleteTenantContext(String tenantIdentifier) throws 
NoSuchTenantException {
+        final File file = new File(directory, tenantIdentifier);
+        if (!file.exists() || !file.isDirectory()) {
+            throw new NoSuchTenantException(tenantIdentifier);
+        }
+        try {
+            FileUtils.deleteDirectory(file);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private void validateTenantIdentifier(String tenantIdentifier) throws 
IllegalArgumentException {
+        if (Strings.isNullOrEmpty(tenantIdentifier)) {
+            throw new IllegalArgumentException("Tenant identifier cannot be 
null or empty");
+        }
+        if (tenantIdentifier.startsWith(".")) {
+            throw new IllegalArgumentException("Illegal tenant identifier 
string: " + tenantIdentifier
+                    + ". Cannot start with dot ('.').");
+        }
+        try {
+            Paths.get(tenantIdentifier);
+        } catch (InvalidPathException ex) {
+            throw new IllegalArgumentException("Illegal tenant identifier 
string: " + tenantIdentifier, ex);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/a91b35e3/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
 
b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
index 3feef3c..eae3dc6 100644
--- 
a/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
+++ 
b/core/src/main/java/org/apache/metamodel/membrane/controllers/model/RestDataSourceDefinition.java
@@ -23,6 +23,8 @@ import java.util.Map;
 
 import javax.validation.constraints.NotNull;
 
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.factory.DataContextPropertiesImpl;
 import org.apache.metamodel.membrane.app.DataSourceDefinition;
 
 import com.fasterxml.jackson.annotation.JsonAnyGetter;
@@ -37,6 +39,22 @@ public class RestDataSourceDefinition implements 
DataSourceDefinition {
     @NotNull
     private String type;
 
+    // default constructor
+    public RestDataSourceDefinition() {
+    }
+
+    // specialized constructor for DataContextProperties conversion
+    public RestDataSourceDefinition(DataContextProperties 
dataContextProperties) {
+        properties.putAll(dataContextProperties.toMap());
+        type = (String) 
properties.remove(DataContextPropertiesImpl.PROPERTY_DATA_CONTEXT_TYPE);
+    }
+
+    public DataContextProperties toDataContextProperties() {
+        final DataContextPropertiesImpl dataContextPropertiesImpl = new 
DataContextPropertiesImpl(properties);
+        dataContextPropertiesImpl.setDataContextType(type);
+        return dataContextPropertiesImpl;
+    }
+
     @Override
     public String getType() {
         return type;

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/a91b35e3/core/src/main/resources/context/application-context.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/context/application-context.xml 
b/core/src/main/resources/context/application-context.xml
index e28e2b0..b5219ce 100644
--- a/core/src/main/resources/context/application-context.xml
+++ b/core/src/main/resources/context/application-context.xml
@@ -27,8 +27,14 @@ under the License.
        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:property-placeholder
+               ignore-resource-not-found="true" 
system-properties-mode="ENVIRONMENT" />
+
        <context:component-scan 
base-package="org.apache.metamodel.membrane.app" />
 
-       <bean id="tenantRegistry" 
class="org.apache.metamodel.membrane.app.InMemoryTenantRegistry" />
+       <bean id="tenantRegistry"
+               
class="org.apache.metamodel.membrane.app.registry.file.FileBasedTenantRegistry">
+               <constructor-arg name="directory" value="${DATA_DIRECTORY}" />
+       </bean>
 
 </beans>

http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/a91b35e3/undertow/Dockerfile
----------------------------------------------------------------------
diff --git a/undertow/Dockerfile b/undertow/Dockerfile
index 3e14546..371d24b 100644
--- a/undertow/Dockerfile
+++ b/undertow/Dockerfile
@@ -19,4 +19,8 @@ FROM openjdk:8-jre-alpine
 
 COPY target/membrane-undertow-server.jar membrane-undertow-server.jar
 
+VOLUME /data
+
+ENV DATA_DIRECTORY=/data
+
 CMD java -server -jar membrane-undertow-server.jar

Reply via email to