Define new, abstract ResourceCapableCacheDataImporterExporter capable of handling dynamic Resources.
The primary concerns involve resolving Resources for import/export along with reading from and writing to any Resource using the Spring Resource abstraction. Resolves gh-92.
This commit is contained in:
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.geode.data.support;
|
||||
|
||||
import static org.springframework.geode.core.util.ObjectUtils.initialize;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.geode.cache.GemFireCache;
|
||||
import org.apache.geode.cache.Region;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.context.expression.BeanFactoryAccessor;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.spel.SpelCompilerMode;
|
||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.DataBindingPropertyAccessor;
|
||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
import org.springframework.geode.core.env.EnvironmentMapAdapter;
|
||||
import org.springframework.geode.core.io.ResourceReader;
|
||||
import org.springframework.geode.core.io.ResourceResolver;
|
||||
import org.springframework.geode.core.io.ResourceWriter;
|
||||
import org.springframework.geode.core.io.support.ByteArrayResourceReader;
|
||||
import org.springframework.geode.core.io.support.FileResourceWriter;
|
||||
import org.springframework.geode.core.io.support.ResourceLoaderResourceResolver;
|
||||
import org.springframework.geode.core.io.support.ResourcePrefix;
|
||||
import org.springframework.geode.core.io.support.ResourceUtils;
|
||||
import org.springframework.geode.data.AbstractCacheDataImporterExporter;
|
||||
import org.springframework.geode.data.CacheDataImporterExporter;
|
||||
import org.springframework.geode.expression.SmartEnvironmentAccessor;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link AbstractCacheDataImporterExporter} extension and implementation capable of handling and managing import
|
||||
* and export {@link Resource Resources}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.beans.factory.InitializingBean
|
||||
* @see org.springframework.context.ResourceLoaderAware
|
||||
* @see org.springframework.expression.Expression
|
||||
* @see org.springframework.expression.ExpressionParser
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.core.io.ResourceLoader
|
||||
* @see org.springframework.geode.core.io.ResourceReader
|
||||
* @see org.springframework.geode.core.io.ResourceResolver
|
||||
* @see org.springframework.geode.core.io.ResourceWriter
|
||||
* @see org.springframework.geode.data.AbstractCacheDataImporterExporter
|
||||
* @since 1.3.1
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class ResourceCapableCacheDataImporterExporter extends AbstractCacheDataImporterExporter
|
||||
implements InitializingBean, ResourceLoaderAware {
|
||||
|
||||
protected static final String CACHE_DATA_EXPORT_RESOURCE_LOCATION_PROPERTY_NAME =
|
||||
"spring.boot.data.gemfire.cache.data.export.resource.location";
|
||||
|
||||
protected static final String CACHE_DATA_IMPORT_RESOURCE_LOCATION_PROPERTY_NAME =
|
||||
"spring.boot.data.gemfire.cache.data.import.resource.location";
|
||||
|
||||
protected static final String RESOURCE_NAME_PATTERN = "data-%s.json";
|
||||
|
||||
@Autowired(required = false)
|
||||
private ExportResourceResolver exportResourceResolver;
|
||||
|
||||
@Autowired(required = false)
|
||||
private ImportResourceResolver importResourceResolver;
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
@Autowired(required = false)
|
||||
private ResourceReader resourceReader;
|
||||
|
||||
@Autowired(required = false)
|
||||
private ResourceWriter resourceWriter;
|
||||
|
||||
/**
|
||||
* Initializes the Export & Import {@link ResourceResolver ResourceResolvers} as needed along with
|
||||
* the reader and writer for the {@link Resource} used on import and export.
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
|
||||
this.exportResourceResolver = initialize(this.exportResourceResolver, FileSystemExportResourceResolver::new);
|
||||
this.importResourceResolver = initialize(this.importResourceResolver, ClassPathImportResourceResolver::new);
|
||||
this.resourceReader = initialize(this.resourceReader, ByteArrayResourceReader::new);
|
||||
this.resourceWriter = initialize(this.resourceWriter, FileResourceWriter::new);
|
||||
|
||||
getResourceLoader().ifPresent(resourceLoader -> {
|
||||
|
||||
if (this.exportResourceResolver instanceof ResourceLoaderAware) {
|
||||
((ResourceLoaderAware) this.exportResourceResolver).setResourceLoader(resourceLoader);
|
||||
}
|
||||
|
||||
if (this.importResourceResolver instanceof ResourceLoaderAware) {
|
||||
((ResourceLoaderAware) this.importResourceResolver).setResourceLoader(resourceLoader);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured reference to the {@link ExportResourceResolver}.
|
||||
*
|
||||
* @return the configured reference to the {@link ExportResourceResolver}.
|
||||
* @see ExportResourceResolver
|
||||
*/
|
||||
protected @NonNull ExportResourceResolver getExportResourceResolver() {
|
||||
return this.exportResourceResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured reference to the {@link ImportResourceResolver}.
|
||||
*
|
||||
* @return the configured reference to the {@link ImportResourceResolver}.
|
||||
* @see ImportResourceResolver
|
||||
*/
|
||||
protected @NonNull ImportResourceResolver getImportResourceResolver() {
|
||||
return this.importResourceResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link ResourceLoader} used by this {@link CacheDataImporterExporter} to resolve
|
||||
* and load {@link Resource Resources}.
|
||||
*
|
||||
* @param resourceLoader {@link ResourceLoader} used to resolve and load {@link Resource Resources}.
|
||||
* @see org.springframework.core.io.ResourceLoader
|
||||
*/
|
||||
@Override
|
||||
public void setResourceLoader(@Nullable ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Optional} reference to the configured {@link ResourceLoader}
|
||||
* used to load {@link Resource Resources}.
|
||||
*
|
||||
* @return an {@link Optional} reference to the configured {@link ResourceLoader}.
|
||||
* @see org.springframework.core.io.ResourceLoader
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
protected Optional<ResourceLoader> getResourceLoader() {
|
||||
return Optional.ofNullable(this.resourceLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured {@link ResourceReader} used to read data from the {@link Resource} on {@literal import}.
|
||||
*
|
||||
* @return the configured {@link ResourceReader}.
|
||||
* @see org.springframework.geode.core.io.ResourceReader
|
||||
*/
|
||||
protected @NonNull ResourceReader getResourceReader() {
|
||||
return this.resourceReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured {@link ResourceWriter} used to write data to the {@link Resource} on {@literal export}.
|
||||
*
|
||||
* @return the configured {@link ResourceWriter}.
|
||||
* @see org.springframework.geode.core.io.ResourceWriter
|
||||
*/
|
||||
protected @NonNull ResourceWriter getResourceWriter() {
|
||||
return this.resourceWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ResourceResolver} interface extension used to resolve {@link GemFireCache cache}
|
||||
* {@link Resource Resources}.
|
||||
*
|
||||
* @see org.springframework.geode.core.io.ResourceResolver
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
* @see org.apache.geode.cache.Region
|
||||
*/
|
||||
protected interface CacheResourceResolver extends ResourceResolver {
|
||||
|
||||
/**
|
||||
* Tries to resolve a {@link Resource} to a {@link String location} containing data for the given {@link Region}.
|
||||
* The {@link Region} is used to determine the {@link String location} of the {@link Resource} to load.
|
||||
*
|
||||
* @param region {@link Region} used to resolve the {@link Resource}.
|
||||
* @return an {@link Optional} {@link Resource} handle to a {@link String location} containing data
|
||||
* for the given {@link Region}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
Optional<Resource> resolve(@NonNull Region<?, ?> region);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class containing functionality common to all {@link GemFireCache cache} based
|
||||
* {@link ResourceResolver ResourceResolvers}, whether for import or export.
|
||||
*
|
||||
* @see org.springframework.geode.core.io.support.ResourceLoaderResourceResolver
|
||||
* @see CacheResourceResolver
|
||||
*/
|
||||
protected abstract class AbstractCacheResourceResolver extends ResourceLoaderResourceResolver
|
||||
implements CacheResourceResolver {
|
||||
|
||||
private final ExpressionParser expressionParser;
|
||||
|
||||
private final Map<String, Expression> compiledExpressions;
|
||||
|
||||
private final SimpleEvaluationContext.Builder evaluationContextBuilder;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link AbstractCacheResourceResolver}.
|
||||
*
|
||||
* This constructor initializes the SpEL objects used to parse and evaluate SpEL expressions in order to
|
||||
* fully qualify and resolve {@link Resource} {@link String locations} defined as properties
|
||||
* in Spring Boot {@literal application.properties} for Import & Export {@link Resource Resources}.
|
||||
*
|
||||
* @see #newExpressionParser()
|
||||
* @see #newEvaluationContextBuilder()
|
||||
*/
|
||||
public AbstractCacheResourceResolver() {
|
||||
|
||||
this.expressionParser = newExpressionParser();
|
||||
this.evaluationContextBuilder = newEvaluationContextBuilder();
|
||||
this.compiledExpressions = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
private ExpressionParser newExpressionParser() {
|
||||
|
||||
ClassLoader classLoader = getApplicationContext()
|
||||
.map(ApplicationContext::getClassLoader)
|
||||
.orElseGet(ClassUtils::getDefaultClassLoader);
|
||||
|
||||
return new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.MIXED, classLoader));
|
||||
}
|
||||
|
||||
private SimpleEvaluationContext.Builder newEvaluationContextBuilder() {
|
||||
|
||||
PropertyAccessor[] propertyAccessors = {
|
||||
new BeanFactoryAccessor(),
|
||||
DataBindingPropertyAccessor.forReadOnlyAccess(),
|
||||
SmartEnvironmentAccessor.create()
|
||||
};
|
||||
|
||||
SimpleEvaluationContext.Builder builder = SimpleEvaluationContext.forPropertyAccessors(propertyAccessors)
|
||||
.withInstanceMethods();
|
||||
|
||||
String conversionServiceBeanName = ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME;
|
||||
|
||||
return getApplicationContext()
|
||||
.filter(applicationContext -> applicationContext.containsBean(conversionServiceBeanName))
|
||||
.map(applicationContext -> applicationContext.getBean(conversionServiceBeanName, ConversionService.class))
|
||||
.map(builder::withConversionService)
|
||||
.orElse(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link EvaluationContext} used during the evaluation of SpEL {@link String expressions}.
|
||||
*
|
||||
* @return a new {@link EvaluationContext}; never {@literal null}.
|
||||
* @see org.springframework.expression.EvaluationContext
|
||||
*/
|
||||
protected @NonNull EvaluationContext newEvaluationContext() {
|
||||
return this.evaluationContextBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured {@link ExpressionParser} used to parse SpEL {@link String expressions}.
|
||||
*
|
||||
* @return the configured {@link ExpressionParser}; never {@literal null}.
|
||||
* @see org.springframework.expression.ExpressionParser
|
||||
*/
|
||||
protected @NonNull ExpressionParser getExpressionParser() {
|
||||
return this.expressionParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured {@link ParserContext} used by the {@link ExpressionParser} to identify SpEL expressions.
|
||||
*
|
||||
* @return the configured {@link ParserContext}.
|
||||
* @see org.springframework.expression.ParserContext
|
||||
*/
|
||||
protected @NonNull ParserContext getParserContext() {
|
||||
return ParserContext.TEMPLATE_EXPRESSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
protected boolean isQualified(@Nullable Resource resource) {
|
||||
return super.isQualified(resource) && resource.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines a fully-qualified {@link String resource location} for the given {@link Region}.
|
||||
*
|
||||
* @param region {@link Region} to evaluate; must not be {@literal null}.
|
||||
* @return a fully-qualified {@link String resource location} for the given {@link Region}.
|
||||
* @see Region
|
||||
* @see #getResourceName(Region)
|
||||
* @see #getResourcePath()
|
||||
*/
|
||||
protected @NonNull String getFullyQualifiedResourceLocation(@NonNull Region<?, ?> region) {
|
||||
return String.format("%1$s%2$s", getResourcePath(), getResourceName(region));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the {@link String location} of a {@link Resource} for the given {@link Region}.
|
||||
*
|
||||
* @param region {@link Region} used to locate the desired {@link Resource}; must not be {@literal null}.
|
||||
* @return a {@link Resource} {@link String location} for the given {@link Region}.
|
||||
* @throws IllegalArgumentException if {@link Region} is {@literal null}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
*/
|
||||
protected @NonNull String getResourceLocation(@NonNull Region<?, ?> region, @NonNull String propertyName) {
|
||||
|
||||
Assert.notNull(region, "Region must not be null");
|
||||
Assert.hasText(propertyName, () -> String.format("Property name [%s] must be specified", propertyName));
|
||||
|
||||
return getEnvironment()
|
||||
.filter(environment -> environment.containsProperty(propertyName))
|
||||
.map(environment -> environment.getProperty(propertyName))
|
||||
.filter(StringUtils::hasText)
|
||||
.map(resourceLocation -> evaluate(resourceLocation, region))
|
||||
.orElseGet(() -> getFullyQualifiedResourceLocation(region));
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given SpEL {@link String expression}.
|
||||
*
|
||||
* @param expressionString {@link String} containing the SpEL expression to evaluate; must not be {@literal null}.
|
||||
* @param region {@link Region} used to resolve {@literal regionName} variable references
|
||||
* in the {@link String expression}; must not be {@literal null}.
|
||||
* @return the value of the evaluated {@link String expression}.
|
||||
* @see org.springframework.expression.Expression#getValue(EvaluationContext, Object)
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see #parse(String)
|
||||
*/
|
||||
protected @Nullable String evaluate(@NonNull String expressionString, @NonNull Region<?, ?> region) {
|
||||
|
||||
EvaluationContext evaluationContext = newEvaluationContext();
|
||||
|
||||
evaluationContext.setVariable("regionName", region.getName().toLowerCase());
|
||||
|
||||
getEnvironment().ifPresent(environment ->
|
||||
evaluationContext.setVariable("env", EnvironmentMapAdapter.from(environment)));
|
||||
|
||||
Expression expression = parse(expressionString);
|
||||
|
||||
Object value = getApplicationContext()
|
||||
.map(applicationContext -> expression.getValue(evaluationContext, applicationContext))
|
||||
.orElseGet(() -> expression.getValue(evaluationContext));
|
||||
|
||||
return value != null ? value.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given {@link String expressionString}.
|
||||
*
|
||||
* This method will cache parsed {@link Expression Expressions} to speed up the evaluation process.
|
||||
*
|
||||
* @param expressionString {@link String} containing the SpEL expression to parse.
|
||||
* @return an {@link Expression} object parsed from the given {@link String expression}.
|
||||
* @see org.springframework.expression.ExpressionParser#parseExpression(String, ParserContext)
|
||||
* @see org.springframework.expression.Expression
|
||||
* @see #getExpressionParser()
|
||||
* @see #getParserContext()
|
||||
*/
|
||||
protected Expression parse(String expressionString) {
|
||||
return this.compiledExpressions.computeIfAbsent(expressionString,
|
||||
it -> getExpressionParser().parseExpression(it, getParserContext()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines a {@link String resource name} for the given {@link Region}.
|
||||
*
|
||||
* The default implementation bases the {@link String resource name} on
|
||||
* the {@link Region#getName() Region's lowercase name}.
|
||||
*
|
||||
* @param region {@link Region} to evaluate; must not be {@literal null}.
|
||||
* @return a {@link String resource name} for the given {@link Region}.
|
||||
* @see Region
|
||||
* @see #getResourceName(String)
|
||||
*/
|
||||
protected @NonNull String getResourceName(@NonNull Region<?, ?> region) {
|
||||
return getResourceName(region.getName().toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines a {@link String resource name} for the given {@link String name}.
|
||||
*
|
||||
* @param name {@link String} containing the name to evaluate; must not be {@literal null}.
|
||||
* @return a {@link String resource name} from the given {@link String name}.
|
||||
*/
|
||||
protected @NonNull String getResourceName(@NonNull String name) {
|
||||
return String.format(RESOURCE_NAME_PATTERN, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link String base path} for the targeted {@link Resource}.
|
||||
*
|
||||
* @return the {@link String base path} for the targeted {@link Resource}.
|
||||
*/
|
||||
protected abstract @NonNull String getResourcePath();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker interface extending {@link CacheResourceResolver} for cache data exports.
|
||||
*
|
||||
* @see org.springframework.geode.core.io.ResourceResolver
|
||||
* @see CacheResourceResolver
|
||||
*/
|
||||
public interface ExportResourceResolver extends CacheResourceResolver { }
|
||||
|
||||
/**
|
||||
* Abstract base class extended by export {@link CacheResourceResolver} implementations, providing a template
|
||||
* to resolve the {@link Resource} used on export.
|
||||
*
|
||||
* @see AbstractCacheResourceResolver
|
||||
* @see ExportResourceResolver
|
||||
*/
|
||||
protected abstract class AbstractExportResourceResolver extends AbstractCacheResourceResolver
|
||||
implements ExportResourceResolver {
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Optional<Resource> resolve(@NonNull Region<?, ?> region) {
|
||||
|
||||
Assert.notNull(region, "Region must not be null");
|
||||
|
||||
String resourceLocation = getResourceLocation(region, CACHE_DATA_EXPORT_RESOURCE_LOCATION_PROPERTY_NAME);
|
||||
|
||||
Optional<Resource> resource = resolve(resourceLocation);
|
||||
|
||||
if (!resource.filter(ResourceUtils::isWritable).isPresent()) {
|
||||
getLogger().warn("WARNING! Resource [{}] for Region [{}] is not writable",
|
||||
resourceLocation, region.getFullPath());
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
protected @Nullable Resource onMissingResource(Resource resource, String location) {
|
||||
|
||||
getLogger().warn("WARNING! Resource at location [{}] does not exist; will try to create it", location);
|
||||
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link Resource} used for {@literal export} from the {@literal filesystem}.
|
||||
*/
|
||||
public class FileSystemExportResourceResolver extends AbstractExportResourceResolver {
|
||||
|
||||
@Override
|
||||
protected @NonNull String getResourcePath() {
|
||||
return String.format("%1$s%2$s", ResourcePrefix.FILESYSTEM_URL_PREFIX.toUrlPrefix(),
|
||||
System.getProperty("user.dir"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker interface extending {@link CacheResourceResolver} for cache data imports.
|
||||
*
|
||||
* @see org.springframework.geode.core.io.ResourceResolver
|
||||
* @see CacheResourceResolver
|
||||
*/
|
||||
public interface ImportResourceResolver extends CacheResourceResolver { }
|
||||
|
||||
/**
|
||||
* Abstract base class extended by import {@link ResourceResolver} implementations, providing a template
|
||||
* to resolve the {@link Resource} to import.
|
||||
*
|
||||
* @see AbstractCacheResourceResolver
|
||||
* @see ImportResourceResolver
|
||||
*/
|
||||
protected abstract class AbstractImportResourceResolver extends AbstractCacheResourceResolver
|
||||
implements ImportResourceResolver {
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Optional<Resource> resolve(@NonNull Region<?, ?> region) {
|
||||
|
||||
Assert.notNull(region, "Region must not be null");
|
||||
|
||||
String resourceLocation = getResourceLocation(region, CACHE_DATA_IMPORT_RESOURCE_LOCATION_PROPERTY_NAME);
|
||||
|
||||
Optional<Resource> resource = resolve(resourceLocation);
|
||||
|
||||
Assert.state(resource.isPresent(), () -> String.format("Resource [%1$s] for Region [%2$s] does not exist",
|
||||
resourceLocation, region.getFullPath()));
|
||||
|
||||
boolean readable = resource.filter(Resource::isReadable).isPresent();
|
||||
|
||||
Assert.state(readable, () -> String.format("Resource [%1$s] for Region [%2$s] is not readable",
|
||||
resourceLocation, region.getFullPath()));
|
||||
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link Resource} to {@literal import} from the {@literal classpath}.
|
||||
*/
|
||||
public class ClassPathImportResourceResolver extends AbstractImportResourceResolver {
|
||||
|
||||
@Override
|
||||
protected @NonNull String getResourcePath() {
|
||||
return ResourcePrefix.CLASSPATH_URL_PREFIX.toUrlPrefix();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,770 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.geode.data.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.WritableResource;
|
||||
import org.springframework.data.gemfire.tests.support.MapBuilder;
|
||||
import org.springframework.data.gemfire.tests.util.ReflectionUtils;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.geode.core.io.ResourceReader;
|
||||
import org.springframework.geode.core.io.ResourceWriter;
|
||||
import org.springframework.geode.core.io.support.ByteArrayResourceReader;
|
||||
import org.springframework.geode.core.io.support.FileResourceWriter;
|
||||
import org.springframework.geode.core.io.support.ResourcePrefix;
|
||||
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.AbstractCacheResourceResolver;
|
||||
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.AbstractExportResourceResolver;
|
||||
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.AbstractImportResourceResolver;
|
||||
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ClassPathImportResourceResolver;
|
||||
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ExportResourceResolver;
|
||||
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.FileSystemExportResourceResolver;
|
||||
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ImportResourceResolver;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link ResourceCapableCacheDataImporterExporter}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.core.io.ResourceLoader
|
||||
* @see org.springframework.core.io.WritableResource
|
||||
* @see org.springframework.geode.core.io.ResourceReader
|
||||
* @see org.springframework.geode.core.io.ResourceWriter
|
||||
* @see org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public class ResourceCapableCacheDataImporterExporterUnitTests {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T inject(@NonNull T target, @NonNull String fieldName, @Nullable Object value) {
|
||||
|
||||
try {
|
||||
return (T) ReflectionUtils.setField(target, fieldName, value);
|
||||
}
|
||||
catch (NoSuchFieldException cause) {
|
||||
throw newRuntimeException(cause, "Failed to set field [%s] on object of type [%s] to value [%s]",
|
||||
fieldName, ObjectUtils.nullSafeClassName(target), value);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterPropertiesSetUsesImportExportReaderWriterResourceDefaults() {
|
||||
|
||||
ResourceCapableCacheDataImporterExporter importerExporter = new TestResourceCapableCacheDataImporterExporter();
|
||||
|
||||
importerExporter.afterPropertiesSet();
|
||||
|
||||
assertThat(importerExporter.getExportResourceResolver()).isInstanceOf(FileSystemExportResourceResolver.class);
|
||||
assertThat(importerExporter.getImportResourceResolver()).isInstanceOf(ClassPathImportResourceResolver.class);
|
||||
assertThat(importerExporter.getResourceLoader().orElse(null)).isNull();
|
||||
assertThat(importerExporter.getResourceReader()).isInstanceOf(ByteArrayResourceReader.class);
|
||||
assertThat(importerExporter.getResourceWriter()).isInstanceOf(FileResourceWriter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterPropertiesSetWithInjectedImportExportResourceResolversAndResourceReaderWriter() {
|
||||
|
||||
ResourceLoaderAwareExportResourceResolver mockExportResourceResolver =
|
||||
mock(ResourceLoaderAwareExportResourceResolver.class);
|
||||
|
||||
ResourceLoaderAwareImportResourceResolver mockImportResourceResolver =
|
||||
mock(ResourceLoaderAwareImportResourceResolver.class);
|
||||
|
||||
ResourceLoader mockResourceLoader = mock(ResourceLoader.class);
|
||||
|
||||
ResourceReader mockResourceReader = mock(ResourceReader.class);
|
||||
|
||||
ResourceWriter mockResourceWriter = mock(ResourceWriter.class);
|
||||
|
||||
ResourceCapableCacheDataImporterExporter importerExporter = new TestResourceCapableCacheDataImporterExporter();
|
||||
|
||||
inject(importerExporter, "exportResourceResolver", mockExportResourceResolver);
|
||||
inject(importerExporter, "importResourceResolver", mockImportResourceResolver);
|
||||
inject(importerExporter, "resourceReader", mockResourceReader);
|
||||
inject(importerExporter, "resourceWriter", mockResourceWriter);
|
||||
|
||||
importerExporter.setResourceLoader(mockResourceLoader);
|
||||
importerExporter.afterPropertiesSet();
|
||||
|
||||
assertThat(importerExporter.getExportResourceResolver()).isEqualTo(mockExportResourceResolver);
|
||||
assertThat(importerExporter.getImportResourceResolver()).isEqualTo(mockImportResourceResolver);
|
||||
assertThat(importerExporter.getResourceLoader().orElse(null)).isEqualTo(mockResourceLoader);
|
||||
assertThat(importerExporter.getResourceReader()).isEqualTo(mockResourceReader);
|
||||
assertThat(importerExporter.getResourceWriter()).isEqualTo(mockResourceWriter);
|
||||
|
||||
verify(mockExportResourceResolver, times(1)).setResourceLoader(eq(mockResourceLoader));
|
||||
verify(mockImportResourceResolver, times(1)).setResourceLoader(eq(mockResourceLoader));
|
||||
verifyNoInteractions(mockResourceLoader, mockResourceReader, mockResourceWriter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetResourceLoader() {
|
||||
|
||||
ResourceLoader mockResourceLoader = mock(ResourceLoader.class);
|
||||
|
||||
ResourceCapableCacheDataImporterExporter importerExporter = new TestResourceCapableCacheDataImporterExporter();
|
||||
|
||||
assertThat(importerExporter.getResourceLoader().orElse(null)).isNull();
|
||||
|
||||
importerExporter.setResourceLoader(mockResourceLoader);
|
||||
|
||||
assertThat(importerExporter.getResourceLoader().orElse(null)).isEqualTo(mockResourceLoader);
|
||||
|
||||
importerExporter.setResourceLoader(null);
|
||||
|
||||
assertThat(importerExporter.getResourceLoader().orElse(null)).isNull();
|
||||
}
|
||||
|
||||
// Tests for CacheResourceResolver and friends (sub-types)
|
||||
|
||||
@Test
|
||||
public void expressionParserIsASpelExpressionParser() {
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver =
|
||||
new TestResourceCapableCacheDataImporterExporter().new TestCacheResourceResolver();
|
||||
|
||||
assertThat(resourceResolver.getExpressionParser()).isInstanceOf(SpelExpressionParser.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parserContextIsTemplateBased() {
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver =
|
||||
new TestResourceCapableCacheDataImporterExporter().new TestCacheResourceResolver();
|
||||
|
||||
assertThat(resourceResolver.getParserContext()).isEqualTo(ParserContext.TEMPLATE_EXPRESSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isQualifiedWithExistingResource() {
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver =
|
||||
new TestResourceCapableCacheDataImporterExporter().new TestCacheResourceResolver();
|
||||
|
||||
Resource mockResource = mock(Resource.class);
|
||||
|
||||
doReturn(true).when(mockResource).exists();
|
||||
|
||||
assertThat(resourceResolver.isQualified(mockResource)).isTrue();
|
||||
|
||||
verify(mockResource, times(1)).exists();
|
||||
verifyNoMoreInteractions(mockResource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isQualifiedWithNonExistingResource() {
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver =
|
||||
new TestResourceCapableCacheDataImporterExporter().new TestCacheResourceResolver();
|
||||
|
||||
Resource mockResource = mock(Resource.class);
|
||||
|
||||
doReturn(false).when(mockResource).exists();
|
||||
|
||||
assertThat(resourceResolver.isQualified(mockResource)).isFalse();
|
||||
|
||||
verify(mockResource, times(1)).exists();
|
||||
verifyNoMoreInteractions(mockResource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isQualifiedWithNullResource() {
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver =
|
||||
new TestResourceCapableCacheDataImporterExporter().new TestCacheResourceResolver();
|
||||
|
||||
assertThat(resourceResolver.isQualified(null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFullyQualifiedResourceLocationCallsGetResourcePathAndGetResourceNameWithRegion() {
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter().new TestCacheResourceResolver());
|
||||
|
||||
doReturn("/path/to/").when(resourceResolver).getResourcePath();
|
||||
doReturn("resource.xml").when(resourceResolver).getResourceName(eq(mockRegion));
|
||||
|
||||
assertThat(resourceResolver.getFullyQualifiedResourceLocation(mockRegion)).isEqualTo("/path/to/resource.xml");
|
||||
|
||||
verify(resourceResolver, times(1)).getResourcePath();
|
||||
verify(resourceResolver, times(1)).getResourceName(eq(mockRegion));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceLocationIsEvaluated() {
|
||||
|
||||
String testPropertyName = "test.property.name";
|
||||
String rawPropertyValue = "https://skullbox:8181/nurv/cache/%s/data/resource.json";
|
||||
String evaluatedPropertyValue = "https://skullbox:8181/nurv/cache/example/data/resource.json";
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
doReturn("Example").when(mockRegion).getName();
|
||||
|
||||
Environment mockEnvironment = mock(Environment.class);
|
||||
|
||||
doReturn(true).when(mockEnvironment).containsProperty(eq(testPropertyName));
|
||||
doReturn(rawPropertyValue).when(mockEnvironment).getProperty(eq(testPropertyName));
|
||||
|
||||
TestResourceCapableCacheDataImporterExporter importerExporter =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter());
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver = spy(importerExporter.new TestCacheResourceResolver());
|
||||
|
||||
doReturn(Optional.of(mockEnvironment)).when(importerExporter).getEnvironment();
|
||||
|
||||
doAnswer(invocation -> {
|
||||
|
||||
String expression = invocation.getArgument(0);
|
||||
Region<?, ?> region = invocation.getArgument(1);
|
||||
|
||||
return String.format(expression, region.getName().toLowerCase());
|
||||
|
||||
}).when(resourceResolver).evaluate(anyString(), any(Region.class));
|
||||
|
||||
assertThat(resourceResolver.getResourceLocation(mockRegion, testPropertyName))
|
||||
.isEqualTo(evaluatedPropertyValue);
|
||||
|
||||
verify(mockEnvironment, times(1)).containsProperty(eq(testPropertyName));
|
||||
verify(mockEnvironment, times(1)).getProperty(eq(testPropertyName));
|
||||
verify(mockRegion, times(1)).getName();
|
||||
verify(importerExporter, times(1)).getEnvironment();
|
||||
verify(resourceResolver, times(1)).evaluate(eq(rawPropertyValue), eq(mockRegion));
|
||||
verifyNoMoreInteractions(mockEnvironment, mockRegion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceLocationCallsGetFullyQualifiedResourceLocationWhenEnvironmentIsNull() {
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
TestResourceCapableCacheDataImporterExporter importerExporter =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter());
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver = spy(importerExporter.new TestCacheResourceResolver());
|
||||
|
||||
doReturn(Optional.empty()).when(importerExporter).getEnvironment();
|
||||
doReturn("/path/to/resource.xml").when(resourceResolver)
|
||||
.getFullyQualifiedResourceLocation(eq(mockRegion));
|
||||
|
||||
assertThat(resourceResolver.getResourceLocation(mockRegion, "test.property.name"))
|
||||
.isEqualTo("/path/to/resource.xml");
|
||||
|
||||
verify(importerExporter, times(1)).getEnvironment();
|
||||
verify(resourceResolver, never()).evaluate(anyString(), any(Region.class));
|
||||
verifyNoInteractions(mockRegion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceLocationCallsGetFullyQualifiedResourceLocationWhenEnvironmentDoesNotContainProperty() {
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
Environment mockEnvironment = mock(Environment.class);
|
||||
|
||||
doReturn(false).when(mockEnvironment).containsProperty(anyString());
|
||||
|
||||
TestResourceCapableCacheDataImporterExporter importerExporter =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter());
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver = spy(importerExporter.new TestCacheResourceResolver());
|
||||
|
||||
doReturn(Optional.of(mockEnvironment)).when(importerExporter).getEnvironment();
|
||||
doReturn("/path/to/resource.bin").when(resourceResolver)
|
||||
.getFullyQualifiedResourceLocation(eq(mockRegion));
|
||||
|
||||
assertThat(resourceResolver.getResourceLocation(mockRegion, "test.property.name"))
|
||||
.isEqualTo("/path/to/resource.bin");
|
||||
|
||||
verify(importerExporter, times(1)).getEnvironment();
|
||||
verify(resourceResolver, never()).evaluate(anyString(), any(Region.class));
|
||||
verify(mockEnvironment, times(1)).containsProperty(eq("test.property.name"));
|
||||
verifyNoMoreInteractions(mockEnvironment);
|
||||
verifyNoInteractions(mockRegion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceLocationCallsGetFullyQualifiedResourceLocationWhenEnvironmentPropertyIsNotSet() {
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
Environment mockEnvironment = mock(Environment.class);
|
||||
|
||||
doReturn(true).when(mockEnvironment).containsProperty(eq("test.property.name"));
|
||||
doReturn(null).when(mockEnvironment).getProperty(anyString());
|
||||
|
||||
TestResourceCapableCacheDataImporterExporter importerExporter =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter());
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver = spy(importerExporter.new TestCacheResourceResolver());
|
||||
|
||||
doReturn(Optional.of(mockEnvironment)).when(importerExporter).getEnvironment();
|
||||
doReturn("/path/to/resource.dat").when(resourceResolver)
|
||||
.getFullyQualifiedResourceLocation(eq(mockRegion));
|
||||
|
||||
assertThat(resourceResolver.getResourceLocation(mockRegion, "test.property.name"))
|
||||
.isEqualTo("/path/to/resource.dat");
|
||||
|
||||
verify(importerExporter, times(1)).getEnvironment();
|
||||
verify(resourceResolver, never()).evaluate(anyString(), any(Region.class));
|
||||
verify(mockEnvironment, times(1)).containsProperty(eq("test.property.name"));
|
||||
verify(mockEnvironment, times(1)).getProperty(eq("test.property.name"));
|
||||
verifyNoMoreInteractions(mockEnvironment);
|
||||
verifyNoInteractions(mockRegion);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getResourceLocationWithNullRegion() {
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver =
|
||||
new TestResourceCapableCacheDataImporterExporter().new TestCacheResourceResolver();
|
||||
|
||||
try {
|
||||
resourceResolver.getResourceLocation(null, "test.property.name");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Region must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void getResourceLocationWithNoPropertyName() {
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver =
|
||||
new TestResourceCapableCacheDataImporterExporter().new TestCacheResourceResolver();
|
||||
|
||||
try {
|
||||
resourceResolver.getResourceLocation(mockRegion, " ");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Property name [ ] must be specified");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verifyNoInteractions(mockRegion);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceNameFromRegion() {
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
doReturn("Example").when(mockRegion).getName();
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver =
|
||||
new TestResourceCapableCacheDataImporterExporter().new TestCacheResourceResolver();
|
||||
|
||||
assertThat(resourceResolver.getResourceName(mockRegion))
|
||||
.isEqualTo(String.format(ResourceCapableCacheDataImporterExporter.RESOURCE_NAME_PATTERN, "example"));
|
||||
|
||||
verify(mockRegion, times(1)).getName();
|
||||
verifyNoMoreInteractions(mockRegion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceNameFromString() {
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver =
|
||||
new TestResourceCapableCacheDataImporterExporter().new TestCacheResourceResolver();
|
||||
|
||||
assertThat(resourceResolver.getResourceName("EXAMPLE"))
|
||||
.isEqualTo(String.format(ResourceCapableCacheDataImporterExporter.RESOURCE_NAME_PATTERN, "EXAMPLE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("all")
|
||||
public void evaluatesExpressionContainingBeanAndPropertyAndRegionReferences() {
|
||||
|
||||
ApplicationContext mockApplicationContext = mock(ApplicationContext.class);
|
||||
|
||||
Environment mockEnvironment = mock(Environment.class);
|
||||
|
||||
Map<String, String> environmentProperties = MapBuilder.<String, String>newMapBuilder()
|
||||
.put("port", "8181")
|
||||
.put("user.password", "s3c3rt")
|
||||
.put("user.name", System.getProperty("user.name", "jonDoe"))
|
||||
.build();
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
doReturn(true).when(mockApplicationContext).containsBean("utility");
|
||||
doReturn(UtilityBean.INSTANCE).when(mockApplicationContext).getBean("utility");
|
||||
doReturn(mockEnvironment).when(mockApplicationContext).getEnvironment();
|
||||
doAnswer(invocation -> environmentProperties.containsKey(invocation.getArgument(0)))
|
||||
.when(mockEnvironment).containsProperty(any());
|
||||
doAnswer(invocation -> environmentProperties.get(invocation.getArgument(0)))
|
||||
.when(mockEnvironment).getProperty(any());
|
||||
doReturn("Example").when(mockRegion).getName();
|
||||
|
||||
TestResourceCapableCacheDataImporterExporter importerExporter =
|
||||
mock(TestResourceCapableCacheDataImporterExporter.class);
|
||||
|
||||
doReturn(Optional.ofNullable(mockApplicationContext)).when(importerExporter).getApplicationContext();
|
||||
doReturn(Optional.ofNullable(mockEnvironment)).when(importerExporter).getEnvironment();
|
||||
|
||||
AbstractCacheResourceResolver resourceResolver = importerExporter.new TestCacheResourceResolver();
|
||||
|
||||
String expression = "https://#{#env['user.name']}:#{#env['user.password']}@skullbox:#{port}/nurv/cache/#{utility.toUpperCase(#regionName)}/data/import";
|
||||
String parsedExpression = String.format("https://%s:s3c3rt@skullbox:8181/nurv/cache/EXAMPLE/data/import",
|
||||
environmentProperties.get("user.name"));
|
||||
|
||||
assertThat(resourceResolver.evaluate(expression, mockRegion)).isEqualTo(parsedExpression);
|
||||
|
||||
verify(mockApplicationContext, times(1)).containsBean(eq("utility"));
|
||||
verify(mockApplicationContext, times(1)).getBean(eq("utility"));
|
||||
verify(mockApplicationContext, times(2)).getEnvironment();
|
||||
verify(mockEnvironment, times(1)).containsProperty(eq("port"));
|
||||
verify(mockEnvironment, times(1)).getProperty(eq("port"));
|
||||
verify(mockEnvironment, times(1)).getProperty(eq("user.password"));
|
||||
verify(mockEnvironment, times(1)).getProperty(eq("user.name"));
|
||||
verify(mockRegion, times(1)).getName();
|
||||
verifyNoMoreInteractions(mockEnvironment, mockRegion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveExportResource() {
|
||||
|
||||
Logger mockLogger = mock(Logger.class);
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
doReturn("/Example").when(mockRegion).getFullPath();
|
||||
|
||||
ResourceLoader mockResourceLoader = mock(ResourceLoader.class);
|
||||
|
||||
WritableResource mockResource = mock(WritableResource.class);
|
||||
|
||||
doReturn(mockResource).when(mockResourceLoader).getResource(eq("/path/to/resource.xml"));
|
||||
doReturn(false).when(mockResource).exists();
|
||||
doReturn(false).when(mockResource).isWritable();
|
||||
|
||||
TestResourceCapableCacheDataImporterExporter importerExporter =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter());
|
||||
|
||||
AbstractExportResourceResolver exportResourceResolver = spy(importerExporter.new TestExportResourceResolver());
|
||||
|
||||
exportResourceResolver.setResourceLoader(mockResourceLoader);
|
||||
|
||||
doReturn(mockLogger).when(importerExporter).getLogger();
|
||||
|
||||
doReturn("/path/to/resource.xml")
|
||||
.when(exportResourceResolver).getResourceLocation(eq(mockRegion),
|
||||
eq(ResourceCapableCacheDataImporterExporter.CACHE_DATA_EXPORT_RESOURCE_LOCATION_PROPERTY_NAME));
|
||||
|
||||
//doReturn(Optional.of(mockResource)).when(exportResourceResolver).resolve(eq("/path/to/resource.xml"));
|
||||
|
||||
assertThat(exportResourceResolver.resolve(mockRegion)).isEqualTo(Optional.of(mockResource));
|
||||
|
||||
verify(importerExporter, times(2)).getLogger();
|
||||
verify(exportResourceResolver, times(1)).getResourceLocation(eq(mockRegion),
|
||||
eq(ResourceCapableCacheDataImporterExporter.CACHE_DATA_EXPORT_RESOURCE_LOCATION_PROPERTY_NAME));
|
||||
verify(exportResourceResolver, times(1)).isQualified(eq(mockResource));
|
||||
verify(exportResourceResolver, times(1))
|
||||
.onMissingResource(eq(mockResource), eq("/path/to/resource.xml"));
|
||||
verify(exportResourceResolver, times(1)).resolve(eq("/path/to/resource.xml"));
|
||||
verify(mockResourceLoader, times(1)).getResource(eq("/path/to/resource.xml"));
|
||||
verify(mockResource, times(1)).exists();
|
||||
verify(mockResource, times(1)).isWritable();
|
||||
verify(mockLogger, times(1))
|
||||
.warn(eq("WARNING! Resource at location [{}] does not exist; will try to create it"),
|
||||
eq("/path/to/resource.xml"));
|
||||
verify(mockLogger, times(1))
|
||||
.warn(eq("WARNING! Resource [{}] for Region [{}] is not writable"), eq("/path/to/resource.xml"),
|
||||
eq("/Example"));
|
||||
verify(mockRegion, times(1)).getFullPath();
|
||||
verifyNoMoreInteractions(mockLogger, mockRegion, mockResource, mockResourceLoader);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void resolveExportResourceWithNullRegionThrowsIllegalArgumentException() {
|
||||
|
||||
AbstractExportResourceResolver exportResourceResolver =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter().new TestExportResourceResolver());
|
||||
|
||||
try {
|
||||
exportResourceResolver.resolve((Region<?, ?>) null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Region must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileSystemExportResourceResolverResolvesToFileSystemPath() {
|
||||
|
||||
AbstractExportResourceResolver exportResourceResolver =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter().new FileSystemExportResourceResolver());
|
||||
|
||||
assertThat(exportResourceResolver.getResourcePath())
|
||||
.isEqualTo(String.format("file://%s", System.getProperty("user.dir")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveImportResource() {
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
Resource mockResource = mock(Resource.class);
|
||||
|
||||
doReturn(true).when(mockResource).isReadable();
|
||||
|
||||
AbstractImportResourceResolver importResourceResolver =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter().new TestImportResourceResolver());
|
||||
|
||||
doReturn("/path/to/resource.json")
|
||||
.when(importResourceResolver).getResourceLocation(eq(mockRegion),
|
||||
eq(ResourceCapableCacheDataImporterExporter.CACHE_DATA_IMPORT_RESOURCE_LOCATION_PROPERTY_NAME));
|
||||
|
||||
doReturn(Optional.of(mockResource)).when(importResourceResolver).resolve(eq("/path/to/resource.json"));
|
||||
|
||||
assertThat(importResourceResolver.resolve(mockRegion)).isEqualTo(Optional.of(mockResource));
|
||||
|
||||
verify(importResourceResolver, times(1)).getResourceLocation(eq(mockRegion),
|
||||
eq(ResourceCapableCacheDataImporterExporter.CACHE_DATA_IMPORT_RESOURCE_LOCATION_PROPERTY_NAME));
|
||||
verify(importResourceResolver, times(1)).resolve(eq("/path/to/resource.json"));
|
||||
verify(mockResource, times(1)).isReadable();
|
||||
verifyNoMoreInteractions(mockRegion);
|
||||
verifyNoInteractions(mockRegion);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void resolveImportResourceWhenResourceIsNotPresentThrowsIllegalStateException() {
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
doReturn("/Example").when(mockRegion).getFullPath();
|
||||
|
||||
AbstractImportResourceResolver importResourceResolver =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter().new TestImportResourceResolver());
|
||||
|
||||
doReturn("/path/to/resource.json")
|
||||
.when(importResourceResolver).getResourceLocation(eq(mockRegion),
|
||||
eq(ResourceCapableCacheDataImporterExporter.CACHE_DATA_IMPORT_RESOURCE_LOCATION_PROPERTY_NAME));
|
||||
|
||||
doReturn(Optional.empty()).when(importResourceResolver).resolve(eq("/path/to/resource.json"));
|
||||
|
||||
try {
|
||||
importResourceResolver.resolve(mockRegion);
|
||||
}
|
||||
catch (IllegalStateException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Resource [/path/to/resource.json] for Region [/Example] does not exist");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(importResourceResolver, times(1)).getResourceLocation(eq(mockRegion),
|
||||
eq(ResourceCapableCacheDataImporterExporter.CACHE_DATA_IMPORT_RESOURCE_LOCATION_PROPERTY_NAME));
|
||||
verify(importResourceResolver, times(1)).resolve(eq("/path/to/resource.json"));
|
||||
verify(mockRegion, times(1)).getFullPath();
|
||||
verifyNoMoreInteractions(mockRegion);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void resolveImportResourceWhenResourceIsNotReadableThrowsIllegalStateException() {
|
||||
|
||||
Region<?, ?> mockRegion = mock(Region.class);
|
||||
|
||||
doReturn("/Example").when(mockRegion).getFullPath();
|
||||
|
||||
Resource mockResource = mock(Resource.class);
|
||||
|
||||
doReturn(false).when(mockResource).isReadable();
|
||||
|
||||
AbstractImportResourceResolver importResourceResolver =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter().new TestImportResourceResolver());
|
||||
|
||||
doReturn("/path/to/resource.json")
|
||||
.when(importResourceResolver).getResourceLocation(eq(mockRegion),
|
||||
eq(ResourceCapableCacheDataImporterExporter.CACHE_DATA_IMPORT_RESOURCE_LOCATION_PROPERTY_NAME));
|
||||
|
||||
doReturn(Optional.of(mockResource)).when(importResourceResolver).resolve(eq("/path/to/resource.json"));
|
||||
|
||||
try {
|
||||
importResourceResolver.resolve(mockRegion);
|
||||
}
|
||||
catch (IllegalStateException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Resource [/path/to/resource.json] for Region [/Example] is not readable");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(importResourceResolver, times(1)).getResourceLocation(eq(mockRegion),
|
||||
eq(ResourceCapableCacheDataImporterExporter.CACHE_DATA_IMPORT_RESOURCE_LOCATION_PROPERTY_NAME));
|
||||
verify(importResourceResolver, times(1)).resolve(eq("/path/to/resource.json"));
|
||||
verify(mockResource, times(1)).isReadable();
|
||||
verify(mockRegion, times(1)).getFullPath();
|
||||
verifyNoMoreInteractions(mockRegion, mockResource);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void resolveImportResourceWithNullRegionThrowsIllegalArgumentException() {
|
||||
|
||||
AbstractImportResourceResolver importResourceResolver =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter().new TestImportResourceResolver());
|
||||
|
||||
try {
|
||||
importResourceResolver.resolve((Region<?, ?>) null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Region must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void classPathImportResourceResolverResolvesToClassPath() {
|
||||
|
||||
AbstractImportResourceResolver importResourceResolver =
|
||||
spy(new TestResourceCapableCacheDataImporterExporter().new ClassPathImportResourceResolver());
|
||||
|
||||
assertThat(importResourceResolver.getResourcePath())
|
||||
.isEqualTo(ResourcePrefix.CLASSPATH_URL_PREFIX.toUrlPrefix());
|
||||
}
|
||||
|
||||
interface ResourceLoaderAwareExportResourceResolver extends ResourceLoaderAware, ExportResourceResolver { }
|
||||
|
||||
interface ResourceLoaderAwareImportResourceResolver extends ResourceLoaderAware, ImportResourceResolver { }
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
static class TestResourceCapableCacheDataImporterExporter extends ResourceCapableCacheDataImporterExporter {
|
||||
|
||||
@Override
|
||||
protected Optional<ApplicationContext> getApplicationContext() {
|
||||
return super.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Environment> getEnvironment() {
|
||||
return super.getEnvironment();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Logger getLogger() {
|
||||
return super.getLogger();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull Region<?, ?> doExportFrom(@NonNull Region region) {
|
||||
return region;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull Region<?, ?> doImportInto(@NonNull Region region) {
|
||||
return region;
|
||||
}
|
||||
|
||||
class TestCacheResourceResolver extends AbstractCacheResourceResolver {
|
||||
|
||||
@Override
|
||||
public Optional<Resource> resolve(@NonNull Region<?, ?> region) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@NonNull @Override
|
||||
protected String getResourcePath() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class TestExportResourceResolver extends AbstractExportResourceResolver {
|
||||
|
||||
@NonNull @Override
|
||||
protected String getResourcePath() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class TestImportResourceResolver extends AbstractImportResourceResolver {
|
||||
|
||||
@NonNull @Override
|
||||
protected String getResourcePath() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class UtilityBean {
|
||||
|
||||
public static UtilityBean INSTANCE = new UtilityBean();
|
||||
|
||||
public String toUpperCase(String value) {
|
||||
return value.toUpperCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user