SGF-713 - Override generated OQL from Repository query methods.
This commit is contained in:
@@ -28,6 +28,7 @@ import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.model.BasicPersistentEntity;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -67,9 +68,10 @@ public class GemfirePersistentEntity<T> extends BasicPersistentEntity<T, Gemfire
|
||||
protected static String resolveRegionName(Class<?> persistentEntityType, Annotation regionAnnotation) {
|
||||
|
||||
Optional<String> regionName = Optional.ofNullable(regionAnnotation)
|
||||
.map((annotation) -> getAnnotationAttributeStringValue(annotation, "value"));
|
||||
.map(annotation -> getAnnotationAttributeStringValue(annotation, "value"))
|
||||
.filter(StringUtils::hasText);
|
||||
|
||||
return regionName.filter(StringUtils::hasText).orElse(persistentEntityType.getSimpleName());
|
||||
return regionName.orElse(persistentEntityType.getSimpleName());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@@ -131,13 +133,14 @@ public class GemfirePersistentEntity<T> extends BasicPersistentEntity<T, Gemfire
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the {@link org.apache.geode.cache.Region} in which this {@link PersistentEntity}
|
||||
* will be stored.
|
||||
* Returns the {@link String name} of the {@link org.apache.geode.cache.Region}
|
||||
* in which this {@link PersistentEntity} will be stored.
|
||||
*
|
||||
* @return the name of the {@link org.apache.geode.cache.Region} in which this {@link PersistentEntity}
|
||||
* will be stored.
|
||||
* @return the {@link String name} of the {@link org.apache.geode.cache.Region}
|
||||
* in which this {@link PersistentEntity} will be stored.
|
||||
* @see org.apache.geode.cache.Region#getName()
|
||||
*/
|
||||
@NonNull
|
||||
public String getRegionName() {
|
||||
return this.regionName;
|
||||
}
|
||||
@@ -166,7 +169,7 @@ public class GemfirePersistentEntity<T> extends BasicPersistentEntity<T, Gemfire
|
||||
return null;
|
||||
}
|
||||
|
||||
return (property.isExplicitIdProperty() ? property : null);
|
||||
return property.isExplicitIdProperty() ? property : null;
|
||||
}
|
||||
else {
|
||||
return property;
|
||||
|
||||
@@ -18,10 +18,13 @@ package org.springframework.data.gemfire.mapping;
|
||||
|
||||
import static org.springframework.data.gemfire.util.CollectionUtils.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
@@ -66,11 +69,13 @@ public class GemfirePersistentProperty extends AnnotationBasedPersistentProperty
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this {@link GemfirePersistentProperty} explicitly identifies an entity property identifier,
|
||||
* one in which the user explicitly annotated a entity class member (field or getter/setter).
|
||||
* Determines whether this {@link GemfirePersistentProperty} explicitly identifies
|
||||
* an {@link GemfirePersistentEntity entity} identifier, one in which the user explicitly annotated
|
||||
* the {@link GemfirePersistentEntity owning entity} class member ({@link Field} or property,
|
||||
* i.e. {@link Method getter/setter}).
|
||||
*
|
||||
* @return a boolean value indicating whether this {@link GemfirePersistentProperty} explicitly identifies
|
||||
* an entity property identifier.
|
||||
* an {@link GemfirePersistentEntity entity} identifier.
|
||||
* @see org.springframework.data.annotation.Id
|
||||
* @see #isAnnotationPresent(Class)
|
||||
*/
|
||||
@@ -84,15 +89,37 @@ public class GemfirePersistentProperty extends AnnotationBasedPersistentProperty
|
||||
*/
|
||||
@Override
|
||||
public boolean isIdProperty() {
|
||||
return (super.isIdProperty() || SUPPORTED_IDENTIFIER_NAMES.contains(getName()));
|
||||
return super.isIdProperty() || SUPPORTED_IDENTIFIER_NAMES.contains(getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this {@link GemfirePersistentProperty persistent property} is {@literal transient}
|
||||
* and thus impervious to persistent operations.
|
||||
*
|
||||
* A {@link GemfirePersistentProperty persistent property} is considered {@literal transient}
|
||||
* if the {@link GemfirePersistentEntity owning entity's} field/property is annotated with
|
||||
* {@link Transient} or the field/property is modified with {@link Modifier#TRANSIENT transient}.
|
||||
*
|
||||
* @return a boolean value indicating whether this {@link GemfirePersistentProperty persistent property}
|
||||
* is {@literal transient} and thus impervious to persistent operations.
|
||||
*/
|
||||
@Override
|
||||
public boolean isTransient() {
|
||||
return super.isTransient()
|
||||
|| getProperty().getField().filter(field -> Modifier.isTransient(field.getModifiers())).isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link String name} of this {@link GemfirePersistentProperty's} {@link Class type}.
|
||||
*
|
||||
* @return the {@link String name} of this {@link GemfirePersistentProperty's} {@link Class type}.
|
||||
* @see java.lang.Class#getName()
|
||||
* @see #getType()
|
||||
*/
|
||||
public String getTypeName() {
|
||||
return getType().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
@@ -77,7 +77,8 @@ public class Regions implements Iterable<Region<?, ?>> {
|
||||
Assert.notNull(entityType, "Entity type must not be null");
|
||||
|
||||
String regionName = Optional.ofNullable(this.mappingContext.getPersistentEntity(entityType))
|
||||
.map(entity -> entity.getRegionName()).orElseGet(entityType::getSimpleName);
|
||||
.map(entity -> entity.getRegionName())
|
||||
.orElseGet(entityType::getSimpleName);
|
||||
|
||||
return (Region<?, T>) this.regions.get(regionName);
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ import org.springframework.core.annotation.AliasFor;
|
||||
public @interface Region {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Class<? extends Annotation>> REGION_ANNOTATION_TYPES = Arrays.asList(
|
||||
ClientRegion.class, LocalRegion.class, PartitionRegion.class, ReplicateRegion.class, Region.class);
|
||||
List<Class<? extends Annotation>> REGION_ANNOTATION_TYPES =
|
||||
Arrays.asList(ClientRegion.class, LocalRegion.class, PartitionRegion.class, ReplicateRegion.class, Region.class);
|
||||
|
||||
/**
|
||||
* Name, or fully-qualified bean name of the {@link org.apache.geode.cache.Region}
|
||||
|
||||
@@ -21,13 +21,16 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.data.annotation.QueryAnnotation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@QueryAnnotation
|
||||
@Documented
|
||||
public @interface Query {
|
||||
|
||||
String value() default "";
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.springframework.data.gemfire.mapping.GemfireMappingContext;
|
||||
import org.springframework.data.gemfire.mapping.annotation.Region;
|
||||
import org.springframework.data.gemfire.repository.GemfireRepository;
|
||||
import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
|
||||
@@ -34,12 +36,13 @@ import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
|
||||
|
||||
/**
|
||||
* {@link RepositoryConfigurationExtension} implementation handling GemFire specific extensions to the Repository XML
|
||||
* namespace and annotation-based configuration meta-data.
|
||||
* {@link RepositoryConfigurationExtension} implementation handling Apache Geode and Pivotal GemFire specific extensions
|
||||
* in the Repository XML namespace and Annotation-based configuration meta-data.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport
|
||||
*/
|
||||
public class GemfireRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport {
|
||||
|
||||
@@ -47,8 +50,8 @@ public class GemfireRepositoryConfigurationExtension extends RepositoryConfigura
|
||||
private static final String MAPPING_CONTEXT_PROPERTY_NAME = "gemfireMappingContext";
|
||||
private static final String MAPPING_CONTEXT_REF_ATTRIBUTE_NAME = "mappingContextRef";
|
||||
|
||||
static final String DEFAULT_MAPPING_CONTEXT_BEAN_NAME = String.format("%1$s.%2$s",
|
||||
GemfireMappingContext.class.getName(), "DEFAULT");
|
||||
static final String DEFAULT_MAPPING_CONTEXT_BEAN_NAME =
|
||||
String.format("%1$s.%2$s", GemfireMappingContext.class.getName(), "DEFAULT");
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -92,9 +95,7 @@ public class GemfireRepositoryConfigurationExtension extends RepositoryConfigura
|
||||
*/
|
||||
@Override
|
||||
public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource configurationSource) {
|
||||
builder.addPropertyReference(MAPPING_CONTEXT_PROPERTY_NAME,
|
||||
configurationSource.getAttribute(MAPPING_CONTEXT_REF_ATTRIBUTE_NAME)
|
||||
.orElse(DEFAULT_MAPPING_CONTEXT_BEAN_NAME));
|
||||
addMappingContextPropertyReference(builder, configurationSource);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -103,6 +104,22 @@ public class GemfireRepositoryConfigurationExtension extends RepositoryConfigura
|
||||
*/
|
||||
@Override
|
||||
public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource configurationSource) {
|
||||
addMappingContextPropertyReference(builder, configurationSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a property reference to the store-specific {@link MappingContext}
|
||||
* in the given {@link BeanDefinitionBuilder bean definition}.
|
||||
*
|
||||
* @param builder {@link BeanDefinitionBuilder} used to build the target bean definition.
|
||||
* @param configurationSource {@link RepositoryConfigurationSource} containing {@link Repository}
|
||||
* configuration meta-data.
|
||||
* @see org.springframework.beans.factory.support.BeanDefinitionBuilder
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationSource
|
||||
*/
|
||||
private void addMappingContextPropertyReference(BeanDefinitionBuilder builder,
|
||||
RepositoryConfigurationSource configurationSource) {
|
||||
|
||||
builder.addPropertyReference(MAPPING_CONTEXT_PROPERTY_NAME,
|
||||
configurationSource.getAttribute(MAPPING_CONTEXT_REF_ATTRIBUTE_NAME)
|
||||
.orElse(DEFAULT_MAPPING_CONTEXT_BEAN_NAME));
|
||||
@@ -117,9 +134,21 @@ public class GemfireRepositoryConfigurationExtension extends RepositoryConfigura
|
||||
|
||||
super.registerBeansForRoot(registry, configurationSource);
|
||||
|
||||
if (!configurationSource.getAttribute(MAPPING_CONTEXT_REF_ATTRIBUTE_NAME).isPresent()) {
|
||||
if (noMappingContextIsConfigured(configurationSource)) {
|
||||
registry.registerBeanDefinition(DEFAULT_MAPPING_CONTEXT_BEAN_NAME,
|
||||
new RootBeanDefinition(GemfireMappingContext.class));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a {@link GemfireMappingContext mapping context} has already been configured.
|
||||
*
|
||||
* @param configurationSource {@link RepositoryConfigurationSource} used to check for the presence
|
||||
* of an existing {@link MappingContext} configuration.
|
||||
* @return a boolean value indicating whether a {@link GemfireMappingContext mapping context}
|
||||
* has already been configured.
|
||||
*/
|
||||
private boolean noMappingContextIsConfigured(RepositoryConfigurationSource configurationSource) {
|
||||
return !configurationSource.getAttribute(MAPPING_CONTEXT_REF_ATTRIBUTE_NAME).isPresent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,14 @@ import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
|
||||
/**
|
||||
* Query creator to create {@link QueryString} instances.
|
||||
* {@link AbstractQueryCreator} to create {@link QueryString} instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryBuilder
|
||||
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator
|
||||
* @see org.springframework.data.repository.query.parser.Part
|
||||
* @see org.springframework.data.repository.query.parser.PartTree
|
||||
*/
|
||||
class GemfireQueryCreator extends AbstractQueryCreator<QueryString, Predicates> {
|
||||
|
||||
@@ -47,6 +51,7 @@ class GemfireQueryCreator extends AbstractQueryCreator<QueryString, Predicates>
|
||||
* @param entity must not be {@literal null}.
|
||||
*/
|
||||
public GemfireQueryCreator(PartTree tree, GemfirePersistentEntity<?> entity) {
|
||||
|
||||
super(tree);
|
||||
|
||||
this.queryBuilder = new QueryBuilder(entity, tree);
|
||||
@@ -59,7 +64,9 @@ class GemfireQueryCreator extends AbstractQueryCreator<QueryString, Predicates>
|
||||
*/
|
||||
@Override
|
||||
public QueryString createQuery(Sort dynamicSort) {
|
||||
|
||||
this.indexes = new IndexProvider();
|
||||
|
||||
return super.createQuery(dynamicSort);
|
||||
}
|
||||
|
||||
@@ -96,7 +103,8 @@ class GemfireQueryCreator extends AbstractQueryCreator<QueryString, Predicates>
|
||||
*/
|
||||
@Override
|
||||
protected QueryString complete(Predicates criteria, Sort sort) {
|
||||
QueryString query = queryBuilder.create(criteria).orderBy(sort);
|
||||
|
||||
QueryString query = this.queryBuilder.create(criteria).orderBy(sort);
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(String.format("Created Query [%s]", query.toString()));
|
||||
|
||||
@@ -67,7 +67,6 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
assertNonPagingQueryMethod(method);
|
||||
|
||||
this.method = method;
|
||||
|
||||
this.entity = mappingContext.getPersistentEntity(getDomainClass());
|
||||
}
|
||||
|
||||
@@ -81,6 +80,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#getParameterTypes()
|
||||
*/
|
||||
private void assertNonPagingQueryMethod(Method method) {
|
||||
|
||||
for (Class<?> type : method.getParameterTypes()) {
|
||||
if (Pageable.class.isAssignableFrom(type)) {
|
||||
throw new IllegalStateException(String.format("Pagination is not supported by GemFire Repositories;"
|
||||
@@ -95,7 +95,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @return the {@link GemfirePersistentEntity} the method deals with.
|
||||
*/
|
||||
public GemfirePersistentEntity<?> getPersistentEntity() {
|
||||
return entity;
|
||||
return this.entity;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,9 +117,12 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#getAnnotation(Class)
|
||||
*/
|
||||
String getAnnotatedQuery() {
|
||||
Query query = method.getAnnotation(Query.class);
|
||||
String queryString = (query != null ? (String) AnnotationUtils.getValue(query) : null);
|
||||
return (StringUtils.hasText(queryString) ? queryString : null);
|
||||
|
||||
Query query = this.method.getAnnotation(Query.class);
|
||||
|
||||
String queryString = query != null ? (String) AnnotationUtils.getValue(query) : null;
|
||||
|
||||
return StringUtils.hasText(queryString) ? queryString : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +134,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#isAnnotationPresent(Class)
|
||||
*/
|
||||
public boolean hasHint() {
|
||||
return method.isAnnotationPresent(Hint.class);
|
||||
return this.method.isAnnotationPresent(Hint.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,8 +145,10 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#getAnnotation(Class)
|
||||
*/
|
||||
public String[] getHints() {
|
||||
|
||||
Hint hint = method.getAnnotation(Hint.class);
|
||||
return (hint != null ? hint.value() : EMPTY_STRING_ARRAY);
|
||||
|
||||
return hint != null ? hint.value() : EMPTY_STRING_ARRAY;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +160,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#isAnnotationPresent(Class)
|
||||
*/
|
||||
public boolean hasImport() {
|
||||
return method.isAnnotationPresent(Import.class);
|
||||
return this.method.isAnnotationPresent(Import.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,8 +171,10 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#getAnnotation(Class)
|
||||
*/
|
||||
public String getImport() {
|
||||
|
||||
Import importStatement = method.getAnnotation(Import.class);
|
||||
return (importStatement != null ? importStatement.value() : null);
|
||||
|
||||
return importStatement != null ? importStatement.value() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,7 +186,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#isAnnotationPresent(Class)
|
||||
*/
|
||||
public boolean hasLimit() {
|
||||
return method.isAnnotationPresent(Limit.class);
|
||||
return this.method.isAnnotationPresent(Limit.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,8 +197,10 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#getAnnotation(Class)
|
||||
*/
|
||||
public int getLimit() {
|
||||
|
||||
Limit limit = method.getAnnotation(Limit.class);
|
||||
return (limit != null ? limit.value() : Integer.MAX_VALUE);
|
||||
|
||||
return limit != null ? limit.value() : Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,7 +211,6 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#isAnnotationPresent(Class)
|
||||
*/
|
||||
public boolean hasTrace() {
|
||||
return method.isAnnotationPresent(Trace.class);
|
||||
return this.method.isAnnotationPresent(Trace.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -28,29 +29,37 @@ import org.springframework.util.Assert;
|
||||
* @see org.springframework.data.gemfire.repository.query.GemfireQueryMethod
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery
|
||||
*/
|
||||
abstract class GemfireRepositoryQuery implements RepositoryQuery {
|
||||
public abstract class GemfireRepositoryQuery implements RepositoryQuery {
|
||||
|
||||
private final GemfireQueryMethod queryMethod;
|
||||
|
||||
private QueryPostProcessor<?, String> queryPostProcessor = ProvidedQueryPostProcessor.IDENTITY;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* Constructor used for testing purposes only!
|
||||
*/
|
||||
GemfireRepositoryQuery() {
|
||||
queryMethod = null;
|
||||
this.queryMethod = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GemfireRepositoryQuery} using the given {@link GemfireQueryMethod}.
|
||||
*
|
||||
* @param queryMethod must not be {@literal null}.
|
||||
* Constructs a new instance of {@link GemfireRepositoryQuery} initialized with
|
||||
* the given {@link GemfireQueryMethod}.
|
||||
*
|
||||
* @param queryMethod {@link GemfireQueryMethod} capturing the meta-data
|
||||
* for the {@link Repository} {@link QueryMethod}; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link GemfireQueryMethod query method} is {@literal null}.
|
||||
* @see org.springframework.data.gemfire.repository.query.GemfireQueryMethod
|
||||
*/
|
||||
public GemfireRepositoryQuery(GemfireQueryMethod queryMethod) {
|
||||
Assert.notNull(queryMethod);
|
||||
|
||||
Assert.notNull(queryMethod, "QueryMethod must not be null");
|
||||
|
||||
this.queryMethod = queryMethod;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
|
||||
*/
|
||||
@@ -59,4 +68,38 @@ abstract class GemfireRepositoryQuery implements RepositoryQuery {
|
||||
return this.queryMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the composed {@link QueryPostProcessor QueryPostProcessors}, which gets applied
|
||||
* to the OQL query prior to execution.
|
||||
*
|
||||
* @return a reference to the composed {@link QueryPostProcessor QueryPostProcessors}.
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryPostProcessor
|
||||
*/
|
||||
protected QueryPostProcessor<?, String> getQueryPostProcessor() {
|
||||
return this.queryPostProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given {@link QueryPostProcessor} used to post process OQL queries
|
||||
* generated from {@link Repository} {@link QueryMethod query methods}.
|
||||
*
|
||||
* @param queryPostProcessor {@link QueryPostProcessor} to register.
|
||||
* @return this {@link GemfireRepositoryQuery}.
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryPostProcessor
|
||||
*/
|
||||
public GemfireRepositoryQuery register(QueryPostProcessor<?, String> queryPostProcessor) {
|
||||
this.queryPostProcessor = this.queryPostProcessor.processBefore(queryPostProcessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
enum ProvidedQueryPostProcessor implements QueryPostProcessor<Repository, String> {
|
||||
|
||||
IDENTITY {
|
||||
|
||||
@Override
|
||||
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
|
||||
return query;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,26 +22,29 @@ import java.util.List;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
|
||||
/**
|
||||
* {@link GemfireRepositoryQuery} backed by a {@link PartTree} and thus, deriving an OQL query from the backing query
|
||||
* method's name.
|
||||
*
|
||||
* {@link GemfireRepositoryQuery} backed by a {@link PartTree}, deriving an OQL query
|
||||
* from the backing query method's name/signature.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.gemfire.repository.query.GemfireRepositoryQuery
|
||||
*/
|
||||
public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
|
||||
private final GemfireQueryMethod method;
|
||||
private final PartTree tree;
|
||||
|
||||
private final GemfireTemplate template;
|
||||
|
||||
private final PartTree tree;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PartTreeGemfireRepositoryQuery} using the given {@link GemfireQueryMethod} and
|
||||
* {@link GemfireTemplate}.
|
||||
*
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param template must not be {@literal null}.
|
||||
*/
|
||||
@@ -51,30 +54,51 @@ public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
|
||||
Class<?> domainClass = method.getEntityInformation().getJavaType();
|
||||
|
||||
this.tree = new PartTree(method.getName(), domainClass);
|
||||
this.method = method;
|
||||
this.template = template;
|
||||
this.tree = new PartTree(method.getName(), domainClass);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(method.getParameters(), parameters);
|
||||
public Object execute(Object[] arguments) {
|
||||
|
||||
QueryString query = new GemfireQueryCreator(tree, method.getPersistentEntity())
|
||||
.createQuery(parameterAccessor.getSort());
|
||||
QueryString query = createQuery(this.method, this.tree, arguments);
|
||||
|
||||
RepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery(query.toString(), method, template);
|
||||
GemfireRepositoryQuery repositoryQuery = newRepositoryQuery(query, this.method, this.template);
|
||||
|
||||
return repositoryQuery.execute(prepareStringParameters(parameters));
|
||||
return repositoryQuery.execute(prepareStringParameters(arguments));
|
||||
}
|
||||
|
||||
private QueryString createQuery(GemfireQueryMethod queryMethod, PartTree tree, Object[] arguments) {
|
||||
|
||||
ParametersParameterAccessor parameterAccessor =
|
||||
new ParametersParameterAccessor(queryMethod.getParameters(), arguments);
|
||||
|
||||
GemfireQueryCreator queryCreator = new GemfireQueryCreator(tree, queryMethod.getPersistentEntity());
|
||||
|
||||
return queryCreator.createQuery(parameterAccessor.getSort());
|
||||
}
|
||||
|
||||
private GemfireRepositoryQuery newRepositoryQuery(QueryString query,
|
||||
GemfireQueryMethod queryMethod, GemfireTemplate template) {
|
||||
|
||||
GemfireRepositoryQuery repositoryQuery =
|
||||
new StringBasedGemfireRepositoryQuery(query.toString(), queryMethod, template);
|
||||
|
||||
repositoryQuery.register(getQueryPostProcessor());
|
||||
|
||||
return repositoryQuery;
|
||||
}
|
||||
|
||||
private Object[] prepareStringParameters(Object[] parameters) {
|
||||
Iterator<Part> partsIterator = tree.getParts().iterator();
|
||||
List<Object> stringParameters = new ArrayList<Object>(parameters.length);
|
||||
|
||||
Iterator<Part> partsIterator = this.tree.getParts().iterator();
|
||||
|
||||
List<Object> stringParameters = new ArrayList<>(parameters.length);
|
||||
|
||||
for (Object parameter : parameters) {
|
||||
if (parameter == null || parameter instanceof Sort) {
|
||||
@@ -99,5 +123,4 @@ public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
|
||||
return stringParameters.toArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
import org.springframework.data.gemfire.mapping.GemfirePersistentEntity;
|
||||
import org.springframework.data.gemfire.repository.query.support.OqlKeyword;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
@@ -39,20 +40,21 @@ class QueryBuilder {
|
||||
|
||||
/* (non-Javadoc) */
|
||||
static String asQuery(GemfirePersistentEntity<?> entity, PartTree tree) {
|
||||
return String.format(SELECT_OQL_TEMPLATE, (tree.isDistinct() ? OqlKeyword.DISTINCT : ""),
|
||||
|
||||
return String.format(SELECT_OQL_TEMPLATE, tree.isDistinct() ? OqlKeyword.DISTINCT : "",
|
||||
entity.getRegionName(), DEFAULT_ALIAS).replaceAll("\\s{2,}", " ");
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
static String validateQuery(String query) {
|
||||
Assert.hasText(query, "An OQL Query must be specified");
|
||||
Assert.hasText(query, "Query is required");
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of {@link QueryBuilder} initialized with the given query {@link String}.
|
||||
* Constructs a new instance of {@link QueryBuilder} initialized with the given {@link String query}.
|
||||
*
|
||||
* @param query {@link String} containing the base OQL query.
|
||||
* @param query {@link String} containing the base {@link String OQL query}.
|
||||
* @see #validateQuery(String)
|
||||
*/
|
||||
public QueryBuilder(String query) {
|
||||
@@ -60,16 +62,15 @@ class QueryBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of {@link QueryBuilder} with the given {@link GemfirePersistentEntity}
|
||||
* and {@link PartTree} that determines the GemFire {@link org.apache.geode.cache.Region}
|
||||
* to query and whether the query should capture unique results.
|
||||
* Constructs a new instance of {@link QueryBuilder} initialized with the given {@link GemfirePersistentEntity}
|
||||
* and {@link PartTree} used to determine the {@link Region} to query and whether the query
|
||||
* should capture unique results.
|
||||
*
|
||||
* @param entity {@link GemfirePersistentEntity} used to determine the GemFire
|
||||
* {@link org.apache.geode.cache.Region} to query.
|
||||
* @param tree {@link PartTree} containing parts of the OQL Query for determining things
|
||||
* like uniqueness.
|
||||
* @param entity {@link GemfirePersistentEntity} used to determine the {@link Region} to query.
|
||||
* @param tree {@link PartTree} containing parts of the OQL Query for determining things like uniqueness.
|
||||
* @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity
|
||||
* @see org.springframework.data.repository.query.parser.PartTree
|
||||
* @see #QueryBuilder(String)
|
||||
*/
|
||||
public QueryBuilder(GemfirePersistentEntity<?> entity, PartTree tree) {
|
||||
this(asQuery(entity, tree));
|
||||
@@ -97,8 +98,10 @@ class QueryBuilder {
|
||||
* @see org.springframework.data.gemfire.repository.query.Predicate
|
||||
*/
|
||||
protected String withPredicate(String query, Predicate predicate) {
|
||||
return (predicate == null ? query
|
||||
: String.format(WHERE_CLAUSE_TEMPLATE, query, predicate.toString(DEFAULT_ALIAS)));
|
||||
|
||||
return predicate != null
|
||||
? String.format(WHERE_CLAUSE_TEMPLATE, query, predicate.toString(DEFAULT_ALIAS))
|
||||
: query;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.data.gemfire.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link QueryPostProcessor} defines a contract for implementations to post process a given {@link String OQL query}
|
||||
* and possibly return a new or modified version of the same {@link String OQL query}.
|
||||
*
|
||||
* {@link QueryPostProcessor QueryPostProcessors} are particularly useful for {@link Repository}
|
||||
* {@link QueryMethod query method} generated {@link String queries}, giving the user a chance
|
||||
* via the callback to further process the generated {@link String query}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @param <T> {@link Class type} identifying the {@link Repository Repositories} to match on for registration.
|
||||
* @param <QUERY> {@link Class type} of the query to process.
|
||||
* @see org.springframework.data.repository.Repository
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("unused")
|
||||
public interface QueryPostProcessor<T extends Repository, QUERY> extends Ordered {
|
||||
|
||||
Object[] EMPTY_ARGUMENTS = new Object[0];
|
||||
|
||||
/**
|
||||
* Defines the {@link Integer order} of this {@link QueryPostProcessor} relative to
|
||||
* other {@link QueryPostProcessor QueryPostProcessors} in a sort.
|
||||
*
|
||||
* Defaults to the {@link Ordered#LOWEST_PRECEDENCE}.
|
||||
*
|
||||
* @return an {@link Integer} value specifying the {@link Integer order} of this {@link QueryPostProcessor}
|
||||
* relative to other {@link QueryPostProcessor QueryPostProcessors} in a sort.
|
||||
* @see org.springframework.core.Ordered#getOrder()
|
||||
*/
|
||||
@Override
|
||||
default int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used to post process the given {@link QUERY OQL query} and return a possibly new
|
||||
* or modified {@link String OQL query}.
|
||||
*
|
||||
* This callback is invoked for OQL queries generated from the SD Repository {@link QueryMethod} signature
|
||||
* as well as OQL queries specified using the {@link Query @Query} annotation and OQL queries
|
||||
* defined in a application properties file.
|
||||
*
|
||||
* @param query {@link QUERY OQL query} to process.
|
||||
* @return a possibly new or modified version of the same {@link String OQL query}.
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
*/
|
||||
default QUERY postProcess(@NonNull QueryMethod queryMethod, QUERY query) {
|
||||
return postProcess(queryMethod, query, EMPTY_ARGUMENTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used to post process the given {@link QUERY OQL query} and return a possibly new
|
||||
* or modified {@link String OQL query}.
|
||||
*
|
||||
* This callback is invoked for OQL queries generated from the SD Repository {@link QueryMethod} signature
|
||||
* as well as OQL queries specified using the {@link Query @Query} annotation and OQL queries
|
||||
* defined in a application properties file.
|
||||
*
|
||||
* @param query {@link QUERY OQL query} to process.
|
||||
* @param arguments array of {@link Object Objects} containing the arguments to the query parameters.
|
||||
* @return a possibly new or modified version of the same {@link String OQL query}.
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
*/
|
||||
QUERY postProcess(@NonNull QueryMethod queryMethod, QUERY query, Object... arguments);
|
||||
|
||||
/**
|
||||
* Builder method used to compose 2 {@link QueryPostProcessor QueryPostProcessors}
|
||||
* with this {@link QueryPostProcessor} preceding the given {@link QueryPostProcessor}.
|
||||
*
|
||||
* @param queryPostProcessor {@link QueryPostProcessor} to compose with this {@link QueryPostProcessor}.
|
||||
* @return a composed {@link QueryPostProcessor} consisting of this {@link QueryPostProcessor}
|
||||
* followed by the given {@link QueryPostProcessor}. Returns this {@link QueryPostProcessor}
|
||||
* if given {@link QueryPostProcessor} is {@literal null}.
|
||||
*/
|
||||
@NonNull
|
||||
default QueryPostProcessor<?, QUERY> processBefore(@Nullable QueryPostProcessor<?, QUERY> queryPostProcessor) {
|
||||
return queryPostProcessor == null ? this : (queryMethod, query, arguments) ->
|
||||
queryPostProcessor.postProcess(queryMethod, this.postProcess(queryMethod, query, arguments), arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to compose 2 {@link QueryPostProcessor QueryPostProcessors}
|
||||
* with the given {@link QueryPostProcessor} preceding this {@link QueryPostProcessor}.
|
||||
*
|
||||
* @param queryPostProcessor {@link QueryPostProcessor} to compose with this {@link QueryPostProcessor}.
|
||||
* @return a composed {@link QueryPostProcessor} consisting of the given {@link QueryPostProcessor}
|
||||
* followed by this {@link QueryPostProcessor}. Returns this {@link QueryPostProcessor}
|
||||
* if given {@link QueryPostProcessor} is {@literal null}.
|
||||
*/
|
||||
@NonNull
|
||||
default QueryPostProcessor<?, QUERY> processAfter(@Nullable QueryPostProcessor<?, QUERY> queryPostProcessor) {
|
||||
return queryPostProcessor == null ? this : (queryMethod, query, arguments) ->
|
||||
this.postProcess(queryMethod, queryPostProcessor.postProcess(queryMethod, query, arguments), arguments);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIsEmpty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -30,7 +32,7 @@ import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link QueryString} is a utility class used to construct GemFire OQL query statement syntax.
|
||||
* {@link QueryString} is a utility class used to construct GemFire OQL query statements.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
@@ -42,6 +44,7 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class QueryString {
|
||||
|
||||
// OQL Query Patterns
|
||||
protected static final Pattern HINT_PATTERN = Pattern.compile("<HINT '\\w+'(, '\\w+')*>");
|
||||
protected static final Pattern IMPORT_PATTERN = Pattern.compile("IMPORT .+;");
|
||||
protected static final Pattern LIMIT_PATTERN = Pattern.compile("LIMIT \\d+");
|
||||
@@ -59,40 +62,82 @@ public class QueryString {
|
||||
private static final String IN_PARAMETER_PATTERN = "(?<=IN (SET|LIST) \\$)\\d";
|
||||
private static final String REGION_PATTERN = "\\/(\\/?\\w)+";
|
||||
|
||||
private final String query;
|
||||
private static final String COUNT_QUERY = "count(*)";
|
||||
private static final String STAR_QUERY = "*";
|
||||
|
||||
/* (non-Javadoc) */
|
||||
static String asQuery(Class<?> domainType, boolean isCountQuery) {
|
||||
return String.format(SELECT_OQL_TEMPLATE, (isCountQuery ? "count(*)" : "*"),
|
||||
validateDomainType(domainType).getSimpleName());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
static <T> Class<T> validateDomainType(Class<T> domainType) {
|
||||
Assert.notNull(domainType, "domainType must not be null");
|
||||
return domainType;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
static String validateQuery(String query) {
|
||||
Assert.hasText(query, "An OQL Query must be specified");
|
||||
return query;
|
||||
/**
|
||||
* Factory method used to construct an instance of {@link QueryString} initialized with the given {@link String query}.
|
||||
*
|
||||
* @param query {@link String} containing the OQL query.
|
||||
* @return a new {@link QueryString} initialized with the given {@link String query}.
|
||||
* @throws IllegalArgumentException if {@link String query} is not specified.
|
||||
* @see #QueryString(String)
|
||||
*/
|
||||
public static QueryString of(String query) {
|
||||
return new QueryString(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of {@link QueryString} initialized with the given GemFire OQL Query {@link String}.
|
||||
* Factory method used to construct an instance of {@link QueryString} initialized with
|
||||
* the given {@link Class domain type} for which the query will be created.
|
||||
*
|
||||
* @param query {@link String} specifying the GemFire OQL Query.
|
||||
* @throws IllegalArgumentException if the query string is unspecified (null or empty).
|
||||
* @param domainType {@link Class domain object type} for which the query will be created.
|
||||
* @return a new {@link QueryString} initialized with the given {@link String query}.
|
||||
* @throws IllegalArgumentException if {@link Class domain type} is {@literal null}.
|
||||
* @see #QueryString(Class)
|
||||
*/
|
||||
public static QueryString from(Class<?> domainType) {
|
||||
return new QueryString(domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to construct an instance of {@link QueryString} initialized with
|
||||
* the given {@link Class domain type} for which a count query will be created.
|
||||
*
|
||||
* @param domainType {@link Class domain object type} for which the query will be created.
|
||||
* @return a new {@link QueryString} initialized with the given {@link String query}.
|
||||
* @throws IllegalArgumentException if {@link Class domain type} is {@literal null}.
|
||||
* @see #QueryString(Class)
|
||||
*/
|
||||
public static QueryString count(Class<?> domainType) {
|
||||
return new QueryString(domainType, true);
|
||||
}
|
||||
|
||||
static String asQuery(Class<?> domainType, boolean isCountQuery) {
|
||||
return String.format(SELECT_OQL_TEMPLATE, isCountQuery ? COUNT_QUERY : STAR_QUERY,
|
||||
validateDomainType(domainType).getSimpleName());
|
||||
}
|
||||
|
||||
static <T> Class<T> validateDomainType(Class<T> domainType) {
|
||||
Assert.notNull(domainType, "Domain type is required");
|
||||
return domainType;
|
||||
}
|
||||
|
||||
static String validateQuery(String query) {
|
||||
Assert.hasText(query, String.format("Query [%s] is required", query));
|
||||
return query;
|
||||
}
|
||||
|
||||
private final String query;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link QueryString} initialized with the given {@link String OQL query}.
|
||||
*
|
||||
* @param query {@link String} containing the OQL query.
|
||||
* @throws IllegalArgumentException if {@link String query} is {@literal null} or empty.
|
||||
* @see #validateQuery(String)
|
||||
* @see java.lang.String
|
||||
*/
|
||||
public QueryString(String query) {
|
||||
this.query = validateQuery(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a GemFire OQL {@literal SELECT} Query for the given domain class.
|
||||
* Constructs a new instance of {@link QueryString} initialized from the given {@link Class domain type},
|
||||
* which is used to construct an OQL {@literal SELECT} query statement.
|
||||
*
|
||||
* @param domainType application domain object type to query; must not be {@literal null}.
|
||||
* @param domainType {@link Class application domain type} to query; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link Class domain type} is {@literal null}.
|
||||
* @see #QueryString(Class, boolean)
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@@ -101,16 +146,49 @@ public class QueryString {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a GemFire OQL {@literal SELECT} Query for the given domain class. {@code isCountQuery} indicates
|
||||
* whether to select a count or select the contents of the objects of the given domain object type.
|
||||
* Constructs a new instance of {@link QueryString} initialized from the given {@link Class domain type},
|
||||
* which is used to construct an OQL {@literal SELECT} query statement.
|
||||
*
|
||||
* @param domainType application domain object type to query; must not be {@literal null}.
|
||||
* @param isCountQuery boolean value to indicate if this is a count query.
|
||||
* @throws IllegalArgumentException if {@code domainType} is null.
|
||||
* {@code asCountQuery} is a {@link Boolean} flag indicating whether to select a count or select the contents
|
||||
* of the objects for the given {@link Class domain type}.
|
||||
*
|
||||
* @param domainType {@link Class application domain type} to query; must not be {@literal null}.
|
||||
* @param asCountQuery boolean value to indicate if this is a count query.
|
||||
* @throws IllegalArgumentException if {@link Class domain type} is {@literal null}.
|
||||
* @see #asQuery(Class, boolean)
|
||||
* @see #QueryString(String)
|
||||
*/
|
||||
public QueryString(Class<?> domainType, boolean isCountQuery) {
|
||||
this(asQuery(domainType, isCountQuery));
|
||||
public QueryString(Class<?> domainType, boolean asCountQuery) {
|
||||
this(asQuery(domainType, asCountQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the {@literal SELECT query} with a {@literal SELECT DISTINCT query} if the {@link String query}
|
||||
* is not already distinct; i.e. does not contain the {@literal DISTINCT} keyword.
|
||||
*
|
||||
* @return a {@literal SELECT DISTINCT query} if {@link String query} does not contain
|
||||
* the {@literal DISTINCT} keyword.
|
||||
* @see java.lang.String#replaceFirst(String, String)
|
||||
* @see #asDistinct(String)
|
||||
*/
|
||||
public QueryString asDistinct() {
|
||||
return QueryString.of(asDistinct(this.query));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the {@literal SELECT query} with a {@literal SELECT DISTINCT query} if the {@link String query}
|
||||
* is not already distinct; i.e. does not contain the {@literal DISTINCT} keyword.
|
||||
*
|
||||
* @param query {@link String} containing the {@link String query} to evaluate.
|
||||
* @return a {@literal SELECT DISTINCT query} if {@link String query} does not contain
|
||||
* the {@literal DISTINCT} keyword.
|
||||
* @see java.lang.String#replaceFirst(String, String)
|
||||
*/
|
||||
String asDistinct(String query) {
|
||||
|
||||
return query.contains(OqlKeyword.DISTINCT.getKeyword()) ? query
|
||||
: query.replaceFirst(OqlKeyword.SELECT.getKeyword(),
|
||||
String.format("%1$s %2$s", OqlKeyword.SELECT.getKeyword(), OqlKeyword.DISTINCT.getKeyword()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,8 +199,9 @@ public class QueryString {
|
||||
* @return a Query String having "in" parameters bound with values.
|
||||
*/
|
||||
public QueryString bindIn(Collection<?> values) {
|
||||
if (values != null) {
|
||||
return new QueryString(this.query.replaceFirst(IN_PATTERN, String.format("(%s)",
|
||||
|
||||
if (!nullSafeIsEmpty(values)) {
|
||||
return QueryString.of(this.query.replaceFirst(IN_PATTERN, String.format("(%s)",
|
||||
StringUtils.collectionToDelimitedString(values, ", ", "'", "'"))));
|
||||
}
|
||||
|
||||
@@ -130,35 +209,40 @@ public class QueryString {
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the domain classes referenced inside the current query with the given {@link Region}.
|
||||
* Replaces the {@link Class domain classes} referenced inside the current {@link String query}
|
||||
* with the given {@link Region}.
|
||||
*
|
||||
* @param domainClass the class type of the GemFire persistent entity to query; must not be {@literal null}.
|
||||
* @param region the GemFire Region in which to query; must not be {@literal null}.
|
||||
* @return a Query String with the FROM clause in the OQL statement evaluated and replaced with
|
||||
* the fully-qualified Region to query.
|
||||
* @param domainClass {@link Class type} of the persistent entity to query; must not be {@literal null}.
|
||||
* @param region {@link Region} to query; must not be {@literal null}.
|
||||
* @return a new {@link QueryString} with an OQL {@literal SELECT statement} having a {@literal FROM clause}
|
||||
* based on the selected {@link Region}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see java.lang.Class
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public QueryString forRegion(Class<?> domainClass, Region<?, ?> region) {
|
||||
return new QueryString(this.query.replaceAll(REGION_PATTERN, region.getFullPath()));
|
||||
public QueryString fromRegion(Class<?> domainClass, Region<?, ?> region) {
|
||||
return QueryString.of(this.query.replaceAll(REGION_PATTERN, region.getFullPath()));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Returns the parameter indexes used in this query.
|
||||
*
|
||||
* @return the parameter indexes used in this query or an empty {@link Iterable} if none are used.
|
||||
* @see java.lang.Iterable
|
||||
*/
|
||||
public Iterable<Integer> getInParameterIndexes() {
|
||||
|
||||
Pattern pattern = Pattern.compile(IN_PARAMETER_PATTERN);
|
||||
Matcher matcher = pattern.matcher(query);
|
||||
List<Integer> result = new ArrayList<>();
|
||||
|
||||
Matcher matcher = pattern.matcher(this.query);
|
||||
|
||||
List<Integer> indexes = new ArrayList<>();
|
||||
|
||||
while (matcher.find()) {
|
||||
result.add(Integer.parseInt(matcher.group()));
|
||||
indexes.add(Integer.parseInt(matcher.group()));
|
||||
}
|
||||
|
||||
return result;
|
||||
return indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,36 +255,26 @@ public class QueryString {
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryString
|
||||
*/
|
||||
public QueryString orderBy(Sort sort) {
|
||||
|
||||
if (hasSort(sort)) {
|
||||
StringBuilder orderClause = new StringBuilder("ORDER BY ");
|
||||
|
||||
StringBuilder orderByClause = new StringBuilder("ORDER BY ");
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (Sort.Order order : sort) {
|
||||
orderClause.append(count++ > 0 ? ", " : "");
|
||||
orderClause.append(String.format("%1$s %2$s", order.getProperty(), order.getDirection()));
|
||||
orderByClause.append(count++ > 0 ? ", " : "");
|
||||
orderByClause.append(String.format("%1$s %2$s", order.getProperty(), order.getDirection()));
|
||||
}
|
||||
|
||||
return new QueryString(String.format("%1$s %2$s", makeDistinct(this.query), orderClause.toString()));
|
||||
return new QueryString(String.format("%1$s %2$s", asDistinct(this.query), orderByClause.toString()));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
private boolean hasSort(Sort sort) {
|
||||
return (sort != null && sort.iterator().hasNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the SELECT query with a SELECT DISTINCT query if the query does not contain the DISTINCT OQL keyword.
|
||||
*
|
||||
* @param query {@link String} containing the query to evaluate.
|
||||
* @return a SELECT DISTINCT query if {@code query} does not contain the DISTINCT OQL keyword.
|
||||
*/
|
||||
String makeDistinct(String query) {
|
||||
return (query.contains(OqlKeyword.DISTINCT.getKeyword()) ? query
|
||||
: query.replaceFirst(OqlKeyword.SELECT.getKeyword(),
|
||||
String.format("%1$s %2$s", OqlKeyword.SELECT.getKeyword(), OqlKeyword.DISTINCT.getKeyword())));
|
||||
return sort != null && sort.iterator().hasNext();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,15 +284,17 @@ public class QueryString {
|
||||
* @return a new {@link QueryString} if hints are not null or empty, or return this {@link QueryString}.
|
||||
*/
|
||||
public QueryString withHints(String... hints) {
|
||||
|
||||
if (!ObjectUtils.isEmpty(hints)) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (String hint : hints) {
|
||||
builder.append(builder.length() > 0 ? ", " : "");
|
||||
builder.append(String.format("'%1$s'", hint));
|
||||
builder.append(String.format("'%s'", hint));
|
||||
}
|
||||
|
||||
return new QueryString(String.format(HINTS_OQL_TEMPLATE, builder.toString(), query));
|
||||
return QueryString.of(String.format(HINTS_OQL_TEMPLATE, builder.toString(), this.query));
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -231,8 +307,8 @@ public class QueryString {
|
||||
* @return a new {@link QueryString} if an import was declared, or return this {@link QueryString}.
|
||||
*/
|
||||
public QueryString withImport(String importExpression) {
|
||||
return (StringUtils.hasText(importExpression) ?
|
||||
new QueryString(String.format(IMPORT_OQL_TEMPLATE, importExpression, query)) : this);
|
||||
return StringUtils.hasText(importExpression) ?
|
||||
QueryString.of(String.format(IMPORT_OQL_TEMPLATE, importExpression, this.query)) : this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,7 +318,7 @@ public class QueryString {
|
||||
* @return a new {@link QueryString} if a limit was specified, or return this {@link QueryString}.
|
||||
*/
|
||||
public QueryString withLimit(Integer limit) {
|
||||
return (limit != null ? new QueryString(String.format(LIMIT_OQL_TEMPLATE, query, limit)) : this);
|
||||
return limit != null ? QueryString.of(String.format(LIMIT_OQL_TEMPLATE, this.query, limit)) : this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,15 +327,18 @@ public class QueryString {
|
||||
* @return a new {@link QueryString} with tracing enabled.
|
||||
*/
|
||||
public QueryString withTrace() {
|
||||
return new QueryString(String.format(TRACE_OQL_TEMPLATE, query));
|
||||
return QueryString.of(String.format(TRACE_OQL_TEMPLATE, this.query));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
/**
|
||||
* Returns a {@link String} representation of this {@link QueryString}.
|
||||
*
|
||||
* @return a {@link String} representation of this {@link QueryString}.
|
||||
* @see java.lang.Object#toString()
|
||||
* @see java.lang.String
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return query;
|
||||
return this.query;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,19 @@
|
||||
*/
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeSize;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -49,8 +54,14 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
* Constructor used for testing purposes only!
|
||||
*/
|
||||
StringBasedGemfireRepositoryQuery() {
|
||||
query = null;
|
||||
template = null;
|
||||
|
||||
this.query = null;
|
||||
this.template = null;
|
||||
|
||||
register(ProvidedQueryPostProcessors.LIMIT
|
||||
.processBefore(ProvidedQueryPostProcessors.IMPORT)
|
||||
.processBefore(ProvidedQueryPostProcessors.HINT)
|
||||
.processBefore(ProvidedQueryPostProcessors.TRACE));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,32 +84,61 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
* @param template must not be {@literal null}.
|
||||
*/
|
||||
public StringBasedGemfireRepositoryQuery(String query, GemfireQueryMethod queryMethod, GemfireTemplate template) {
|
||||
|
||||
super(queryMethod);
|
||||
|
||||
Assert.notNull(template, "GemfireTemplate must not be null");
|
||||
Assert.state(!(queryMethod.isModifyingQuery() || queryMethod.isPageQuery()), INVALID_QUERY);
|
||||
|
||||
this.userDefinedQuery |= !StringUtils.hasText(query);
|
||||
this.query = new QueryString(StringUtils.hasText(query) ? query : queryMethod.getAnnotatedQuery());
|
||||
this.query = QueryString.of(StringUtils.hasText(query) ? query : queryMethod.getAnnotatedQuery());
|
||||
this.template = template;
|
||||
|
||||
if (queryMethod.isModifyingQuery() || queryMethod.isPageQuery()) {
|
||||
throw new IllegalStateException(INVALID_QUERY);
|
||||
}
|
||||
register(ProvidedQueryPostProcessors.LIMIT
|
||||
.processBefore(ProvidedQueryPostProcessors.IMPORT)
|
||||
.processBefore(ProvidedQueryPostProcessors.HINT)
|
||||
.processBefore(ProvidedQueryPostProcessors.TRACE));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
/**
|
||||
* Sets this {@link RepositoryQuery} to be user-defined.
|
||||
*
|
||||
* @return this {@link RepositoryQuery}.
|
||||
*/
|
||||
public StringBasedGemfireRepositoryQuery asUserDefinedQuery() {
|
||||
this.userDefinedQuery = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
/**
|
||||
* Determines whether the query represented by this {@link RepositoryQuery} is user-defined or was generated by
|
||||
* the Spring Data {@link Repository} infrastructure.
|
||||
*
|
||||
* @return a boolean value indicating whether this {@link RepositoryQuery} is user-defined or was generated by
|
||||
* the Spring Data {@link Repository} infrastructure.
|
||||
*/
|
||||
public boolean isUserDefinedQuery() {
|
||||
return userDefinedQuery;
|
||||
return this.userDefinedQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the {@link QueryString managed query}.
|
||||
*
|
||||
* @return a reference to the {@link QueryString managed query}.
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryString
|
||||
*/
|
||||
protected QueryString getQuery() {
|
||||
return this.query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the {@link GemfireTemplate} used to perform all data access and query operations.
|
||||
*
|
||||
* @return a reference to the {@link GemfireTemplate} used to perform all data access and query operations.
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
*/
|
||||
protected GemfireTemplate getTemplate() {
|
||||
return this.template;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -106,72 +146,69 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
QueryMethod localQueryMethod = getQueryMethod();
|
||||
public Object execute(Object[] arguments) {
|
||||
|
||||
QueryString query = (isUserDefinedQuery() ? this.query
|
||||
: this.query.forRegion(localQueryMethod.getEntityInformation().getJavaType(), template.getRegion()));
|
||||
QueryMethod queryMethod = getQueryMethod();
|
||||
|
||||
QueryString query = preProcess(queryMethod, getQuery(), arguments);
|
||||
|
||||
String queryString = query.toString();
|
||||
String processedQueryString = getQueryPostProcessor().postProcess(queryMethod, queryString, arguments);
|
||||
|
||||
SelectResults<?> selectResults = getTemplate().find(processedQueryString, arguments);
|
||||
|
||||
return postProcess(queryMethod, selectResults);
|
||||
}
|
||||
|
||||
QueryString preProcess(QueryMethod queryMethod, QueryString query, Object[] arguments) {
|
||||
|
||||
query = isUserDefinedQuery() ? query
|
||||
: query.fromRegion(queryMethod.getEntityInformation().getJavaType(), getTemplate().getRegion());
|
||||
|
||||
ParametersParameterAccessor parameterAccessor =
|
||||
new ParametersParameterAccessor(localQueryMethod.getParameters(), parameters);
|
||||
new ParametersParameterAccessor(queryMethod.getParameters(), arguments);
|
||||
|
||||
for (Integer index : query.getInParameterIndexes()) {
|
||||
query = query.bindIn(toCollection(parameterAccessor.getBindableValue(index - 1)));
|
||||
}
|
||||
|
||||
query = applyQueryAnnotationExtensions(localQueryMethod, query);
|
||||
return query;
|
||||
}
|
||||
|
||||
Collection<?> result = toCollection(template.find(query.toString(), parameters));
|
||||
Object postProcess(QueryMethod queryMethod, SelectResults<?> selectResults) {
|
||||
|
||||
if (localQueryMethod.isCollectionQuery()) {
|
||||
return result;
|
||||
Collection<?> collection = toCollection(selectResults);
|
||||
|
||||
if (queryMethod.isCollectionQuery()) {
|
||||
return collection;
|
||||
}
|
||||
else if (localQueryMethod.isQueryForEntity()) {
|
||||
if (result.isEmpty()) {
|
||||
else if (queryMethod.isQueryForEntity()) {
|
||||
if (collection.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
else if (result.size() == 1) {
|
||||
return result.iterator().next();
|
||||
else if (collection.size() == 1) {
|
||||
return collection.iterator().next();
|
||||
}
|
||||
else {
|
||||
throw new IncorrectResultSizeDataAccessException(1, result.size());
|
||||
throw new IncorrectResultSizeDataAccessException(1, collection.size());
|
||||
}
|
||||
}
|
||||
else if (isSingleResultNonEntityQuery(localQueryMethod, result)) {
|
||||
return result.iterator().next();
|
||||
else if (isSingleNonEntityResult(queryMethod, collection)) {
|
||||
return collection.iterator().next();
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Unsupported query: " + query.toString());
|
||||
throw newIllegalStateException("Unsupported query: %s", query.toString());
|
||||
}
|
||||
}
|
||||
|
||||
QueryString applyQueryAnnotationExtensions(QueryMethod queryMethod, QueryString queryString) {
|
||||
QueryString resolvedQueryString = queryString;
|
||||
@SuppressWarnings("all")
|
||||
boolean isSingleNonEntityResult(QueryMethod method, Collection<?> result) {
|
||||
|
||||
if (queryMethod instanceof GemfireQueryMethod) {
|
||||
GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod;
|
||||
String query = queryString.toString().toUpperCase();
|
||||
Class<?> methodReturnType = method.getReturnedObjectType();
|
||||
|
||||
if (gemfireQueryMethod.hasImport() && !QueryString.IMPORT_PATTERN.matcher(query).find()) {
|
||||
resolvedQueryString = resolvedQueryString.withImport(gemfireQueryMethod.getImport());
|
||||
}
|
||||
if (gemfireQueryMethod.hasHint() && !QueryString.HINT_PATTERN.matcher(query).find()) {
|
||||
resolvedQueryString = resolvedQueryString.withHints(gemfireQueryMethod.getHints());
|
||||
}
|
||||
if (gemfireQueryMethod.hasLimit() && !QueryString.LIMIT_PATTERN.matcher(query).find()) {
|
||||
resolvedQueryString = resolvedQueryString.withLimit(gemfireQueryMethod.getLimit());
|
||||
}
|
||||
if (gemfireQueryMethod.hasTrace() && !QueryString.TRACE_PATTERN.matcher(query).find()) {
|
||||
resolvedQueryString = resolvedQueryString.withTrace();
|
||||
}
|
||||
}
|
||||
methodReturnType = methodReturnType != null ? methodReturnType : Void.class;
|
||||
|
||||
return resolvedQueryString;
|
||||
}
|
||||
|
||||
boolean isSingleResultNonEntityQuery(QueryMethod method, Collection<?> result) {
|
||||
return (!method.isCollectionQuery() && method.getReturnedObjectType() != null
|
||||
&& !Void.TYPE.equals(method.getReturnedObjectType()) && result != null && result.size() == 1);
|
||||
return nullSafeSize(result) == 1 && !Void.TYPE.equals(methodReturnType) && !method.isCollectionQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,7 +222,8 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
* @see org.springframework.util.CollectionUtils#arrayToList(Object)
|
||||
* @see org.apache.geode.cache.query.SelectResults
|
||||
*/
|
||||
Collection<?> toCollection(final Object source) {
|
||||
Collection<?> toCollection(Object source) {
|
||||
|
||||
if (source instanceof SelectResults) {
|
||||
return ((SelectResults) source).asList();
|
||||
}
|
||||
@@ -198,6 +236,81 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return (source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singletonList(source));
|
||||
return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singletonList(source);
|
||||
}
|
||||
|
||||
enum ProvidedQueryPostProcessors implements QueryPostProcessor<Repository, String> {
|
||||
|
||||
HINT {
|
||||
|
||||
@Override
|
||||
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
|
||||
|
||||
if (queryMethod instanceof GemfireQueryMethod) {
|
||||
|
||||
GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod;
|
||||
|
||||
if (gemfireQueryMethod.hasHint() && !QueryString.HINT_PATTERN.matcher(query).find()) {
|
||||
query = QueryString.of(query).withHints(gemfireQueryMethod.getHints()).toString();
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
},
|
||||
|
||||
IMPORT {
|
||||
|
||||
@Override
|
||||
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
|
||||
|
||||
if (queryMethod instanceof GemfireQueryMethod) {
|
||||
|
||||
GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod;
|
||||
|
||||
if (gemfireQueryMethod.hasImport() && !QueryString.IMPORT_PATTERN.matcher(query).find()) {
|
||||
query = QueryString.of(query).withImport(gemfireQueryMethod.getImport()).toString();
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
},
|
||||
|
||||
LIMIT {
|
||||
|
||||
@Override
|
||||
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
|
||||
|
||||
if (queryMethod instanceof GemfireQueryMethod) {
|
||||
|
||||
GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod;
|
||||
|
||||
if (gemfireQueryMethod.hasLimit() && !QueryString.LIMIT_PATTERN.matcher(query).find()) {
|
||||
query = QueryString.of(query).withLimit(gemfireQueryMethod.getLimit()).toString();
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
},
|
||||
|
||||
TRACE {
|
||||
|
||||
@Override
|
||||
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
|
||||
|
||||
if (queryMethod instanceof GemfireQueryMethod) {
|
||||
|
||||
GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod;
|
||||
|
||||
if (gemfireQueryMethod.hasTrace() && !QueryString.TRACE_PATTERN.matcher(query).find()) {
|
||||
query = QueryString.of(query).withTrace().toString();
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.util.StringUtils;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public enum OqlKeyword {
|
||||
|
||||
AND,
|
||||
AS,
|
||||
COUNT,
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.gemfire.mapping.GemfirePersistentEntity;
|
||||
import org.springframework.data.gemfire.mapping.GemfirePersistentProperty;
|
||||
@@ -34,6 +35,7 @@ import org.springframework.data.gemfire.repository.query.PartTreeGemfireReposito
|
||||
import org.springframework.data.gemfire.repository.query.StringBasedGemfireRepositoryQuery;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
@@ -41,6 +43,9 @@ import org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
import org.springframework.data.repository.query.EvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -54,25 +59,65 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
private static final Class<org.springframework.data.gemfire.mapping.annotation.Region> REGION_ANNOTATION =
|
||||
org.springframework.data.gemfire.mapping.annotation.Region.class;
|
||||
|
||||
static final String REGION_NOT_FOUND = "Region [%1$s] for Domain Type [%2$s] using Repository [%3$s] was not found;"
|
||||
+ " You must configure a Region with name [%1$s] in the application context";
|
||||
|
||||
static final String REGION_REPOSITORY_ID_TYPE_MISMATCH =
|
||||
"Region [%1$s] requires keys of type [%2$s], but Repository [%3$s] declared an id of type [%4$s]";
|
||||
|
||||
static final String REPOSITORY_ENTITY_ID_TYPE_MISMATCH =
|
||||
"Repository [%1$s] declared an id of type [%2$s], but entity [%3$s] has an id of type [%4$s]";
|
||||
|
||||
private final MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext;
|
||||
|
||||
private final Regions regions;
|
||||
|
||||
/**
|
||||
* Creates a new {@link GemfireRepositoryFactory}.
|
||||
* Constructs a new instance of {@link GemfireRepositoryFactory} initialized with the given collection
|
||||
* of configured {@link Region Regions} and the {@link MappingContext}.
|
||||
*
|
||||
* @param regions must not be {@literal null}.
|
||||
* @param mappingContext the {@link MappingContext} used by the constructed Repository for mapping entities
|
||||
* to the underlying data store, must not be {@literal null}.
|
||||
* @param regions {@link Iterable} collection of configured {@link Region Regions} used by this application;
|
||||
* must not be {@literal null}.
|
||||
* @param mappingContext {@link MappingContext} used to map entities to the underlying data store,
|
||||
* must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if either {@link Regions} or the {@link MappingContext} is {@literal null}.
|
||||
* @see org.springframework.data.gemfire.mapping.GemfireMappingContext
|
||||
* @see org.springframework.data.gemfire.mapping.Regions
|
||||
* @see org.springframework.data.mapping.context.MappingContext
|
||||
*/
|
||||
public GemfireRepositoryFactory(Iterable<Region<?, ?>> regions,
|
||||
MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext) {
|
||||
public GemfireRepositoryFactory(@NonNull Iterable<Region<?, ?>> regions,
|
||||
@NonNull MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext) {
|
||||
|
||||
Assert.notNull(regions, "Regions must not be null");
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null");
|
||||
Assert.notNull(regions, "Regions are required");
|
||||
Assert.notNull(mappingContext, "MappingContext is required");
|
||||
|
||||
this.regions = new Regions(regions, mappingContext);
|
||||
this.mappingContext = mappingContext;
|
||||
this.regions = new Regions(regions, this.mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the GemFire {@link MappingContext} used to provide mapping meta-data
|
||||
* between {@link Class entity types} and the data store.
|
||||
*
|
||||
* @return a reference to the GemFire {@link MappingContext}.
|
||||
* @see org.springframework.data.gemfire.mapping.GemfireMappingContext
|
||||
* @see org.springframework.data.mapping.context.MappingContext
|
||||
*/
|
||||
protected MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> getMappingContext() {
|
||||
return this.mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the configured, application-defined {@link Region Regions}.
|
||||
*
|
||||
* @return a reference to the configured, application-defined {@link Region Regions}.
|
||||
* @see org.springframework.data.gemfire.mapping.Regions
|
||||
*/
|
||||
protected Regions getRegions() {
|
||||
return this.regions;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -80,13 +125,18 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T, ID> GemfireEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
|
||||
return new DefaultGemfireEntityInformation<>(resolvePersistentEntity(domainClass));
|
||||
}
|
||||
|
||||
GemfirePersistentEntity<T> entity =
|
||||
(GemfirePersistentEntity<T>) mappingContext.getPersistentEntity(domainClass);
|
||||
|
||||
return new DefaultGemfireEntityInformation<>(entity);
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* #getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
|
||||
return SimpleGemfireRepository.class;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -99,53 +149,96 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
GemfireEntityInformation<?, Serializable> entityInformation =
|
||||
getEntityInformation(repositoryInformation.getDomainType());
|
||||
|
||||
GemfireTemplate gemfireTemplate = getTemplate(repositoryInformation);
|
||||
GemfireTemplate gemfireTemplate = newTemplate(repositoryInformation);
|
||||
|
||||
return getTargetRepositoryViaReflection(repositoryInformation, gemfireTemplate, entityInformation);
|
||||
}
|
||||
|
||||
GemfireTemplate getTemplate(RepositoryMetadata metadata) {
|
||||
|
||||
GemfirePersistentEntity<?> entity = mappingContext.getPersistentEntity(metadata.getDomainType());
|
||||
|
||||
String entityRegionName = entity.getRegionName();
|
||||
String repositoryRegionName = getRepositoryRegionName(metadata.getRepositoryInterface());
|
||||
String resolvedRegionName = StringUtils.hasText(repositoryRegionName) ? repositoryRegionName : entityRegionName;
|
||||
|
||||
Region<?, ?> region = regions.getRegion(resolvedRegionName);
|
||||
|
||||
if (region == null) {
|
||||
throw newIllegalStateException("No Region [%1$s] was found for domain class [%2$s];"
|
||||
+ " Make sure you have configured a GemFire Region of that name in your application context",
|
||||
resolvedRegionName, metadata.getDomainType().getName());
|
||||
}
|
||||
|
||||
Class<?> regionKeyType = region.getAttributes().getKeyConstraint();
|
||||
Class<?> entityIdType = metadata.getIdType();
|
||||
|
||||
if (regionKeyType != null && entity.getIdProperty() != null) {
|
||||
Assert.isTrue(regionKeyType.isAssignableFrom(entityIdType), String.format(
|
||||
"The Region referenced only supports keys of type [%1$s], but the entity to be stored has an id of type [%2$s]",
|
||||
regionKeyType.getName(), entityIdType.getName()));
|
||||
}
|
||||
|
||||
return new GemfireTemplate(region);
|
||||
}
|
||||
|
||||
String getRepositoryRegionName(Class<?> repositoryInterface) {
|
||||
return (repositoryInterface.isAnnotationPresent(org.springframework.data.gemfire.mapping.annotation.Region.class) ?
|
||||
repositoryInterface.getAnnotation(org.springframework.data.gemfire.mapping.annotation.Region.class).value() : null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
/**
|
||||
* Constructs a new instance of {@link GemfireTemplate} initialized with the identified {@link Region}
|
||||
* used to back all persistent, data access operations defined by the {@link Repository}.
|
||||
*
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* #getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
|
||||
* @param repositoryMetadata {@link RepositoryMetadata} containing meta-data about the {@link Repository}.
|
||||
* @return a new instance of {@link GemfireTemplate} initialized with the identified {@link Region}.
|
||||
* @see #resolvePersistentEntity(Class)
|
||||
* @see #resolveRegion(RepositoryMetadata, GemfirePersistentEntity)
|
||||
* @see #validate(RepositoryMetadata, GemfirePersistentEntity, Region)
|
||||
* @see org.springframework.data.repository.core.RepositoryMetadata
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see org.apache.geode.cache.Region
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
|
||||
return SimpleGemfireRepository.class;
|
||||
protected GemfireTemplate newTemplate(RepositoryMetadata repositoryMetadata) {
|
||||
|
||||
GemfirePersistentEntity<?> entity = resolvePersistentEntity(repositoryMetadata.getDomainType());
|
||||
|
||||
return new GemfireTemplate(validate(repositoryMetadata, entity, resolveRegion(repositoryMetadata, entity)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> GemfirePersistentEntity<T> resolvePersistentEntity(Class<?> domainType) {
|
||||
return (GemfirePersistentEntity<T>) getMappingContext().getPersistentEntity(domainType);
|
||||
}
|
||||
|
||||
private Region<?, ?> resolveRegion(RepositoryMetadata repositoryMetadata, GemfirePersistentEntity entity) {
|
||||
|
||||
String resolvedRegionName = getRepositoryRegionName(repositoryMetadata)
|
||||
.orElseGet(() -> getEntityRegionName(repositoryMetadata, entity));
|
||||
|
||||
return resolveRegion(repositoryMetadata, resolvedRegionName);
|
||||
}
|
||||
|
||||
private Region<?, ?> validate(RepositoryMetadata repositoryMetadata, GemfirePersistentEntity<?> entity,
|
||||
Region<?, ?> region) {
|
||||
|
||||
Assert.notNull(region, "Region is required");
|
||||
|
||||
Class<?> repositoryIdType = repositoryMetadata.getIdType();
|
||||
|
||||
Optional.ofNullable(region.getAttributes().getKeyConstraint())
|
||||
.ifPresent(regionKeyType -> Assert.isTrue(regionKeyType.isAssignableFrom(repositoryIdType),
|
||||
() -> String.format(REGION_REPOSITORY_ID_TYPE_MISMATCH, region.getFullPath(), regionKeyType.getName(),
|
||||
repositoryMetadata.getRepositoryInterface().getName(), repositoryIdType.getName())));
|
||||
|
||||
Optional.ofNullable(entity)
|
||||
.map(GemfirePersistentEntity::getIdProperty)
|
||||
.ifPresent(entityIdProperty -> Assert.isTrue(repositoryIdType.isAssignableFrom(entityIdProperty.getType()),
|
||||
() -> String.format(REPOSITORY_ENTITY_ID_TYPE_MISMATCH, repositoryMetadata.getRepositoryInterface().getName(),
|
||||
repositoryIdType.getName(), entityIdProperty.getOwner().getName(), entityIdProperty.getTypeName())));
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
String getEntityRegionName(@NonNull RepositoryMetadata repositoryMetadata,
|
||||
@Nullable GemfirePersistentEntity entity) {
|
||||
|
||||
Optional<GemfirePersistentEntity> optionalEntity = Optional.ofNullable(entity);
|
||||
|
||||
return optionalEntity
|
||||
.map(GemfirePersistentEntity::getRegionName)
|
||||
.filter(StringUtils::hasText)
|
||||
.orElseGet(() -> optionalEntity
|
||||
.map(GemfirePersistentEntity::getType)
|
||||
.map(Class::getSimpleName)
|
||||
.orElseGet(() -> repositoryMetadata.getDomainType().getSimpleName()));
|
||||
}
|
||||
|
||||
Optional<String> getRepositoryRegionName(@NonNull RepositoryMetadata repositoryMetadata) {
|
||||
|
||||
return Optional.ofNullable(repositoryMetadata)
|
||||
.map(RepositoryMetadata::getRepositoryInterface)
|
||||
.filter(repositoryInterface -> repositoryInterface.isAnnotationPresent(REGION_ANNOTATION))
|
||||
.map(repositoryInterface -> repositoryInterface.getAnnotation(REGION_ANNOTATION))
|
||||
.map(regionAnnotation -> regionAnnotation.value())
|
||||
.filter(StringUtils::hasText);
|
||||
}
|
||||
|
||||
Region<?, ?> resolveRegion(@NonNull RepositoryMetadata repositoryMetadata, String regionNamePath) {
|
||||
|
||||
return Optional.ofNullable(getRegions().getRegion(regionNamePath))
|
||||
.orElseThrow(() -> newIllegalStateException(REGION_NOT_FOUND,
|
||||
regionNamePath, repositoryMetadata.getDomainType().getName(),
|
||||
repositoryMetadata.getRepositoryInterface().getName()));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -158,22 +251,33 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
|
||||
EvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
return Optional.of(
|
||||
(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries) -> {
|
||||
return Optional.of((Method method, RepositoryMetadata repositoryMetadata, ProjectionFactory projectionFactory,
|
||||
NamedQueries namedQueries) -> {
|
||||
|
||||
GemfireQueryMethod queryMethod = new GemfireQueryMethod(method, metadata, factory, mappingContext);
|
||||
GemfireTemplate template = getTemplate(metadata);
|
||||
GemfireQueryMethod queryMethod =
|
||||
newQueryMethod(method, repositoryMetadata, projectionFactory, evaluationContextProvider);
|
||||
|
||||
GemfireTemplate template = newTemplate(repositoryMetadata);
|
||||
|
||||
if (queryMethod.hasAnnotatedQuery()) {
|
||||
return new StringBasedGemfireRepositoryQuery(queryMethod, template).asUserDefinedQuery();
|
||||
}
|
||||
|
||||
if (namedQueries.hasQuery(queryMethod.getNamedQueryName())) {
|
||||
return new StringBasedGemfireRepositoryQuery(namedQueries.getQuery(queryMethod.getNamedQueryName()),
|
||||
String namedQueryName = queryMethod.getNamedQueryName();
|
||||
|
||||
if (namedQueries.hasQuery(namedQueryName)) {
|
||||
return new StringBasedGemfireRepositoryQuery(namedQueries.getQuery(namedQueryName),
|
||||
queryMethod, template).asUserDefinedQuery();
|
||||
}
|
||||
|
||||
return new PartTreeGemfireRepositoryQuery(queryMethod, template);
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "unused" })
|
||||
protected <T extends QueryMethod> T newQueryMethod(Method method, RepositoryMetadata repositoryMetadata,
|
||||
ProjectionFactory projectionFactory, EvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
return (T) new GemfireQueryMethod(method, repositoryMetadata, projectionFactory, getMappingContext());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,23 +16,41 @@
|
||||
|
||||
package org.springframework.data.gemfire.repository.support;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.data.gemfire.mapping.GemfirePersistentEntity;
|
||||
import org.springframework.data.gemfire.mapping.GemfirePersistentProperty;
|
||||
import org.springframework.data.gemfire.repository.query.GemfireRepositoryQuery;
|
||||
import org.springframework.data.gemfire.repository.query.QueryPostProcessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.RepositoryDefinition;
|
||||
import org.springframework.data.repository.core.support.QueryCreationListener;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} adapter for {@link GemfireRepositoryFactory}.
|
||||
*
|
||||
@@ -50,9 +68,11 @@ import org.springframework.util.Assert;
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* @see org.apache.geode.cache.Region
|
||||
*/
|
||||
public class GemfireRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable>
|
||||
public class GemfireRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
|
||||
extends RepositoryFactoryBeanSupport<T, S, ID> implements ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private Iterable<Region<?, ?>> regions;
|
||||
|
||||
private MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext;
|
||||
@@ -67,33 +87,49 @@ public class GemfireRepositoryFactoryBean<T extends Repository<S, ID>, S, ID ext
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a reference to the Spring {@link ApplicationContext} in which this object runs.
|
||||
* Sets a reference to the Spring {@link ApplicationContext}.
|
||||
*
|
||||
* @param applicationContext the Spring {@link ApplicationContext} reference.
|
||||
* @param applicationContext reference to the Spring {@link ApplicationContext}.
|
||||
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(ApplicationContext)
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
Collection<Region> regions = applicationContext.getBeansOfType(Region.class).values();
|
||||
this.regions = (Iterable) Collections.unmodifiableCollection(regions);
|
||||
|
||||
this.applicationContext = applicationContext;
|
||||
|
||||
this.regions = Collections.unmodifiableSet(applicationContext.getBeansOfType(Region.class).entrySet().stream()
|
||||
.<Region<?, ?>>map(Map.Entry::getValue)
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link MappingContext} used to perform domain object type to store mappings.
|
||||
* Returns an {@link Optional} reference to the configured Spring {@link ApplicationContext}.
|
||||
*
|
||||
* @param mappingContext the {@link MappingContext} to set.
|
||||
* @return an {@link Optional} reference to the configured Spring {@link ApplicationContext}.
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see java.util.Optional
|
||||
*/
|
||||
protected Optional<ApplicationContext> getApplicationContext() {
|
||||
return Optional.ofNullable(this.applicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link MappingContext} used to perform application domain object type to data store mappings.
|
||||
*
|
||||
* @param mappingContext {@link MappingContext} to configure.
|
||||
* @see org.springframework.data.gemfire.mapping.GemfireMappingContext
|
||||
* @see org.springframework.data.mapping.context.MappingContext
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setGemfireMappingContext(MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext) {
|
||||
setMappingContext(mappingContext);
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the Spring Data {@link MappingContext} used to perform domain object type
|
||||
* Returns a reference to the Spring Data {@link MappingContext} used to perform application domain object type
|
||||
* to data store mappings.
|
||||
*
|
||||
* @return a reference to the {@link MappingContext}.
|
||||
@@ -106,10 +142,31 @@ public class GemfireRepositoryFactoryBean<T extends Repository<S, ID>, S, ID ext
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Iterable} reference to the GemFire {@link Region}s defined
|
||||
* in the Spring {@link ApplicationContext}.
|
||||
* Attempts to resolve the {@link MappingContext} used to map {@link GemfirePersistentEntity entities}
|
||||
* to Pivotal GemFire.
|
||||
*
|
||||
* @return a reference to all GemFire {@link Region}s defined in the Spring {@link ApplicationContext}.
|
||||
* @return a reference to the resolved {@link MappingContext}.
|
||||
* @throws IllegalStateException if the {@link MappingContext} cannot be resolved.
|
||||
* @see org.springframework.data.gemfire.mapping.GemfireMappingContext
|
||||
* @see org.springframework.data.mapping.context.MappingContext
|
||||
*/
|
||||
private MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> resolveGemfireMappingContext() {
|
||||
|
||||
MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext =
|
||||
getGemfireMappingContext();
|
||||
|
||||
Assert.state(mappingContext != null, "GemfireMappingContext must not be null");
|
||||
|
||||
return mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Iterable} reference to the {@link Region Regions}
|
||||
* defined in the Spring {@link ApplicationContext}.
|
||||
*
|
||||
* @return a reference to all {@link Region Regions} defined in the Spring {@link ApplicationContext}.
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see java.lang.Iterable
|
||||
*/
|
||||
protected Iterable<Region<?, ?>> getRegions() {
|
||||
return this.regions;
|
||||
@@ -123,7 +180,15 @@ public class GemfireRepositoryFactoryBean<T extends Repository<S, ID>, S, ID ext
|
||||
*/
|
||||
@Override
|
||||
protected RepositoryFactorySupport createRepositoryFactory() {
|
||||
return new GemfireRepositoryFactory(getRegions(), getGemfireMappingContext());
|
||||
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(getRegions(), getGemfireMappingContext());
|
||||
|
||||
getApplicationContext()
|
||||
.map(applicationContext -> new QueryPostProcessorRegistrationOnQueryCreationListener(applicationContext))
|
||||
.ifPresent(repositoryFactory::addQueryCreationListener);
|
||||
|
||||
return repositoryFactory;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -132,7 +197,108 @@ public class GemfireRepositoryFactoryBean<T extends Repository<S, ID>, S, ID ext
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.state(getGemfireMappingContext() != null, "GemfireMappingContext must not be null");
|
||||
resolveGemfireMappingContext();
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
|
||||
protected class QueryPostProcessorRegistrationOnQueryCreationListener
|
||||
implements QueryCreationListener<GemfireRepositoryQuery> {
|
||||
|
||||
private Iterable<QueryPostProcessorMetadata> queryPostProcessorsMetadata;
|
||||
|
||||
public QueryPostProcessorRegistrationOnQueryCreationListener(ApplicationContext applicationContext) {
|
||||
|
||||
Assert.notNull(applicationContext, "ApplicationContext must not be null");
|
||||
|
||||
List<QueryPostProcessor> queryPostProcessors =
|
||||
new ArrayList<>(applicationContext.getBeansOfType(QueryPostProcessor.class).values());
|
||||
|
||||
queryPostProcessors.sort(OrderComparator.INSTANCE);
|
||||
|
||||
this.queryPostProcessorsMetadata = queryPostProcessors.stream()
|
||||
.map(QueryPostProcessorMetadata::from)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected Iterable<QueryPostProcessorMetadata> getQueryPostProcessorsMetadata() {
|
||||
return this.queryPostProcessorsMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreation(GemfireRepositoryQuery repositoryQuery) {
|
||||
|
||||
Class<?> repositoryInterface = getRepositoryInformation().getRepositoryInterface();
|
||||
|
||||
StreamSupport.stream(getQueryPostProcessorsMetadata().spliterator(), false)
|
||||
.filter(queryPostProcessorMetadata -> queryPostProcessorMetadata.isMatch(repositoryInterface))
|
||||
.forEach(queryPostProcessorMetadata -> queryPostProcessorMetadata.register(repositoryQuery));
|
||||
}
|
||||
}
|
||||
|
||||
static class QueryPostProcessorMetadata {
|
||||
|
||||
private static final Map<QueryPostProcessorKey, QueryPostProcessorMetadata> cache = new WeakHashMap<>();
|
||||
|
||||
private final Class<?> declaredRepositoryType;
|
||||
|
||||
private final QueryPostProcessor<?, ?> queryPostProcessor;
|
||||
|
||||
static QueryPostProcessorMetadata from(@NonNull QueryPostProcessor<?, ?> queryPostProcessor) {
|
||||
|
||||
return cache.computeIfAbsent(QueryPostProcessorKey.of(queryPostProcessor),
|
||||
key -> new QueryPostProcessorMetadata(key.getQueryPostProcessor()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
QueryPostProcessorMetadata(@NonNull QueryPostProcessor<?, ?> queryPostProcessor) {
|
||||
|
||||
Assert.notNull(queryPostProcessor, "QueryPostProcessor must not be null");
|
||||
|
||||
this.queryPostProcessor = queryPostProcessor;
|
||||
|
||||
List<TypeInformation<?>> typeArguments = ClassTypeInformation.from(queryPostProcessor.getClass())
|
||||
.getRequiredSuperTypeInformation(QueryPostProcessor.class)
|
||||
.getTypeArguments();
|
||||
|
||||
this.declaredRepositoryType = Optional.of(typeArguments)
|
||||
.filter(list -> !list.isEmpty())
|
||||
.map(list -> list.get(0))
|
||||
.map(typeInfo -> typeInfo.getType())
|
||||
.orElse((Class) Repository.class);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Class<?> getDeclaredRepositoryType() {
|
||||
return this.declaredRepositoryType;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@SuppressWarnings("unchecked")
|
||||
QueryPostProcessor<?, String> getQueryPostProcessor() {
|
||||
return (QueryPostProcessor<?, String>) this.queryPostProcessor;
|
||||
}
|
||||
|
||||
boolean isMatch(Class<?> repositoryInterface) {
|
||||
|
||||
return repositoryInterface != null
|
||||
&& (getDeclaredRepositoryType().isAssignableFrom(repositoryInterface)
|
||||
|| repositoryInterface.isAnnotationPresent(RepositoryDefinition.class));
|
||||
}
|
||||
|
||||
GemfireRepositoryQuery register(GemfireRepositoryQuery repositoryQuery) {
|
||||
|
||||
repositoryQuery.register(getQueryPostProcessor());
|
||||
|
||||
return repositoryQuery;
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
private static class QueryPostProcessorKey {
|
||||
|
||||
@lombok.NonNull @Getter
|
||||
QueryPostProcessor<?, ?> queryPostProcessor;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
* @param entityInformation must not be {@literal null}.
|
||||
*/
|
||||
public SimpleGemfireRepository(GemfireTemplate template, EntityInformation<T, ID> entityInformation) {
|
||||
|
||||
Assert.notNull(template, "Template must not be null");
|
||||
Assert.notNull(entityInformation, "EntityInformation must not be null");
|
||||
|
||||
@@ -79,9 +80,10 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
*/
|
||||
@Override
|
||||
public <U extends T> U save(U entity) {
|
||||
ID id = entityInformation.getRequiredId(entity);
|
||||
|
||||
template.put(id, entity);
|
||||
ID id = this.entityInformation.getRequiredId(entity);
|
||||
|
||||
this.template.put(id, entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -92,11 +94,12 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
*/
|
||||
@Override
|
||||
public <U extends T> Iterable<U> saveAll(Iterable<U> entities) {
|
||||
|
||||
Map<ID, U> entitiesToSave = new HashMap<>();
|
||||
|
||||
entities.forEach(entity -> entitiesToSave.put(entityInformation.getRequiredId(entity), entity));
|
||||
entities.forEach(entity -> entitiesToSave.put(this.entityInformation.getRequiredId(entity), entity));
|
||||
|
||||
template.putAll(entitiesToSave);
|
||||
this.template.putAll(entitiesToSave);
|
||||
|
||||
return entitiesToSave.values();
|
||||
}
|
||||
@@ -107,9 +110,10 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
*/
|
||||
@Override
|
||||
public T save(Wrapper<T, ID> wrapper) {
|
||||
|
||||
T entity = wrapper.getEntity();
|
||||
|
||||
template.put(wrapper.getKey(), entity);
|
||||
this.template.put(wrapper.getKey(), entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -120,8 +124,9 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
*/
|
||||
@Override
|
||||
public long count() {
|
||||
|
||||
SelectResults<Integer> results =
|
||||
template.find(String.format("SELECT count(*) FROM %s", template.getRegion().getFullPath()));
|
||||
this.template.find(String.format("SELECT count(*) FROM %s", this.template.getRegion().getFullPath()));
|
||||
|
||||
return Long.valueOf(results.iterator().next());
|
||||
}
|
||||
@@ -141,7 +146,7 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
*/
|
||||
@Override
|
||||
public Optional<T> findById(ID id) {
|
||||
return Optional.ofNullable(template.get(id));
|
||||
return Optional.ofNullable(this.template.get(id));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -150,8 +155,9 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
*/
|
||||
@Override
|
||||
public Collection<T> findAll() {
|
||||
|
||||
SelectResults<T> results =
|
||||
template.find(String.format("SELECT * FROM %s", template.getRegion().getFullPath()));
|
||||
this.template.find(String.format("SELECT * FROM %s", this.template.getRegion().getFullPath()));
|
||||
|
||||
return results.asList();
|
||||
}
|
||||
@@ -162,11 +168,12 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
*/
|
||||
@Override
|
||||
public Iterable<T> findAll(Sort sort) {
|
||||
QueryString query = new QueryString("SELECT * FROM /RegionPlaceholder")
|
||||
.forRegion(entityInformation.getJavaType(), template.getRegion())
|
||||
|
||||
QueryString query = QueryString.of("SELECT * FROM /RegionPlaceholder")
|
||||
.fromRegion(this.entityInformation.getJavaType(), this.template.getRegion())
|
||||
.orderBy(sort);
|
||||
|
||||
SelectResults<T> selectResults = template.find(query.toString());
|
||||
SelectResults<T> selectResults = this.template.find(query.toString());
|
||||
|
||||
return selectResults.asList();
|
||||
}
|
||||
@@ -177,9 +184,10 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
*/
|
||||
@Override
|
||||
public Collection<T> findAllById(Iterable<ID> ids) {
|
||||
|
||||
List<ID> keys = Streamable.of(ids).stream().collect(StreamUtils.toUnmodifiableList());
|
||||
|
||||
return CollectionUtils.<ID, T>nullSafeMap(template.getAll(keys)).values().stream()
|
||||
return CollectionUtils.<ID, T>nullSafeMap(this.template.getAll(keys)).values().stream()
|
||||
.filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -189,7 +197,7 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
*/
|
||||
@Override
|
||||
public void deleteById(ID id) {
|
||||
template.remove(id);
|
||||
this.template.remove(id);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -198,7 +206,7 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
*/
|
||||
@Override
|
||||
public void delete(T entity) {
|
||||
deleteById(entityInformation.getRequiredId(entity));
|
||||
deleteById(this.entityInformation.getRequiredId(entity));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -216,8 +224,9 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
* @see org.apache.geode.cache.RegionAttributes#getDataPolicy()
|
||||
*/
|
||||
boolean isPartitioned(Region<?, ?> region) {
|
||||
return (region != null && region.getAttributes() != null
|
||||
&& isPartitioned(region.getAttributes().getDataPolicy()));
|
||||
|
||||
return region != null && region.getAttributes() != null
|
||||
&& isPartitioned(region.getAttributes().getDataPolicy());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -225,7 +234,7 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
* @see org.apache.geode.cache.DataPolicy#withPartitioning()
|
||||
*/
|
||||
boolean isPartitioned(DataPolicy dataPolicy) {
|
||||
return (dataPolicy != null && dataPolicy.withPartitioning());
|
||||
return dataPolicy != null && dataPolicy.withPartitioning();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -234,8 +243,9 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
* @see org.apache.geode.cache.Cache#getCacheTransactionManager()
|
||||
*/
|
||||
boolean isTransactionPresent(Region<?, ?> region) {
|
||||
return (region.getRegionService() instanceof Cache
|
||||
&& isTransactionPresent(((Cache) region.getRegionService()).getCacheTransactionManager()));
|
||||
|
||||
return region.getRegionService() instanceof Cache
|
||||
&& isTransactionPresent(((Cache) region.getRegionService()).getCacheTransactionManager());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -243,7 +253,7 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
* @see org.apache.geode.cache.CacheTransactionManager#exists()
|
||||
*/
|
||||
boolean isTransactionPresent(CacheTransactionManager cacheTransactionManager) {
|
||||
return (cacheTransactionManager != null && cacheTransactionManager.exists());
|
||||
return cacheTransactionManager != null && cacheTransactionManager.exists();
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@@ -257,7 +267,8 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
*/
|
||||
@Override
|
||||
public void deleteAll() {
|
||||
template.execute((GemfireCallback<Void>) region -> {
|
||||
this.template.execute((GemfireCallback<Void>) region -> {
|
||||
|
||||
if (isPartitioned(region) || isTransactionPresent(region)) {
|
||||
doRegionClear(region);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import static java.util.stream.StreamSupport.stream;
|
||||
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
@@ -29,12 +30,16 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The CollectionUtils class is a utility class for working with Java Collections Framework and classes.
|
||||
* {@link CollectionUtils} is a utility class for working with the Java Collections Framework and classes.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.util.Collection
|
||||
@@ -62,7 +67,7 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @see java.lang.Iterable
|
||||
* @see java.util.Collection
|
||||
*/
|
||||
public static <E, T extends Collection<E>> T addAll(T collection, Iterable<E> iterable) {
|
||||
public static <E, T extends Collection<E>> T addAll(@NonNull T collection, @Nullable Iterable<E> iterable) {
|
||||
|
||||
Assert.notNull(collection, "Collection is required");
|
||||
|
||||
@@ -79,7 +84,7 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @return an unmodifiable {@link Set} containing the elements from the given object array.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> Set<T> asSet(T... elements) {
|
||||
public static <T> Set<T> asSet(@NonNull T... elements) {
|
||||
|
||||
Set<T> set = new HashSet<>(elements.length);
|
||||
|
||||
@@ -96,17 +101,10 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @return a boolean value indicating whether the collection contains at least 1 element from the given array.
|
||||
* @see java.util.Collection#contains(Object)
|
||||
*/
|
||||
public static boolean containsAny(Collection<?> collection, Object... elements) {
|
||||
public static boolean containsAny(@Nullable Collection<?> collection, @Nullable Object... elements) {
|
||||
|
||||
if (collection != null) {
|
||||
for (Object element : nullSafeArray(elements, Object.class)) {
|
||||
if (collection.contains(element)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return Arrays.asList(nullSafeArray(elements, Object.class)).stream()
|
||||
.anyMatch(element -> nullSafeCollection(collection).contains(element));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,8 +115,9 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @see java.lang.Iterable
|
||||
* @see #nullSafeIterable(Iterable)
|
||||
*/
|
||||
@NonNull
|
||||
public static <T> Iterable<T> emptyIterable() {
|
||||
return nullSafeIterable(null);
|
||||
return Collections::emptyIterator;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,8 +129,9 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @see java.lang.Iterable
|
||||
* @see java.util.Enumeration
|
||||
*/
|
||||
public static <T> Iterable<T> iterable(Enumeration<T> enumeration) {
|
||||
return () -> toIterator(enumeration);
|
||||
@NonNull
|
||||
public static <T> Iterable<T> iterable(@Nullable Enumeration<T> enumeration) {
|
||||
return () -> toIterator(nullSafeEnumeration(enumeration));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,8 +143,9 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @see java.lang.Iterable
|
||||
* @see java.util.Iterator
|
||||
*/
|
||||
public static <T> Iterable<T> iterable(Iterator<T> iterator) {
|
||||
return () -> iterator;
|
||||
@NonNull
|
||||
public static <T> Iterable<T> iterable(@Nullable Iterator<T> iterator) {
|
||||
return () -> nullSafeIterator(iterator);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,10 +159,27 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @see java.util.Collections#emptyList()
|
||||
* @see java.util.Collection
|
||||
*/
|
||||
public static <T> Collection<T> nullSafeCollection(Collection<T> collection) {
|
||||
@NonNull
|
||||
public static <T> Collection<T> nullSafeCollection(@Nullable Collection<T> collection) {
|
||||
return collection != null ? collection : Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe operation returning the given {@link Enumeration} if not {@literal null}
|
||||
* or an {@link Collections#emptyEnumeration() empty Enumeration} if {@literal null}.
|
||||
*
|
||||
* @param <T> {@link Class type} of elements contained in the {@link Enumeration}.
|
||||
* @param enumeration {@link Enumeration} to evaluate.
|
||||
* @return the given {@link Enumeration} if not {@literal null}
|
||||
* or an {@link Collections#emptyEnumeration() empty Enumeration}.
|
||||
* @see java.util.Collections#emptyEnumeration()
|
||||
* @see java.util.Enumeration
|
||||
*/
|
||||
@NonNull
|
||||
public static <T> Enumeration<T> nullSafeEnumeration(@Nullable Enumeration<T> enumeration) {
|
||||
return enumeration != null ? enumeration : Collections.emptyEnumeration();
|
||||
}
|
||||
|
||||
/**
|
||||
* A null-safe operation returning the original Iterable object if non-null or a default, empty Iterable
|
||||
* implementation if null.
|
||||
@@ -169,11 +187,12 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @param <T> the class type of the iterable elements.
|
||||
* @param iterable the Iterable object evaluated for a null reference.
|
||||
* @return the Iterable object if not null or a default, empty Iterable implementation otherwise.
|
||||
* @see #emptyIterable()
|
||||
* @see java.lang.Iterable
|
||||
* @see java.util.Iterator
|
||||
*/
|
||||
public static <T> Iterable<T> nullSafeIterable(Iterable<T> iterable) {
|
||||
return iterable != null ? iterable : Collections::emptyIterator;
|
||||
@NonNull
|
||||
public static <T> Iterable<T> nullSafeIterable(@Nullable Iterable<T> iterable) {
|
||||
return iterable != null ? iterable : emptyIterable();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,10 +205,27 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @return {@code iterable} if not {@literal null} or empty otherwise return {@code defaultIterable}.
|
||||
* @see java.lang.Iterable
|
||||
*/
|
||||
public static <E, T extends Iterable<E>> T nullSafeIterable(T iterable, T defaultIterable) {
|
||||
@Nullable
|
||||
public static <E, T extends Iterable<E>> T nullSafeIterable(@Nullable T iterable, @Nullable T defaultIterable) {
|
||||
return Optional.ofNullable(iterable).filter(it -> it.iterator().hasNext()).orElse(defaultIterable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe operation returning the given {@link Iterator} if not {@literal null}
|
||||
* or an {@link Collections#emptyIterator() empty Iterator} if {@literal null}.
|
||||
*
|
||||
* @param <T> {@link Class type} of elements contained in the {@link Iterator}.
|
||||
* @param iterator {@link Iterator} to evaluate.
|
||||
* @return the given {@link Iterator} if not {@literal null}
|
||||
* or an {@link Collections#emptyIterator() empty Iterator}.
|
||||
* @see java.util.Collections#emptyIterator()
|
||||
* @see java.util.Iterator
|
||||
*/
|
||||
@NonNull
|
||||
public static <T> Iterator<T> nullSafeIterator(@Nullable Iterator<T> iterator) {
|
||||
return iterator != null ? iterator : Collections.emptyIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe operation returning the given {@link List} if not {@literal null}
|
||||
* or an empty {@link List} if {@literal null}.
|
||||
@@ -200,7 +236,8 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @see java.util.Collections#emptyList()
|
||||
* @see java.util.List
|
||||
*/
|
||||
public static <T> List<T> nullSafeList(List<T> list) {
|
||||
@NonNull
|
||||
public static <T> List<T> nullSafeList(@Nullable List<T> list) {
|
||||
return list != null ? list : Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -216,7 +253,8 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @see java.util.Map
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public static <K, V> Map<K, V> nullSafeMap(Map<K, V> map) {
|
||||
@NonNull
|
||||
public static <K, V> Map<K, V> nullSafeMap(@Nullable Map<K, V> map) {
|
||||
return map != null ? map : Collections.<K, V>emptyMap();
|
||||
}
|
||||
|
||||
@@ -230,10 +268,59 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @see java.util.Collections#emptySet()
|
||||
* @see java.util.Set
|
||||
*/
|
||||
public static <T> Set<T> nullSafeSet(Set<T> set) {
|
||||
@NonNull
|
||||
public static <T> Set<T> nullSafeSet(@Nullable Set<T> set) {
|
||||
return set != null ? set : Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given {@link Collection} is {@link Collection#isEmpty() empty}.
|
||||
*
|
||||
* @param collection {@link Collection} to evaluate.
|
||||
* @return a boolean value indicating whether the given {@link Collection} is {@link Collection#isEmpty() empty}.
|
||||
* @see #nullSafeCollection(Collection)
|
||||
* @see java.util.Collection#isEmpty()
|
||||
*/
|
||||
public static boolean nullSafeIsEmpty(@Nullable Collection<?> collection) {
|
||||
return nullSafeCollection(collection).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given {@link Map} is {@link Map#isEmpty() empty}.
|
||||
*
|
||||
* @param map {@link Map} to evaluate.
|
||||
* @return a boolean value indicating whether the given {@link Map} is {@link Map#isEmpty() empty}.
|
||||
* @see #nullSafeMap(Map)
|
||||
* @see java.util.Map#isEmpty()
|
||||
*/
|
||||
public static boolean nullSafeIsEmpty(@Nullable Map<?, ?> map) {
|
||||
return nullSafeMap(map).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the {@link Collection#size()} of the given {@link Collection}.
|
||||
*
|
||||
* @param collection {@link Collection} to evaluate.
|
||||
* @return the {@link Collection#size()} of the given {@link Collection}.
|
||||
* @see #nullSafeCollection(Collection)
|
||||
* @see java.util.Collection#size()
|
||||
*/
|
||||
public static int nullSafeSize(@Nullable Collection<?> collection) {
|
||||
return nullSafeCollection(collection).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the {@link Map#size()} of the given {@link Map}.
|
||||
*
|
||||
* @param map {@link Map} to evaluate.
|
||||
* @return the {@link Map#size()} of the given {@link Map}.
|
||||
* @see #nullSafeMap(Map)
|
||||
* @see java.util.Map#size()
|
||||
*/
|
||||
public static int nullSafeSize(@Nullable Map<?, ?> map) {
|
||||
return nullSafeMap(map).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sors the elements of the given {@link List} by their natural, {@link Comparable} ordering.
|
||||
*
|
||||
@@ -243,7 +330,9 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @see java.util.Collections#sort(List)
|
||||
* @see java.util.List
|
||||
*/
|
||||
public static <T extends Comparable<T>> List<T> sort(List<T> list) {
|
||||
public static <T extends Comparable<T>> List<T> sort(@NonNull List<T> list) {
|
||||
|
||||
Assert.notNull(list, "List is required");
|
||||
|
||||
Collections.sort(list);
|
||||
|
||||
@@ -263,7 +352,7 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
* @throws NullPointerException if either the list or indexes are null.
|
||||
* @see java.util.List
|
||||
*/
|
||||
public static <T> List<T> subList(List<T> source, int... indices) {
|
||||
public static <T> List<T> subList(@NonNull List<T> source, int... indices) {
|
||||
|
||||
Assert.notNull(source, "List is required");
|
||||
|
||||
@@ -276,23 +365,36 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio
|
||||
return result;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public static String toString(Map<?, ?> map) {
|
||||
/**
|
||||
* Returns a {@link String} representation of the given {@link Map}.
|
||||
*
|
||||
* @param map {@link Map} represent as a {@link String}.
|
||||
* @return a {@link String} describing the given {@link Map}.
|
||||
* @see #newSortedMap(Map)
|
||||
* @see java.util.Map
|
||||
*/
|
||||
@NonNull
|
||||
public static String toString(@Nullable Map<?, ?> map) {
|
||||
|
||||
StringBuilder builder = new StringBuilder("{\n");
|
||||
|
||||
int count = 0;
|
||||
AtomicInteger count = new AtomicInteger(0);
|
||||
|
||||
for (Map.Entry<?, ?> entry : new TreeMap<>(map).entrySet()) {
|
||||
builder.append(++count > 1 ? ",\n" : "");
|
||||
newSortedMap(map).forEach((key, value) -> {
|
||||
builder.append(count.incrementAndGet() > 1 ? ",\n" : "");
|
||||
builder.append("\t");
|
||||
builder.append(entry.getKey());
|
||||
builder.append(key);
|
||||
builder.append(" = ");
|
||||
builder.append(entry.getValue());
|
||||
}
|
||||
builder.append(value);
|
||||
});
|
||||
|
||||
builder.append("\n}");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static SortedMap<?, ?> newSortedMap(@Nullable Map<?, ?> map) {
|
||||
return new TreeMap<>(nullSafeMap(map));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user