SGF-713 - Override generated OQL from Repository query methods.
This commit is contained in:
@@ -45,7 +45,7 @@ https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#
|
||||
Using this approach, it is a simple matter to bootstrap _Spring Data Repositories_ using the SDG `@EnableGemfireRepositories`
|
||||
annotation:
|
||||
|
||||
.Bootstrap _Spring Data Geode Repositories_ with `@EnableGemfireRepositories`
|
||||
.Bootstrap _Spring Data GemFire Repositories_ with `@EnableGemfireRepositories`
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@@ -93,7 +93,7 @@ Many other aspects of the SDG's _Repository_ infrastructure extension maybe cust
|
||||
https://docs.spring.io/spring-data/gemfire/docs/current/api/org/springframework/data/gemfire/repository/config/EnableGemfireRepositories.html[`@EnableGemfireRepositories` _Javadoc_]
|
||||
for more details on all configuration settings.
|
||||
|
||||
[[gemfire-repositories.executing-queries]]
|
||||
[[gemfire-repositories.queries.executing]]
|
||||
== Executing OQL Queries
|
||||
|
||||
_Spring Data GemFire Repositories_ enable the definition of query methods to easily execute GemFire OQL Queries
|
||||
@@ -195,7 +195,7 @@ becomes too verbose, you can annotate the query methods with `@Query` as seen fo
|
||||
| `x.active = false`
|
||||
|===
|
||||
|
||||
[[gemfire-repositories:oql-extensions]]
|
||||
[[gemfire-repositories.queries.oql-extensions]]
|
||||
== OQL Query Extensions using Annotations
|
||||
|
||||
Many query languages, such as Pivotal GemFire's OQL (Object Query Language), have extensions that are not directly
|
||||
@@ -334,3 +334,197 @@ indexes and other optimizations wisely as an improper or poorly choosen index ca
|
||||
performance given the overhead in maintaining the index. The "ReputationIdx" was only used to serve the purpose
|
||||
of the example.
|
||||
====
|
||||
|
||||
[[gemfire-repositories.queries.post-processing]]
|
||||
== Query Post Processing
|
||||
|
||||
Using the Spring Data _Repository_ abstraction, query method convention for defining data store specific queries
|
||||
(e.g. OQL) is easy and convenient. However, it is sometimes desirable to still want to inspect or even possibly
|
||||
modify the query "generated" from the _Repository_ query method.
|
||||
|
||||
Since 2.0.x, _Spring Data GemFire_ introduces the `o.s.d.gemfire.repository.query.QueryPostProcessor`
|
||||
functional interface. The interface is loosely defined as follows...
|
||||
|
||||
.QueryPostProcessor
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import ...;
|
||||
|
||||
@FunctionalInterface
|
||||
interface QueryPostProcessor<T extends Repository, QUERY> extends Ordered {
|
||||
|
||||
QUERY postProcess(QueryMethod queryMethod, QUERY query, Object... arguments);
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
There are additional default methods provided to allow users to compose instances of `QueryPostProcessor` very similar
|
||||
to how https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html#compose-java.util.function.Function-[java.util.function.Function.andThen(:Function)]
|
||||
and https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html#compose-java.util.function.Function-[java.util.function.Function.compose(:Function)]
|
||||
work.
|
||||
|
||||
Additionally, you will notice that the `QueryPostProcessor` interface implements the
|
||||
https://docs.spring.io/spring/docs/5.0.2.RELEASE/javadoc-api/org/springframework/core/Ordered.html[`org.springframework.core.Ordered`]
|
||||
interface, which is useful when multiple `QueryPostProcessors` are declared and registered in the Spring context
|
||||
and used to create a pipeline of processing for a group of generated query method queries.
|
||||
|
||||
Finally, the `QueryPostProcessor` accepts type arguments corresponding to the type parameters, `T` and `QUERY`,
|
||||
respectively. Type of `T` extends the _Spring Data Commons_ marker interface,
|
||||
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/Repository.html[`org.springframework.data.repository.Repository`].
|
||||
We will discuss this further below. All `QUERY` type parameter arguments in _Spring Data GemFire's_ case
|
||||
will be of type `java.lang.String`.
|
||||
|
||||
NOTE: It is useful to define the query as type `QUERY` since this `QueryPostProcessor` interface maybe ported to
|
||||
_Spring Data Commons_ and therefore must handle all forms of queries by different data stores (e.g. JPA, MongoDB,
|
||||
or Redis).
|
||||
|
||||
As user may implement this interface to receive a callback with the query that was generated from the application
|
||||
`Repository` interface method when the method is called.
|
||||
|
||||
For example, I might want to log all queries from all application _Repository_ interface definitions. I could do so
|
||||
using the following `QueryPostProcessor` implementation...
|
||||
|
||||
.LoggingQueryPostProcessor
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
package example;
|
||||
|
||||
import ...;
|
||||
|
||||
class LoggingQueryPostProcessor implements QueryPostProcessor<Repository, String> {
|
||||
|
||||
private Logger logger = Logger.getLogger("someLoggerName");
|
||||
|
||||
@Override
|
||||
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
|
||||
|
||||
String message = String.format("Executing query [%s] with arguments [%s]", query, Arrays.toString(arguments));
|
||||
|
||||
this.logger.info(message);
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The `LoggingQueryPostProcessor` was typed to the Spring Data `org.springframework.data.repository.Repository`
|
||||
marker interface, and therefore, will log all application _Repository_ interface query method "generated" queries.
|
||||
|
||||
You could limit the scope of this logging to queries only from certain types of application _Repository_ interfaces,
|
||||
such as, say, an `CustomerRepository`...
|
||||
|
||||
.CustomerRepository
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface CustomerRepository extends CrudRepository<Customer, Long> {
|
||||
|
||||
Customer findByAccountNumber(String accountNumber);
|
||||
|
||||
List<Customer> findByLastNameLike(String lastName);
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Then, I could have typed the `LoggingQueryPostProcessor` specifically to the `CustomerRepository`, like so...
|
||||
|
||||
.CustomerLoggingQueryPostProcessor
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
class LoggingQueryPostProcessor implements QueryPostProcessor<CustomerRepository, String> { .. }
|
||||
----
|
||||
====
|
||||
|
||||
As result, only queries defined in the `CustomerRepository` interface (e.g. `findByAccountNumber`) would be logged.
|
||||
|
||||
I might want to create a `QueryPostProcessor` for a specific query defined by a _Repository_ query method. For example,
|
||||
say I want to "`LIMIT`" the OQL query generated from the `CustomerRepository.findByLastNameLike(:String)` query method
|
||||
to only return 5 results and I want to order the `Customers` by `firstName`, ascending. Well, then, I can define
|
||||
a custom `QueryPostProcessor` like so...
|
||||
|
||||
.OrderedLimitedCustomerByLastNameQueryPostProcessor
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
class OrderedLimitedCustomerByLastNameQueryPostProcessor implements QueryPostProcessor<CustomerRepository, String> {
|
||||
|
||||
private final int limit;
|
||||
|
||||
public OrderedLimitedCustomerByLastNameQueryPostProcessor(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
|
||||
|
||||
return "findByLastNameLike".equals(queryMethod.getName())
|
||||
? query.trim()
|
||||
.replace("SELECT", "SELECT DISTINCT")
|
||||
.concat(" ORDER BY firstName ASC")
|
||||
.concat(String.format(" LIMIT %d", this.limit))
|
||||
: query;
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
While this works, it possible to achieve the same affect just using the Spring Data _Repository_ convention and extensions
|
||||
provided by _Spring Data GemFire_. For instance, the same query could be defined as...
|
||||
|
||||
.CustomerRepository using the convention
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface CustomerRepository extends CrudRepository<Customer, Long> {
|
||||
|
||||
@Limit(5)
|
||||
List<Customer> findDistinctByLastNameLikeOrderByFirstNameDesc(String lastName);
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
However, if you do not have control over the application `CustomerRepository` interface definition,
|
||||
then the `QueryPostProcessor` (i.e. `OrderedLimitedCustomerByLastNameQueryPostProcessor`) is convenient.
|
||||
|
||||
If I want to ensure the `LoggingQueryPostProcessor` always comes after the other application-defined `QueryPostProcessors`
|
||||
that I may have declared and registered in the Spring `ApplicationContext`, then I can set the `order` property
|
||||
by overriding the `o.s.core.Ordered.getOrder()` method.
|
||||
|
||||
.Defining the `order` property
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
class LoggingQueryPostProcessor implements QueryPostProcessor<Repository, String> {
|
||||
|
||||
@Override
|
||||
int getOrder() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomerQueryPostProcessor implements QueryPostProcessor<CustomerRepository, String> {
|
||||
|
||||
@Override
|
||||
int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
This ensures that I will always see the affects of the post processing applied by my other `QueryPostProcessors`
|
||||
before my `LoggingQueryPostProcessor` logs the query.
|
||||
|
||||
You can define as many `QueryPostProcessors` in the Spring `ApplicationContext` as you like and apply them in any
|
||||
order, to all or specific application _Repository_ interfaces, and be a granular as yuo like using the provided
|
||||
arguments to the `postProcess(..)` method callback.
|
||||
|
||||
@@ -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,25 +29,33 @@ 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}.
|
||||
* Constructs a new instance of {@link GemfireRepositoryQuery} initialized with
|
||||
* the given {@link GemfireQueryMethod}.
|
||||
*
|
||||
* @param queryMethod must not be {@literal null}.
|
||||
* @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;
|
||||
}
|
||||
|
||||
@@ -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,22 +22,25 @@ 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}.
|
||||
@@ -51,9 +54,9 @@ 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);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -61,20 +64,41 @@ public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
* @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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.geode.cache.GemFireCache;
|
||||
import org.apache.geode.cache.Region;
|
||||
import org.apache.geode.cache.RegionAttributes;
|
||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
|
||||
import org.springframework.data.gemfire.repository.sample.Animal;
|
||||
import org.springframework.data.gemfire.repository.sample.Plant;
|
||||
import org.springframework.data.gemfire.repository.sample.PlantRepository;
|
||||
import org.springframework.data.gemfire.repository.sample.RabbitRepository;
|
||||
import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
/**
|
||||
* Integration tests for Spring Data [GemFire] Repositories testing compatibility of {@link Region}
|
||||
* {@link RegionAttributes#getKeyConstraint() key type}, {@link Repository} {@link Class ID type}
|
||||
* and {@link PersistentEntity entity} {@link Class ID type}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @since 1.4.0
|
||||
* @link https://github.com/spring-projects/spring-data-gemfire/pull/55
|
||||
*/
|
||||
public class IncompatibleRegionKeyRepositoryIdAndEntityIdRepositoryIntegrationTests {
|
||||
|
||||
private static ConfigurableApplicationContext newApplicationContext(Class<?> testConfiguration) {
|
||||
return new AnnotationConfigApplicationContext(testConfiguration);
|
||||
}
|
||||
|
||||
private void withTestConfigurationExpectIllegalArgumentExceptionWithMessage(Class<?> testConfiguration,
|
||||
Class<? extends Repository> repositoryType, Supplier<String> exceptionMessage) {
|
||||
|
||||
try {
|
||||
|
||||
ConfigurableApplicationContext applicationContext = newApplicationContext(testConfiguration);
|
||||
|
||||
assertThat(applicationContext.getBean(repositoryType)).isNotNull();
|
||||
}
|
||||
catch (BeanCreationException expected) {
|
||||
|
||||
assertThat(expected).hasCauseInstanceOf(IllegalArgumentException.class);
|
||||
assertThat(expected.getCause()).hasMessage(exceptionMessage.get());
|
||||
|
||||
throw (IllegalArgumentException) expected.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void withRegionUsingStringKeyAndRepositoryUsingLongId() {
|
||||
|
||||
withTestConfigurationExpectIllegalArgumentExceptionWithMessage(
|
||||
TestIncompatibleRegionKeyRepositoryIdTypeConfiguration.class,
|
||||
RabbitRepository.class,
|
||||
() -> String.format("Region [/Rabbits] requires keys of type [%1$s], but Repository [%2$s] declared an id of type [%3$s]",
|
||||
String.class.getName(), RabbitRepository.class.getName(), Long.class.getName()));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void withRepositoryUsingStringIdAndEntityUsingLongId() {
|
||||
|
||||
withTestConfigurationExpectIllegalArgumentExceptionWithMessage(
|
||||
TestIncompatibleRepositoryIdEntityIdTypeConfiguration.class,
|
||||
PlantRepository.class,
|
||||
() -> String.format("Repository [%1$s] declared an id of type [%2$s], but entity [%3$s] has an id of type [%4$s]",
|
||||
PlantRepository.class.getName(), String.class.getName(), Plant.class.getName(), Long.class.getName()));
|
||||
}
|
||||
|
||||
@ClientCacheApplication
|
||||
@EnableGemFireMockObjects
|
||||
@EnableGemfireRepositories(basePackageClasses = RabbitRepository.class,
|
||||
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = RabbitRepository.class)
|
||||
)
|
||||
@SuppressWarnings("unused")
|
||||
static class TestIncompatibleRegionKeyRepositoryIdTypeConfiguration {
|
||||
|
||||
@Bean("Rabbits")
|
||||
public ClientRegionFactoryBean<String, Animal> clientRegion(GemFireCache gemfireCache) {
|
||||
|
||||
ClientRegionFactoryBean<String, Animal> clientRegion = new ClientRegionFactoryBean<>();
|
||||
|
||||
clientRegion.setCache(gemfireCache);
|
||||
clientRegion.setClose(false);
|
||||
clientRegion.setKeyConstraint(String.class);
|
||||
clientRegion.setShortcut(ClientRegionShortcut.LOCAL);
|
||||
clientRegion.setValueConstraint(Animal.class);
|
||||
|
||||
return clientRegion;
|
||||
}
|
||||
}
|
||||
|
||||
@ClientCacheApplication
|
||||
@EnableGemFireMockObjects
|
||||
@EnableGemfireRepositories(basePackageClasses = PlantRepository.class,
|
||||
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = PlantRepository.class)
|
||||
)
|
||||
@SuppressWarnings("unused")
|
||||
static class TestIncompatibleRepositoryIdEntityIdTypeConfiguration {
|
||||
|
||||
@Bean("Plants")
|
||||
public ClientRegionFactoryBean<Object, Object> clientRegion(GemFireCache gemfireCache) {
|
||||
|
||||
ClientRegionFactoryBean<Object, Object> clientRegion = new ClientRegionFactoryBean<>();
|
||||
|
||||
clientRegion.setCache(gemfireCache);
|
||||
clientRegion.setClose(false);
|
||||
clientRegion.setShortcut(ClientRegionShortcut.PROXY);
|
||||
|
||||
return clientRegion;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,23 +17,18 @@
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.data.gemfire.mapping.GemfirePersistentEntity;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
|
||||
/**
|
||||
* Test suite of test cases testing the contract and functionality of the {@link QueryBuilder} class.
|
||||
* Unit tests for {@link QueryBuilder} class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
@@ -43,12 +38,11 @@ import org.springframework.data.repository.query.parser.PartTree;
|
||||
*/
|
||||
public class QueryBuilderUnitTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void createQueryBuilderWithDistinctQuery() {
|
||||
|
||||
GemfirePersistentEntity<?> mockPersistentEntity = mock(GemfirePersistentEntity.class);
|
||||
|
||||
PartTree mockPartTree = mock(PartTree.class);
|
||||
|
||||
when(mockPersistentEntity.getRegionName()).thenReturn("Example");
|
||||
@@ -64,7 +58,9 @@ public class QueryBuilderUnitTests {
|
||||
|
||||
@Test
|
||||
public void createQueryBuilderWithNonDistinctQuery() {
|
||||
|
||||
GemfirePersistentEntity<?> mockPersistentEntity = mock(GemfirePersistentEntity.class);
|
||||
|
||||
PartTree mockPartTree = mock(PartTree.class);
|
||||
|
||||
when(mockPersistentEntity.getRegionName()).thenReturn("Example");
|
||||
@@ -78,24 +74,31 @@ public class QueryBuilderUnitTests {
|
||||
verify(mockPartTree, times(1)).isDistinct();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void createQueryBuilderWithNullQueryString() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(is(equalTo("An OQL Query must be specified")));
|
||||
|
||||
new QueryBuilder(null);
|
||||
try {
|
||||
new QueryBuilder(null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Query is required");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("all")
|
||||
public void createWithPredicate() {
|
||||
|
||||
Predicate mockPredicate = mock(Predicate.class);
|
||||
|
||||
when(mockPredicate.toString(eq(QueryBuilder.DEFAULT_ALIAS))).thenReturn("x.id = 1");
|
||||
|
||||
QueryBuilder queryBuilder = new QueryBuilder(
|
||||
String.format("SELECT * FROM /Example %s", QueryBuilder.DEFAULT_ALIAS));
|
||||
QueryBuilder queryBuilder =
|
||||
new QueryBuilder(String.format("SELECT * FROM /Example %s", QueryBuilder.DEFAULT_ALIAS));
|
||||
|
||||
QueryString queryString = queryBuilder.create(mockPredicate);
|
||||
|
||||
@@ -107,6 +110,7 @@ public class QueryBuilderUnitTests {
|
||||
|
||||
@Test
|
||||
public void createWithNullPredicate() {
|
||||
|
||||
QueryBuilder queryBuilder = new QueryBuilder("SELECT * FROM /Example");
|
||||
|
||||
QueryString queryString = queryBuilder.create(null);
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.apache.geode.cache.GemFireCache;
|
||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
|
||||
import org.springframework.data.gemfire.mapping.GemfireMappingContext;
|
||||
import org.springframework.data.gemfire.repository.sample.Person;
|
||||
import org.springframework.data.gemfire.repository.sample.PersonRepository;
|
||||
import org.springframework.data.gemfire.repository.sample.User;
|
||||
import org.springframework.data.gemfire.repository.sample.UserRepository;
|
||||
import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
* The QueryPostProcessorIntegrationTests class...
|
||||
*
|
||||
* @author John Blum
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration
|
||||
public class QueryPostProcessorIntegrationTests {
|
||||
|
||||
private static final AtomicLong idSequence = new AtomicLong(0L);
|
||||
|
||||
@Autowired
|
||||
private FindOrderedLimitedPeopleByFirstNameQueryPostProcessor peopleQueryPostProcessor;
|
||||
|
||||
@Autowired
|
||||
private PersonRepository personRepository;
|
||||
|
||||
@Autowired
|
||||
private RecordingQueryPostProcessor recordingQueryPostProcessor;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
private static Person newPerson(String firstName, String lastName) {
|
||||
|
||||
Person person = new Person(firstName, lastName);
|
||||
|
||||
person.id = idSequence.incrementAndGet();
|
||||
|
||||
return person;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
this.personRepository.save(newPerson("Jon", "Doe"));
|
||||
this.personRepository.save(newPerson("Cookie", "Doe"));
|
||||
this.personRepository.save(newPerson("Pie", "Doe"));
|
||||
this.personRepository.save(newPerson("Sour", "Doe"));
|
||||
this.personRepository.save(newPerson("Jack", "BeNimble"));
|
||||
this.personRepository.save(newPerson("Jack", "BeQuick"));
|
||||
this.personRepository.save(newPerson("Jack", "Black"));
|
||||
this.personRepository.save(newPerson("Jack", "JumpedOverTheCandleStick"));
|
||||
this.personRepository.save(newPerson("Jack", "Handy"));
|
||||
this.personRepository.save(newPerson("Jack", "Sparrow"));
|
||||
this.personRepository.save(newPerson("Agent", "Smith"));
|
||||
|
||||
this.userRepository.save(new User("abuser"));
|
||||
this.userRepository.save(new User("jdoe"));
|
||||
this.userRepository.save(new User("root"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryPostProcessingProcessesGeneratedOqlQueries() {
|
||||
|
||||
assertThat(this.personRepository.count()).isEqualTo(11);
|
||||
assertThat(this.userRepository.count()).isEqualTo(3);
|
||||
assertThat(this.recordingQueryPostProcessor.queries).isEmpty();
|
||||
|
||||
List<User> users = this.userRepository.findDistinctByUsernameLike("%doe");
|
||||
|
||||
assertThat(users).hasSize(1);
|
||||
assertThat(this.recordingQueryPostProcessor.queries).hasSize(1);
|
||||
assertThat(this.recordingQueryPostProcessor.queries)
|
||||
.containsExactly("SELECT DISTINCT * FROM /Users x WHERE x.username LIKE $1");
|
||||
|
||||
Collection<Person> bakingDoes = this.personRepository.findByFirstnameIn("Cookie", "Pie", "Sour");
|
||||
|
||||
assertThat(bakingDoes).hasSize(3);
|
||||
assertThat(this.recordingQueryPostProcessor.queries).hasSize(2);
|
||||
assertThat(this.recordingQueryPostProcessor.queries).containsExactly(
|
||||
"SELECT DISTINCT * FROM /Users x WHERE x.username LIKE $1",
|
||||
"SELECT * FROM /simple x WHERE x.firstname IN SET ('Cookie', 'Pie', 'Sour')"
|
||||
);
|
||||
|
||||
Collection<Person> jacks = this.personRepository.findByFirstname("Jack");
|
||||
|
||||
assertThat(jacks).hasSize(1);
|
||||
assertThat(jacks.stream().findFirst().map(Person::getName).orElse(null)).isEqualTo("Jack Sparrow");
|
||||
|
||||
assertThat(this.recordingQueryPostProcessor.queries).hasSize(3);
|
||||
assertThat(this.recordingQueryPostProcessor.queries).containsExactly(
|
||||
"SELECT DISTINCT * FROM /Users x WHERE x.username LIKE $1",
|
||||
"SELECT * FROM /simple x WHERE x.firstname IN SET ('Cookie', 'Pie', 'Sour')",
|
||||
"SELECT DISTINCT * FROM /simple x WHERE x.firstname = $1 ORDER BY lastname DESC LIMIT 1"
|
||||
);
|
||||
}
|
||||
|
||||
@ClientCacheApplication
|
||||
@SuppressWarnings("unused")
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean("simple")
|
||||
public ClientRegionFactoryBean<Object, Object> peopleRegion(GemFireCache gemfireCache) {
|
||||
|
||||
ClientRegionFactoryBean<Object, Object> clientRegion = new ClientRegionFactoryBean<>();
|
||||
|
||||
clientRegion.setCache(gemfireCache);
|
||||
clientRegion.setClose(false);
|
||||
clientRegion.setShortcut(ClientRegionShortcut.LOCAL);
|
||||
|
||||
return clientRegion;
|
||||
}
|
||||
|
||||
@Bean("Users")
|
||||
public ClientRegionFactoryBean<Object, Object> usersRegion(GemFireCache gemfireCache) {
|
||||
|
||||
ClientRegionFactoryBean<Object, Object> clientRegion = new ClientRegionFactoryBean<>();
|
||||
|
||||
clientRegion.setCache(gemfireCache);
|
||||
clientRegion.setClose(false);
|
||||
clientRegion.setShortcut(ClientRegionShortcut.LOCAL);
|
||||
|
||||
return clientRegion;
|
||||
}
|
||||
|
||||
@Bean
|
||||
GemfireMappingContext mappingContext() {
|
||||
return new GemfireMappingContext();
|
||||
}
|
||||
|
||||
@Bean
|
||||
GemfireRepositoryFactoryBean<PersonRepository, Person, Long> personRepository() {
|
||||
return new GemfireRepositoryFactoryBean<>(PersonRepository.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
GemfireRepositoryFactoryBean<UserRepository, User, String> userRepository() {
|
||||
return new GemfireRepositoryFactoryBean<>(UserRepository.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
FindOrderedLimitedPeopleByFirstNameQueryPostProcessor personQueryPostProcess() {
|
||||
return new FindOrderedLimitedPeopleByFirstNameQueryPostProcessor(1);
|
||||
}
|
||||
|
||||
@Bean
|
||||
RecordingQueryPostProcessor recordingQueryPostProcessor() {
|
||||
return new RecordingQueryPostProcessor();
|
||||
}
|
||||
}
|
||||
|
||||
static class FindOrderedLimitedPeopleByFirstNameQueryPostProcessor implements QueryPostProcessor<PersonRepository, String> {
|
||||
|
||||
private final int limit;
|
||||
|
||||
FindOrderedLimitedPeopleByFirstNameQueryPostProcessor(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
|
||||
|
||||
return "findByFirstname".equals(queryMethod.getName())
|
||||
? query.trim().replace("SELECT", "SELECT DISTINCT")
|
||||
.concat(" ORDER BY lastname DESC").concat(String.format(" LIMIT %d", this.limit))
|
||||
: query;
|
||||
}
|
||||
}
|
||||
|
||||
static class RecordingQueryPostProcessor implements QueryPostProcessor<Repository, String> {
|
||||
|
||||
List<String> queries = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
|
||||
this.queries.add(query);
|
||||
return query;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link QueryPostProcessor}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryPostProcessor
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class QueryPostProcessorUnitTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void processAfterReturnsCompositeQueryPostProcessorAndPostProcessesInOrder() {
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
String query = "SELECT * FROM /Test";
|
||||
|
||||
QueryPostProcessor<?, String> mockQueryPostProcessorOne = mock(QueryPostProcessor.class);
|
||||
QueryPostProcessor<?, String> mockQueryPostProcessorTwo = mock(QueryPostProcessor.class);
|
||||
|
||||
when(mockQueryPostProcessorOne.processAfter(any())).thenCallRealMethod();
|
||||
when(mockQueryPostProcessorOne.postProcess(any(QueryMethod.class), anyString(), any())).thenReturn(query);
|
||||
when(mockQueryPostProcessorTwo.postProcess(any(QueryMethod.class), anyString(), any())).thenReturn(query);
|
||||
|
||||
QueryPostProcessor<?, String> composite = mockQueryPostProcessorOne.processAfter(mockQueryPostProcessorTwo);
|
||||
|
||||
assertThat(composite).isNotNull();
|
||||
assertThat(composite).isNotSameAs(mockQueryPostProcessorOne);
|
||||
assertThat(composite).isNotSameAs(mockQueryPostProcessorTwo);
|
||||
assertThat(composite.postProcess(mockQueryMethod, query)).isEqualTo(query);
|
||||
|
||||
InOrder inOrder = inOrder(mockQueryPostProcessorOne, mockQueryPostProcessorTwo);
|
||||
|
||||
inOrder.verify(mockQueryPostProcessorTwo, times(1))
|
||||
.postProcess(eq(mockQueryMethod), eq(query), any());
|
||||
|
||||
inOrder.verify(mockQueryPostProcessorOne, times(1))
|
||||
.postProcess(eq(mockQueryMethod), eq(query), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processAfterReturnsThis() {
|
||||
|
||||
QueryPostProcessor<?, ?> mockQueryPostProcessor = mock(QueryPostProcessor.class);
|
||||
|
||||
when(mockQueryPostProcessor.processAfter(any())).thenCallRealMethod();
|
||||
|
||||
assertThat(mockQueryPostProcessor.processAfter(null)).isSameAs(mockQueryPostProcessor);
|
||||
}
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void processBeforeReturnsCompositeQueryPostProcessorAndPostProcessesInOrder() {
|
||||
|
||||
QueryMethod mockQueryMethod = mock(QueryMethod.class);
|
||||
|
||||
String query = "SELECT * FROM /Test";
|
||||
|
||||
QueryPostProcessor<?, String> mockQueryPostProcessorOne = mock(QueryPostProcessor.class);
|
||||
QueryPostProcessor<?, String> mockQueryPostProcessorTwo = mock(QueryPostProcessor.class);
|
||||
|
||||
when(mockQueryPostProcessorOne.processBefore(any())).thenCallRealMethod();
|
||||
when(mockQueryPostProcessorOne.postProcess(any(QueryMethod.class), anyString(), any())).thenReturn(query);
|
||||
when(mockQueryPostProcessorTwo.postProcess(any(QueryMethod.class), anyString(), any())).thenReturn(query);
|
||||
|
||||
QueryPostProcessor<?, String> composite = mockQueryPostProcessorOne.processBefore(mockQueryPostProcessorTwo);
|
||||
|
||||
assertThat(composite).isNotNull();
|
||||
assertThat(composite).isNotSameAs(mockQueryPostProcessorOne);
|
||||
assertThat(composite).isNotSameAs(mockQueryPostProcessorTwo);
|
||||
assertThat(composite.postProcess(mockQueryMethod, query)).isEqualTo(query);
|
||||
|
||||
InOrder inOrder = inOrder(mockQueryPostProcessorOne, mockQueryPostProcessorTwo);
|
||||
|
||||
inOrder.verify(mockQueryPostProcessorOne, times(1))
|
||||
.postProcess(eq(mockQueryMethod), eq(query), any());
|
||||
|
||||
inOrder.verify(mockQueryPostProcessorTwo, times(1))
|
||||
.postProcess(eq(mockQueryMethod), eq(query), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processBeforeReturnsThis() {
|
||||
|
||||
QueryPostProcessor<?, ?> mockQueryPostProcessor = mock(QueryPostProcessor.class);
|
||||
|
||||
when(mockQueryPostProcessor.processBefore(any())).thenCallRealMethod();
|
||||
|
||||
assertThat(mockQueryPostProcessor.processBefore(null)).isSameAs(mockQueryPostProcessor);
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,6 @@
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -61,61 +58,110 @@ public class QueryStringUnitTests {
|
||||
@SuppressWarnings("rawtypes")
|
||||
Region region;
|
||||
|
||||
protected Sort.Order newSortOrder(String property) {
|
||||
private Sort.Order newSortOrder(String property) {
|
||||
return newSortOrder(property, Sort.Direction.ASC);
|
||||
}
|
||||
|
||||
protected Sort.Order newSortOrder(String property, Sort.Direction direction) {
|
||||
private Sort.Order newSortOrder(String property, Sort.Direction direction) {
|
||||
return new Sort.Order(direction, property);
|
||||
}
|
||||
|
||||
protected Sort newSort(Sort.Order... orders) {
|
||||
return new Sort(orders);
|
||||
private Sort newSort(Sort.Order... orders) {
|
||||
return Sort.by(orders);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createQueryStringWithDomainType() {
|
||||
public void constructQueryStringWithDomainType() {
|
||||
assertThat(new QueryString(Person.class).toString()).isEqualTo("SELECT * FROM /Person");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createQueryStringWithDomainTypeHavingCount() {
|
||||
public void constructQueryStringWithDomainTypeAsCount() {
|
||||
assertThat(new QueryString(Person.class, true).toString()).isEqualTo("SELECT count(*) FROM /Person");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createQueryStringWithNullDomainType() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(is(equalTo("domainType must not be null")));
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructQueryStringWithNullDomainType() {
|
||||
|
||||
new QueryString((Class<?>) null);
|
||||
try {
|
||||
new QueryString((Class<?>) null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Domain type is required");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createQueryStringWithQuery() {
|
||||
public void constructQueryStringWithQuery() {
|
||||
|
||||
String query = "SELECT * FROM /Example";
|
||||
|
||||
assertThat(new QueryString(query).toString()).isEqualTo(query);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createQueryStringWithUnspecifiedQueryThrowsIllegalArgumentException() {
|
||||
assertUnspecifiedQueryThrowsIllegalArgumentException("");
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructQueryStringWithBlankQueryThrowsIllegalArgumentException() {
|
||||
assertUnspecifiedQueryThrowsIllegalArgumentException(" ");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructQueryStringWithEmptyQueryThrowsIllegalArgumentException() {
|
||||
assertUnspecifiedQueryThrowsIllegalArgumentException("");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructQueryStringWithNullQueryThrowsIllegalArgumentException() {
|
||||
assertUnspecifiedQueryThrowsIllegalArgumentException(null);
|
||||
}
|
||||
|
||||
protected void assertUnspecifiedQueryThrowsIllegalArgumentException(String query) {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(is(equalTo("An OQL Query must be specified")));
|
||||
private void assertUnspecifiedQueryThrowsIllegalArgumentException(String query) {
|
||||
|
||||
new QueryString(query);
|
||||
try {
|
||||
new QueryString(query);
|
||||
}
|
||||
catch (Exception expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Query [%s] is required", query);
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryStringOfQuery() {
|
||||
|
||||
QueryString query = QueryString.of("SELECT * FROM /Test");
|
||||
|
||||
assertThat(query).isNotNull();
|
||||
assertThat(query.toString()).isEqualTo("SELECT * FROM /Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryStringFromDomainType() {
|
||||
|
||||
QueryString query = QueryString.from(Person.class);
|
||||
|
||||
assertThat(query).isNotNull();
|
||||
assertThat(query.toString()).isEqualTo("SELECT * FROM /Person");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryStringCountingObjectsOfDomainType() {
|
||||
|
||||
QueryString query = QueryString.count(Person.class);
|
||||
|
||||
assertThat(query).isNotNull();
|
||||
assertThat(query.toString()).isEqualTo("SELECT count(*) FROM /Person");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hintPatternMatches() {
|
||||
|
||||
assertThat(matches(HINT_PATTERN, "<HINT 'ExampleIndex'>")).isTrue();
|
||||
assertThat(matches(HINT_PATTERN, "<HINT 'IdIdx'> SELECT * FROM /Example WHERE id = $1")).isTrue();
|
||||
assertThat(matches(HINT_PATTERN, "<HINT 'LastNameIdx', 'BirthDateIdx'> SELECT * FROM /Person WHERE lastName = $1 AND birthDate = $2")).isTrue();
|
||||
@@ -123,6 +169,7 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void hintPatternNoMatches() {
|
||||
|
||||
assertThat(matches(HINT_PATTERN, "HINT")).isFalse();
|
||||
assertThat(matches(HINT_PATTERN, "<HINT>")).isFalse();
|
||||
assertThat(matches(HINT_PATTERN, "<HINT ''>")).isFalse();
|
||||
@@ -136,6 +183,7 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void importPatternMatches() {
|
||||
|
||||
assertThat(matches(IMPORT_PATTERN, "IMPORT *;")).isTrue();
|
||||
assertThat(matches(IMPORT_PATTERN, "IMPORT org.example.*;")).isTrue();
|
||||
assertThat(matches(IMPORT_PATTERN, "IMPORT org.example.Type;")).isTrue();
|
||||
@@ -144,6 +192,7 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void importPatternNoMatches() {
|
||||
|
||||
assertThat(matches(IMPORT_PATTERN, "IMPORT")).isFalse();
|
||||
assertThat(matches(IMPORT_PATTERN, "IMPORT ;")).isFalse();
|
||||
assertThat(matches(IMPORT_PATTERN, "IMPORT *")).isFalse();
|
||||
@@ -153,6 +202,7 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void limitPatternMatches() {
|
||||
|
||||
assertThat(matches(LIMIT_PATTERN, "LIMIT 0")).isTrue();
|
||||
assertThat(matches(LIMIT_PATTERN, "LIMIT 1")).isTrue();
|
||||
assertThat(matches(LIMIT_PATTERN, "LIMIT 10")).isTrue();
|
||||
@@ -162,6 +212,7 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void limitPatternNoMatches() {
|
||||
|
||||
assertThat(matches(LIMIT_PATTERN, "LIMIT")).isFalse();
|
||||
assertThat(matches(LIMIT_PATTERN, "LIMIT lO")).isFalse();
|
||||
assertThat(matches(LIMIT_PATTERN, "LIMIT AF")).isFalse();
|
||||
@@ -172,6 +223,7 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void tracePatternMatches() {
|
||||
|
||||
assertThat(matches(TRACE_PATTERN, "<TRACE>")).isTrue();
|
||||
assertThat(matches(TRACE_PATTERN, "<TRACE> SELECT * FROM /Example")).isTrue();
|
||||
assertThat(matches(TRACE_PATTERN, "<TRACE>SELECT * FROM /Example")).isTrue();
|
||||
@@ -180,56 +232,78 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void tracePatternNoMatches() {
|
||||
|
||||
assertThat(matches(TRACE_PATTERN, "TRACE")).isFalse();
|
||||
assertThat(matches(TRACE_PATTERN, "TRACE SELECT * FROM /Example")).isFalse();
|
||||
assertThat(matches(TRACE_PATTERN, "<TRACE SELECT * FROM /Example>")).isFalse();
|
||||
}
|
||||
|
||||
protected boolean matches(Pattern pattern, String value) {
|
||||
private boolean matches(Pattern pattern, String value) {
|
||||
return pattern.matcher(value).find();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asDistinctQuery() {
|
||||
|
||||
QueryString query = QueryString.of("SELECT * FROM /Test");
|
||||
|
||||
assertThat(query.asDistinct().toString()).isEqualTo("SELECT DISTINCT * FROM /Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asDistinctWithDistinctQuery() {
|
||||
|
||||
QueryString query = QueryString.of("SELECT DISTINCT * FROM /Test");
|
||||
|
||||
assertThat(query.asDistinct().toString()).isEqualTo("SELECT DISTINCT * FROM /Test");
|
||||
}
|
||||
|
||||
// SGF-251
|
||||
@Test
|
||||
public void replacesDomainTypeSimpleNameWithRegionPathCorrectly() {
|
||||
QueryString query = new QueryString(Person.class);
|
||||
|
||||
when(region.getFullPath()).thenReturn("/foo/bar");
|
||||
QueryString query = QueryString.from(Person.class);
|
||||
|
||||
assertThat(query.forRegion(Person.class, region).toString()).isEqualTo("SELECT * FROM /foo/bar");
|
||||
when(this.region.getFullPath()).thenReturn("/foo/bar");
|
||||
|
||||
verify(region, times(1)).getFullPath();
|
||||
assertThat(query.toString()).isEqualTo("SELECT * FROM /Person");
|
||||
assertThat(query.fromRegion(Person.class, this.region).toString()).isEqualTo("SELECT * FROM /foo/bar");
|
||||
|
||||
verify(this.region, times(1)).getFullPath();
|
||||
}
|
||||
|
||||
// SGF-156, SGF-251
|
||||
@Test
|
||||
public void replacesFromClauseWithRegionPathCorrectly() {
|
||||
QueryString query = new QueryString("SELECT * FROM /Persons p WHERE p.lastname = $1");
|
||||
|
||||
when(region.getFullPath()).thenReturn("/People");
|
||||
QueryString query = QueryString.of("SELECT * FROM /Persons p WHERE p.lastname = $1");
|
||||
|
||||
assertThat(query.forRegion(Person.class, region).toString())
|
||||
when(this.region.getFullPath()).thenReturn("/People");
|
||||
|
||||
assertThat(query.fromRegion(Person.class, this.region).toString())
|
||||
.isEqualTo("SELECT * FROM /People p WHERE p.lastname = $1");
|
||||
|
||||
verify(region, times(1)).getFullPath();
|
||||
verify(this.region, times(1)).getFullPath();
|
||||
}
|
||||
|
||||
// SGF-252
|
||||
@Test
|
||||
public void replacesFullyQualifiedSubRegionPathWithRegionPathCorrectly() {
|
||||
QueryString query = new QueryString("SELECT * FROM //Local/Root/Users u WHERE u.username = $1");
|
||||
|
||||
when(region.getFullPath()).thenReturn("/Remote/Root/Users");
|
||||
QueryString query = QueryString.of("SELECT * FROM //Local/Root/Users u WHERE u.username = $1");
|
||||
|
||||
assertThat(query.forRegion(RootUser.class, region).toString())
|
||||
when(this.region.getFullPath()).thenReturn("/Remote/Root/Users");
|
||||
|
||||
assertThat(query.fromRegion(RootUser.class, this.region).toString())
|
||||
.isEqualTo("SELECT * FROM /Remote/Root/Users u WHERE u.username = $1");
|
||||
|
||||
verify(region, times(1)).getFullPath();
|
||||
verify(this.region, times(1)).getFullPath();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindsInValuesCorrectly() {
|
||||
QueryString query = new QueryString("SELECT * FROM /Collection WHERE elements IN SET $1");
|
||||
|
||||
QueryString query = QueryString.of("SELECT * FROM /Collection WHERE elements IN SET $1");
|
||||
|
||||
assertThat(query.bindIn(Arrays.asList(1, 2, 3)).toString())
|
||||
.isEqualTo("SELECT * FROM /Collection WHERE elements IN SET ('1', '2', '3')");
|
||||
@@ -237,22 +311,26 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void detectsInParameterIndexesCorrectly() {
|
||||
QueryString query = new QueryString("SELECT * FROM /Example WHERE values IN SET $1 OR IN SET $2");
|
||||
|
||||
QueryString query = QueryString.of("SELECT * FROM /Example WHERE values IN SET $1 OR IN SET $2");
|
||||
|
||||
assertThat(query.getInParameterIndexes()).isEqualTo(Arrays.asList(1, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addsNoOrderByClauseCorrectly() {
|
||||
QueryString query = new QueryString("SELECT * FROM /People p").orderBy(null);
|
||||
|
||||
QueryString query = QueryString.of("SELECT * FROM /People p").orderBy(null);
|
||||
|
||||
assertThat(query.toString()).isEqualTo("SELECT * FROM /People p");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addsOrderByClauseCorrectly() {
|
||||
QueryString query = new QueryString("SELECT * FROM /People p WHERE p.lastName = $1")
|
||||
.orderBy(newSort(newSortOrder("lastName", Sort.Direction.DESC), newSortOrder("firstName")));
|
||||
|
||||
QueryString query = QueryString.of("SELECT * FROM /People p WHERE p.lastName = $1")
|
||||
.orderBy(newSort(newSortOrder("lastName", Sort.Direction.DESC),
|
||||
newSortOrder("firstName")));
|
||||
|
||||
assertThat(query.toString())
|
||||
.isEqualTo("SELECT DISTINCT * FROM /People p WHERE p.lastName = $1 ORDER BY lastName DESC, firstName ASC");
|
||||
@@ -260,7 +338,8 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void addsSingleOrderByClauseCorrectly() {
|
||||
QueryString query = new QueryString("SELECT DISTINCT p.lastName FROM /People p WHERE p.firstName = $1")
|
||||
|
||||
QueryString query = QueryString.of("SELECT DISTINCT p.lastName FROM /People p WHERE p.firstName = $1")
|
||||
.orderBy(newSort(newSortOrder("lastName")));
|
||||
|
||||
assertThat(query.toString())
|
||||
@@ -269,16 +348,18 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void withHints() {
|
||||
assertThat(new QueryString("SELECT * FROM /Example").withHints("IdIdx").toString())
|
||||
|
||||
assertThat(QueryString.of("SELECT * FROM /Example").withHints("IdIdx").toString())
|
||||
.isEqualTo("<HINT 'IdIdx'> SELECT * FROM /Example");
|
||||
|
||||
assertThat(new QueryString("SELECT * FROM /Example").withHints("IdIdx", "SpatialIdx", "TxDateIdx").toString())
|
||||
assertThat(QueryString.of("SELECT * FROM /Example").withHints("IdIdx", "SpatialIdx", "TxDateIdx").toString())
|
||||
.isEqualTo("<HINT 'IdIdx', 'SpatialIdx', 'TxDateIdx'> SELECT * FROM /Example");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutHints() {
|
||||
QueryString expectedQueryString = new QueryString("SELECT * FROM /Example");
|
||||
|
||||
QueryString expectedQueryString = QueryString.of("SELECT * FROM /Example");
|
||||
|
||||
assertThat(expectedQueryString.withHints()).isSameAs(expectedQueryString);
|
||||
assertThat(expectedQueryString.withHints((String[]) null)).isSameAs(expectedQueryString);
|
||||
@@ -286,13 +367,14 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void withImport() {
|
||||
assertThat(new QueryString("SELECT * FROM /People").withImport("org.example.app.domain.Person").toString())
|
||||
assertThat(QueryString.of("SELECT * FROM /People").withImport("org.example.app.domain.Person").toString())
|
||||
.isEqualTo("IMPORT org.example.app.domain.Person; SELECT * FROM /People");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutImport() {
|
||||
QueryString expectedQueryString = new QueryString("SELECT * FROM /Example");
|
||||
|
||||
QueryString expectedQueryString = QueryString.of("SELECT * FROM /Example");
|
||||
|
||||
assertThat(expectedQueryString.withImport(null)).isSameAs(expectedQueryString);
|
||||
assertThat(expectedQueryString.withImport("")).isSameAs(expectedQueryString);
|
||||
@@ -301,40 +383,47 @@ public class QueryStringUnitTests {
|
||||
|
||||
@Test
|
||||
public void withLimit() {
|
||||
assertThat(new QueryString("SELECT * FROM /Example").withLimit(10).toString())
|
||||
|
||||
assertThat(QueryString.of("SELECT * FROM /Example").withLimit(10).toString())
|
||||
.isEqualTo("SELECT * FROM /Example LIMIT 10");
|
||||
|
||||
assertThat(new QueryString("SELECT * FROM /Example").withLimit(0).toString())
|
||||
assertThat(QueryString.of("SELECT * FROM /Example").withLimit(0).toString())
|
||||
.isEqualTo("SELECT * FROM /Example LIMIT 0");
|
||||
|
||||
assertThat(new QueryString("SELECT * FROM /Example").withLimit(-5).toString())
|
||||
assertThat(QueryString.of("SELECT * FROM /Example").withLimit(-5).toString())
|
||||
.isEqualTo("SELECT * FROM /Example LIMIT -5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutLimit() {
|
||||
QueryString expectedQueryString = new QueryString("SELECT * FROM /Example");
|
||||
|
||||
QueryString expectedQueryString = QueryString.of("SELECT * FROM /Example");
|
||||
|
||||
assertThat(expectedQueryString.withLimit(null)).isSameAs(expectedQueryString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withTrace() {
|
||||
assertThat(new QueryString("SELECT * FROM /Example").withTrace().toString())
|
||||
assertThat(QueryString.of("SELECT * FROM /Example").withTrace().toString())
|
||||
.isEqualTo("<TRACE> SELECT * FROM /Example");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withHintAndTrace() {
|
||||
assertThat(new QueryString("SELECT * FROM /Example").withHints("IdIdx").withTrace().toString())
|
||||
assertThat(QueryString.of("SELECT * FROM /Example").withHints("IdIdx").withTrace().toString())
|
||||
.isEqualTo("<TRACE> <HINT 'IdIdx'> SELECT * FROM /Example");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withHintImportLimitAndTrace() {
|
||||
QueryString query = new QueryString("SELECT * FROM /Example");
|
||||
|
||||
assertThat(query.withImport("org.example.domain.Type").withHints("IdIdx", "NameIdx").withLimit(20).withTrace().toString())
|
||||
QueryString query = QueryString.of("SELECT * FROM /Example")
|
||||
.withImport("org.example.domain.Type")
|
||||
.withHints("IdIdx", "NameIdx")
|
||||
.withLimit(20)
|
||||
.withTrace();
|
||||
|
||||
assertThat(query.toString())
|
||||
.isEqualTo("<TRACE> <HINT 'IdIdx', 'NameIdx'> IMPORT org.example.domain.Type; SELECT * FROM /Example LIMIT 20");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,7 @@ package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.sameInstance;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@@ -59,7 +57,9 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
|
||||
@Test
|
||||
public void testToCollectionWithSelectResults() {
|
||||
|
||||
SelectResults mockSelectResults = mock(SelectResults.class, "testToCollectionWithSelectResults.SelectResults");
|
||||
|
||||
List<String> expectedList = Arrays.asList("one", "two", "three");
|
||||
|
||||
when(mockSelectResults.asList()).thenReturn(expectedList);
|
||||
@@ -71,7 +71,9 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
|
||||
@Test
|
||||
public void testToCollectionWithResultsBag() {
|
||||
|
||||
ResultsBag mockResultsBag = mock(ResultsBag.class, "testToCollectionWithResultsBag.ResultsBag");
|
||||
|
||||
List<String> expectedList = Arrays.asList("a", "b", "c");
|
||||
|
||||
when(mockResultsBag.asList()).thenReturn(expectedList);
|
||||
@@ -83,7 +85,9 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
|
||||
@Test
|
||||
public void testToCollectionWithCollection() {
|
||||
|
||||
List<String> expectedList = Arrays.asList("x", "y", "z");
|
||||
|
||||
Collection<?> actualList = repositoryQuery.toCollection(expectedList);
|
||||
|
||||
assertSame(expectedList, actualList);
|
||||
@@ -91,7 +95,9 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
|
||||
@Test
|
||||
public void testToCollectionWithArray() {
|
||||
|
||||
Object[] array = { 1, 2, 3 };
|
||||
|
||||
Collection<?> list = repositoryQuery.toCollection(array);
|
||||
|
||||
assertNotNull(list);
|
||||
@@ -103,6 +109,7 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
|
||||
@Test
|
||||
public void testToCollectionWithSingleObject() {
|
||||
|
||||
Collection<?> list = repositoryQuery.toCollection("test");
|
||||
|
||||
assertTrue(list instanceof List);
|
||||
@@ -113,6 +120,7 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
|
||||
@Test
|
||||
public void testToCollectionWithNull() {
|
||||
|
||||
Collection<?> list = repositoryQuery.toCollection(null);
|
||||
|
||||
assertNotNull(list);
|
||||
@@ -121,6 +129,7 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
|
||||
@Test
|
||||
public void applyAllQueryAnnotationExtensions() {
|
||||
|
||||
GemfireQueryMethod mockQueryMethod = mock(GemfireQueryMethod.class, "MockGemfireQueryMethod");
|
||||
|
||||
when(mockQueryMethod.hasHint()).thenReturn(true);
|
||||
@@ -131,18 +140,18 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
when(mockQueryMethod.getLimit()).thenReturn(10);
|
||||
when(mockQueryMethod.hasTrace()).thenReturn(true);
|
||||
|
||||
QueryString queryString = new QueryString("SELECT * FROM /Example");
|
||||
QueryString queryString = QueryString.of("SELECT * FROM /Example");
|
||||
|
||||
assertThat(queryString.toString(), is(equalTo("SELECT * FROM /Example")));
|
||||
|
||||
StringBasedGemfireRepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery();
|
||||
|
||||
QueryString actualQueryString = repositoryQuery.applyQueryAnnotationExtensions(mockQueryMethod, queryString);
|
||||
String postProcessedQueryString = repositoryQuery.getQueryPostProcessor()
|
||||
.postProcess(mockQueryMethod, queryString.toString());
|
||||
|
||||
assertThat(actualQueryString, is(notNullValue()));
|
||||
assertThat(actualQueryString, is(not(sameInstance(queryString))));
|
||||
assertThat(actualQueryString.toString(), is(equalTo(
|
||||
"<TRACE> <HINT 'IdIdx', 'NameIdx'> IMPORT org.example.domain.Type; SELECT * FROM /Example LIMIT 10")));
|
||||
assertThat(postProcessedQueryString, is(notNullValue()));
|
||||
assertThat(postProcessedQueryString,
|
||||
is(equalTo("<TRACE> <HINT 'IdIdx', 'NameIdx'> IMPORT org.example.domain.Type; SELECT * FROM /Example LIMIT 10")));
|
||||
|
||||
verify(mockQueryMethod, times(1)).hasHint();
|
||||
verify(mockQueryMethod, times(1)).getHints();
|
||||
@@ -155,6 +164,7 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
|
||||
@Test
|
||||
public void applyHintLimitAndTraceQueryAnnotationExtensionsWithExistingHintAndLimit() {
|
||||
|
||||
GemfireQueryMethod mockQueryMethod = mock(GemfireQueryMethod.class, "MockGemfireQueryMethod");
|
||||
|
||||
when(mockQueryMethod.hasHint()).thenReturn(true);
|
||||
@@ -170,12 +180,11 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
|
||||
StringBasedGemfireRepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery();
|
||||
|
||||
QueryString actualQueryString = repositoryQuery.applyQueryAnnotationExtensions(mockQueryMethod, queryString);
|
||||
String postProcessedQueryString = repositoryQuery.getQueryPostProcessor().postProcess(mockQueryMethod, queryString.toString());
|
||||
|
||||
assertThat(actualQueryString, is(notNullValue()));
|
||||
assertThat(actualQueryString, is(not(sameInstance(queryString))));
|
||||
assertThat(actualQueryString.toString(), is(equalTo(
|
||||
"<TRACE> <HINT 'LastNameIdx'> SELECT * FROM /Example LIMIT 25")));
|
||||
assertThat(postProcessedQueryString, is(notNullValue()));
|
||||
assertThat(postProcessedQueryString,
|
||||
is(equalTo("<TRACE> <HINT 'LastNameIdx'> SELECT * FROM /Example LIMIT 25")));
|
||||
|
||||
verify(mockQueryMethod, times(1)).hasHint();
|
||||
verify(mockQueryMethod, never()).getHints();
|
||||
@@ -188,6 +197,7 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
|
||||
@Test
|
||||
public void applyImportAndTraceQueryAnnotationExtensionsWithExistingTrace() {
|
||||
|
||||
GemfireQueryMethod mockQueryMethod = mock(GemfireQueryMethod.class, "MockGemfireQueryMethod");
|
||||
|
||||
when(mockQueryMethod.hasHint()).thenReturn(false);
|
||||
@@ -202,12 +212,11 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
|
||||
StringBasedGemfireRepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery();
|
||||
|
||||
QueryString actualQueryString = repositoryQuery.applyQueryAnnotationExtensions(mockQueryMethod, queryString);
|
||||
String postProcessedQueryString = repositoryQuery.getQueryPostProcessor().postProcess(mockQueryMethod, queryString.toString());
|
||||
|
||||
assertThat(actualQueryString, is(notNullValue()));
|
||||
assertThat(actualQueryString, is(not(sameInstance(queryString))));
|
||||
assertThat(actualQueryString.toString(), is(equalTo(
|
||||
"IMPORT org.example.domain.Type; <TRACE> SELECT * FROM /Example")));
|
||||
assertThat(postProcessedQueryString, is(notNullValue()));
|
||||
assertThat(postProcessedQueryString,
|
||||
is(equalTo("IMPORT org.example.domain.Type; <TRACE> SELECT * FROM /Example")));
|
||||
|
||||
verify(mockQueryMethod, times(1)).hasHint();
|
||||
verify(mockQueryMethod, never()).getHints();
|
||||
@@ -217,5 +226,4 @@ public class StringBasedGemfireRepositoryQueryTest {
|
||||
verify(mockQueryMethod, never()).getLimit();
|
||||
verify(mockQueryMethod, times(1)).hasTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import org.springframework.data.gemfire.mapping.annotation.Region;
|
||||
import org.springframework.data.gemfire.mapping.annotation.ReplicateRegion;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* The Customer class is a class abstraction modeling a Customer.
|
||||
*
|
||||
@@ -29,6 +31,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* @see Region
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Data
|
||||
@ReplicateRegion("Customers")
|
||||
@SuppressWarnings("unused")
|
||||
public class Customer {
|
||||
@@ -42,39 +45,15 @@ public class Customer {
|
||||
public Customer() {
|
||||
}
|
||||
|
||||
public Customer(final Long id) {
|
||||
public Customer(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Customer(final String firstName, final String lastName) {
|
||||
public Customer(String firstName, String lastName) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(final Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(final String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(final String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return String.format("%1$s %2$s", getFirstName(), getLastName());
|
||||
}
|
||||
@@ -85,7 +64,8 @@ public class Customer {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -100,7 +80,7 @@ public class Customer {
|
||||
&& ObjectUtils.nullSafeEquals(this.getLastName(), that.getLastName());
|
||||
}
|
||||
|
||||
protected static int hashCodeIgnoreNull(final Object obj) {
|
||||
protected static int hashCodeIgnoreNull(Object obj) {
|
||||
return (obj != null ? obj.hashCode() : 0);
|
||||
}
|
||||
|
||||
@@ -115,7 +95,6 @@ public class Customer {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%1$s %2$s", getFirstName(), getLastName());
|
||||
return getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.sample;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
/**
|
||||
* The IncompatibleRegionKeyEntityIdAnimalRepositoryTest class is a test suite of test cases testing the functionality
|
||||
* behind PR #55 involving persisting application domain object/entities to multiple Regions in GemFire's Cache.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @since 1.4.0
|
||||
* @link https://github.com/spring-projects/spring-data-gemfire/pull/55
|
||||
*/
|
||||
public class IncompatibleRegionKeyEntityIdAnimalRepositoryTest {
|
||||
|
||||
private static final String APPLICATION_CONTEXT_CONFIG_LOCATION = String.format("%1$s%2$s%1$s%3$s",
|
||||
File.separator, AnimalRepositoryTest.class.getPackage().getName().replace('.', File.separatorChar),
|
||||
String.format("%s-context.xml", IncompatibleRegionKeyEntityIdAnimalRepositoryTest.class.getSimpleName()));
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void storeAnimalHavingLongIdInRabbitsRegionWithStringKey() {
|
||||
try {
|
||||
ConfigurableApplicationContext applicationContext =
|
||||
new ClassPathXmlApplicationContext(APPLICATION_CONTEXT_CONFIG_LOCATION);
|
||||
|
||||
applicationContext.getBean(RabbitRepository.class);
|
||||
}
|
||||
// NOTE the ClassCastException thrown from GemFire is unexpected; this is not correct and the identifying type
|
||||
// mismatch should be caught and handled by GemfireRepositoryFactory.getTemplate(..) method on line 129
|
||||
// (appropriately throwing an IllegalArgumentException) after satisfying the condition on line 128,
|
||||
// which always occurs with the @Region annotation set on the domain class/entity!
|
||||
catch (ClassCastException unexpected) {
|
||||
//unexpected.printStackTrace(System.err);
|
||||
//assertTrue(unexpected.getMessage().contains("key ( java.lang.Long ) does not satisfy keyConstraint ( java.lang.String )"));
|
||||
fail(unexpected.getMessage());
|
||||
}
|
||||
catch (BeanCreationException expected) {
|
||||
//expected.printStackTrace(System.err);
|
||||
assertTrue(expected.getCause() instanceof IllegalArgumentException);
|
||||
assertEquals(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]",
|
||||
String.class.getName(), Long.class.getName()), expected.getCause().getMessage());
|
||||
|
||||
throw (IllegalArgumentException) expected.getCause();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,20 +33,20 @@ import org.springframework.util.ObjectUtils;
|
||||
public class Plant {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
@@ -55,7 +55,8 @@ public class Plant {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -71,9 +72,12 @@ public class Plant {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int hashValue = 17;
|
||||
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getId());
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getName());
|
||||
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
@@ -81,5 +85,4 @@ public class Plant {
|
||||
public String toString() {
|
||||
return String.format("{ @type = %1$s, id = %2$s, name = %3$s }", getClass().getSimpleName(), getId(), getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.sample;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
/**
|
||||
* The PlantRepositoryTest class is a test suite of test cases testing the functionality behind PR #55 involving
|
||||
* persisting application domain object/entities to multiple Regions in GemFire's Cache.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.springframework.context.ConfigurableApplicationContext
|
||||
* @since 1.4.0
|
||||
* @link https://github.com/spring-projects/spring-data-gemfire/pull/55
|
||||
*/
|
||||
public class PlantRepositoryTest {
|
||||
|
||||
private static final String APPLICATION_CONTEXT_CONFIG_LOCATION = String.format("%1$s%2$s%1$s%3$s",
|
||||
File.separator, PlantRepositoryTest.class.getPackage().getName().replace('.', File.separatorChar),
|
||||
"PlantRepositoryTest-context.xml");
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void storePlantHavingStringIdInPlantsRegionWithLongKey() {
|
||||
try {
|
||||
ConfigurableApplicationContext context =
|
||||
new ClassPathXmlApplicationContext(APPLICATION_CONTEXT_CONFIG_LOCATION);
|
||||
|
||||
context.getBean(PlantRepository.class);
|
||||
}
|
||||
// NOTE technically, the IllegalArgumentException for incompatible Region 'Key' and Entity ID is thrown
|
||||
// when the Spring container starts up and the Repository beans are created.
|
||||
catch (BeanCreationException expected) {
|
||||
//expected.printStackTrace(System.err);
|
||||
assertTrue(expected.getCause() instanceof IllegalArgumentException);
|
||||
assertEquals(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]",
|
||||
Long.class.getName(), String.class.getName()), expected.getCause().getMessage());
|
||||
|
||||
throw (IllegalArgumentException) expected.getCause();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,8 @@ public class User implements Comparable<User> {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -113,9 +114,12 @@ public class User implements Comparable<User> {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int hashValue = 17;
|
||||
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getEmail());
|
||||
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getUsername());
|
||||
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
@@ -123,5 +127,4 @@ public class User implements Comparable<User> {
|
||||
public String toString() {
|
||||
return getUsername();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,18 +16,16 @@
|
||||
|
||||
package org.springframework.data.gemfire.repository.support;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newUnsupportedOperationException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
@@ -36,285 +34,509 @@ import java.util.Collections;
|
||||
import org.apache.geode.cache.Region;
|
||||
import org.apache.geode.cache.RegionAttributes;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.gemfire.mapping.GemfireMappingContext;
|
||||
import org.springframework.data.gemfire.mapping.GemfirePersistentEntity;
|
||||
import org.springframework.data.gemfire.repository.GemfireRepository;
|
||||
import org.springframework.data.gemfire.repository.sample.Person;
|
||||
import org.springframework.data.gemfire.util.RegionUtils;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GemfireRepositoryFactory}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.data.gemfire.repository.support.GemfireRepositoryFactory
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@SuppressWarnings("unused")
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class GemfireRepositoryFactoryUnitTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
private GemfireMappingContext gemfireMappingContext = new GemfireMappingContext();
|
||||
private GemfireMappingContext mappingContext;
|
||||
|
||||
@Mock
|
||||
private Region<Object, Object> mockRegion;
|
||||
private Region mockRegion;
|
||||
|
||||
@Mock
|
||||
@SuppressWarnings("rawtypes")
|
||||
private RegionAttributes mockRegionAttributes;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <K, V> Region<K, V> configureMockRegion(Region<K, V> mockRegion, String name,
|
||||
Class<K> keyType, Class<V> valueType) {
|
||||
|
||||
when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes);
|
||||
when(mockRegion.getFullPath()).thenReturn(String.format("%1$s%2$s", Region.SEPARATOR, name));
|
||||
when(mockRegion.getName()).thenReturn(name);
|
||||
when(mockRegionAttributes.getKeyConstraint()).thenReturn(keyType);
|
||||
when(mockRegionAttributes.getValueConstraint()).thenReturn(valueType);
|
||||
|
||||
return mockRegion;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <K, V> Region<K, V> mockRegion(String name, Class<K> keyType, Class<V> valueType) {
|
||||
return configureMockRegion(mock(Region.class, name), name, keyType, valueType);
|
||||
}
|
||||
|
||||
protected RepositoryMetadata mockRepositoryMetadata(final Class<?> domainType, final Class<?> idType,
|
||||
final Class<?> repositoryInterface) {
|
||||
|
||||
RepositoryMetadata mockRepositoryMetadata = mock(RepositoryMetadata.class);
|
||||
|
||||
when(mockRepositoryMetadata.getDomainType()).then(new Answer<Class<?>>() {
|
||||
@Override public Class<?> answer(InvocationOnMock invocation) throws Throwable {
|
||||
return domainType;
|
||||
}
|
||||
});
|
||||
|
||||
when(mockRepositoryMetadata.getIdType()).then(new Answer<Class<?>>() {
|
||||
@Override public Class<?> answer(InvocationOnMock invocation) throws Throwable {
|
||||
return idType;
|
||||
}
|
||||
});
|
||||
|
||||
when(mockRepositoryMetadata.getRepositoryInterface()).then(new Answer<Class<?>>() {
|
||||
@Override public Class<?> answer(InvocationOnMock invocation) throws Throwable {
|
||||
return repositoryInterface;
|
||||
}
|
||||
});
|
||||
|
||||
return mockRepositoryMetadata;
|
||||
}
|
||||
|
||||
@Before
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setup() {
|
||||
configureMockRegion(mockRegion, "simple", Object.class, Object.class);
|
||||
this.mappingContext = new GemfireMappingContext();
|
||||
configureMockRegion(this.mockRegion, "simple", Object.class, Object.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructGemfireRepositoryFactoryWithNullMappingContextThrowsIllegalArgumentException() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage("MappingContext must not be null");
|
||||
|
||||
new GemfireRepositoryFactory(Collections.<Region<?, ?>>emptyList(), null);
|
||||
@SuppressWarnings("unchecked")
|
||||
private <K, V> Region<K, V> mockRegion(String name, Class<K> keyType, Class<V> valueType) {
|
||||
return configureMockRegion(this.mockRegion, name, keyType, valueType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructGemfireRepositoryFactoryWithNullRegionsThrowsIllegalArgumentException() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage("Regions must not be null");
|
||||
@SuppressWarnings("unchecked")
|
||||
private <K, V> Region<K, V> configureMockRegion(Region<K, V> mockRegion, String name,
|
||||
Class<K> keyType, Class<V> valueType) {
|
||||
|
||||
new GemfireRepositoryFactory(null, gemfireMappingContext);
|
||||
when(mockRegion.getAttributes()).thenReturn(this.mockRegionAttributes);
|
||||
when(mockRegion.getFullPath()).thenReturn(RegionUtils.toRegionPath(name));
|
||||
when(mockRegion.getName()).thenReturn(name);
|
||||
when(this.mockRegionAttributes.getKeyConstraint()).thenReturn(keyType);
|
||||
when(this.mockRegionAttributes.getValueConstraint()).thenReturn(valueType);
|
||||
|
||||
return mockRegion;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRepositoryRegionNameFromRepositoryInterfaceWithRegionAnnotation() {
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory(
|
||||
Collections.<Region<?, ?>>emptyList(), gemfireMappingContext);
|
||||
private RepositoryMetadata mockRepositoryMetadata(Class<?> domainType, Class<?> idType,
|
||||
Class<?> repositoryInterface) {
|
||||
|
||||
assertThat(gemfireRepositoryFactory.getRepositoryRegionName(PersonRepository.class),
|
||||
is(equalTo("People")));
|
||||
}
|
||||
RepositoryMetadata mockRepositoryMetadata = mock(RepositoryMetadata.class);
|
||||
|
||||
@Test
|
||||
public void getRepositoryRegionNameFromRepositoryInterfaceWithoutRegionAnnotation() {
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory(
|
||||
Collections.<Region<?, ?>>emptyList(), gemfireMappingContext);
|
||||
when(mockRepositoryMetadata.getDomainType()).thenAnswer(invocation -> domainType);
|
||||
when(mockRepositoryMetadata.getIdType()).thenAnswer(invocation -> idType);
|
||||
when(mockRepositoryMetadata.getRepositoryInterface()).thenAnswer(invocation -> repositoryInterface);
|
||||
|
||||
assertThat(gemfireRepositoryFactory.getRepositoryRegionName(SampleCustomGemfireRepository.class),
|
||||
is(nullValue(String.class)));
|
||||
return mockRepositoryMetadata;
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void getTemplateReturnsGemfireTemplateForPeopleRegion() {
|
||||
RepositoryMetadata mockRepositoryMetadata = mockRepositoryMetadata(Person.class, Long.class,
|
||||
PersonRepository.class);
|
||||
public void constructGemfireRepositoryFactorySuccessfully() {
|
||||
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.singletonList(this.mockRegion), this.mappingContext);
|
||||
|
||||
assertThat(repositoryFactory).isNotNull();
|
||||
assertThat(repositoryFactory.getMappingContext()).isEqualTo(this.mappingContext);
|
||||
assertThat(repositoryFactory.getRegions()).contains(this.mockRegion);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructGemfireRepositoryFactoryWithNullMappingContextThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
new GemfireRepositoryFactory(Collections.emptyList(), null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("MappingContext is required");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructGemfireRepositoryFactoryWithNullRegionsThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
new GemfireRepositoryFactory(null, this.mappingContext);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Regions are required");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEntityRegionNameFromEntityRegionName() {
|
||||
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.emptyList(), this.mappingContext);
|
||||
|
||||
GemfirePersistentEntity<?> mockPersistentEntity = mock(GemfirePersistentEntity.class);
|
||||
|
||||
when(mockPersistentEntity.getRegionName()).thenReturn("MockRegionName");
|
||||
|
||||
RepositoryMetadata mockRepositoryMetadata = mock(RepositoryMetadata.class);
|
||||
|
||||
assertThat(repositoryFactory.getEntityRegionName(mockRepositoryMetadata, mockPersistentEntity))
|
||||
.isEqualTo("MockRegionName");
|
||||
|
||||
verify(mockPersistentEntity, times(1)).getRegionName();
|
||||
verify(mockPersistentEntity, never()).getType();
|
||||
verifyZeroInteractions(mockRepositoryMetadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEntityRegionNameFromEntityDomainType() {
|
||||
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.emptyList(), this.mappingContext);
|
||||
|
||||
GemfirePersistentEntity<?> mockPersistentEntity = mock(GemfirePersistentEntity.class);
|
||||
|
||||
RepositoryMetadata mockRepositoryMetadata = mock(RepositoryMetadata.class);
|
||||
|
||||
when(mockPersistentEntity.getRegionName()).thenReturn(" ");
|
||||
when(mockPersistentEntity.getType()).thenAnswer(invocation -> Person.class);
|
||||
|
||||
assertThat(repositoryFactory.getEntityRegionName(mockRepositoryMetadata, mockPersistentEntity))
|
||||
.isEqualTo(Person.class.getSimpleName());
|
||||
|
||||
verify(mockPersistentEntity, times(1)).getRegionName();
|
||||
verify(mockPersistentEntity, times(1)).getType();
|
||||
verifyZeroInteractions(mockRepositoryMetadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEntityRegionNameFromRepositoryMetadata() {
|
||||
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.emptyList(), this.mappingContext);
|
||||
|
||||
GemfirePersistentEntity<?> mockPersistentEntity = mock(GemfirePersistentEntity.class);
|
||||
|
||||
RepositoryMetadata mockRepositoryMetadata = mock(RepositoryMetadata.class);
|
||||
|
||||
when(mockPersistentEntity.getRegionName()).thenReturn(" ");
|
||||
when(mockPersistentEntity.getType()).thenReturn(null);
|
||||
when(mockRepositoryMetadata.getDomainType()).thenAnswer(invocation -> Person.class);
|
||||
|
||||
assertThat(repositoryFactory.getEntityRegionName(mockRepositoryMetadata, mockPersistentEntity))
|
||||
.isEqualTo(Person.class.getSimpleName());
|
||||
|
||||
verify(mockPersistentEntity, times(1)).getRegionName();
|
||||
verify(mockPersistentEntity, times(1)).getType();
|
||||
verify(mockRepositoryMetadata, times(1)).getDomainType();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEntityRegionNameFromRepositoryMetadataWhenEntityIsNull() {
|
||||
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.emptyList(), this.mappingContext);
|
||||
|
||||
RepositoryMetadata mockRepositoryMetadata = mock(RepositoryMetadata.class);
|
||||
|
||||
when(mockRepositoryMetadata.getDomainType()).thenAnswer(invocation -> Person.class);
|
||||
|
||||
assertThat(repositoryFactory.getEntityRegionName(mockRepositoryMetadata, null))
|
||||
.isEqualTo(Person.class.getSimpleName());
|
||||
|
||||
verify(mockRepositoryMetadata, times(1)).getDomainType();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRepositoryRegionNameFromRegionAnnotatedRepositoryInterface() {
|
||||
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.emptyList(), this.mappingContext);
|
||||
|
||||
RepositoryMetadata mockRepositoryMetadata = mock(RepositoryMetadata.class);
|
||||
|
||||
when(mockRepositoryMetadata.getRepositoryInterface()).thenAnswer(invocation -> PeopleRepository.class);
|
||||
|
||||
assertThat(gemfireRepositoryFactory.getRepositoryRegionName(mockRepositoryMetadata).orElse(null))
|
||||
.isEqualTo("People");
|
||||
|
||||
verify(mockRepositoryMetadata, times(1)).getRepositoryInterface();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRepositoryRegionNameFromRegionAnnotatedRepositoryInterfaceHavingNoValue() {
|
||||
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.emptyList(), this.mappingContext);
|
||||
|
||||
RepositoryMetadata mockRepositoryMetadata = mock(RepositoryMetadata.class);
|
||||
|
||||
when(mockRepositoryMetadata.getRepositoryInterface()).thenAnswer(invocation -> NonQualifiedRegionAnnotatedRepository.class);
|
||||
|
||||
assertThat(gemfireRepositoryFactory.getRepositoryRegionName(mockRepositoryMetadata).isPresent()).isFalse();
|
||||
|
||||
verify(mockRepositoryMetadata, times(1)).getRepositoryInterface();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRepositoryRegionNameFromNonRegionAnnotatedRepositoryInterface() {
|
||||
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.emptyList(), this.mappingContext);
|
||||
|
||||
RepositoryMetadata mockRepositoryMetadata = mock(RepositoryMetadata.class);
|
||||
|
||||
when(mockRepositoryMetadata.getRepositoryInterface())
|
||||
.thenAnswer(invocation -> TestGemfireRepository.class);
|
||||
|
||||
assertThat(gemfireRepositoryFactory.getRepositoryRegionName(mockRepositoryMetadata).isPresent()).isFalse();
|
||||
|
||||
verify(mockRepositoryMetadata, times(1)).getRepositoryInterface();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveRegionWithRepositoryMetadataAndRegionNamePath() {
|
||||
|
||||
RepositoryMetadata mockRepositoryMetadata =
|
||||
mockRepositoryMetadata(Person.class, Long.class, PeopleRepository.class);
|
||||
|
||||
Region<?, ?> mockRegionOne = mockRegion("RegionOne", Person.class, Long.class);
|
||||
Region<?, ?> mockRegionTwo = mockRegion("RegionTwo", Object.class, Long.class);
|
||||
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(Arrays.asList(mockRegionOne, mockRegionTwo), this.mappingContext);
|
||||
|
||||
assertThat(repositoryFactory.resolveRegion(mockRepositoryMetadata, mockRegionTwo.getName()))
|
||||
.isEqualTo(mockRegionTwo);
|
||||
|
||||
assertThat(repositoryFactory.resolveRegion(mockRepositoryMetadata, mockRegionOne.getFullPath()))
|
||||
.isEqualTo(mockRegionOne);
|
||||
|
||||
verifyZeroInteractions(mockRepositoryMetadata);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void resolveRegionWithRepositoryMetadataAndNonExistingRegionNamePath() {
|
||||
|
||||
try {
|
||||
RepositoryMetadata mockRepositoryMetadata =
|
||||
mockRepositoryMetadata(Person.class, Long.class, PeopleRepository.class);
|
||||
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.emptyList(), this.mappingContext);
|
||||
|
||||
repositoryFactory.resolveRegion(mockRepositoryMetadata, "Test");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage(GemfireRepositoryFactory.REGION_NOT_FOUND,
|
||||
"Test", Person.class.getName());
|
||||
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void newTemplateReturnsGemfireTemplateForPeopleRegion() {
|
||||
|
||||
RepositoryMetadata mockRepositoryMetadata =
|
||||
mockRepositoryMetadata(Person.class, Long.class, PeopleRepository.class);
|
||||
|
||||
Region<Long, Person> mockPeopleRegion = mockRegion("People", Long.class, Person.class);
|
||||
|
||||
Iterable<Region<?, ?>> regions = Arrays.asList(mockRegion, mockPeopleRegion);
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(Arrays.asList(this.mockRegion, mockPeopleRegion), mappingContext);
|
||||
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory(
|
||||
regions, gemfireMappingContext);
|
||||
GemfireTemplate gemfireTemplate = repositoryFactory.newTemplate(mockRepositoryMetadata);
|
||||
|
||||
GemfireTemplate gemfireTemplate = gemfireRepositoryFactory.getTemplate(mockRepositoryMetadata);
|
||||
|
||||
assertThat(gemfireTemplate, is(notNullValue(GemfireTemplate.class)));
|
||||
assertThat(gemfireTemplate.<Long, Person>getRegion(), is(equalTo(mockPeopleRegion)));
|
||||
assertThat(gemfireTemplate).isNotNull();
|
||||
assertThat(gemfireTemplate.getRegion()).isEqualTo(mockPeopleRegion);
|
||||
|
||||
verify(mockPeopleRegion, times(1)).getAttributes();
|
||||
verify(mockPeopleRegion, times(1)).getFullPath();
|
||||
verify(mockPeopleRegion, times(1)).getName();
|
||||
verify(mockRegionAttributes, times(1)).getKeyConstraint();
|
||||
verify(this.mockRegionAttributes, times(1)).getKeyConstraint();
|
||||
verify(mockRepositoryMetadata, times(1)).getDomainType();
|
||||
verify(mockRepositoryMetadata, times(1)).getIdType();
|
||||
verify(mockRepositoryMetadata, times(1)).getRepositoryInterface();
|
||||
verifyNoMoreInteractions(mockRepositoryMetadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTemplateReturnsGemfireTemplateForSimpleRegion() {
|
||||
RepositoryMetadata mockRepositoryMetadata = mockRepositoryMetadata(Person.class, Long.class,
|
||||
SampleCustomGemfireRepository.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
public void newTemplateReturnsGemfireTemplateForSimpleRegion() {
|
||||
|
||||
Iterable<Region<?, ?>> regions = Collections.<Region<?, ?>>singleton(mockRegion);
|
||||
RepositoryMetadata mockRepositoryMetadata =
|
||||
mockRepositoryMetadata(Person.class, Long.class, TestGemfireRepository.class);
|
||||
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory(
|
||||
regions, gemfireMappingContext);
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.singleton(this.mockRegion), this.mappingContext);
|
||||
|
||||
GemfireTemplate gemfireTemplate = gemfireRepositoryFactory.getTemplate(mockRepositoryMetadata);
|
||||
GemfireTemplate gemfireTemplate = gemfireRepositoryFactory.newTemplate(mockRepositoryMetadata);
|
||||
|
||||
assertThat(gemfireTemplate, is(notNullValue(GemfireTemplate.class)));
|
||||
assertThat(gemfireTemplate.getRegion(), is(equalTo(mockRegion)));
|
||||
assertThat(gemfireTemplate).isNotNull();
|
||||
assertThat(gemfireTemplate.getRegion()).isEqualTo(this.mockRegion);
|
||||
|
||||
verify(this.mockRegion, times(1)).getAttributes();
|
||||
verify(this.mockRegionAttributes, times(1)).getKeyConstraint();
|
||||
verify(mockRepositoryMetadata, times(1)).getDomainType();
|
||||
verify(mockRepositoryMetadata, times(1)).getIdType();
|
||||
verify(mockRepositoryMetadata, times(1)).getRepositoryInterface();
|
||||
verifyNoMoreInteractions(mockRepositoryMetadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTemplateThrowsIllegalArgumentExceptionForIncompatibleRegionKeyTypeAndRepositoryIdType() {
|
||||
RepositoryMetadata mockRepositoryMetadata = mockRepositoryMetadata(Person.class, Long.class,
|
||||
PersonRepository.class);
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void newTemplateWithIncompatibleRegionKeyTypeAndRepositoryIdTypeThrowsIllegalArgumentException() {
|
||||
|
||||
Region<String, Person> mockPeopleRegion = mockRegion("People", String.class, Person.class);
|
||||
RepositoryMetadata mockRepositoryMetadata =
|
||||
mockRepositoryMetadata(Person.class, Long.class, PeopleRepository.class);
|
||||
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory(
|
||||
Collections.<Region<?, ?>>singleton(mockPeopleRegion), gemfireMappingContext);
|
||||
Region<Integer, Person> mockPeopleRegion = mockRegion("People", Integer.class, Person.class);
|
||||
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.singleton(mockPeopleRegion), this.mappingContext);
|
||||
|
||||
try {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(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]",
|
||||
String.class.getName(), Long.class.getName()));
|
||||
gemfireRepositoryFactory.newTemplate(mockRepositoryMetadata);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
gemfireRepositoryFactory.getTemplate(mockRepositoryMetadata);
|
||||
assertThat(expected).hasMessage(GemfireRepositoryFactory.REGION_REPOSITORY_ID_TYPE_MISMATCH,
|
||||
"/People", Integer.class.getName(), PeopleRepository.class.getName(), Long.class.getName());
|
||||
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockPeopleRegion, times(1)).getAttributes();
|
||||
verify(mockPeopleRegion, atLeastOnce()).getFullPath();
|
||||
verify(this.mockRegionAttributes, times(1)).getKeyConstraint();
|
||||
verify(mockRepositoryMetadata, times(1)).getDomainType();
|
||||
verify(mockRepositoryMetadata, times(1)).getIdType();
|
||||
verify(mockPeopleRegion, times(1)).getAttributes();
|
||||
verify(mockPeopleRegion, times(1)).getFullPath();
|
||||
verify(mockPeopleRegion, times(1)).getName();
|
||||
verify(mockRegionAttributes, times(1)).getKeyConstraint();
|
||||
verify(mockRepositoryMetadata, times(2)).getRepositoryInterface();
|
||||
verifyNoMoreInteractions(mockRepositoryMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTemplateThrowsIllegalStateExceptionForRegionNotFound() {
|
||||
RepositoryMetadata mockRepositoryMetadata = mockRepositoryMetadata(Person.class, Long.class,
|
||||
PersonRepository.class);
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void newTemplateWithIncompatibleRepositoryIdTypeAndEntityIdTypeThrowsIllegalArgumentException() {
|
||||
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory(
|
||||
Collections.<Region<?, ?>>singleton(mockRegion), gemfireMappingContext);
|
||||
RepositoryMetadata mockRepositoryMetadata =
|
||||
mockRepositoryMetadata(Person.class, Integer.class, PeopleIntegerRepository.class);
|
||||
|
||||
Region<String, Person> mockPeopleRegion = mockRegion("People", null, null);
|
||||
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.singleton(mockPeopleRegion), this.mappingContext);
|
||||
|
||||
try {
|
||||
exception.expect(IllegalStateException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(String.format(
|
||||
"No Region [People] was found for domain class [%s]; Make sure you have configured a GemFire Region of that name in your application context",
|
||||
Person.class.getName()));
|
||||
gemfireRepositoryFactory.newTemplate(mockRepositoryMetadata);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
gemfireRepositoryFactory.getTemplate(mockRepositoryMetadata);
|
||||
assertThat(expected).hasMessage(GemfireRepositoryFactory.REPOSITORY_ENTITY_ID_TYPE_MISMATCH,
|
||||
PeopleIntegerRepository.class.getName(), Integer.class.getName(), Person.class.getName(),
|
||||
Long.class.getName());
|
||||
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockPeopleRegion, times(1)).getAttributes();
|
||||
verify(this.mockRegionAttributes, times(1)).getKeyConstraint();
|
||||
verify(mockRepositoryMetadata, times(1)).getDomainType();
|
||||
verify(mockRepositoryMetadata, times(1)).getIdType();
|
||||
verify(mockRepositoryMetadata, times(2)).getRepositoryInterface();
|
||||
verifyNoMoreInteractions(mockRepositoryMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@SuppressWarnings("unchecked")
|
||||
public void newTemplateWithNonExistingRegionThrowsIllegalStateException() {
|
||||
|
||||
RepositoryMetadata mockRepositoryMetadata =
|
||||
mockRepositoryMetadata(Person.class, Long.class, PeopleRepository.class);
|
||||
|
||||
GemfireRepositoryFactory gemfireRepositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.singleton(this.mockRegion), this.mappingContext);
|
||||
|
||||
try {
|
||||
gemfireRepositoryFactory.newTemplate(mockRepositoryMetadata);
|
||||
}
|
||||
catch (IllegalStateException expected) {
|
||||
|
||||
assertThat(expected).hasMessage(GemfireRepositoryFactory.REGION_NOT_FOUND,
|
||||
"People", Person.class.getName(), PeopleRepository.class.getName());
|
||||
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockRepositoryMetadata, times(2)).getDomainType();
|
||||
verify(mockRepositoryMetadata, never()).getIdType();
|
||||
verify(mockRegion, times(1)).getFullPath();
|
||||
verify(mockRegion, times(1)).getName();
|
||||
verifyZeroInteractions(mockRegionAttributes);
|
||||
verify(mockRepositoryMetadata, times(2)).getRepositoryInterface();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://jira.spring.io/browse/SGF-112
|
||||
* @link <a href="https://jira.spring.io/browse/SGF-112">Repositories should reject PagingAndSortingRepository and Pageable parameters</a>
|
||||
*/
|
||||
@Test
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@SuppressWarnings("unchecked")
|
||||
public void rejectsInterfacesExtendingPagingAndSortingRepository() {
|
||||
exception.expect(IllegalStateException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage(startsWith("Pagination is not supported by GemFire Repositories"));
|
||||
|
||||
GemfireRepositoryFactory repositoryFactory = new GemfireRepositoryFactory(
|
||||
Collections.<Region<?, ?>>singletonList(mockRegion), new GemfireMappingContext());
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.singletonList(this.mockRegion), new GemfireMappingContext());
|
||||
|
||||
repositoryFactory.getRepository(SamplePagingAndSortingRepository.class);
|
||||
try {
|
||||
repositoryFactory.getRepository(SamplePagingAndSortingRepository.class);
|
||||
}
|
||||
catch (IllegalStateException expected) {
|
||||
|
||||
assertThat(expected).hasMessageStartingWith("Pagination is not supported by GemFire Repositories");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void usesConfiguredRepositoryBaseClass() {
|
||||
GemfireRepositoryFactory repositoryFactory = new GemfireRepositoryFactory(
|
||||
Collections.<Region<?, ?>>singletonList(mockRegion), new GemfireMappingContext());
|
||||
|
||||
repositoryFactory.setRepositoryBaseClass(CustomBaseRepository.class);
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.singletonList(this.mockRegion), this.mappingContext);
|
||||
|
||||
GemfireRepository<?, ?> gemfireRepository = repositoryFactory.getRepository(SampleCustomGemfireRepository.class,
|
||||
new SampleCustomRepositoryImpl());
|
||||
repositoryFactory.setRepositoryBaseClass(TestCustomBaseRepository.class);
|
||||
|
||||
assertThat(((Advised) gemfireRepository).getTargetClass(), is(equalTo((Class) CustomBaseRepository.class)));
|
||||
GemfireRepository<?, ?> gemfireRepository =
|
||||
repositoryFactory.getRepository(TestGemfireRepository.class,
|
||||
RepositoryComposition.RepositoryFragments.just(new TestCustomRepositoryImpl()));
|
||||
|
||||
assertThat(((Advised) gemfireRepository).getTargetClass()).isEqualTo(TestCustomBaseRepository.class);
|
||||
}
|
||||
|
||||
interface SamplePagingAndSortingRepository extends PagingAndSortingRepository<Person, Long> {
|
||||
}
|
||||
|
||||
static class CustomBaseRepository<T, ID extends Serializable> extends SimpleGemfireRepository<T, ID> {
|
||||
static class TestCustomBaseRepository<T, ID extends Serializable> extends SimpleGemfireRepository<T, ID> {
|
||||
|
||||
public CustomBaseRepository(GemfireTemplate template, EntityInformation<T, ID> entityInformation) {
|
||||
public TestCustomBaseRepository(GemfireTemplate template, EntityInformation<T, ID> entityInformation) {
|
||||
super(template, entityInformation);
|
||||
}
|
||||
}
|
||||
|
||||
interface SampleCustomRepository<T> {
|
||||
interface TestCustomRepository<T> {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
void doCustomUpdate(T entity);
|
||||
|
||||
}
|
||||
|
||||
class SampleCustomRepositoryImpl<T> implements SampleCustomRepository<T> {
|
||||
class TestCustomRepositoryImpl<T> implements TestCustomRepository<T> {
|
||||
|
||||
@Override
|
||||
public void doCustomUpdate(final T entity) {
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
public void doCustomUpdate(T entity) {
|
||||
throw newUnsupportedOperationException("Not Implemented");
|
||||
}
|
||||
}
|
||||
|
||||
interface SampleCustomGemfireRepository extends GemfireRepository<Person, Long>, SampleCustomRepository<Person> {
|
||||
}
|
||||
interface TestGemfireRepository extends GemfireRepository<Person, Long>, TestCustomRepository<Person> { }
|
||||
|
||||
@org.springframework.data.gemfire.mapping.annotation.Region("People")
|
||||
interface PersonRepository extends GemfireRepository<Person, Long> {
|
||||
}
|
||||
interface PeopleRepository extends GemfireRepository<Person, Long> { }
|
||||
|
||||
@org.springframework.data.gemfire.mapping.annotation.Region("People")
|
||||
interface PeopleIntegerRepository extends GemfireRepository<Person, Integer> { }
|
||||
|
||||
@org.springframework.data.gemfire.mapping.annotation.Region
|
||||
interface NonQualifiedRegionAnnotatedRepository extends GemfireRepository<Person, Long> { }
|
||||
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
package org.springframework.data.gemfire.util;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -35,10 +33,11 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.data.gemfire.test.support.MapBuilder;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CollectionUtils}.
|
||||
@@ -59,9 +58,6 @@ import org.junit.rules.ExpectedException;
|
||||
*/
|
||||
public class CollectionUtilsUnitTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void addAllIterableElementsToList() {
|
||||
|
||||
@@ -88,14 +84,19 @@ public class CollectionUtilsUnitTests {
|
||||
assertThat(target).contains(1, 2, 3, 4, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void addIterableToNullCollection() {
|
||||
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage("Collection is required");
|
||||
try {
|
||||
CollectionUtils.addAll(null, Collections.emptySet());
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
CollectionUtils.addAll(null, Collections.emptySet());
|
||||
assertThat(expected).hasMessage("Collection is required");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -185,6 +186,11 @@ public class CollectionUtilsUnitTests {
|
||||
assertThat(CollectionUtils.containsAny(null, 1)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsAnyWithNullCollectionAndNullArrayIsFalse() {
|
||||
assertThat(CollectionUtils.containsAny(null, (Object[]) null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyIterableReturnsEmptyIterable() {
|
||||
|
||||
@@ -197,25 +203,20 @@ public class CollectionUtilsUnitTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void iterableEnumeration() {
|
||||
public void iterableOfEnumeration() {
|
||||
|
||||
Enumeration<String> mockEnumeration = mock(Enumeration.class, "MockEnumeration");
|
||||
Enumeration<Object> mockEnumeration = mock(Enumeration.class, "MockEnumeration");
|
||||
|
||||
when(mockEnumeration.hasMoreElements()).thenReturn(true).thenReturn(true).thenReturn(true).thenReturn(false);
|
||||
when(mockEnumeration.nextElement()).thenReturn("zero").thenReturn("one").thenReturn("two")
|
||||
when(mockEnumeration.nextElement()).thenReturn(1).thenReturn(2).thenReturn(3)
|
||||
.thenThrow(new NoSuchElementException("Enumeration exhausted"));
|
||||
|
||||
Iterable<String> iterable = CollectionUtils.iterable(mockEnumeration);
|
||||
Iterable<Object> iterable = CollectionUtils.iterable(mockEnumeration);
|
||||
|
||||
assertThat(iterable).isNotNull();
|
||||
|
||||
List<String> actualList = new ArrayList<String>(3);
|
||||
|
||||
for (String element : iterable) {
|
||||
actualList.add(element);
|
||||
}
|
||||
|
||||
assertThat(actualList).isEqualTo(Arrays.asList("zero", "one", "two"));
|
||||
//assertThat(iterable).containsExactly(1, 2, 3);
|
||||
assertThat(StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toSet()))
|
||||
.containsExactly(1, 2, 3);
|
||||
|
||||
verify(mockEnumeration, times(4)).hasMoreElements();
|
||||
verify(mockEnumeration, times(3)).nextElement();
|
||||
@@ -223,30 +224,93 @@ public class CollectionUtilsUnitTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void iterableIterator() {
|
||||
public void iterableOfSingleEnumeration() {
|
||||
|
||||
Iterator<String> mockIterator = mock(Iterator.class, "MockIterator");
|
||||
Enumeration<Object> mockEnumeration = mock(Enumeration.class);
|
||||
|
||||
when(mockEnumeration.hasMoreElements()).thenReturn(true).thenReturn(false);
|
||||
when(mockEnumeration.nextElement()).thenReturn(1)
|
||||
.thenThrow(new NoSuchElementException("Enumeration exhausted"));
|
||||
|
||||
Iterable<Object> iterable = CollectionUtils.iterable(mockEnumeration);
|
||||
|
||||
assertThat(iterable).isNotNull();
|
||||
//assertThat(iterable).containsExactly(1);
|
||||
assertThat(StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toSet()))
|
||||
.containsExactly(1);
|
||||
|
||||
verify(mockEnumeration, times(2)).hasMoreElements();
|
||||
verify(mockEnumeration, times(1)).nextElement();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void iterableOfNullEnumeration() {
|
||||
|
||||
Iterable<?> iterable = CollectionUtils.iterable((Enumeration<?>) null);
|
||||
|
||||
assertThat(iterable).isNotNull();
|
||||
assertThat(iterable).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void iterableOfIterator() {
|
||||
|
||||
Iterator<Object> mockIterator = mock(Iterator.class, "MockIterator");
|
||||
|
||||
when(mockIterator.hasNext()).thenReturn(true).thenReturn(true).thenReturn(true).thenReturn(false);
|
||||
when(mockIterator.next()).thenReturn("zero").thenReturn("one").thenReturn("two")
|
||||
when(mockIterator.next()).thenReturn(1).thenReturn(2).thenReturn(3)
|
||||
.thenThrow(new NoSuchElementException("Iterator exhausted"));
|
||||
|
||||
Iterable<String> iterable = CollectionUtils.iterable(mockIterator);
|
||||
Iterable<Object> iterable = CollectionUtils.iterable(mockIterator);
|
||||
|
||||
assertThat(iterable).isNotNull();
|
||||
|
||||
List<String> actualList = new ArrayList<String>(3);
|
||||
Set<Object> set = new HashSet<>();
|
||||
|
||||
for (String element : iterable) {
|
||||
actualList.add(element);
|
||||
}
|
||||
iterable.forEach(set::add);
|
||||
|
||||
assertThat(actualList).containsAll(Arrays.asList("zero", "one", "two"));
|
||||
assertThat(set).hasSize(3);
|
||||
assertThat(set).containsExactly(1, 2, 3);
|
||||
|
||||
verify(mockIterator, times(4)).hasNext();
|
||||
verify(mockIterator, times(3)).next();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void iterableOfSingleIterator() {
|
||||
|
||||
Iterator<Object> mockIterator = mock(Iterator.class, "MockIterator");
|
||||
|
||||
when(mockIterator.hasNext()).thenReturn(true).thenReturn(false);
|
||||
when(mockIterator.next()).thenReturn(1).thenThrow(new NoSuchElementException("Iterator exhausted"));
|
||||
|
||||
Iterable<Object> iterable = CollectionUtils.iterable(mockIterator);
|
||||
|
||||
assertThat(iterable).isNotNull();
|
||||
|
||||
Set<Object> set = new HashSet<>();
|
||||
|
||||
iterable.forEach(set::add);
|
||||
|
||||
assertThat(set).hasSize(1);
|
||||
assertThat(set).containsExactly(1);
|
||||
|
||||
verify(mockIterator, times(2)).hasNext();
|
||||
verify(mockIterator, times(1)).next();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iterableOfNullIterator() {
|
||||
|
||||
Iterable<?> iterable = CollectionUtils.iterable((Iterator<?>) null);
|
||||
|
||||
assertThat(iterable).isNotNull();
|
||||
assertThat(iterable).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeCollectionWithNonNullCollection() {
|
||||
|
||||
@@ -264,6 +328,23 @@ public class CollectionUtilsUnitTests {
|
||||
assertThat(collection.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeEnumerationWithNonNullEnumeration() {
|
||||
|
||||
Enumeration<?> mockEnumeration = mock(Enumeration.class);
|
||||
|
||||
assertThat(CollectionUtils.nullSafeEnumeration(mockEnumeration)).isSameAs(mockEnumeration);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeEnumerationWithNullEnumeration() {
|
||||
|
||||
Enumeration<?> enumeration = CollectionUtils.nullSafeEnumeration(null);
|
||||
|
||||
assertThat(enumeration).isNotNull();
|
||||
assertThat(enumeration.hasMoreElements()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void nullSafeIterableWithNonNullIterable() {
|
||||
@@ -344,6 +425,23 @@ public class CollectionUtilsUnitTests {
|
||||
assertThat(CollectionUtils.nullSafeIterable((Iterable<?>) null, null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeIteratorWithNonNullIterator() {
|
||||
|
||||
Iterator<?> mockIterator = mock(Iterator.class);
|
||||
|
||||
assertThat(CollectionUtils.nullSafeIterator(mockIterator)).isSameAs(mockIterator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeIteratorWithNullIterator() {
|
||||
|
||||
Iterator<?> iterator = CollectionUtils.nullSafeIterator(null);
|
||||
|
||||
assertThat(iterator).isNotNull();
|
||||
assertThat(iterator).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeListWithNonNullList() {
|
||||
|
||||
@@ -395,18 +493,97 @@ public class CollectionUtilsUnitTests {
|
||||
assertThat(set.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeIsEmptyCollectionWithNonNulNonEmptyCollectionReturnsFalse() {
|
||||
assertThat(CollectionUtils.nullSafeIsEmpty(Collections.singleton(1))).isFalse();
|
||||
assertThat(CollectionUtils.nullSafeIsEmpty(Collections.singletonList(1))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeIsEmptyCollectionWithEmptyCollectionReturnsTrue() {
|
||||
assertThat(CollectionUtils.isEmpty(Collections.emptyList())).isTrue();
|
||||
assertThat(CollectionUtils.isEmpty(Collections.emptySet())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeIsEmptyCollectionWithNullCollectionReturnsTrue() {
|
||||
assertThat(CollectionUtils.isEmpty((Collection<?>) null)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeIsEmptyMapWithNonNullNonEmptyMapReturnsFalse() {
|
||||
assertThat(CollectionUtils.isEmpty(Collections.singletonMap("key", "value"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeIsEmptyMapWithEmptyMapReturnsTrue() {
|
||||
assertThat(CollectionUtils.isEmpty(Collections.emptyMap())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeIsEmptyMapWithNullMapReturnsTrue() {
|
||||
assertThat(CollectionUtils.isEmpty((Map<?, ?>) null)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeCollectionSizeWithNonNullNonEmptyCollectionReturnsSize() {
|
||||
assertThat(CollectionUtils.nullSafeSize(Collections.singleton(1))).isEqualTo(1);
|
||||
assertThat(CollectionUtils.nullSafeSize(Collections.singletonList(1))).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeCollectionSizeWithEmptyCollectionReturnsZero() {
|
||||
assertThat(CollectionUtils.nullSafeSize(Collections.emptyList())).isZero();
|
||||
assertThat(CollectionUtils.nullSafeSize(Collections.emptySet())).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeCollectionSizeWithNullCollectionReturnsZero() {
|
||||
assertThat(CollectionUtils.nullSafeSize((Collection<?>) null)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeMapSizeWithNonNullNonEmptyMapReturnsSize() {
|
||||
assertThat(CollectionUtils.nullSafeSize(Collections.singletonMap("key", "value"))).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeMapSizeWithEmptyMapReturnsZero() {
|
||||
assertThat(CollectionUtils.nullSafeSize(Collections.emptyMap())).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullSafeMapSizeWithNullMapReturnsZero() {
|
||||
assertThat(CollectionUtils.nullSafeSize((Map<?, ?>) null)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sortIsSuccessful() {
|
||||
|
||||
List<Integer> list = new ArrayList<Integer>(Arrays.asList(2, 3, 1));
|
||||
List<Integer> list = new ArrayList<>(Arrays.asList(2, 3, 1));
|
||||
List<Integer> sortedList = CollectionUtils.sort(list);
|
||||
|
||||
assertThat(sortedList).isSameAs(list);
|
||||
assertThat(sortedList).isEqualTo(Arrays.asList(1, 2, 3));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void sortWithNullListThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
CollectionUtils.sort(null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("List is required");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subListFromListWithIndexesReturnsSubList() {
|
||||
public void subListOfListWithIndexesReturnsSubList() {
|
||||
|
||||
List<Integer> list = Arrays.asList(0, 1, 2, 3);
|
||||
List<Integer> subList = CollectionUtils.subList(list, 1, 3);
|
||||
@@ -417,8 +594,13 @@ public class CollectionUtilsUnitTests {
|
||||
assertThat(subList).containsAll(Arrays.asList(1, 3));
|
||||
}
|
||||
|
||||
@Test(expected = IndexOutOfBoundsException.class)
|
||||
public void subListOfListWithInvalidIndexThrowsIndexOutOfBoundsException() {
|
||||
CollectionUtils.subList(Arrays.asList(0, 1, 2), 1, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subListFromListWithNoIndexesReturnsEmptyList() {
|
||||
public void subListOfListWithNoIndexesReturnsEmptyList() {
|
||||
|
||||
List<Integer> subList = CollectionUtils.subList(Arrays.asList(0, 1, 2));
|
||||
|
||||
@@ -426,18 +608,59 @@ public class CollectionUtilsUnitTests {
|
||||
assertThat(subList.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test(expected = IndexOutOfBoundsException.class)
|
||||
public void subListFromListWithInvalidIndexThrowsIndexOutOfBoundsException() {
|
||||
CollectionUtils.subList(Arrays.asList(0, 1, 2), 1, 3);
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void subListOfNullListThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
CollectionUtils.subList(null, 1, 2, 3);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("List is required");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subListWithNullSourceListThrowsIllegalArgumentException() {
|
||||
public void toStringOfMultiEntryMap() {
|
||||
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectCause(is(nullValue(Throwable.class)));
|
||||
exception.expectMessage("List is required");
|
||||
Map<String, String> map = MapBuilder.<String, String>newMapBuilder()
|
||||
.put("keyOne", "valueOne")
|
||||
.put("keyTwo", "valueTwo")
|
||||
.build();
|
||||
|
||||
CollectionUtils.subList(null, 1, 2, 3);
|
||||
String mapString = CollectionUtils.toString(map);
|
||||
|
||||
assertThat(mapString).isNotNull();
|
||||
assertThat(mapString).isEqualTo("{\n\tkeyOne = valueOne,\n\tkeyTwo = valueTwo\n}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringOfSingleEntryMap() {
|
||||
|
||||
String mapString = CollectionUtils.toString(Collections.singletonMap("key", "value"));
|
||||
|
||||
assertThat(mapString).isNotNull();
|
||||
assertThat(mapString).isEqualTo("{\n\tkey = value\n}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringOfEmptyMap() {
|
||||
|
||||
String mapString = CollectionUtils.toString(Collections.emptyMap());
|
||||
|
||||
assertThat(mapString).isNotNull();
|
||||
assertThat(mapString).isEqualTo("{\n\n}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringOfNullMap() {
|
||||
|
||||
String mapString = CollectionUtils.toString(null);
|
||||
|
||||
assertThat(mapString).isNotNull();
|
||||
assertThat(mapString).isEqualTo("{\n\n}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
||||
xmlns:gfe-data="http://www.springframework.org/schema/data/gemfire"
|
||||
xmlns:repo="http://www.springframework.org/schema/data/repository"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
||||
http://www.springframework.org/schema/data/gemfire http://www.springframework.org/schema/data/gemfire/spring-data-gemfire.xsd
|
||||
http://www.springframework.org/schema/data/repository http://www.springframework.org/schema/data/repository/spring-repository.xsd
|
||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
|
||||
">
|
||||
|
||||
<util:properties id="gemfireProperties">
|
||||
<prop key="name">springGemFireIncompatibleRegionKeyEntityIdAnimalRepositoryTest</prop>
|
||||
<prop key="mcast-port">0</prop>
|
||||
<prop key="log-level">warning</prop>
|
||||
</util:properties>
|
||||
|
||||
<gfe:cache properties-ref="gemfireProperties"/>
|
||||
|
||||
<gfe:replicated-region
|
||||
id="Rabbits"
|
||||
persistent="false"
|
||||
key-constraint="java.lang.String"
|
||||
value-constraint="org.springframework.data.gemfire.repository.sample.Animal"/>
|
||||
|
||||
<gfe-data:repositories base-package="org.springframework.data.gemfire.repository.sample">
|
||||
<repo:include-filter type="assignable" expression="org.springframework.data.gemfire.repository.sample.RabbitRepository"/>
|
||||
</gfe-data:repositories>
|
||||
|
||||
</beans>
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
||||
xmlns:gfe-data="http://www.springframework.org/schema/data/gemfire"
|
||||
xmlns:repo="http://www.springframework.org/schema/data/repository"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
||||
http://www.springframework.org/schema/data/gemfire http://www.springframework.org/schema/data/gemfire/spring-data-gemfire.xsd
|
||||
http://www.springframework.org/schema/data/repository http://www.springframework.org/schema/data/repository/spring-repository.xsd
|
||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
|
||||
">
|
||||
|
||||
<util:properties id="gemfireProperties">
|
||||
<prop key="name">springGemFirePlantRepositoryTest</prop>
|
||||
<prop key="mcast-port">0</prop>
|
||||
<prop key="log-level">warning</prop>
|
||||
</util:properties>
|
||||
|
||||
<gfe:cache properties-ref="gemfireProperties"/>
|
||||
|
||||
<gfe:replicated-region
|
||||
id="Plants"
|
||||
persistent="false"
|
||||
key-constraint="java.lang.Long"
|
||||
value-constraint="org.springframework.data.gemfire.repository.sample.Plant"/>
|
||||
|
||||
<gfe-data:repositories base-package="org.springframework.data.gemfire.repository.sample">
|
||||
<repo:include-filter type="assignable" expression="org.springframework.data.gemfire.repository.sample.PlantRepository"/>
|
||||
</gfe-data:repositories>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user