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