Restructure the Spring Boot for Apache Geode project to mirror Spring Boot's project structure.
Resolves gh-60.
This commit is contained in:
@@ -0,0 +1,756 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.cache;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
import org.apache.geode.cache.DiskStore;
|
||||
import org.apache.geode.cache.Region;
|
||||
import org.apache.geode.cache.asyncqueue.AsyncEvent;
|
||||
import org.apache.geode.cache.asyncqueue.AsyncEventListener;
|
||||
import org.apache.geode.cache.asyncqueue.AsyncEventQueue;
|
||||
import org.apache.geode.cache.asyncqueue.AsyncEventQueueFactory;
|
||||
import org.apache.geode.cache.wan.GatewayEventFilter;
|
||||
import org.apache.geode.cache.wan.GatewayEventSubstitutionFilter;
|
||||
import org.apache.geode.cache.wan.GatewaySender;
|
||||
import org.apache.geode.cache.wan.GatewaySender.OrderPolicy;
|
||||
|
||||
import org.springframework.data.gemfire.PeerRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.config.annotation.RegionConfigurer;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.geode.cache.RepositoryAsyncEventListener.AsyncEventErrorHandler;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A Spring Data for Apache Geode {@link RegionConfigurer} implementation used to configure a target {@link Region}
|
||||
* to use {@literal Asynchronous Inline Caching} based on the Spring Data {@link CrudRepository Repositories}
|
||||
* abstraction.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.function.Function
|
||||
* @see java.util.function.Predicate
|
||||
* @see org.apache.geode.cache.Cache
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventListener
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueue
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueueFactory
|
||||
* @see org.apache.geode.cache.wan.GatewayEventFilter
|
||||
* @see org.apache.geode.cache.wan.GatewayEventSubstitutionFilter
|
||||
* @see org.apache.geode.cache.wan.GatewaySender.OrderPolicy
|
||||
* @see org.springframework.data.gemfire.PeerRegionFactoryBean
|
||||
* @see org.springframework.data.gemfire.config.annotation.RegionConfigurer
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see org.springframework.geode.cache.RepositoryAsyncEventListener.AsyncEventErrorHandler
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class AsyncInlineCachingRegionConfigurer<T, ID> implements RegionConfigurer {
|
||||
|
||||
protected static final Predicate<String> DEFAULT_REGION_BEAN_NAME_PREDICATE = beanName -> false;
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link AsyncInlineCachingRegionConfigurer} initialized with
|
||||
* the given Spring Data {@link CrudRepository} and {@link Predicate} identifying the target {@link Region}
|
||||
* on which to configure {@literal Asynchronous Inline Caching}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the entity.
|
||||
* @param <ID> {@link Class type} of the identifier, or {@link Region} key.
|
||||
* @param repository {@link CrudRepository} used to perform data access operations on an external data source
|
||||
* triggered by cache events and operations on the identified {@link Region}; must not be {@literal null}.
|
||||
* @param regionBeanName {@link Predicate} used to identify the {@link Region} by {@link String name} on which
|
||||
* {@literal Asynchronous Inline Caching} will be configured.
|
||||
* @return a new {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @throws IllegalArgumentException if {@link CrudRepository} is {@literal null}.
|
||||
* @see #AsyncInlineCachingRegionConfigurer(CrudRepository, Predicate)
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
public static <T, ID> AsyncInlineCachingRegionConfigurer<T, ID> create(@NonNull CrudRepository<T, ID> repository,
|
||||
@Nullable Predicate<String> regionBeanName) {
|
||||
|
||||
return new AsyncInlineCachingRegionConfigurer<>(repository, regionBeanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link AsyncInlineCachingRegionConfigurer} initialized with
|
||||
* the given Spring Data {@link CrudRepository} and {@link String} identifying the target {@link Region}
|
||||
* on which to configure {@literal Asynchronous Inline Caching}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the entity.
|
||||
* @param <ID> {@link Class type} of the identifier, or {@link Region} key.
|
||||
* @param repository {@link CrudRepository} used to perform data access operations on an external data source
|
||||
* triggered by cache events and operations on the identified {@link Region}; must not be {@literal null}.
|
||||
* @param regionBeanName {@link String} used to identify the {@link Region} by {@link String name} on which
|
||||
* {@literal Asynchronous Inline Caching} will be configured.
|
||||
* @return a new {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @throws IllegalArgumentException if {@link CrudRepository} is {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see #create(CrudRepository, Predicate)
|
||||
* @see java.lang.String
|
||||
*/
|
||||
public static <T, ID> AsyncInlineCachingRegionConfigurer<T, ID> create(@NonNull CrudRepository<T, ID> repository,
|
||||
@Nullable String regionBeanName) {
|
||||
|
||||
return create(repository, Predicate.isEqual(regionBeanName));
|
||||
}
|
||||
|
||||
private AsyncEventErrorHandler asyncEventErrorHandler;
|
||||
|
||||
private Boolean batchConflationEnabled;
|
||||
private Boolean diskSynchronous;
|
||||
private Boolean forwardExpirationDestroy;
|
||||
private Boolean parallel;
|
||||
private Boolean persistent;
|
||||
private Boolean pauseEventDispatching;
|
||||
|
||||
private final CrudRepository<T, ID> repository;
|
||||
|
||||
private Function<AsyncEventListener, AsyncEventListener> asyncEventListenerPostProcessor;
|
||||
|
||||
private Function<AsyncEventQueue, AsyncEventQueue> asyncEventQueuePostProcessor;
|
||||
|
||||
private Function<AsyncEventQueueFactory, AsyncEventQueueFactory> asyncEventQueueFactoryPostProcessor;
|
||||
|
||||
private Integer batchSize;
|
||||
private Integer batchTimeInterval;
|
||||
private Integer dispatcherThreads;
|
||||
private Integer maximumQueueMemory;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private GatewayEventSubstitutionFilter gatewayEventSubstitutionFilter;
|
||||
|
||||
private GatewaySender.OrderPolicy orderPolicy;
|
||||
|
||||
private List<GatewayEventFilter> gatewayEventFilters;
|
||||
|
||||
private final Predicate<String> regionBeanName;
|
||||
|
||||
private String diskStoreName;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link AsyncInlineCachingRegionConfigurer} initialized with the given
|
||||
* {@link CrudRepository} and {@link Predicate} identifying the {@link Region} on which
|
||||
* {@literal Asynchronous Inline Caching} will be configured.
|
||||
*
|
||||
* @param repository {@link CrudRepository} used to perform data access operations on an external data source
|
||||
* triggered by cache events and operations on the identified {@link Region}; must not be {@literal null}.
|
||||
* @param regionBeanName {@link Predicate} used to identify the {@link Region} by {@link String name} on which
|
||||
* {@literal Asynchronous Inline Caching} will be configured.
|
||||
* @throws IllegalArgumentException if {@link CrudRepository} is {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer(@NonNull CrudRepository<T, ID> repository,
|
||||
@Nullable Predicate<String> regionBeanName) {
|
||||
|
||||
Assert.notNull(repository, "CrudRepository must not be null");
|
||||
|
||||
this.repository = repository;
|
||||
this.regionBeanName = regionBeanName != null ? regionBeanName : DEFAULT_REGION_BEAN_NAME_PREDICATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Predicate} identifying the {@link Region} on which {@literal Asynchronous Inline Caching}
|
||||
* will be configured.
|
||||
*
|
||||
* @return the {@link Predicate} used to match the {@link Region} by {@link String name} on which
|
||||
* {@literal Asynchronous Inline Caching} will be configured; never {@literal null}.
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
protected @NonNull Predicate<String> getRegionBeanName() {
|
||||
return this.regionBeanName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Spring Data {@link CrudRepository} used to perform data access operations on an external data source
|
||||
* triggered cache events and operations on the target {@link Region}.
|
||||
*
|
||||
* @return the Spring Data {@link CrudRepository} used to perform data access operations on an external data source
|
||||
* triggered cache events and operations on the target {@link Region}; never {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
*/
|
||||
protected @NonNull CrudRepository<T, ID> getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the target {@link Region} by {@link String name} with {@literal Asynchronous Inline Caching}
|
||||
* functionality.
|
||||
*
|
||||
* Effectively, this Configurer creates an {@link AsyncEventQueue} attached to the target {@link Region} with a
|
||||
* registered {@link RepositoryAsyncEventListener} to perform asynchronous data access operations triggered by
|
||||
* cache operations to an external, backend data source using a Spring Data {@link CrudRepository}.
|
||||
*
|
||||
* @param beanName {@link String} specifying the name of the target {@link Region} and bean name
|
||||
* in the Spring container.
|
||||
* @param bean {@link PeerRegionFactoryBean} containing the configuration of the target {@link Region}
|
||||
* in the Spring container.
|
||||
* @see org.springframework.data.gemfire.PeerRegionFactoryBean
|
||||
* @see #newAsyncEventQueue(Cache, String)
|
||||
*/
|
||||
@Override
|
||||
public void configure(String beanName, PeerRegionFactoryBean<?, ?> bean) {
|
||||
|
||||
if (getRegionBeanName().test(beanName)) {
|
||||
|
||||
AsyncEventQueue queue = newAsyncEventQueue((Cache) bean.getCache(), beanName);
|
||||
|
||||
bean.addAsyncEventQueues(ArrayUtils.asArray(queue));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new {@link String ID} for the {@link AsyncEventQueue}.
|
||||
*
|
||||
* @param regionBeanName {@link String name} of the target {@link Region}.
|
||||
* @return a new {@link String ID} for the {@link AsyncEventQueue}.
|
||||
*/
|
||||
protected @NonNull String generateId(@NonNull String regionBeanName) {
|
||||
|
||||
Assert.hasText(regionBeanName, () -> String.format("Region bean name [%s] must be specified", regionBeanName));
|
||||
|
||||
return regionBeanName.concat(String.format("-AEQ-%s", UUID.randomUUID().toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of an {@link AsyncEventQueue} to attach to the target {@link Region} configured for
|
||||
* {@literal Asynchronous Inline Caching}.
|
||||
*
|
||||
* @param peerCache reference to the {@link Cache peer cache}; must not be {@literal null}.
|
||||
* @param regionBeanName {@link String name} of the target {@link Region}; must not be {@literal null}.
|
||||
* @return a new {@link AsyncEventQueue}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueue
|
||||
* @see org.apache.geode.cache.Cache
|
||||
* @see #generateId(String)
|
||||
* @see #newAsyncEventQueueFactory(Cache)
|
||||
* @see #newAsyncEventQueue(AsyncEventQueueFactory, String, AsyncEventListener)
|
||||
* @see #newRepositoryAsyncEventListener()
|
||||
* @see #postProcess(AsyncEventListener)
|
||||
* @see #postProcess(AsyncEventQueue)
|
||||
* @see #postProcess(AsyncEventQueueFactory)
|
||||
*/
|
||||
protected AsyncEventQueue newAsyncEventQueue(@NonNull Cache peerCache, @NonNull String regionBeanName) {
|
||||
|
||||
AsyncEventQueueFactory asyncEventQueueFactory = newAsyncEventQueueFactory(peerCache);
|
||||
|
||||
Optional.ofNullable(this.batchConflationEnabled).ifPresent(asyncEventQueueFactory::setBatchConflationEnabled);
|
||||
Optional.ofNullable(this.batchSize).ifPresent(asyncEventQueueFactory::setBatchSize);
|
||||
Optional.ofNullable(this.batchTimeInterval).ifPresent(asyncEventQueueFactory::setBatchTimeInterval);
|
||||
Optional.ofNullable(this.diskStoreName).filter(StringUtils::hasText).ifPresent(asyncEventQueueFactory::setDiskStoreName);
|
||||
Optional.ofNullable(this.diskSynchronous).ifPresent(asyncEventQueueFactory::setDiskSynchronous);
|
||||
Optional.ofNullable(this.dispatcherThreads).ifPresent(asyncEventQueueFactory::setDispatcherThreads);
|
||||
Optional.ofNullable(this.forwardExpirationDestroy).ifPresent(asyncEventQueueFactory::setForwardExpirationDestroy);
|
||||
Optional.ofNullable(this.gatewayEventSubstitutionFilter).ifPresent(asyncEventQueueFactory::setGatewayEventSubstitutionListener);
|
||||
Optional.ofNullable(this.maximumQueueMemory).ifPresent(asyncEventQueueFactory::setMaximumQueueMemory);
|
||||
Optional.ofNullable(this.orderPolicy).ifPresent(asyncEventQueueFactory::setOrderPolicy);
|
||||
Optional.ofNullable(this.parallel).ifPresent(asyncEventQueueFactory::setParallel);
|
||||
Optional.ofNullable(this.persistent).ifPresent(asyncEventQueueFactory::setPersistent);
|
||||
|
||||
CollectionUtils.nullSafeList(this.gatewayEventFilters).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(asyncEventQueueFactory::addGatewayEventFilter);
|
||||
|
||||
if (Boolean.TRUE.equals(this.pauseEventDispatching)) {
|
||||
asyncEventQueueFactory.pauseEventDispatching();
|
||||
}
|
||||
|
||||
String asyncEventQueueId = generateId(regionBeanName);
|
||||
|
||||
AsyncEventListener asyncEventListener = newRepositoryAsyncEventListener();
|
||||
|
||||
asyncEventListener = postProcess(asyncEventListener);
|
||||
asyncEventQueueFactory = postProcess(asyncEventQueueFactory);
|
||||
|
||||
AsyncEventQueue asyncEventQueue =
|
||||
newAsyncEventQueue(asyncEventQueueFactory, asyncEventQueueId, asyncEventListener);
|
||||
|
||||
asyncEventQueue = postProcess(asyncEventQueue);
|
||||
|
||||
return asyncEventQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs (creates) a new instance of {@link AsyncEventQueue} using the given {@link AsyncEventQueueFactory}
|
||||
* with the given {@link String AEQ ID} and {@link AsyncEventListener}.
|
||||
*
|
||||
* @param factory {@link AsyncEventQueueFactory} used to create the {@link AsyncEventQueue};
|
||||
* must not be {@literal null}.
|
||||
* @param asyncEventQueueId {@link String} containing the {@literal ID} for the {@link AsyncEventQueue};
|
||||
* must not be {@literal null}.
|
||||
* @param listener {@link AsyncEventListener} registered with the {@link AsyncEventQueue} to process cache events
|
||||
* from the {@link AsyncEventQueue} attached to the {@link Region}.
|
||||
* @return a new {@link AsyncEventQueue} with the {@link String ID} and registered {@link AsyncEventListener}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueueFactory
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueue
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventListener
|
||||
*/
|
||||
protected @NonNull AsyncEventQueue newAsyncEventQueue(@NonNull AsyncEventQueueFactory factory,
|
||||
@NonNull String asyncEventQueueId, @NonNull AsyncEventListener listener) {
|
||||
|
||||
return factory.create(asyncEventQueueId, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs (creates) a new instance of the {@link AsyncEventQueueFactory} from the given {@literal peer}
|
||||
* {@link Cache}.
|
||||
*
|
||||
* @param peerCache {@literal Peer} {@link Cache} instance used to create an instance of
|
||||
* the {@link AsyncEventQueueFactory}; must not be {@literal null}.
|
||||
* @return a new instance of {@link AsyncEventQueueFactory} to create a {@link AsyncEventQueue}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueueFactory
|
||||
* @see org.apache.geode.cache.Cache
|
||||
*/
|
||||
protected @NonNull AsyncEventQueueFactory newAsyncEventQueueFactory(@NonNull Cache peerCache) {
|
||||
return peerCache.createAsyncEventQueueFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Apache Geode {@link AsyncEventListener} to register on an {@link AsyncEventQueue} attached to
|
||||
* the target {@link Region}, which uses the {@link CrudRepository} to perform data access operations on an external
|
||||
* backend data source asynchronously when cache events and operations occur on the target {@link Region}.
|
||||
*
|
||||
* @return a new {@link RepositoryAsyncEventListener}.
|
||||
* @see org.springframework.geode.cache.RepositoryAsyncEventListener
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventListener
|
||||
* @see #newRepositoryAsyncEventListener(CrudRepository)
|
||||
* @see #getRepository()
|
||||
*/
|
||||
protected @NonNull AsyncEventListener newRepositoryAsyncEventListener() {
|
||||
return newRepositoryAsyncEventListener(getRepository());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Apache Geode {@link AsyncEventListener} to register on an {@link AsyncEventQueue} attached to
|
||||
* the target {@link Region}, which uses the given {@link CrudRepository} to perform data access operations on an
|
||||
* external, backend data source asynchronously when cache events and operations occur on the target {@link Region}.
|
||||
*
|
||||
* @param repository Spring Data {@link CrudRepository} used to perform data access operations on the external,
|
||||
* backend data source; must not be {@literal null}.
|
||||
* @return a new {@link RepositoryAsyncEventListener}.
|
||||
* @see org.springframework.geode.cache.RepositoryAsyncEventListener
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventListener
|
||||
* @see #getRepository()
|
||||
*/
|
||||
protected @NonNull AsyncEventListener newRepositoryAsyncEventListener(@NonNull CrudRepository<T, ID> repository) {
|
||||
return new RepositoryAsyncEventListener<>(repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the user-defined {@link Function} to the framework constructed/provided {@link AsyncEventListener}
|
||||
* for post processing.
|
||||
*
|
||||
* @param asyncEventListener {@link AsyncEventListener} constructed by the framework and post processed by
|
||||
* end-user code encapsulated in the {@link #applyToListener(Function) configured} {@link Function}.
|
||||
* @return the post-processed {@link AsyncEventListener}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventListener
|
||||
* @see #applyToListener(Function)
|
||||
*/
|
||||
protected @NonNull AsyncEventListener postProcess(@NonNull AsyncEventListener asyncEventListener) {
|
||||
return resolveAsyncEventListenerPostProcessor().apply(asyncEventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the user-defined {@link Function} to the framework constructed/provided {@link AsyncEventQueue}
|
||||
* for post processing.
|
||||
*
|
||||
* @param asyncEventQueue {@link AsyncEventQueue} constructed by the framework and post processed by
|
||||
* end-user code encapsulated in the {@link #applyToQueue(Function) configured} {@link Function}.
|
||||
* @return the post-processed {@link AsyncEventQueue}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueue
|
||||
* @see #applyToQueue(Function)
|
||||
*/
|
||||
protected @NonNull AsyncEventQueue postProcess(@NonNull AsyncEventQueue asyncEventQueue) {
|
||||
|
||||
Function<AsyncEventQueue, AsyncEventQueue> asyncEventQueuePostProcessor =
|
||||
this.asyncEventQueuePostProcessor;
|
||||
|
||||
return asyncEventQueuePostProcessor != null
|
||||
? asyncEventQueuePostProcessor.apply(asyncEventQueue)
|
||||
: asyncEventQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the user-defined {@link Function} to the framework constructed/provided {@link AsyncEventQueueFactory}
|
||||
* for post processing.
|
||||
*
|
||||
* @param asyncEventQueueFactory {@link AsyncEventQueueFactory} constructed by the framework and post processed by
|
||||
* end-user code encapsulated in the {@link #applyToQueueFactory(Function) configured} {@link Function}.
|
||||
* @return the post-processed {@link AsyncEventQueueFactory}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueueFactory
|
||||
* @see #applyToQueueFactory(Function)
|
||||
*/
|
||||
protected @NonNull AsyncEventQueueFactory postProcess(@NonNull AsyncEventQueueFactory asyncEventQueueFactory) {
|
||||
|
||||
Function<AsyncEventQueueFactory, AsyncEventQueueFactory> asyncEventQueueFactoryPostProcessor =
|
||||
this.asyncEventQueueFactoryPostProcessor;
|
||||
|
||||
return asyncEventQueueFactoryPostProcessor != null
|
||||
? asyncEventQueueFactoryPostProcessor.apply(asyncEventQueueFactory)
|
||||
: asyncEventQueueFactory;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private @NonNull Function<AsyncEventListener, AsyncEventListener> resolveAsyncEventListenerPostProcessor() {
|
||||
|
||||
AsyncEventErrorHandler asyncEventErrorHandler = this.asyncEventErrorHandler;
|
||||
|
||||
Function<AsyncEventListener, AsyncEventListener> resolvedListenerPostProcessor = asyncEventErrorHandler != null
|
||||
? listener -> {
|
||||
|
||||
if (listener instanceof RepositoryAsyncEventListener) {
|
||||
((RepositoryAsyncEventListener<T, ID>) listener).setAsyncEventErrorHandler(asyncEventErrorHandler);
|
||||
}
|
||||
|
||||
return listener;
|
||||
}
|
||||
: Function.identity();
|
||||
|
||||
Function<AsyncEventListener, AsyncEventListener> asyncEventListenerPostProcessor =
|
||||
this.asyncEventListenerPostProcessor;
|
||||
|
||||
if (asyncEventListenerPostProcessor != null) {
|
||||
resolvedListenerPostProcessor = resolvedListenerPostProcessor.andThen(asyncEventListenerPostProcessor);
|
||||
}
|
||||
|
||||
return resolvedListenerPostProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the given user-defined {@link Function} applied to the framework constructed
|
||||
* and provided {@link AsyncEventListener} for post processing.
|
||||
*
|
||||
* @param asyncEventListenerPostProcessor user-defined {@link Function} encapsulating the logic applied to
|
||||
* the framework constructed/provided {@link AsyncEventListener} for post-processing.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventListener
|
||||
* @see java.util.function.Function
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> applyToListener(
|
||||
@Nullable Function<AsyncEventListener, AsyncEventListener> asyncEventListenerPostProcessor) {
|
||||
|
||||
this.asyncEventListenerPostProcessor = asyncEventListenerPostProcessor;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the given user-defined {@link Function} applied to the framework constructed
|
||||
* and provided {@link AsyncEventQueue} for post processing.
|
||||
*
|
||||
* @param asyncEventQueuePostProcessor user-defined {@link Function} encapsulating the logic applied to
|
||||
* the framework constructed {@link AsyncEventQueue} for post-processing.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueue
|
||||
* @see java.util.function.Function
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> applyToQueue(
|
||||
@Nullable Function<AsyncEventQueue, AsyncEventQueue> asyncEventQueuePostProcessor) {
|
||||
|
||||
this.asyncEventQueuePostProcessor = asyncEventQueuePostProcessor;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the given user-defined {@link Function} applied to the framework constructed
|
||||
* and provided {@link AsyncEventQueueFactory} for post processing.
|
||||
*
|
||||
* @param asyncEventQueueFactoryPostProcessor user-defined {@link Function} encapsulating the logic applied to
|
||||
* the framework constructed {@link AsyncEventQueueFactory} for post-processing.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventQueueFactory
|
||||
* @see java.util.function.Function
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> applyToQueueFactory(
|
||||
@Nullable Function<AsyncEventQueueFactory, AsyncEventQueueFactory> asyncEventQueueFactoryPostProcessor) {
|
||||
|
||||
this.asyncEventQueueFactoryPostProcessor = asyncEventQueueFactoryPostProcessor;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure a {@link AsyncEventErrorHandler} to handle errors thrown while processing
|
||||
* {@link AsyncEvent AsyncEvents} in the {@link AsyncEventListener}.
|
||||
*
|
||||
* @param errorHandler {@link AsyncEventErrorHandler} used to handle errors thrown while processing
|
||||
* {@link AsyncEvent AsyncEvents} in the {@link AsyncEventListener}.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @see org.springframework.geode.cache.RepositoryAsyncEventListener.AsyncEventErrorHandler
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withAsyncEventErrorHandler(
|
||||
@Nullable AsyncEventErrorHandler errorHandler) {
|
||||
|
||||
this.asyncEventErrorHandler = errorHandler;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to enable all {@link AsyncEventQueue AEQs} attached to {@link Region Regions} hosted
|
||||
* and distributed across the cache cluster to process cache events.
|
||||
*
|
||||
* Default is {@literal false}, or {@literal serial}.
|
||||
*
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @see #withSerialQueue()
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withParallelQueue() {
|
||||
this.parallel = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to enable the {@link AsyncEventQueue} to persist cache events to disk in order to
|
||||
* preserve unprocessed cache events while offline.
|
||||
*
|
||||
* Keep in mind that the {@link AsyncEventQueue} must be persistent if the data {@link Region}
|
||||
* to which the AEQ is attached is persistent.
|
||||
*
|
||||
* Default is {@literal false}.
|
||||
*
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withPersistentQueue() {
|
||||
this.persistent = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the {@link AsyncEventQueue} to conflate cache events in the queue.
|
||||
*
|
||||
* When conflation is enabled, the AEQ listener will only receive the latest update in the AEQ for cache entry
|
||||
* based on key.
|
||||
*
|
||||
* Defaults to {@literal false}.
|
||||
*
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueBatchConflationEnabled() {
|
||||
this.batchConflationEnabled = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the {@link AsyncEventQueue} {@link Integer batch size}, which determines
|
||||
* the number (i.e. threshold) of cache events that will trigger the AEQ listener, before any set period of time.
|
||||
*
|
||||
* The batch size is often used in tandem with the batch time interval, which determines when the AEQ listener
|
||||
* will be invoked after a period of time if the batch size is not reached within the period so that cache events
|
||||
* can also be processed in a timely manner if they are occurring infrequently.
|
||||
*
|
||||
* Defaults to {@literal 100}.
|
||||
*
|
||||
* @param batchSize the {@link Integer number} of cache events in the queue before the AEQ listener is called.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @see #withQueueBatchTimeInterval(Duration)
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueBatchSize(int batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the {@link AsyncEventQueue} {@link Duration batch time interval} determining
|
||||
* when the AEQ listener will be trigger before the number of cache events reaches any set size.
|
||||
*
|
||||
* The {@link Duration} is converted to milliseconds (ms), as expected by the configuration
|
||||
* of the {@link AsyncEventQueue}.
|
||||
*
|
||||
* The batch time interval is often used in tandem with batch size, which determines for how many cache events
|
||||
* in the queue will trigger the AEQ listener. If cache events are occurring rather frequently, then the batch size
|
||||
* can help reduce memory consumption by processing the cache events before the batch time interval expires.
|
||||
*
|
||||
* Defaults to {@literal 5 ms}.
|
||||
*
|
||||
* @param batchTimeInterval {@link Duration} of time to determine when the AEQ listener should be invoked with
|
||||
* any existing cache events in the queue.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueBatchTimeInterval(Duration batchTimeInterval) {
|
||||
|
||||
this.batchTimeInterval = batchTimeInterval != null
|
||||
? Long.valueOf(batchTimeInterval.toMillis()).intValue()
|
||||
: null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the {@link String name} of the {@link DiskStore} used by
|
||||
* the {@link AsyncEventQueue} to persist or overflow cache events.
|
||||
*
|
||||
* By default, the AEQ will write cache events to the {@literal DEFAULT} {@link DiskStore}.
|
||||
*
|
||||
* @param diskStoreName {@link String name} of the {@link DiskStore}.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueDiskStore(String diskStoreName) {
|
||||
this.diskStoreName = diskStoreName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the {@link AsyncEventQueue} to perform all disk write operations synchronously.
|
||||
*
|
||||
* Default is {@literal true}.
|
||||
*
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueDiskSynchronizationEnabled() {
|
||||
this.diskSynchronous = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method to configure the number of {@link Thread Threads} to process the cache events (contents)
|
||||
* in the {@link AsyncEventQueue} when the queue is parallel.
|
||||
*
|
||||
* When a queue is parallel, the total number of queues is determined by the number of Geode members
|
||||
* hosting the {@link Region} to which the queue is attached.
|
||||
*
|
||||
* When a queue is serial and multiple dispatcher threads are configured, Geode creates an additional copy of
|
||||
* the queue for each thread on each Geode member that hosts the queue. When the queue is serial and multiple
|
||||
* dispatcher threads are configure, then you can use the {@link GatewaySender} {@link OrderPolicy} to control
|
||||
* the distribution of cache events from the queue by the threads.
|
||||
*
|
||||
* Default is {@literal 5}.
|
||||
*
|
||||
* @param dispatcherThreadCount {@link Integer number} of dispatcher {@link Thread Threads} processing cache events
|
||||
* in the queue.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueDispatcherThreadCount(int dispatcherThreadCount) {
|
||||
this.dispatcherThreads = dispatcherThreadCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure whether the {@link AsyncEventQueue} is currently processing cache events
|
||||
* or is paused.
|
||||
*
|
||||
* When paused, cache events will not be dispatched to the AEQ listener for processing. Call the
|
||||
* {@link AsyncEventQueue#resumeEventDispatching()} to resume cache event processing and AEQ listener callbacks.
|
||||
*
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueEventDispatchingPaused() {
|
||||
this.pauseEventDispatching = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method to configure the {@link AsyncEventQueue} with a {@link List} of
|
||||
* {@link GatewayEventFilter GatewayEventFilters} to filter cache events sent to the configured AEQ listener.
|
||||
*
|
||||
* @param eventFilters {@link List} of {@link GatewayEventFilter GatewayEventFilters} used to control and filter
|
||||
* the cache events sent to the configured AEQ listener.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @see org.apache.geode.cache.wan.GatewayEventFilter
|
||||
* @see java.util.List
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueEventFilters(List<GatewayEventFilter> eventFilters) {
|
||||
this.gatewayEventFilters = eventFilters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the {@link AsyncEventQueue} with a
|
||||
* {@link GatewayEventSubstitutionFilter cache event substitution filter} used to replace (or "substitute")
|
||||
* the original cache entry event value enqueued in the AEQ.
|
||||
*
|
||||
* @param eventSubstitutionFilter {@link GatewayEventSubstitutionFilter} used to replace/substitute the value
|
||||
* in the enqueued cache entry event.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @see org.apache.geode.cache.wan.GatewayEventSubstitutionFilter
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueEventSubstitutionFilter(
|
||||
@Nullable GatewayEventSubstitutionFilter<ID, T> eventSubstitutionFilter) {
|
||||
|
||||
this.gatewayEventSubstitutionFilter = eventSubstitutionFilter;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure whether cache {@link Region} entry destroyed events due to expiration
|
||||
* are forwarded to the {@link AsyncEventQueue}.
|
||||
*
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueForwardedExpirationDestroyEvents() {
|
||||
this.forwardExpirationDestroy = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the maximum JVM Heap memory in megabytes used by the {@link AsyncEventQueue}.
|
||||
*
|
||||
* After the maximum memory threshold is reached then the AEQ overflows cache events to disk.
|
||||
*
|
||||
* Default to {@literal 100 MB}.
|
||||
*
|
||||
* @param maximumMemory {@link Integer} value specifying the maximum amount of memory in megabytes used by the AEQ
|
||||
* to capture cache events.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueMaxMemory(int maximumMemory) {
|
||||
this.maximumQueueMemory = maximumMemory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the {@link AsyncEventQueue} order of processing for cache events when the AEQ
|
||||
* is serial and the AEQ is using multiple dispatcher threads.
|
||||
*
|
||||
* @param orderPolicy {@link GatewaySender} {@link OrderPolicy} used to determine the order of processing
|
||||
* for cache events when the AEQ is serial and uses multiple dispatcher threads.
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @see org.apache.geode.cache.wan.GatewaySender.OrderPolicy
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withQueueOrderPolicy(
|
||||
@Nullable GatewaySender.OrderPolicy orderPolicy) {
|
||||
|
||||
this.orderPolicy = orderPolicy;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to enable a single {@link AsyncEventQueue AEQ} attached to a {@link Region Region}
|
||||
* (possibly) hosted and distributed across the cache cluster to process cache events.
|
||||
*
|
||||
* Default is {@literal false}, or {@literal serial}.
|
||||
*
|
||||
* @return this {@link AsyncInlineCachingRegionConfigurer}.
|
||||
* @see #withParallelQueue()
|
||||
*/
|
||||
public AsyncInlineCachingRegionConfigurer<T, ID> withSerialQueue() {
|
||||
this.parallel = false;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.cache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.geode.cache.CacheLoader;
|
||||
import org.apache.geode.cache.CacheWriter;
|
||||
import org.apache.geode.cache.Region;
|
||||
|
||||
import org.springframework.data.gemfire.PeerRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.config.annotation.RegionConfigurer;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link RegionConfigurer} implementation used to enable Inline Caching on a designated {@link Region}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.function.Predicate
|
||||
* @see org.apache.geode.cache.CacheLoader
|
||||
* @see org.apache.geode.cache.CacheWriter
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.data.gemfire.config.annotation.RegionConfigurer
|
||||
* @see org.springframework.geode.cache.RepositoryCacheLoaderRegionConfigurer
|
||||
* @see org.springframework.geode.cache.RepositoryCacheWriterRegionConfigurer
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class InlineCachingRegionConfigurer<T, ID> implements RegionConfigurer {
|
||||
|
||||
private final List<RegionConfigurer> regionConfigurers = new ArrayList<>();
|
||||
|
||||
private final RegionConfigurer compositeRegionConfigurer = new RegionConfigurer() {
|
||||
|
||||
@Override
|
||||
public void configure(String beanName, ClientRegionFactoryBean<?, ?> bean) {
|
||||
regionConfigurers.forEach(regionConfigurer -> regionConfigurer.configure(beanName, bean));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(String beanName, PeerRegionFactoryBean<?, ?> bean) {
|
||||
regionConfigurers.forEach(regionConfigurer -> regionConfigurer.configure(beanName, bean));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link InlineCachingRegionConfigurer} initialized with
|
||||
* the given {@link CrudRepository} used for Inline Caching and {@link Predicate} used to identify
|
||||
* the target {@link Region} on which the {@link CacheLoader} and {@link CacheWriter} will be registered.
|
||||
*
|
||||
* @param repository Spring Data {@link CrudRepository} used for Inline Caching between a {@link Region}
|
||||
* and external data source.
|
||||
* @param regionBeanName {@link Predicate} identifying the target {@link Region} on which to enable Inline Caching.
|
||||
* @throws IllegalArgumentException if {@link CrudRepository} is {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
public InlineCachingRegionConfigurer(@NonNull CrudRepository<T, ID> repository,
|
||||
@Nullable Predicate<String> regionBeanName) {
|
||||
|
||||
Assert.notNull(repository, "CrudRepository is required");
|
||||
|
||||
regionBeanName = regionBeanName != null ? regionBeanName : beanName -> false;
|
||||
|
||||
this.regionConfigurers.add(newRepositoryCacheLoaderRegionConfigurer(repository, regionBeanName));
|
||||
this.regionConfigurers.add(newRepositoryCacheWriterRegionConfigurer(repository, regionBeanName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link RepositoryCacheLoaderRegionConfigurer} initialized with
|
||||
* the given {@link CrudRepository} to load (read-through) {@link Region} values on cache misses
|
||||
* and {@link Predicate} to identify the target {@link Region} on which to register the {@link CacheLoader}.
|
||||
*
|
||||
* @param repository {@link CrudRepository} used to load {@link Region} values on cache misses.
|
||||
* @param regionBeanName {@link Predicate} used to identify the target {@link Region} on which
|
||||
* to register the {@link CacheLoader}.
|
||||
* @return a new {@link RepositoryCacheLoaderRegionConfigurer}.
|
||||
* @see org.springframework.geode.cache.RepositoryCacheLoaderRegionConfigurer
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
protected RepositoryCacheLoaderRegionConfigurer<T, ID> newRepositoryCacheLoaderRegionConfigurer(
|
||||
@NonNull CrudRepository<T, ID> repository, @Nullable Predicate<String> regionBeanName) {
|
||||
|
||||
return new RepositoryCacheLoaderRegionConfigurer<>(repository, regionBeanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link RepositoryCacheWriterRegionConfigurer} initialized with
|
||||
* the given {@link CrudRepository} to write-through to an external data source and {@link Predicate}
|
||||
* to identify the target {@link Region} on which to register the {@link CacheWriter}.
|
||||
*
|
||||
* @param repository {@link CrudRepository} used to write-through to the external data source.
|
||||
* @param regionBeanName {@link Predicate} used to identify the target {@link Region} on which
|
||||
* to register the {@link CacheWriter}.
|
||||
* @return a new {@link RepositoryCacheWriterRegionConfigurer}.
|
||||
* @see org.springframework.geode.cache.RepositoryCacheWriterRegionConfigurer
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
protected RepositoryCacheWriterRegionConfigurer<T, ID> newRepositoryCacheWriterRegionConfigurer(
|
||||
@NonNull CrudRepository<T, ID> repository, @Nullable Predicate<String> regionBeanName) {
|
||||
|
||||
return new RepositoryCacheWriterRegionConfigurer<>(repository, regionBeanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(String beanName, ClientRegionFactoryBean<?, ?> bean) {
|
||||
this.compositeRegionConfigurer.configure(beanName, bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(String beanName, PeerRegionFactoryBean<?, ?> bean) {
|
||||
this.compositeRegionConfigurer.configure(beanName, bean);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,604 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.cache;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.geode.cache.Operation;
|
||||
import org.apache.geode.cache.asyncqueue.AsyncEvent;
|
||||
import org.apache.geode.cache.asyncqueue.AsyncEventListener;
|
||||
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An Apache Geode {@link AsyncEventListener} that uses a Spring Data {@link CrudRepository} to perform
|
||||
* data access operations to a backend, external data source asynchronously, triggered by cache operations.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.function.Function
|
||||
* @see org.apache.geode.cache.Operation
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEvent
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEventListener
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class RepositoryAsyncEventListener<T, ID> implements AsyncEventListener {
|
||||
|
||||
protected static final AsyncEventErrorHandler DEFAULT_ASYNC_EVENT_ERROR_HANDLER = eventError -> false;
|
||||
|
||||
private AsyncEventErrorHandler asyncEventErrorHandler = DEFAULT_ASYNC_EVENT_ERROR_HANDLER;
|
||||
|
||||
private final AtomicBoolean hasFired = new AtomicBoolean(false);
|
||||
|
||||
private final AtomicLong firedCount = new AtomicLong(0L);
|
||||
|
||||
private final CrudRepository<T, ID> repository;
|
||||
|
||||
private final List<AsyncEventOperationRepositoryFunction<T, ID>> repositoryFunctions = new CopyOnWriteArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link RepositoryAsyncEventListener} initialized with the given Spring Data
|
||||
* {@link CrudRepository}.
|
||||
*
|
||||
* @param repository Spring Data {@link CrudRepository} used to perform data access operations to a backend,
|
||||
* external data source when triggered by a cache operation; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link CrudRepository} is {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
*/
|
||||
public RepositoryAsyncEventListener(@NonNull CrudRepository<T, ID> repository) {
|
||||
|
||||
Assert.notNull(repository, "CrudRepository must not be null");
|
||||
|
||||
this.repository = repository;
|
||||
|
||||
this.repositoryFunctions.addAll(Arrays.asList(
|
||||
new CreateUpdateAsyncEventRepositoryFunction<>(this),
|
||||
new RemoveAsyncEventRepositoryFunction<>(this)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this listener has (ever) been fired (triggered) by the GemFire/Geode AEQ system.
|
||||
*
|
||||
* @return a boolean value indicating whether this listener has been fired (triggered).
|
||||
* @see #hasFiredSinceLastCheck()
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public boolean hasFired() {
|
||||
return getFiredCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this listener has been fired (triggered) by the GemFire/Geode AEQ system
|
||||
* since the last check.
|
||||
*
|
||||
* A call to this method clears the flag.
|
||||
*
|
||||
* @return a boolean value indicating whether this listener has been fired (triggered) since the last check.
|
||||
* @see #hasFired()
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public boolean hasFiredSinceLastCheck() {
|
||||
return this.hasFired.compareAndSet(true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how many times this listener has been fired (triggered) by the GemFire/Geode AEQ system.
|
||||
*
|
||||
* @return a {@link Long} value indicating how many times this listener has been fired (triggered).
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public long getFiredCount() {
|
||||
return this.firedCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures an {@link AsyncEventErrorHandler} to handle errors that may occur when this listener is invoked with
|
||||
* a batch of {@link AsyncEvent AsyncEvents}.
|
||||
*
|
||||
* Since the processing of {@link AsyncEvent AsyncEvents} is asynchronous, the {@link AsyncEventErrorHandler} gives
|
||||
* users the opportunity to respond to errors for each {@link AsyncEvent} as it is is processed given this listener
|
||||
* is designed to coordinate data/state changes occurring in an Apache Geode cache with an external data source.
|
||||
*
|
||||
* @param asyncEventErrorHandler {@link AsyncEventErrorHandler} used to handle errors while processing the batch of
|
||||
* {@link AsyncEvent AsyncEvents}.
|
||||
* @see AsyncEventErrorHandler
|
||||
*/
|
||||
public void setAsyncEventErrorHandler(@Nullable AsyncEventErrorHandler asyncEventErrorHandler) {
|
||||
this.asyncEventErrorHandler = asyncEventErrorHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured {@link AsyncEventErrorHandler} used to handle errors that may occur when this listener
|
||||
* is invoked with a batch of {@link AsyncEvent AsyncEvents}.
|
||||
*
|
||||
* Defaults to an {@link AsyncEventErrorHandler} that always returns {@literal false} on any error.
|
||||
*
|
||||
* @return the configured {@link AsyncEventErrorHandler}; never {@literal null}.
|
||||
* @see AsyncEventErrorHandler
|
||||
*/
|
||||
protected @NonNull AsyncEventErrorHandler getAsyncEventErrorHandler() {
|
||||
return this.asyncEventErrorHandler != null ? this.asyncEventErrorHandler : DEFAULT_ASYNC_EVENT_ERROR_HANDLER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the configured Spring Data {@link CrudRepository} used by this {@link AsyncEventListener}
|
||||
* to perform data access operations to a external, backend data source asynchronously when triggered by a cache
|
||||
* operation.
|
||||
*
|
||||
* @return a reference to the configured Spring Data {@link CrudRepository}; never {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
*/
|
||||
protected @NonNull CrudRepository<T, ID> getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link List} of {@link AsyncEventOperationRepositoryFunction} objects used to process
|
||||
* {@link AsyncEvent AsyncEvents} passed to this listener by inspecting the {@link Operation}
|
||||
* on the {@link AsyncEvent} and calling the appropriate {@link CrudRepository} method.
|
||||
*
|
||||
* @return a {@link List} of {@link AsyncEventOperationRepositoryFunction} objects to process
|
||||
* the {@link AsyncEvent AsyncEvents}; never {@literal null}.
|
||||
* @see AsyncEventOperationRepositoryFunction
|
||||
*/
|
||||
protected @NonNull List<AsyncEventOperationRepositoryFunction<T, ID>> getRepositoryFunctions() {
|
||||
return this.repositoryFunctions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes each {@link AsyncEvent} in order by first determining whether the {@link AsyncEvent} can be processed
|
||||
* by this listener and then invokes the appropriate Spring Data {@link CrudRepository} data access operation
|
||||
* corresponding to the {@link AsyncEvent} {@link Operation}.
|
||||
*
|
||||
* @param events {@link List} of {@link AsyncEvent AsyncEvents} to process.
|
||||
* @return a boolean value indicating whether all {@link AsyncEvent AsyncEvents} were processed successfully
|
||||
* by this listener.
|
||||
* If any {@link AsyncEvent} fails to be processed (just one), then this method will return {@literal false}.
|
||||
* If any {@link AsyncEvent} cannot be handled, then this method will return {@literal false}, even if other
|
||||
* {@link AsyncEvent AsyncEvents} were successfully processed.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEvent
|
||||
* @see AsyncEventOperationRepositoryFunction
|
||||
* @see #getRepositoryFunctions()
|
||||
* @see java.util.List
|
||||
*/
|
||||
@Override
|
||||
public final boolean processEvents(List<AsyncEvent> events) {
|
||||
|
||||
try {
|
||||
return doProcessEvents(events);
|
||||
}
|
||||
finally {
|
||||
this.firedCount.incrementAndGet();
|
||||
this.hasFired.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #processEvents(List)
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
protected boolean doProcessEvents(List<AsyncEvent> events) {
|
||||
|
||||
AtomicBoolean result = new AtomicBoolean(true);
|
||||
|
||||
CollectionUtils.nullSafeList(events).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(event -> {
|
||||
|
||||
Optional<AsyncEventOperationRepositoryFunction<T, ID>> repositoryFunction =
|
||||
getRepositoryFunctions().stream()
|
||||
.filter(function -> function.canProcess(event))
|
||||
.findFirst();
|
||||
|
||||
boolean processed = Boolean.TRUE.equals(repositoryFunction
|
||||
.map(function -> function.apply(event))
|
||||
.orElse(false));
|
||||
|
||||
result.compareAndSet(true, processed);
|
||||
});
|
||||
|
||||
return result.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link AsyncEventOperationRepositoryFunction} capable of processing {@link AsyncEvent AsyncEvents}
|
||||
* by {@link Operation} and invoking the appropriate Spring Data {@link CrudRepository} data access operation.
|
||||
*
|
||||
* {@link AsyncEventOperationRepositoryFunction AsyncEventOperationRepositoryFunctions} can be registered for
|
||||
* {@link AsyncEvent} {@link Operation Operations} not currently handled by this listener. Alternatively, users can
|
||||
* override existing {@link AsyncEventOperationRepositoryFunction AsyncEventOperationRepositoryFunctions} provided
|
||||
* by this listener to alter the default behavior, or effectively the Spring Data {@link CrudRepository} data access
|
||||
* operation invoked based on the {@link AsyncEvent} {@link Operation}. The {@code repositoryFunction} arguments are
|
||||
* prepended to the {@link List} of registered {@link Function Functions} to implement the override, where the first
|
||||
* {@link Function} found capable of handling the {@link AsyncEvent} {@link Operation} will be applied.
|
||||
*
|
||||
* @param repositoryFunction {@link AsyncEventOperationRepositoryFunction} used to process
|
||||
* {@link AsyncEvent AsyncEvents} by {@link Operation} invoking the appropriate Spring Data {@link CrudRepository}
|
||||
* data access operation; must not be {@literal null}.
|
||||
* @return a boolean value indicating whether the registration was successful.
|
||||
* @see AsyncEventOperationRepositoryFunction
|
||||
* @see #getRepositoryFunctions()
|
||||
*/
|
||||
public boolean register(@NonNull AsyncEventOperationRepositoryFunction<T, ID> repositoryFunction) {
|
||||
|
||||
if (repositoryFunction != null) {
|
||||
getRepositoryFunctions().add(0, repositoryFunction);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given {@link AsyncEventOperationRepositoryFunction} from this listener.
|
||||
*
|
||||
* @param repositoryFunction {@link AsyncEventOperationRepositoryFunction} to unregister.
|
||||
* @return a boolean value indicating whether the un-registration was successful.
|
||||
* @see AsyncEventOperationRepositoryFunction
|
||||
* @see #getRepositoryFunctions()
|
||||
*/
|
||||
public boolean unregister(@Nullable AsyncEventOperationRepositoryFunction<T, ID> repositoryFunction) {
|
||||
return getRepositoryFunctions().remove(repositoryFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AsyncEventError} is a wrapper class encapsulating the {@link AsyncEvent} along with
|
||||
* the {@link Throwable error} that was thrown while processing the event.
|
||||
*
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEvent
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public static class AsyncEventError {
|
||||
|
||||
private final AsyncEvent<?, ?> event;
|
||||
|
||||
private final Throwable cause;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link AsyncEventError} initialized with the required {@link AsyncEvent}
|
||||
* and {@link Throwable} thrown while processing the event.
|
||||
*
|
||||
* @param event processed {@link AsyncEvent}; must not be {@literal null}.
|
||||
* @param cause {@link Throwable error} thrown while processing the event; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if the {@link AsyncEvent} or the {@link Throwable} are {@literal null}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEvent
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public AsyncEventError(@NonNull AsyncEvent<?, ?> event, @NonNull Throwable cause) {
|
||||
|
||||
Assert.notNull(event, "AsyncEvent must not be null");
|
||||
Assert.notNull(cause, "Cause must not be null");
|
||||
|
||||
this.event = event;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Throwable} thrown while processing the {@link AsyncEvent}.
|
||||
*
|
||||
* @return the {@link Throwable} thrown while processing the {@link AsyncEvent}.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public @NonNull Throwable getCause() {
|
||||
return this.cause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link AsyncEvent} being processed when the {@link Throwable error} occurred.
|
||||
*
|
||||
* @return the {@link AsyncEvent} being processed when the {@link Throwable error} occurred.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEvent
|
||||
*/
|
||||
public @NonNull AsyncEvent<?, ?> getEvent() {
|
||||
return this.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Error [%s] thrown when processing AsyncEvent [%s]",
|
||||
getCause().getMessage(), getEvent());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link AsyncEventErrorHandler} interface is a {@link Function} and {@link FunctionalInterface} used to
|
||||
* handle errors while processing {@link AsyncEvent AsyncEvents}.
|
||||
*
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @see java.util.function.Function
|
||||
* @see AsyncEventError
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AsyncEventErrorHandler extends Function<AsyncEventError, Boolean> { }
|
||||
|
||||
/**
|
||||
* The {@link AsyncEventOperationRepositoryFunction} interface is a {@link Function} and {@link FunctionalInterface}
|
||||
* that translates the {@link AsyncEvent} {@link Operation} into a Spring Data {@link CrudRepository} method
|
||||
* invocation.
|
||||
*
|
||||
* @param <T> {@link Class type} of the entity tied to the event.
|
||||
* @param <ID> {@link Class type} of the identifier of the entity.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEvent
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @see java.util.function.Function
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AsyncEventOperationRepositoryFunction<T, ID> extends Function<AsyncEvent<ID, T>, Boolean> {
|
||||
|
||||
/**
|
||||
* Determines whether the given {@link AsyncEvent} can be processed by this {@link Function}.
|
||||
*
|
||||
* Implementing classes must override this method to specify which {@link AsyncEvent}
|
||||
* {@link Operation Operations} they are capable of processing.
|
||||
*
|
||||
* @param event {@link AsyncEvent} to evaluate.
|
||||
* @return a boolean value indicating whether this {@link Function} is capable of processing
|
||||
* the given {@link AsyncEvent}. Default returns {@literal false}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEvent
|
||||
*/
|
||||
default boolean canProcess(@Nullable AsyncEvent<ID, T> event) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AbstractAsyncEventOperationRepositoryFunction} is an abstract base class implementing the
|
||||
* {@link AsyncEventOperationRepositoryFunction} interface to provided a default {@literal template} implementation
|
||||
* of the {@link Function#apply(Object)} method.
|
||||
*
|
||||
* @param <T> {@link Class type} of the entity tied to the event.
|
||||
* @param <ID> {@link Class type} of the identifier of the entity.
|
||||
* @see AsyncEventOperationRepositoryFunction
|
||||
*/
|
||||
public static abstract class AbstractAsyncEventOperationRepositoryFunction<T, ID>
|
||||
implements AsyncEventOperationRepositoryFunction<T, ID> {
|
||||
|
||||
private final RepositoryAsyncEventListener<T, ID> listener;
|
||||
|
||||
/**
|
||||
* Constructs an new instance of {@link AbstractAsyncEventOperationRepositoryFunction} initialized with
|
||||
* the given, required {@link RepositoryAsyncEventListener} to which this function is associated.
|
||||
*
|
||||
* @param listener {@link RepositoryAsyncEventListener} processing {@link AsyncEvent AsyncEvents}
|
||||
* by invoking this {@link Function} to handle them.
|
||||
* @throws IllegalArgumentException if {@link RepositoryAsyncEventListener} is {@literal null}.
|
||||
* @see RepositoryAsyncEventListener
|
||||
*/
|
||||
public AbstractAsyncEventOperationRepositoryFunction(@NonNull RepositoryAsyncEventListener<T, ID> listener) {
|
||||
|
||||
Assert.notNull(listener, "RepositoryAsyncEventListener must not be null");
|
||||
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias to the {@link RepositoryAsyncEventListener#getAsyncEventErrorHandler() configured}
|
||||
* {@link RepositoryAsyncEventListener} {@link AsyncEventErrorHandler}.
|
||||
*
|
||||
* @return the configured {@link AsyncEventErrorHandler}; never {@literal null}.
|
||||
* @see RepositoryAsyncEventListener#getAsyncEventErrorHandler()
|
||||
* @see AsyncEventErrorHandler
|
||||
* @see #getListener()
|
||||
*/
|
||||
protected AsyncEventErrorHandler getErrorHandler() {
|
||||
return getListener().getAsyncEventErrorHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the associated {@link RepositoryAsyncEventListener}.
|
||||
*
|
||||
* @return a reference to the associated {@link RepositoryAsyncEventListener}; never {@literal null}.
|
||||
* @see RepositoryAsyncEventListener
|
||||
*/
|
||||
protected @NonNull RepositoryAsyncEventListener<T, ID> getListener() {
|
||||
return this.listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias to the {@link RepositoryAsyncEventListener#getRepository() configured}
|
||||
* {@link RepositoryAsyncEventListener} {@link CrudRepository}.
|
||||
*
|
||||
* @return the configured {@link CrudRepository}; never {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see RepositoryAsyncEventListener#getRepository()
|
||||
* @see #getListener()
|
||||
*/
|
||||
protected @NonNull CrudRepository<T, ID> getRepository() {
|
||||
return getListener().getRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the given {@link AsyncEvent} by first determining whether the event can be processed by this
|
||||
* {@link Function}, and then proceeds to extract the {@link AsyncEvent#getDeserializedValue() entity}
|
||||
* associated with the event to invoke the appropriate Spring Data {@link CrudRepository} data access operation
|
||||
* determined by the {@link AsyncEvent} {@link Operation}.
|
||||
*
|
||||
* If an {@link Throwable error} is thrown while processing the {@link AsyncEvent}, then the
|
||||
* {@link AsyncEventErrorHandler} is called to handle the error and perform any necessary/required
|
||||
* post-processing actions.
|
||||
*
|
||||
* {@link AsyncEventErrorHandler} can be implemented to retry the operation with incremental backoff, based on
|
||||
* count or time, record the failure, perform resource cleanup actions, whatever is necessary and appropriate
|
||||
* to the application use case.
|
||||
*
|
||||
* @param event {@link AsyncEvent} to process.
|
||||
* @return a boolean value indicating whether the event was successfully processed.
|
||||
* @throws IllegalStateException if the resolve entity is {@literal null}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEvent
|
||||
* @see AsyncEventErrorHandler
|
||||
* @see #canProcess(AsyncEvent)
|
||||
* @see #doRepositoryOp(Object)
|
||||
* @see #resolveEntity(AsyncEvent)
|
||||
* @see #getErrorHandler()
|
||||
*/
|
||||
@Override
|
||||
public Boolean apply(@Nullable AsyncEvent<ID, T> event) {
|
||||
|
||||
try {
|
||||
if (canProcess(event)) {
|
||||
|
||||
T entity = resolveEntity(event);
|
||||
|
||||
doRepositoryOp(entity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Throwable cause) {
|
||||
return getErrorHandler().apply(new AsyncEventError(event, cause));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the appropriate Spring Data {@link CrudRepository} data access operation based on the
|
||||
* {@link AsyncEvent} {@link Operation} as determined by {@link AsyncEvent#getOperation()}.
|
||||
*
|
||||
* @param <R> {@link Class type} of the Spring Data {@link CrudRepository} data access operation return value.
|
||||
* @param entity entity to process.
|
||||
* @return the result of invoking the Spring Data {@link CrudRepository} data access operation.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
*/
|
||||
protected abstract <R> R doRepositoryOp(@NonNull T entity);
|
||||
|
||||
/**
|
||||
* Resolves the {@link AsyncEvent#getDeserializedValue() entity} associated with the {@link AsyncEvent}.
|
||||
*
|
||||
* @param event {@link AsyncEvent} from which to resolve the entity.
|
||||
* @return the resolve entity from the {@link AsyncEvent}.
|
||||
* @throws IllegalArgumentException if {@link AsyncEvent} is {@literal null}.
|
||||
* @throws IllegalStateException if the resolved {@link AsyncEvent#getDeserializedValue() entity}
|
||||
* is {@literal null}.
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEvent#getDeserializedValue()
|
||||
* @see org.apache.geode.cache.asyncqueue.AsyncEvent
|
||||
*/
|
||||
protected T resolveEntity(@NonNull AsyncEvent<ID, T> event) {
|
||||
|
||||
Assert.notNull(event, "AsyncEvent must not be null");
|
||||
|
||||
T entity = event.getDeserializedValue();
|
||||
|
||||
Assert.state(entity != null, "The entity (deserialized value) was null");
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link AsyncEventOperationRepositoryFunction} capable of handling {@link Operation#CREATE}
|
||||
* and {@link Operation#UPDATE} {@link AsyncEvent AsyncEvents}.
|
||||
*
|
||||
* Invokes the {@link CrudRepository#save(Object)} data access operation.
|
||||
*
|
||||
* @param <T> {@link Class type} of the entity tied to the event.
|
||||
* @param <ID> {@link Class type} of the identifier of the entity.
|
||||
*/
|
||||
public static class CreateUpdateAsyncEventRepositoryFunction<T, ID>
|
||||
extends AbstractAsyncEventOperationRepositoryFunction<T, ID> {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link CreateUpdateAsyncEventRepositoryFunction} initialized with the given,
|
||||
* required {@link RepositoryAsyncEventListener}.
|
||||
*
|
||||
* @param listener {@link RepositoryAsyncEventListener} forwarding {@link AsyncEvent AsyncEvents} for processing
|
||||
* by this {@link Function}
|
||||
* @see RepositoryAsyncEventListener
|
||||
*/
|
||||
public CreateUpdateAsyncEventRepositoryFunction(@NonNull RepositoryAsyncEventListener<T, ID> listener) {
|
||||
super(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean canProcess(@Nullable AsyncEvent<ID, T> event) {
|
||||
|
||||
Operation operation = event != null ? event.getOperation() : null;
|
||||
|
||||
return operation != null && (operation.isCreate() || operation.isUpdate());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <R> R doRepositoryOp(T entity) {
|
||||
return (R) getRepository().save(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link Function} implementation capable of handling {@link Operation#REMOVE} {@link AsyncEvent AsyncEvents}.
|
||||
*
|
||||
* Invokes the {@link CrudRepository#delete(Object)} data access operation.
|
||||
*
|
||||
* @param <T> {@link Class type} of the entity tied to the event.
|
||||
* @param <ID> {@link Class type} of the identifier of the entity.
|
||||
*/
|
||||
public static class RemoveAsyncEventRepositoryFunction<T, ID>
|
||||
extends AbstractAsyncEventOperationRepositoryFunction<T, ID> {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link RemoveAsyncEventRepositoryFunction} initialized with the given, required
|
||||
* {@link RepositoryAsyncEventListener}.
|
||||
*
|
||||
* @param listener {@link RepositoryAsyncEventListener} forwarding {@link AsyncEvent AsyncEvents} for processing
|
||||
* by this {@link Function}
|
||||
* @see RepositoryAsyncEventListener
|
||||
*/
|
||||
public RemoveAsyncEventRepositoryFunction(@NonNull RepositoryAsyncEventListener<T, ID> listener) {
|
||||
super(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean canProcess(@Nullable AsyncEvent<ID, T> event) {
|
||||
|
||||
Operation operation = event != null ? event.getOperation() : null;
|
||||
|
||||
return Operation.REMOVE.equals(operation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
protected <R> R doRepositoryOp(T entity) {
|
||||
getRepository().delete(entity);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.cache;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.geode.cache.CacheLoader;
|
||||
import org.apache.geode.cache.CacheLoaderException;
|
||||
import org.apache.geode.cache.CacheRuntimeException;
|
||||
import org.apache.geode.cache.LoaderHelper;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.geode.cache.support.RepositoryCacheLoaderWriterSupport;
|
||||
|
||||
/**
|
||||
* A {@link CacheLoader} implementation backed by a Spring Data {@link CrudRepository} used to load an entity
|
||||
* from an external data source.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.CacheLoader
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see org.springframework.geode.cache.support.CacheLoaderSupport
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class RepositoryCacheLoader<T, ID> extends RepositoryCacheLoaderWriterSupport<T, ID> {
|
||||
|
||||
protected static final String CACHE_LOAD_EXCEPTION_MESSAGE = "Error while loading Entity [%s] with Repository [%s]";
|
||||
|
||||
public RepositoryCacheLoader(CrudRepository<T, ID> repository) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T load(LoaderHelper<ID, T> helper) throws CacheLoaderException {
|
||||
|
||||
try {
|
||||
return getRepository().findById(helper.getKey()).orElse(null);
|
||||
}
|
||||
catch (Exception cause) {
|
||||
throw newCacheRuntimeException(() -> String.format(CACHE_LOAD_EXCEPTION_MESSAGE,
|
||||
helper.getKey(), getRepository().getClass().getName()), cause);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheRuntimeException newCacheRuntimeException(Supplier<String> messageSupplier, Throwable cause) {
|
||||
return new CacheLoaderException(messageSupplier.get(), cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.cache;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.geode.cache.CacheLoader;
|
||||
import org.apache.geode.cache.Region;
|
||||
|
||||
import org.springframework.data.gemfire.PeerRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.config.annotation.RegionConfigurer;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Spring Data {@link RegionConfigurer} implementation used to adapt and register a Spring Data {@link CrudRepository}
|
||||
* as a {@link CacheLoader} for a targeted {@link Region}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @param <T> {@link Class type} of the persistent entity.
|
||||
* @param <ID> {@link Class type} of the persistent entity identifier (ID).
|
||||
* @see java.util.function.Predicate
|
||||
* @see org.apache.geode.cache.CacheLoader
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.data.gemfire.PeerRegionFactoryBean
|
||||
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
|
||||
* @see org.springframework.data.gemfire.config.annotation.RegionConfigurer
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class RepositoryCacheLoaderRegionConfigurer<T, ID> implements RegionConfigurer {
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link RepositoryCacheLoaderRegionConfigurer} initialized with
|
||||
* the given Spring Data {@link CrudRepository} used to load {@link Region} values on cache misses as well as
|
||||
* the given {@link Predicate} used to identify/qualify the {@link Region} on which the {@link CrudRepository}
|
||||
* will be registered and used as a {@link CacheLoader}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the persistent entity.
|
||||
* @param <ID> {@link Class type} of the persistent entity identifier (ID).
|
||||
* @param repository {@link CrudRepository} used to load {@link Region} values on cache misses.
|
||||
* @param regionBeanName {@link Predicate} used to identify/qualify the {@link Region} on which
|
||||
* the {@link CrudRepository} will be registered and used as a {@link CacheLoader}.
|
||||
* @return a new instance of {@link RepositoryCacheLoaderRegionConfigurer}.
|
||||
* @throws IllegalArgumentException if {@link CrudRepository} is {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see java.util.function.Predicate
|
||||
* @see #RepositoryCacheLoaderRegionConfigurer(CrudRepository, Predicate)
|
||||
*/
|
||||
public static <T, ID> RepositoryCacheLoaderRegionConfigurer<T, ID> create(@NonNull CrudRepository<T, ID> repository,
|
||||
@Nullable Predicate<String> regionBeanName) {
|
||||
|
||||
return new RepositoryCacheLoaderRegionConfigurer<>(repository, regionBeanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link RepositoryCacheLoaderRegionConfigurer} initialized with
|
||||
* the given Spring Data {@link CrudRepository} used to load {@link Region} values on cache misses as well as
|
||||
* the given {@link String} identifying/qualifying the {@link Region} on which the {@link CrudRepository}
|
||||
* will be registered and used as a {@link CacheLoader}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the persistent entity.
|
||||
* @param <ID> {@link Class type} of the persistent entity identifier (ID).
|
||||
* @param repository {@link CrudRepository} used to load {@link Region} values on cache misses.
|
||||
* @param regionBeanName {@link String} containing the bean name identifying/qualifying the {@link Region}
|
||||
* on which the {@link CrudRepository} will be registered and used as a {@link CacheLoader}.
|
||||
* @return a new instance of {@link RepositoryCacheLoaderRegionConfigurer}.
|
||||
* @throws IllegalArgumentException if {@link CrudRepository} is {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see java.lang.String
|
||||
* @see #create(CrudRepository, Predicate)
|
||||
*/
|
||||
public static <T, ID> RepositoryCacheLoaderRegionConfigurer<T, ID> create(@NonNull CrudRepository<T, ID> repository,
|
||||
@Nullable String regionBeanName) {
|
||||
|
||||
return create(repository, Predicate.isEqual(regionBeanName));
|
||||
}
|
||||
|
||||
private final CrudRepository<T, ID> repository;
|
||||
|
||||
private final Predicate<String> regionBeanName;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link RepositoryCacheLoaderRegionConfigurer} initialized with the given Spring Data
|
||||
* {@link CrudRepository} used to load {@link Region} values on cache misses as well as the given {@link Predicate}
|
||||
* used to identify/qualify the {@link Region} on which the {@link CrudRepository} will be registered
|
||||
* and used as a {@link CacheLoader}.
|
||||
*
|
||||
* @param repository {@link CrudRepository} used to load {@link Region} values on cache misses.
|
||||
* @param regionBeanName {@link Predicate} used to identify/qualify the {@link Region} on which
|
||||
* the {@link CrudRepository} will be registered and used as a {@link CacheLoader}.
|
||||
* @throws IllegalArgumentException if {@link CrudRepository} is {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
public RepositoryCacheLoaderRegionConfigurer(@NonNull CrudRepository<T, ID> repository,
|
||||
@Nullable Predicate<String> regionBeanName) {
|
||||
|
||||
Assert.notNull(repository, "CrudRepository is required");
|
||||
|
||||
this.repository = repository;
|
||||
this.regionBeanName = regionBeanName != null ? regionBeanName : beanName -> false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured {@link Predicate} used to identify/qualify the {@link Region}
|
||||
* on which the {@link CrudRepository} will be registered as a {@link CacheLoader} for cache misses.
|
||||
*
|
||||
* @return the configured {@link Predicate} used to identify/qualify the {@link Region}
|
||||
* targeted for the {@link CacheLoader} registration.
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
protected @NonNull Predicate<String> getRegionBeanName() {
|
||||
return this.regionBeanName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured Spring Data {@link CrudRepository} adapted/wrapped as a {@link CacheLoader}
|
||||
* and used to load {@link Region} values on cache misses.
|
||||
*
|
||||
* @return the configured {@link CrudRepository} used to load {@link Region} values on cache misses.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
*/
|
||||
protected @NonNull CrudRepository<T, ID> getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void configure(String beanName, ClientRegionFactoryBean<?, ?> bean) {
|
||||
|
||||
if (getRegionBeanName().test(beanName)) {
|
||||
bean.setCacheLoader(newRepositoryCacheLoader());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void configure(String beanName, PeerRegionFactoryBean<?, ?> bean) {
|
||||
|
||||
if (getRegionBeanName().test(beanName)) {
|
||||
bean.setCacheLoader(newRepositoryCacheLoader());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link RepositoryCacheLoader} adapting the {@link CrudRepository}
|
||||
* as an instance of a {@link CacheLoader}.
|
||||
*
|
||||
* @return a new {@link RepositoryCacheLoader}.
|
||||
* @see org.springframework.geode.cache.RepositoryCacheLoader
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see org.apache.geode.cache.CacheLoader
|
||||
* @see #getRepository()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected RepositoryCacheLoader newRepositoryCacheLoader() {
|
||||
return new RepositoryCacheLoader<>(getRepository());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.cache;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.geode.cache.CacheRuntimeException;
|
||||
import org.apache.geode.cache.CacheWriter;
|
||||
import org.apache.geode.cache.CacheWriterException;
|
||||
import org.apache.geode.cache.EntryEvent;
|
||||
import org.apache.geode.cache.RegionEvent;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.geode.cache.support.RepositoryCacheLoaderWriterSupport;
|
||||
import org.springframework.geode.core.util.function.FunctionUtils;
|
||||
|
||||
/**
|
||||
* A {@link CacheWriter} implementation backed by a Spring Data {@link CrudRepository} used to persist a cache entry
|
||||
* (i.e. entity) to a backend, external data source.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.CacheWriter
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see org.springframework.geode.cache.support.RepositoryCacheLoaderWriterSupport
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class RepositoryCacheWriter<T, ID> extends RepositoryCacheLoaderWriterSupport<T, ID> {
|
||||
|
||||
public RepositoryCacheWriter(CrudRepository<T, ID> repository) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeCreate(EntryEvent<ID, T> event) throws CacheWriterException {
|
||||
doRepositoryOp(event.getNewValue(), getRepository()::save);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeUpdate(EntryEvent<ID, T> event) throws CacheWriterException {
|
||||
doRepositoryOp(event.getNewValue(), getRepository()::save);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDestroy(EntryEvent<ID, T> event) throws CacheWriterException {
|
||||
|
||||
//doRepositoryOp(event.getOldValue(), FunctionUtils.toNullReturningFunction(getRepository()::delete));
|
||||
doRepositoryOp(event.getKey(), FunctionUtils.toNullReturningFunction(getRepository()::deleteById));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeRegionClear(RegionEvent<ID, T> event) throws CacheWriterException {
|
||||
|
||||
if (isNukeAndPaveEnabled()) {
|
||||
doRepositoryOp(null, FunctionUtils.toNullReturningFunction(it -> getRepository().deleteAll()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeRegionDestroy(RegionEvent<ID, T> event) throws CacheWriterException {
|
||||
// TODO: perhaps implement by releasing external data source resources
|
||||
// (i.e. destroy database object(s), e.g. DROP TABLE)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheRuntimeException newCacheRuntimeException(Supplier<String> messageSupplier, Throwable cause) {
|
||||
return new CacheWriterException(messageSupplier.get(), cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.cache;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.geode.cache.CacheWriter;
|
||||
import org.apache.geode.cache.Region;
|
||||
|
||||
import org.springframework.data.gemfire.PeerRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.config.annotation.RegionConfigurer;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Spring Data {@link RegionConfigurer} implementation used to adapt and register a Spring Data {@link CrudRepository}
|
||||
* as a {@link CacheWriter} for a targeted {@link Region}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @param <T> {@link Class type} of the persistent entity.
|
||||
* @param <ID> {@link Class type} of the persistent entity identifier (ID).
|
||||
* @see java.util.function.Predicate
|
||||
* @see org.apache.geode.cache.CacheWriter
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.data.gemfire.PeerRegionFactoryBean
|
||||
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
|
||||
* @see org.springframework.data.gemfire.config.annotation.RegionConfigurer
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class RepositoryCacheWriterRegionConfigurer<T, ID> implements RegionConfigurer {
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link RepositoryCacheWriterRegionConfigurer} initialized with
|
||||
* the given Spring Data {@link CrudRepository} used to write {@link Region} values to a backend data source
|
||||
* /data store along with a given {@link Predicate} to identify/qualify the {@link Region} on which
|
||||
* the {@link CrudRepository} will be registered and used as a {@link CacheWriter}.
|
||||
*
|
||||
* @param repository {@link CrudRepository} used to write {@link Region} values to a backend data source.
|
||||
* @param regionBeanName {@link Predicate} used to identify/qualify the {@link Region} on which
|
||||
* the {@link CrudRepository} will be registered and used as a {@link CacheWriter}.
|
||||
* @return a new instance of {@link RepositoryCacheWriterRegionConfigurer}.
|
||||
* @throws IllegalArgumentException if {@link CrudRepository} is {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see java.util.function.Predicate
|
||||
* @see #RepositoryCacheWriterRegionConfigurer(CrudRepository, Predicate)
|
||||
*/
|
||||
public static <T, ID> RepositoryCacheWriterRegionConfigurer<T, ID> create(@NonNull CrudRepository<T, ID> repository,
|
||||
@Nullable Predicate<String> regionBeanName) {
|
||||
|
||||
return new RepositoryCacheWriterRegionConfigurer<>(repository, regionBeanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link RepositoryCacheWriterRegionConfigurer} initialized with
|
||||
* the given Spring Data {@link CrudRepository} used to write {@link Region} values to a backend data source
|
||||
* /data store along with a given {@link Predicate} to identify/qualify the {@link Region} on which
|
||||
* the {@link CrudRepository} will be registered and used as a {@link CacheWriter}.
|
||||
*
|
||||
* @param repository {@link CrudRepository} used to write {@link Region} values to a backend data source.
|
||||
* @param regionBeanName {@link String} containing the bean name identifying/qualifying the {@link Region}
|
||||
* on which the {@link CrudRepository} will be registered and used as a {@link CacheWriter}.
|
||||
* @return a new instance of {@link RepositoryCacheWriterRegionConfigurer}.
|
||||
* @throws IllegalArgumentException if {@link CrudRepository} is {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see java.lang.String
|
||||
* @see #create(CrudRepository, Predicate)
|
||||
*/
|
||||
public static <T, ID> RepositoryCacheWriterRegionConfigurer<T, ID> create(@NonNull CrudRepository<T, ID> repository,
|
||||
@Nullable String regionBeanName) {
|
||||
|
||||
return create(repository, Predicate.isEqual(regionBeanName));
|
||||
}
|
||||
|
||||
private final CrudRepository<T, ID> repository;
|
||||
|
||||
private final Predicate<String> regionBeanName;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link RepositoryCacheWriterRegionConfigurer} initialized with the given Spring Data
|
||||
* {@link CrudRepository} used to write {@link Region} values to a backend data source/data store along with
|
||||
* a given {@link Predicate} to identify/qualify the {@link Region} on which the {@link CrudRepository} will be
|
||||
* registered and used as a {@link CacheWriter}.
|
||||
*
|
||||
* @param repository {@link CrudRepository} used to write {@link Region} values to a backend data source.
|
||||
* @param regionBeanName {@link Predicate} used to identify/qualify the {@link Region} on which
|
||||
* the {@link CrudRepository} will be registered and used as a {@link CacheWriter}.
|
||||
* @throws IllegalArgumentException if {@link CrudRepository} is {@literal null}.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
public RepositoryCacheWriterRegionConfigurer(@NonNull CrudRepository<T, ID> repository,
|
||||
@Nullable Predicate<String> regionBeanName) {
|
||||
|
||||
Assert.notNull(repository, "CrudRepository is required");
|
||||
|
||||
this.repository = repository;
|
||||
this.regionBeanName = regionBeanName != null ? regionBeanName : beanName -> false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured {@link Predicate} used to identify/qualify the {@link Region}
|
||||
* on which the {@link CrudRepository} will be registered as a {@link CacheWriter} for write through.
|
||||
*
|
||||
* @return the configured {@link Predicate} used to identify/qualify the {@link Region}
|
||||
* targeted for the {@link CacheWriter} registration.
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
protected @NonNull Predicate<String> getRegionBeanName() {
|
||||
return regionBeanName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured Spring Data {@link CrudRepository} adapted/wrapped as a {@link CacheWriter}
|
||||
* and used to write {@link Region} values to a backend data source/data store.
|
||||
*
|
||||
* @return the configured {@link CrudRepository} used to write {@link Region} values to a backend data source.
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
*/
|
||||
protected @NonNull CrudRepository<T, ID> getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void configure(String beanName, ClientRegionFactoryBean<?, ?> bean) {
|
||||
|
||||
if (getRegionBeanName().test(beanName)) {
|
||||
bean.setCacheWriter(newRepositoryCacheWriter());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void configure(String beanName, PeerRegionFactoryBean<?, ?> bean) {
|
||||
|
||||
if (getRegionBeanName().test(beanName)) {
|
||||
bean.setCacheWriter(newRepositoryCacheWriter());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link RepositoryCacheWriter} adapting the {@link CrudRepository}
|
||||
* as an instance of a {@link CacheWriter}.
|
||||
*
|
||||
* @return a new {@link RepositoryCacheWriter}.
|
||||
* @see org.springframework.geode.cache.RepositoryCacheWriter
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see org.apache.geode.cache.CacheWriter
|
||||
* @see #getRepository()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected RepositoryCacheWriter newRepositoryCacheWriter() {
|
||||
return new RepositoryCacheWriter<>(getRepository());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.cache.support;
|
||||
|
||||
import org.apache.geode.cache.CacheLoader;
|
||||
|
||||
/**
|
||||
* The {@link CacheLoaderSupport} interface is an extension of {@link CacheLoader} and a {@link FunctionalInterface}
|
||||
* useful in Lambda expressions.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @see org.apache.geode.cache.CacheLoader
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CacheLoaderSupport<K, V> extends CacheLoader<K, V> {
|
||||
|
||||
/**
|
||||
* Closes any resources opened and used by this {@link CacheLoader}.
|
||||
*
|
||||
* @see org.apache.geode.cache.CacheLoader#close()
|
||||
*/
|
||||
@Override
|
||||
default void close() {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.cache.support;
|
||||
|
||||
import org.apache.geode.cache.CacheWriter;
|
||||
import org.apache.geode.cache.CacheWriterException;
|
||||
import org.apache.geode.cache.EntryEvent;
|
||||
import org.apache.geode.cache.RegionEvent;
|
||||
|
||||
/**
|
||||
* Class supporting the implementation of Apache Geode {@link CacheWriter CacheWriters}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.CacheWriter
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public interface CacheWriterSupport<K, V> extends CacheWriter<K, V> {
|
||||
|
||||
@Override
|
||||
default void beforeCreate(EntryEvent<K, V> event) throws CacheWriterException { }
|
||||
|
||||
@Override
|
||||
default void beforeUpdate(EntryEvent<K, V> event) throws CacheWriterException { }
|
||||
|
||||
@Override
|
||||
default void beforeDestroy(EntryEvent<K, V> event) throws CacheWriterException { }
|
||||
|
||||
@Override
|
||||
default void beforeRegionClear(RegionEvent<K, V> event) throws CacheWriterException { }
|
||||
|
||||
@Override
|
||||
default void beforeRegionDestroy(RegionEvent<K, V> event) throws CacheWriterException { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.cache.support;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.geode.cache.CacheLoader;
|
||||
import org.apache.geode.cache.CacheLoaderException;
|
||||
import org.apache.geode.cache.CacheRuntimeException;
|
||||
import org.apache.geode.cache.CacheWriter;
|
||||
import org.apache.geode.cache.LoaderHelper;
|
||||
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base class supporting the implementation of Apache Geode {@link CacheLoader CacheLoaders}
|
||||
* and {@link CacheWriter CacheWriters} backed by Spring Data {@link Repository Repositories}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.CacheLoader
|
||||
* @see org.apache.geode.cache.CacheWriter
|
||||
* @see org.springframework.context.EnvironmentAware
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see org.springframework.data.repository.Repository
|
||||
* @see org.springframework.geode.cache.support.CacheLoaderSupport
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public abstract class RepositoryCacheLoaderWriterSupport<T, ID>
|
||||
implements CacheLoaderSupport<ID, T>, CacheWriterSupport<ID, T>, EnvironmentAware {
|
||||
|
||||
public static final String NUKE_AND_PAVE_PROPERTY = "spring.boot.data.gemfire.data.source.nuke-and-pave";
|
||||
|
||||
protected static final String DATA_ACCESS_ERROR =
|
||||
"Exception occurred while accessing entity [%s] in external data source";
|
||||
|
||||
private final CrudRepository<T, ID> repository;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
protected RepositoryCacheLoaderWriterSupport(@NonNull CrudRepository<T, ID> repository) {
|
||||
|
||||
Assert.notNull(repository, "Repository is required");
|
||||
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
protected boolean isNukeAndPaveEnabled() {
|
||||
|
||||
return getEnvironment()
|
||||
.map(env -> env.getProperty(NUKE_AND_PAVE_PROPERTY, Boolean.class))
|
||||
.orElse(Boolean.getBoolean(NUKE_AND_PAVE_PROPERTY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(@Nullable Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
protected Optional<Environment> getEnvironment() {
|
||||
return Optional.ofNullable(this.environment);
|
||||
}
|
||||
|
||||
public @NonNull CrudRepository<T, ID> getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
protected <S, R> R doRepositoryOp(S entity, Function<S, R> repositoryOperation) {
|
||||
|
||||
try {
|
||||
return repositoryOperation.apply(entity);
|
||||
}
|
||||
catch (Throwable cause) {
|
||||
throw newCacheRuntimeException(() -> String.format(DATA_ACCESS_ERROR, entity), cause);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T load(LoaderHelper<ID, T> helper) throws CacheLoaderException {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract CacheRuntimeException newCacheRuntimeException(
|
||||
Supplier<String> messageSupplier, Throwable cause);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <U extends RepositoryCacheLoaderWriterSupport<T, ID>> U with(Environment environment) {
|
||||
|
||||
setEnvironment(environment);
|
||||
|
||||
return (U) this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.cloud.CloudPlatform;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.data.gemfire.config.annotation.EnableClusterConfiguration;
|
||||
import org.springframework.data.gemfire.support.ConnectionEndpointList;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* The {@link ClusterAvailableConfiguration} class is a Spring {@link Configuration} class that enables configuration
|
||||
* when an Apache Geode cluster of servers are available.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.boot.autoconfigure.condition.AnyNestedCondition
|
||||
* @see org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform
|
||||
* @see org.springframework.boot.cloud.CloudPlatform
|
||||
* @see org.springframework.context.annotation.ConditionContext
|
||||
* @see org.springframework.context.annotation.Conditional
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see org.springframework.core.type.AnnotatedTypeMetadata
|
||||
* @see org.springframework.data.gemfire.config.annotation.EnableClusterConfiguration
|
||||
* @see org.springframework.geode.config.annotation.ClusterAwareConfiguration
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Configuration
|
||||
@Conditional(ClusterAvailableConfiguration.AnyClusterAvailableCondition.class)
|
||||
@EnableClusterConfiguration(requireHttps = false, useHttp = true)
|
||||
@SuppressWarnings("unused")
|
||||
public class ClusterAvailableConfiguration {
|
||||
|
||||
private static final Set<CloudPlatform> SUPPORTED_CLOUD_PLATFORMS =
|
||||
CollectionUtils.asSet(CloudPlatform.CLOUD_FOUNDRY, CloudPlatform.KUBERNETES);
|
||||
|
||||
public static final class AnyClusterAvailableCondition extends AnyNestedCondition {
|
||||
|
||||
public AnyClusterAvailableCondition() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
//@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
|
||||
@Conditional(CloudFoundryClusterAvailableCondition.class)
|
||||
static class IsCloudFoundryClusterAvailableCondition { }
|
||||
|
||||
//@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
|
||||
@Conditional(KubernetesClusterAvailableCondition.class)
|
||||
static class IsKubernetesClusterAvailableCondition { }
|
||||
|
||||
@Conditional(StandaloneClusterAvailableCondition.class)
|
||||
static class IsStandaloneClusterAvailableCondition { }
|
||||
|
||||
}
|
||||
|
||||
protected static abstract class AbstractCloudPlatformAvailableCondition
|
||||
extends ClusterAwareConfiguration.ClusterAwareCondition {
|
||||
|
||||
protected abstract String getCloudPlatformName();
|
||||
|
||||
@Override
|
||||
protected String getRuntimeEnvironmentName() {
|
||||
return getCloudPlatformName();
|
||||
}
|
||||
|
||||
protected abstract boolean isCloudPlatformActive(@NonNull Environment environment);
|
||||
|
||||
protected boolean isInfoLoggingEnabled() {
|
||||
return getLogger().isInfoEnabled();
|
||||
}
|
||||
|
||||
protected boolean isMatchingStrictOrLoggable(boolean match, boolean strictMatch) {
|
||||
return match && (strictMatch || isInfoLoggingEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized final boolean matches(@NonNull ConditionContext conditionContext,
|
||||
@NonNull AnnotatedTypeMetadata typeMetadata) {
|
||||
|
||||
boolean match = isCloudPlatformActive(conditionContext.getEnvironment());
|
||||
boolean strictMatch = isStrictMatch(conditionContext, typeMetadata);
|
||||
|
||||
if (isMatchingStrictOrLoggable(match, strictMatch)) {
|
||||
match |= super.matches(conditionContext, typeMetadata);
|
||||
}
|
||||
|
||||
if (match && !wasClusterAvailabilityEvaluated()) {
|
||||
set(true);
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void logConnectedRuntimeEnvironment(@NonNull Logger logger) {
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Spring Boot application is running in a client/server topology,"
|
||||
+ " inside a [{}] Cloud-managed Environment", getRuntimeEnvironmentName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void logUnconnectedRuntimeEnvironment(@NonNull Logger logger) {
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("No cluster was found; Spring Boot application is running in a [{}]"
|
||||
+ " Cloud-managed Environment", getRuntimeEnvironmentName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureTopology(@NonNull Environment environment,
|
||||
@NonNull ConnectionEndpointList connectionEndpoints, int connectionCount) {
|
||||
// do nothing!
|
||||
}
|
||||
}
|
||||
|
||||
public static class CloudFoundryClusterAvailableCondition extends AbstractCloudPlatformAvailableCondition {
|
||||
|
||||
protected static final String CLOUD_FOUNDRY_NAME = "CloudFoundry";
|
||||
protected static final String RUNTIME_ENVIRONMENT_NAME = "VMware Tanzu GemFire for VMs";
|
||||
|
||||
@Override
|
||||
protected String getCloudPlatformName() {
|
||||
return CLOUD_FOUNDRY_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRuntimeEnvironmentName() {
|
||||
return RUNTIME_ENVIRONMENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCloudPlatformActive(@NonNull Environment environment) {
|
||||
return environment != null && CloudPlatform.CLOUD_FOUNDRY.isActive(environment);
|
||||
}
|
||||
}
|
||||
|
||||
public static class KubernetesClusterAvailableCondition extends AbstractCloudPlatformAvailableCondition {
|
||||
|
||||
protected static final String KUBERNETES_NAME = "Kubernetes";
|
||||
protected static final String RUNTIME_ENVIRONMENT_NAME = "VMware Tanzu GemFire for K8S";
|
||||
|
||||
@Override
|
||||
protected String getCloudPlatformName() {
|
||||
return KUBERNETES_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRuntimeEnvironmentName() {
|
||||
return RUNTIME_ENVIRONMENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCloudPlatformActive(@NonNull Environment environment) {
|
||||
return environment != null && CloudPlatform.KUBERNETES.isActive(environment);
|
||||
}
|
||||
}
|
||||
|
||||
public static class StandaloneClusterAvailableCondition
|
||||
extends ClusterAwareConfiguration.ClusterAwareCondition {
|
||||
|
||||
@Override
|
||||
public synchronized boolean matches(@NonNull ConditionContext conditionContext,
|
||||
@NonNull AnnotatedTypeMetadata typeMetadata) {
|
||||
|
||||
return isNotSupportedCloudPlatform(conditionContext)
|
||||
&& super.matches(conditionContext, typeMetadata);
|
||||
}
|
||||
|
||||
private boolean isNotSupportedCloudPlatform(@NonNull ConditionContext conditionContext) {
|
||||
return conditionContext != null && isNotSupportedCloudPlatform(conditionContext.getEnvironment());
|
||||
}
|
||||
|
||||
private boolean isNotSupportedCloudPlatform(@NonNull Environment environment) {
|
||||
|
||||
CloudPlatform activeCloudPlatform = environment != null
|
||||
? CloudPlatform.getActive(environment)
|
||||
: null;
|
||||
|
||||
return !isSupportedCloudPlatform(activeCloudPlatform);
|
||||
}
|
||||
|
||||
private boolean isSupportedCloudPlatform(@NonNull CloudPlatform cloudPlatform) {
|
||||
return cloudPlatform != null && SUPPORTED_CLOUD_PLATFORMS.contains(cloudPlatform);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,775 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.geode.cache.client.ClientCache;
|
||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
||||
import org.apache.geode.cache.client.Pool;
|
||||
import org.apache.geode.cache.client.PoolManager;
|
||||
import org.apache.geode.cache.server.CacheServer;
|
||||
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
|
||||
import org.springframework.data.gemfire.support.ConnectionEndpoint;
|
||||
import org.springframework.data.gemfire.support.ConnectionEndpointList;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.geode.cache.SimpleCacheResolver;
|
||||
import org.springframework.geode.core.util.ObjectUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ClusterAwareConfiguration} class is a Spring {@link Configuration @Configuration} class imported by
|
||||
* {@link EnableClusterAware} used to determine whether a Spring Boot application using Apache Geode should run
|
||||
* in {@literal local-only mode} or {@literal client/server}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.annotation.Annotation
|
||||
* @see java.net.InetSocketAddress
|
||||
* @see java.net.Socket
|
||||
* @see java.net.SocketAddress
|
||||
* @see org.apache.geode.cache.client.ClientCache
|
||||
* @see org.apache.geode.cache.client.ClientRegionShortcut
|
||||
* @see org.apache.geode.cache.client.Pool
|
||||
* @see org.apache.geode.cache.client.PoolManager
|
||||
* @see org.apache.geode.cache.server.CacheServer
|
||||
* @see org.springframework.context.ApplicationListener
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @see org.springframework.context.annotation.Condition
|
||||
* @see org.springframework.context.annotation.ConditionContext
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.context.annotation.ImportAware
|
||||
* @see org.springframework.context.event.ContextClosedEvent
|
||||
* @see org.springframework.core.annotation.AnnotationAttributes
|
||||
* @see org.springframework.core.env.ConfigurableEnvironment
|
||||
* @see org.springframework.core.env.EnumerablePropertySource
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see org.springframework.core.env.PropertySource
|
||||
* @see org.springframework.core.type.AnnotatedTypeMetadata
|
||||
* @see org.springframework.core.type.AnnotationMetadata
|
||||
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
|
||||
* @see org.springframework.data.gemfire.support.ConnectionEndpoint
|
||||
* @see org.springframework.data.gemfire.support.ConnectionEndpointList
|
||||
* @see org.springframework.geode.cache.SimpleCacheResolver
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Configuration
|
||||
@Import({ ClusterAvailableConfiguration.class, ClusterNotAvailableConfiguration.class })
|
||||
public class ClusterAwareConfiguration extends AbstractAnnotationConfigSupport implements ImportAware {
|
||||
|
||||
static final boolean DEFAULT_CLUSTER_AWARE_CONDITION_MATCH = false;
|
||||
static final boolean DEFAULT_CLUSTER_AWARE_CONDITION_STRICT_MATCH = false;
|
||||
|
||||
static final int DEFAULT_CACHE_SERVER_PORT = CacheServer.DEFAULT_PORT;
|
||||
static final int DEFAULT_LOCATOR_PORT = 10334;
|
||||
static final int DEFAULT_TIMEOUT_IN_MILLISECONDS = 500;
|
||||
|
||||
static final ClientRegionShortcut LOCAL_CLIENT_REGION_SHORTCUT = ClientRegionShortcut.LOCAL;
|
||||
|
||||
static final String LOCALHOST = "localhost";
|
||||
static final String MATCHING_PROPERTY_PATTERN = "spring\\.data\\.gemfire\\.pool\\..*locators|servers";
|
||||
static final String STRICT_MATCH_ATTRIBUTE_NAME = "strictMatch";
|
||||
|
||||
static final String CLUSTER_AWARE_CONFIGURATION_PROPERTY_SOURCE_NAME =
|
||||
ClusterAwareConfiguration.class.getSimpleName().concat("PropertySource");
|
||||
|
||||
static final String SPRING_BOOT_DATA_GEMFIRE_CLUSTER_CONDITION_MATCH_PROPERTY =
|
||||
"spring.boot.data.gemfire.cluster.condition.match";
|
||||
|
||||
static final String SPRING_BOOT_DATA_GEMFIRE_CLUSTER_CONDITION_MATCH_STRICT_PROPERTY =
|
||||
"spring.boot.data.gemfire.cluster.condition.match.strict";
|
||||
|
||||
static final String SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY =
|
||||
"spring.data.gemfire.cache.client.region.shortcut";
|
||||
|
||||
private static final AtomicBoolean strictMatchConfiguration =
|
||||
new AtomicBoolean(DEFAULT_CLUSTER_AWARE_CONDITION_STRICT_MATCH);
|
||||
|
||||
private static final Function<ConditionContext, Boolean> configuredMatchFunction = conditionContext ->
|
||||
Optional.ofNullable(conditionContext)
|
||||
.map(ConditionContext::getEnvironment)
|
||||
.map(environment -> environment.getProperty(SPRING_BOOT_DATA_GEMFIRE_CLUSTER_CONDITION_MATCH_PROPERTY,
|
||||
Boolean.class, DEFAULT_CLUSTER_AWARE_CONDITION_MATCH))
|
||||
.orElse(DEFAULT_CLUSTER_AWARE_CONDITION_MATCH);
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClusterAwareConfiguration.class);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
protected @NonNull Class<? extends Annotation> getAnnotationType() {
|
||||
return EnableClusterAware.class;
|
||||
}
|
||||
|
||||
protected boolean isStrictMatchConfigured(@NonNull AnnotationAttributes enableClusterAwareAttributes) {
|
||||
return enableClusterAwareAttributes != null
|
||||
&& Boolean.TRUE.equals(enableClusterAwareAttributes.getBoolean(STRICT_MATCH_ATTRIBUTE_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void setImportMetadata(@NonNull AnnotationMetadata importMetadata) {
|
||||
|
||||
if (isAnnotationPresent(importMetadata)) {
|
||||
|
||||
AnnotationAttributes enableClusterAwareAttributes = getAnnotationAttributes(importMetadata);
|
||||
|
||||
strictMatchConfiguration.set(isStrictMatchConfigured(enableClusterAwareAttributes));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class ClusterAwareCondition implements Condition {
|
||||
|
||||
private static final AtomicReference<Boolean> clusterAvailable = new AtomicReference<>(null);
|
||||
|
||||
protected static final String RUNTIME_ENVIRONMENT_NAME = "Apache Geode-based Cluster on Bare Metal";
|
||||
|
||||
private static @NonNull ApplicationListener<ContextClosedEvent> clusterAwareConditionResetOnContextClosedApplicationListener() {
|
||||
return contextClosedEvent-> reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an Apache Geode-based Cluster is available in the runtime environment.
|
||||
*
|
||||
* @return a boolean value indicating whether an Apache Geode-based Cluster is available in
|
||||
* the runtime environment.
|
||||
* @see #wasClusterAvailabilityEvaluated()
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
return Boolean.TRUE.equals(clusterAvailable.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the state of this {@link Condition} to reevaluate whether an Apache Geode-based Cluster
|
||||
* is available in the runtime environment.
|
||||
*
|
||||
* @see #set(Boolean)
|
||||
*/
|
||||
public static void reset() {
|
||||
set(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the {@code clusterAvailable} variable.
|
||||
*
|
||||
* @param available state to set the {@code clusterAvailable} variable to.
|
||||
* @see #reset()
|
||||
*/
|
||||
protected static void set(@Nullable Boolean available) {
|
||||
clusterAvailable.set(available);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the {@link Condition} that determines whether an Apache Geode-based Cluster is available
|
||||
* in the runtime environment has been evaluated.
|
||||
*
|
||||
* @return a boolean value indicating whether the {@link Condition} that determines
|
||||
* whether an Apache Geode-based Cluster is available in the runtime environment has been evaluated.
|
||||
* @see #isAvailable()
|
||||
*/
|
||||
public static boolean wasClusterAvailabilityEvaluated() {
|
||||
return clusterAvailable.get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link String} containing a description of the runtime environment.
|
||||
*
|
||||
* @return a {@link String} containing a description of the runtime environment.
|
||||
*/
|
||||
protected String getRuntimeEnvironmentName() {
|
||||
return RUNTIME_ENVIRONMENT_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean matches(@NonNull ConditionContext conditionContext,
|
||||
@NonNull AnnotatedTypeMetadata typeMetadata) {
|
||||
|
||||
boolean matches = isMatch(conditionContext) || doCachedMatch(conditionContext);
|
||||
boolean strictMatch = isStrictMatch(conditionContext, typeMetadata);
|
||||
|
||||
failOnStrictMatchAndNoMatches(strictMatch, matches);
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
boolean isMatch(@NonNull ConditionContext conditionContext) {
|
||||
return isAvailable() || configuredMatchFunction.apply(conditionContext);
|
||||
}
|
||||
|
||||
protected boolean isStrictMatch(@NonNull ConditionContext conditionContext,
|
||||
@NonNull AnnotatedTypeMetadata typeMetadata) {
|
||||
|
||||
Environment environment = conditionContext.getEnvironment();
|
||||
|
||||
Function<ConfigurableListableBeanFactory, Boolean> isStrictMatchEnabledFunction = beanFactory -> {
|
||||
|
||||
boolean strictMatchEnabled = strictMatchConfiguration.get();
|
||||
|
||||
if (!strictMatchEnabled) {
|
||||
|
||||
String annotationName = EnableClusterAware.class.getName();
|
||||
|
||||
strictMatchEnabled = beanFactory != null
|
||||
&& Arrays.stream(ArrayUtils.nullSafeArray(beanFactory.getBeanDefinitionNames(), String.class))
|
||||
.map(beanFactory::getBeanDefinition)
|
||||
.filter(AnnotatedBeanDefinition.class::isInstance)
|
||||
.map(AnnotatedBeanDefinition.class::cast)
|
||||
.map(AnnotatedBeanDefinition::getMetadata)
|
||||
.filter(annotationMetadata -> annotationMetadata.hasAnnotation(annotationName))
|
||||
.findFirst()
|
||||
.map(annotationMetadata -> annotationMetadata.getAnnotationAttributes(annotationName))
|
||||
.map(AnnotationAttributes::fromMap)
|
||||
.map(annotationAttributes -> annotationAttributes.getBoolean(STRICT_MATCH_ATTRIBUTE_NAME))
|
||||
.orElse(DEFAULT_CLUSTER_AWARE_CONDITION_STRICT_MATCH);
|
||||
}
|
||||
|
||||
return strictMatchEnabled;
|
||||
};
|
||||
|
||||
return environment.getProperty(SPRING_BOOT_DATA_GEMFIRE_CLUSTER_CONDITION_MATCH_STRICT_PROPERTY,
|
||||
Boolean.class, isStrictMatchEnabledFunction.apply(conditionContext.getBeanFactory()));
|
||||
}
|
||||
|
||||
protected boolean isStrictMatchAndNoMatches(boolean strictMatch, boolean matches) {
|
||||
return strictMatch && !matches;
|
||||
}
|
||||
|
||||
protected void failOnStrictMatchAndNoMatches(boolean strictMatch, boolean matches) {
|
||||
|
||||
if (isStrictMatchAndNoMatches(strictMatch, matches)) {
|
||||
|
||||
String message =
|
||||
String.format("Failed to find available cluster in [%1$s] when strictMatch was [%2$s]",
|
||||
getRuntimeEnvironmentName(), strictMatch);
|
||||
|
||||
throw new ClusterNotAvailableException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches the result of the computed {@link #doMatch(ConditionContext)} operation.
|
||||
*
|
||||
* Subsequent calls returns the cached value of the computed (once) {@link #doMatch(ConditionContext)}
|
||||
* operation.
|
||||
*
|
||||
* @param conditionContext Spring {@link ConditionContext} capturing the context in which the conditions
|
||||
* are evaluated; must not be {@literal null}.
|
||||
* @return a boolean value indicating whether the conditions match (i.e. {@literal true}).
|
||||
* @see org.springframework.context.annotation.ConditionContext
|
||||
* @see #registerApplicationListener(ConditionContext)
|
||||
* @see #doMatch(ConditionContext)
|
||||
*/
|
||||
protected boolean doCachedMatch(@NonNull ConditionContext conditionContext) {
|
||||
|
||||
Supplier<Boolean> evaluateConditionMatches = () -> {
|
||||
registerApplicationListener(conditionContext);
|
||||
return doMatch(conditionContext);
|
||||
};
|
||||
|
||||
UnaryOperator<Boolean> clusterAvailableUpdateFunction = currentClusterAvailable ->
|
||||
ObjectUtils.initialize(currentClusterAvailable, evaluateConditionMatches);
|
||||
|
||||
return clusterAvailable.updateAndGet(clusterAvailableUpdateFunction);
|
||||
}
|
||||
|
||||
protected @NonNull ConditionContext registerApplicationListener(@NonNull ConditionContext conditionContext) {
|
||||
|
||||
Optional.ofNullable(conditionContext)
|
||||
.map(ConditionContext::getResourceLoader)
|
||||
.filter(ConfigurableApplicationContext.class::isInstance)
|
||||
.map(ConfigurableApplicationContext.class::cast)
|
||||
.ifPresent(applicationContext -> applicationContext
|
||||
.addApplicationListener(clusterAwareConditionResetOnContextClosedApplicationListener()));
|
||||
|
||||
return conditionContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual conditional match to determine whether this Spring Boot for Apache Geode application
|
||||
* can connect to an available Apache Geode cluster available in any environment (e.g. Standalone or Cloud).
|
||||
*
|
||||
* @param conditionContext Spring {@link ConditionContext} capturing the context in which the condition(s)
|
||||
* are evaluated; must not be {@literal null}.
|
||||
* @return the given {@link ConditionContext}.
|
||||
* @see org.springframework.context.annotation.ConditionContext
|
||||
* @see #doCachedMatch(ConditionContext)
|
||||
* @see #getConnectionEndpoints(Environment)
|
||||
* @see #countConnections(ConnectionEndpointList)
|
||||
* @see #configureTopology(Environment, ConnectionEndpointList, int)
|
||||
* @see #logRuntimeEnvironment(Logger, int)
|
||||
* @see #isMatch(ConnectionEndpointList, int)
|
||||
*/
|
||||
protected boolean doMatch(@NonNull ConditionContext conditionContext) {
|
||||
|
||||
Environment environment = conditionContext.getEnvironment();
|
||||
|
||||
ConnectionEndpointList connectionEndpoints = getConnectionEndpoints(environment);
|
||||
|
||||
int connectionCount = countConnections(connectionEndpoints);
|
||||
|
||||
configureTopology(environment, connectionEndpoints, connectionCount);
|
||||
logRuntimeEnvironment(getLogger(), connectionCount);
|
||||
|
||||
return isMatch(connectionEndpoints, connectionCount);
|
||||
}
|
||||
|
||||
boolean isMatch(@NonNull ConnectionEndpointList connectionEndpoints, int connectionCount) {
|
||||
return isConnected(connectionCount);
|
||||
}
|
||||
|
||||
protected @NonNull Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
protected ConnectionEndpointList getConnectionEndpoints(@NonNull Environment environment) {
|
||||
|
||||
return new ConnectionEndpointList(getDefaultConnectionEndpoints(environment))
|
||||
.add(getConfiguredConnectionEndpoints(environment))
|
||||
.add(getPooledConnectionEndpoints(environment));
|
||||
}
|
||||
|
||||
protected List<ConnectionEndpoint> getDefaultConnectionEndpoints(@NonNull Environment environment) {
|
||||
|
||||
return Arrays.asList(
|
||||
new ConnectionEndpoint(LOCALHOST, DEFAULT_CACHE_SERVER_PORT),
|
||||
new ConnectionEndpoint(LOCALHOST, DEFAULT_LOCATOR_PORT)
|
||||
);
|
||||
}
|
||||
|
||||
protected List<ConnectionEndpoint> getConfiguredConnectionEndpoints(@NonNull Environment environment) {
|
||||
|
||||
List<ConnectionEndpoint> connectionEndpoints = new ArrayList<>();
|
||||
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
|
||||
ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment;
|
||||
|
||||
MutablePropertySources propertySources = configurableEnvironment.getPropertySources();
|
||||
|
||||
if (propertySources != null) {
|
||||
|
||||
Pattern pattern = Pattern.compile(MATCHING_PROPERTY_PATTERN);
|
||||
|
||||
for (PropertySource<?> propertySource : propertySources) {
|
||||
if (propertySource instanceof EnumerablePropertySource) {
|
||||
|
||||
EnumerablePropertySource<?> enumerablePropertySource =
|
||||
(EnumerablePropertySource<?>) propertySource;
|
||||
|
||||
String[] propertyNames = enumerablePropertySource.getPropertyNames();
|
||||
|
||||
Arrays.stream(ArrayUtils.nullSafeArray(propertyNames, String.class))
|
||||
.filter(StringUtils::hasText)
|
||||
.filter(propertyName-> pattern.matcher(propertyName).find())
|
||||
.forEach(propertyName -> {
|
||||
|
||||
String propertyValue = environment.getProperty(propertyName);
|
||||
|
||||
if (StringUtils.hasText(propertyValue)) {
|
||||
|
||||
int defaultPort = propertyName.toLowerCase().contains("servers")
|
||||
? DEFAULT_CACHE_SERVER_PORT
|
||||
: DEFAULT_LOCATOR_PORT;
|
||||
|
||||
String[] propertyValueArray = propertyValue.split(",");
|
||||
|
||||
ConnectionEndpointList list =
|
||||
ConnectionEndpointList.parse(defaultPort, propertyValueArray);
|
||||
|
||||
connectionEndpoints.addAll(list);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return connectionEndpoints;
|
||||
}
|
||||
|
||||
protected List<ConnectionEndpoint> getPooledConnectionEndpoints(@NonNull Environment environment) {
|
||||
|
||||
List<ConnectionEndpoint> pooledConnectionEndpoints = new ArrayList<>();
|
||||
|
||||
getPoolsFromApacheGeode().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(ConnectionEndpointListBuilder::from)
|
||||
.forEach(pooledConnectionEndpoints::addAll);
|
||||
|
||||
return pooledConnectionEndpoints;
|
||||
}
|
||||
|
||||
protected Collection<Pool> getPoolsFromApacheGeode() {
|
||||
|
||||
Set<Pool> pools = new HashSet<>();
|
||||
|
||||
pools.addAll(getPoolsFromClientCache());
|
||||
pools.addAll(getPoolsFromPoolManager());
|
||||
|
||||
return pools;
|
||||
}
|
||||
|
||||
// Technically, should be registered with the PoolManager, but...
|
||||
Collection<Pool> getPoolsFromClientCache() {
|
||||
|
||||
return SimpleCacheResolver.getInstance().resolveClientCache()
|
||||
.map(ClientCache::getDefaultPool)
|
||||
.map(Collections::singleton)
|
||||
.orElseGet(Collections::emptySet);
|
||||
}
|
||||
|
||||
Collection<Pool> getPoolsFromPoolManager() {
|
||||
|
||||
Map<String, Pool> namedPools = PoolManager.getAll();
|
||||
|
||||
return CollectionUtils.nullSafeMap(namedPools).values().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected int countConnections(@NonNull ConnectionEndpointList connectionEndpoints) {
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (ConnectionEndpoint connectionEndpoint : connectionEndpoints) {
|
||||
|
||||
try (Socket socket = connect(connectionEndpoint)){
|
||||
|
||||
count += isConnected(socket) ? 1 : 0;
|
||||
|
||||
if (getLogger().isInfoEnabled()) {
|
||||
getLogger().info("Successfully connected to {}", connectionEndpoint);
|
||||
}
|
||||
}
|
||||
catch (IOException | SocketCreationException cause) {
|
||||
|
||||
if (getLogger().isInfoEnabled()) {
|
||||
getLogger().info("Failed to connect to {}", connectionEndpoint);
|
||||
}
|
||||
|
||||
if (getLogger().isDebugEnabled()) {
|
||||
getLogger().debug("Connection failed because:", cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
protected boolean isConnected(@NonNull Socket socket) {
|
||||
return socket != null && socket.isConnected();
|
||||
}
|
||||
|
||||
protected @NonNull Socket connect(@NonNull ConnectionEndpoint connectionEndpoint) throws IOException {
|
||||
|
||||
SocketAddress socketAddress = connectionEndpoint.toInetSocketAddress();
|
||||
|
||||
Socket socket = connectionEndpoint instanceof PoolConnectionEndpoint
|
||||
? newSocket((PoolConnectionEndpoint) connectionEndpoint)
|
||||
: newSocket(connectionEndpoint);
|
||||
|
||||
socket.connect(socketAddress, DEFAULT_TIMEOUT_IN_MILLISECONDS);
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
protected @NonNull Socket newSocket(@NonNull ConnectionEndpoint connectionEndpoint) throws IOException {
|
||||
|
||||
Socket socket = new Socket();
|
||||
|
||||
socket.setKeepAlive(false);
|
||||
socket.setReuseAddress(true);
|
||||
socket.setSoLinger(false, 0);
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
protected @NonNull Socket newSocket(@NonNull PoolConnectionEndpoint poolConnectionEndpoint) {
|
||||
|
||||
Function<Throwable, Socket> ioExceptionHandlingFunction = cause -> {
|
||||
|
||||
String message = String.format("Failed to create Socket from PoolConnectionEndpoint [%s]",
|
||||
poolConnectionEndpoint);
|
||||
|
||||
throw new SocketCreationException(message, cause);
|
||||
};
|
||||
|
||||
return poolConnectionEndpoint.getPool()
|
||||
.map(Pool::getSocketFactory)
|
||||
.map(socketFactory -> ObjectUtils.<Socket>doOperationSafely(socketFactory::createSocket,
|
||||
ioExceptionHandlingFunction))
|
||||
.orElseGet(() -> ObjectUtils.<Socket>doOperationSafely(() ->
|
||||
newSocket((ConnectionEndpoint) poolConnectionEndpoint), ioExceptionHandlingFunction));
|
||||
}
|
||||
|
||||
protected boolean close(@Nullable Socket socket) {
|
||||
|
||||
return ObjectUtils.<Boolean>doOperationSafely(() -> {
|
||||
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}, cause -> false);
|
||||
}
|
||||
|
||||
protected boolean isConnected(int connectionCount) {
|
||||
return connectionCount > 0;
|
||||
}
|
||||
|
||||
protected boolean isNotConnected(int connectionCount) {
|
||||
return !isConnected(connectionCount);
|
||||
}
|
||||
|
||||
private void configureEnvironment(@NonNull Environment environment) {
|
||||
|
||||
if (environment != null) {
|
||||
if (!environment.containsProperty(SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY)) {
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
|
||||
MutablePropertySources propertySources = ((ConfigurableEnvironment) environment).getPropertySources();
|
||||
|
||||
propertySources.addFirst(new MapPropertySource(CLUSTER_AWARE_CONFIGURATION_PROPERTY_SOURCE_NAME,
|
||||
Collections.singletonMap(SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY,
|
||||
LOCAL_CLIENT_REGION_SHORTCUT.name())));
|
||||
}
|
||||
else {
|
||||
System.setProperty(SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY,
|
||||
LOCAL_CLIENT_REGION_SHORTCUT.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void configureTopology(@NonNull Environment environment,
|
||||
@NonNull ConnectionEndpointList connectionEndpoints, int connectionCount) {
|
||||
|
||||
if (isNotConnected(connectionCount)) {
|
||||
configureEnvironment(environment);
|
||||
}
|
||||
}
|
||||
|
||||
protected void logConnectedRuntimeEnvironment(@NonNull Logger logger) {
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Spring Boot application is running in a client/server topology"
|
||||
+ " using a standalone Apache Geode-based cluster");
|
||||
}
|
||||
}
|
||||
|
||||
protected void logConnectedRuntimeEnvironment(@NonNull Logger logger, int connectionCount) {
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Cluster was found; Auto-configuration made [{}] successful connection(s)",
|
||||
connectionCount);
|
||||
}
|
||||
|
||||
logConnectedRuntimeEnvironment(logger);
|
||||
}
|
||||
|
||||
protected void logRuntimeEnvironment(@NonNull Logger logger, int connectionCount) {
|
||||
|
||||
if (isConnected(connectionCount)) {
|
||||
logConnectedRuntimeEnvironment(logger, connectionCount);
|
||||
}
|
||||
else {
|
||||
logUnconnectedRuntimeEnvironment(logger);
|
||||
}
|
||||
}
|
||||
|
||||
protected void logUnconnectedRuntimeEnvironment(@NonNull Logger logger) {
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("No cluster was found; Spring Boot application will run in standalone [LOCAL] mode"
|
||||
+ " unless strictMode is false and the application is running in a Cloud-managed Environment");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ConnectionEndpointListBuilder {
|
||||
|
||||
protected static @NonNull ConnectionEndpointList from(@NonNull Pool pool) {
|
||||
|
||||
ConnectionEndpointList list = new ConnectionEndpointList();
|
||||
|
||||
if (pool != null) {
|
||||
|
||||
Set<InetSocketAddress> poolSocketAddresses = new HashSet<>();
|
||||
|
||||
collect(poolSocketAddresses, pool.getLocators());
|
||||
collect(poolSocketAddresses, pool.getOnlineLocators());
|
||||
collect(poolSocketAddresses, pool.getServers());
|
||||
|
||||
poolSocketAddresses.stream()
|
||||
.map(ConnectionEndpoint::from)
|
||||
.map(PoolConnectionEndpoint::from)
|
||||
.map(it -> it.with(pool))
|
||||
.forEach(list::add);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static <T extends Collection<InetSocketAddress>> T collect(@NonNull T collection,
|
||||
@NonNull Collection<InetSocketAddress> socketAddressesToCollect) {
|
||||
|
||||
CollectionUtils.nullSafeCollection(socketAddressesToCollect).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(collection::add);
|
||||
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class PoolConnectionEndpoint extends ConnectionEndpoint {
|
||||
|
||||
protected static PoolConnectionEndpoint from(@NonNull ConnectionEndpoint connectionEndpoint) {
|
||||
return new PoolConnectionEndpoint(connectionEndpoint.getHost(), connectionEndpoint.getPort());
|
||||
}
|
||||
|
||||
private Pool pool;
|
||||
|
||||
PoolConnectionEndpoint(@NonNull String host, int port) {
|
||||
super(host, port);
|
||||
}
|
||||
|
||||
public Optional<Pool> getPool() {
|
||||
return Optional.ofNullable(this.pool);
|
||||
}
|
||||
|
||||
public @NonNull PoolConnectionEndpoint with(@Nullable Pool pool) {
|
||||
this.pool = pool;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof PoolConnectionEndpoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PoolConnectionEndpoint that = (PoolConnectionEndpoint) obj;
|
||||
|
||||
return super.equals(that)
|
||||
&& this.getPool().equals(that.getPool());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int hashValue = super.hashCode();
|
||||
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getPool());
|
||||
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ConnectionEndpoint [%1$s] from Pool [%2$s]",
|
||||
super.toString(), getPool().map(Pool::getName).orElse(""));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected static class SocketCreationException extends RuntimeException {
|
||||
|
||||
protected SocketCreationException() { }
|
||||
|
||||
protected SocketCreationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
protected SocketCreationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
protected SocketCreationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import static org.springframework.geode.config.annotation.ClusterAwareConfiguration.LOCAL_CLIENT_REGION_SHORTCUT;
|
||||
import static org.springframework.geode.config.annotation.ClusterAwareConfiguration.SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY;
|
||||
|
||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
|
||||
import org.springframework.boot.cloud.CloudPlatform;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.data.gemfire.GemfireUtils;
|
||||
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.config.annotation.RegionConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.support.CacheTypeAwareRegionFactoryBean;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ClusterNotAvailableConfiguration} class is a Spring {@link Configuration} class that enables configuration
|
||||
* when an Apache Geode cluster of servers is not available.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.config.BeanPostProcessor
|
||||
* @see org.springframework.boot.autoconfigure.condition.AllNestedConditions
|
||||
* @see org.springframework.boot.cloud.CloudPlatform
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Condition
|
||||
* @see org.springframework.context.annotation.ConditionContext
|
||||
* @see org.springframework.context.annotation.Conditional
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see org.springframework.core.type.AnnotatedTypeMetadata
|
||||
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
|
||||
* @see org.springframework.data.gemfire.config.annotation.RegionConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.support.CacheTypeAwareRegionFactoryBean
|
||||
* @see org.springframework.geode.config.annotation.ClusterAwareConfiguration
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Configuration
|
||||
@Conditional(ClusterNotAvailableConfiguration.AllClusterNotAvailableConditions.class)
|
||||
@SuppressWarnings("unused")
|
||||
public class ClusterNotAvailableConfiguration {
|
||||
|
||||
@Bean
|
||||
BeanPostProcessor localClientRegionBeanPostProcessor(@NonNull Environment environment) {
|
||||
|
||||
return new BeanPostProcessor() {
|
||||
|
||||
@Nullable @Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
|
||||
if (isClientRegion(bean)) {
|
||||
configureAsLocalClientRegion(environment, bean);
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unused")
|
||||
RegionConfigurer localClientRegionConfigurer(@NonNull Environment environment) {
|
||||
|
||||
return new RegionConfigurer() {
|
||||
|
||||
@Override
|
||||
public void configure(String beanName, ClientRegionFactoryBean<?, ?> bean) {
|
||||
configureAsLocalClientRegion(environment, bean);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected boolean isClientRegion(@Nullable Object bean) {
|
||||
return bean instanceof CacheTypeAwareRegionFactoryBean || bean instanceof ClientRegionFactoryBean;
|
||||
}
|
||||
|
||||
protected @NonNull Object configureAsLocalClientRegion(@NonNull Environment environment,
|
||||
@NonNull Object clientRegion) {
|
||||
|
||||
return clientRegion instanceof ClientRegionFactoryBean
|
||||
? configureAsLocalClientRegion(environment, (ClientRegionFactoryBean<?, ?>) clientRegion)
|
||||
: configureAsLocalClientRegion(environment, (CacheTypeAwareRegionFactoryBean<?, ?>) clientRegion);
|
||||
}
|
||||
|
||||
protected @NonNull <K, V> CacheTypeAwareRegionFactoryBean<K, V> configureAsLocalClientRegion(
|
||||
@NonNull Environment environment, @NonNull CacheTypeAwareRegionFactoryBean<K, V> clientRegion) {
|
||||
|
||||
ClientRegionShortcut shortcut =
|
||||
environment.getProperty(SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY,
|
||||
ClientRegionShortcut.class, LOCAL_CLIENT_REGION_SHORTCUT);
|
||||
|
||||
clientRegion.setClientRegionShortcut(shortcut);
|
||||
clientRegion.setPoolName(GemfireUtils.DEFAULT_POOL_NAME);
|
||||
|
||||
return clientRegion;
|
||||
}
|
||||
|
||||
protected @NonNull <K, V> ClientRegionFactoryBean<K, V> configureAsLocalClientRegion(
|
||||
@NonNull Environment environment, @NonNull ClientRegionFactoryBean<K, V> clientRegion) {
|
||||
|
||||
ClientRegionShortcut shortcut =
|
||||
environment.getProperty(SPRING_DATA_GEMFIRE_CACHE_CLIENT_REGION_SHORTCUT_PROPERTY,
|
||||
ClientRegionShortcut.class, LOCAL_CLIENT_REGION_SHORTCUT);
|
||||
|
||||
clientRegion.setPoolName(null);
|
||||
clientRegion.setShortcut(shortcut);
|
||||
|
||||
return clientRegion;
|
||||
}
|
||||
|
||||
public static final class AllClusterNotAvailableConditions extends AllNestedConditions {
|
||||
|
||||
public AllClusterNotAvailableConditions() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@Conditional(ClusterNotAvailableCondition.class)
|
||||
static class IsClusterNotAvailableCondition { }
|
||||
|
||||
@Conditional(NotCloudFoundryEnvironmentCondition.class)
|
||||
static class IsNotCloudFoundryEnvironmentCondition { }
|
||||
|
||||
@Conditional(NotKubernetesEnvironmentCondition.class)
|
||||
static class IsNotKubernetesEnvironmentCondition { }
|
||||
|
||||
}
|
||||
|
||||
public static final class ClusterNotAvailableCondition extends ClusterAwareConfiguration.ClusterAwareCondition {
|
||||
|
||||
@Override
|
||||
public synchronized boolean matches(@NonNull ConditionContext conditionContext,
|
||||
@NonNull AnnotatedTypeMetadata typeMetadata) {
|
||||
|
||||
return !super.matches(conditionContext, typeMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class NotCloudFoundryEnvironmentCondition implements Condition {
|
||||
|
||||
@Override
|
||||
public boolean matches(@NonNull ConditionContext context, @NonNull AnnotatedTypeMetadata metadata) {
|
||||
return !CloudPlatform.CLOUD_FOUNDRY.isActive(context.getEnvironment());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class NotKubernetesEnvironmentCondition implements Condition {
|
||||
|
||||
@Override
|
||||
public boolean matches(@NonNull ConditionContext context, @NonNull AnnotatedTypeMetadata metadata) {
|
||||
return !CloudPlatform.KUBERNETES.isActive(context.getEnvironment());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
/**
|
||||
* The {@link ClusterNotAvailableException} is a {@link RuntimeException} indicating that no Apache Geode cluster
|
||||
* was provisioned and available to service Apache Geode {@link org.apache.geode.cache.client.ClientCache} applications.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.RuntimeException
|
||||
* @see org.apache.geode.cache.client.ClientCache
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ClusterNotAvailableException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Constructs a new uninitialized instance of {@link ClusterNotAvailableException}.
|
||||
*/
|
||||
public ClusterNotAvailableException() { }
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ClusterNotAvailableException} initialized with
|
||||
* the given {@link String message} describing the exception.
|
||||
*
|
||||
* @param message {@link String} containing a description of the exception.
|
||||
*/
|
||||
public ClusterNotAvailableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ClusterNotAvailableException} initialized with
|
||||
* the given {@link Throwable} as the cause of this exception.
|
||||
*
|
||||
* @param cause {@link Throwable} indicating the cause of this exception.
|
||||
*/
|
||||
public ClusterNotAvailableException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ClusterNotAvailableException} initialized with
|
||||
* the given {@link String message} describing the exception along with the given {@link Throwable}
|
||||
* as the cause of this exception.
|
||||
*
|
||||
* @param message {@link String} containing a description of the exception.
|
||||
* @param cause {@link Throwable} indicating the cause of this exception.
|
||||
*/
|
||||
public ClusterNotAvailableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link DistributedSystemIdConfiguration} class is a Spring {@link Configuration} class used to configure
|
||||
* the {@literal distributed-system-id} for a {@link Cache peer Cache member} in a cluster
|
||||
* when using the P2P topology.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.Cache
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.ImportAware
|
||||
* @see org.springframework.core.annotation.AnnotationAttributes
|
||||
* @see org.springframework.core.type.AnnotationMetadata
|
||||
* @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
|
||||
* @see org.springframework.geode.config.annotation.UseDistributedSystemId
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
@SuppressWarnings("unused")
|
||||
public class DistributedSystemIdConfiguration extends AbstractAnnotationConfigSupport implements ImportAware {
|
||||
|
||||
private static final String GEMFIRE_DISTRIBUTED_SYSTEM_ID_PROPERTY = "distributed-system-id";
|
||||
|
||||
private Integer distributedSystemId;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotationType() {
|
||||
return UseDistributedSystemId.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
|
||||
if (isAnnotationPresent(importMetadata)) {
|
||||
|
||||
AnnotationAttributes distributedSystemIdAttributes = getAnnotationAttributes(importMetadata);
|
||||
|
||||
setDistributedSystemId(distributedSystemIdAttributes.containsKey("value")
|
||||
? distributedSystemIdAttributes.getNumber("value") : null);
|
||||
|
||||
setDistributedSystemId(distributedSystemIdAttributes.containsKey("id")
|
||||
? distributedSystemIdAttributes.getNumber("id") : null);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setDistributedSystemId(Integer distributedSystemId) {
|
||||
|
||||
this.distributedSystemId = Optional.ofNullable(distributedSystemId)
|
||||
.filter(id -> id > -1)
|
||||
.orElse(this.distributedSystemId);
|
||||
}
|
||||
|
||||
protected Optional<Integer> getDistributedSystemId() {
|
||||
|
||||
return Optional.ofNullable(this.distributedSystemId)
|
||||
.filter(id -> id > -1);
|
||||
}
|
||||
|
||||
protected Logger getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
private int validateDistributedSystemId(int distributedSystemId) {
|
||||
|
||||
Assert.isTrue(distributedSystemId >= -1 && distributedSystemId < 256,
|
||||
String.format("Distributed System ID [%d] must be between -1 and 255", distributedSystemId));
|
||||
|
||||
return distributedSystemId;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ClientCacheConfigurer clientCacheDistributedSystemIdConfigurer() {
|
||||
|
||||
return (beanName, clientCacheFactoryBean) -> getDistributedSystemId().ifPresent(distributedSystemId -> {
|
||||
|
||||
Logger logger = getLogger();
|
||||
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Distributed System Id [{}] was set on the ClientCache instance, which will not have any effect",
|
||||
distributedSystemId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
PeerCacheConfigurer peerCacheDistributedSystemIdConfigurer() {
|
||||
|
||||
return (beanName, cacheFactoryBean) ->
|
||||
getDistributedSystemId().ifPresent(id -> cacheFactoryBean.getProperties()
|
||||
.setProperty(GEMFIRE_DISTRIBUTED_SYSTEM_ID_PROPERTY,
|
||||
String.valueOf(validateDistributedSystemId(id))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.geode.cache.client.ClientCache;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The {@link DurableClientConfiguration} class is a Spring {@link Configuration} class used to configure
|
||||
* this {@link ClientCache} instance as a {@literal Durable Client} by setting the {@literal durable-client-id}
|
||||
* and {@literal durable-client-timeout} properties in addition to enabling {@literal keepAlive}
|
||||
* on {@link ClientCache} shutdown.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.client.ClientCache
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.ImportAware
|
||||
* @see org.springframework.core.annotation.AnnotationAttributes
|
||||
* @see org.springframework.core.type.AnnotationMetadata
|
||||
* @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
|
||||
* @see org.springframework.geode.config.annotation.EnableDurableClient
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
@SuppressWarnings("unused")
|
||||
public class DurableClientConfiguration extends AbstractAnnotationConfigSupport implements ImportAware {
|
||||
|
||||
public static final boolean DEFAULT_KEEP_ALIVE = true;
|
||||
public static final boolean DEFAULT_READY_FOR_EVENTS = true;
|
||||
|
||||
public static final int DEFAULT_DURABLE_CLIENT_TIMEOUT = 300;
|
||||
|
||||
private Boolean keepAlive = DEFAULT_KEEP_ALIVE;
|
||||
private Boolean readyForEvents = DEFAULT_READY_FOR_EVENTS;
|
||||
|
||||
private Integer durableClientTimeout = DEFAULT_DURABLE_CLIENT_TIMEOUT;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private String durableClientId;
|
||||
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotationType() {
|
||||
return EnableDurableClient.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
|
||||
if (isAnnotationPresent(importMetadata)) {
|
||||
|
||||
AnnotationAttributes enableDurableClientAttributes = getAnnotationAttributes(importMetadata);
|
||||
|
||||
this.durableClientId = enableDurableClientAttributes.containsKey("id")
|
||||
? enableDurableClientAttributes.getString("id")
|
||||
: null;
|
||||
|
||||
this.durableClientTimeout = enableDurableClientAttributes.containsKey("timeout")
|
||||
? enableDurableClientAttributes.getNumber("timeout")
|
||||
: DEFAULT_DURABLE_CLIENT_TIMEOUT;
|
||||
|
||||
this.keepAlive = enableDurableClientAttributes.containsKey("keepAlive")
|
||||
? enableDurableClientAttributes.getBoolean("keepAlive")
|
||||
: DEFAULT_KEEP_ALIVE;
|
||||
|
||||
this.readyForEvents = enableDurableClientAttributes.containsKey("readyForEvents")
|
||||
? enableDurableClientAttributes.getBoolean("readyForEvents")
|
||||
: DEFAULT_READY_FOR_EVENTS;
|
||||
}
|
||||
}
|
||||
|
||||
protected Optional<String> getDurableClientId() {
|
||||
|
||||
return Optional.ofNullable(this.durableClientId)
|
||||
.filter(StringUtils::hasText);
|
||||
}
|
||||
|
||||
protected Integer getDurableClientTimeout() {
|
||||
|
||||
return this.durableClientTimeout != null
|
||||
? this.durableClientTimeout
|
||||
: DEFAULT_DURABLE_CLIENT_TIMEOUT;
|
||||
}
|
||||
|
||||
protected Boolean getKeepAlive() {
|
||||
|
||||
return this.keepAlive != null
|
||||
? this.keepAlive
|
||||
: DEFAULT_KEEP_ALIVE;
|
||||
}
|
||||
|
||||
protected Boolean getReadyForEvents() {
|
||||
|
||||
return this.readyForEvents != null
|
||||
? this.readyForEvents
|
||||
: DEFAULT_READY_FOR_EVENTS;
|
||||
}
|
||||
|
||||
protected Logger getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ClientCacheConfigurer clientCacheDurableClientConfigurer() {
|
||||
|
||||
return (beanName, clientCacheFactoryBean) -> getDurableClientId().ifPresent(durableClientId -> {
|
||||
|
||||
clientCacheFactoryBean.setDurableClientId(durableClientId);
|
||||
clientCacheFactoryBean.setDurableClientTimeout(getDurableClientTimeout());
|
||||
clientCacheFactoryBean.setKeepAlive(getKeepAlive());
|
||||
clientCacheFactoryBean.setReadyForEvents(getReadyForEvents());
|
||||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
PeerCacheConfigurer peerCacheDurableClientConfigurer() {
|
||||
|
||||
return (beanName, cacheFactoryBean) -> getDurableClientId().ifPresent(durableClientId -> {
|
||||
|
||||
Logger logger = getLogger();
|
||||
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Durable Client ID [{}] was set on a peer Cache instance, which will not have any effect",
|
||||
durableClientId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.apache.geode.cache.client.ClientCache;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* The {@link EnableClusterAware} annotation helps Spring Boot applications using Apache Geode decide whether it needs
|
||||
* to operate in {@literal local-only mode} or in a {@literal client/server topology}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.annotation.Annotation
|
||||
* @see java.lang.annotation.Documented
|
||||
* @see java.lang.annotation.Inherited
|
||||
* @see java.lang.annotation.Retention
|
||||
* @see java.lang.annotation.Target
|
||||
* @see org.apache.geode.cache.client.ClientCache
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.data.gemfire.config.annotation.EnableClusterConfiguration
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Import(ClusterAwareConfiguration.class)
|
||||
@SuppressWarnings("unused")
|
||||
public @interface EnableClusterAware {
|
||||
|
||||
/**
|
||||
* Determines whether the matching algorithm is strict.
|
||||
*
|
||||
* This means that at least 1 connection to a cluster of servers (1 or more) must be established
|
||||
* before the cluster aware logic considers that a cluster actually exists.
|
||||
*
|
||||
* Previously, in cloud-managed environments (e.g. VMware Tanzu Application Service (TAS) for VMs, formerly known as
|
||||
* Pivotal Platform or Pivotal CloudFoundry (PCF), or Kubernetes, known as VMware Tanzu Application Service for K8S)
|
||||
* it was assumed that a cluster would be provisioned and available, and that the Spring Boot, Apache Geode
|
||||
* {@link ClientCache} application would connect to the cluster on deployment (push).
|
||||
*
|
||||
* However, is entirely possible that users may push Spring Boot, Apache Geode {@link ClientCache} applications
|
||||
* to a cloud-managed environment where not cluster was provisioned and is available, and user simply want their
|
||||
* apps to run in local-only mode.
|
||||
*
|
||||
* The strict match configuration setting absolutely requires that at least 1 connection must be established. Use
|
||||
* of this configuration setting also promotes a fail-fast protocol, or at least early detection (when log levels
|
||||
* are adjusted accordingly) that a cluster is not available.
|
||||
*
|
||||
* Use {@literal spring.boot.data.gemfire.cluster.condition.match.strict}
|
||||
* in Spring Boot {@literal application.properties}.
|
||||
*
|
||||
* Defaults to {@literal false}.
|
||||
*
|
||||
* @return a boolean value indicating whether strict matching mode is enabled.
|
||||
*/
|
||||
boolean strictMatch() default ClusterAwareConfiguration.DEFAULT_CLUSTER_AWARE_CONDITION_STRICT_MATCH;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.apache.geode.cache.client.ClientCache;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* The {@link EnableDurableClient} annotation configures a {@link ClientCache} instance as a {@literal Durable Client}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.annotation.Documented
|
||||
* @see java.lang.annotation.Inherited
|
||||
* @see java.lang.annotation.Retention
|
||||
* @see java.lang.annotation.Target
|
||||
* @see org.apache.geode.cache.client.ClientCache
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.geode.config.annotation.DurableClientConfiguration
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Import(DurableClientConfiguration.class)
|
||||
@SuppressWarnings("unused")
|
||||
public @interface EnableDurableClient {
|
||||
|
||||
/**
|
||||
* Used only for clients in a client/server installation. If set, this indicates that the client is durable
|
||||
* and identifies the client. The ID is used by servers to reestablish any messaging that was interrupted
|
||||
* by client downtime.
|
||||
*/
|
||||
String id();
|
||||
|
||||
/**
|
||||
* Configure whether the server should keep the durable client's queues alive for the timeout period.
|
||||
*
|
||||
* Defaults to {@literal true}.
|
||||
*/
|
||||
boolean keepAlive() default DurableClientConfiguration.DEFAULT_KEEP_ALIVE;
|
||||
|
||||
/**
|
||||
* Configures whether the {@link ClientCache} is ready to recieve events on startup.
|
||||
*
|
||||
* Defaults to {@literal true}.
|
||||
*/
|
||||
boolean readyForEvents() default DurableClientConfiguration.DEFAULT_READY_FOR_EVENTS;
|
||||
|
||||
/**
|
||||
* Used only for clients in a client/server installation. Number of seconds this client can remain disconnected
|
||||
* from its server and have the server continue to accumulate durable events for it.
|
||||
*
|
||||
* Defaults to {@literal 300 seconds}, or {@literal 5 minutes}.
|
||||
*/
|
||||
int timeout() default DurableClientConfiguration.DEFAULT_DURABLE_CLIENT_TIMEOUT;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Spring {@link Annotation} to enable Apache Geode Security (Auth).
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.annotation.Annotation
|
||||
* @see java.lang.annotation.Documented
|
||||
* @see java.lang.annotation.Inherited
|
||||
* @see java.lang.annotation.Retention
|
||||
* @see java.lang.annotation.Target
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.geode.config.annotation.SecurityManagerConfiguration
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Import(SecurityManagerConfiguration.class)
|
||||
@SuppressWarnings("unused")
|
||||
public @interface EnableSecurityManager {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Spring {@link Annotation} to enable Apache Geode Security (Authentication/Authorization (Auth)) through proxying.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.annotation.Annotation
|
||||
* @see java.lang.annotation.Documented
|
||||
* @see java.lang.annotation.Inherited
|
||||
* @see java.lang.annotation.Retention
|
||||
* @see java.lang.annotation.Target
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.geode.config.annotation.SecurityManagerProxyConfiguration
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Import(SecurityManagerProxyConfiguration.class)
|
||||
@SuppressWarnings("unused")
|
||||
public @interface EnableSecurityManagerProxy {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
import org.apache.geode.cache.client.ClientCache;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The {@link GroupsConfiguration} class is a Spring {@link Configuration} class used to configure the {@literal groups}
|
||||
* in which is member belongs in an Apache Geode distributed system, whether the member is a {@link ClientCache} in a
|
||||
* client/server topology or a {@link Cache peer Cache} in a cluster using the P2P topology.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.Cache
|
||||
* @see org.apache.geode.cache.client.ClientCache
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.ImportAware
|
||||
* @see org.springframework.core.annotation.AnnotationAttributes
|
||||
* @see org.springframework.core.type.AnnotationMetadata
|
||||
* @see org.springframework.data.gemfire.CacheFactoryBean
|
||||
* @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
|
||||
* @see org.springframework.geode.config.annotation.UseGroups
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
@SuppressWarnings("unused")
|
||||
public class GroupsConfiguration extends AbstractAnnotationConfigSupport implements ImportAware {
|
||||
|
||||
private static final String GEMFIRE_GROUPS_PROPERTY = "groups";
|
||||
|
||||
private String[] groups = {};
|
||||
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotationType() {
|
||||
return UseGroups.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
|
||||
if (isAnnotationPresent(importMetadata)) {
|
||||
|
||||
AnnotationAttributes inGroupsAttributes = getAnnotationAttributes(importMetadata);
|
||||
|
||||
setGroups(inGroupsAttributes.containsKey("value")
|
||||
? inGroupsAttributes.getStringArray("value") : null);
|
||||
|
||||
setGroups(inGroupsAttributes.containsKey("groups")
|
||||
? inGroupsAttributes.getStringArray("groups") : null);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setGroups(String[] groups) {
|
||||
|
||||
this.groups = Optional.ofNullable(groups)
|
||||
.filter(it -> it.length > 0)
|
||||
.orElse(this.groups);
|
||||
}
|
||||
|
||||
protected Optional<String[]> getGroups() {
|
||||
|
||||
return Optional.ofNullable(this.groups)
|
||||
.filter(it -> it.length > 0);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ClientCacheConfigurer clientCacheGroupsConfigurer() {
|
||||
return (beaName, clientCacheFactoryBean) -> configureGroups(clientCacheFactoryBean);
|
||||
}
|
||||
|
||||
@Bean
|
||||
PeerCacheConfigurer peerCacheGroupsConfigurer() {
|
||||
return (beaName, peerCacheFactoryBean) -> configureGroups(peerCacheFactoryBean);
|
||||
}
|
||||
|
||||
private void configureGroups(CacheFactoryBean cacheFactoryBean) {
|
||||
getGroups().ifPresent(groups -> cacheFactoryBean.getProperties()
|
||||
.setProperty(GEMFIRE_GROUPS_PROPERTY, StringUtils.arrayToCommaDelimitedString(groups)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.LocatorConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LocatorsConfiguration} class is a Spring {@link Configuration} class used to configure Apache Geode
|
||||
* {@literal locators} and/or {@literal remote-locators} properties used by a {@link Cache peer Cache member}
|
||||
* to join a cluster of servers when using the P2P topology.
|
||||
*
|
||||
* The {@literal remote-locators} property is used to configure the Locators that a cluster will use in order to
|
||||
* connect to a remote site in a multi-site (WAN) topology configuration. To use Locators in a WAN configuration,
|
||||
* you must specify a unique distributed system ID ({@literal distributed-system-id}) for the local cluster
|
||||
* and remote Locator(s) for the remote clusters to which you will connect.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.Cache
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.ImportAware
|
||||
* @see org.springframework.core.annotation.AnnotationAttributes
|
||||
* @see org.springframework.core.type.AnnotationMetadata
|
||||
* @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
|
||||
* @see org.springframework.geode.config.annotation.UseLocators
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
@SuppressWarnings("unused")
|
||||
public class LocatorsConfiguration extends AbstractAnnotationConfigSupport implements ImportAware {
|
||||
|
||||
protected static final String DEFAULT_LOCATORS = "localhost[10334]";
|
||||
protected static final String DEFAULT_REMOTE_LOCATORS = "";
|
||||
protected static final String LOCATORS_PROPERTY = "locators";
|
||||
protected static final String REMOTE_LOCATORS_PROPERTY = "remote-locators";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private String locators;
|
||||
private String remoteLocators;
|
||||
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotationType() {
|
||||
return UseLocators.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
|
||||
if (isAnnotationPresent(importMetadata)) {
|
||||
|
||||
AnnotationAttributes useLocatorsAttributes = getAnnotationAttributes(importMetadata);
|
||||
|
||||
setLocators(useLocatorsAttributes.containsKey("locators")
|
||||
? useLocatorsAttributes.getString("locators") : null);
|
||||
|
||||
setRemoteLocators(useLocatorsAttributes.containsKey("remoteLocators")
|
||||
? useLocatorsAttributes.getString("remoteLocators") : null);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setLocators(String locators) {
|
||||
this.locators = StringUtils.hasText(locators) ? locators : null;
|
||||
}
|
||||
|
||||
protected Optional<String> getLocators() {
|
||||
|
||||
return Optional.ofNullable(this.locators)
|
||||
.filter(StringUtils::hasText);
|
||||
}
|
||||
|
||||
protected Logger getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
protected void setRemoteLocators(String remoteLocators) {
|
||||
this.remoteLocators = StringUtils.hasText(remoteLocators) ? remoteLocators : null;
|
||||
}
|
||||
|
||||
protected Optional<String> getRemoteLocators() {
|
||||
|
||||
return Optional.ofNullable(this.remoteLocators)
|
||||
.filter(StringUtils::hasText);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ClientCacheConfigurer clientCacheLocatorsConfigurer() {
|
||||
|
||||
return (beanName, clientCacheFactoryBean) -> {
|
||||
|
||||
Logger logger = getLogger();
|
||||
|
||||
getLocators().ifPresent(locators -> {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("The '{}' property was configured [{}];"
|
||||
+ " however, this value does not have any effect for ClientCache instances",
|
||||
LOCATORS_PROPERTY, locators);
|
||||
}
|
||||
});
|
||||
|
||||
getRemoteLocators().ifPresent(remoteLocators -> {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("The '{}' property was configured [{}];"
|
||||
+ " however, this value does not have any effect for ClientCache instances",
|
||||
REMOTE_LOCATORS_PROPERTY, remoteLocators);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
LocatorConfigurer locatorLocatorsConfigurer() {
|
||||
|
||||
return (beanName, locatorFactoryBean) -> {
|
||||
|
||||
Properties gemfireProperties = locatorFactoryBean.getGemFireProperties();
|
||||
|
||||
getLocators().ifPresent(locators -> gemfireProperties.setProperty(LOCATORS_PROPERTY, locators));
|
||||
|
||||
getRemoteLocators().ifPresent(remoteLocators ->
|
||||
gemfireProperties.setProperty(REMOTE_LOCATORS_PROPERTY, remoteLocators));
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
PeerCacheConfigurer peerCacheLocatorsConfigurer() {
|
||||
|
||||
return (beanName, cacheFactoryBean) -> {
|
||||
|
||||
Properties gemfireProperties = cacheFactoryBean.getProperties();
|
||||
|
||||
getLocators().ifPresent(locators -> gemfireProperties.setProperty(LOCATORS_PROPERTY, locators));
|
||||
|
||||
getRemoteLocators().ifPresent(remoteLocators ->
|
||||
gemfireProperties.setProperty(REMOTE_LOCATORS_PROPERTY, remoteLocators));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
import org.apache.geode.cache.client.ClientCache;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The {@link MemberNameConfiguration} class is a Spring {@link Configuration} class used to configure an Apache Geode
|
||||
* member name in the distributed system, whether the member is a {@link ClientCache client} in the client/server
|
||||
* topology or a {@link Cache peer} in a cluster using the P2P topology.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.Cache
|
||||
* @see org.apache.geode.cache.client.ClientCache
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.ImportAware
|
||||
* @see org.springframework.core.annotation.AnnotationAttributes
|
||||
* @see org.springframework.core.type.AnnotationMetadata
|
||||
* @see org.springframework.data.gemfire.CacheFactoryBean
|
||||
* @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
|
||||
* @see org.springframework.geode.config.annotation.UseMemberName
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Configuration
|
||||
@SuppressWarnings("unused")
|
||||
public class MemberNameConfiguration extends AbstractAnnotationConfigSupport implements ImportAware {
|
||||
|
||||
private static final String GEMFIRE_NAME_PROPERTY = "name";
|
||||
private static final String SPRING_APPLICATION_NAME_PROPERTY = "spring.application.name";
|
||||
private static final String SPRING_DATA_GEMFIRE_CACHE_NAME_PROPERTY = "spring.data.gemfire.cache.name";
|
||||
private static final String SPRING_DATA_GEMFIRE_NAME_PROPERTY = "spring.data.gemfire.name";
|
||||
private static final String SPRING_DATA_GEODE_CACHE_NAME_PROPERTY = "spring.data.geode.cache.name";
|
||||
private static final String SPRING_DATA_GEODE_NAME_PROPERTY = "spring.data.geode.name";
|
||||
|
||||
private static final Set<String> NAME_PROPERTIES = new HashSet<>();
|
||||
|
||||
static {
|
||||
NAME_PROPERTIES.add(SPRING_APPLICATION_NAME_PROPERTY);
|
||||
NAME_PROPERTIES.add(SPRING_DATA_GEMFIRE_CACHE_NAME_PROPERTY);
|
||||
NAME_PROPERTIES.add(SPRING_DATA_GEMFIRE_NAME_PROPERTY);
|
||||
NAME_PROPERTIES.add(SPRING_DATA_GEODE_CACHE_NAME_PROPERTY);
|
||||
NAME_PROPERTIES.add(SPRING_DATA_GEODE_NAME_PROPERTY);
|
||||
}
|
||||
|
||||
private String memberName;
|
||||
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotationType() {
|
||||
return UseMemberName.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
|
||||
if (isAnnotationPresent(importMetadata)) {
|
||||
|
||||
AnnotationAttributes memberNameAttributes = getAnnotationAttributes(importMetadata);
|
||||
|
||||
setMemberName(memberNameAttributes.containsKey("value")
|
||||
? memberNameAttributes.getString("value") : null);
|
||||
|
||||
setMemberName(memberNameAttributes.containsKey("name")
|
||||
? memberNameAttributes.getString("name") : null);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setMemberName(String memberName) {
|
||||
|
||||
this.memberName = Optional.ofNullable(memberName)
|
||||
.filter(StringUtils::hasText)
|
||||
.orElse(this.memberName);
|
||||
}
|
||||
|
||||
protected Optional<String> getMemberName() {
|
||||
|
||||
return Optional.ofNullable(this.memberName)
|
||||
.filter(StringUtils::hasText);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE) // apply first (e.g. before CacheNameAutoConfiguration)
|
||||
ClientCacheConfigurer clientCacheMemberNameConfigurer(Environment environment) {
|
||||
return (beanName, clientCacheFactoryBean) -> configureMemberName(environment, clientCacheFactoryBean);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE) // apply first (e.g. before CacheNameAutoConfiguration)
|
||||
PeerCacheConfigurer peerCacheMemberNameConfigurer(Environment environment) {
|
||||
return (beanName, peerCacheFactoryBean) -> configureMemberName(environment, peerCacheFactoryBean);
|
||||
}
|
||||
|
||||
private void configureMemberName(Environment environment, CacheFactoryBean cacheFactoryBean) {
|
||||
|
||||
getMemberName()
|
||||
.filter(memberName -> namePropertiesNotPresent(environment))
|
||||
.ifPresent(memberName ->
|
||||
cacheFactoryBean.getProperties().setProperty(GEMFIRE_NAME_PROPERTY, memberName));
|
||||
}
|
||||
|
||||
private boolean namePropertiesArePresent(Environment environment) {
|
||||
|
||||
return NAME_PROPERTIES.stream()
|
||||
.anyMatch(environment::containsProperty);
|
||||
}
|
||||
|
||||
private boolean namePropertiesNotPresent(Environment environment) {
|
||||
return !namePropertiesArePresent(environment);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer;
|
||||
|
||||
/**
|
||||
* Spring {@link Configuration} class used to configure a {@link org.apache.geode.security.SecurityManager},
|
||||
* thereby enabling Security (Auth) on this Apache Geode node.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Configuration
|
||||
@SuppressWarnings("unused")
|
||||
public class SecurityManagerConfiguration {
|
||||
|
||||
@Bean
|
||||
ClientCacheConfigurer clientSecurityManagerConfigurer(org.apache.geode.security.SecurityManager securityManager) {
|
||||
return (beanName, clientCacheFactoryBean) -> clientCacheFactoryBean.setSecurityManager(securityManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
PeerCacheConfigurer peerSecurityManagerConfigurer(org.apache.geode.security.SecurityManager securityManager) {
|
||||
return (beanName, cacheFactoryBean) -> cacheFactoryBean.setSecurityManager(securityManager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.data.gemfire.config.annotation.EnableBeanFactoryLocator;
|
||||
import org.springframework.data.gemfire.config.annotation.EnableSecurity;
|
||||
import org.springframework.geode.security.support.SecurityManagerProxy;
|
||||
|
||||
/**
|
||||
* Spring {@link Configuration} class used to configure a {@link org.apache.geode.security.SecurityManager},
|
||||
* thereby enabling Security (Auth) on this Apache Geode node.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.context.ApplicationListener
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.event.ContextRefreshedEvent
|
||||
* @see org.springframework.data.gemfire.config.annotation.EnableBeanFactoryLocator
|
||||
* @see org.springframework.data.gemfire.config.annotation.EnableSecurity
|
||||
* @see org.springframework.geode.security.support.SecurityManagerProxy
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Configuration
|
||||
@EnableBeanFactoryLocator
|
||||
@EnableSecurity(securityManagerClassName = "org.springframework.geode.security.support.SecurityManagerProxy")
|
||||
@SuppressWarnings("unused")
|
||||
public class SecurityManagerProxyConfiguration implements ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
|
||||
SecurityManagerProxy securityManagerProxy = SecurityManagerProxy.getInstance();
|
||||
|
||||
securityManagerProxy.setBeanFactory(event.getApplicationContext().getAutowireCapableBeanFactory());
|
||||
securityManagerProxy.init(new Properties());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
import org.apache.geode.cache.client.ClientCache;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* The {@link UseDistributedSystemId} annotation configures the {@literal distributed-system-id} property
|
||||
* of a {@link Cache peer Cache member} in an Apache Geode P2P topology.
|
||||
*
|
||||
* This configuration annotation is only applicable on {@link Cache peer Cache members}
|
||||
* and has no effect on {@link ClientCache} instances.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.annotation.Documented
|
||||
* @see java.lang.annotation.Inherited
|
||||
* @see java.lang.annotation.Retention
|
||||
* @see java.lang.annotation.Target
|
||||
* @see org.apache.geode.cache.Cache
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.core.annotation.AliasFor
|
||||
* @see org.springframework.geode.config.annotation.DistributedSystemIdConfiguration
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Import(DistributedSystemIdConfiguration.class)
|
||||
@SuppressWarnings("unused")
|
||||
public @interface UseDistributedSystemId {
|
||||
|
||||
/**
|
||||
* Configures the identifier used to distinguish messages from different distributed systems.
|
||||
*
|
||||
* This is required for Portable Data eXchange (PDX) data serialization.
|
||||
*
|
||||
* Set distributed-system-id to different values for different systems in a multi-site (WAN) configuration,
|
||||
* and to different values for production vs. development environments. This setting must be the same
|
||||
* for every member of a given distributed system and unique to each distributed system within a WAN installation.
|
||||
*
|
||||
* Valid values are integers in the range -1…255. -1 means no setting.
|
||||
*
|
||||
* Defaults to {@literal -1}.
|
||||
*/
|
||||
@AliasFor("id")
|
||||
int value() default -1;
|
||||
|
||||
/**
|
||||
* Configures the identifier used to distinguish messages from different distributed systems.
|
||||
*
|
||||
* Alias for {@literal #value()}.
|
||||
*/
|
||||
@AliasFor("value")
|
||||
int id() default -1;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
import org.apache.geode.cache.client.ClientCache;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* The {@link UseGroups} annotation configures the groups in which the member belongs in an Apache Geode
|
||||
* distributed system, whether the member is a {@link ClientCache} in a client/server topology
|
||||
* or a {@link Cache peer Cache} in a cluster using the P2P topology.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.annotation.Documented
|
||||
* @see java.lang.annotation.Inherited
|
||||
* @see java.lang.annotation.Retention
|
||||
* @see java.lang.annotation.Target
|
||||
* @see org.apache.geode.cache.Cache
|
||||
* @see org.apache.geode.cache.client.ClientCache
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.core.annotation.AliasFor
|
||||
* @see org.springframework.geode.config.annotation.GroupsConfiguration
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Import(GroupsConfiguration.class)
|
||||
@SuppressWarnings("unused")
|
||||
public @interface UseGroups {
|
||||
|
||||
@AliasFor("groups")
|
||||
String[] value() default {};
|
||||
|
||||
@AliasFor("value")
|
||||
String[] groups() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import static org.springframework.geode.config.annotation.LocatorsConfiguration.DEFAULT_LOCATORS;
|
||||
import static org.springframework.geode.config.annotation.LocatorsConfiguration.DEFAULT_REMOTE_LOCATORS;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* The {@link UseLocators} annotation configures the {@literal locators} and/or {@literal remote-locators} Apache Geode
|
||||
* properties used by a {@link Cache peer Cache member} to join a cluster of servers when using the P2P topology
|
||||
* as well as when configuring the multi-site, WAN topology.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.annotation.Documented
|
||||
* @see java.lang.annotation.Inherited
|
||||
* @see java.lang.annotation.Retention
|
||||
* @see java.lang.annotation.Target
|
||||
* @see org.apache.geode.cache.Cache
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.core.annotation.AliasFor
|
||||
* @see org.springframework.geode.config.annotation.MemberNameConfiguration
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Import(LocatorsConfiguration.class)
|
||||
@SuppressWarnings("unused")
|
||||
public @interface UseLocators {
|
||||
|
||||
/**
|
||||
* @see #locators()
|
||||
*/
|
||||
@AliasFor("locators")
|
||||
String value() default DEFAULT_LOCATORS;
|
||||
|
||||
/**
|
||||
* The list of Locators used by system members. The list must be configured consistently for every member of
|
||||
* the cluster (a.k.a. distributed system). If the list is empty, Locators will not be used.
|
||||
*
|
||||
* For each Locator, provide a hostname and/or address (separated by ‘@’, if you use both), followed by
|
||||
* a port number in brackets.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* <code>
|
||||
* locators=address1[port1],address2[port2],...,addressN[portN]
|
||||
* locators=hostname1@address1[port1],hostname2@address2[port2],...,hostnameN@addressN[portN]
|
||||
* locators=hostname1[port1],hostname2[port2],...,hostnameN[portN]
|
||||
* </code>
|
||||
*
|
||||
* Defaults to {@literal localhost[10334]}.
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String locators() default DEFAULT_LOCATORS;
|
||||
|
||||
/**
|
||||
* Used to configure the Locators that a cluster will use in order to connect to a remote site in a multi-site
|
||||
* (WAN) topology configuration.
|
||||
*
|
||||
* To use Locators in a WAN configuration, you must specify a unique distributed system ID ({@literal distributed-system-id})
|
||||
* for the local cluster and remote Locator(s) for the remote clusters to which you will connect.
|
||||
*
|
||||
* For each remote Locator, provide a host name and/or address (separated by ‘@’, if you use both), followed by
|
||||
* a port number in brackets.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* <code>
|
||||
* remote-locators=address1[port1],address2[port2],...,addressN[portN]
|
||||
* remote-locators=hostname1@address1[port1],hostname2@address2[port2],...,hostnameN@addressN[portN]
|
||||
* remote-locators=hostname1[port1],hostname2[port2],...,hostnameN[portN]
|
||||
* </code>
|
||||
*
|
||||
* Defaults to unset.
|
||||
*/
|
||||
String remoteLocators() default DEFAULT_REMOTE_LOCATORS;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
import org.apache.geode.cache.client.ClientCache;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* The {@link UseMemberName} annotation configures the {@literal name} of the member in the Apache Geode
|
||||
* distributed system, whether the member is a {@link ClientCache client} in the client/server topology
|
||||
* or a {@link Cache peer Cache member} in the cluster using the P2P topology.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.annotation.Documented
|
||||
* @see java.lang.annotation.Inherited
|
||||
* @see java.lang.annotation.Retention
|
||||
* @see java.lang.annotation.Target
|
||||
* @see org.apache.geode.cache.Cache
|
||||
* @see org.apache.geode.cache.client.ClientCache
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.core.annotation.AliasFor
|
||||
* @see org.springframework.geode.config.annotation.MemberNameConfiguration
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
@Import(MemberNameConfiguration.class)
|
||||
public @interface UseMemberName {
|
||||
|
||||
/**
|
||||
* Alias for the {@link String name} of the Apache Geode distributed system member.
|
||||
*
|
||||
* @see #value()
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* {@link String Name} used for the Apache Geode distributed system member.
|
||||
*
|
||||
* @see #name()
|
||||
*/
|
||||
@AliasFor("name")
|
||||
String value() default "";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.config.annotation.support;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
|
||||
|
||||
/**
|
||||
* The {@link TypelessAnnotationConfigSupport} class is an extension of SDG's {@link AbstractAnnotationConfigSupport}
|
||||
* based class for resolving {@link AnnotatedTypeMetadata}, however, is not based on any specific {@link Annotation}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.annotation.Annotation
|
||||
* @see org.springframework.core.type.AnnotatedTypeMetadata
|
||||
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class TypelessAnnotationConfigSupport extends AbstractAnnotationConfigSupport {
|
||||
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotationType() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.context.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
|
||||
import org.springframework.context.annotation.AnnotationConfigRegistry;
|
||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
|
||||
import org.springframework.context.annotation.ScopeMetadataResolver;
|
||||
import org.springframework.context.support.AbstractRefreshableConfigApplicationContext;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheApplication;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.data.gemfire.util.SpringUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A {@literal refreshable} {@link ApplicationContext} capable of loading {@link Class component classes} used for
|
||||
* {@link Annotation} based configuration in addition to scanning {@link String configuration locations}, and then
|
||||
* providing the ability to reload/refresh the context at some point later during runtime.
|
||||
*
|
||||
* DISCLAIMER: Currently, this {@link ApplicationContext} implementation (and extension) is being used exclusively for
|
||||
* testing and experimental ({@literal R&D}) purposes. It was designed around Apache Geode's forced-disconnect
|
||||
* / auto-reconnect functionality, providing support for this behavior inside a Spring context. Specifically, this
|
||||
* concern is only applicable when using Spring Boot to configure and bootstrap Apache Geode peer member
|
||||
* {@link org.apache.geode.cache.Cache} applications, such as when annotating your Spring Boot application with
|
||||
* SDG's {@link PeerCacheApplication} annotation. This {@link ApplicationContext} implementation is not recommended for
|
||||
* use in Production Systems/Applications (yet).
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.config.BeanDefinition
|
||||
* @see org.springframework.beans.factory.support.BeanDefinitionRegistry
|
||||
* @see org.springframework.beans.factory.support.BeanNameGenerator
|
||||
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.springframework.context.annotation.AnnotatedBeanDefinitionReader
|
||||
* @see org.springframework.context.annotation.AnnotationConfigRegistry
|
||||
* @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner
|
||||
* @see org.springframework.context.annotation.ScopeMetadataResolver
|
||||
* @see org.springframework.context.support.AbstractRefreshableConfigApplicationContext
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class RefreshableAnnotationConfigApplicationContext extends AbstractRefreshableConfigApplicationContext
|
||||
implements AnnotationConfigRegistry {
|
||||
|
||||
protected static final boolean DEFAULT_COPY_CONFIGURATION = false;
|
||||
protected static final boolean USE_DEFAULT_FILTERS = true;
|
||||
|
||||
@Nullable
|
||||
private BeanNameGenerator beanNameGenerator;
|
||||
|
||||
@Nullable
|
||||
private volatile DefaultListableBeanFactory beanFactory;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final Set<String> basePackages = new LinkedHashSet<>();
|
||||
private final Set<Class<?>> componentClasses = new LinkedHashSet<>();
|
||||
|
||||
@Nullable
|
||||
private ScopeMetadataResolver scopeMetadataResolver;
|
||||
|
||||
// TODO: WARNING - Calling refreshBeanFactory() in the constructor to eagerly create a BeanFactory is problematic.
|
||||
// However, it does follow the BeanFactory creation pattern used by the GenericApplicationContext (and extensions
|
||||
// like AnnotationConfigApplicationContext) that this RefreshableAnnotationConfigApplicationContext implementation
|
||||
// is trying to preserve. Although, the AnnotationConfigApplicationContext implementation is NOT refreshable either,
|
||||
// yet the AnnotationConfigWebApplicationContext is but doesn't eagerly create a BeanFactory during construction,
|
||||
// so... The main problem with eagerly creating the BeanFactory in the constructor is the BeanFactory will be
|
||||
// closed and reconstructed on refresh, as well as on each refresh thereafter.
|
||||
|
||||
/**
|
||||
* Constructs an new instance of the {@link RefreshableAnnotationConfigApplicationContext}
|
||||
* with default container state and no {@literal parent} {@link ApplicationContext}.
|
||||
*
|
||||
* @see #RefreshableAnnotationConfigApplicationContext(ApplicationContext)
|
||||
*/
|
||||
public RefreshableAnnotationConfigApplicationContext() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the {@link RefreshableAnnotationConfigApplicationContext} initialized with
|
||||
* the {@literal parent} {@link ApplicationContext}.
|
||||
*
|
||||
* Additionally, this constructor eagerly initializes a {@link ConfigurableListableBeanFactory},
|
||||
* unlike {@link org.springframework.context.support.AbstractRefreshableApplicationContext} implementations,
|
||||
* but exactly like {@link org.springframework.context.support.GenericApplicationContext} implementations.
|
||||
*
|
||||
* @param parent parent {@link ApplicationContext} to this child context.
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see #refreshBeanFactory()
|
||||
*/
|
||||
public RefreshableAnnotationConfigApplicationContext(@Nullable ApplicationContext parent) {
|
||||
super(parent);
|
||||
refreshBeanFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link BeanNameGenerator} strategy used by this {@link ApplicationContext} to generate
|
||||
* {@link String bean names} for {@link BeanDefinition bean definitions}.
|
||||
*
|
||||
* @param beanNameGenerator {@link BeanNameGenerator} used to generate {@link String bean names}
|
||||
* for {@link BeanDefinition bean definitions}.
|
||||
* @see org.springframework.beans.factory.support.BeanNameGenerator
|
||||
*/
|
||||
public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) {
|
||||
this.beanNameGenerator = beanNameGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Optional optionally} configured {@link BeanNameGenerator} strategy used by this
|
||||
* {@link ApplicationContext} to generate {@link String bean names} for {@link BeanDefinition bean definitions}.
|
||||
*
|
||||
* @return the {@link BeanNameGenerator} strategy used to generate {@link String bean names}
|
||||
* for {@link BeanDefinition bean definitions}.
|
||||
* @see org.springframework.beans.factory.support.BeanNameGenerator
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
protected Optional<BeanNameGenerator> getBeanNameGenerator() {
|
||||
return Optional.ofNullable(this.beanNameGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured {@link Logger} used to log framework messages to the application log.
|
||||
*
|
||||
* @return the configured {@link Logger}.
|
||||
* @see org.slf4j.Logger
|
||||
*/
|
||||
protected @NonNull Logger getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link ScopeMetadataResolver} strategy used by this {@link ApplicationContext} to resolve
|
||||
* the {@literal scope} for {@link BeanDefinition bean definitions}.
|
||||
*
|
||||
* @param scopeMetadataResolver {@link ScopeMetadataResolver} used to resolve the {@literal scope}
|
||||
* of {@link BeanDefinition bean definitions}.
|
||||
* @see org.springframework.context.annotation.ScopeMetadataResolver
|
||||
*/
|
||||
public void setScopeMetadataResolver(@Nullable ScopeMetadataResolver scopeMetadataResolver) {
|
||||
this.scopeMetadataResolver = scopeMetadataResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Optional optionally} configured {@link ScopeMetadataResolver} strategy used by
|
||||
* this {@link ApplicationContext} to resolve the {@literal scope} for {@link BeanDefinition bean definitions}.
|
||||
*
|
||||
* @return the configured {@link ScopeMetadataResolver} used to resolve the {@literal scope}
|
||||
* for {@link BeanDefinition bean definitions}.
|
||||
* @see org.springframework.context.annotation.ScopeMetadataResolver
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
public Optional<ScopeMetadataResolver> getScopeMetadataResolver() {
|
||||
return Optional.ofNullable(this.scopeMetadataResolver);
|
||||
}
|
||||
|
||||
protected boolean isCopyConfigurationEnabled() {
|
||||
return DEFAULT_COPY_CONFIGURATION;
|
||||
}
|
||||
|
||||
protected boolean isUsingDefaultFilters() {
|
||||
return USE_DEFAULT_FILTERS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads {@link BeanDefinition BeanDefinitions} from Annotation configuration (component) classes
|
||||
* as well as from other resource locations (e.g. XML).
|
||||
*
|
||||
* @param beanFactory {@link DefaultListableBeanFactory} to configure.
|
||||
* @throws BeansException if loading and configuring the {@link BeanDefinition BeanDefintions} for the target
|
||||
* {@link DefaultListableBeanFactory} fails.
|
||||
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory
|
||||
* @see #newAnnotatedBeanDefinitionReader(BeanDefinitionRegistry)
|
||||
* @see #newClassBeanDefinitionScanner(BeanDefinitionRegistry)
|
||||
* @see #getConfigLocations()
|
||||
*/
|
||||
@Override
|
||||
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException {
|
||||
|
||||
AnnotatedBeanDefinitionReader reader = configure(newAnnotatedBeanDefinitionReader(beanFactory));
|
||||
|
||||
ClassPathBeanDefinitionScanner scanner = configure(newClassBeanDefinitionScanner(beanFactory));
|
||||
|
||||
getBeanNameGenerator().ifPresent(beanNameGenerator -> {
|
||||
reader.setBeanNameGenerator(beanNameGenerator);
|
||||
scanner.setBeanNameGenerator(beanNameGenerator);
|
||||
beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
|
||||
});
|
||||
|
||||
getScopeMetadataResolver().ifPresent(scopeMetadataResolver -> {
|
||||
reader.setScopeMetadataResolver(scopeMetadataResolver);
|
||||
scanner.setScopeMetadataResolver(scopeMetadataResolver);
|
||||
});
|
||||
|
||||
Arrays.stream(ArrayUtils.nullSafeArray(getConfigLocations(), String.class)).forEach(configLocation -> {
|
||||
try {
|
||||
Class<?> type = ClassUtils.forName(configLocation, getClassLoader());
|
||||
getLogger().trace("Registering [{}]", configLocation);
|
||||
reader.register(type);
|
||||
}
|
||||
catch (ClassNotFoundException cause) {
|
||||
|
||||
getLogger().trace(String.format("Could not load class for config location [%s] - trying package scan.",
|
||||
configLocation), cause);
|
||||
|
||||
if (scanner.scan(configLocation) == 0) {
|
||||
getLogger().debug("No component classes found for specified class/package [{}]", configLocation);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private AnnotatedBeanDefinitionReader configure(AnnotatedBeanDefinitionReader reader) {
|
||||
|
||||
Set<Class<?>> componentClasses = this.componentClasses;
|
||||
|
||||
if (!componentClasses.isEmpty()) {
|
||||
getLogger().debug("Registering component classes: {}", componentClasses);
|
||||
reader.register(ClassUtils.toClassArray(componentClasses));
|
||||
}
|
||||
|
||||
return reader;
|
||||
}
|
||||
|
||||
private ClassPathBeanDefinitionScanner configure(ClassPathBeanDefinitionScanner scanner) {
|
||||
|
||||
Set<String> basePackages = this.basePackages;
|
||||
|
||||
if (!basePackages.isEmpty()) {
|
||||
getLogger().debug("Scanning base packages: {}", basePackages);
|
||||
scanner.scan(StringUtils.toStringArray(basePackages));
|
||||
}
|
||||
|
||||
return scanner;
|
||||
}
|
||||
|
||||
protected AnnotatedBeanDefinitionReader newAnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
|
||||
return new AnnotatedBeanDefinitionReader(registry, getEnvironment());
|
||||
}
|
||||
|
||||
protected ClassPathBeanDefinitionScanner newClassBeanDefinitionScanner(BeanDefinitionRegistry registry) {
|
||||
return new ClassPathBeanDefinitionScanner(registry, isUsingDefaultFilters(), getEnvironment());
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-registers Singleton beans registered with the previous {@link ConfigurableListableBeanFactory BeanFactory}
|
||||
* (prior to refresh) with this {@link ApplicationContext}, iff this context was previously active
|
||||
* and subsequently refreshed.
|
||||
*
|
||||
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#copyConfigurationFrom(ConfigurableBeanFactory)
|
||||
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerSingleton(String, Object)
|
||||
* @see #getBeanFactory()
|
||||
*/
|
||||
@Override
|
||||
protected void onRefresh() {
|
||||
|
||||
super.onRefresh();
|
||||
|
||||
ConfigurableListableBeanFactory currentBeanFactory = getBeanFactory();
|
||||
|
||||
if (this.beanFactory != null) {
|
||||
|
||||
Arrays.stream(ArrayUtils.nullSafeArray(this.beanFactory.getSingletonNames(), String.class))
|
||||
.filter(singletonBeanName -> !currentBeanFactory.containsSingleton(singletonBeanName))
|
||||
.forEach(singletonBeanName -> currentBeanFactory
|
||||
.registerSingleton(singletonBeanName, this.beanFactory.getSingleton(singletonBeanName)));
|
||||
|
||||
if (isCopyConfigurationEnabled()) {
|
||||
currentBeanFactory.copyConfigurationFrom(this.beanFactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a reference to the previous {@link ConfigurableListableBeanFactory} in order to copy its configuration
|
||||
* and state on {@link ApplicationContext} refresh invocations.
|
||||
*
|
||||
* @see #getBeanFactory()
|
||||
*/
|
||||
@Override
|
||||
protected void prepareRefresh() {
|
||||
this.beanFactory = (DefaultListableBeanFactory) SpringUtils.safeGetValue(this::getBeanFactory);
|
||||
super.prepareRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void register(Class<?>... componentClasses) {
|
||||
|
||||
Arrays.stream(ArrayUtils.nullSafeArray(componentClasses, Class.class))
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(this.componentClasses::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void scan(String... basePackages) {
|
||||
|
||||
Arrays.stream(ArrayUtils.nullSafeArray(basePackages, String.class))
|
||||
.filter(StringUtils::hasText)
|
||||
.forEach(this.basePackages::add);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.context.logging;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Spring {@link ApplicationListener} used to log the state of the Spring {@link Environment}
|
||||
* on the {@link ContextRefreshedEvent}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.slf4j.Logger
|
||||
* @see org.slf4j.LoggerFactory
|
||||
* @see org.springframework.context.ApplicationListener
|
||||
* @see org.springframework.context.event.ContextRefreshedEvent
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see org.springframework.core.env.PropertySource
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class EnvironmentLoggingApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
protected static final String SYSTEM_ERR_ENABLED_PROPERTY =
|
||||
"spring.context.environment.logging.system-err.enabled";
|
||||
|
||||
static final ThreadLocal<Environment> threadLocalEnvironmentReference = new ThreadLocal<>();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(@NonNull ContextRefreshedEvent contextRefreshedEvent) {
|
||||
|
||||
Environment environment = contextRefreshedEvent.getApplicationContext().getEnvironment();
|
||||
|
||||
threadLocalEnvironmentReference.set(environment);
|
||||
|
||||
try {
|
||||
log("ENV: [%s]", ObjectUtils.nullSafeClassName(environment));
|
||||
log("ENV: Active Profiles %s", Arrays.toString(environment.getActiveProfiles()));
|
||||
log("ENV: Default Profiles %s", Arrays.toString(environment.getDefaultProfiles()));
|
||||
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
|
||||
ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) environment;
|
||||
|
||||
for (PropertySource<?> propertySource : configurableEnvironment.getPropertySources()) {
|
||||
log("ENV: PropertySource [%s]", propertySource.getName());
|
||||
getCompositePropertySourceLoggingFunction().apply(propertySource);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
threadLocalEnvironmentReference.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@literal Composite} {@link Function} capable of introspecting and logging properties
|
||||
* from specifically typed {@link PropertySource PropertySources}.
|
||||
*
|
||||
* @return a {@literal Composite} {@link Function} capable of introspecting and logging the properties
|
||||
* from specifically typed {@link PropertySource PropertySources}.
|
||||
* @see org.springframework.core.env.PropertySource
|
||||
* @see java.util.function.Function
|
||||
*/
|
||||
protected Function<PropertySource<?>, PropertySource<?>> getCompositePropertySourceLoggingFunction() {
|
||||
return new EnumerablePropertySourceLoggingFunction().andThen(new MapPropertySourceLoggingFunction());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the configured SLF4J {@link Logger}.
|
||||
*
|
||||
* @return a reference to the configured SLF4J {@link Logger}.
|
||||
* @see org.slf4j.Logger
|
||||
*/
|
||||
protected @NonNull Logger getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given {@link String message} formatted with the given array of {@link Object arguments}
|
||||
* to the configured Spring Boot application log.
|
||||
*
|
||||
* The given {@link String message} will only be logged if it contains text, otherwise this method does nothing
|
||||
* and silently returns.
|
||||
*
|
||||
* @param message {@link String} containing the message to log.
|
||||
* @param args optional array of {@link Object arguments} to apply when formatting the {@link String message}.
|
||||
* @see #logToSlf4jLogger(String, Object...)
|
||||
* @see #logToSystemErr(String, Object...)
|
||||
*/
|
||||
protected void log(String message, Object... args) {
|
||||
|
||||
if (StringUtils.hasText(message)) {
|
||||
logToSlf4jLogger(message, args);
|
||||
logToSystemErr(message, args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given {@link String message} to the configured SLF4J {@link Logger}.
|
||||
*
|
||||
* @param message {@link String} containing the message to log.
|
||||
* @param args optional array of {@link Object arguments} to apply when formatting the {@link String message}.
|
||||
* @see #getLogger()
|
||||
*/
|
||||
void logToSlf4jLogger(String message, Object... args) {
|
||||
//getLogger().debug(String.format(message, args), args);
|
||||
getLogger().debug(String.format(message, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given {@link String message} to {@link System#err}.
|
||||
*
|
||||
* This logging method is available to perform poor mans logging when explicit SLF4J {@link Logger} configuration
|
||||
* was not provided in the deployed Spring Boot application. However, in most cases, this logging method should not
|
||||
* be used and proper SLF4J {@link Logger} configuration should be provided in most cases.
|
||||
*
|
||||
* This logging option is only enabled when the {@literal spring.context.environment.logging.system-err.enabled}
|
||||
* property is set to {@literal true}.
|
||||
*
|
||||
* @param message {@link String} containing the message to log.
|
||||
* @param args optional array of {@link Object arguments} to apply when formatting the {@link String message}.
|
||||
*/
|
||||
void logToSystemErr(String message, Object... args) {
|
||||
|
||||
if (isSystemErrLoggingEnabled()) {
|
||||
message = message.trim().endsWith("%n") ? message : message.concat("%n");
|
||||
System.err.printf(message, args);
|
||||
System.err.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSystemErrLoggingEnabled() {
|
||||
|
||||
return Optional.ofNullable(threadLocalEnvironmentReference.get())
|
||||
.map(environment -> environment.getProperty(SYSTEM_ERR_ENABLED_PROPERTY, Boolean.class, false))
|
||||
.orElseGet(() -> Boolean.getBoolean(SYSTEM_ERR_ENABLED_PROPERTY));
|
||||
}
|
||||
|
||||
protected abstract class AbstractPropertySourceLoggingFunction
|
||||
implements Function<PropertySource<?>, PropertySource<?>> {
|
||||
|
||||
protected void logProperties(@NonNull Iterable<String> propertyNames,
|
||||
@NonNull Function<String, Object> propertyValueFunction) {
|
||||
|
||||
log("Properties [");
|
||||
|
||||
for (String propertyName : CollectionUtils.nullSafeIterable(propertyNames)) {
|
||||
log("\t%1$s = %2$s", propertyName, propertyValueFunction.apply(propertyName));
|
||||
}
|
||||
|
||||
log("]");
|
||||
}
|
||||
}
|
||||
|
||||
protected class EnumerablePropertySourceLoggingFunction extends AbstractPropertySourceLoggingFunction {
|
||||
|
||||
@Override
|
||||
public @Nullable PropertySource<?> apply(@Nullable PropertySource<?> propertySource) {
|
||||
|
||||
if (propertySource instanceof EnumerablePropertySource) {
|
||||
|
||||
EnumerablePropertySource<?> enumerablePropertySource =
|
||||
(EnumerablePropertySource<?>) propertySource;
|
||||
|
||||
String[] propertyNames = enumerablePropertySource.getPropertyNames();
|
||||
|
||||
Arrays.sort(propertyNames);
|
||||
|
||||
logProperties(Arrays.asList(propertyNames), enumerablePropertySource::getProperty);
|
||||
}
|
||||
|
||||
return propertySource;
|
||||
}
|
||||
}
|
||||
|
||||
// The PropertySource may not be enumerable but may use a Map as its source.
|
||||
protected class MapPropertySourceLoggingFunction extends AbstractPropertySourceLoggingFunction {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public @Nullable PropertySource<?> apply(@Nullable PropertySource<?> propertySource) {
|
||||
|
||||
if (!(propertySource instanceof EnumerablePropertySource)) {
|
||||
|
||||
Object source = propertySource != null
|
||||
? propertySource.getSource()
|
||||
: null;
|
||||
|
||||
if (source instanceof Map) {
|
||||
|
||||
Map<String, Object> map = new TreeMap<>((Map<String, Object>) source);
|
||||
|
||||
logProperties(map.keySet(), map::get);
|
||||
}
|
||||
}
|
||||
|
||||
return propertySource;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.context.logging;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
|
||||
import org.springframework.boot.context.logging.LoggingApplicationListener;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.event.GenericApplicationListener;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.gemfire.config.annotation.EnableLogging;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Spring {@link GenericApplicationListener} used to configure Apache Geode Logging from existing Spring Data
|
||||
* for Apache Geode Logging configuration support, such as when using the {@link EnableLogging} annotation
|
||||
* or alternatively using {@link Properties}.
|
||||
*
|
||||
* This listener must be ordered before the Spring Boot {@link LoggingApplicationListener}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.Properties
|
||||
* @see org.springframework.boot.SpringApplication
|
||||
* @see org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
|
||||
* @see org.springframework.boot.context.logging.LoggingApplicationListener
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.springframework.context.ApplicationEvent
|
||||
* @see org.springframework.context.event.GenericApplicationListener
|
||||
* @see org.springframework.core.Ordered
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class GeodeLoggingApplicationListener implements GenericApplicationListener {
|
||||
|
||||
private static final Class<?>[] EVENT_TYPES = { ApplicationEnvironmentPreparedEvent.class };
|
||||
private static final Class<?>[] SOURCE_TYPES = { ApplicationContext.class, SpringApplication.class };
|
||||
|
||||
public static final String SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY = "spring.boot.data.gemfire.log.level";
|
||||
public static final String SPRING_DATA_GEMFIRE_CACHE_LOG_LEVEL = "spring.data.gemfire.cache.log-level";
|
||||
public static final String SPRING_DATA_GEMFIRE_LOGGING_LOG_LEVEL = "spring.data.gemfire.logging.level";
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
|
||||
return LoggingApplicationListener.DEFAULT_ORDER > Ordered.HIGHEST_PRECEDENCE
|
||||
? LoggingApplicationListener.DEFAULT_ORDER - 1
|
||||
: Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(@Nullable ApplicationEvent event) {
|
||||
|
||||
if (event instanceof ApplicationEnvironmentPreparedEvent) {
|
||||
|
||||
ApplicationEnvironmentPreparedEvent environmentPreparedEvent = (ApplicationEnvironmentPreparedEvent) event;
|
||||
|
||||
onApplicationEnvironmentPreparedEvent(environmentPreparedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onApplicationEnvironmentPreparedEvent(
|
||||
@NonNull ApplicationEnvironmentPreparedEvent environmentPreparedEvent) {
|
||||
|
||||
Assert.notNull(environmentPreparedEvent, "ApplicationEnvironmentPreparedEvent must not be null");
|
||||
|
||||
Environment environment = environmentPreparedEvent.getEnvironment();
|
||||
|
||||
if (isSystemPropertyNotSet(SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY)) {
|
||||
|
||||
String logLevel = environment.getProperty(SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY,
|
||||
environment.getProperty(SPRING_DATA_GEMFIRE_LOGGING_LOG_LEVEL,
|
||||
environment.getProperty(SPRING_DATA_GEMFIRE_CACHE_LOG_LEVEL)));
|
||||
|
||||
setSystemProperty(SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY, logLevel);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isSystemPropertySet(@Nullable String propertyName) {
|
||||
return StringUtils.hasText(propertyName) && StringUtils.hasText(System.getProperty(propertyName));
|
||||
}
|
||||
|
||||
protected boolean isSystemPropertyNotSet(@Nullable String propertyName) {
|
||||
return !isSystemPropertySet(propertyName);
|
||||
}
|
||||
|
||||
protected void setSystemProperty(@NonNull String propertyName, @Nullable String propertyValue) {
|
||||
|
||||
Assert.hasText(propertyName, () -> String.format("PropertyName [%s] is required", propertyName));
|
||||
|
||||
if (StringUtils.hasText(propertyValue)) {
|
||||
System.setProperty(propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsEventType(@NonNull ResolvableType eventType) {
|
||||
|
||||
Class<?> rawType = eventType.getRawClass();
|
||||
|
||||
return rawType != null && Arrays.stream(EVENT_TYPES).anyMatch(it -> it.isAssignableFrom(rawType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSourceType(@Nullable Class<?> sourceType) {
|
||||
return sourceType != null && Arrays.stream(SOURCE_TYPES).anyMatch(it -> it.isAssignableFrom(sourceType));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.env;
|
||||
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newUnsupportedOperationException;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link Map} implementation adapting an {@link Environment} object in order to use the {@link Environment}
|
||||
* as a {@link Map}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.Map
|
||||
* @see java.util.AbstractMap
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see org.springframework.core.env.PropertySource
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Adapter_pattern">Adapter Software Design Pattern</a>
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public class EnvironmentMapAdapter extends AbstractMap<String, String> {
|
||||
|
||||
/**
|
||||
* Factory method used to construct an new instance of {@link EnvironmentMapAdapter} initialized with
|
||||
* the given {@link Environment}.
|
||||
*
|
||||
* @param environment {@link Environment} to adapt; must not be {@literal null}.
|
||||
* @return a new instance of {@link EnvironmentMapAdapter} for the given {@link Environment}.
|
||||
* @throws IllegalArgumentException if {@link Environment} is {@literal null}.
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see #EnvironmentMapAdapter(Environment)
|
||||
*/
|
||||
public static EnvironmentMapAdapter from(@NonNull Environment environment) {
|
||||
return new EnvironmentMapAdapter(environment);
|
||||
}
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link EnvironmentMapAdapter} initialized with the given {@link Environment}.
|
||||
*
|
||||
* @param environment {@link Environment} to adapt; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link Environment} is {@literal null}.
|
||||
* @see org.springframework.core.env.Environment
|
||||
*/
|
||||
public EnvironmentMapAdapter(@NonNull Environment environment) {
|
||||
|
||||
Assert.notNull(environment, "Environment must not be null");
|
||||
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured {@link Environment} object being adapted by this {@link Map}.
|
||||
*
|
||||
* @return the configured {@link Environment}; never {@literal null}.
|
||||
* @see org.springframework.core.env.Environment
|
||||
*/
|
||||
protected @NonNull Environment getEnvironment() {
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method determining whether the given {@link Object key} is a property
|
||||
* in the underlying {@link Environment}.
|
||||
*
|
||||
* @return a boolean value indicating whether the given {@link Object key} is a property
|
||||
* in the underlying {@link Environment}.
|
||||
* @see org.springframework.core.env.Environment#containsProperty(String)
|
||||
* @see #getEnvironment()
|
||||
*/
|
||||
@Override
|
||||
public boolean containsKey(@Nullable Object key) {
|
||||
return key != null && getEnvironment().containsProperty(String.valueOf(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link String value} for the property identified by the given {@link Map} {@link Object key}
|
||||
* from the underlying {@link Environment}.
|
||||
*
|
||||
* @param key {@link Object key} identifying the property whose value will be retrieved from the {@link Environment}.
|
||||
* @return the {@link String value} of the property identified by the given {@link Map} {@link Object key}
|
||||
* from the {@link Environment}.
|
||||
* @see org.springframework.core.env.Environment#getProperty(String)
|
||||
* @see #getEnvironment()
|
||||
*/
|
||||
@Override
|
||||
public @Nullable String get(@Nullable Object key) {
|
||||
return key != null ? getEnvironment().getProperty(String.valueOf(key)) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Set<Entry<String, String>> entrySet() {
|
||||
|
||||
Environment environment = getEnvironment();
|
||||
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
|
||||
Set<Entry<String, String>> entrySet = new HashSet<>();
|
||||
|
||||
for (PropertySource<?> propertySource : ((ConfigurableEnvironment) environment).getPropertySources()) {
|
||||
if (propertySource instanceof EnumerablePropertySource) {
|
||||
for (String propertyName : ((EnumerablePropertySource<?>) propertySource).getPropertyNames()) {
|
||||
entrySet.add(new EnvironmentEntry(environment, propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableSet(entrySet);
|
||||
}
|
||||
|
||||
throw newUnsupportedOperationException("Unable to determine the entrySet from the Environment [%s]",
|
||||
getEnvironment().getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EnvironmentEntry} is a {@code Map.Entry} implementation mapping an {@link Environment} property (key)
|
||||
* to its value.
|
||||
*
|
||||
* @see java.util.Map.Entry
|
||||
* @see org.springframework.core.env.Environment
|
||||
*/
|
||||
protected static class EnvironmentEntry implements Map.Entry<String, String> {
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
private final String key;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link EnvironmentEntry} initialized with the given {@link Environment}
|
||||
* and {@link String key} (property).
|
||||
*
|
||||
* @param environment {@link Environment} to which the {@link String key} belongs; must not be {@literal null}.
|
||||
* @param key {@link String} referring to the property from the {@link Environment}; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if the {@link Environment} or the {@link String key} is {@literal null}.
|
||||
* @see org.springframework.core.env.Environment
|
||||
*/
|
||||
public EnvironmentEntry(@NonNull Environment environment, @NonNull String key) {
|
||||
|
||||
Assert.notNull(environment, "Environment must not be null");
|
||||
Assert.hasText(key, () -> String.format("Key [%s] must be specified", key));
|
||||
|
||||
this.environment = environment;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured {@link Environment} to which this {@code Map.Entry} belongs.
|
||||
*
|
||||
* @return the configured {@link Environment}; never {@literal null}.
|
||||
* @see org.springframework.core.env.Environment
|
||||
*/
|
||||
protected @NonNull Environment getEnvironment() {
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link String key} (property) of this {@code Map.Entry}.
|
||||
*
|
||||
* @return the {@link String key} (property) of this {@code Map.Entry}.
|
||||
*/
|
||||
@Override
|
||||
public @NonNull String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link String value} mapped to the {@link #getKey() key} (property) in this {@code Map.Entry}
|
||||
* ({@link Environment}).
|
||||
*
|
||||
* @return the {@link String value} mapped to the {@link #getKey() key} (property) in this {@code Map.Entry}
|
||||
* ({@link Environment}).
|
||||
* @see org.springframework.core.env.Environment#getProperty(String)
|
||||
* @see #getEnvironment()
|
||||
* @see #getKey()
|
||||
*/
|
||||
@Override
|
||||
public @Nullable String getValue() {
|
||||
return getEnvironment().getProperty(getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public String setValue(String value) {
|
||||
throw newUnsupportedOperationException("Setting the value of Environment property [%s] is not supported",
|
||||
getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.env;
|
||||
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertiesPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.geode.core.env.support.CloudCacheService;
|
||||
import org.springframework.geode.core.env.support.Service;
|
||||
import org.springframework.geode.core.env.support.User;
|
||||
import org.springframework.geode.core.util.ObjectUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The {@link VcapPropertySource} class is a Spring {@link PropertySource} to process
|
||||
* {@literal VCAP} environment properties in Pivotal CloudFoundry.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Iterable
|
||||
* @see java.net.URL
|
||||
* @see java.util.Properties
|
||||
* @see java.util.function.Predicate
|
||||
* @see org.springframework.core.env.ConfigurableEnvironment
|
||||
* @see org.springframework.core.env.EnumerablePropertySource
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see org.springframework.core.env.PropertiesPropertySource
|
||||
* @see org.springframework.core.env.PropertySource
|
||||
* @see org.springframework.geode.core.env.support.CloudCacheService
|
||||
* @see org.springframework.geode.core.env.support.Service
|
||||
* @see org.springframework.geode.core.env.support.User
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class VcapPropertySource extends PropertySource<EnumerablePropertySource<?>> implements Iterable<String> {
|
||||
|
||||
private static final String CLOUD_CACHE_TAG_NAME = "cloudcache";
|
||||
private static final String GEMFIRE_TAG_NAME = "gemfire";
|
||||
private static final String THIS_PROPERTY_SOURCE_NAME = "boot.data.gemfire.vcap";
|
||||
private static final String VCAP_APPLICATION_PROPERTY = "vcap.application.";
|
||||
private static final String VCAP_APPLICATION_NAME_PROPERTY = VCAP_APPLICATION_PROPERTY + "name";
|
||||
private static final String VCAP_APPLICATION_URIS_PROPERTY = VCAP_APPLICATION_PROPERTY + "uris";
|
||||
private static final String VCAP_PROPERTY_SOURCE_NAME = "vcap";
|
||||
private static final String VCAP_SERVICES_PROPERTY = "vcap.services.";
|
||||
private static final String VCAP_SERVICES_SERVICE_NAME_LOCATORS_PROPERTY = VCAP_SERVICES_PROPERTY + "%s.credentials.locators";
|
||||
private static final String VCAP_SERVICES_SERVICE_NAME_NAME_PROPERTY = VCAP_SERVICES_PROPERTY + "%s.name";
|
||||
private static final String VCAP_SERVICES_SERVICE_NAME_TLS_ENABLED_PROPERTY = VCAP_SERVICES_PROPERTY + "%s.credentials.tls-enabled";
|
||||
private static final String VCAP_SERVICES_SERVICE_NAME_URL_GFSH_PROPERTY = VCAP_SERVICES_PROPERTY + "%s.credentials.urls.gfsh";
|
||||
private static final String VCAP_SERVICES_SERVICE_NAME_USERS_PROPERTY = VCAP_SERVICES_PROPERTY + "%s.credentials.users";
|
||||
private static final String VCAP_SERVICES_SERVICE_NAME_USERS_INDEX_PROPERTY = VCAP_SERVICES_SERVICE_NAME_USERS_PROPERTY + "[%d]";
|
||||
|
||||
private static final Predicate<Object> CLOUD_CACHE_SERVICE_PREDICATE =
|
||||
propertyValue -> String.valueOf(propertyValue).toLowerCase().contains(CLOUD_CACHE_TAG_NAME);
|
||||
|
||||
private static final Predicate<Object> GEMFIRE_SERVICE_PREDICATE =
|
||||
propertyValue -> String.valueOf(propertyValue).toLowerCase().contains(GEMFIRE_TAG_NAME);
|
||||
|
||||
private static final Predicate<Object> CLOUD_CACHE_AND_GEMFIRE_SERVICE_PREDICATE =
|
||||
CLOUD_CACHE_SERVICE_PREDICATE.and(GEMFIRE_SERVICE_PREDICATE);
|
||||
|
||||
private static final Predicate<String> VCAP_APPLICATION_PROPERTIES_PREDICATE =
|
||||
propertyName -> String.valueOf(propertyName).trim().toLowerCase().startsWith(VCAP_APPLICATION_PROPERTY);
|
||||
|
||||
private static final Predicate<PropertySource<?>> VCAP_REQUIRED_PROPERTIES_PREDICATE =
|
||||
propertySource -> propertySource.containsProperty(VCAP_APPLICATION_NAME_PROPERTY)
|
||||
&& propertySource.containsProperty(VCAP_APPLICATION_URIS_PROPERTY);
|
||||
|
||||
private static final Predicate<String> VCAP_SERVICES_PROPERTIES_PREDICATE =
|
||||
propertyName -> String.valueOf(propertyName).trim().toLowerCase().startsWith(VCAP_SERVICES_PROPERTY);
|
||||
|
||||
public static VcapPropertySource from(Environment environment) {
|
||||
|
||||
return Optional.ofNullable(environment)
|
||||
.filter(ConfigurableEnvironment.class::isInstance)
|
||||
.map(ConfigurableEnvironment.class::cast)
|
||||
.map(ConfigurableEnvironment::getPropertySources)
|
||||
.map(propertySources -> propertySources.get(VCAP_PROPERTY_SOURCE_NAME))
|
||||
.map(VcapPropertySource::from)
|
||||
.orElseThrow(() -> newIllegalArgumentException(
|
||||
"Environment was not configurable or does not contain an enumerable [%s] PropertySource",
|
||||
VCAP_PROPERTY_SOURCE_NAME));
|
||||
}
|
||||
|
||||
public static VcapPropertySource from(Properties properties) {
|
||||
|
||||
return Optional.ofNullable(properties)
|
||||
.map(it -> new PropertiesPropertySource(THIS_PROPERTY_SOURCE_NAME, properties))
|
||||
.filter(VCAP_REQUIRED_PROPERTIES_PREDICATE)
|
||||
.map(VcapPropertySource::new)
|
||||
.orElseThrow(() -> newIllegalArgumentException("Properties are required"));
|
||||
}
|
||||
|
||||
public static VcapPropertySource from(PropertySource<?> propertySource) {
|
||||
|
||||
return Optional.ofNullable(propertySource)
|
||||
.filter(it -> VCAP_PROPERTY_SOURCE_NAME.equals(it.getName()))
|
||||
.filter(VCAP_REQUIRED_PROPERTIES_PREDICATE)
|
||||
.filter(EnumerablePropertySource.class::isInstance)
|
||||
.map(EnumerablePropertySource.class::cast)
|
||||
.map(VcapPropertySource::new)
|
||||
.orElseThrow(() -> newIllegalArgumentException(
|
||||
"An EnumerablePropertySource named [%s] containing VCAP properties is required",
|
||||
VCAP_PROPERTY_SOURCE_NAME));
|
||||
}
|
||||
|
||||
private Predicate<String> vcapServicePredicate;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link PropertySource} from the existing, required {@link EnumerablePropertySource} instance
|
||||
* with the default name, {@literal boot.data.gemfire.vcap}, containing the {@literal VCAP} environment variable
|
||||
* configuration.
|
||||
*
|
||||
* @param propertySource existing, required {@link EnumerablePropertySource} containing the {@literal VCAP}
|
||||
* environment variables.
|
||||
* @throws IllegalArgumentException if the {@literal EnumerablePropertySource} is {@literal null}.
|
||||
* @see org.springframework.core.env.EnumerablePropertySource
|
||||
*/
|
||||
private VcapPropertySource(EnumerablePropertySource<?> propertySource) {
|
||||
super(THIS_PROPERTY_SOURCE_NAME, propertySource);
|
||||
}
|
||||
|
||||
protected Set<String> findAllPropertiesByNameMatching(Predicate<String> predicate) {
|
||||
return findAllPropertiesByNameMatching(this, predicate);
|
||||
}
|
||||
|
||||
protected Set<String> findAllPropertiesByNameMatching(Iterable<String> properties, Predicate<String> predicate) {
|
||||
|
||||
return StreamSupport.stream(CollectionUtils.nullSafeIterable(properties).spliterator(), false)
|
||||
.filter(predicate)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected Set<String> findAllPropertiesByValueMatching(Predicate<Object> predicate) {
|
||||
return findAllPropertiesByValueMatching(this, predicate);
|
||||
}
|
||||
|
||||
protected Set<String> findAllPropertiesByValueMatching(Iterable<String> properties, Predicate<Object> predicate) {
|
||||
|
||||
return StreamSupport.stream(CollectionUtils.nullSafeIterable(properties).spliterator(), false)
|
||||
.filter(propertyName -> predicate.test(getProperty(propertyName)))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public Set<String> findAllVcapApplicationProperties() {
|
||||
return findAllPropertiesByNameMatching(VCAP_APPLICATION_PROPERTIES_PREDICATE);
|
||||
}
|
||||
|
||||
public Set<String> findAllVcapServicesProperties() {
|
||||
return findTargetVcapServiceProperties(VCAP_SERVICES_PROPERTIES_PREDICATE);
|
||||
}
|
||||
|
||||
public Set<String> findTargetVcapServiceProperties(Predicate<String> vcapServicePropertiesPredicate) {
|
||||
return findAllPropertiesByNameMatching(filterByVcapServicePropertiesPredicate(vcapServicePropertiesPredicate));
|
||||
}
|
||||
|
||||
private Predicate<String> filterByVcapServicePropertiesPredicate(Predicate<String> vcapServicePropertiesPredicate) {
|
||||
|
||||
return isValid(vcapServicePropertiesPredicate)
|
||||
? VCAP_SERVICES_PROPERTIES_PREDICATE.and(vcapServicePropertiesPredicate)
|
||||
: VCAP_SERVICES_PROPERTIES_PREDICATE;
|
||||
}
|
||||
|
||||
private boolean isValid(Predicate<String> vcapServicePropertiesPredicate) {
|
||||
|
||||
return vcapServicePropertiesPredicate != null
|
||||
&& vcapServicePropertiesPredicate != VCAP_SERVICES_PROPERTIES_PREDICATE;
|
||||
}
|
||||
|
||||
public Optional<CloudCacheService> findFirstCloudCacheService() {
|
||||
|
||||
return findFirstCloudCacheServiceName()
|
||||
.map(serviceName -> {
|
||||
|
||||
CloudCacheService service = CloudCacheService.with(serviceName);
|
||||
|
||||
Object locators = getProperty(String.format(VCAP_SERVICES_SERVICE_NAME_LOCATORS_PROPERTY, service));
|
||||
|
||||
Optional.ofNullable(locators)
|
||||
.map(String::valueOf)
|
||||
.filter(StringUtils::hasText)
|
||||
.ifPresent(service::withLocators);
|
||||
|
||||
Object tlsEnabled = getProperty(String.format(VCAP_SERVICES_SERVICE_NAME_TLS_ENABLED_PROPERTY, service));
|
||||
|
||||
Optional.ofNullable(tlsEnabled)
|
||||
.map(String::valueOf)
|
||||
.map(Boolean::parseBoolean)
|
||||
.ifPresent(service::withTls);
|
||||
|
||||
Object gfshUrl = getProperty(String.format(VCAP_SERVICES_SERVICE_NAME_URL_GFSH_PROPERTY, service));
|
||||
|
||||
Optional.ofNullable(gfshUrl)
|
||||
.map(String::valueOf)
|
||||
.filter(StringUtils::hasText)
|
||||
.map(urlString -> ObjectUtils.doOperationSafely(() -> new URL(urlString)))
|
||||
.ifPresent(service::withGfshUrl);
|
||||
|
||||
return service;
|
||||
});
|
||||
}
|
||||
|
||||
public CloudCacheService requireFirstCloudCacheService() {
|
||||
|
||||
return findFirstCloudCacheService().orElseThrow(() ->
|
||||
newIllegalStateException("Unable to resolve a CloudCache Service Instance"));
|
||||
}
|
||||
|
||||
public Optional<String> findFirstCloudCacheServiceName() {
|
||||
|
||||
Iterable<String> vcapServicesProperties = findTargetVcapServiceProperties(getVcapServicePredicate());
|
||||
|
||||
return findAllPropertiesByValueMatching(vcapServicesProperties, CLOUD_CACHE_AND_GEMFIRE_SERVICE_PREDICATE)
|
||||
.stream()
|
||||
.filter(propertyName -> propertyName.endsWith(".tags"))
|
||||
.map(propertyName -> propertyName.substring(VCAP_SERVICES_PROPERTY.length()))
|
||||
.map(propertyName -> propertyName.substring(0, propertyName.indexOf(".")))
|
||||
.filter(StringUtils::hasText)
|
||||
.min(String.CASE_INSENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
public String requireFirstCloudCacheServiceName() {
|
||||
|
||||
String tags = String.format("%1$s, %2$s", CLOUD_CACHE_TAG_NAME, GEMFIRE_TAG_NAME);
|
||||
|
||||
return findFirstCloudCacheServiceName()
|
||||
.orElseThrow(() -> newIllegalStateException("No service with tags [%s] was found", tags));
|
||||
}
|
||||
|
||||
public Optional<User> findUserByName(Service service, String targetUsername) {
|
||||
|
||||
Assert.hasText(targetUsername, String.format("Target username [%s] is required", targetUsername));
|
||||
|
||||
Optional<User> optionalUser = Optional.empty();
|
||||
|
||||
String serviceName = service.getName();
|
||||
String userPropertyName = String.format(VCAP_SERVICES_SERVICE_NAME_USERS_INDEX_PROPERTY, serviceName, 0);
|
||||
|
||||
for (int index = 1; containsProperty(asUserUsernameProperty(userPropertyName)); index++) {
|
||||
|
||||
String username = String.valueOf(getProperty(asUserUsernameProperty(userPropertyName)));
|
||||
|
||||
if (username.equals(targetUsername)) {
|
||||
break;
|
||||
}
|
||||
|
||||
userPropertyName = String.format(VCAP_SERVICES_SERVICE_NAME_USERS_INDEX_PROPERTY, serviceName, index);
|
||||
}
|
||||
|
||||
if (containsProperty(asUserUsernameProperty(userPropertyName))) {
|
||||
|
||||
String username = String.valueOf(getProperty(asUserUsernameProperty(userPropertyName)));
|
||||
String password = String.valueOf(getProperty(asUserPasswordProperty(userPropertyName)));
|
||||
|
||||
User user = User.with(username)
|
||||
.withPassword(password);
|
||||
|
||||
optionalUser = Optional.of(user);
|
||||
}
|
||||
|
||||
return optionalUser;
|
||||
}
|
||||
|
||||
public Optional<User> findFirstUserByRoleClusterOperator(Service service) {
|
||||
|
||||
Optional<User> optionalUser = Optional.empty();
|
||||
|
||||
String serviceName = service.getName();
|
||||
String userPropertyName = String.format(VCAP_SERVICES_SERVICE_NAME_USERS_INDEX_PROPERTY, serviceName, 0);
|
||||
|
||||
for (int index = 1; containsProperty(asUserRolesProperty(userPropertyName)); index++) {
|
||||
|
||||
String roles = String.valueOf(getProperty(asUserRolesProperty(userPropertyName)));
|
||||
|
||||
if (roles.contains(User.Role.CLUSTER_OPERATOR.name().toLowerCase())) {
|
||||
break;
|
||||
}
|
||||
|
||||
userPropertyName = String.format(VCAP_SERVICES_SERVICE_NAME_USERS_INDEX_PROPERTY, serviceName, index);
|
||||
}
|
||||
|
||||
if (containsProperty(asUserUsernameProperty(userPropertyName))) {
|
||||
|
||||
String username = String.valueOf(getProperty(asUserUsernameProperty(userPropertyName)));
|
||||
String password = String.valueOf(getProperty(asUserPasswordProperty(userPropertyName)));
|
||||
|
||||
User user = User.with(username)
|
||||
.withPassword(password)
|
||||
.withRole(User.Role.CLUSTER_OPERATOR);
|
||||
|
||||
optionalUser = Optional.of(user);
|
||||
}
|
||||
|
||||
return optionalUser;
|
||||
}
|
||||
|
||||
private String asUserPasswordProperty(String userProperty) {
|
||||
return String.format("%s.password", userProperty);
|
||||
}
|
||||
|
||||
private String asUserRolesProperty(String userProperty) {
|
||||
return String.format("%s.roles", userProperty);
|
||||
}
|
||||
|
||||
private String asUserUsernameProperty(String userProperty) {
|
||||
return String.format("%s.username", userProperty);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
return getSource().getProperty(name);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Predicate<String> getVcapServicePredicate() {
|
||||
|
||||
return this.vcapServicePredicate != null
|
||||
? this.vcapServicePredicate
|
||||
: propertyName -> true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return Collections.unmodifiableList(Arrays.asList(getSource().getPropertyNames())).iterator();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public VcapPropertySource withVcapServiceName(@NonNull String serviceName) {
|
||||
|
||||
Assert.hasText(serviceName, "Service name is required");
|
||||
|
||||
String resolvedServiceName = StringUtils.trimAllWhitespace(serviceName);
|
||||
|
||||
Predicate<String> vcapServiceNamePredicate = propertyName ->
|
||||
propertyName.startsWith(String.format("%1$s%2$s.", VCAP_SERVICES_PROPERTY, resolvedServiceName));
|
||||
|
||||
return withVcapServicePredicate(vcapServiceNamePredicate);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public VcapPropertySource withVcapServicePredicate(@Nullable Predicate<String> vcapServicePredicate) {
|
||||
|
||||
this.vcapServicePredicate = vcapServicePredicate;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.env.support;
|
||||
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.gemfire.GemfireUtils;
|
||||
import org.springframework.geode.core.util.ObjectUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The {@link CloudCacheService} class is an Abstract Data Type (ADT) modeling the Pivotal Cloud Cache service
|
||||
* in Pivotal CloudFoundry (PCF).
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.net.URL
|
||||
* @see org.springframework.geode.core.env.support.Service
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class CloudCacheService extends Service {
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new {@link CloudCacheService} initialized with the given {@link String name}.
|
||||
*
|
||||
* @param name {@link String} containing the name of the {@link CloudCacheService}.
|
||||
* @throws IllegalArgumentException if the {@link String name} is {@literal null} or empty.
|
||||
* @return the new {@link CloudCacheService} with the given {@link String name}.
|
||||
* @see #CloudCacheService(String)
|
||||
*/
|
||||
public static CloudCacheService with(String name) {
|
||||
return new CloudCacheService(name);
|
||||
}
|
||||
|
||||
private Boolean tlsEnabled;
|
||||
|
||||
private String locators;
|
||||
|
||||
private URL gfshUrl;
|
||||
|
||||
/**
|
||||
* Construct a new instance of {@link CloudCacheService} initialized with the given {@link String name}.
|
||||
*
|
||||
* @param name {@link String} containing the name of the {@link CloudCacheService}.
|
||||
* @throws IllegalArgumentException if the {@link String name} is {@literal null} or empty.
|
||||
*/
|
||||
private CloudCacheService(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Optional} Gfsh {@link URL} if configured, used to connect to Apache Geode's Management REST API
|
||||
* (service).
|
||||
*
|
||||
* @return an {@link Optional} Gfsh {@link URL} used to connect to Apache Geode's Management REST API (service).
|
||||
* @see #withGfshUrl(URL)
|
||||
* @see java.util.Optional
|
||||
* @see java.net.URL
|
||||
*/
|
||||
public Optional<URL> getGfshUrl() {
|
||||
return Optional.ofNullable(this.gfshUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Optional} {@link String} containing the list of Apache Geode Locator network endpoints.
|
||||
*
|
||||
* The format of the {@link String}, if present, is {@literal host1[port1],host2[port2], ...,hostN[portN]}.
|
||||
*
|
||||
* @return an {@link Optional} {@link String} containing the list of Apache Geode Locator network endpoints.
|
||||
* @see #withLocators(String)
|
||||
*/
|
||||
public Optional<String> getLocators() {
|
||||
|
||||
return Optional.ofNullable(this.locators)
|
||||
.filter(StringUtils::hasText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link List} of Apache Geode Locator network endpoints.
|
||||
*
|
||||
* Returns an {@link Collections#emptyList() empty List} if no Locators were configured.
|
||||
*
|
||||
* @return a {@link List} of Apache Geode Locator network endpoints.
|
||||
* @see #getLocators()
|
||||
*/
|
||||
public List<Locator> getLocatorList() {
|
||||
|
||||
return getLocators()
|
||||
.map(Locator::parseLocators)
|
||||
.orElseGet(Collections::emptyList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Boolean} determining whether TLS/SSL is enabled between the client and the Pivotal Cloud Cache
|
||||
* (PCC) service instance in Pivotal CloudFoundry (PCF).
|
||||
*
|
||||
* @return {@literal true} if TLS is enabled, {@literal false} if not.
|
||||
*/
|
||||
public boolean isTlsEnabled() {
|
||||
return Boolean.TRUE.equals(this.tlsEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the Gfsh {@link URL} to connect to the Apache Geode
|
||||
* Management REST API (service).
|
||||
*
|
||||
* @param gfshUrl {@link URL} used to connect to the Apache Geode Management REST API (service).
|
||||
* @return this {@link CloudCacheService}.
|
||||
* @see #getGfshUrl()
|
||||
*/
|
||||
public CloudCacheService withGfshUrl(URL gfshUrl) {
|
||||
|
||||
this.gfshUrl = gfshUrl;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure the {@link String list of Locator} network endpoints.
|
||||
*
|
||||
* @param locators {@link String} containing a comma-delimited list of Locator network endpoints
|
||||
* of the format: {@literal host1[port1],host2[port2], ...,hostN[portN]}.
|
||||
* @return this {@link CloudCacheService}.
|
||||
* @see #getLocators()
|
||||
*/
|
||||
public CloudCacheService withLocators(String locators) {
|
||||
|
||||
this.locators = locators;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to configure whether TLS/SSL is enabled between a client and the Pivotal Cloud Cache (PCC)
|
||||
* service instance in Pivotal CloudFoundry (PCF).
|
||||
*
|
||||
* @param enabled {@link Boolean} value indicating whether TLS/SSL is enabled.
|
||||
* @return this {@link CloudCacheService}.
|
||||
* @see #isTlsEnabled()
|
||||
*/
|
||||
public CloudCacheService withTls(Boolean enabled) {
|
||||
|
||||
this.tlsEnabled = enabled;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class Locator implements Comparable<Locator> {
|
||||
|
||||
static final int DEFAULT_LOCATOR_PORT = GemfireUtils.DEFAULT_LOCATOR_PORT;
|
||||
|
||||
static final String DEFAULT_LOCATOR_HOST = "localhost";
|
||||
|
||||
private Integer port;
|
||||
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new {@link Locator} on the default {@link String host}
|
||||
* and {@link Integer port}.
|
||||
*
|
||||
* @return a new, default {@link Locator}.
|
||||
* @see #newLocator(String, int)
|
||||
*/
|
||||
public static Locator newLocator() {
|
||||
return newLocator(DEFAULT_LOCATOR_HOST, DEFAULT_LOCATOR_PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new {@link Locator} running on the default {@link String host}
|
||||
* and configured to listen on the given {@link Integer port}.
|
||||
*
|
||||
* @param port {@link Integer} containing the port number on which the {@link Locator} is listening.
|
||||
* @return a new {@link Locator} running on the default {@link String host},
|
||||
* listening on the given {@link Integer port}.
|
||||
* @throws IllegalArgumentException if the {@link Integer port} is less than {@literal 0}.
|
||||
* @see #newLocator(String, int)
|
||||
*/
|
||||
public static Locator newLocator(int port) {
|
||||
return newLocator(DEFAULT_LOCATOR_HOST, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new {@link Locator} configured to run on the given {@link String host}
|
||||
* and listening on the default {@link Integer port}.
|
||||
*
|
||||
* @param host {@link String} containing the name of the host on which the {@link Locator} is running.
|
||||
* @return a new {@link Locator} running on the configured {@link String host},
|
||||
* listening on the default {@link Integer port}.
|
||||
* @throws IllegalArgumentException if {@link String host} is {@literal null} or empty.
|
||||
* @see #newLocator(String, int)
|
||||
*/
|
||||
public static Locator newLocator(String host) {
|
||||
return newLocator(host, DEFAULT_LOCATOR_PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new {@link Locator} running on the configured {@link String host}
|
||||
* and listening on the configured {@link Integer port}.
|
||||
*
|
||||
* @param host {@link String} containing the name of the host on which the {@link Locator} is running.
|
||||
* @param port {@link Integer} containing the port number on which the {@link Locator} is listening.
|
||||
* @throws IllegalArgumentException if {@link String host} is {@literal null} or empty,
|
||||
* or the {@link Integer port} is less than {@literal 0}.
|
||||
* @return a new {@link Locator} on the configured {@link String host} and {@link Integer port}.
|
||||
*/
|
||||
public static Locator newLocator(String host, int port) {
|
||||
|
||||
Assert.hasText(host, String.format("Host [%s] is required", host));
|
||||
Assert.isTrue(port > -1, String.format("Port [%d] must be greater than equal to 0", port));
|
||||
|
||||
return new Locator(host, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to parse a {@link String comma-delimited list of Locator network endpoints}
|
||||
* into a {@link List} of {@link Locator} objects.
|
||||
*
|
||||
* The {@link String comma-delimited list of Locators} must be formatted as
|
||||
* {@literal host1[port1],host2[port2], ...,hostN[portN]}.
|
||||
*
|
||||
* @param locators {@link String} containing a comma-delimited list of Locator network endpoints.
|
||||
* @return a new {@link List} of {@link Locator} objects or an empty {@link List}
|
||||
* if no Locators were specified.
|
||||
* @throws IllegalArgumentException if an individual Locator {@link String host[port]} is not valid.
|
||||
* @see #parse(String)
|
||||
*/
|
||||
public static List<Locator> parseLocators(String locators) {
|
||||
|
||||
return Arrays.stream(String.valueOf(locators).split(","))
|
||||
.filter(StringUtils::hasText)
|
||||
.map(Locator::parse)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to parse an individual {@link String host[port]} network endpoint for a Locator.
|
||||
*
|
||||
* @param hostPort {@link String} containing the Locator host and port to parse.
|
||||
* @return a new {@link Locator} configured from the given {@link String}.
|
||||
* @throws IllegalArgumentException if the {@link String hostPort} are not valid.
|
||||
* @see #parseHost(String)
|
||||
* @see #parsePort(String)
|
||||
* @see #newLocator(String, int)
|
||||
*/
|
||||
public static Locator parse(String hostPort) {
|
||||
|
||||
return Optional.ofNullable(hostPort)
|
||||
.filter(StringUtils::hasText)
|
||||
.map(it -> {
|
||||
|
||||
String host = parseHost(it);
|
||||
int port = parsePort(it);
|
||||
|
||||
return newLocator(host, port);
|
||||
})
|
||||
.orElseThrow(() -> newIllegalArgumentException("Locator host/port [%s] is not valid", hostPort));
|
||||
}
|
||||
|
||||
private static String parseHost(String value) {
|
||||
|
||||
int index = String.valueOf(value).trim().indexOf("[");
|
||||
|
||||
return index > 0 ? value.trim().substring(0, index).trim()
|
||||
: index != 0 && StringUtils.hasText(value) ? value.trim()
|
||||
: DEFAULT_LOCATOR_HOST;
|
||||
}
|
||||
|
||||
private static int parsePort(String value) {
|
||||
|
||||
StringBuilder digits = new StringBuilder();
|
||||
|
||||
for (char chr : String.valueOf(value).toCharArray()) {
|
||||
if (Character.isDigit(chr)) {
|
||||
digits.append(chr);
|
||||
}
|
||||
}
|
||||
|
||||
return digits.length() > 0 ? Integer.parseInt(digits.toString()) : DEFAULT_LOCATOR_PORT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@link Locator} initialized with the {@link String host} and {@link Integer port}
|
||||
* on which this {@link Locator} is running and listening for connections.
|
||||
*
|
||||
* @param host {@link String} containing the name of the host on which this {@link Locator} is running.
|
||||
* @param port {@link Integer} specifying the port number on which this {@link Locator} is listening.
|
||||
*/
|
||||
private Locator(String host, Integer port) {
|
||||
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link String name} of the host on which this {@link Locator} is running.
|
||||
*
|
||||
* Defaults to {@literal localhost}.
|
||||
*
|
||||
* @return the {@link String name} of the host on which this {@link Locator} is running.
|
||||
*/
|
||||
public String getHost() {
|
||||
return StringUtils.hasText(this.host) ? this.host : DEFAULT_LOCATOR_HOST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Integer port} on which this {@link Locator} is listening.
|
||||
*
|
||||
* Defaults to {@literal 10334}.
|
||||
*
|
||||
* @return the {@link Integer port} on which this {@link Locator} is listening.
|
||||
*/
|
||||
public int getPort() {
|
||||
return this.port != null ? this.port : DEFAULT_LOCATOR_PORT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Locator other) {
|
||||
|
||||
int result = this.getHost().compareTo(other.getHost());
|
||||
|
||||
return result != 0 ? result : (this.getPort() - other.getPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof Locator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Locator that = (Locator) obj;
|
||||
|
||||
return this.getHost().equals(that.getHost())
|
||||
&& this.getPort() == that.getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int hashValue = 17;
|
||||
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getHost());
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getPort());
|
||||
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s[%d]", getHost(), getPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.env.support;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link Service} class is an Abstract Data Type (ADT) modeling a Pivotal CloudFoundry Service.
|
||||
*
|
||||
* @author John Blum
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class Service {
|
||||
|
||||
/**
|
||||
* Factory method to construct a new {@link Service} initialized with a {@link String name}.
|
||||
*
|
||||
* @param name {@link String} containing the name of the {@link Service}.
|
||||
* @return a new {@link Service} configured with the given {@link String name}.
|
||||
* @throws IllegalArgumentException if the {@link String name} is {@literal null} or empty.
|
||||
* @see #Service(String)
|
||||
*/
|
||||
public static Service with(String name) {
|
||||
return new Service(name);
|
||||
}
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Service} initialized with a {@link String name}.
|
||||
*
|
||||
* @param name {@link String} containing the name of the {@link Service}.
|
||||
* @throws IllegalArgumentException if the {@link String name} is {@literal null} or empty.
|
||||
*/
|
||||
Service(String name) {
|
||||
|
||||
Assert.hasText(name, String.format("Service name [%s] is required", name));
|
||||
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link String name} of this {@link Service}.
|
||||
*
|
||||
* @return this {@link Service Service's} {@link String name}.
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
182
spring-geode-project/spring-geode/src/main/java/org/springframework/geode/core/env/support/User.java
vendored
Normal file
182
spring-geode-project/spring-geode/src/main/java/org/springframework/geode/core/env/support/User.java
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.env.support;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The {@link User} class is an Abstract Data Type (ADT) modeling a user in Pivotal CloudFoundry (PCF).
|
||||
*
|
||||
* @author John Blum
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class User implements Comparable<User> {
|
||||
|
||||
private Role role;
|
||||
|
||||
private final String name;
|
||||
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new {@link User} initialized with the given {@link String name}.
|
||||
*
|
||||
* @param name {@link String} containing the name of the {@link User}.
|
||||
* @return a new {@link User} initialized witht he given {@link String name}.
|
||||
* @throws IllegalArgumentException if {@link String name} is {@literal null} or empty.
|
||||
* @see #User(String)
|
||||
*/
|
||||
public static User with(String name) {
|
||||
return new User(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link User} initialized with the given {@link String name}.
|
||||
*
|
||||
* @param name {@link String} containing the name of the {@link User}.
|
||||
* @throws IllegalArgumentException if {@link String name} is {@literal null} or empty.
|
||||
*/
|
||||
private User(String name) {
|
||||
|
||||
Assert.hasText(name, String.format("User name [%s] is required", name));
|
||||
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link String name} of this {@link User}.
|
||||
*
|
||||
* @return a {@link String} containing the {@link User User's} name.
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Optional} {@link String} containing {@link User User's} password.
|
||||
*
|
||||
* @return an {@link Optional} {@link String} containing {@link User User's} password.
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
public Optional<String> getPassword() {
|
||||
return Optional.ofNullable(this.password).filter(StringUtils::hasText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Optional} {@link Role} for this {@link User}.
|
||||
*
|
||||
* @return an {@link Optional} {@link Role} for this {@link User}.
|
||||
* @see org.springframework.geode.core.env.support.User.Role
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
public Optional<Role> getRole() {
|
||||
return Optional.ofNullable(this.role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to set this {@link User User's} {@link String password}.
|
||||
*
|
||||
* @param password {@link String} containing this {@link User User's} password.
|
||||
* @return this {@link User}.
|
||||
*/
|
||||
public User withPassword(String password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to set this {@link User User's} {@link Role}.
|
||||
*
|
||||
* @param role assigned {@link Role} of this {@link User}.
|
||||
* @return this {@link User}.
|
||||
* @see org.springframework.geode.core.env.support.User
|
||||
*/
|
||||
public User withRole(Role role) {
|
||||
this.role = role;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public int compareTo(User other) {
|
||||
return this.getName().compareTo(other.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof User)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
User that = (User) obj;
|
||||
|
||||
return this.getName().equals(that.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int hashValue = 17;
|
||||
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getName());
|
||||
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
public enum Role {
|
||||
|
||||
CLUSTER_OPERATOR,
|
||||
DEVELOPER;
|
||||
|
||||
public static Role of(String name) {
|
||||
|
||||
return Arrays.stream(values())
|
||||
.filter(role -> role.name().equalsIgnoreCase(String.valueOf(name).trim()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public boolean isClusterOperator() {
|
||||
return CLUSTER_OPERATOR.equals(this);
|
||||
}
|
||||
|
||||
public boolean isDeveloper() {
|
||||
return DEVELOPER.equals(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.geode.core.io.support.ResourceUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Abstract base class providing functionality common to all {@link ResourceReader} implementations.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.io.InputStream
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see ResourceReader
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public abstract class AbstractResourceReader implements ResourceReader {
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public @NonNull byte[] read(@NonNull Resource resource) {
|
||||
|
||||
return Optional.ofNullable(resource)
|
||||
.filter(this::isAbleToHandle)
|
||||
.map(this::preProcess)
|
||||
.map(it -> {
|
||||
try (InputStream in = it.getInputStream()) {
|
||||
return doRead(in);
|
||||
}
|
||||
catch (IOException cause) {
|
||||
throw new ResourceReadException(String.format("Failed to read from Resource [%s]",
|
||||
it.getDescription()), cause);
|
||||
}
|
||||
})
|
||||
.orElseThrow(() -> new UnhandledResourceException(String.format("Unable to handle Resource [%s]",
|
||||
ResourceUtils.nullSafeGetDescription(resource))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this reader is able to handle and read from the target {@link Resource}.
|
||||
*
|
||||
* The default implementation determines that the {@link Resource} can be handled if the {@link Resource} handle
|
||||
* is not {@literal null}.
|
||||
*
|
||||
* @param resource {@link Resource} to evaluate.
|
||||
* @return a boolean value indicating whether this reader is able to handle and read from
|
||||
* the target {@link Resource}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected boolean isAbleToHandle(@Nullable Resource resource) {
|
||||
return resource != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data from the target {@link Resource} (intentionally) by using the {@link InputStream} returned by
|
||||
* {@link Resource#getInputStream()}.
|
||||
*
|
||||
* However, other algorithm/strategy implementations are free to read from the {@link Resource} as is appropriate
|
||||
* for the given context (e.g. cloud environment). In those cases, implementors should override
|
||||
* the {@link #read(Resource)} method.
|
||||
*
|
||||
* @param resourceInputStream {@link InputStream} used to read data from the target {@link Resource}.
|
||||
* @return a {@literal non-null} byte array containing the data from the target {@link Resource}.
|
||||
* @throws IOException if an I/O error occurs while reading from the {@link Resource}.
|
||||
* @see java.io.InputStream
|
||||
* @see #read(Resource)
|
||||
*/
|
||||
protected abstract @NonNull byte[] doRead(@NonNull InputStream resourceInputStream) throws IOException;
|
||||
|
||||
/**
|
||||
* Pre-processes the target {@link Resource} before reading from the {@link Resource}.
|
||||
*
|
||||
* @param resource {@link Resource} to pre-process; never {@literal null}.
|
||||
* @return the given, target {@link Resource}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
protected @NonNull Resource preProcess(@NonNull Resource resource) {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.WritableResource;
|
||||
import org.springframework.geode.core.io.support.ResourceUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Abstract base class providing functionality common to all {@link ResourceWriter} implementations.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.io.OutputStream
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.core.io.WritableResource
|
||||
* @see ResourceWriter
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public abstract class AbstractResourceWriter implements ResourceWriter {
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void write(@NonNull Resource resource, byte[] data) {
|
||||
|
||||
ResourceUtils.asWritableResource(resource)
|
||||
.filter(this::isAbleToHandle)
|
||||
.map(this::preProcess)
|
||||
.map(it -> {
|
||||
try (OutputStream out = it.getOutputStream()) {
|
||||
doWrite(out, data);
|
||||
return true;
|
||||
}
|
||||
catch (IOException cause) {
|
||||
throw new ResourceWriteException(String.format("Failed to write to Resource [%s]",
|
||||
it.getDescription()), cause);
|
||||
}
|
||||
})
|
||||
.orElseThrow(() -> new UnhandledResourceException(String.format("Unable to handle Resource [%s]",
|
||||
ResourceUtils.nullSafeGetDescription(resource))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this writer is able to handle and write to the target {@link Resource}.
|
||||
*
|
||||
* The default implementation determines that the {@link Resource} can be handled if the {@link Resource} handle
|
||||
* is not {@literal null}.
|
||||
*
|
||||
* @param resource {@link Resource} to evaluate.
|
||||
* @return a boolean value indicating whether this writer is able to handle and write to the target {@link Resource}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected boolean isAbleToHandle(@Nullable Resource resource) {
|
||||
return resource != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given data to the target {@link Resource} (intentionally) by using the {@link OutputStream}
|
||||
* returned by {@link WritableResource#getOutputStream()}.
|
||||
*
|
||||
* However, other algorithm/strategy implementations are free to write to the {@link Resource} as is appropriate
|
||||
* for the given context (e.g. cloud environment). In those cases, implementors should override
|
||||
* the {@link #write(Resource, byte[])} method.
|
||||
*
|
||||
* @param resourceOutputStream {@link OutputStream} returned from {@link WritableResource#getOutputStream()}
|
||||
* used to write the given data to the locations identified by the target {@link Resource}.
|
||||
* @param data array of bytes containing the data to write.
|
||||
* @throws IOException if an I/O error occurs while writing to the target {@link Resource}.
|
||||
* @see java.io.OutputStream
|
||||
*/
|
||||
protected abstract void doWrite(OutputStream resourceOutputStream, byte[] data) throws IOException;
|
||||
|
||||
/**
|
||||
* Pre-processes the target {@link WritableResource} before writing to the {@link WritableResource}.
|
||||
*
|
||||
* @param resource {@link WritableResource} to pre-process; never {@literal null}.
|
||||
* @return the given, target {@link WritableResource}.
|
||||
* @see org.springframework.core.io.WritableResource
|
||||
*/
|
||||
protected @NonNull WritableResource preProcess(@NonNull WritableResource resource) {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* A Java {@link RuntimeException} indicating a problem accessing (e.g. reading/writing) the data
|
||||
* of the target {@link Resource}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.RuntimeException
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @since 1.3.1
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ResourceDataAccessException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceDataAccessException} with no {@link String message}
|
||||
* or known {@link Throwable cause}.
|
||||
*/
|
||||
public ResourceDataAccessException() { }
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceDataAccessException} initialized with the given {@link String message}
|
||||
* to describe the error.
|
||||
*
|
||||
* @param message {@link String} describing the {@link RuntimeException}.
|
||||
*/
|
||||
public ResourceDataAccessException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceDataAccessException} initialized with the given {@link Throwable}
|
||||
* signifying the underlying cause of this {@link RuntimeException}.
|
||||
*
|
||||
* @param cause {@link Throwable} signifying the underlying cause of this {@link RuntimeException}.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public ResourceDataAccessException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceDataAccessException} initialized with the given {@link String message}
|
||||
* describing the error along with a {@link Throwable} signifying the underlying cause of this
|
||||
* {@link RuntimeException}.
|
||||
*
|
||||
* @param message {@link String} describing the {@link RuntimeException}.
|
||||
* @param cause {@link Throwable} signifying the underlying cause of this {@link RuntimeException}.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public ResourceDataAccessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* {@link RuntimeException} indication that a {@link Resource} could not be found.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.RuntimeException
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @since 1.3.1
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ResourceNotFoundException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceNotFoundException} with no {@link String message}
|
||||
* or known {@link Throwable cause}.
|
||||
*/
|
||||
public ResourceNotFoundException() { }
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceNotFoundException} initialized with the given {@link String message}
|
||||
* to describe the error.
|
||||
*
|
||||
* @param message {@link String} describing the {@link RuntimeException}.
|
||||
*/
|
||||
public ResourceNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceNotFoundException} initialized with the given {@link Throwable}
|
||||
* signifying the underlying cause of this {@link RuntimeException}.
|
||||
*
|
||||
* @param cause {@link Throwable} signifying the underlying cause of this {@link RuntimeException}.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public ResourceNotFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceNotFoundException} initialized with the given {@link String message}
|
||||
* describing the error along with a {@link Throwable} signifying the underlying cause of this
|
||||
* {@link RuntimeException}.
|
||||
*
|
||||
* @param message {@link String} describing the {@link RuntimeException}.
|
||||
* @param cause {@link Throwable} signifying the underlying cause of this {@link RuntimeException}.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public ResourceNotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* A {@link ResourceDataAccessException} and Java {@link RuntimeException} indicating a problem
|
||||
* while reading from the target {@link Resource}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.RuntimeException
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.geode.core.io.ResourceDataAccessException
|
||||
* @since 1.3.1
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ResourceReadException extends ResourceDataAccessException {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceReadException} with no {@link String message}
|
||||
* or known {@link Throwable cause}.
|
||||
*/
|
||||
public ResourceReadException() { }
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceReadException} initialized with the given {@link String message}
|
||||
* to describe the error.
|
||||
*
|
||||
* @param message {@link String} describing the {@link RuntimeException}.
|
||||
*/
|
||||
public ResourceReadException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceReadException} initialized with the given {@link Throwable}
|
||||
* signifying the underlying cause of this {@link RuntimeException}.
|
||||
*
|
||||
* @param cause {@link Throwable} signifying the underlying cause of this {@link RuntimeException}.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public ResourceReadException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceReadException} initialized with the given {@link String message}
|
||||
* describing the error along with a {@link Throwable} signifying the underlying cause of this
|
||||
* {@link RuntimeException}.
|
||||
*
|
||||
* @param message {@link String} describing the {@link RuntimeException}.
|
||||
* @param cause {@link Throwable} signifying the underlying cause of this {@link RuntimeException}.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public ResourceReadException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Interface (contract) for readers to define the algorithm and strategy for reading data from a {@link Resource},
|
||||
* such as by using the {@link Resource Resource's} {@link Resource#getInputStream() InputStream}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.nio.ByteBuffer
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @since 1.3.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("unused")
|
||||
public interface ResourceReader {
|
||||
|
||||
/**
|
||||
* Reads data from the {@literal non-null} {@link Resource} into a byte array.
|
||||
*
|
||||
* This method should throw an {@link UnhandledResourceException} if the algorithm and strategy used by this reader
|
||||
* is not able to or capable of reading from the {@link Resource} at its location. This allows subsequent readers
|
||||
* in a composition to possibly handle the {@link Resource}. Any other {@link Throwable} thrown by this {@code read}
|
||||
* method will break the chain of read calls in the composition.
|
||||
*
|
||||
* @param resource {@link Resource} to read data from.
|
||||
* @return a {@literal non-null} byte array containing the data from the {@link Resource}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
@NonNull byte[] read(@NonNull Resource resource);
|
||||
|
||||
/**
|
||||
* Reads data from the {@literal non-null} {@link Resource} into a {@link ByteBuffer}.
|
||||
*
|
||||
* @param resource {@link Resource} to read data from.
|
||||
* @return a {@literal non-null} {@link ByteBuffer} containing the data from the {@link Resource}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see java.nio.ByteBuffer
|
||||
* @see #read(Resource)
|
||||
*/
|
||||
default @NonNull ByteBuffer readIntoByteBuffer(@NonNull Resource resource) {
|
||||
return ByteBuffer.wrap(read(resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes this {@link ResourceReader} with the given {@link ResourceReader}
|
||||
* using the {@literal Composite Software Design Pattern}.
|
||||
*
|
||||
* @param reader {@link ResourceReader} to compose with this reader.
|
||||
* @return a composite {@link ResourceReader} composed of this {@link ResourceReader}
|
||||
* and the given {@link ResourceReader}. If the given {@link ResourceReader} is {@literal null},
|
||||
* then this {@link ResourceReader} is returned.
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Composite_pattern">Compsite Software Design Pattern</a>
|
||||
* @see ResourceReader
|
||||
*/
|
||||
default @NonNull ResourceReader thenReadFrom(@Nullable ResourceReader reader) {
|
||||
|
||||
return reader == null ? this
|
||||
: resource -> {
|
||||
try {
|
||||
return this.read(resource);
|
||||
}
|
||||
catch (UnhandledResourceException ignore) {
|
||||
return reader.read(resource);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Interface defining a contract encapsulating an algorithm/strategy for resolving {@link Resource Resources}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @since 1.3.1.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ResourceResolver {
|
||||
|
||||
/**
|
||||
* Gets the {@link ClassLoader} used by this {@link ResourceResolver} to resolve {@literal classpath}
|
||||
* {@link Resource Resources}.
|
||||
*
|
||||
* By default, this method will return a {@link ClassLoader} determined by {@link ClassUtils#getDefaultClassLoader()},
|
||||
* which first tries to return the {@link Thread#getContextClassLoader()}, then {@link Class#getClassLoader()},
|
||||
* and finally, {@link ClassLoader#getSystemClassLoader()}.
|
||||
*
|
||||
* @return an {@link Optional} {@link ClassLoader} used to resolve {@literal classpath} {@link Resource Resources}.
|
||||
* @see org.springframework.util.ClassUtils#getDefaultClassLoader()
|
||||
* @see java.lang.ClassLoader
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
default Optional<ClassLoader> getClassLoader() {
|
||||
return Optional.ofNullable(ClassUtils.getDefaultClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to resolve a {@link Resource} handle from the given, {@literal non-null} {@link String location}
|
||||
* (e.g. {@link String filesystem path}).
|
||||
*
|
||||
* @param location {@link String location} identifying the {@link Resource} to resolve;
|
||||
* must not be {@literal null}.
|
||||
* @return an {@link Optional} {@link Resource} handle for the given {@link String location}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
Optional<Resource> resolve(@NonNull String location);
|
||||
|
||||
/**
|
||||
* Returns a {@literal non-null}, {@literal existing} {@link Resource} handle resolved from the given,
|
||||
* {@literal non-null} {@link String location} (e.g. {@link String filesystem path}).
|
||||
*
|
||||
* @param location {@link String location} identifying the {@link Resource} to resolve;
|
||||
* must not be {@literal null}.
|
||||
* @return a {@literal non-null}, {@literal existing} {@link Resource} handle for
|
||||
* the resolved {@link String location}.
|
||||
* @throws ResourceNotFoundException if a {@link Resource} cannot be resolved from the given {@link String location}.
|
||||
* A {@link Resource} is unresolvable if the given {@link String location} does not exist (physically);
|
||||
* see {@link Resource#exists()}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see #resolve(String)
|
||||
*/
|
||||
default @NonNull Resource require(@NonNull String location) {
|
||||
return resolve(location)
|
||||
.filter(Resource::exists)
|
||||
.orElseThrow(() -> new ResourceNotFoundException(String.format("Resource [%s] does not exist", location)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* A {@link ResourceDataAccessException} and Java {@link RuntimeException} indicating a problem
|
||||
* while writing to the target {@link Resource}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.RuntimeException
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.geode.core.io.ResourceDataAccessException
|
||||
* @since 1.3.1
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ResourceWriteException extends ResourceDataAccessException {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceWriteException} with no {@link String message}
|
||||
* or known {@link Throwable cause}.
|
||||
*/
|
||||
public ResourceWriteException() { }
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceWriteException} initialized with the given {@link String message}
|
||||
* to describe the error.
|
||||
*
|
||||
* @param message {@link String} describing the {@link RuntimeException}.
|
||||
*/
|
||||
public ResourceWriteException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceWriteException} initialized with the given {@link Throwable}
|
||||
* signifying the underlying cause of this {@link RuntimeException}.
|
||||
*
|
||||
* @param cause {@link Throwable} signifying the underlying cause of this {@link RuntimeException}.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public ResourceWriteException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourceWriteException} initialized with the given {@link String message}
|
||||
* describing the error along with a {@link Throwable} signifying the underlying cause of this
|
||||
* {@link RuntimeException}.
|
||||
*
|
||||
* @param message {@link String} describing the {@link RuntimeException}.
|
||||
* @param cause {@link Throwable} signifying the underlying cause of this {@link RuntimeException}.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public ResourceWriteException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.WritableResource;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* Interface (contract) for writers to define the algorithm or strategy for writing data to a target {@link Resource},
|
||||
* such as by using the {@link WritableResource WritableResource's}
|
||||
* {@link WritableResource#getOutputStream()} OutputStream}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.core.io.WritableResource
|
||||
* @since 1.3.1
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("unused")
|
||||
public interface ResourceWriter {
|
||||
|
||||
/**
|
||||
* Writes data to the target {@link Resource} as defined by the algorithm/strategy of this writer.
|
||||
*
|
||||
* This method should throw an {@link UnhandledResourceException} if the algorithm or strategy used by this writer
|
||||
* is not able to or capable of writing to the {@link Resource} at its location. This allows subsequent writers
|
||||
* in a composition to possibly handle the {@link Resource}. Any other {@link Exception} thrown by this
|
||||
* {@code write} method will break the chain of write calls in the composition.
|
||||
*
|
||||
* @param resource {@link Resource} to write data to.
|
||||
* @param data array of bytes containing the data to write to the target {@link Resource}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
void write(@NonNull Resource resource, byte[] data);
|
||||
|
||||
/**
|
||||
* Writes data contained in the {@link ByteBuffer} to the target {@link Resource} as defined by
|
||||
* the algorithm/strategy of this writer.
|
||||
*
|
||||
* This method should throw an {@link UnhandledResourceException} if the algorithm or strategy used by this writer
|
||||
* is not able to or capable of writing to the {@link Resource} at its location. This allows subsequent writers
|
||||
* in a composition to possibly handle the {@link Resource}. Any other {@link Exception} thrown by this
|
||||
* {@code write} method will break the chain of write calls in the composition.
|
||||
*
|
||||
* @param resource {@link Resource} to write data to.
|
||||
* @param data {@link ByteBuffer} containing the data to write to the target {@link Resource}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see java.nio.ByteBuffer
|
||||
* @see #write(Resource, byte[])
|
||||
*/
|
||||
default void write (@NonNull Resource resource, ByteBuffer data) {
|
||||
write(resource, data.array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes this {@link ResourceWriter} with the given {@link ResourceWriter}
|
||||
* using the {@literal Composite Software Design Pattern}.
|
||||
*
|
||||
* @param writer {@link ResourceWriter} to compose with this writer.
|
||||
* @return a composite {@link ResourceWriter} composed of this {@link ResourceWriter}
|
||||
* and the given {@link ResourceWriter}. If the given {@link ResourceWriter} is {@literal null},
|
||||
* then this {@link ResourceWriter} is returned.
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Composite_pattern">Compsite Software Design Pattern</a>
|
||||
* @see ResourceWriter
|
||||
*/
|
||||
default ResourceWriter thenWriteTo(ResourceWriter writer) {
|
||||
|
||||
return writer == null ? this
|
||||
: (resource, data) -> {
|
||||
try {
|
||||
this.write(resource, data);
|
||||
}
|
||||
catch (UnhandledResourceException ignore) {
|
||||
writer.write(resource, data);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* A {@link RuntimeException} indicating that a {@link Resource} was not properly handled during normal processing.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.RuntimeException
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @since 1.3.1
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class UnhandledResourceException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link UnhandledResourceException} with no {@link String message}
|
||||
* or no known {@link Throwable cause}.
|
||||
*/
|
||||
public UnhandledResourceException() { }
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link UnhandledResourceException} initialized with the given {@link String message}
|
||||
* to describe the error.
|
||||
*
|
||||
* @param message {@link String} describing the {@link RuntimeException}.
|
||||
*/
|
||||
public UnhandledResourceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link UnhandledResourceException} initialized with the given {@link Throwable}
|
||||
* signifying the underlying cause of this {@link RuntimeException}.
|
||||
*
|
||||
* @param cause {@link Throwable} signifying the underlying cause of this {@link RuntimeException}.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public UnhandledResourceException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link UnhandledResourceException} initialized with the given {@link String message}
|
||||
* describing the error along with a {@link Throwable} signifying the underlying cause of this
|
||||
* {@link RuntimeException}.
|
||||
*
|
||||
* @param message {@link String} describing the {@link RuntimeException}.
|
||||
* @param cause {@link Throwable} signifying the underlying cause of this {@link RuntimeException}.
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public UnhandledResourceException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io.support;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.geode.core.io.AbstractResourceReader;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* A concrete {@link AbstractResourceReader} implementation that reads data from a target {@link Resource Resource's}
|
||||
* {@link Resource#getInputStream() InputStream} into a byte array.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.io.InputStream
|
||||
* @see java.io.ByteArrayOutputStream
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.geode.core.io.AbstractResourceReader
|
||||
* @since 1.3.1
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ByteArrayResourceReader extends AbstractResourceReader {
|
||||
|
||||
protected static final int DEFAULT_BUFFER_SIZE = 32768;
|
||||
|
||||
/**
|
||||
* Returns the required {@link Integer#TYPE buffer size} used to capture data from the target {@link Resource}
|
||||
* in chunks.
|
||||
*
|
||||
* Subclasses are encouraged to override this method as necessary to tune the buffer size. By default, the
|
||||
* buffer size is {@literal 32K} or {@literal 32768} bytes.
|
||||
*
|
||||
* @return the required {@link Integer#TYPE buffer size} to read from the {@link Resource} in chunks.
|
||||
*/
|
||||
protected int getBufferSize() {
|
||||
return DEFAULT_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
protected @NonNull byte[] doRead(@NonNull InputStream resourceInputStream) throws IOException {
|
||||
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream(resourceInputStream.available())) {
|
||||
|
||||
byte[] buffer = new byte[getBufferSize()];
|
||||
|
||||
for (int bytesRead = resourceInputStream.read(buffer); bytesRead != -1; bytesRead = resourceInputStream.read(buffer)) {
|
||||
// using a buffer will trigger a flush() automatically in the OutputStream
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
out.flush();
|
||||
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io.support;
|
||||
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.geode.core.io.AbstractResourceWriter;
|
||||
import org.springframework.geode.core.io.ResourceDataAccessException;
|
||||
import org.springframework.geode.core.io.ResourceWriteException;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link AbstractResourceWriter} implementation that writes data of a {@link File} based {@link Resource}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.io.File
|
||||
* @see java.io.OutputStream
|
||||
* @see java.nio.file.Files
|
||||
* @see java.nio.file.OpenOption
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.geode.core.io.AbstractResourceWriter
|
||||
* @since 1.3.1
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class FileResourceWriter extends AbstractResourceWriter {
|
||||
|
||||
protected static final boolean DEFAULT_APPEND_TO_FILE = false;
|
||||
|
||||
protected static final int DEFAULT_BUFFER_SIZE = 16384;
|
||||
|
||||
private final ThreadLocal<Resource> resource = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
protected void doWrite(OutputStream resourceOutputStream, byte[] data) {
|
||||
|
||||
if (ResourceUtils.isNotEmpty(data)) {
|
||||
|
||||
int bufferSize = getBufferSize();
|
||||
int length = Math.min(bufferSize, data.length);
|
||||
int offset = 0;
|
||||
|
||||
try (OutputStream out = decorate(resourceOutputStream)) {
|
||||
|
||||
while (offset < data.length) {
|
||||
out.write(data, offset, length);
|
||||
offset += bufferSize;
|
||||
length = Math.min(bufferSize, data.length - offset);
|
||||
}
|
||||
|
||||
out.flush();
|
||||
}
|
||||
catch (IOException cause) {
|
||||
|
||||
String message = String.format("Failed to write data (%1$d byte(s)) to Resource using [%2$s]",
|
||||
data.length, getClass().getName());
|
||||
|
||||
throw new ResourceWriteException(message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
protected boolean isAbleToHandle(@Nullable Resource resource) {
|
||||
|
||||
if (super.isAbleToHandle(resource) && resource.isFile()) {
|
||||
this.resource.set(resource);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured {@link Integer#TYPE buffer size} used by this writer to chunk the data written to
|
||||
* the {@link File}.
|
||||
* <p>
|
||||
* Subclasses should override this method to tune the buffer size based on the context and requirements.
|
||||
*
|
||||
* @return the configured {@link Integer#TYPE buffer size}.
|
||||
*/
|
||||
protected int getBufferSize() {
|
||||
return DEFAULT_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured {@link OpenOption OpenOptions} used to configure the stream writing to the {@link File}.
|
||||
*
|
||||
* By default, the {@link File} will be {@link StandardOpenOption#CREATE created},
|
||||
* {@link StandardOpenOption#TRUNCATE_EXISTING truncated} and {@link StandardOpenOption#WRITE written} to.
|
||||
*
|
||||
* Subclasses should override this method to tune the {@link File} stream based on context and requirements.
|
||||
*
|
||||
* @return configured {@link OpenOption OpenOptions}.
|
||||
* @see java.nio.file.OpenOption
|
||||
*/
|
||||
protected OpenOption[] getOpenOptions() {
|
||||
|
||||
return ArrayUtils.asArray(
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING,
|
||||
StandardOpenOption.WRITE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Optional} reference to the target {@link Resource}.
|
||||
*
|
||||
* @return an {@link Optional} reference to the target {@link Resource}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
protected Optional<Resource> getResource() {
|
||||
return Optional.ofNullable(this.resource.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates the given {@link OutputStream} by adding buffering capabilities.
|
||||
*
|
||||
* @param outputStream {@link OutputStream} to decorate.
|
||||
* @return the decorated {@link OutputStream}.
|
||||
* @see #newFileOutputStream()
|
||||
* @see java.io.OutputStream
|
||||
*/
|
||||
protected @NonNull OutputStream decorate(@Nullable OutputStream outputStream) {
|
||||
|
||||
return outputStream instanceof BufferedOutputStream ? outputStream
|
||||
: outputStream != null ? new BufferedOutputStream(outputStream, getBufferSize())
|
||||
: newFileOutputStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to construct a new {@link File} based {@link OutputStream} from the {@literal target} {@link Resource}.
|
||||
*
|
||||
* By default, the constructed {@link OutputStream} is also buffered (e.g. {@link BufferedOutputStream}).
|
||||
*
|
||||
* @return a {@link OutputStream} writing to a {@link File} identified by the {@literal target} {@link Resource}.
|
||||
* @throws IllegalStateException if the {@literal target} {@link Resource} cannot be handled as a {@link File}.
|
||||
* @throws DataAccessResourceFailureException if the {@link OutputStream} could not be created.
|
||||
* @see java.io.BufferedOutputStream
|
||||
* @see java.io.OutputStream
|
||||
* @see #getBufferSize()
|
||||
* @see #getOpenOptions()
|
||||
* @see #getResource()
|
||||
*/
|
||||
protected OutputStream newFileOutputStream() {
|
||||
|
||||
return getResource()
|
||||
.filter(this::isAbleToHandle)
|
||||
.map(resource -> {
|
||||
try {
|
||||
|
||||
OutputStream fileOutputStream =
|
||||
Files.newOutputStream(resource.getFile().toPath(), getOpenOptions());
|
||||
|
||||
return new BufferedOutputStream(fileOutputStream, getBufferSize());
|
||||
}
|
||||
catch (IOException cause) {
|
||||
|
||||
String message = String.format("Failed to access the Resource [%s] as a file",
|
||||
resource.getDescription());
|
||||
|
||||
throw new ResourceDataAccessException(message, cause);
|
||||
}
|
||||
})
|
||||
.orElseThrow(() -> newIllegalStateException("Resource [%s] is not a file based resource",
|
||||
getResource().map(Resource::getDescription).orElse(null)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io.support;
|
||||
|
||||
import static org.springframework.geode.core.util.ObjectUtils.initialize;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.geode.core.io.ResourceNotFoundException;
|
||||
import org.springframework.geode.core.io.ResourceResolver;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link ResourceResolver} implementation using Spring's {@link ResourceLoader} to resolve
|
||||
* and load {@link Resource Resources}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.springframework.context.ResourceLoaderAware
|
||||
* @see org.springframework.core.io.ClassPathResource
|
||||
* @see org.springframework.core.io.DefaultResourceLoader
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.core.io.ResourceLoader
|
||||
* @see org.springframework.geode.core.io.ResourceResolver
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public class ResourceLoaderResourceResolver implements ResourceLoaderAware, ResourceResolver {
|
||||
|
||||
private final AtomicReference<ResourceLoader> resolvedResourceLoader = new AtomicReference<>(null);
|
||||
|
||||
/**
|
||||
* Gets an {@link Optional} {@link ClassLoader} used by the {@link ResourceLoader} to resolve and load
|
||||
* {@link Resource Resources} located on the {@literal classpath}.
|
||||
*
|
||||
* Returns the {@link ResourceLoader#getClassLoader() ClassLoader} from the configured {@link ResourceLoader},
|
||||
* if present. Otherwise, returns a {@link ClassLoader} determined by {@link ClassUtils#getDefaultClassLoader()},
|
||||
* which first tries to return the {@link Thread#getContextClassLoader()}, then {@link Class#getClassLoader()},
|
||||
* and finally, {@link ClassLoader#getSystemClassLoader()}.
|
||||
*
|
||||
* @return an {@link Optional} {@link ClassLoader} to resolve and load {@link Resource Resources}.
|
||||
* @see org.springframework.core.io.ResourceLoader#getClassLoader()
|
||||
* @see java.lang.ClassLoader
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
@Override
|
||||
public Optional<ClassLoader> getClassLoader() {
|
||||
|
||||
return Optional.ofNullable(Optional.ofNullable(this.resolvedResourceLoader.get())
|
||||
.map(ResourceLoader::getClassLoader)
|
||||
.orElseGet(ClassUtils::getDefaultClassLoader));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link ResourceLoader} used by this {@link ResourceResolver} 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.resolvedResourceLoader.set(resourceLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the configured {@link ResourceLoader} used to load {@link Resource Resources}.
|
||||
*
|
||||
* If a {@link ResourceLoader} was not explicitly configured, then a {@literal default} {@link ResourceLoader}
|
||||
* using a {@literal default} {@link ClassLoader} is provided.
|
||||
*
|
||||
* @return a reference to the configured {@link ResourceLoader}; never {@literal null}.
|
||||
* @see org.springframework.core.io.ResourceLoader
|
||||
* @see #newResourceLoader()
|
||||
*/
|
||||
protected @NonNull ResourceLoader getResourceLoader() {
|
||||
return this.resolvedResourceLoader.updateAndGet(resourceLoader ->
|
||||
initialize(resourceLoader, this::newResourceLoader));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new, {@literal default} instance of {@link ResourceLoader} to load {@link Resource Resources}.
|
||||
*
|
||||
* Specifically, creates a standalone {@link DefaultResourceLoader} initialized with a {@literal default}
|
||||
* {@link ClassLoader} as determined by {@link #getClassLoader()}.
|
||||
*
|
||||
* @return a new, {@literal default} instance of {@link ResourceLoader}.
|
||||
* @see org.springframework.core.io.ResourceLoader
|
||||
*/
|
||||
protected @NonNull ResourceLoader newResourceLoader() {
|
||||
|
||||
return getClassLoader()
|
||||
.map(DefaultResourceLoader::new)
|
||||
.orElseGet(DefaultResourceLoader::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Resource} handle at the given {@link String location}.
|
||||
*
|
||||
* By default, a {@link ClassPathResource} is constructed.
|
||||
*
|
||||
* @param location {@link String location} of the new {@link Resource}; must not be {@literal null}.
|
||||
* @return a new {@link Resource} handle at the given {@link String location}.
|
||||
* @throws IllegalArgumentException if {@link String location} is not specified.
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
protected @NonNull Resource newResource(@NonNull String location) {
|
||||
|
||||
Assert.hasText(location, () ->
|
||||
String.format("The location [%s] of the Resource must be specified", location));
|
||||
|
||||
return new ClassPathResource(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the {@link Resource} is a {@literal qualified} {@link Resource}.
|
||||
*
|
||||
* Qualifications are determined by the application Requirements and Use Case (UC) at time of resolution.
|
||||
* For example, it maybe that the {@link Resource} must {@link Resource#exists() exist} to qualify, or that
|
||||
* the {@link Resource} must have a valid protocol, path and name.
|
||||
*
|
||||
* This default implementation requires the target {@link Resource} to not be {@literal null}.
|
||||
*
|
||||
* @param resource {@link Resource} to qualify.
|
||||
* @return a boolean value indicating whether the {@link Resource} is qualified.
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
protected boolean isQualified(@Nullable Resource resource) {
|
||||
return resource != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to perform when the {@link Resource} identified at the specified {@link String location} is missing,
|
||||
* or was not {@link #isQualified(Resource) qualified}.
|
||||
*
|
||||
* @param resource missing {@link Resource}.
|
||||
* @param location {@link String} containing the location identifying the missing {@link Resource}.
|
||||
* @throws ResourceNotFoundException if the {@link Resource} cannot be found at the specified {@link String location}.
|
||||
* @return a different {@link Resource}, possibly. Alternatively, this method may throw
|
||||
* a {@link ResourceNotFoundException}.
|
||||
* @see #isQualified(Resource)
|
||||
*/
|
||||
protected @Nullable Resource onMissingResource(@Nullable Resource resource, @NonNull String location) {
|
||||
throw new ResourceNotFoundException(String.format("Failed to resolve Resource [%1$s] at location [%2$s]",
|
||||
ResourceUtils.nullSafeGetDescription(resource), location));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used by subclasses to process the loaded {@link Resource} as determined by
|
||||
* the {@link #getResourceLoader() ResourceLoader}.
|
||||
*
|
||||
* @param resource {@link Resource} to post-process.
|
||||
* @return the {@link Resource}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
protected Resource postProcess(Resource resource) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to resolve a {@link Resource} at the given {@link String location} using a Spring {@link ResourceLoader},
|
||||
* such as a Spring {@link ApplicationContext}.
|
||||
*
|
||||
* The targeted, identified {@link Resource} can be further {@link #isQualified(Resource) qualified} by subclasses
|
||||
* based on application requirements or use case (UC).
|
||||
*
|
||||
* In the event that a {@link Resource} cannot be identified at the given {@link String location}, then applications
|
||||
* have 1 last opportunity to handle the missing {@link Resource} event, and either return a different or default
|
||||
* {@link Resource} or throw a {@link ResourceNotFoundException}.
|
||||
*
|
||||
* @param location {@link String location} identifying the {@link Resource} to resolve;
|
||||
* must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link String location} is not specified.
|
||||
* @return an {@link Optional} {@link Resource} handle for the given {@link String location}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see #isQualified(Resource)
|
||||
* @see #onMissingResource(Resource, String)
|
||||
* @see #postProcess(Resource)
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
@Override
|
||||
public Optional<Resource> resolve(@NonNull String location) {
|
||||
|
||||
Assert.hasText(location, () ->
|
||||
String.format("The location [%s] of the Resource to resolve must be specified", location));
|
||||
|
||||
Resource resource = getResourceLoader().getResource(location);
|
||||
|
||||
resource = postProcess(resource);
|
||||
|
||||
Resource resolvedResource = isQualified(resource)
|
||||
? resource
|
||||
: onMissingResource(resource, location);
|
||||
|
||||
return Optional.ofNullable(resolvedResource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io.support;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An enumeration of {@link Resource} {@link String prefixes} recognized by the Spring Framework.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public enum ResourcePrefix {
|
||||
|
||||
CLASSPATH_URL_PREFIX(ResourceLoader.CLASSPATH_URL_PREFIX),
|
||||
FILESYSTEM_URL_PREFIX("file:"),
|
||||
HTTP_URL_PREFIX("http:");
|
||||
|
||||
public static final String RESOURCE_PATH_SEPARATOR = "/";
|
||||
|
||||
/**
|
||||
* Factory method used to try and find a {@link ResourcePrefix} enumerated value
|
||||
* matching the given {@link String prefix}.
|
||||
*
|
||||
* @param prefix {@link String} with the name of the prefix.
|
||||
* @return a {@link ResourcePrefix} matching the given {@link String prefix} by name, or {@literal null}
|
||||
* if the {@link String prefix} does not match any {@link ResourcePrefix} enumerated value.
|
||||
*/
|
||||
public static @Nullable ResourcePrefix from(@Nullable String prefix) {
|
||||
|
||||
if (StringUtils.hasText(prefix)) {
|
||||
|
||||
prefix = prefix.trim().toLowerCase();
|
||||
|
||||
for (ResourcePrefix resourcePrefix : values()) {
|
||||
if (resourcePrefix.toString().equals(prefix)) {
|
||||
return resourcePrefix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private final String prefix;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ResourcePrefix} initialized with the named {@link String prefix}.
|
||||
*
|
||||
* @param prefix {@link String name} of the prefix.
|
||||
* @throws IllegalArgumentException if the {@link String prefix} is not specified.
|
||||
*/
|
||||
ResourcePrefix(String prefix) {
|
||||
Assert.hasText(prefix, "Resource prefix must be specified");
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the network protocol that this {@link ResourcePrefix} represents.
|
||||
*
|
||||
* @return the network protocol that this {@link ResourcePrefix} represents.
|
||||
*/
|
||||
public String getProtocol() {
|
||||
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (char character : this.prefix.toCharArray()) {
|
||||
if (Character.isAlphabetic(character)) {
|
||||
buffer.append(character);
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link String pattern} or template used to construct a {@link java.net.URL} prefix
|
||||
* from this {@link ResourcePrefix}.
|
||||
*
|
||||
* @return the {@link String pattern} or template used to construct a {@link java.net.URL} prefix
|
||||
* from this {@link ResourcePrefix}.
|
||||
* @see #toUrlPrefix()
|
||||
*/
|
||||
protected String getUrlPrefixPattern() {
|
||||
return this.equals(CLASSPATH_URL_PREFIX) ? "%s" : "%1$s%2$s%2$s";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link ResourcePrefix} as a prefix use in a {@link java.net.URL}.
|
||||
*
|
||||
* @return the {@link java.net.URL} prefix.
|
||||
* @see #getUrlPrefixPattern()
|
||||
*/
|
||||
public String toUrlPrefix() {
|
||||
return String.format(getUrlPrefixPattern(), toString(), RESOURCE_PATH_SEPARATOR);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io.support;
|
||||
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.WritableResource;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Abstract utility class containing functionality to work with {@link Resource Resources}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.core.io.WritableResource
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public abstract class ResourceUtils {
|
||||
|
||||
/**
|
||||
* Returns the {@link Resource} as a {@link WritableResource} if possible.
|
||||
*
|
||||
* This method makes a best effort to determine whether the target {@link Resource} is actually {@literal writable}.
|
||||
* Even still, it may be possible that a write to the target {@link Resource} will fail.
|
||||
*
|
||||
* The {@link Resource} is {@literal writable} if the {@link Resource} is an instance of {@link WritableResource}
|
||||
* and {@link WritableResource#isWritable()} returns {@literal true}.
|
||||
*
|
||||
* @param resource {@link Resource} to cast to a {@link WritableResource}.
|
||||
* @return a {@link WritableResource} from the target {@link Resource} if possible; never {@literal null}.
|
||||
* @throws IllegalStateException if the target {@link Resource} is not {@literal writable}.
|
||||
* @see org.springframework.core.io.WritableResource
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
public static @NonNull WritableResource asStrictlyWritableResource(@Nullable Resource resource) {
|
||||
|
||||
return Optional.ofNullable(resource)
|
||||
.filter(WritableResource.class::isInstance)
|
||||
.map(WritableResource.class::cast)
|
||||
.filter(WritableResource::isWritable)
|
||||
.orElseThrow(() -> newIllegalStateException("Resource [%s] is not writable",
|
||||
ResourceUtils.nullSafeGetDescription(resource)));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Optional Optionally} return the {@link Resource} as a {@link WritableResource}.
|
||||
*
|
||||
* The {@link Resource} must be an instance of {@link WritableResource}.
|
||||
*
|
||||
* @param resource {@link Resource} to cast to a {@link WritableResource}.
|
||||
* @return the {@link Resource} as a {@link WritableResource} if the {@link Resource}
|
||||
* is an instance of {@link WritableResource}, otherwise returns {@link Optional#empty()}.
|
||||
* @see org.springframework.core.io.WritableResource
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
public static Optional<WritableResource> asWritableResource(@Nullable Resource resource) {
|
||||
|
||||
return Optional.ofNullable(resource)
|
||||
.filter(WritableResource.class::isInstance)
|
||||
.map(WritableResource.class::cast);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given byte array is {@literal null} or {@literal empty}.
|
||||
*
|
||||
* @param array byte array to evaluate.
|
||||
* @return a boolean value indicating whether the given byte array is {@literal null} or {@literal empty}.
|
||||
*/
|
||||
public static boolean isNotEmpty(@Nullable byte[] array) {
|
||||
return array != null && array.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe operation to determine whether the given {@link Resource} is readable.
|
||||
*
|
||||
* @param resource {@link Resource} to evaluate.
|
||||
* @return a boolean value indicating whether the given {@link Resource} is readable.
|
||||
* @see org.springframework.core.io.Resource#isReadable()
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
public static boolean isReadable(@Nullable Resource resource) {
|
||||
return resource != null && resource.isReadable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe operation to determine whether the given {@link Resource} is writable.
|
||||
*
|
||||
* @param resource {@link Resource} to evaluate.
|
||||
* @return a boolean value indicating whether the given {@link Resource} is writable.
|
||||
* @see org.springframework.core.io.WritableResource#isWritable()
|
||||
* @see org.springframework.core.io.WritableResource
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
public static boolean isWritable(@Nullable Resource resource) {
|
||||
return resource instanceof WritableResource && ((WritableResource) resource).isWritable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to get the {@link Resource#getDescription() description} of the given {@link Resource}.
|
||||
*
|
||||
* @param resource {@link Resource} to describe.
|
||||
* @return a {@link Resource#getDescription() description} of the {@link Resource}, or {@literal null}
|
||||
* if the {@link Resource} handle is {@literal null}.
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
public static @Nullable String nullSafeGetDescription(@Nullable Resource resource) {
|
||||
return resource != null ? resource.getDescription() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.io.support;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.geode.core.io.ResourceResolver;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ResourceResolver} that returns a single (i.e. {@literal Singleton}) {@link Resource}
|
||||
* regardless of {@link String location}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.geode.core.io.ResourceResolver
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public class SingleResourceResolver implements ResourceResolver {
|
||||
|
||||
@Nullable
|
||||
private final Resource resource;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link SingleResourceResolver} initialized with the given {@link Resource}.
|
||||
*
|
||||
* @param resource the {@literal single} {@link Resource} consistently resolved by this resolver.
|
||||
* @see org.springframework.core.io.Resource
|
||||
*/
|
||||
public SingleResourceResolver(@Nullable Resource resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Optional<Resource> resolve(@NonNull String location) {
|
||||
return Optional.ofNullable(this.resource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.util;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Abstract utility class used to process {@link Object managed object} {@literal aware} {@link Object objects},
|
||||
* such as {@link ApplicationContextAware} {@link Object objects} in a Spring context.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.function.Consumer
|
||||
* @see org.springframework.beans.factory.BeanClassLoaderAware
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware
|
||||
* @see org.springframework.beans.factory.BeanNameAware
|
||||
* @see org.springframework.context.ApplicationContextAware
|
||||
* @see org.springframework.context.ApplicationEventPublisherAware
|
||||
* @see org.springframework.context.EnvironmentAware
|
||||
* @see org.springframework.context.ResourceLoaderAware
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public abstract class ObjectAwareUtils {
|
||||
|
||||
public static final Consumer<Object> NO_OP = target -> { };
|
||||
|
||||
/**
|
||||
* Returns a {@link Consumer} capable of initializing an {@link ApplicationContextAware} {@link Object}
|
||||
* with the given {@link ApplicationContext}.
|
||||
*
|
||||
* The {@link ApplicationContextAware#setApplicationContext(ApplicationContext)} method is only called on
|
||||
* the {@link ApplicationContextAware} {@link Object} if the {@link ApplicationContext} is not {@literal null}.
|
||||
*
|
||||
* @param applicationContext {@link ApplicationContext} set on the {@link ApplicationContextAware} {@link Object}
|
||||
* by the {@link Consumer}.
|
||||
* @return a {@link Consumer} capable of initializing an {@link ApplicationContextAware} {@link Object}
|
||||
* with the given {@link ApplicationContext}; never {@literal null}.
|
||||
* @see org.springframework.context.ApplicationContextAware
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see java.util.function.Consumer
|
||||
*/
|
||||
public static @NonNull Consumer<Object> applicationContextAwareObjectInitializer(
|
||||
@Nullable ApplicationContext applicationContext) {
|
||||
|
||||
return applicationContext == null ? NO_OP : target -> {
|
||||
if (target instanceof ApplicationContextAware) {
|
||||
((ApplicationContextAware) target).setApplicationContext(applicationContext);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Consumer} capable of initializing an {@link ApplicationEventPublisherAware} {@link Object}
|
||||
* with the given {@link ApplicationEventPublisher}.
|
||||
*
|
||||
* The {@link ApplicationEventPublisherAware#setApplicationEventPublisher(ApplicationEventPublisher)} method is only
|
||||
* called on the {@link ApplicationEventPublisherAware} {@link Object} if the {@link ApplicationEventPublisherAware}
|
||||
* is not {@literal null}.
|
||||
*
|
||||
* @param applicationEventPublisher {@link ApplicationEventPublisher} set on
|
||||
* the {@link ApplicationEventPublisherAware} {@link Object} by the {@link Consumer}.
|
||||
* @return a {@link Consumer} capable of initializing an {@link ApplicationEventPublisherAware} {@link Object}
|
||||
* with the given {@link ApplicationEventPublisher}; never {@literal null}.
|
||||
* @see org.springframework.context.ApplicationEventPublisherAware
|
||||
* @see org.springframework.context.ApplicationEventPublisher
|
||||
* @see java.util.function.Consumer
|
||||
*/
|
||||
public static @NonNull Consumer<Object> applicationEventPublisherAwareObjectInitializer(
|
||||
@Nullable ApplicationEventPublisher applicationEventPublisher) {
|
||||
|
||||
return applicationEventPublisher == null ? NO_OP : target -> {
|
||||
if (target instanceof ApplicationEventPublisherAware) {
|
||||
((ApplicationEventPublisherAware) target).setApplicationEventPublisher(applicationEventPublisher);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Consumer} capable of initializing an {@link BeanClassLoaderAware} {@link Object}
|
||||
* with the given bean {@link ClassLoader}.
|
||||
*
|
||||
* The {@link BeanClassLoaderAware#setBeanClassLoader(ClassLoader)} method is only called on
|
||||
* the {@link BeanClassLoaderAware} {@link Object} if the {@link ClassLoader} is not {@literal null}.
|
||||
*
|
||||
* @param beanClassLoader {@link ClassLoader} set on the {@link BeanClassLoaderAware} {@link Object}
|
||||
* by the {@link Consumer}.
|
||||
* @return a {@link Consumer} capable of initializing an {@link BeanClassLoaderAware} {@link Object}
|
||||
* with the given bean {@link ClassLoader}; never {@literal null}.
|
||||
* @see org.springframework.beans.factory.BeanClassLoaderAware
|
||||
* @see java.util.function.Consumer
|
||||
* @see java.lang.ClassLoader
|
||||
*/
|
||||
public static @NonNull Consumer<Object> beanClassLoaderAwareObjectInitializer(@Nullable ClassLoader beanClassLoader) {
|
||||
|
||||
return beanClassLoader == null ? NO_OP : target -> {
|
||||
if (target instanceof BeanClassLoaderAware) {
|
||||
((BeanClassLoaderAware) target).setBeanClassLoader(beanClassLoader);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Consumer} capable of initializing an {@link BeanFactoryAware} {@link Object}
|
||||
* with the given {@link BeanFactory}.
|
||||
*
|
||||
* The {@link BeanFactoryAware#setBeanFactory(BeanFactory)} method is only called on the {@link BeanFactoryAware}
|
||||
* {@link Object} if the {@link BeanFactory} is not {@literal null}.
|
||||
*
|
||||
* @param beanFactory {@link BeanFactory} set on the {@link BeanFactoryAware} {@link Object} by the {@link Consumer}.
|
||||
* @return a {@link Consumer} capable of initializing an {@link BeanFactoryAware} {@link Object}
|
||||
* with the given {@link BeanFactory}; never {@literal null}.
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware
|
||||
* @see org.springframework.beans.factory.BeanFactory
|
||||
* @see java.util.function.Consumer
|
||||
*/
|
||||
public static @NonNull Consumer<Object> beanFactoryAwareObjectInitializer(@Nullable BeanFactory beanFactory) {
|
||||
|
||||
return beanFactory == null ? NO_OP : target -> {
|
||||
if (target instanceof BeanFactoryAware) {
|
||||
((BeanFactoryAware) target).setBeanFactory(beanFactory);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Consumer} capable of initializing an {@link BeanNameAware} {@link Object}
|
||||
* with the given {@link String bean name}.
|
||||
*
|
||||
* The {@link BeanNameAware#setBeanName(String)} method is only called on the {@link BeanNameAware} {@link Object}
|
||||
* if the {@link String bean name} is not {@literal null}.
|
||||
*
|
||||
* @param beanName {@link String} containing the bean name to set on the {@link BeanNameAware} {@link Object}
|
||||
* by the {@link Consumer}.
|
||||
* @return a {@link Consumer} capable of initializing an {@link BeanNameAware} {@link Object}
|
||||
* with the given {@link String bean name}; never {@literal null}.
|
||||
* @see org.springframework.beans.factory.BeanNameAware
|
||||
* @see java.util.function.Consumer
|
||||
*/
|
||||
public static @NonNull Consumer<Object> beanNameAwareObjectInitializer(@Nullable String beanName) {
|
||||
|
||||
return hasNoText(beanName) ? NO_OP : target -> {
|
||||
if (target instanceof BeanNameAware) {
|
||||
((BeanNameAware) target).setBeanName(beanName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Consumer} capable of initializing an {@link EnvironmentAware} {@link Object}
|
||||
* with the given {@link Environment}.
|
||||
*
|
||||
* The {@link EnvironmentAware#setEnvironment(Environment)} method is only called on the {@link EnvironmentAware}
|
||||
* {@link Object} if the {@link Environment} is not {@literal null}.
|
||||
*
|
||||
* @param environment {@link Environment} set on the {@link EnvironmentAware} {@link Object} by the {@link Consumer}.
|
||||
* @return a {@link Consumer} capable of initializing an {@link EnvironmentAware} {@link Object}
|
||||
* with the given {@link Environment}; never {@literal null}.
|
||||
* @see org.springframework.context.EnvironmentAware
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see java.util.function.Consumer
|
||||
*/
|
||||
public static @NonNull Consumer<Object> environmentAwareObjectInitializer(@Nullable Environment environment) {
|
||||
|
||||
return environment == null ? NO_OP : target -> {
|
||||
if (target instanceof EnvironmentAware) {
|
||||
((EnvironmentAware) target).setEnvironment(environment);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Consumer} capable of initializing an {@link ResourceLoaderAware} {@link Object}
|
||||
* with the given {@link ResourceLoader}.
|
||||
*
|
||||
* The {@link ResourceLoaderAware#setResourceLoader(ResourceLoader)} method is only called on
|
||||
* the {@link ResourceLoaderAware} {@link Object} if the {@link ResourceLoader} is not {@literal null}.
|
||||
*
|
||||
* @param resourceLoader {@link ResourceLoader} set on the {@link ResourceLoaderAware} {@link Object}
|
||||
* by the {@link Consumer}.
|
||||
* @return a {@link Consumer} capable of initializing an {@link ResourceLoaderAware} {@link Object}
|
||||
* with the given {@link ResourceLoader}; never {@literal null}.
|
||||
* @see org.springframework.context.ResourceLoaderAware
|
||||
* @see org.springframework.core.io.ResourceLoader
|
||||
* @see java.util.function.Consumer
|
||||
*/
|
||||
public static @NonNull Consumer<Object> resourceLoaderAwareObjectInitializer(@Nullable ResourceLoader resourceLoader) {
|
||||
|
||||
return resourceLoader == null ? NO_OP : target -> {
|
||||
if (target instanceof ResourceLoaderAware) {
|
||||
((ResourceLoaderAware) target).setResourceLoader(resourceLoader);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean hasNoText(@Nullable String value) {
|
||||
return !StringUtils.hasText(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.util;
|
||||
|
||||
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.geode.pdx.PdxInstance;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ObjectUtils} class is an abstract utility class with operations for {@link Object objects}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Object
|
||||
* @see java.lang.reflect.Constructor
|
||||
* @see java.lang.reflect.Field
|
||||
* @see java.lang.reflect.Method
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class ObjectUtils extends org.springframework.util.ObjectUtils {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ObjectUtils.class);
|
||||
|
||||
/**
|
||||
* Tries to cast the given source {@link Object} into an instance of the given {@link Class} type.
|
||||
*
|
||||
* This method is cable of handling Apache Geode {@link PdxInstance} types.
|
||||
*
|
||||
* @param <T> desired {@link Class type} of the source {@link Object}.
|
||||
* @param source {@link Object} to evaluate.
|
||||
* @param type desired target {@link Class} type; must not be {@literal null}.
|
||||
* @return the source {@link Object} cast to an instance of the given {@link Class} type.
|
||||
* @throws IllegalArgumentException if the source {@link Object} is not an instance of
|
||||
* the given {@link Class} type or the {@link Class} type is {@literal null}.
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see java.lang.Class
|
||||
* @see java.lang.Object
|
||||
*/
|
||||
public static @Nullable <T> T asType(@Nullable Object source, @NonNull Class<T> type) {
|
||||
|
||||
Assert.notNull(type, "Class type must not be null");
|
||||
|
||||
Object target = source instanceof PdxInstance
|
||||
? ((PdxInstance) source).getObject()
|
||||
: source;
|
||||
|
||||
return target == null ? null
|
||||
: Optional.of(target)
|
||||
.filter(type::isInstance)
|
||||
.map(type::cast)
|
||||
.orElseThrow(() -> newIllegalArgumentException("Object [%s] is not an instance of type [%s]",
|
||||
nullSafeClassName(target), type.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely executes the given {@link ExceptionThrowingOperation} handling any checked {@link Exception}
|
||||
* thrown during the normal execution of the operation by rethrowing an {@link IllegalStateException}
|
||||
* wrapping the original checked {@link Exception}.
|
||||
*
|
||||
* @param <T> {@link Class type} of {@link Object value} returned from the execution of the operation.
|
||||
* @param operation {@link ExceptionThrowingOperation} to execute; must not be {@literal null}.
|
||||
* @return the result of the {@link ExceptionThrowingOperation}.
|
||||
* @throws IllegalStateException wrapping any checked {@link Exception} thrown by the operation.
|
||||
* @see org.springframework.geode.core.util.ObjectUtils.ExceptionThrowingOperation
|
||||
* @see #doOperationSafely(ExceptionThrowingOperation, Object)
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> T doOperationSafely(@NonNull ExceptionThrowingOperation<T> operation) {
|
||||
return doOperationSafely(operation, (T) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely executes the given {@link ExceptionThrowingOperation} handling any checked {@link Exception}
|
||||
* thrown during the normal execution of the operation by returning the given {@link Object default value}
|
||||
* or throwing an {@link IllegalStateException} if the {@link Object default value} is {@literal null}.
|
||||
*
|
||||
* @param <T> {@link Class type} of {@link Object value} returned from the execution of the operation
|
||||
* as well as the {@link Class type} of the {@link Object default value}.
|
||||
* @param operation {@link ExceptionThrowingOperation} to execute; must not be {@literal null}.
|
||||
* @param defaultValue {@link Object value} to return if the execution of the operation
|
||||
* results in a checked {@link Exception}.
|
||||
* @return the result of the {@link ExceptionThrowingOperation}, returning the {@link Object default value}
|
||||
* if the execution of the operation throws a checked {@link Exception}
|
||||
* @throws IllegalStateException wrapping any checked {@link Exception} thrown by the operation
|
||||
* when the {@link Object default value} is {@literal null}.
|
||||
* @see org.springframework.geode.core.util.ObjectUtils.ExceptionThrowingOperation
|
||||
* @see #doOperationSafely(ExceptionThrowingOperation, Supplier)
|
||||
* @see #returnValueThrowOnNull(Object, RuntimeException)
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> T doOperationSafely(@NonNull ExceptionThrowingOperation<T> operation, @NonNull T defaultValue) {
|
||||
|
||||
Supplier<T> valueSupplier = () -> defaultValue;
|
||||
|
||||
return doOperationSafely(operation, valueSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely executes the given {@link ExceptionThrowingOperation} handling any checked {@link Exception}
|
||||
* thrown during the normal execution of the operation by returning a {@link Object default value}
|
||||
* supplied by the given {@link Supplier}, or throws an {@link IllegalStateException}
|
||||
* if the {@link Supplier supplied value} is {@literal null}.
|
||||
*
|
||||
* @param <T> {@link Class type} of {@link Object value} returned from the execution of the operation
|
||||
* as well as the {@link Class type} of the {@link Object default value}.
|
||||
* @param operation {@link ExceptionThrowingOperation} to execute; must not be {@literal null}.
|
||||
* @param valueSupplier {@link Supplier} of the {@link Object value} to return if the execution of the operation
|
||||
* results in a checked {@link Exception}; must not be {@literal null}.
|
||||
* @return the result of the {@link ExceptionThrowingOperation}, returning the {@link Supplier supplied value}
|
||||
* if the execution of the operation throws a checked {@link Exception}
|
||||
* @throws IllegalStateException wrapping any checked {@link Exception} thrown by the operation
|
||||
* when the {@link Supplier supplied value} is {@literal null}.
|
||||
* @see org.springframework.geode.core.util.ObjectUtils.ExceptionThrowingOperation
|
||||
* @see #doOperationSafely(ExceptionThrowingOperation, Function)
|
||||
* @see #returnValueThrowOnNull(Object, RuntimeException)
|
||||
* @see java.util.function.Supplier
|
||||
*/
|
||||
public static <T> T doOperationSafely(@NonNull ExceptionThrowingOperation<T> operation,
|
||||
@NonNull Supplier<T> valueSupplier) {
|
||||
|
||||
Function<Throwable, T> exceptionHandlingFunction = cause ->
|
||||
returnValueThrowOnNull(valueSupplier.get(), newIllegalStateException(cause, "Failed to execute operation"));
|
||||
|
||||
return doOperationSafely(operation, exceptionHandlingFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely executes the given {@link ExceptionThrowingOperation} handling any checked {@link Exception}
|
||||
* thrown during the normal execution of the operation by invoking the provided {@link Exception}
|
||||
* handling {@link Function}.
|
||||
*
|
||||
* @param <T> {@link Class type} of {@link Object value} returned from the execution of the operation.
|
||||
* @param operation {@link ExceptionThrowingOperation} to execute; must not be {@literal null}.
|
||||
* @param exceptionHandlingFunction {@link Function} used to handle any checked {@link Exception}
|
||||
* thrown by {@link ExceptionThrowingOperation}; must not be {@literal null}.
|
||||
* @return the result of the {@link ExceptionThrowingOperation}.
|
||||
* @see org.springframework.geode.core.util.ObjectUtils.ExceptionThrowingOperation
|
||||
* @see java.util.function.Function
|
||||
* @see java.lang.Throwable
|
||||
*/
|
||||
public static <T> T doOperationSafely(@NonNull ExceptionThrowingOperation<T> operation,
|
||||
@NonNull Function<Throwable, T> exceptionHandlingFunction) {
|
||||
|
||||
try {
|
||||
return operation.run();
|
||||
}
|
||||
catch (Exception cause) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Failed to execute operation [%s]", operation), cause);
|
||||
}
|
||||
|
||||
return exceptionHandlingFunction.apply(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a {@link Method} with the given {@link Method#getName() name} on {@link Class type} which can be invoked
|
||||
* with the given {@link Object arguments}.
|
||||
*
|
||||
* @param type {@link Class} type to evaluate for the {@link Method}.
|
||||
* @param methodName {@link String} containing the name of the {@link Method} to find.
|
||||
* @param args {@link Object array of arguments} used when invoking the method.
|
||||
* @return an {@link Optional} {@link Method} on {@link Class type} potentially matching
|
||||
* the {@link Object arguments} of the invocation.
|
||||
* @see java.lang.Class
|
||||
* @see java.lang.reflect.Method
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
public static Optional<Method> findMethod(@NonNull Class<?> type, @NonNull String methodName, Object... args) {
|
||||
|
||||
return Arrays.stream(nullSafeArray(type.getDeclaredMethods(), Method.class))
|
||||
.filter(methodNameMatchesPredicate(methodName))
|
||||
.filter(argumentsMatchParameterTypesPredicate(args))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private static Predicate<Method> argumentsMatchParameterTypesPredicate(Object... args) {
|
||||
|
||||
return method -> {
|
||||
|
||||
Class<?>[] parameterTypes = nullSafeArray(method.getParameterTypes(), Class.class);
|
||||
|
||||
Object[] arguments = nullSafeArray(args, Object.class);
|
||||
|
||||
if (arguments.length != parameterTypes.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < parameterTypes.length; index++) {
|
||||
|
||||
Object argument = arguments[index];
|
||||
|
||||
if (argument != null && !parameterTypes[index].isInstance(argument)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
private static Predicate<Method> methodNameMatchesPredicate(String methodName) {
|
||||
return method -> method.getName().equals(methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Object value} of the given {@link String named} {@link Field} on the given {@link Object}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the {@link Field Field's} value.
|
||||
* @param obj {@link Object} containing the {@link String named} {@link Field}.
|
||||
* @param fieldName {@link String} containing the name of the {@link Field}.
|
||||
* @return the {@link Object value} of the {@link String named} {@link Field} on the given {@link Object}.
|
||||
* @throws IllegalArgumentException if {@link Object} is {@literal null}, the {@link String named} {@link Field}
|
||||
* is not specified or the given {@link Object} contains no {@link Field} with the given {@link String name}.
|
||||
* @see #get(Object, Field)
|
||||
* @see java.lang.Object
|
||||
*/
|
||||
public static <T> T get(Object obj, String fieldName) {
|
||||
|
||||
Assert.notNull(obj, "Object is required");
|
||||
Assert.hasText(fieldName, String.format("Field name [%s] is required", fieldName));
|
||||
|
||||
Field field = ReflectionUtils.findField(obj.getClass(), fieldName);
|
||||
|
||||
if (field != null) {
|
||||
|
||||
field = makeAccessible(field);
|
||||
|
||||
return get(obj, field);
|
||||
}
|
||||
|
||||
throw newIllegalArgumentException("No field with name [%s] exists on object of type [%s]",
|
||||
fieldName, ObjectUtils.nullSafeClassName(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Object value} of the given {@link Field} on the given {@link Object}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the {@link Field Field's} value.
|
||||
* @param obj {@link Object} containing the {@link Field}.
|
||||
* @param field {@link Field} of the given {@link Object}.
|
||||
* @return the {@link Object value} of the {@link Field} on the given {@link Object}.
|
||||
* @throws IllegalArgumentException if {@link Object} or {@link Field} is {@literal null}.
|
||||
* @see java.lang.reflect.Field
|
||||
* @see java.lang.Object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T get(Object obj, Field field) {
|
||||
|
||||
Assert.notNull(obj, "Object is required");
|
||||
Assert.notNull(field, "Field is required");
|
||||
|
||||
return doOperationSafely(() -> (T) field.get(obj), (T) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* An initialization operator used to evalutate a given {@link Object target} and conditionally
|
||||
* {@link Supplier supply} a new value if the {@link Object target} is {@literal null}.
|
||||
*
|
||||
* The {@code initialize} operator simplifies a common initialization safety pattern that appears in code as:
|
||||
*
|
||||
* <code>
|
||||
* target = target != null ? target : new Target();
|
||||
* </code>
|
||||
*
|
||||
* While the expression uses Java's ternary operator, users could very well use a if-then-else statement instead.
|
||||
* Either way, since Java is {@literal call-by-value} then the above statement and expression can be replaced with:
|
||||
*
|
||||
* <code>
|
||||
* target = initialize(target, Target::new);
|
||||
* </code>
|
||||
*
|
||||
* @param <T> {@link Class type} of the {@link Object target}.
|
||||
* @param target {@link Object} to evaluate and initialize; must not be {@literal null}.
|
||||
* @param supplier {@link Supplier} used to initialize the {@link Object target} on return.
|
||||
* @return the existing {@link Object target} if not {@literal null}, otherwise invoke the {@link Supplier}
|
||||
* to supply a new instance of {@code T}.
|
||||
*/
|
||||
public static <T> T initialize(@Nullable T target, @NonNull Supplier<T> supplier) {
|
||||
return target != null ? target : supplier.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a {@link Method} on an {@link Object} with the given {@link String name}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the {@link Method} return value.
|
||||
* @param obj {@link Object} on which to invoke the {@link Method}.
|
||||
* @param methodName {@link String} containing the name of the {@link Method} to invoke on {@link Object}.
|
||||
* @return the return value of the invoked {@link Method} on {@link Object}.
|
||||
* @throws IllegalArgumentException if no {@link Method} with {@link String name} could be found on {@link Object}.
|
||||
* @see java.lang.reflect.Method
|
||||
* @see java.lang.Object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T invoke(Object obj, String methodName) {
|
||||
|
||||
return (T) Optional.ofNullable(obj)
|
||||
.map(Object::getClass)
|
||||
.map(type -> ReflectionUtils.findMethod(type, methodName))
|
||||
.map(ObjectUtils::makeAccessible)
|
||||
.map(method -> ReflectionUtils.invokeMethod(method, obj))
|
||||
.orElseThrow(() -> newIllegalArgumentException("Method [%1$s] on Object of type [%2$s] not found",
|
||||
methodName, org.springframework.util.ObjectUtils.nullSafeClassName(obj)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the {@link Constructor} accessible.
|
||||
*
|
||||
* @param constructor {@link Constructor} to make accessible; must not be {@literal null}.
|
||||
* @return the given {@link Constructor}.
|
||||
* @see java.lang.reflect.Constructor
|
||||
*/
|
||||
public static Constructor<?> makeAccessible(@NonNull Constructor<?> constructor) {
|
||||
|
||||
ReflectionUtils.makeAccessible(constructor);
|
||||
|
||||
return constructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the {@link Field} accessible.
|
||||
*
|
||||
* @param field {@link Field} to make accessible; must not be {@literal null}.
|
||||
* @return the given {@link Field}.
|
||||
* @see java.lang.reflect.Field
|
||||
*/
|
||||
public static Field makeAccessible(Field field) {
|
||||
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the {@link Method} accessible.
|
||||
*
|
||||
* @param method {@link Method} to make accessible; must not be {@literal null}.
|
||||
* @return the given {@link Method}.
|
||||
* @see java.lang.reflect.Method
|
||||
*/
|
||||
public static Method makeAccessible(Method method) {
|
||||
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given {@link Object value} or throws an {@link IllegalArgumentException}
|
||||
* if {@link Object value} is {@literal null}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the {@link Object value}.
|
||||
* @param value {@link Object} to return.
|
||||
* @return the {@link Object value} or throw an {@link IllegalArgumentException}
|
||||
* if {@link Object value} is {@literal null}.
|
||||
* @see #returnValueThrowOnNull(Object, RuntimeException)
|
||||
*/
|
||||
public static <T> T returnValueThrowOnNull(T value) {
|
||||
return returnValueThrowOnNull(value, newIllegalArgumentException("Value must not be null"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given {@link Object value} or throws the given {@link RuntimeException}
|
||||
* if {@link Object value} is {@literal null}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the {@link Object value}.
|
||||
* @param value {@link Object} to return.
|
||||
* @param exception {@link RuntimeException} to throw if {@link Object value} is {@literal null}.
|
||||
* @return the {@link Object value} or throw the given {@link RuntimeException}
|
||||
* if {@link Object value} is {@literal null}.
|
||||
*/
|
||||
public static <T> T returnValueThrowOnNull(T value, RuntimeException exception) {
|
||||
|
||||
if (value == null) {
|
||||
throw exception;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link Object invocation target} for the given {@link Method}.
|
||||
*
|
||||
* If the {@link Method} is {@link Modifier#STATIC} then {@literal null} is returned,
|
||||
* otherwise {@link Object target} will be returned.
|
||||
*
|
||||
* @param <T> {@link Class type} of the {@link Object target}.
|
||||
* @param target {@link Object} on which the {@link Method} will be invoked.
|
||||
* @param method {@link Method} to invoke on the {@link Object}.
|
||||
* @return the resolved {@link Object invocation method}.
|
||||
* @see java.lang.Object
|
||||
* @see java.lang.reflect.Method
|
||||
*/
|
||||
public static <T> T resolveInvocationTarget(T target, Method method) {
|
||||
return Modifier.isStatic(method.getModifiers()) ? null : target;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ExceptionThrowingOperation<T> {
|
||||
T run() throws Exception;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.util;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.boot.logging.LoggingSystem;
|
||||
import org.springframework.boot.logging.LoggingSystemFactory;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Abstract base class used to perform actions on Spring Boot configuration and components.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.geode.core.util.SpringExtensions
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class SpringBootExtensions extends SpringExtensions {
|
||||
|
||||
/**
|
||||
* Cleans up all resources allocated by the {@link LoggingSystem} loaded, configured and initialized by Spring Boot.
|
||||
*
|
||||
* @see #cleanUpLoggingSystem(ClassLoader)
|
||||
* @see java.lang.ClassLoader
|
||||
*/
|
||||
public static void cleanUpLoggingSystem() {
|
||||
cleanUpLoggingSystem(ClassUtils.getDefaultClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up all resources allocated by the {@link LoggingSystem} loaded, configured and initialized by Spring Boot.
|
||||
*
|
||||
* @param classLoader Java {@link ClassLoader} used to resolve the Spring Boot {@link LoggingSystem}
|
||||
* representing the logging provider (e.g. Logback).
|
||||
* @see java.lang.ClassLoader
|
||||
*/
|
||||
public static void cleanUpLoggingSystem(@NonNull ClassLoader classLoader) {
|
||||
|
||||
Optional.ofNullable(LoggingSystemFactory.fromSpringFactories())
|
||||
.map(loggingSystemFactory -> loggingSystemFactory.getLoggingSystem(classLoader))
|
||||
.ifPresent(LoggingSystem::cleanUp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.util;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link SpringExtensions} is an abstract utility class containing functions to extend the functionality of Spring.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.BeanFactory
|
||||
* @see org.springframework.beans.factory.config.BeanDefinition
|
||||
* @see org.springframework.beans.factory.support.BeanDefinitionRegistry
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @since 1.6.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class SpringExtensions {
|
||||
|
||||
public static final String BEAN_DEFINITION_METADATA_JSON = "{\n"
|
||||
+ "\t'beanName': '%1$s',%n"
|
||||
+ "\t'beanClassName': '%2$s',%n"
|
||||
+ "\t'description': '%3$s',%n"
|
||||
+ "\t'originatingBeanDefinition': '%4$s',%n"
|
||||
+ "\t'parentName': '%5$s',%n"
|
||||
+ "\t'resourceDescription': '%6$s',%n"
|
||||
+ "\t'source': '%7$s',%n"
|
||||
+ "}";
|
||||
|
||||
public static final String EMPTY_JSON_OBJECT = "{}";
|
||||
|
||||
public static @NonNull String getBeanDefinitionMetadata(@NonNull String beanName,
|
||||
@Nullable ApplicationContext applicationContext) {
|
||||
|
||||
return Optional.ofNullable(applicationContext)
|
||||
.filter(ConfigurableApplicationContext.class::isInstance)
|
||||
.map(ConfigurableApplicationContext.class::cast)
|
||||
.map(ConfigurableApplicationContext::getBeanFactory)
|
||||
.map(beanFactory -> getBeanDefinitionMetadata(beanName, beanFactory))
|
||||
.orElse(EMPTY_JSON_OBJECT);
|
||||
}
|
||||
|
||||
public static @NonNull String getBeanDefinitionMetadata(@NonNull String beanName,
|
||||
@Nullable BeanFactory beanFactory) {
|
||||
|
||||
return Optional.ofNullable(beanFactory)
|
||||
.filter(BeanDefinitionRegistry.class::isInstance)
|
||||
.map(BeanDefinitionRegistry.class::cast)
|
||||
.map(registry -> getBeanDefinitionMetadata(beanName, registry))
|
||||
.orElse(EMPTY_JSON_OBJECT);
|
||||
}
|
||||
|
||||
public static @NonNull String getBeanDefinitionMetadata(@NonNull String beanName,
|
||||
@Nullable BeanDefinitionRegistry beanDefinitionRegistry) {
|
||||
|
||||
return Optional.ofNullable(beanDefinitionRegistry)
|
||||
.filter(registry -> StringUtils.hasText(beanName))
|
||||
.map(registry -> registry.getBeanDefinition(beanName))
|
||||
.map(beanDefinition -> getBeanDefinitionMetadata(beanName, beanDefinition))
|
||||
.orElse(EMPTY_JSON_OBJECT);
|
||||
}
|
||||
|
||||
public static @NonNull String getBeanDefinitionMetadata(@Nullable String beanName,
|
||||
@Nullable BeanDefinition beanDefinition) {
|
||||
|
||||
if (beanDefinition != null) {
|
||||
return String.format(BEAN_DEFINITION_METADATA_JSON, beanName,
|
||||
beanDefinition.getBeanClassName(),
|
||||
beanDefinition.getDescription(),
|
||||
beanDefinition.getOriginatingBeanDefinition(),
|
||||
beanDefinition.getParentName(),
|
||||
beanDefinition.getResourceDescription(),
|
||||
beanDefinition.getSource());
|
||||
}
|
||||
|
||||
return EMPTY_JSON_OBJECT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.core.util.function;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Utility methods for using Java {@link Function Functions}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see Function
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class FunctionUtils {
|
||||
|
||||
public static <T, R> Function<T, R> toNullReturningFunction(Consumer<T> consumer) {
|
||||
|
||||
return object -> {
|
||||
consumer.accept(object);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* Copyright 2017-present 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;
|
||||
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Abstract base class implementing the {@link CacheDataImporter} and {@link CacheDataExporter} interfaces in order to
|
||||
* simplify import/export data access operation implementations in a consistent way.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.function.Predicate
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.springframework.context.ApplicationContextAware
|
||||
* @see org.springframework.context.EnvironmentAware
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see org.springframework.geode.data.CacheDataImporterExporter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unused" })
|
||||
public abstract class AbstractCacheDataImporterExporter
|
||||
implements ApplicationContextAware, CacheDataImporterExporter, EnvironmentAware {
|
||||
|
||||
protected static final boolean DEFAULT_CACHE_DATA_EXPORT_ENABLED = false;
|
||||
protected static final boolean DEFAULT_CACHE_DATA_IMPORT_ENABLED = true;
|
||||
|
||||
protected static final String CACHE_DATA_EXPORT_ENABLED_PROPERTY_NAME =
|
||||
"spring.boot.data.gemfire.cache.data.export.enabled";
|
||||
|
||||
protected static final String CACHE_DATA_IMPORT_ACTIVE_PROFILES_PROPERTY_NAME =
|
||||
"spring.boot.data.gemfire.cache.data.import.active-profiles";
|
||||
|
||||
protected static final String CACHE_DATA_IMPORT_ENABLED_PROPERTY_NAME =
|
||||
"spring.boot.data.gemfire.cache.data.import.enabled";
|
||||
|
||||
protected static final String DEFAULT_CACHE_DATA_IMPORT_ACTIVE_PROFILES = "";
|
||||
|
||||
private static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private Predicate<Region<?, ?>> regionPredicate = newRegionPredicate();
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link Predicate} used to filter {@link Region Regions} on data import/export.
|
||||
*
|
||||
* The default {@link Predicate} accepts all {@link Region Regions}. Override the {@link #getRegionPredicate()}
|
||||
* method to change the default behavior.
|
||||
*
|
||||
* @return a new instance of {@link Predicate} used to filter {@link Region Regions} on data import/export.
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see java.util.function.Predicate
|
||||
* @see #getRegionPredicate()
|
||||
*/
|
||||
private Predicate<Region<?, ?>> newRegionPredicate() {
|
||||
return region -> true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a reference to a {@link ApplicationContext} used by this data importer/exporter to perform its function.
|
||||
*
|
||||
* @param applicationContext {@link ApplicationContext} used by this data importer/exporter.
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an {@link Optional} reference to the configured {@link ApplicationContext} used by
|
||||
* this data importer/exporter to perform its function.
|
||||
*
|
||||
* @return an {@link Optional} reference to the configured {@link ApplicationContext} used by
|
||||
* this data importer/exporter.
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
protected Optional<ApplicationContext> getApplicationContext() {
|
||||
return Optional.ofNullable(this.applicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a required reference to the configured {@link ApplicationContext} used by this data importer/exporter.
|
||||
*
|
||||
* @return a required reference to the configured {@link ApplicationContext} used by this data importer/exporter.
|
||||
* @throws IllegalStateException if an {@link ApplicationContext} was not configured
|
||||
* ({@link #setApplicationContext(ApplicationContext)} set).
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see #getApplicationContext()
|
||||
*/
|
||||
protected ApplicationContext requireApplicationContext() {
|
||||
|
||||
return getApplicationContext()
|
||||
.orElseThrow(() -> newIllegalStateException("ApplicationContext was not configured"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a reference to the configured {@link Environment} used by this data importer/exporter
|
||||
* to perform its function.
|
||||
*
|
||||
* @param environment reference to the configured {@link Environment}.
|
||||
* @see org.springframework.core.env.Environment
|
||||
*/
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Optional} reference to the configured {@link Environment} used by this data importer/exporter
|
||||
* to access {@link Environment} specific configuration.
|
||||
*
|
||||
* @return an {@link Optional} reference to the configured {@link Environment} used by this data importer/exporter
|
||||
* to access {@link Environment} specific configuration.
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
protected Optional<Environment> getEnvironment() {
|
||||
return Optional.ofNullable(this.environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a required reference to the configured {@link Environment} used by this data importer/exporter
|
||||
* to access {@link Environment} specific configuration.
|
||||
*
|
||||
* @return a required reference to the configured {@link Environment}.
|
||||
* @throws IllegalStateException if the {@link Environment} was not configured
|
||||
* ({@link #setEnvironment(Environment) set}).
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see #getEnvironment()
|
||||
*/
|
||||
protected Environment requireEnvironment() {
|
||||
|
||||
return getEnvironment()
|
||||
.orElseThrow(() -> newIllegalStateException("Environment was not configured"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link Logger} to log messages.
|
||||
*
|
||||
* @return the configured {@link Logger}.
|
||||
* @see org.slf4j.Logger
|
||||
*/
|
||||
protected Logger getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured {@link Predicate} used to filter {@link Region Regions} on data import/export.
|
||||
*
|
||||
* @return the configured {@link Predicate} used to filter {@link Region Regions} on data import/export.
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see java.util.function.Predicate
|
||||
*/
|
||||
protected @NonNull Predicate<Region<?, ?>> getRegionPredicate() {
|
||||
|
||||
return Optional.ofNullable(this.regionPredicate)
|
||||
.orElseGet(this::newRegionPredicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to determine whether export has been explicitly configured and enabled or disabled.
|
||||
*
|
||||
* @param environment {@link Environment} used to assess the configuration of export.
|
||||
* @return a boolean value indicating whether the export is enabled ({@literal true})
|
||||
* or disabled ({@literal false}); {@literal false} by default.
|
||||
* @see org.springframework.core.env.Environment
|
||||
*/
|
||||
protected boolean isExportEnabled(@Nullable Environment environment) {
|
||||
|
||||
return environment != null
|
||||
&& Boolean.TRUE.equals(environment.getProperty(CACHE_DATA_EXPORT_ENABLED_PROPERTY_NAME, Boolean.class,
|
||||
DEFAULT_CACHE_DATA_EXPORT_ENABLED));
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports data contained in the given {@link Region}.
|
||||
*
|
||||
* @param region {@link Region} to export data from.
|
||||
* @return the given {@link Region}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see #isExportEnabled(Environment)
|
||||
* @see #getRegionPredicate()
|
||||
* @see #doExportFrom(Region)
|
||||
*/
|
||||
@NonNull @Override
|
||||
public Region exportFrom(@NonNull Region region) {
|
||||
|
||||
Assert.notNull(region, "Region must not be null");
|
||||
|
||||
boolean exportEnabled = getEnvironment()
|
||||
.filter(this::isExportEnabled)
|
||||
.filter(environment -> getRegionPredicate().test(region))
|
||||
.isPresent();
|
||||
|
||||
return exportEnabled ? doExportFrom(region) : region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports data contained in the given {@link Region}.
|
||||
*
|
||||
* @param region {@link Region} to export data from.
|
||||
* @return the given {@link Region}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see #exportFrom(Region)
|
||||
*/
|
||||
protected abstract @NonNull Region doExportFrom(@NonNull Region region);
|
||||
|
||||
/**
|
||||
* Null-safe method to determine whether import has been explicitly configured and enabled or disabled.
|
||||
*
|
||||
* @param environment {@link Environment} used to assess the configuration of the import.
|
||||
* @return a boolean value indicating whether the import is enabled ({@literal true})
|
||||
* or disabled ({@literal false}).
|
||||
* @see org.springframework.core.env.Environment
|
||||
*/
|
||||
protected boolean isImportEnabled(@Nullable Environment environment) {
|
||||
|
||||
return environment != null
|
||||
&& Boolean.TRUE.equals(environment.getProperty(CACHE_DATA_IMPORT_ENABLED_PROPERTY_NAME, Boolean.class,
|
||||
DEFAULT_CACHE_DATA_IMPORT_ENABLED));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the Cache Data Import data access operation is enabled based on the configured, active/default
|
||||
* {@literal Profiles} as declared in the Spring {@link Environment}.
|
||||
*
|
||||
* @param environment {@link Environment} used to evaluate the configured, active {@literal Profiles};
|
||||
* must not be {@literal null}.
|
||||
* @return a boolean value indicating whether the the Cache Data Import data access operation is enabled based on
|
||||
* the configured, active/default {@literal Profiles}.
|
||||
* @throws IllegalArgumentException if {@link Environment} is {@literal null}.
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see #useDefaultProfilesIfEmpty(Environment, Set)
|
||||
* @see #getActiveProfiles(Environment)
|
||||
*/
|
||||
protected boolean isImportProfilesActive(@NonNull Environment environment) {
|
||||
|
||||
Assert.notNull(environment, "Environment must not be null");
|
||||
|
||||
boolean importProfilesActive = true;
|
||||
|
||||
String cacheDataImportActiveProfiles =
|
||||
environment.getProperty(CACHE_DATA_IMPORT_ACTIVE_PROFILES_PROPERTY_NAME,
|
||||
DEFAULT_CACHE_DATA_IMPORT_ACTIVE_PROFILES);
|
||||
|
||||
Set<String> cacheDataImportProfiles = commaDelimitedStringToSet(cacheDataImportActiveProfiles);
|
||||
|
||||
if (!cacheDataImportProfiles.isEmpty()) {
|
||||
|
||||
Set<String> configuredProfiles = useDefaultProfilesIfEmpty(environment, getActiveProfiles(environment));
|
||||
|
||||
// The configured, "Active Profiles" must contain at least 1 of the configured cacheDataImportProfiles.
|
||||
importProfilesActive = CollectionUtils.containsAny(configuredProfiles, cacheDataImportProfiles);
|
||||
}
|
||||
|
||||
return importProfilesActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports data into the given {@link Region}.
|
||||
*
|
||||
* @param region {@link Region} to import data into.
|
||||
* @return the given {@link Region}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see #isImportEnabled(Environment)
|
||||
* @see #isImportProfilesActive(Environment)
|
||||
* @see #getRegionPredicate()
|
||||
* @see #doImportInto(Region)
|
||||
*/
|
||||
@NonNull @Override
|
||||
public Region importInto(@NonNull Region region) {
|
||||
|
||||
Assert.notNull(region, "Region must not be null");
|
||||
|
||||
boolean importEnabled = getEnvironment()
|
||||
.filter(this::isImportEnabled)
|
||||
.filter(this::isImportProfilesActive)
|
||||
.filter(environment -> getRegionPredicate().test(region))
|
||||
.isPresent();
|
||||
|
||||
return importEnabled ? doImportInto(region) : region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports data into the given {@link Region}.
|
||||
*
|
||||
* @param region {@link Region} to import data into.
|
||||
* @return the given {@link Region}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see #importInto(Region)
|
||||
*/
|
||||
protected abstract @NonNull Region doImportInto(@NonNull Region region);
|
||||
|
||||
@NonNull Set<String> commaDelimitedStringToSet(@Nullable String commaDelimitedString) {
|
||||
|
||||
return StringUtils.hasText(commaDelimitedString)
|
||||
? Arrays.stream(commaDelimitedString.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::hasText)
|
||||
.collect(Collectors.toSet())
|
||||
: Collections.emptySet();
|
||||
}
|
||||
|
||||
@NonNull Set<String> getActiveProfiles(@NonNull Environment environment) {
|
||||
|
||||
return environment != null
|
||||
? toSet(environment.getActiveProfiles(), String.class)
|
||||
: Collections.emptySet();
|
||||
}
|
||||
|
||||
@NonNull Set<String> useDefaultProfilesIfEmpty(@NonNull Environment environment,
|
||||
@Nullable Set<String> activeProfiles) {
|
||||
|
||||
Set<String> resolvedProfiles = CollectionUtils.nullSafeSet(activeProfiles).stream()
|
||||
.filter(StringUtils::hasText)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (resolvedProfiles.isEmpty()) {
|
||||
|
||||
Set<String> defaultProfiles = environment != null
|
||||
? toSet(environment.getDefaultProfiles(), String.class).stream()
|
||||
.filter(StringUtils::hasText)
|
||||
.collect(Collectors.toSet())
|
||||
: Collections.emptySet();
|
||||
|
||||
if (isNotDefaultProfileOnlySet(defaultProfiles)) {
|
||||
resolvedProfiles = defaultProfiles;
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedProfiles;
|
||||
}
|
||||
|
||||
// The Set of configured Profiles cannot be null, empty or contain only the "default" Profile.
|
||||
boolean isNotDefaultProfileOnlySet(@Nullable Set<String> profiles) {
|
||||
|
||||
return Objects.nonNull(profiles)
|
||||
&& !profiles.isEmpty()
|
||||
&& !Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME).containsAll(profiles);
|
||||
}
|
||||
|
||||
private static @NonNull <T> Set<T> toSet(@Nullable T[] array, @NonNull Class<T> type) {
|
||||
return CollectionUtils.asSet(ArrayUtils.nullSafeArray(array, type));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2017-present 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;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
|
||||
import org.springframework.data.gemfire.ResolvableRegionFactoryBean;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* The {@link CacheDataExporter} interface is a {@link FunctionalInterface} defining a contract for exporting data
|
||||
* from a cache {@link Region}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor
|
||||
* @see org.springframework.data.gemfire.ResolvableRegionFactoryBean
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("rawtypes")
|
||||
public interface CacheDataExporter extends DestructionAwareBeanPostProcessor {
|
||||
|
||||
/**
|
||||
* Exports any data contained in a {@link Region} on destruction.
|
||||
*
|
||||
* @param bean {@link Object} bean to evaluate.
|
||||
* @param beanName {@link String} containing the name of the bean.
|
||||
* @throws BeansException if exporting data from a {@link Region} fails!
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see #exportFrom(Region)
|
||||
*/
|
||||
@Override
|
||||
default void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
|
||||
|
||||
if (bean instanceof Region) {
|
||||
exportFrom((Region) bean);
|
||||
}
|
||||
else if (bean instanceof ResolvableRegionFactoryBean) {
|
||||
exportFrom(((ResolvableRegionFactoryBean) bean).getRegion());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports data contained in the given {@link Region}.
|
||||
*
|
||||
* @param region {@link Region} to export data from.
|
||||
* @return the given {@link Region}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
*/
|
||||
@NonNull Region exportFrom(@NonNull Region region);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2017-present 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;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link CacheDataImporter} interface is a {@link FunctionalInterface} defininig a contract for importing data
|
||||
* into a cache {@link Region}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.beans.factory.config.BeanPostProcessor
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("rawtypes")
|
||||
public interface CacheDataImporter extends BeanPostProcessor {
|
||||
|
||||
/**
|
||||
* Imports data from an external data source into a given {@link Region} after initialization.
|
||||
*
|
||||
* @param bean {@link Object} bean to evaluate.
|
||||
* @param beanName {@link String} containing the name of the bean.
|
||||
* @throws BeansException if importing data into a {@link Region} fails!
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see #importInto(Region)
|
||||
*/
|
||||
@Nullable @Override
|
||||
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
|
||||
if (bean instanceof Region) {
|
||||
bean = importInto((Region) bean);
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports data into the given {@link Region}.
|
||||
*
|
||||
* @param region {@link Region} to import data into.
|
||||
* @return the given {@link Region}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
*/
|
||||
@NonNull Region importInto(@NonNull Region region);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2017-present 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;
|
||||
|
||||
import org.apache.geode.cache.GemFireCache;
|
||||
|
||||
/**
|
||||
* Convenient {@link Class#isInterface() interface} to extend when the implementation supports both
|
||||
* data import and export from/to a {@link GemFireCache}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
* @see org.springframework.geode.data.CacheDataExporter
|
||||
* @see org.springframework.geode.data.CacheDataImporter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public interface CacheDataImporterExporter extends CacheDataExporter, CacheDataImporter {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
import org.apache.geode.pdx.PdxInstance;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.geode.data.CacheDataExporter;
|
||||
import org.springframework.geode.data.CacheDataImporter;
|
||||
import org.springframework.geode.data.json.converter.AbstractObjectArrayToJsonConverter;
|
||||
import org.springframework.geode.data.json.converter.JsonToPdxArrayConverter;
|
||||
import org.springframework.geode.data.json.converter.support.JacksonJsonToPdxConverter;
|
||||
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter;
|
||||
import org.springframework.geode.pdx.ObjectPdxInstanceAdapter;
|
||||
import org.springframework.geode.pdx.PdxInstanceWrapper;
|
||||
import org.springframework.geode.util.CacheUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link JsonCacheDataImporterExporter} class is a {@link CacheDataImporter} and {@link CacheDataExporter}
|
||||
* implementation that can export/import JSON data to/from a {@link Resource} given a target {@link Region}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.geode.data.CacheDataExporter
|
||||
* @see org.springframework.geode.data.CacheDataImporter
|
||||
* @see org.springframework.geode.data.json.converter.JsonToPdxArrayConverter
|
||||
* @see org.springframework.geode.data.json.converter.ObjectArrayToJsonConverter
|
||||
* @see org.springframework.geode.data.json.converter.support.JacksonJsonToPdxConverter
|
||||
* @see org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter
|
||||
* @see org.springframework.geode.pdx.ObjectPdxInstanceAdapter
|
||||
* @see org.springframework.geode.pdx.PdxInstanceWrapper
|
||||
* @see org.springframework.stereotype.Component
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Component
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class JsonCacheDataImporterExporter extends ResourceCapableCacheDataImporterExporter {
|
||||
|
||||
protected static final PdxInstance[] EMPTY_PDX_INSTANCE_ARRAY = {};
|
||||
|
||||
@Autowired(required = false)
|
||||
private JsonToPdxArrayConverter jsonToPdxArrayConverter;
|
||||
|
||||
private final RegionValuesToJsonConverter regionValuesToJsonConverter = new RegionValuesToJsonConverter();
|
||||
|
||||
/**
|
||||
* Determines whether the given array is empty or not. An array is not empty if the array reference
|
||||
* is not {@literal null} and contains at least 1 element.
|
||||
*
|
||||
* @param <T> {@link Class type} of the array elements.
|
||||
* @param array {@link Object} array to evaluate.
|
||||
* @return a boolean value indicating whether the array is empty or not.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> boolean isNotEmpty(T... array) {
|
||||
return array != null && array.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the JSON to PDX (array) converter.
|
||||
*
|
||||
* @see #newJsonToPdxArrayConverter()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
|
||||
super.afterPropertiesSet();
|
||||
|
||||
this.jsonToPdxArrayConverter = this.jsonToPdxArrayConverter != null
|
||||
? this.jsonToPdxArrayConverter
|
||||
: newJsonToPdxArrayConverter();
|
||||
}
|
||||
|
||||
private @NonNull JsonToPdxArrayConverter newJsonToPdxArrayConverter() {
|
||||
return new JacksonJsonToPdxConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the configured {@link JsonToPdxArrayConverter}.
|
||||
*
|
||||
* @return a reference to the configured {@link JsonToPdxArrayConverter}.
|
||||
* @see org.springframework.geode.data.json.converter.JsonToPdxArrayConverter
|
||||
*/
|
||||
protected @NonNull JsonToPdxArrayConverter getJsonToPdxArrayConverter() {
|
||||
return this.jsonToPdxArrayConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@NonNull @Override
|
||||
public Region doExportFrom(@NonNull Region region) {
|
||||
|
||||
Assert.notNull(region, "Region must not be null");
|
||||
|
||||
getExportResourceResolver()
|
||||
.resolve(region)
|
||||
.ifPresent(resource -> {
|
||||
|
||||
String json = toJson(region);
|
||||
|
||||
getLogger().debug("Saving JSON [{}] from Region [{}]", json, region.getName());
|
||||
|
||||
getResourceWriter().write(resource, json.getBytes());
|
||||
});
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@NonNull @Override
|
||||
public Region doImportInto(@NonNull Region region) {
|
||||
|
||||
Assert.notNull(region, "Region must not be null");
|
||||
|
||||
getImportResourceResolver()
|
||||
.resolve(region)
|
||||
.map(this.getResourceReader()::read)
|
||||
.map(this::toPdx)
|
||||
.ifPresent(pdxInstances -> regionPutPdx(region, pdxInstances));
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts all PDX data from the {@link PdxInstance} array into the target {@link Region} mapped to
|
||||
* the PDX {@link PdxInstance#isIdentityField(String) identifier} as the {@literal key}.
|
||||
*
|
||||
* @param region target {@link Region} to store the PDX data; must not be {@literal null}
|
||||
* @param pdx {@link PdxInstance} array containing the PDX data to store in the target {@link Region}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.apache.geode.cache.Region#put(Object, Object)
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
void regionPutPdx(@NonNull Region region, @Nullable PdxInstance[] pdx) {
|
||||
|
||||
Arrays.stream(ArrayUtils.nullSafeArray(pdx, PdxInstance.class)).forEach(pdxInstance ->
|
||||
region.put(resolveKey(pdxInstance), resolveValue(pdxInstance)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Post processes the given {{@link PdxInstance}.
|
||||
*
|
||||
* @param pdxInstance {@link PdxInstance} to process.
|
||||
* @return the {@link PdxInstance}.
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
protected PdxInstance postProcess(PdxInstance pdxInstance) {
|
||||
return pdxInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link Object key} used to map the given {@link PdxInstance} as the {@link Object value}
|
||||
* for the {@code Region.Entry} stored in the {@link Region}.
|
||||
*
|
||||
* @param pdxInstance {@link PdxInstance} used to resolve the {@link Object key}.
|
||||
* @return the resolved {@link Object key}.
|
||||
* @see org.springframework.geode.pdx.PdxInstanceWrapper#getIdentifier()
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
protected @NonNull Object resolveKey(@NonNull PdxInstance pdxInstance) {
|
||||
return PdxInstanceWrapper.from(pdxInstance).getIdentifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link Object value} to store in the {@link Region} from the given {@link PdxInstance}.
|
||||
*
|
||||
* If the given {@link PdxInstance} is an instance of {@link PdxInstanceWrapper} then this method will return
|
||||
* the underlying, {@link PdxInstanceWrapper#getDelegate() delegate} {@link PdxInstance}.
|
||||
*
|
||||
* If the given {@link PdxInstance} is an instance of {@link ObjectPdxInstanceAdapter} then this method will return
|
||||
* the underlying, {@link ObjectPdxInstanceAdapter#getObject() Object}.
|
||||
*
|
||||
* Otherwise, the given {@link PdxInstance} is returned.
|
||||
*
|
||||
* @param pdxInstance {@link PdxInstance} to unwrap.
|
||||
* @return the resolved {@link Object value}.
|
||||
* @see org.springframework.geode.pdx.ObjectPdxInstanceAdapter#unwrap(PdxInstance)
|
||||
* @see org.springframework.geode.pdx.PdxInstanceWrapper#unwrap(PdxInstance)
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see #postProcess(PdxInstance)
|
||||
*/
|
||||
protected @Nullable Object resolveValue(@Nullable PdxInstance pdxInstance) {
|
||||
return ObjectPdxInstanceAdapter.unwrap(PdxInstanceWrapper.unwrap(postProcess(pdxInstance)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert {@link Object values} contained in the {@link Region} to {@link String JSON}.
|
||||
*
|
||||
* @param region {@link Region} to process; must not be {@literal null}.
|
||||
* @return {@link String JSON} containing the {@link Object values} from the given {@link Region}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected @NonNull String toJson(@NonNull Region region) {
|
||||
return this.regionValuesToJsonConverter.convert(region);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the array of {@link Byte#TYPE bytes} containing multiple {@link String JSON} objects
|
||||
* into an array of {@link PdxInstance PdxInstances}.
|
||||
*
|
||||
* @param json array of {@link Byte#TYPE bytes} containing the {@link String JSON} to convert to PDX.
|
||||
* @return an array of {@link PdxInstance PdxInstances} for each {@link String JSON} object.
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see #getJsonToPdxArrayConverter()
|
||||
*/
|
||||
protected @NonNull PdxInstance[] toPdx(@NonNull byte[] json) {
|
||||
|
||||
return isNotEmpty(json)
|
||||
? getJsonToPdxArrayConverter().convert(json)
|
||||
: EMPTY_PDX_INSTANCE_ARRAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all {@link Region#values() values} in the targeted {@link Region} into {@literal JSON}.
|
||||
*
|
||||
* The converter is capable of handling both {@link Object Objects} and PDX.
|
||||
*
|
||||
* @see org.springframework.geode.data.json.converter.AbstractObjectArrayToJsonConverter
|
||||
*/
|
||||
static class RegionValuesToJsonConverter extends AbstractObjectArrayToJsonConverter {
|
||||
|
||||
@NonNull <K, V> String convert(@NonNull Region<K, V> region) {
|
||||
|
||||
Assert.notNull(region, "Region must not be null");
|
||||
|
||||
return super.convert(CollectionUtils.nullSafeCollection(CacheUtils.collectValues(region)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.geode.data.json.converter.support.JSONFormatterPdxToJsonConverter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An abstract base class implementing {@link ObjectArrayToJsonConverter} encapsulating functionality common
|
||||
* to all implementations.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Iterable
|
||||
* @see java.util.Map
|
||||
* @see org.springframework.geode.data.json.converter.ObjectArrayToJsonConverter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public abstract class AbstractObjectArrayToJsonConverter implements ObjectArrayToJsonConverter {
|
||||
|
||||
protected static final String BEGIN_ARRAY = "[";
|
||||
protected static final String EMPTY_STRING = "";
|
||||
protected static final String END_ARRAY = "]";
|
||||
protected static final String JSON_OBJECT_SEPARATOR = ", ";
|
||||
|
||||
private ObjectToJsonConverter converter = newObjectToJsonConverter();
|
||||
|
||||
// TODO configure via an SPI
|
||||
private @NonNull ObjectToJsonConverter newObjectToJsonConverter() {
|
||||
return new JSONFormatterPdxToJsonConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the configured {@link ObjectToJsonConverter} used to convert
|
||||
* individual {@link Object Objects} into {@link String JSON}.
|
||||
*
|
||||
* @return a reference to the configured {@link ObjectToJsonConverter}; never {@literal null}.
|
||||
* @see org.springframework.geode.data.json.converter.ObjectToJsonConverter
|
||||
*/
|
||||
protected @NonNull ObjectToJsonConverter getObjectToJsonConverter() {
|
||||
return this.converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given {@link Iterable} of {@link Object Objects} into a {@link String JSON} array.
|
||||
*
|
||||
* @param iterable {@link Iterable} containing the {@link Object Objects} to convert into {@link String JSON};
|
||||
* must not be {@literal null}.
|
||||
* @return the {@link String JSON} generated from the given {@link Iterable} of {@link Object Objects};
|
||||
* never {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link Iterable} is {@literal null}.
|
||||
* @see #getObjectToJsonConverter()
|
||||
* @see java.lang.Iterable
|
||||
*/
|
||||
@Override
|
||||
public @NonNull String convert(@NonNull Iterable<?> iterable) {
|
||||
|
||||
Assert.notNull(iterable, "Iterable must not be null");
|
||||
|
||||
StringBuilder json = new StringBuilder(BEGIN_ARRAY);
|
||||
|
||||
ObjectToJsonConverter converter = getObjectToJsonConverter();
|
||||
|
||||
boolean addComma = false;
|
||||
|
||||
for (Object value : CollectionUtils.nullSafeIterable(iterable)) {
|
||||
json.append(addComma ? JSON_OBJECT_SEPARATOR : EMPTY_STRING);
|
||||
json.append(converter.convert(value));
|
||||
addComma = true;
|
||||
}
|
||||
|
||||
json.append(END_ARRAY);
|
||||
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@link Map#values() values} from the given {@link Map} into {@link String JSON}.
|
||||
*
|
||||
* @param <K> {@link Class} type of the {@link Map#keySet() keys}.
|
||||
* @param <V> {@link Class} type of the {@link Map#values() values}.
|
||||
* @param map {@link Map} containing the {@link Map#values() values} to convert into {@link String JSON}.
|
||||
* @return {@link String JSON} generated from the {@link Map#values() values} in the given {@link Map}.
|
||||
* @see #convert(Iterable)
|
||||
* @see java.util.Map
|
||||
*/
|
||||
public @NonNull <K, V> String convert(@Nullable Map<K, V> map) {
|
||||
return convert(CollectionUtils.nullSafeMap(map).values());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* Spring {@link Converter} interface extension defining a contract to convert {@link String JSON}
|
||||
* to an array of {@link Object Objects}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Object
|
||||
* @see java.lang.String
|
||||
* @see org.springframework.core.convert.converter.Converter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public interface JsonToObjectArrayConverter extends Converter<String, Object[]> {
|
||||
|
||||
/**
|
||||
* Converts the array of {@link Byte#TYPE bytes} containing JSON into an array of {@link Object Objects}.
|
||||
*
|
||||
* @param json array of {@link Byte#TYPE bytes} containing the JSON to convert; must not be {@literal null}.
|
||||
* @return an array of {@link Object Objects} converted from the array of {@link Byte#TYPE bytes} containing JSON.
|
||||
* @see #convert(Object)
|
||||
*/
|
||||
default @NonNull Object[] convert(@NonNull byte[] json) {
|
||||
return convert(new String(json));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* Spring {@link Converter} interface extension defining a contract to convert
|
||||
* from {@link String JSON} to an {@link Object} (POJO).
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Object
|
||||
* @see java.lang.String
|
||||
* @see org.springframework.core.convert.converter.Converter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public interface JsonToObjectConverter extends Converter<String, Object> {
|
||||
|
||||
/**
|
||||
* Converts the array of {@link Byte#TYPE bytes} containing JSON into an {@link Object} (POJO).
|
||||
*
|
||||
* @param json array of {@link Byte#TYPE bytes} containing JSON to convert into an {@link Object} (POJO);
|
||||
* must not be {@literal null}.
|
||||
* @return an {@link Object} (POJO) converted from the array of {@link Byte#TYPE bytes} containing JSON.
|
||||
* @see #convert(Object)
|
||||
*/
|
||||
default @NonNull Object convert(@NonNull byte[] json) {
|
||||
return convert(new String(json));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter;
|
||||
|
||||
import org.apache.geode.pdx.PdxInstance;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* A Spring {@link Converter} interface extension defining a contract to convert
|
||||
* from {@link String JSON} to an array of {@link PdxInstance} objects.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @see java.lang.String
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see org.springframework.core.convert.converter.Converter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface JsonToPdxArrayConverter extends Converter<String, PdxInstance[]> {
|
||||
|
||||
/**
|
||||
* Converts the array of {@link Byte#TYPE bytes} containing JSON into an array of {@link PdxInstance} objects.
|
||||
*
|
||||
* @param json array of {@link Byte#TYPE bytes} containing the JSON to convert; must not be {@literal null}.
|
||||
* @return an array of {@link PdxInstance} objects converted from the array of {@link Byte#TYPE bytes}
|
||||
* containing JSON.
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see #convert(Object)
|
||||
*/
|
||||
default @NonNull PdxInstance[] convert(@NonNull byte[] json) {
|
||||
return convert(new String(json));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter;
|
||||
|
||||
import org.apache.geode.pdx.PdxInstance;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* A Spring {@link Converter} interface extension defining a contract to convert
|
||||
* from {@link String JSON} to a {@link PdxInstance}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @see java.lang.String
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see org.springframework.core.convert.converter.Converter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface JsonToPdxConverter extends Converter<String, PdxInstance> {
|
||||
|
||||
/**
|
||||
* Converts the array of {@link Byte#TYPE bytes} containing JSON into a {@link PdxInstance}.
|
||||
*
|
||||
* @param json array of {@link Byte#TYPE bytes} containing JSON to convert into a {@link PdxInstance};
|
||||
* must not be {@literal null}.
|
||||
* @return a {@link PdxInstance} converted from the array of {@link Byte#TYPE bytes} containing JSON.
|
||||
* @see #convert(Object)
|
||||
*/
|
||||
default @NonNull PdxInstance convert(@NonNull byte[] json) {
|
||||
return convert(new String(json));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A Spring {@link Converter} interface extension defining a contract to convert
|
||||
* an {@link Iterable} or an array of {@link Object Objects} into {@link String JSON}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @see java.lang.Iterable
|
||||
* @see java.lang.Object
|
||||
* @see java.lang.String
|
||||
* @see org.springframework.core.convert.converter.Converter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ObjectArrayToJsonConverter extends Converter<Iterable<?>, String> {
|
||||
|
||||
/**
|
||||
* Converts the given array of {@link Object Objects} into {@link String JSON}.
|
||||
*
|
||||
* @param array array of {@link Object Objects} to convert into {@link String JSON}.
|
||||
* @return {@link String JSON} generated from the given array of {@link Object Objects}.
|
||||
* @see #convert(Object)
|
||||
*/
|
||||
default @NonNull String convert(@Nullable Object... array) {
|
||||
return convert(Arrays.asList(ArrayUtils.nullSafeArray(array, Object.class)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* A Spring {@link Converter} interface extension defining a contract to convert
|
||||
* from an {@link Object} into a {@link String JSON}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @see java.lang.Object
|
||||
* @see java.lang.String
|
||||
* @see org.springframework.core.convert.converter.Converter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ObjectToJsonConverter extends Converter<Object, String> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter.support;
|
||||
|
||||
import org.apache.geode.pdx.JSONFormatter;
|
||||
import org.apache.geode.pdx.JSONFormatterException;
|
||||
import org.apache.geode.pdx.PdxInstance;
|
||||
|
||||
import org.springframework.geode.data.json.converter.JsonToObjectConverter;
|
||||
import org.springframework.geode.data.json.converter.JsonToPdxConverter;
|
||||
import org.springframework.geode.pdx.ObjectPdxInstanceAdapter;
|
||||
import org.springframework.geode.pdx.PdxInstanceWrapper;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* A {@link JsonToPdxConverter} implementation using the Apache Geode {@link JSONFormatter} to convert
|
||||
* from a {@literal JSON} {@link String} to a {@link PdxInstance}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.pdx.JSONFormatter
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see org.springframework.geode.data.json.converter.JsonToPdxConverter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class JSONFormatterJsonToPdxConverter implements JsonToPdxConverter {
|
||||
|
||||
private JsonToObjectConverter converter = newJsonToObjectConverter();
|
||||
|
||||
// TODO configure via an SPIs
|
||||
private JsonToObjectConverter newJsonToObjectConverter() {
|
||||
return new JacksonJsonToObjectConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the configured {@link JsonToObjectConverter} used to convert from {@link String JSON}
|
||||
* to an {@link Object}.
|
||||
*
|
||||
* @return a reference to the configured {@link JsonToObjectConverter}; never {@literal null}.
|
||||
* @see org.springframework.geode.data.json.converter.JsonToObjectConverter
|
||||
*/
|
||||
protected @NonNull JsonToObjectConverter getJsonToObjectConverter() {
|
||||
return this.converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public final @NonNull PdxInstance convert(@NonNull String json) {
|
||||
|
||||
try {
|
||||
return convertJsonToPdx(json);
|
||||
}
|
||||
catch (JSONFormatterException cause) {
|
||||
return convertJsonToObjectToPdx(json);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts the given {@link Object} as a {@link PdxInstance}.
|
||||
*
|
||||
* @param target {@link Object} to adapt as PDX; must not be {@literal null}.
|
||||
* @return a {@link PdxInstance} representing the given {@link Object}.
|
||||
* @see org.springframework.geode.pdx.ObjectPdxInstanceAdapter#from(Object)
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
protected @NonNull PdxInstance adapt(@NonNull Object target) {
|
||||
return ObjectPdxInstanceAdapter.from(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given {@link String JSON} into a {@link Object} and then adapts the {@link Object}
|
||||
* as a {@link PdxInstance}.
|
||||
*
|
||||
* @param json {@link String JSON} to convert into an {@link Object} into PDX.
|
||||
* @return a {@link PdxInstance} converted from the given {@link String JSON}.
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see #getJsonToObjectConverter()
|
||||
* @see #adapt(Object)
|
||||
*/
|
||||
protected @NonNull PdxInstance convertJsonToObjectToPdx(@NonNull String json) {
|
||||
return adapt(getJsonToObjectConverter().convert(json));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given {@link String JSON} to {@link PdxInstance PDX}.
|
||||
*
|
||||
* @param json {@link String} containing JSON to convert to PDX; must not be {@literal null}.
|
||||
* @return JSON for the given {@link PdxInstance PDX}.
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see #jsonFormatterFromJson(String)
|
||||
* @see #wrap(PdxInstance)
|
||||
*/
|
||||
protected @NonNull PdxInstance convertJsonToPdx(@NonNull String json) {
|
||||
return wrap(jsonFormatterFromJson(json));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts {@link String JSON} into {@link PdxInstance PDX} using {@link JSONFormatter#fromJSON(String)}.
|
||||
*
|
||||
* @param json {@link String JSON} to convert to {@link PdxInstance PDX}; must not be {@literal null}.
|
||||
* @return {@link PdxInstance PDX} generated from the given, required {@link String JSON}; never {@literal null}.
|
||||
* @see org.apache.geode.pdx.JSONFormatter#fromJSON(String)
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
protected @NonNull PdxInstance jsonFormatterFromJson(@NonNull String json) {
|
||||
return JSONFormatter.fromJSON(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the given {@link PdxInstance} in a new instance of {@link PdxInstanceWrapper}.
|
||||
*
|
||||
* @param pdxInstance {@link PdxInstance} to wrap.
|
||||
* @return a new instance of {@link PdxInstanceWrapper} wrapping the given {@link PdxInstance}.
|
||||
* @see org.springframework.geode.pdx.PdxInstanceWrapper#from(PdxInstance)
|
||||
* @see org.springframework.geode.pdx.PdxInstanceWrapper
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
protected @NonNull PdxInstanceWrapper wrap(@NonNull PdxInstance pdxInstance) {
|
||||
return PdxInstanceWrapper.from(pdxInstance);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter.support;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.apache.geode.pdx.JSONFormatter;
|
||||
import org.apache.geode.pdx.PdxInstance;
|
||||
import org.apache.geode.pdx.WritablePdxInstance;
|
||||
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.geode.data.json.converter.ObjectToJsonConverter;
|
||||
import org.springframework.geode.pdx.PdxInstanceBuilder;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link ObjectToJsonConverter} implementation using the Apache Geode {@link JSONFormatter} to convert
|
||||
* from a {@link PdxInstance} to a {@literal JSON} {@link String}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see com.fasterxml.jackson.databind.JsonNode
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
* @see com.fasterxml.jackson.databind.node.ObjectNode
|
||||
* @see org.apache.geode.pdx.JSONFormatter
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see org.apache.geode.pdx.WritablePdxInstance
|
||||
* @see org.springframework.geode.data.json.converter.ObjectToJsonConverter
|
||||
* @see org.springframework.geode.data.json.converter.support.JacksonObjectToJsonConverter
|
||||
* @see org.springframework.geode.pdx.PdxInstanceBuilder
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class JSONFormatterPdxToJsonConverter extends JacksonObjectToJsonConverter {
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Nullable @Override
|
||||
public final String convert(@Nullable Object source) {
|
||||
|
||||
return Optional.ofNullable(source)
|
||||
.filter(PdxInstance.class::isInstance)
|
||||
.map(PdxInstance.class::cast)
|
||||
.map(this::convertPdxToJson)
|
||||
.orElseGet(() -> convertPojoToJson(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given {@link Object} to JSON.
|
||||
*
|
||||
* @param source {@link Object} to convert to JSON.
|
||||
* @return the JSON generated from the given {@link Object}.
|
||||
* @see JacksonObjectToJsonConverter#convert(Object)
|
||||
*/
|
||||
protected @Nullable String convertPojoToJson(Object source) {
|
||||
return super.convert(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given {@link PdxInstance PDX} to {@link String JSON}.
|
||||
*
|
||||
* @param pdxInstance {@link PdxInstance} to convert to JSON; must not be {@literal null}.
|
||||
* @return JSON generated from the given {@link PdxInstance}.
|
||||
* @see org.apache.geode.pdx.JSONFormatter#toJSON(PdxInstance)
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see #jsonFormatterToJson(PdxInstance)
|
||||
*/
|
||||
protected @NonNull String convertPdxToJson(@NonNull PdxInstance pdxInstance) {
|
||||
return decorate(pdxInstance, jsonFormatterToJson(pdxInstance));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts {@link PdxInstance PDX} into {@link String JSON} using {@link JSONFormatter#toJSON(PdxInstance)}.
|
||||
*
|
||||
* @param pdxInstance {@link PdxInstance PDX} to convert to {@link String JSON}; must not be {@literal null}.
|
||||
* @return {@link String JSON} generated from the given, required {@link PdxInstance PDX}; never {@literal null}.
|
||||
* @see org.apache.geode.pdx.JSONFormatter#toJSON(PdxInstance)
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
@NonNull String jsonFormatterToJson(@NonNull PdxInstance pdxInstance) {
|
||||
return JSONFormatter.toJSON(pdxInstance);
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING!!!
|
||||
*
|
||||
* First, this method might be less than optimal and could lead to PDX type explosion!
|
||||
*
|
||||
* Second, this {@code pdxInstance.createWriter().setField(AT_TYPE_METADATA_PROPERTY_NAME, className);} ...
|
||||
*
|
||||
* Throws:
|
||||
* org.apache.geode.pdx.PdxFieldDoesNotExistException: A field named @type does not exist on ...
|
||||
* PdxType[dsid=0,typenum=7232261,name=example.app.crm.model.Customer,fields=[id:long:identity:0:idx0(relativeOffset)=0:idx1(vlfOffsetIndex)=0, name:String:1:idx0(relativeOffset)=8:idx1(vlfOffsetIndex)=-1,]]
|
||||
* at org.apache.geode.pdx.internal.WritablePdxInstanceImpl.setField(WritablePdxInstanceImpl.java:119)
|
||||
* ...
|
||||
*
|
||||
* This code needs to create a {@literal new} {@link PdxInstance} from an existing {@link PdxInstance}
|
||||
* or add the new (PDX) field to the PDX type metadata using {@code PdxType.addField(:PdxField)} before
|
||||
* setting the new field on the {@link PdxInstance} using the {@link WritablePdxInstance}. Unfortunately,
|
||||
* {@code PdxType} is part of the internal API and updating and ditributing a {@code PdxType} is complicated,
|
||||
* requiring a Distributed Lock, among other responsibilities.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected @NonNull PdxInstance decorate(@NonNull PdxInstance pdxInstance) {
|
||||
|
||||
if (isMissingObjectTypeMetadata(pdxInstance)) {
|
||||
|
||||
String pdxInstanceClassName = pdxInstance.getClassName();
|
||||
|
||||
Assert.isTrue(hasValidClassName(pdxInstance), () ->
|
||||
String.format("Class name [%s] is required and cannot be equal to [%s]",
|
||||
pdxInstanceClassName, JSONFormatter.JSON_CLASSNAME));
|
||||
|
||||
pdxInstance = newPdxInstanceBuilder()
|
||||
.copy(pdxInstance)
|
||||
.writeString(AT_TYPE_METADATA_PROPERTY_NAME, pdxInstanceClassName)
|
||||
.create();
|
||||
}
|
||||
|
||||
return pdxInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link PdxInstanceBuilder}.
|
||||
*
|
||||
* @return a new instance of {@link PdxInstanceBuilder}; never {@literal null}.
|
||||
* @see org.springframework.geode.pdx.PdxInstanceBuilder
|
||||
*/
|
||||
@NonNull PdxInstanceBuilder newPdxInstanceBuilder() {
|
||||
return PdxInstanceBuilder.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates the given {@link String JSON} to include the {@literal @type} metadata property in order to
|
||||
* indicate the type of the {@literal JSON} object, which is required for deserialization back to PDX.
|
||||
*
|
||||
* If an {@link JsonProcessingException} is thrown during this operation and if the {@link PdxInstance}
|
||||
* has a {@link #hasValidClassName(PdxInstance) valid class name}, then an attempt is made to serialize
|
||||
* the {@link PdxInstance#getObject() object instance} of the {@link PdxInstance} to {@link String JSON}
|
||||
* using Jackson's {@link ObjectMapper}.
|
||||
*
|
||||
* @param pdxInstance required {@link PdxInstance} from which the {@link String JSON} was serialized;
|
||||
* must not be {@literal null}.
|
||||
* @param json {@link String JSON} generated from the serialization of the {@link PdxInstance};
|
||||
* must not be {@literal null}.
|
||||
* @return the decorated {@link String JSON} including the {@literal @type} metadata property.
|
||||
* @throws DataRetrievalFailureException if {@link String JSON} cannot be decorated with type metadata
|
||||
* and the {@link PdxInstance} is not based on a valid {@link Class} type.
|
||||
* @see JacksonObjectToJsonConverter#convert(Object)
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see #newObjectMapper(Object)
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected @NonNull String decorate(@NonNull PdxInstance pdxInstance, @NonNull String json) {
|
||||
|
||||
if (isDecorationRequired(pdxInstance, json)) {
|
||||
try {
|
||||
|
||||
ObjectMapper objectMapper = newObjectMapper(json);
|
||||
|
||||
JsonNode jsonNode = objectMapper.readTree(json);
|
||||
|
||||
if (isMissingObjectTypeMetadata(jsonNode)) {
|
||||
((ObjectNode) jsonNode).put(AT_TYPE_METADATA_PROPERTY_NAME, pdxInstance.getClassName());
|
||||
json = objectMapper.writeValueAsString(jsonNode);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
catch (JsonProcessingException cause) {
|
||||
|
||||
if (hasValidClassName(pdxInstance)) {
|
||||
return convertPojoToJson(pdxInstance.getObject());
|
||||
}
|
||||
|
||||
String message = String.format("Failed to parse JSON [%s]", json);
|
||||
|
||||
throw new DataRetrievalFailureException(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to determine whether the given {@link PdxInstance}
|
||||
* has a valid {@link Class#getName() Class Name}.
|
||||
*
|
||||
* @param pdxInstance {@link PdxInstance} to evaluate;
|
||||
* @return a boolean value indicating whether the {@link PdxInstance}
|
||||
* has a valid {@link Class#getName() Class Name}.
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
boolean hasValidClassName(@Nullable PdxInstance pdxInstance) {
|
||||
|
||||
return Optional.ofNullable(pdxInstance)
|
||||
.map(PdxInstance::getClassName)
|
||||
.filter(StringUtils::hasText)
|
||||
.filter(className -> !JSONFormatter.JSON_CLASSNAME.equals(className))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
private boolean isDecorationRequired(@Nullable PdxInstance pdxInstance, @Nullable String json) {
|
||||
return isMissingObjectTypeMetadata(pdxInstance) && isValidJson(json);
|
||||
}
|
||||
|
||||
private boolean isMissingObjectTypeMetadata(@Nullable JsonNode node) {
|
||||
return isObjectNode(node) && !node.has(AT_TYPE_METADATA_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
private boolean isMissingObjectTypeMetadata(@Nullable PdxInstance pdxInstance) {
|
||||
return pdxInstance != null && !pdxInstance.hasField(AT_TYPE_METADATA_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to determine if the given {@link JsonNode} represents a valid {@link String JSON} object.
|
||||
*
|
||||
* @param node {@link JsonNode} to evaluate.
|
||||
* @return a boolean valued indicating whether the given {@link JsonNode} is a valid {@link ObjectNode}.
|
||||
* @see com.fasterxml.jackson.databind.node.ObjectNode
|
||||
* @see com.fasterxml.jackson.databind.JsonNode
|
||||
*/
|
||||
boolean isObjectNode(@Nullable JsonNode node) {
|
||||
return node != null && (node.isObject() || node instanceof ObjectNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to determine whether the given {@link String JSON} is valid.
|
||||
*
|
||||
* @param json {@link String} containing JSON to evaluate.
|
||||
* @return a boolean value indicating whether the given {@link String JSON} is valid.
|
||||
*/
|
||||
boolean isValidJson(@Nullable String json) {
|
||||
return StringUtils.hasText(json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter.support;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||
import com.fasterxml.jackson.databind.node.POJONode;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.geode.data.json.converter.JsonToObjectConverter;
|
||||
import org.springframework.geode.pdx.PdxInstanceWrapper;
|
||||
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;
|
||||
|
||||
/**
|
||||
* {@link JsonToObjectConverter} implementation using Jackson to convert {@link String JSON}
|
||||
* to an {@link Object} (POJO).
|
||||
*
|
||||
* @author John Blum
|
||||
* @see com.fasterxml.jackson.databind.JsonNode
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
* @see com.fasterxml.jackson.databind.node.POJONode
|
||||
* @see org.springframework.core.convert.converter.Converter
|
||||
* @see org.springframework.geode.data.json.converter.JsonToObjectConverter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class JacksonJsonToObjectConverter implements JsonToObjectConverter {
|
||||
|
||||
protected static final String AT_TYPE_FIELD_NAME = PdxInstanceWrapper.AT_TYPE_FIELD_NAME;
|
||||
|
||||
private ObjectMapper objectMapper = newObjectMapper();
|
||||
|
||||
/**
|
||||
* Constructs a new Jackson {@link ObjectMapper} to convert {@link String JSON} into an {@link Object} (POJO).
|
||||
*
|
||||
* @return a new Jackson {@link ObjectMapper}; never {@literal null}.
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
*/
|
||||
// TODO configure via an SPI
|
||||
private @NonNull ObjectMapper newObjectMapper() {
|
||||
|
||||
return new ObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true)
|
||||
.findAndRegisterModules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the configured Jackson {@link ObjectMapper} used by this {@link Converter}
|
||||
* to convert {@link String JSON} into an {@link Object} (POJO).
|
||||
*
|
||||
* @return a reference to the configured Jackson {@link ObjectMapper}.
|
||||
*/
|
||||
protected @NonNull ObjectMapper getObjectMapper() {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from {@link String JSON} to an {@link Object} (POJO) using Jackson's {@link ObjectMapper}.
|
||||
*
|
||||
* @param json {@link String} containing {@literal JSON} to convert.
|
||||
* @return an {@link Object} (POJO) converted from the given {@link String JSON}.
|
||||
* @see #getObjectMapper()
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Object convert(@Nullable String json) {
|
||||
|
||||
if (StringUtils.hasText(json)) {
|
||||
|
||||
String objectTypeName = null;
|
||||
|
||||
try {
|
||||
|
||||
ObjectMapper objectMapper = getObjectMapper();
|
||||
|
||||
JsonNode jsonNode = objectMapper.readTree(json);
|
||||
|
||||
if (isPojo(jsonNode)) {
|
||||
return ((POJONode) jsonNode).getPojo();
|
||||
}
|
||||
else {
|
||||
|
||||
Assert.state(jsonNode.isObject(), () -> String.format("The JSON [%s] must be an object", json));
|
||||
|
||||
Assert.state(jsonNode.has(AT_TYPE_FIELD_NAME),
|
||||
() -> String.format("The JSON object [%1$s] must have an '%2$s' metadata field",
|
||||
json, AT_TYPE_FIELD_NAME));
|
||||
|
||||
objectTypeName = jsonNode.get(AT_TYPE_FIELD_NAME).asText();
|
||||
|
||||
Class<?> objectType =
|
||||
ClassUtils.forName(objectTypeName, Thread.currentThread().getContextClassLoader());
|
||||
|
||||
return objectMapper.readValue(json, objectType);
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException cause) {
|
||||
throw new MappingException(String.format("Failed to map JSON [%1$s] to an Object of type [%2$s]",
|
||||
json, objectTypeName), cause);
|
||||
}
|
||||
catch (JsonProcessingException cause) {
|
||||
throw new DataRetrievalFailureException(String.format("Failed to read JSON [%s]", json), cause);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to determine whether the given {@link JsonNode} represents a {@link Object POJO}.
|
||||
*
|
||||
* @param jsonNode {@link JsonNode} to evaluate.
|
||||
* @return a boolean value indicating whether the given {@link JsonNode} represents a {@link Object POJO}.
|
||||
* @see com.fasterxml.jackson.databind.JsonNode
|
||||
*/
|
||||
boolean isPojo(@Nullable JsonNode jsonNode) {
|
||||
|
||||
return jsonNode != null
|
||||
&& (jsonNode instanceof POJONode || jsonNode.isPojo() || JsonNodeType.POJO.equals(jsonNode.getNodeType()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.apache.geode.pdx.PdxInstance;
|
||||
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.geode.data.json.converter.JsonToPdxArrayConverter;
|
||||
import org.springframework.geode.data.json.converter.JsonToPdxConverter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link JacksonJsonToPdxConverter} class is an implementation of the {@link JsonToPdxArrayConverter} that is
|
||||
* capable of converting an array of {@literal JSON} objects into an array of {@link PdxInstance PdxInstances}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see com.fasterxml.jackson.databind.JsonNode
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
* @see com.fasterxml.jackson.databind.node.ArrayNode
|
||||
* @see com.fasterxml.jackson.databind.node.ObjectNode
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see org.springframework.geode.data.json.converter.JsonToPdxArrayConverter
|
||||
* @see org.springframework.geode.data.json.converter.JsonToPdxConverter
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class JacksonJsonToPdxConverter implements JsonToPdxArrayConverter {
|
||||
|
||||
private JsonToPdxConverter converter = newJsonToPdxConverter();
|
||||
|
||||
private ObjectMapper objectMapper = newObjectMapper();
|
||||
|
||||
private @NonNull <T> Iterable<T> asIterable(@NonNull Iterator<T> iterator) {
|
||||
return () -> iterator;
|
||||
}
|
||||
|
||||
// TODO configure via an SPI
|
||||
private JsonToPdxConverter newJsonToPdxConverter() {
|
||||
return new JSONFormatterJsonToPdxConverter();
|
||||
}
|
||||
|
||||
// TODO configure via an SPI
|
||||
private ObjectMapper newObjectMapper() {
|
||||
return new ObjectMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the configured {@link JsonToPdxConverter} used to convert from a single object,
|
||||
* {@literal JSON} {@link String} to PDX (i.e. as a {@link PdxInstance}.
|
||||
*
|
||||
* @return a reference to the configured {@link JsonToPdxConverter}; never {@literal null}.
|
||||
* @see org.springframework.geode.data.json.converter.JsonToPdxConverter
|
||||
*/
|
||||
protected @NonNull JsonToPdxConverter getJsonToPdxConverter() {
|
||||
return this.converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the configured Jackson {@link ObjectMapper}.
|
||||
*
|
||||
* @return a reference to the configured Jackson {@link ObjectMapper}; never {@literal null}.
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
*/
|
||||
protected @NonNull ObjectMapper getObjectMapper() {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given {@link String JSON} containing multiple objects into an array of {@link PdxInstance} objects.
|
||||
*
|
||||
* @param json {@link String JSON} data to convert.
|
||||
* @return an array of {@link PdxInstance} objects from the given {@link String JSON}.
|
||||
* @throws IllegalStateException if the {@link String JSON} does not start with
|
||||
* either a JSON array or a JSON object.
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
@Nullable @Override
|
||||
public PdxInstance[] convert(String json) {
|
||||
|
||||
try {
|
||||
JsonNode jsonNode = getObjectMapper().readTree(json);
|
||||
|
||||
List<PdxInstance> pdxList = new ArrayList<>();
|
||||
|
||||
if (isArray(jsonNode)) {
|
||||
|
||||
ArrayNode arrayNode = (ArrayNode) jsonNode;
|
||||
|
||||
JsonToPdxConverter converter = getJsonToPdxConverter();
|
||||
|
||||
for (JsonNode object : asIterable(CollectionUtils.nullSafeIterator(arrayNode.elements()))) {
|
||||
pdxList.add(converter.convert(object.toString()));
|
||||
}
|
||||
}
|
||||
else if (isObject(jsonNode)) {
|
||||
|
||||
ObjectNode objectNode = (ObjectNode) jsonNode;
|
||||
|
||||
pdxList.add(getJsonToPdxConverter().convert(objectNode.toString()));
|
||||
}
|
||||
else {
|
||||
|
||||
String message = String.format("Unable to process JSON node of type [%s];"
|
||||
+ " expected either an [%s] or an [%s]", jsonNode.getNodeType(),
|
||||
JsonNodeType.OBJECT, JsonNodeType.ARRAY);
|
||||
|
||||
throw new IllegalStateException(message);
|
||||
}
|
||||
|
||||
return pdxList.toArray(new PdxInstance[0]);
|
||||
}
|
||||
catch (JsonProcessingException cause) {
|
||||
throw new DataRetrievalFailureException("Failed to read JSON content", cause);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isArray(@Nullable JsonNode node) {
|
||||
return node != null && (node.isArray() || JsonNodeType.ARRAY.equals(node.getNodeType()));
|
||||
}
|
||||
|
||||
private boolean isObject(@Nullable JsonNode node) {
|
||||
return node != null && (node.isObject() || JsonNodeType.OBJECT.equals(node.getNodeType()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.json.converter.support;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.geode.data.json.converter.ObjectToJsonConverter;
|
||||
import org.springframework.geode.pdx.PdxInstanceWrapper;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link ObjectToJsonConverter} implementation using Jackson's {@link ObjectMapper} to convert
|
||||
* from an {@link Object} to a {@literal JSON} {@link String}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see com.fasterxml.jackson.annotation.JsonTypeInfo
|
||||
* @see com.fasterxml.jackson.core.JsonGenerator
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
* @see com.fasterxml.jackson.databind.MapperFeature
|
||||
* @see org.springframework.core.convert.TypeDescriptor
|
||||
* @see org.springframework.geode.data.json.converter.ObjectToJsonConverter
|
||||
* @see org.springframework.geode.pdx.PdxInstanceWrapper
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class JacksonObjectToJsonConverter implements ObjectToJsonConverter {
|
||||
|
||||
protected static final String AT_TYPE_METADATA_PROPERTY_NAME = PdxInstanceWrapper.AT_TYPE_FIELD_NAME;
|
||||
|
||||
/**
|
||||
* Converts the given {@link Object} into {@link String JSON}.
|
||||
*
|
||||
* @param source {@link Object} to convert into {@link String JSON}.
|
||||
* @return {@link String JSON} generated from the given {@link Object} using Jackson's {@link ObjectMapper}.
|
||||
* @throws IllegalArgumentException if {@link Object source} is {@literal null}.
|
||||
* @throws ConversionFailedException if a {@link JsonProcessingException} is thrown or another error occurs
|
||||
* while trying to convert the given {@link Object} to {@link String JSON}.
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
* @see #convertObjectToJson(Object)
|
||||
*/
|
||||
@Override
|
||||
public @NonNull String convert(@NonNull Object source) {
|
||||
|
||||
Assert.notNull(source, "Source object to convert must not be null");
|
||||
|
||||
try {
|
||||
return convertObjectToJson(source);
|
||||
}
|
||||
catch (JsonProcessingException cause) {
|
||||
throw new ConversionFailedException(TypeDescriptor.forObject(source), TypeDescriptor.valueOf(String.class),
|
||||
source, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given {@link Object} into {@link String JSON}.
|
||||
*
|
||||
* @param source {@link Object} to convert to {@link String JSON}; must not be {@literal null}.
|
||||
* @return {@link String JSON} generated from the given {@link Object}.
|
||||
* @throws IllegalArgumentException if {@link Object source} is {@literal null}.
|
||||
* @throws JsonProcessingException if the generation of {@link String JSON} from the given {@link Object}
|
||||
* results in an error.
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper#writeValueAsString(Object)
|
||||
* @see #newObjectMapper(Object)
|
||||
*/
|
||||
protected @NonNull String convertObjectToJson(@NonNull Object source) throws JsonProcessingException {
|
||||
|
||||
Assert.notNull(source, "Source object to convert must not be null");
|
||||
|
||||
return newObjectMapper(source).writeValueAsString(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the Jackson {@link ObjectMapper} class.
|
||||
*
|
||||
* @return a new instance of the Jackson {@link ObjectMapper} class.
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
*/
|
||||
protected @NonNull ObjectMapper newObjectMapper(@NonNull Object target) {
|
||||
|
||||
Assert.notNull(target, "Target object must not be null");
|
||||
|
||||
return newObjectMapper()
|
||||
.addMixIn(target.getClass(), ObjectTypeMetadataMixin.class)
|
||||
.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true)
|
||||
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
|
||||
.configure(SerializationFeature.INDENT_OUTPUT, true)
|
||||
.findAndRegisterModules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of Jackson's {@link ObjectMapper}.
|
||||
*
|
||||
* @return a new instance of Jackson's {@link ObjectMapper}; never {@literal null}.
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
*/
|
||||
@NonNull ObjectMapper newObjectMapper() {
|
||||
return new ObjectMapper();
|
||||
}
|
||||
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.CLASS,
|
||||
include = JsonTypeInfo.As.PROPERTY,
|
||||
property = AT_TYPE_METADATA_PROPERTY_NAME
|
||||
)
|
||||
@SuppressWarnings("all")
|
||||
interface ObjectTypeMetadataMixin { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
* Copyright 2017-present 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 java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
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.ApplicationContextAware;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.Lifecycle;
|
||||
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.data.gemfire.support.SmartLifecycleSupport;
|
||||
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.data.CacheDataImporterExporter;
|
||||
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ExportResourceResolver;
|
||||
import org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ImportResourceResolver;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link CacheDataImporterExporter} implementation using the {@literal Decorator Software Design Pattern} to wrap
|
||||
* an existing {@link CacheDataImporterExporter} in order to {@literal decorate} the cache (i.e. {@link Region}) data
|
||||
* import and export operations, making them Spring {@link ApplicationContext}, {@link Environment}, {@link Lifecycle},
|
||||
* {@link ResourceLoader} aware and capable.
|
||||
*
|
||||
* This wrapper {@literal decorates} the Apache Geode cache {@link Region} data import operation enabling it
|
||||
* to be configured {@link ImportLifecycle#EAGER eagerly}, after the {@link Region} bean as been initialized,
|
||||
* or {@link ImportLifecycle#LAZY lazily}, once all beans have been fully initialized and the Spring
|
||||
* {@link ApplicationContext} is refreshed.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.springframework.context.ApplicationContextAware
|
||||
* @see org.springframework.context.EnvironmentAware
|
||||
* @see org.springframework.context.Lifecycle
|
||||
* @see org.springframework.context.ResourceLoaderAware
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see org.springframework.core.io.ResourceLoader
|
||||
* @see org.springframework.data.gemfire.support.SmartLifecycleSupport
|
||||
* @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.CacheDataImporterExporter
|
||||
* @see org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ExportResourceResolver
|
||||
* @see org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ImportResourceResolver
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Decorator_pattern">Decorator Software Design Pattern</a>
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class LifecycleAwareCacheDataImporterExporter implements CacheDataImporterExporter,
|
||||
ApplicationContextAware, EnvironmentAware, InitializingBean, ResourceLoaderAware, SmartLifecycleSupport {
|
||||
|
||||
protected static final int DEFAULT_IMPORT_PHASE = Integer.MIN_VALUE + 1000000;
|
||||
|
||||
protected static final String CACHE_DATA_IMPORT_LIFECYCLE_PROPERTY_NAME =
|
||||
"spring.boot.data.gemfire.cache.data.import.lifecycle";
|
||||
|
||||
protected static final String CACHE_DATA_IMPORT_PHASE_PROPERTY_NAME =
|
||||
"spring.boot.data.gemfire.cache.data.import.phase";
|
||||
|
||||
private final AtomicReference<ImportLifecycle> resolvedImportLifecycle = new AtomicReference<>(null);
|
||||
private final AtomicReference<Integer> resolvedImportPhase = new AtomicReference<>(null);
|
||||
|
||||
private final CacheDataImporterExporter importerExporter;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private final Set<Region> regionsForImport = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the {@link LifecycleAwareCacheDataImporterExporter} initialized with the given,
|
||||
* target {@link CacheDataImporterExporter} that is wrapped by this implementation to decorate all cache import
|
||||
* and export data operations in order to make them {@link Lifecycle} aware and capable.
|
||||
*
|
||||
* @param importerExporter {@link CacheDataImporterExporter} wrapped by this implementation to {@literal decorate}
|
||||
* the cache data import/export operations to be {@link Lifecycle} aware and capable; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link CacheDataImporterExporter} is {@literal null}.
|
||||
* @see org.springframework.geode.data.CacheDataImporterExporter
|
||||
*/
|
||||
public LifecycleAwareCacheDataImporterExporter(@NonNull CacheDataImporterExporter importerExporter) {
|
||||
|
||||
Assert.notNull(importerExporter, "The CacheDataImporterExporter to decorate must not be null");
|
||||
|
||||
this.importerExporter = importerExporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the wrapped {@link CacheDataImporterExporter} if the importer/exporter
|
||||
* implements {@link InitializingBean}.
|
||||
*
|
||||
* @throws Exception if {@link CacheDataImporterExporter} initialization fails.
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
CacheDataImporterExporter importerExporter = getCacheDataImporterExporter();
|
||||
|
||||
if (importerExporter instanceof InitializingBean) {
|
||||
((InitializingBean) importerExporter).afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a reference to the Spring {@link ApplicationContext}.
|
||||
*
|
||||
* @param applicationContext Spring {@link ApplicationContext} in which this component operates.
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
|
||||
|
||||
if (applicationContext != null) {
|
||||
|
||||
CacheDataImporterExporter importerExporter = getCacheDataImporterExporter();
|
||||
|
||||
if (importerExporter instanceof ApplicationContextAware) {
|
||||
((ApplicationContextAware) importerExporter).setApplicationContext(applicationContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the configured {@link CacheDataImporterExporter} wrapped by this {@link Lifecycle} aware
|
||||
* and capable {@link CacheDataImporterExporter}.
|
||||
*
|
||||
* @return the {@link CacheDataImporterExporter} enhanced and used as the delegate for this {@link Lifecycle} aware
|
||||
* and capable {@link CacheDataImporterExporter}; never {@literal null}.
|
||||
*/
|
||||
protected @NonNull CacheDataImporterExporter getCacheDataImporterExporter() {
|
||||
return this.importerExporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a reference to the {@link Environment} used to access configuration for the behavior of
|
||||
* the cache data import.
|
||||
*
|
||||
* @param environment {@link Environment} used to access context specific configuration for the cache data import.
|
||||
* @see org.springframework.core.env.Environment
|
||||
*/
|
||||
@Override
|
||||
public void setEnvironment(@Nullable Environment environment) {
|
||||
|
||||
this.environment = environment;
|
||||
|
||||
if (environment != null) {
|
||||
|
||||
CacheDataImporterExporter importerExporter = getCacheDataImporterExporter();
|
||||
|
||||
if (importerExporter instanceof EnvironmentAware) {
|
||||
((EnvironmentAware) importerExporter).setEnvironment(environment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Optional} reference to the configured {@link Environment} used to access configuration
|
||||
* for the behavior of the cache data import.
|
||||
*
|
||||
* If a reference to {@link Environment} was not configured, then this method will return {@link Optional#empty()}.
|
||||
*
|
||||
* @return an {@link Optional} reference to the configured {@link Environment}, or {@link Optional#empty()} if no
|
||||
* {@link Environment} was configured.
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see #setEnvironment(Environment)
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
protected Optional<Environment> getEnvironment() {
|
||||
return Optional.ofNullable(this.environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link ExportResourceResolver} of the wrapped {@link CacheDataImporterExporter}
|
||||
* if the {@link ExportResourceResolver} is not {@literal null} and the {@link CacheDataImporterExporter}
|
||||
* is {@link Resource} capable.
|
||||
*
|
||||
* @param exportResourceResolver {@link ResourceResolver} used to resolve a {@link Resource} for {@literal export}.
|
||||
* @see org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ExportResourceResolver
|
||||
* @see #getCacheDataImporterExporter()
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setExportResourceResolver(@Nullable ExportResourceResolver exportResourceResolver) {
|
||||
|
||||
if (exportResourceResolver != null) {
|
||||
|
||||
CacheDataImporterExporter importerExporter = getCacheDataImporterExporter();
|
||||
|
||||
if (importerExporter instanceof ResourceCapableCacheDataImporterExporter) {
|
||||
((ResourceCapableCacheDataImporterExporter) importerExporter)
|
||||
.setExportResourceResolver(exportResourceResolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link ImportResourceResolver} of the wrapped {@link CacheDataImporterExporter}
|
||||
* if the {@link ImportResourceResolver} is not {@literal null} and the {@link CacheDataImporterExporter}
|
||||
* is {@link Resource} capable.
|
||||
*
|
||||
* @param importResourceResolver {@link ResourceResolver} used to resolve a {@link Resource} for {@literal import}.
|
||||
* @see org.springframework.geode.data.support.ResourceCapableCacheDataImporterExporter.ImportResourceResolver
|
||||
* @see #getCacheDataImporterExporter()
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setImportResourceResolver(@Nullable ImportResourceResolver importResourceResolver) {
|
||||
|
||||
if (importResourceResolver != null) {
|
||||
|
||||
CacheDataImporterExporter importerExporter = getCacheDataImporterExporter();
|
||||
|
||||
if (importerExporter instanceof ResourceCapableCacheDataImporterExporter) {
|
||||
((ResourceCapableCacheDataImporterExporter) importerExporter)
|
||||
.setImportResourceResolver(importResourceResolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public int getPhase() {
|
||||
return resolveImportPhase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Set} of {@link Region Regions} to import data into.
|
||||
*
|
||||
* @return a {@link Set} of {@link Region Regions} to evaluate on import; never {@literal null}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see java.util.Set
|
||||
*/
|
||||
@NonNull Set<Region> getRegionsForImport() {
|
||||
return this.regionsForImport;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
|
||||
if (resourceLoader != null) {
|
||||
|
||||
CacheDataImporterExporter importerExporter = getCacheDataImporterExporter();
|
||||
|
||||
if (importerExporter instanceof ResourceLoaderAware) {
|
||||
((ResourceLoaderAware) importerExporter).setResourceLoader(resourceLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link ResourceReader} of the wrapped {@link CacheDataImporterExporter}
|
||||
* if the {@link ResourceReader} is not {@literal null} and the {@link CacheDataImporterExporter}
|
||||
* is {@link Resource} capable.
|
||||
*
|
||||
* @param resourceReader {@link ResourceReader} used to read data from a {@link Resource} on {@literal import}.
|
||||
* @see org.springframework.geode.core.io.ResourceReader
|
||||
* @see #getCacheDataImporterExporter()
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setResourceReader(@Nullable ResourceReader resourceReader) {
|
||||
|
||||
if (resourceReader != null) {
|
||||
|
||||
CacheDataImporterExporter importerExporter = getCacheDataImporterExporter();
|
||||
|
||||
if (importerExporter instanceof ResourceCapableCacheDataImporterExporter) {
|
||||
((ResourceCapableCacheDataImporterExporter) importerExporter).setResourceReader(resourceReader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link ResourceWriter} of the wrapped {@link CacheDataImporterExporter}
|
||||
* if the {@link ResourceWriter} is not {@literal null} and the {@link CacheDataImporterExporter}
|
||||
* is {@link Resource} capable.
|
||||
*
|
||||
* @param resourceWriter {@link ResourceWriter} used to write data to a {@link Resource} on {@literal export}.
|
||||
* @see org.springframework.geode.core.io.ResourceWriter
|
||||
* @see #getCacheDataImporterExporter()
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setResourceWriter(@Nullable ResourceWriter resourceWriter) {
|
||||
|
||||
if (resourceWriter != null) {
|
||||
|
||||
CacheDataImporterExporter importerExporter = getCacheDataImporterExporter();
|
||||
|
||||
if (importerExporter instanceof ResourceCapableCacheDataImporterExporter) {
|
||||
((ResourceCapableCacheDataImporterExporter) importerExporter).setResourceWriter(resourceWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@NonNull @Override
|
||||
public Region exportFrom(@NonNull Region region) {
|
||||
return getCacheDataImporterExporter().exportFrom(region);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@NonNull @Override
|
||||
public Region importInto(@NonNull Region region) {
|
||||
|
||||
if (resolveImportLifecycle().isEager()) {
|
||||
return getCacheDataImporterExporter().importInto(region);
|
||||
}
|
||||
else {
|
||||
getRegionsForImport().add(region);
|
||||
return region;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the configured {@link ImportLifecycle}.
|
||||
*
|
||||
* The cache data import lifecycle is configured with the
|
||||
* {@literal spring.boot.data.gemfire.cache.data.import.lifecycle} property
|
||||
* in Spring Boot {@literal application.properties}.
|
||||
*
|
||||
* @return the configured {@link ImportLifecycle}.
|
||||
* @see LifecycleAwareCacheDataImporterExporter.ImportLifecycle
|
||||
*/
|
||||
protected ImportLifecycle resolveImportLifecycle() {
|
||||
|
||||
return resolvedImportLifecycle.updateAndGet(currentValue -> currentValue != null ? currentValue
|
||||
: getEnvironment()
|
||||
.map(env -> env.getProperty(CACHE_DATA_IMPORT_LIFECYCLE_PROPERTY_NAME, String.class,
|
||||
ImportLifecycle.getDefault().name()))
|
||||
.map(ImportLifecycle::from)
|
||||
.orElseGet(ImportLifecycle::getDefault));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the configured {@link SmartLifecycleSupport#getPhase() SmartLifecycle Phase} in which the cache data
|
||||
* import will be performed.
|
||||
*
|
||||
* @return the configured {@link SmartLifecycleSupport#getPhase() SmartLifecycle Phase}.
|
||||
* @see #getPhase()
|
||||
*/
|
||||
protected int resolveImportPhase() {
|
||||
|
||||
return resolvedImportPhase.updateAndGet(currentValue -> currentValue != null ? currentValue
|
||||
: getEnvironment()
|
||||
.map(env -> env.getProperty(CACHE_DATA_IMPORT_PHASE_PROPERTY_NAME, Integer.class, DEFAULT_IMPORT_PHASE))
|
||||
.orElse(DEFAULT_IMPORT_PHASE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the cache data import for each of the targeted {@link Region Regions}.
|
||||
*
|
||||
* @see #getCacheDataImporterExporter()
|
||||
* @see #getRegionsForImport()
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
// Technically, the resolveImportLifecycle().isLazy() check is not strictly required since if the cache data
|
||||
// import is "eager", then the regionsForImport Set will be empty anyway.
|
||||
if (resolveImportLifecycle().isLazy()) {
|
||||
getRegionsForImport().forEach(getCacheDataImporterExporter()::importInto);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link Enum Enumeration} defining the different modes for the cache data import lifecycle.
|
||||
*/
|
||||
public enum ImportLifecycle {
|
||||
|
||||
EAGER("Imports cache data during Region bean post processing, after initialization"),
|
||||
LAZY("Imports cache data during the appropriate phase on Lifecycle start");
|
||||
|
||||
private final String description;
|
||||
|
||||
ImportLifecycle(@NonNull String description) {
|
||||
|
||||
Assert.hasText(description, "The enumerated value must have a description");
|
||||
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public static @NonNull ImportLifecycle getDefault() {
|
||||
return LAZY;
|
||||
}
|
||||
|
||||
public static @Nullable ImportLifecycle from(String name) {
|
||||
|
||||
for (ImportLifecycle importCycle : values()) {
|
||||
if (importCycle.name().equalsIgnoreCase(name)) {
|
||||
return importCycle;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isEager() {
|
||||
return EAGER.equals(this);
|
||||
}
|
||||
|
||||
public boolean isLazy() {
|
||||
return LAZY.equals(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.description;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,713 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.context.expression.BeanFactoryAccessor;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.env.Environment;
|
||||
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.core.util.ObjectAwareUtils;
|
||||
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;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* An {@link AbstractCacheDataImporterExporter} extension and implementation capable of handling and managing import
|
||||
* and export {@link Resource Resources}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.io.File
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.beans.factory.InitializingBean
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @see org.springframework.context.ResourceLoaderAware
|
||||
* @see org.springframework.expression.Expression
|
||||
* @see org.springframework.expression.ExpressionParser
|
||||
* @see org.springframework.expression.spel.SpelParserConfiguration
|
||||
* @see org.springframework.expression.spel.standard.SpelExpressionParser
|
||||
* @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";
|
||||
|
||||
private ExportResourceResolver exportResourceResolver;
|
||||
|
||||
private ImportResourceResolver importResourceResolver;
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
private ResourceReader resourceReader;
|
||||
|
||||
private ResourceWriter resourceWriter;
|
||||
|
||||
/**
|
||||
* Initializes the export and import {@link ResourceResolver ResourceResolvers} as needed along with
|
||||
* the {@link ResourceReader reader} and {@link ResourceWriter writer} for the {@link Resource}
|
||||
* used on import and export.
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
|
||||
setExportResourceResolver(initialize(getExportResourceResolver(), FileSystemExportResourceResolver::new));
|
||||
setImportResourceResolver(initialize(getImportResourceResolver(), ClassPathImportResourceResolver::new));
|
||||
setResourceReader(initialize(getResourceReader(), ByteArrayResourceReader::new));
|
||||
setResourceWriter(initialize(getResourceWriter(), FileResourceWriter::new));
|
||||
|
||||
Stream.of(getExportResourceResolver(), getImportResourceResolver())
|
||||
.forEach(this.newCompositeObjectAwareInitializer());
|
||||
}
|
||||
|
||||
Consumer<Object> newCompositeObjectAwareInitializer() {
|
||||
|
||||
return ObjectAwareUtils.applicationContextAwareObjectInitializer(getApplicationContext().orElse(null))
|
||||
.andThen(ObjectAwareUtils.environmentAwareObjectInitializer(getEnvironment().orElse(null)))
|
||||
.andThen(ObjectAwareUtils.resourceLoaderAwareObjectInitializer(getResourceLoader().orElse(null)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a reference to the configured {@link ExportResourceResolver}.
|
||||
*
|
||||
* @param exportResourceResolver configured {@link ExportResourceResolver} used by this importer/exporter
|
||||
* to resolve {@link Resource Resources} on export.
|
||||
* @see ExportResourceResolver
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setExportResourceResolver(@Nullable ExportResourceResolver exportResourceResolver) {
|
||||
this.exportResourceResolver = exportResourceResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured reference to the {@link ExportResourceResolver}.
|
||||
*
|
||||
* The configured {@link ExportResourceResolver} is guaranteed to be {@literal non-null} only if the
|
||||
* {@link #afterPropertiesSet()} initialization method was called after construction of this importer/exporter.
|
||||
* This is definitely true in a Spring context.
|
||||
*
|
||||
* @return the configured reference to the {@link ExportResourceResolver}.
|
||||
* @see ExportResourceResolver
|
||||
*/
|
||||
protected @NonNull ExportResourceResolver getExportResourceResolver() {
|
||||
return this.exportResourceResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a reference to the configured {@link ImportResourceResolver}.
|
||||
*
|
||||
* @param importResourceResolver configured {@link ImportResourceResolver} used by this importer/exporter
|
||||
* to resolve {@link Resource Resources} on import.
|
||||
* @see ImportResourceResolver
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setImportResourceResolver(@Nullable ImportResourceResolver importResourceResolver) {
|
||||
this.importResourceResolver = importResourceResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured reference to the {@link ImportResourceResolver}.
|
||||
*
|
||||
* The configured {@link ImportResourceResolver} is guaranteed to be {@literal non-null} only if the
|
||||
* {@link #afterPropertiesSet()} initialization method was called after construction of this importer/exporter.
|
||||
* This is definitely true in a Spring context.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a reference to the configured {@link ResourceReader}.
|
||||
*
|
||||
* @param resourceReader configured {@link ResourceReader} used by this importer/exporter
|
||||
* to read from a {@link Resource} on import.
|
||||
* @see org.springframework.geode.core.io.ResourceReader
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setResourceReader(@Nullable ResourceReader resourceReader) {
|
||||
this.resourceReader = resourceReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured {@link ResourceReader} used to read data from a {@link Resource} on {@literal import}.
|
||||
*
|
||||
* The configured {@link ResourceReader} is guaranteed to be {@literal non-null} only if the
|
||||
* {@link #afterPropertiesSet()} initialization method was called after construction of this importer/exporter.
|
||||
* This is definitely true in a Spring context.
|
||||
*
|
||||
* @return the configured {@link ResourceReader}.
|
||||
* @see org.springframework.geode.core.io.ResourceReader
|
||||
*/
|
||||
protected @NonNull ResourceReader getResourceReader() {
|
||||
return this.resourceReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a reference to the configured {@link ResourceWriter}.
|
||||
*
|
||||
* @param resourceWriter configured {@link ResourceWriter} used by this importer/exporter
|
||||
* to write to a {@link Resource} on export.
|
||||
* @see org.springframework.geode.core.io.ResourceWriter
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setResourceWriter(@Nullable ResourceWriter resourceWriter) {
|
||||
this.resourceWriter = resourceWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured {@link ResourceWriter} used to write data to the {@link Resource} on {@literal export}.
|
||||
*
|
||||
* The configured {@link ResourceWriter} is guaranteed to be {@literal non-null} only if the
|
||||
* {@link #afterPropertiesSet()} initialization method was called after construction of this importer/exporter.
|
||||
* This is definitely true in a Spring context.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@FunctionalInterface
|
||||
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);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
default Optional<Resource> resolve(@NonNull String location) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 org.springframework.context.ApplicationContextAware
|
||||
* @see org.springframework.context.EnvironmentAware
|
||||
* @see CacheResourceResolver
|
||||
*/
|
||||
protected static abstract class AbstractCacheResourceResolver extends ResourceLoaderResourceResolver
|
||||
implements ApplicationContextAware, CacheResourceResolver, EnvironmentAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private final ExpressionParser expressionParser;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
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 and 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a reference to the Spring {@link ApplicationContext}.
|
||||
*
|
||||
* @param applicationContext reference to the {@link ApplicationContext}.
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Optional} reference to a Spring {@link ApplicationContext}.
|
||||
*
|
||||
* @return an {@link Optional} reference to a Spring {@link ApplicationContext}.
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
protected Optional<ApplicationContext> getApplicationContext() {
|
||||
return Optional.ofNullable(this.applicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a reference to the Spring {@link Environment}.
|
||||
*
|
||||
* @param environment reference to the {@link Environment}.
|
||||
* @see org.springframework.core.env.Environment
|
||||
*/
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Optional} reference to the Spring {@link Environment}.
|
||||
*
|
||||
* @return an {@link Optional} reference to the Spring {@link Environment}.
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
protected Optional<Environment> getEnvironment() {
|
||||
return Optional.ofNullable(this.environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link Logger} to log messages.
|
||||
*
|
||||
* @return the configured {@link Logger}.
|
||||
* @see org.slf4j.Logger
|
||||
*/
|
||||
protected Logger getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ExportResourceResolver extends CacheResourceResolver { }
|
||||
|
||||
/**
|
||||
* Abstract base class extended by export {@link CacheResourceResolver} implementations, providing a template
|
||||
* to resolve the {@link Resource} used for export.
|
||||
*
|
||||
* @see AbstractCacheResourceResolver
|
||||
* @see ExportResourceResolver
|
||||
*/
|
||||
public static 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);
|
||||
|
||||
boolean writable = resource.filter(ResourceUtils::isWritable).isPresent();
|
||||
|
||||
if (!writable) {
|
||||
getLogger().warn("Resource [{}] for Region [{}] is not writable",
|
||||
resourceLocation, region.getFullPath());
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
protected @Nullable Resource onMissingResource(@Nullable Resource resource, @NonNull String location) {
|
||||
|
||||
getLogger().warn("Resource [{}] at location [{}] does not exist; will try to create it on export",
|
||||
ResourceUtils.nullSafeGetDescription(resource), location);
|
||||
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link Resource} used for {@literal export} from the {@literal filesystem}.
|
||||
*/
|
||||
public static class FileSystemExportResourceResolver extends AbstractExportResourceResolver {
|
||||
|
||||
@Override
|
||||
protected @NonNull String getResourcePath() {
|
||||
return String.format("%1$s%2$s%3$s", ResourcePrefix.FILESYSTEM_URL_PREFIX.toUrlPrefix(),
|
||||
System.getProperty("user.dir"), File.separator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker interface extending {@link CacheResourceResolver} for cache data imports.
|
||||
*
|
||||
* @see org.springframework.geode.core.io.ResourceResolver
|
||||
* @see CacheResourceResolver
|
||||
*/
|
||||
@FunctionalInterface
|
||||
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
|
||||
*/
|
||||
public static 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);
|
||||
|
||||
boolean exists = resource.isPresent();
|
||||
boolean readable = exists && resource.filter(Resource::isReadable).isPresent();
|
||||
|
||||
if (!exists) {
|
||||
getLogger().warn("Resource [{}] for Region [{}] could not be found; skipping import for Region",
|
||||
resourceLocation, region.getFullPath());
|
||||
}
|
||||
else {
|
||||
Assert.state(readable, () -> String.format("Resource [%1$s] for Region [%2$s] is not readable",
|
||||
resourceLocation, region.getFullPath()));
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
@Nullable @Override
|
||||
protected Resource onMissingResource(@Nullable Resource resource, @NonNull String location) {
|
||||
|
||||
getLogger().warn("Resource [{}] at location [{}] does not exist; skipping import",
|
||||
ResourceUtils.nullSafeGetDescription(resource), location);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the {@link Resource} to {@literal import} from the {@literal classpath}.
|
||||
*/
|
||||
public static class ClassPathImportResourceResolver extends AbstractImportResourceResolver {
|
||||
|
||||
@Override
|
||||
protected @NonNull String getResourcePath() {
|
||||
return ResourcePrefix.CLASSPATH_URL_PREFIX.toUrlPrefix();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.distributed.event;
|
||||
|
||||
import org.apache.geode.distributed.DistributedMember;
|
||||
import org.apache.geode.distributed.DistributedSystem;
|
||||
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.geode.distributed.event.support.MemberDepartedEvent;
|
||||
import org.springframework.geode.distributed.event.support.MemberJoinedEvent;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link ApplicationContextMembershipListener} class is an extension of {@link MembershipListenerAdapter} used to
|
||||
* adapt the {@link ConfigurableApplicationContext} to handle and process {@link MembershipEvent membership events},
|
||||
* and specifically {@link MemberDepartedEvent} and {@link MemberJoinedEvent}, by
|
||||
* {@link ConfigurableApplicationContext#close() closing} and {@link ConfigurableApplicationContext#refresh() refreshing}
|
||||
* the {@link ConfigurableApplicationContext} when the {@link DistributedMember peer member} departs and joins the
|
||||
* {@link DistributedSystem cluster}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.distributed.DistributedMember
|
||||
* @see org.apache.geode.distributed.DistributedSystem
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @see org.springframework.geode.distributed.event.support.MemberDepartedEvent
|
||||
* @see org.springframework.geode.distributed.event.support.MemberJoinedEvent
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class ApplicationContextMembershipListener
|
||||
extends MembershipListenerAdapter<ApplicationContextMembershipListener> {
|
||||
|
||||
private final ConfigurableApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ConfigurableApplicationContext} initialized with
|
||||
* the given {@link ConfigurableApplicationContext}.
|
||||
*
|
||||
* @param applicationContext configured {@link ConfigurableApplicationContext}; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link ConfigurableApplicationContext} is {@literal null}.
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
*/
|
||||
public ApplicationContextMembershipListener(@NonNull ConfigurableApplicationContext applicationContext) {
|
||||
|
||||
Assert.notNull(applicationContext, "ConfigurableApplicationContext must not be null");
|
||||
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the configured {@link ConfigurableApplicationContext}.
|
||||
*
|
||||
* @return a reference to the configured {@link ConfigurableApplicationContext}.
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
*/
|
||||
protected @NonNull ConfigurableApplicationContext getApplicationContext() {
|
||||
return this.applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the {@link MembershipEvent membership event} when a {@link DistributedMember peer member}
|
||||
* departs from the {@link DistributedSystem cluster} by calling {@link ConfigurableApplicationContext#close()}.
|
||||
*
|
||||
* @param event {@link MemberDepartedEvent} to handle.
|
||||
* @see org.springframework.geode.distributed.event.support.MemberDepartedEvent
|
||||
* @see org.springframework.context.ConfigurableApplicationContext#close()
|
||||
*/
|
||||
@Override
|
||||
public void handleMemberDeparted(MemberDepartedEvent event) {
|
||||
getApplicationContext().close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the {@link MembershipEvent membership event} when a {@link DistributedMember peer member}
|
||||
* joins the {@link DistributedSystem cluster} by calling {@link ConfigurableApplicationContext#refresh()}.
|
||||
*
|
||||
* @param event {@link MemberJoinedEvent} to handle.
|
||||
* @see org.springframework.geode.distributed.event.support.MemberJoinedEvent
|
||||
* @see org.springframework.context.ConfigurableApplicationContext#refresh()
|
||||
*/
|
||||
@Override
|
||||
public void handleMemberJoined(MemberJoinedEvent event) {
|
||||
getApplicationContext().refresh();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.expression;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.EnvironmentCapable;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Spring {@link PropertyAccessor} implementation that knows how to access properties
|
||||
* from {@link Environment} and {@link EnvironmentCapable} objects.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @see org.springframework.core.env.EnvironmentCapable
|
||||
* @see org.springframework.expression.PropertyAccessor
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public class SmartEnvironmentAccessor implements PropertyAccessor {
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link SmartEnvironmentAccessor}.
|
||||
*
|
||||
* @return a new instance of {@link SmartEnvironmentAccessor}.
|
||||
*/
|
||||
public static @NonNull SmartEnvironmentAccessor create() {
|
||||
return new SmartEnvironmentAccessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Nullable @Override
|
||||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return new Class[] { Environment.class, EnvironmentCapable.class };
|
||||
}
|
||||
|
||||
private Optional<Environment> asEnvironment(@Nullable Object target) {
|
||||
|
||||
Environment environment = target instanceof Environment ? (Environment) target
|
||||
: target instanceof EnvironmentCapable ? ((EnvironmentCapable) target).getEnvironment()
|
||||
: null;
|
||||
|
||||
return Optional.ofNullable(environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public boolean canRead(EvaluationContext context, @Nullable Object target, String name) {
|
||||
|
||||
return asEnvironment(target)
|
||||
.filter(environment -> environment.containsProperty(name))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public TypedValue read(EvaluationContext context, @Nullable Object target, String name) {
|
||||
|
||||
String value = asEnvironment(target)
|
||||
.map(environment -> environment.getProperty(name))
|
||||
.orElse(null);
|
||||
|
||||
return new TypedValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return {@literal false}.
|
||||
*/
|
||||
@Override
|
||||
public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue) { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.function.config;
|
||||
|
||||
import org.apache.geode.cache.execute.Execution;
|
||||
import org.apache.geode.cache.execute.Function;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.gemfire.function.config.AbstractFunctionExecutionConfigurationSource;
|
||||
import org.springframework.data.gemfire.function.config.AnnotationFunctionExecutionConfigurationSource;
|
||||
import org.springframework.data.gemfire.function.config.FunctionExecutionBeanDefinitionRegistrar;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link AbstractFunctionExecutionAutoConfigurationExtension} class extends SDG's {@link FunctionExecutionBeanDefinitionRegistrar}
|
||||
* to redefine the location of application POJO {@link Function} {@link Execution} interfaces.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.execute.Execution
|
||||
* @see org.apache.geode.cache.execute.Function
|
||||
* @see org.springframework.beans.factory.BeanFactory
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware
|
||||
* @see org.springframework.boot.autoconfigure.AutoConfigurationPackages
|
||||
* @see org.springframework.core.type.AnnotationMetadata
|
||||
* @see org.springframework.data.gemfire.function.config.FunctionExecutionBeanDefinitionRegistrar
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public abstract class AbstractFunctionExecutionAutoConfigurationExtension
|
||||
extends FunctionExecutionBeanDefinitionRegistrar implements BeanFactoryAware {
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
protected BeanFactory getBeanFactory() {
|
||||
|
||||
Assert.state(this.beanFactory != null, "BeanFactory was not properly configured");
|
||||
|
||||
return this.beanFactory;
|
||||
}
|
||||
|
||||
protected abstract Class<?> getConfiguration();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected AbstractFunctionExecutionConfigurationSource newAnnotationBasedFunctionExecutionConfigurationSource(
|
||||
AnnotationMetadata annotationMetadata) {
|
||||
|
||||
AnnotationMetadata metadata = AnnotationMetadata.introspect(getConfiguration());
|
||||
|
||||
return new AnnotationFunctionExecutionConfigurationSource(metadata) {
|
||||
|
||||
@Override
|
||||
public Iterable<String> getBasePackages() {
|
||||
return AutoConfigurationPackages.get(getBeanFactory());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.function.config;
|
||||
|
||||
import org.apache.geode.cache.execute.Execution;
|
||||
import org.apache.geode.cache.execute.Function;
|
||||
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.data.gemfire.function.config.EnableGemfireFunctionExecutions;
|
||||
|
||||
/**
|
||||
* The {@link GemFireFunctionExecutionAutoConfigurationRegistrar} class is a Spring {@link ImportBeanDefinitionRegistrar}
|
||||
* used to register SDG POJO interfaces defining Apache Geode {@link Function} {@link Execution Executions}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.execute.Execution
|
||||
* @see org.apache.geode.cache.execute.Function
|
||||
* @see org.springframework.data.gemfire.function.config.EnableGemfireFunctionExecutions
|
||||
* @see org.springframework.geode.function.config.AbstractFunctionExecutionAutoConfigurationExtension
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class GemFireFunctionExecutionAutoConfigurationRegistrar
|
||||
extends AbstractFunctionExecutionAutoConfigurationExtension {
|
||||
|
||||
@Override
|
||||
protected Class<?> getConfiguration() {
|
||||
return EnableGemfireFunctionExecutionsConfiguration.class;
|
||||
}
|
||||
|
||||
@EnableGemfireFunctionExecutions
|
||||
private static class EnableGemfireFunctionExecutionsConfiguration { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.function.support;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.geode.cache.execute.FunctionException;
|
||||
import org.apache.geode.cache.execute.ResultCollector;
|
||||
|
||||
/**
|
||||
* The {@link AbstractResultCollector} class is an abstract base implementation of the {@link ResultCollector} interface
|
||||
* encapsulating common functionality for collecting results from a Function execution.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.execute.ResultCollector
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class AbstractResultCollector<T, S> implements ResultCollector<T, S> {
|
||||
|
||||
protected static final String NOT_IMPLEMENTED = "Not Implemented";
|
||||
|
||||
protected static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;
|
||||
|
||||
private AtomicBoolean resultsEnded = new AtomicBoolean(false);
|
||||
|
||||
private S result = null;
|
||||
|
||||
@Override
|
||||
public synchronized S getResult() throws FunctionException {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public S getResult(long duration, TimeUnit unit) throws FunctionException, InterruptedException {
|
||||
|
||||
unit = resolveTimeUnit(unit);
|
||||
|
||||
long durationInMilliseconds = unit.toMillis(duration);
|
||||
long timeout = System.currentTimeMillis() + unit.toMillis(duration);
|
||||
long waitInMilliseconds = Math.max(50, Math.min(durationInMilliseconds / 5, durationInMilliseconds));
|
||||
|
||||
synchronized (this) {
|
||||
while (getResult() == null && System.currentTimeMillis() < timeout) {
|
||||
unit.timedWait(this, waitInMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
return getResult();
|
||||
}
|
||||
|
||||
protected synchronized void setResult(S result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
protected TimeUnit resolveTimeUnit(TimeUnit unit) {
|
||||
return unit != null ? unit : DEFAULT_TIME_UNIT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearResults() {
|
||||
setResult(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endResults() {
|
||||
this.resultsEnded.set(true);
|
||||
}
|
||||
|
||||
protected boolean hasResultsEnded() {
|
||||
return this.resultsEnded.get();
|
||||
}
|
||||
|
||||
protected boolean hasResultsNotEnded() {
|
||||
return !this.resultsEnded.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.function.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.geode.cache.execute.ResultCollector;
|
||||
import org.apache.geode.distributed.DistributedMember;
|
||||
|
||||
/**
|
||||
* The {@link SingleResultReturningCollector} class is an implementation of the {@link ResultCollector} interface
|
||||
* which returns a single {@link Object result}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.execute.ResultCollector
|
||||
* @see org.springframework.geode.function.support.AbstractResultCollector
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class SingleResultReturningCollector<T> extends AbstractResultCollector<T, T> {
|
||||
|
||||
@Override
|
||||
public void addResult(DistributedMember memberID, T resultOfSingleExecution) {
|
||||
setResult(extractSingleResult(resultOfSingleExecution));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T extractSingleResult(Object result) {
|
||||
|
||||
return (T) Optional.ofNullable(result)
|
||||
.filter(this::isInstanceOfIterableOrIterator)
|
||||
.map(this::toIterator)
|
||||
.filter(Iterator::hasNext)
|
||||
.map(Iterator::next)
|
||||
.map(this::extractSingleResult)
|
||||
.orElseGet(() -> isInstanceOfIterableOrIterator(result) ? null : result);
|
||||
}
|
||||
|
||||
private boolean isInstanceOfIterableOrIterator(Object obj) {
|
||||
return obj instanceof Iterable || obj instanceof Iterator;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Iterator<T> toIterator(Object obj) {
|
||||
return obj instanceof Iterator ? (Iterator<T>) obj : toIterator((Iterable<T>) obj);
|
||||
}
|
||||
|
||||
private <T> Iterator<T> toIterator(Iterable<T> iterable) {
|
||||
return iterable != null ? iterable.iterator() : Collections.emptyIterator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.jackson.databind.serializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.std.NumberSerializers;
|
||||
|
||||
/**
|
||||
* The {@link BigDecimalSerializer} class is a {@link NumberSerializers NumberSerializers.Base} serializer
|
||||
* for serializing {@link BigDecimal} values.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.math.BigDecimal
|
||||
* @see com.fasterxml.jackson.databind.ser.std.NumberSerializers
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class BigDecimalSerializer extends NumberSerializers.Base<BigDecimal> {
|
||||
|
||||
public static final BigDecimalSerializer INSTANCE = new BigDecimalSerializer();
|
||||
|
||||
public BigDecimalSerializer() {
|
||||
super(BigDecimal.class, JsonParser.NumberType.BIG_DECIMAL, "bigdecimal");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
|
||||
throws IOException {
|
||||
|
||||
jsonGenerator.writeNumber(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWithType(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider,
|
||||
TypeSerializer typeSerializer) throws IOException {
|
||||
|
||||
serialize(value, jsonGenerator, serializerProvider);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.jackson.databind.serializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.std.NumberSerializers;
|
||||
|
||||
/**
|
||||
* The {@link BigIntegerSerializer} class is a {@link NumberSerializers NumberSerializers.Base} serializer
|
||||
* for serializing {@link BigInteger} values.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.math.BigInteger
|
||||
* @see com.fasterxml.jackson.databind.ser.std.NumberSerializers
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class BigIntegerSerializer extends NumberSerializers.Base<BigInteger> {
|
||||
|
||||
public static final BigIntegerSerializer INSTANCE = new BigIntegerSerializer();
|
||||
|
||||
public BigIntegerSerializer() {
|
||||
super(BigInteger.class, JsonParser.NumberType.BIG_INTEGER, "biginteger");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(BigInteger value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
|
||||
throws IOException {
|
||||
|
||||
jsonGenerator.writeNumber(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWithType(BigInteger value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider,
|
||||
TypeSerializer typeSerializer) throws IOException {
|
||||
|
||||
serialize(value, jsonGenerator, serializerProvider);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.jackson.databind.serializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
|
||||
import com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase;
|
||||
import com.fasterxml.jackson.databind.ser.std.CollectionSerializer;
|
||||
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* The {@link TypelessCollectionSerializer} class is a custom, typeless {@link CollectionSerializer} implementation.
|
||||
*
|
||||
* This {@link AsArraySerializerBase} implementation is a lot like {@link CollectionSerializer}, however it excludes
|
||||
* unnecessary type metadata in the context of Apache Geode.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.Collection
|
||||
* @see com.fasterxml.jackson.core.JsonGenerator
|
||||
* @see com.fasterxml.jackson.databind.JsonSerializer
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
* @see com.fasterxml.jackson.databind.SerializerProvider
|
||||
* @see com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class TypelessCollectionSerializer extends AsArraySerializerBase<Collection<?>> {
|
||||
|
||||
protected static final boolean DEFAULT_UNWRAP_SINGLE = false;
|
||||
protected static final boolean DEFAULT_STATIC_TYPING = false;
|
||||
|
||||
public TypelessCollectionSerializer(ObjectMapper mapper) {
|
||||
super(Collection.class, mapper.getTypeFactory().constructType(Object.class), DEFAULT_STATIC_TYPING,
|
||||
null, null);
|
||||
}
|
||||
|
||||
public TypelessCollectionSerializer(TypelessCollectionSerializer serializer, BeanProperty property,
|
||||
TypeSerializer typeSerializer, JsonSerializer<?> elementSerializer) {
|
||||
|
||||
super(serializer, property, typeSerializer, elementSerializer, DEFAULT_UNWRAP_SINGLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWithType(Collection<?> value, JsonGenerator jsonGenerator,
|
||||
SerializerProvider serializerProvider, TypeSerializer typeSerializer) throws IOException {
|
||||
|
||||
serialize(value, jsonGenerator, serializerProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSingleElement(Collection<?> value) {
|
||||
return value != null && value.size() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeContents(Collection<?> value, JsonGenerator jsonGenerator,
|
||||
SerializerProvider serializerProvider) throws IOException {
|
||||
|
||||
jsonGenerator.setCurrentValue(value);
|
||||
|
||||
PropertySerializerMap serializers = this._dynamicSerializers;
|
||||
TypeSerializer typeSerializer = this._valueTypeSerializer;
|
||||
|
||||
int index = -1;
|
||||
|
||||
try {
|
||||
for (Object element : CollectionUtils.nullSafeCollection(value)) {
|
||||
|
||||
index++;
|
||||
|
||||
if (Objects.isNull(element)) {
|
||||
serializerProvider.defaultSerializeNull(jsonGenerator);
|
||||
}
|
||||
else {
|
||||
|
||||
Class<?> elementType = element.getClass();
|
||||
|
||||
JsonSerializer<Object> serializer = resolveSerializer(serializerProvider, elementType);
|
||||
|
||||
if (typeSerializer != null) {
|
||||
serializer.serializeWithType(element, jsonGenerator, serializerProvider, typeSerializer);
|
||||
}
|
||||
else {
|
||||
serializer.serialize(element, jsonGenerator, serializerProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception cause) {
|
||||
wrapAndThrow(serializerProvider, cause, value, index);
|
||||
}
|
||||
}
|
||||
|
||||
private JavaType constructSpecializedType(SerializerProvider serializerProvider, JavaType baseType, Class<?> subclass) {
|
||||
return serializerProvider.constructSpecializedType(baseType, subclass);
|
||||
}
|
||||
|
||||
private JsonSerializer<Object> resolveSerializer(SerializerProvider serializerProvider, Class<?> type)
|
||||
throws JsonMappingException {
|
||||
|
||||
JsonSerializer<Object> resolvedSerializer = this._elementSerializer;
|
||||
|
||||
if (Objects.isNull(resolvedSerializer)) {
|
||||
|
||||
PropertySerializerMap dynamicSerializers = this._dynamicSerializers;
|
||||
|
||||
resolvedSerializer = dynamicSerializers.serializerFor(type);
|
||||
|
||||
if (Objects.isNull(resolvedSerializer)) {
|
||||
resolvedSerializer = Objects.nonNull(this._elementType) && this._elementType.hasGenericTypes()
|
||||
? this._findAndAddDynamic(dynamicSerializers,
|
||||
constructSpecializedType(serializerProvider, this._elementType, type), serializerProvider)
|
||||
: this._findAndAddDynamic(dynamicSerializers, type, serializerProvider);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedSerializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypelessCollectionSerializer withResolved(BeanProperty property, TypeSerializer typeSerializer,
|
||||
JsonSerializer<?> elementSerializer, Boolean unwrapSingle) {
|
||||
|
||||
return new TypelessCollectionSerializer(this, property, typeSerializer, elementSerializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypelessCollectionSerializer _withValueTypeSerializer(TypeSerializer typeSerializer) {
|
||||
return new TypelessCollectionSerializer(this, this._property, typeSerializer, this._elementSerializer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.pdx;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.geode.cache.GemFireCache;
|
||||
import org.apache.geode.pdx.PdxSerializer;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.data.gemfire.mapping.MappingPdxSerializer;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Spring {@link BeanPostProcessor} used to register additional {@link Class types} handled by
|
||||
* the SDG {@link MappingPdxSerializer}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Class
|
||||
* @see java.util.function.Predicate
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
* @see org.apache.geode.pdx.PdxSerializer
|
||||
* @see org.springframework.beans.factory.config.BeanPostProcessor
|
||||
* @see org.springframework.data.gemfire.mapping.MappingPdxSerializer
|
||||
* @since 1.5.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class MappingPdxSerializerIncludedTypesRegistrar implements BeanPostProcessor {
|
||||
|
||||
/**
|
||||
* Factory methods used to construct a new instance of {@link MappingPdxSerializerIncludedTypesRegistrar}
|
||||
* initialized with given, required array of {@link Class} types that will be registered with
|
||||
* SDG's {@link MappingPdxSerializer} in order to de/serialize the specified {@link Class} types as PDX.
|
||||
*
|
||||
* @param types array of {@link Class} types to be de/serialized as PDX using SDG's {@link MappingPdxSerializer};
|
||||
* must not be {@literal null}.
|
||||
* @return a new instance of {@link MappingPdxSerializerIncludedTypesRegistrar}
|
||||
* @see #MappingPdxSerializerIncludedTypesRegistrar(Class[])
|
||||
*/
|
||||
public static @NonNull MappingPdxSerializerIncludedTypesRegistrar with(Class<?>... types) {
|
||||
return new MappingPdxSerializerIncludedTypesRegistrar(types);
|
||||
}
|
||||
|
||||
private final Class<?>[] types;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link MappingPdxSerializerIncludedTypesRegistrar} initialized with given,
|
||||
* required array of {@link Class} types that will be registered with SDG's {@link MappingPdxSerializer}
|
||||
* in order to de/serialize the specified {@link Class} types as PDX.
|
||||
*
|
||||
* @param types array of {@link Class} types to be de/serialized as PDX using SDG's {@link MappingPdxSerializer};
|
||||
* must not be {@literal null}.
|
||||
* @see java.lang.Class
|
||||
*/
|
||||
public MappingPdxSerializerIncludedTypesRegistrar(@NonNull Class<?>[] types) {
|
||||
|
||||
this.types = Arrays.stream(ArrayUtils.nullSafeArray(types, Class.class))
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(Class[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the array of {@link Class} types to register with SDG's {@link MappingPdxSerializer} in order to
|
||||
* de/serialize the {@link Class} types as PDX.
|
||||
*
|
||||
* @return the configured array of {@link Class} types to be registered with SDG's {@link MappingPdxSerializer}
|
||||
* in order to de/serialize the {@link Class} types as PDX; never {@literal null}.
|
||||
* @see java.lang.Class
|
||||
*/
|
||||
protected @NonNull Class<?>[] getTypes() {
|
||||
return this.types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes an {@link Optional} {@link Predicate} consisting of the configured array {@link Class} types
|
||||
* used to match possible types de/serialized as PDX using SDG's {@link MappingPdxSerializer}.
|
||||
*
|
||||
* @return an {@link Optional} composite {@link Predicate} consisting of the configured
|
||||
* array of {@link Class} types.
|
||||
* @see java.util.function.Predicate
|
||||
* @see java.util.Optional
|
||||
* @see #getTypes()
|
||||
*/
|
||||
protected Optional<Predicate<Class<?>>> getCompositeIncludeTypeFilter() {
|
||||
|
||||
Predicate<Class<?>> compositeIncludeTypeFilter = null;
|
||||
|
||||
for (Class<?> type : getTypes()) {
|
||||
if (type != null) {
|
||||
compositeIncludeTypeFilter = compositeIncludeTypeFilter != null
|
||||
? compositeIncludeTypeFilter.or(newIncludeTypeFilter(type))
|
||||
: newIncludeTypeFilter(type);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.ofNullable(compositeIncludeTypeFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method used to construct a new {@link Class type} include {@link Predicate filter}
|
||||
* that can be registered with SDG's {@link MappingPdxSerializer}.
|
||||
*
|
||||
* The {@link Predicate} matches tested {@link Class types} that are
|
||||
* {@link Class#isAssignableFrom(Class) assignable from} the given {@link Class type}.
|
||||
*
|
||||
* @param type {@link Class} used as the basis for matching in the {@link Predicate}.
|
||||
* @return an (optional} {@link Predicate} from the given {@link Class type};
|
||||
* returns {@literal null} if the given {@link Class type} is {@literal null}.
|
||||
* @see java.util.function.Predicate
|
||||
* @see java.lang.Class
|
||||
*/
|
||||
protected @Nullable Predicate<Class<?>> newIncludeTypeFilter(@Nullable Class<?> type) {
|
||||
|
||||
return type != null
|
||||
? testType -> Objects.nonNull(testType) && type.isAssignableFrom(testType)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the configured {@link Class} types with SDG's {@link MappingPdxSerializer} providing the bean
|
||||
* to post process after initialization is a {@link GemFireCache} instance and SDG's {@link MappingPdxSerializer}
|
||||
* was configured as the cache's {@link PdxSerializer} used to de/serialize objects of the specified {@link Class}
|
||||
* types.
|
||||
*
|
||||
* @param bean {@link Object bean} to evaluate.
|
||||
* @param beanName {@link String} specifying the {@literal name} of the bean in the Spring container.
|
||||
* @return the given {@link Object} bean.
|
||||
* @throws BeansException if post processing of the bean fails.
|
||||
*/
|
||||
@Override
|
||||
public @Nullable Object postProcessAfterInitialization(@Nullable Object bean, @Nullable String beanName)
|
||||
throws BeansException {
|
||||
|
||||
if (bean instanceof GemFireCache) {
|
||||
|
||||
GemFireCache cache = (GemFireCache) bean;
|
||||
|
||||
PdxSerializer pdxSerializer = cache.getPdxSerializer();
|
||||
|
||||
if (pdxSerializer instanceof MappingPdxSerializer) {
|
||||
|
||||
MappingPdxSerializer mappingPdxSerializer = (MappingPdxSerializer) pdxSerializer;
|
||||
|
||||
getCompositeIncludeTypeFilter().ifPresent(mappingPdxSerializer::setIncludeTypeFilters);
|
||||
}
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.pdx;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.geode.pdx.PdxFieldDoesNotExistException;
|
||||
import org.apache.geode.pdx.PdxFieldTypeMismatchException;
|
||||
import org.apache.geode.pdx.PdxInstance;
|
||||
import org.apache.geode.pdx.WritablePdxInstance;
|
||||
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.PropertyAccessor;
|
||||
import org.springframework.beans.PropertyAccessorFactory;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link PdxInstance} implementation that adapts (wraps) a non-null {@link Object} as a {@link PdxInstance}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.beans.PropertyDescriptor
|
||||
* @see java.lang.reflect.Field
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see org.apache.geode.pdx.WritablePdxInstance
|
||||
* @see org.springframework.beans.BeanWrapper
|
||||
* @see org.springframework.beans.PropertyAccessor
|
||||
* @see org.springframework.beans.PropertyAccessorFactory
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class ObjectPdxInstanceAdapter implements PdxInstance {
|
||||
|
||||
protected static final String CLASS_PROPERTY_NAME = "class";
|
||||
protected static final String ID_PROPERTY_NAME = "id";
|
||||
|
||||
private static void assertCondition(boolean condition, Supplier<RuntimeException> runtimeExceptionSupplier) {
|
||||
if (!condition) {
|
||||
throw runtimeExceptionSupplier.get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of the {@link ObjectPdxInstanceAdapter} from
|
||||
* the given {@literal target} {@link Object}.
|
||||
*
|
||||
* @param target {@link Object} to adapt as a {@link PdxInstance}; must not be {@literal null}.
|
||||
* @return a new instance of {@link ObjectPdxInstanceAdapter}.
|
||||
* @throws IllegalArgumentException if {@link Object} is {@literal null}.
|
||||
* @see #ObjectPdxInstanceAdapter(Object)
|
||||
*/
|
||||
public static ObjectPdxInstanceAdapter from(@NonNull Object target) {
|
||||
return new ObjectPdxInstanceAdapter(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe factory method used to unwrap the given {@link PdxInstance}, returning the underlying, target
|
||||
* {@link PdxInstance#getObject() Object} upon which this {@link PdxInstance} is based.
|
||||
*
|
||||
* @param pdxInstance {@link PdxInstance} to unwrap.
|
||||
* @return the underlying, target {@link PdxInstance#getObject() Object} from the given {@link PdxInstance}.
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
public static @Nullable Object unwrap(@Nullable PdxInstance pdxInstance) {
|
||||
|
||||
return pdxInstance instanceof ObjectPdxInstanceAdapter
|
||||
? pdxInstance.getObject()
|
||||
: pdxInstance;
|
||||
}
|
||||
|
||||
private final AtomicReference<String> resolvedIdentityFieldName = new AtomicReference<>(null);
|
||||
|
||||
private transient final BeanWrapper beanWrapper;
|
||||
|
||||
private final Object target;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link ObjectPdxInstanceAdapter} initialized with the given {@link Object}.
|
||||
*
|
||||
* @param target {@link Object} to adapt as a {@link PdxInstance}; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link Object} is {@literal null}.
|
||||
* @see java.lang.Object
|
||||
*/
|
||||
public ObjectPdxInstanceAdapter(Object target) {
|
||||
|
||||
Assert.notNull(target, "Object to adapt must not be null");
|
||||
|
||||
this.target = target;
|
||||
this.beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link BeanWrapper} wrapping the {@literal target} {@link Object} in order to access the {@link Object}
|
||||
* as a Java bean using JavaBeans conventions.
|
||||
*
|
||||
* @return a {@link BeanWrapper} for the {@literal target} {@link Object}; never {@literal null}.
|
||||
* @see org.springframework.beans.BeanWrapper
|
||||
*/
|
||||
protected @NonNull BeanWrapper getBeanWrapper() {
|
||||
return this.beanWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Class#getName()} of the underlying, target {@link Object}.
|
||||
*
|
||||
* @return the {@link Class#getName()} of the underlying, target {@link Object}.
|
||||
* @see java.lang.Object#getClass()
|
||||
* @see java.lang.Class#getName()
|
||||
* @see #getObject()
|
||||
*/
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return getObject().getClass().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this {@link PdxInstance} can be deserialized back into an {@link Object}.
|
||||
*
|
||||
* This method effectively returns {@literal true} since this {@link PdxInstance} implementation is an adapter
|
||||
* for an underlying, target {@link Object} in the first place.
|
||||
*
|
||||
* @return a boolean value indicating whether this {@link PdxInstance} can be deserialized
|
||||
* back into an {@link Object}.
|
||||
* @see #getObject()
|
||||
*/
|
||||
@Override
|
||||
public boolean isDeserializable() {
|
||||
return getObject() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the underlying, target {@link Object} is an {@link Enum enumerated value} {@link Class type}.
|
||||
*
|
||||
* @return a boolean value indicating whether the underlying, target {@link Object}
|
||||
* is an {@link Enum enumerated value} {@link Class type}.
|
||||
* @see java.lang.Object#getClass()
|
||||
* @see java.lang.Class#isEnum()
|
||||
* @see #getObject()
|
||||
*/
|
||||
@Override
|
||||
public boolean isEnum() {
|
||||
return getObject().getClass().isEnum();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Object value} for the {@link PropertyDescriptor property} identified by
|
||||
* the given {@link String field name} on the underlying, target {@link Object}.
|
||||
*
|
||||
* @param fieldName {@link String} containing the name of the field to get the {@link Object value} for.
|
||||
* @return the {@link Object value} for the {@link PropertyDescriptor property} identified by
|
||||
* the given {@link String field name} on the underlying, target {@link Object}.
|
||||
* @see org.springframework.beans.BeanWrapper#getPropertyValue(String)
|
||||
* @see #getBeanWrapper()
|
||||
*/
|
||||
@Override
|
||||
public Object getField(String fieldName) {
|
||||
|
||||
BeanWrapper beanWrapper = getBeanWrapper();
|
||||
|
||||
return beanWrapper.isReadableProperty(fieldName)
|
||||
? beanWrapper.getPropertyValue(fieldName)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link List} of {@link String field names} based on the {@link PropertyDescriptor propeties}
|
||||
* from the underlying, target {@link Object}.
|
||||
*
|
||||
* @return a {@link List} of {@link String field names} / {@link PropertyDescriptor properties} serialized
|
||||
* in the PDX bytes for the underlying, target {@link Object}.
|
||||
* @see org.springframework.beans.BeanWrapper#getPropertyDescriptors()
|
||||
* @see java.beans.PropertyDescriptor
|
||||
* @see #getBeanWrapper()
|
||||
*/
|
||||
@Override
|
||||
public List<String> getFieldNames() {
|
||||
|
||||
PropertyDescriptor[] propertyDescriptors =
|
||||
ArrayUtils.nullSafeArray(getBeanWrapper().getPropertyDescriptors(), PropertyDescriptor.class);
|
||||
|
||||
return Arrays.stream(propertyDescriptors)
|
||||
.map(PropertyDescriptor::getName)
|
||||
.filter(propertyName -> !CLASS_PROPERTY_NAME.equals(propertyName))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given {@link String field name} is an identifier for this {@link PdxInstance}.
|
||||
*
|
||||
* @param fieldName {@link String} containing the name of the field to evaluate.
|
||||
* @return a boolean value indicating whether the given {@link String field name} is an identifier for
|
||||
* this {@link PdxInstance}.
|
||||
* @see #resolveIdentityFieldNameFromProperty(BeanWrapper)
|
||||
*/
|
||||
@Override
|
||||
public boolean isIdentityField(String fieldName) {
|
||||
|
||||
String resolvedIdentityFieldName = this.resolvedIdentityFieldName.updateAndGet(it ->
|
||||
StringUtils.hasText(it) ? it : resolveIdentityFieldNameFromProperty());
|
||||
|
||||
return StringUtils.hasText(resolvedIdentityFieldName) && resolvedIdentityFieldName.equals(fieldName);
|
||||
}
|
||||
|
||||
// Identifier Search Algorithm: @Id Property -> @Id Field -> "id" Property
|
||||
|
||||
@Nullable String resolveIdentityFieldNameFromProperty() {
|
||||
return resolveIdentityFieldNameFromProperty(getBeanWrapper());
|
||||
}
|
||||
|
||||
private @Nullable String resolveIdentityFieldNameFromProperty(@NonNull BeanWrapper beanWrapper) {
|
||||
|
||||
List<PropertyDescriptor> properties =
|
||||
Arrays.asList(ArrayUtils.nullSafeArray(beanWrapper.getPropertyDescriptors(), PropertyDescriptor.class));
|
||||
|
||||
Optional<PropertyDescriptor> atIdAnnotatedProperty = properties.stream()
|
||||
.filter(this::isAtIdAnnotatedProperty)
|
||||
.findFirst();
|
||||
|
||||
return atIdAnnotatedProperty
|
||||
.map(PropertyDescriptor::getName)
|
||||
.orElseGet(() -> resolveIdentityFieldNameFromField(beanWrapper));
|
||||
}
|
||||
|
||||
private boolean isAtIdAnnotatedProperty(@Nullable PropertyDescriptor propertyDescriptor) {
|
||||
|
||||
return Optional.ofNullable(propertyDescriptor)
|
||||
.map(PropertyDescriptor::getReadMethod)
|
||||
.map(method -> AnnotationUtils.findAnnotation(method, Id.class))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
private @Nullable String resolveIdentityFieldNameFromField(@NonNull BeanWrapper beanWrapper) {
|
||||
|
||||
List<Field> fields =
|
||||
Arrays.asList(ArrayUtils.nullSafeArray(beanWrapper.getWrappedClass().getDeclaredFields(), Field.class));
|
||||
|
||||
Optional<PropertyDescriptor> atIdAnnotatedProperty = fields.stream()
|
||||
.map(field -> getPropertyForAtIdAnnotatedField(beanWrapper, field))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst();
|
||||
|
||||
return atIdAnnotatedProperty
|
||||
.map(PropertyDescriptor::getName)
|
||||
.orElseGet(() -> beanWrapper.isReadableProperty(ID_PROPERTY_NAME)
|
||||
? ID_PROPERTY_NAME
|
||||
: null);
|
||||
}
|
||||
|
||||
private @Nullable PropertyDescriptor getPropertyForAtIdAnnotatedField(@NonNull BeanWrapper beanWrapper,
|
||||
@Nullable Field field) {
|
||||
|
||||
return Optional.ofNullable(field)
|
||||
.filter(it -> beanWrapper.isReadableProperty(it.getName()))
|
||||
.filter(it -> Objects.nonNull(AnnotationUtils.findAnnotation(it, Id.class)))
|
||||
.map(it -> beanWrapper.getPropertyDescriptor(it.getName()))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@literal target} {@link Object} being adapted by this {@link PdxInstance}.
|
||||
*
|
||||
* @return the {@literal target} {@link Object} being adapted by this {@link PdxInstance}; never {@literal null}.
|
||||
* @see java.lang.Object
|
||||
*/
|
||||
@Override
|
||||
public Object getObject() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
ObjectPdxInstanceAdapter getParent() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public WritablePdxInstance createWriter() {
|
||||
|
||||
return new WritablePdxInstance() {
|
||||
|
||||
@Override
|
||||
public void setField(String fieldName, Object value) {
|
||||
withPropertyAccessorFor(fieldName, value).setPropertyValue(fieldName, value);
|
||||
}
|
||||
|
||||
private PropertyAccessor withPropertyAccessorFor(String fieldName, Object value) {
|
||||
|
||||
assertFieldIsPresent(fieldName);
|
||||
|
||||
BeanWrapper beanWrapper = getBeanWrapper();
|
||||
|
||||
assertFieldIsWritable(beanWrapper, fieldName);
|
||||
assertValueIsTypeMatch(beanWrapper, fieldName, value);
|
||||
|
||||
return beanWrapper;
|
||||
}
|
||||
|
||||
private void assertFieldIsPresent(String fieldName) {
|
||||
|
||||
Supplier<String> pdxFieldNotFoundExceptionMessageSupplier = () ->
|
||||
String.format("Field [%1$s] does not exist on Object [%2$s]", fieldName, getClassName());
|
||||
|
||||
assertCondition(hasField(fieldName),
|
||||
() -> new PdxFieldDoesNotExistException(pdxFieldNotFoundExceptionMessageSupplier.get()));
|
||||
}
|
||||
|
||||
private void assertFieldIsWritable(BeanWrapper beanWrapper, String fieldName) {
|
||||
|
||||
Supplier<String> pdxFieldNotWritableExceptionMessageSupplier = () ->
|
||||
String.format("Field [%1$s] of Object [%2$s] is not writable", fieldName, getClassName());
|
||||
|
||||
assertCondition(beanWrapper.isWritableProperty(fieldName),
|
||||
() -> new PdxFieldNotWritableException(pdxFieldNotWritableExceptionMessageSupplier.get()));
|
||||
}
|
||||
|
||||
private void assertValueIsTypeMatch(BeanWrapper beanWrapper, String fieldName, Object value) {
|
||||
|
||||
PropertyDescriptor property = beanWrapper.getPropertyDescriptor(fieldName);
|
||||
|
||||
Supplier<String> typeMismatchExceptionMessageSupplier = () ->
|
||||
String.format("Value [%1$s] of type [%2$s] does not match field [%3$s] of type [%4$s] on Object [%5$s]",
|
||||
value, ObjectUtils.nullSafeClassName(value), fieldName, property.getPropertyType().getName(), getClassName());
|
||||
|
||||
assertCondition(isTypeMatch(property, value),
|
||||
() -> new PdxFieldTypeMismatchException(typeMismatchExceptionMessageSupplier.get()));
|
||||
}
|
||||
|
||||
private boolean isTypeMatch(PropertyDescriptor property, Object value) {
|
||||
return value == null || property.getPropertyType().isInstance(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return getParent().getClassName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeserializable() {
|
||||
return getParent().isDeserializable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnum() {
|
||||
return getParent().isEnum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getField(String fieldName) {
|
||||
return getParent().getField(fieldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getFieldNames() {
|
||||
return getParent().getFieldNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdentityField(String fieldName) {
|
||||
return getParent().isIdentityField(fieldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getObject() {
|
||||
return getParent().getObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WritablePdxInstance createWriter() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasField(String fieldName) {
|
||||
return getParent().hasField(fieldName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given {@link String field name} is a {@link PropertyDescriptor property}
|
||||
* on the underlying, target {@link Object}.
|
||||
*
|
||||
* @param fieldName {@link String} containing the name of the field to match against
|
||||
* a {@link PropertyDescriptor property} from the underlying, target {@link Object}.
|
||||
* @return a boolean value that determines whether the given {@link String field name}
|
||||
* is a {@link PropertyDescriptor property} on the underlying, target {@link Object}.
|
||||
* @see #getFieldNames()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasField(String fieldName) {
|
||||
return getFieldNames().contains(fieldName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.security.support;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.geode.security.AuthenticationFailedException;
|
||||
import org.apache.geode.security.ResourcePermission;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.gemfire.support.LazyWiringDeclarableSupport;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link SecurityManagerProxy} class is an Apache Geode {@link org.apache.geode.security.SecurityManager}
|
||||
* proxy implementation delegating to a backing {@link org.apache.geode.security.SecurityManager} implementation
|
||||
* which is registered as a managed bean in a Spring context.
|
||||
*
|
||||
* The idea behind this {@link org.apache.geode.security.SecurityManager} is to enable users to be able to configure
|
||||
* and manage the {@code SecurityManager} as a Spring bean. However, Apache Geode require
|
||||
* the {@link org.apache.geode.security.SecurityManager} to be configured using a System property when launching
|
||||
* Apache Geode Servers with Gfsh, which makes it difficult to "manage" the {@code SecurityManager} instance.
|
||||
*
|
||||
* Therefore, this implementation allows a developer to set the Apache Geode System property using this proxy...
|
||||
*
|
||||
* <code>
|
||||
* gemfire.security-manager=org.springframework.geode.security.support.SecurityManagerProxy
|
||||
* </code>
|
||||
*
|
||||
* And then declare and define a bean in the Spring context implementing the
|
||||
* {@link org.apache.geode.security.SecurityManager} interface...
|
||||
*
|
||||
* <code>
|
||||
* Configuration
|
||||
* class MyApplicationConfiguration {
|
||||
*
|
||||
* Bean
|
||||
* ExampleSecurityManager exampleSecurityManager(Environment environment) {
|
||||
* return new ExampleSecurityManager(environment);
|
||||
* }
|
||||
*
|
||||
* ...
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.security.ResourcePermission
|
||||
* @see org.apache.geode.security.SecurityManager
|
||||
* @see org.springframework.beans.factory.BeanFactory
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware
|
||||
* @see org.springframework.beans.factory.DisposableBean
|
||||
* @see org.springframework.beans.factory.annotation.Autowired
|
||||
* @see org.springframework.data.gemfire.support.LazyWiringDeclarableSupport
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class SecurityManagerProxy extends LazyWiringDeclarableSupport
|
||||
implements org.apache.geode.security.SecurityManager, DisposableBean, BeanFactoryAware {
|
||||
|
||||
private static final AtomicReference<SecurityManagerProxy> INSTANCE = new AtomicReference<>();
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private org.apache.geode.security.SecurityManager securityManager;
|
||||
|
||||
/**
|
||||
* Returns a reference to the single {@link SecurityManagerProxy} instance configured by Apache Geode in startup.
|
||||
*
|
||||
* @return a reference to the single {@link SecurityManagerProxy} instance.
|
||||
*/
|
||||
public static SecurityManagerProxy getInstance() {
|
||||
|
||||
SecurityManagerProxy securityManagerProxy = INSTANCE.get();
|
||||
|
||||
Assert.state(securityManagerProxy != null, "SecurityManagerProxy was not configured");
|
||||
|
||||
return securityManagerProxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link SecurityManagerProxy}, which will delegate all Apache Geode
|
||||
* security operations to a Spring managed {@link org.apache.geode.security.SecurityManager} bean.
|
||||
*/
|
||||
public SecurityManagerProxy() {
|
||||
INSTANCE.compareAndSet(null, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a reference to the current Spring {@link BeanFactory}.
|
||||
*
|
||||
* @param beanFactory reference to the current Spring {@link BeanFactory}.
|
||||
* @throws BeansException if this operation fails to configure the reference to the {@link BeanFactory}.
|
||||
* @see org.springframework.beans.factory.BeanFactory
|
||||
*/
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a reference to the Apache Geode {@link org.apache.geode.security.SecurityManager} instance
|
||||
* delegated to by this {@link SecurityManagerProxy}.
|
||||
*
|
||||
* @param securityManager reference to the underlying Apache Geode {@link org.apache.geode.security.SecurityManager}
|
||||
* instance delegated to by this {@link SecurityManagerProxy}.
|
||||
* @throws IllegalArgumentException if the {@link org.apache.geode.security.SecurityManager} reference
|
||||
* is {@literal null}.
|
||||
* @see org.apache.geode.security.SecurityManager
|
||||
*/
|
||||
@Autowired
|
||||
public void setSecurityManager(org.apache.geode.security.SecurityManager securityManager) {
|
||||
|
||||
Assert.notNull(securityManager, "SecurityManager must not be null");
|
||||
|
||||
this.securityManager = securityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the Apache Geode {@link org.apache.geode.security.SecurityManager} instance
|
||||
* delegated to by this {@link SecurityManagerProxy}.
|
||||
*
|
||||
* @return a reference to the underlying {@link org.apache.geode.security.SecurityManager} instance
|
||||
* delegated to by this {@link SecurityManagerProxy}.
|
||||
* @throws IllegalStateException if the configured {@link org.apache.geode.security.SecurityManager}
|
||||
* was not properly configured.
|
||||
* @see org.apache.geode.security.SecurityManager
|
||||
*/
|
||||
protected org.apache.geode.security.SecurityManager getSecurityManager() {
|
||||
|
||||
Assert.state(this.securityManager != null, "No SecurityManager configured");
|
||||
|
||||
return this.securityManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object authenticate(Properties properties) throws AuthenticationFailedException {
|
||||
return getSecurityManager().authenticate(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authorize(Object principal, ResourcePermission permission) {
|
||||
return getSecurityManager().authorize(principal, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
getSecurityManager().close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
super.destroy();
|
||||
INSTANCE.set(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BeanFactory locateBeanFactory() {
|
||||
return this.beanFactory != null ? this.beanFactory : super.locateBeanFactory();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.security.support;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.geode.security.AuthenticationFailedException;
|
||||
import org.apache.geode.security.ResourcePermission;
|
||||
|
||||
/**
|
||||
* {@link SecurityManagerSupport} is an abstract base class implementing Apache Geode's
|
||||
* {@link org.apache.geode.security.SecurityManager} interface, providing default implementations of the
|
||||
* {@link org.apache.geode.security.SecurityManager} auth methods.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.security.SecurityManager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class SecurityManagerSupport implements org.apache.geode.security.SecurityManager {
|
||||
|
||||
protected static final boolean DEFAULT_AUTHORIZATION = false;
|
||||
|
||||
@Override
|
||||
public void init(Properties securityProperties) { }
|
||||
|
||||
@Override
|
||||
public Object authenticate(Properties credentials) throws AuthenticationFailedException {
|
||||
return new AuthenticationFailedException("Authentication Provider Not Present");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authorize(Object principal, ResourcePermission permission) {
|
||||
return DEFAULT_AUTHORIZATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2017-present 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.test.context;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigRegistry;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.geode.context.annotation.RefreshableAnnotationConfigApplicationContext;
|
||||
import org.springframework.test.context.ContextConfigurationAttributes;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.support.AbstractContextLoader;
|
||||
|
||||
/**
|
||||
* An {@link AbstractContextLoader} from the Spring {@link TestContext} Framework used to load
|
||||
* a {@link RefreshableAnnotationConfigApplicationContext}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @see org.springframework.context.annotation.AnnotationConfigRegistry
|
||||
* @see org.springframework.geode.context.annotation.RefreshableAnnotationConfigApplicationContext
|
||||
* @see org.springframework.test.context.ContextConfigurationAttributes
|
||||
* @see org.springframework.test.context.MergedContextConfiguration
|
||||
* @see org.springframework.test.context.TestContext
|
||||
* @see org.springframework.test.context.support.AbstractContextLoader
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class TestRefreshableApplicationContextLoader extends AbstractContextLoader {
|
||||
|
||||
protected static final String DEFAULT_RESOURCE_SUFFIX = "-context";
|
||||
|
||||
private Class<?> testClass;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
|
||||
|
||||
super.processContextConfiguration(configAttributes);
|
||||
|
||||
this.testClass = configAttributes.getDeclaringClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) {
|
||||
|
||||
ConfigurableApplicationContext applicationContext =
|
||||
configure(newApplicationContext(mergedConfig), mergedConfig);
|
||||
|
||||
applicationContext.registerShutdownHook();
|
||||
applicationContext.refresh();
|
||||
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the {@link RefreshableAnnotationConfigApplicationContext} initialized from
|
||||
* the given {{@link TestContext} @link MergedContextConfiguration merged configuration meta-data}.
|
||||
*
|
||||
* @param contextConfiguration {@link MergedContextConfiguration} from the {@link TestContext} used to configure
|
||||
* and bootstrap a new {@link RefreshableAnnotationConfigApplicationContext}.
|
||||
* @return a configured and bootstrapped {@link ConfigurableApplicationContext} implementation.
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @see org.springframework.geode.context.annotation.RefreshableAnnotationConfigApplicationContext
|
||||
* @see org.springframework.test.context.MergedContextConfiguration
|
||||
* @see #prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)
|
||||
*/
|
||||
protected ConfigurableApplicationContext newApplicationContext(MergedContextConfiguration contextConfiguration) {
|
||||
|
||||
RefreshableAnnotationConfigApplicationContext applicationContext =
|
||||
new RefreshableAnnotationConfigApplicationContext(contextConfiguration.getParentApplicationContext());
|
||||
|
||||
prepareContext(applicationContext, contextConfiguration);
|
||||
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext configure(ConfigurableApplicationContext applicationContext,
|
||||
MergedContextConfiguration contextConfiguration) {
|
||||
|
||||
applicationContext = configureComponentClasses(applicationContext, contextConfiguration);
|
||||
applicationContext = configureScan(applicationContext, contextConfiguration);
|
||||
|
||||
customizeContext(applicationContext, contextConfiguration);
|
||||
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext configureComponentClasses(
|
||||
ConfigurableApplicationContext applicationContext, MergedContextConfiguration contextConfiguration) {
|
||||
|
||||
Optional.ofNullable(applicationContext)
|
||||
.filter(it -> ArrayUtils.isNotEmpty(contextConfiguration.getClasses()))
|
||||
.filter(AnnotationConfigRegistry.class::isInstance)
|
||||
.map(AnnotationConfigRegistry.class::cast)
|
||||
.ifPresent(registry -> registry.register(contextConfiguration.getClasses()));
|
||||
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
private ConfigurableApplicationContext configureScan(ConfigurableApplicationContext applicationContext,
|
||||
MergedContextConfiguration contextConfiguration) {
|
||||
|
||||
Optional.ofNullable(applicationContext)
|
||||
.filter(it -> ArrayUtils.isNotEmpty(contextConfiguration.getLocations()))
|
||||
.filter(AnnotationConfigRegistry.class::isInstance)
|
||||
.map(AnnotationConfigRegistry.class::cast)
|
||||
.ifPresent(registry -> registry.scan(contextConfiguration.getLocations()));
|
||||
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public ApplicationContext loadContext(String... locations) {
|
||||
return loadContext(new MergedContextConfiguration(this.testClass, locations, new Class[0], new String[0],
|
||||
this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
protected String getResourceSuffix() {
|
||||
return DEFAULT_RESOURCE_SUFFIX;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# Spring Boot for Apache Geode Application Listeners
|
||||
org.springframework.context.ApplicationListener=\
|
||||
org.springframework.geode.context.logging.GeodeLoggingApplicationListener,\
|
||||
org.springframework.geode.context.logging.EnvironmentLoggingApplicationListener
|
||||
1782
spring-geode-project/spring-geode/src/main/resources/changelog.txt
Normal file
1782
spring-geode-project/spring-geode/src/main/resources/changelog.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2017-present 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 example.app.crm.config;
|
||||
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
|
||||
import org.springframework.data.cassandra.config.CqlSessionFactoryBean;
|
||||
import org.springframework.data.gemfire.tests.util.IOUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Base test configuration used to configure and bootstrap an Apache Cassandra database with a schema and data.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.core.io.Resource
|
||||
* @see org.springframework.data.cassandra.config.AbstractCassandraConfiguration
|
||||
* @see org.springframework.data.cassandra.config.CqlSessionFactoryBean
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public abstract class TestCassandraConfiguration extends AbstractCassandraConfiguration {
|
||||
|
||||
protected static final int CASSANDRA_DEFAULT_PORT = CqlSessionFactoryBean.DEFAULT_PORT;
|
||||
|
||||
private static final String CASSANDRA_DATA_CQL = "cassandra-data.cql";
|
||||
private static final String CASSANDRA_SCHEMA_CQL = "cassandra-schema.cql";
|
||||
private static final String LOCAL_DATA_CENTER = "datacenter1";
|
||||
private static final String KEYSPACE_NAME = "CustomerService";
|
||||
private static final String SESSION_NAME = "CustomerServiceCluster";
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected String getKeyspaceName() {
|
||||
return KEYSPACE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLocalDataCenter() {
|
||||
return LOCAL_DATA_CENTER;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected String getSessionName() {
|
||||
return SESSION_NAME;
|
||||
}
|
||||
|
||||
/*
|
||||
@Nullable @Override
|
||||
protected KeyspacePopulator keyspacePopulator() {
|
||||
return cqlSession -> loadCassandraCqlScripts().forEach(cqlSession::execute);
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO: Remove use of deprecation after Spring Data for Apache Cassandra issues are resolved!
|
||||
@Override
|
||||
protected List<String> getStartupScripts() {
|
||||
|
||||
List<String> startupScripts = new ArrayList<>(super.getStartupScripts());
|
||||
|
||||
startupScripts.addAll(readLines(new ClassPathResource(CASSANDRA_SCHEMA_CQL)));
|
||||
startupScripts.addAll(readLines(new ClassPathResource(CASSANDRA_DATA_CQL)));
|
||||
|
||||
return startupScripts;
|
||||
}
|
||||
|
||||
private @NonNull List<String> readLines(@NonNull Resource resource) {
|
||||
|
||||
BufferedReader resourceReader = null;
|
||||
|
||||
try {
|
||||
|
||||
resourceReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
|
||||
|
||||
return resourceReader.lines()
|
||||
.filter(StringUtils::hasText)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
catch (IOException cause) {
|
||||
throw newRuntimeException(cause, "Failed to read from Resource [%s]", resource);
|
||||
}
|
||||
finally {
|
||||
IOUtils.close(resourceReader);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2017-present 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 example.app.crm.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
|
||||
/**
|
||||
* Spring {@link @Configuration} for Apache Cassandra using Testcontainers.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.Profile
|
||||
* @see org.testcontainers.containers.GenericContainer
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("inline-caching-cassandra")
|
||||
@SuppressWarnings("unused")
|
||||
public class TestcontainersCassandraConfiguration extends TestCassandraConfiguration {
|
||||
|
||||
private static final String CASSANDRA_DOCKER_IMAGE_NAME = "cassandra:latest";
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("rawtypes")
|
||||
GenericContainer cassandraContainer() {
|
||||
|
||||
GenericContainer cassandraContainer = newCustomCassandraContainer();
|
||||
|
||||
cassandraContainer.start();
|
||||
|
||||
return cassandraContainer;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private GenericContainer newCassandraContainer() {
|
||||
return new GenericContainer(CASSANDRA_DOCKER_IMAGE_NAME)
|
||||
.withExposedPorts(CASSANDRA_DEFAULT_PORT);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private GenericContainer newCustomCassandraContainer() {
|
||||
|
||||
return newCassandraContainer()
|
||||
.withEnv("HEAP_NEWSIZE", "128M")
|
||||
.withEnv("MAX_HEAP_SIZE", "1024M")
|
||||
.withEnv("JVM_OPTS", "-Dcassandra.skip_wait_for_gossip_to_settle=0 -Dcassandra.initial_token=0")
|
||||
.withEnv("CASSANDRA_SNITCH", "GossipingPropertyFileSnitch");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getContactPoints() {
|
||||
return cassandraContainer().getContainerIpAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPort() {
|
||||
return cassandraContainer().getFirstMappedPort();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2017-present 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 example.app.crm.model;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.cassandra.core.mapping.Indexed;
|
||||
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
|
||||
import org.springframework.data.gemfire.mapping.annotation.Region;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* The {@link Customer} class is an Abstract Data Type (ADT) modeling a customer.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see lombok
|
||||
* @see org.springframework.data.annotation.Id
|
||||
* @see org.springframework.data.gemfire.mapping.annotation.Region
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@Region("Customers")
|
||||
@Table(name = "Customers")
|
||||
@org.springframework.data.cassandra.core.mapping.Table("Customers")
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor(staticName = "newCustomer")
|
||||
public class Customer {
|
||||
|
||||
@Id
|
||||
@jakarta.persistence.Id
|
||||
@PrimaryKey
|
||||
private Long id;
|
||||
|
||||
@Indexed
|
||||
private String name;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2017-present 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 example.app.crm.repo;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import example.app.crm.model.Customer;
|
||||
|
||||
/**
|
||||
* {@link CustomerRepository} is a Spring Data {@link CrudRepository} and Data Access Object (DAO) defining basic CRUD
|
||||
* and simple query data access operations on {@link Customer} objects.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.Long
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see example.app.crm.model.Customer
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface CustomerRepository extends CrudRepository<Customer, Long> {
|
||||
|
||||
Customer findByName(String name);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2017-present 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 example.app.crm.service;
|
||||
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import example.app.crm.model.Customer;
|
||||
|
||||
/**
|
||||
* {@link CustomerService} is a Spring {@link Service @Service} class servicing {@link Customer Customers}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.cache.annotation.Cacheable
|
||||
* @see org.springframework.stereotype.Service
|
||||
* @see example.app.crm.model.Customer
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Service
|
||||
public class CustomerService {
|
||||
|
||||
@Cacheable("CustomersByName")
|
||||
public Customer findByName(String name) {
|
||||
return Customer.newCustomer(System.currentTimeMillis(), name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2017-present 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 example.app.env;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* {@link SpringBootApplication} allowing users to review Spring's property resolution precedence.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.boot.ApplicationRunner
|
||||
* @see org.springframework.boot.SpringApplication
|
||||
* @see org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.core.env.Environment
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@SpringBootApplication(exclude = CassandraDataAutoConfiguration.class)
|
||||
public class EnvironmentUsingSpringBootApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(EnvironmentUsingSpringBootApplication.class, args);
|
||||
}
|
||||
|
||||
@Value("${example.app.property:FROM-CODE}")
|
||||
private String testProperty;
|
||||
|
||||
@Bean
|
||||
ApplicationRunner environmentRunner(Environment environment) {
|
||||
|
||||
return args -> {
|
||||
//System.err.printf("PROPERTY is [%s]%n", environment.getProperty("example.app.property"));
|
||||
System.err.printf("PROPERTY is [%s]%n", this.testProperty);
|
||||
};
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user