METAMODEL-1149: Created a file-based tenant registry and docker setup
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 @@
}
@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
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 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;
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 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;
}
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();
+ }
+
+}
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() + "]";
+ }
+}
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);
+ }
+ }
+}
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 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 @@
@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;
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 @@
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>
diff --git a/undertow/Dockerfile b/undertow/Dockerfile
index 3e14546..371d24b 100644
--- a/undertow/Dockerfile
+++ b/undertow/Dockerfile
@@ -19,4 +19,8 @@
COPY target/membrane-undertow-server.jar membrane-undertow-server.jar
+VOLUME /data
+
+ENV DATA_DIRECTORY=/data
+
CMD java -server -jar membrane-undertow-server.jar