DATAGEODE-263 - Add support for PagingAndSortingRepositories.
This commit is contained in:
@@ -291,13 +291,13 @@ public class GemfireTemplate extends GemfireAccessor implements GemfireOperation
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E> SelectResults<E> find(String queryString, Object... params) throws InvalidDataAccessApiUsageException {
|
||||
public <E> SelectResults<E> find(String queryString, Object... arguments) throws InvalidDataAccessApiUsageException {
|
||||
|
||||
try {
|
||||
|
||||
QueryService queryService = resolveQueryService(getRegion());
|
||||
Query query = queryService.newQuery(queryString);
|
||||
Object result = query.execute(params);
|
||||
Object result = query.execute(arguments);
|
||||
|
||||
if (result instanceof SelectResults) {
|
||||
return (SelectResults<E>) result;
|
||||
@@ -314,8 +314,8 @@ public class GemfireTemplate extends GemfireAccessor implements GemfireOperation
|
||||
catch (GemFireCheckedException cause) {
|
||||
throw convertGemFireAccessException(cause);
|
||||
}
|
||||
catch (GemFireException caue) {
|
||||
throw convertGemFireAccessException(caue);
|
||||
catch (GemFireException cause) {
|
||||
throw convertGemFireAccessException(cause);
|
||||
}
|
||||
catch (RuntimeException cause) {
|
||||
|
||||
|
||||
@@ -14,20 +14,20 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.domain;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.gemfire.domain.support.AbstractPageSupport;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ListablePage} class is a Spring Data {@link Page} implementation wrapping a {@link List} as the content
|
||||
@@ -54,7 +54,7 @@ public class ListablePage<T> extends AbstractPageSupport<T> {
|
||||
* @see #ListablePage(List)
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> ListablePage<T> newListablePage(T... content) {
|
||||
public static <T> ListablePage<T> newListablePage(@NonNull T... content) {
|
||||
return new ListablePage<>(Arrays.asList(content));
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class ListablePage<T> extends AbstractPageSupport<T> {
|
||||
* @return a new {@link ListablePage} initialized with the given {@link List} for content.
|
||||
* @see #ListablePage(List)
|
||||
*/
|
||||
public static <T> ListablePage<T> newListablePage(List<T> content) {
|
||||
public static <T> ListablePage<T> newListablePage(@Nullable List<T> content) {
|
||||
return new ListablePage<>(content);
|
||||
}
|
||||
|
||||
@@ -83,8 +83,8 @@ public class ListablePage<T> extends AbstractPageSupport<T> {
|
||||
* @see java.util.Collections#emptyList()
|
||||
* @see java.util.List
|
||||
*/
|
||||
public ListablePage(List<T> content) {
|
||||
this.content = Optional.ofNullable(content).orElse(Collections.emptyList());
|
||||
public ListablePage(@Nullable List<T> content) {
|
||||
this.content = content != null ? content : Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,6 +164,9 @@ public class ListablePage<T> extends AbstractPageSupport<T> {
|
||||
*/
|
||||
@Override
|
||||
public <S> Page<S> map(Function<? super T, ? extends S> converter) {
|
||||
return newListablePage(getContent().stream().map(converter::apply).collect(Collectors.toList()));
|
||||
|
||||
return newListablePage(getContent().stream()
|
||||
.map(converter)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,28 +15,16 @@
|
||||
*/
|
||||
package org.springframework.data.gemfire.repository;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
|
||||
/**
|
||||
* Apache Geode extension of the Spring Data {@link CrudRepository} interface.
|
||||
* Apache Geode extension of the Spring Data {@link PagingAndSortingRepository} interface.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see org.springframework.data.repository.PagingAndSortingRepository
|
||||
*/
|
||||
public interface GemfireRepository<T, ID> extends CrudRepository<T, ID> {
|
||||
|
||||
/**
|
||||
* Returns all entities ordered by the given {@link Sort}.
|
||||
*
|
||||
* @param sort {@link Sort} defining the ordering criteria.
|
||||
* @return all entities ordered by the given {@link Sort}.
|
||||
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
|
||||
* @see org.springframework.data.domain.Sort
|
||||
* @see java.lang.Iterable
|
||||
*/
|
||||
Iterable<T> findAll(Sort sort);
|
||||
public interface GemfireRepository<T, ID> extends PagingAndSortingRepository<T, ID> {
|
||||
|
||||
/**
|
||||
* Save the entity wrapped by the given {@link Wrapper}.
|
||||
|
||||
@@ -13,20 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.gemfire.mapping.GemfirePersistentEntity;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link AbstractQueryCreator} to create {@link QueryString} instances.
|
||||
*
|
||||
@@ -59,10 +58,6 @@ class GemfireQueryCreator extends AbstractQueryCreator<QueryString, Predicates>
|
||||
this.indexes = new IndexProvider();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#createQuery(org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
public QueryString createQuery(Sort dynamicSort) {
|
||||
|
||||
@@ -71,37 +66,21 @@ class GemfireQueryCreator extends AbstractQueryCreator<QueryString, Predicates>
|
||||
return super.createQuery(dynamicSort);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator)
|
||||
*/
|
||||
@Override
|
||||
protected Predicates create(Part part, Iterator<Object> iterator) {
|
||||
return Predicates.create(part, this.indexes);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator)
|
||||
*/
|
||||
@Override
|
||||
protected Predicates and(Part part, Predicates base, Iterator<Object> iterator) {
|
||||
return base.and(Predicates.create(part, this.indexes));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected Predicates or(Predicates base, Predicates criteria) {
|
||||
return base.or(criteria);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#complete(java.lang.Object, org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
protected QueryString complete(Predicates criteria, Sort sort) {
|
||||
|
||||
@@ -130,14 +109,13 @@ class GemfireQueryCreator extends AbstractQueryCreator<QueryString, Predicates>
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public boolean hasNext() {
|
||||
return (index <= Integer.MAX_VALUE);
|
||||
return this.index <= Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer next() {
|
||||
return index++;
|
||||
return this.index++;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,8 +28,12 @@ import org.springframework.data.gemfire.repository.query.annotation.Limit;
|
||||
import org.springframework.data.gemfire.repository.query.annotation.Trace;
|
||||
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.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -49,60 +53,108 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link GemfireQueryMethod} from the given {@link Method}
|
||||
* and {@link RepositoryMetadata}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param metadata must not be {@literal null}.
|
||||
* @param factory must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param method {@link Method} object backing the actual {@literal query} for this {@link QueryMethod};
|
||||
* must not be {@literal null}.
|
||||
* @param metadata {@link RepositoryMetadata} containing metadata about the {@link Repository}
|
||||
* to which this {@link QueryMethod} belongs; must not be {@literal null}.
|
||||
* @param projectionFactory {@link ProjectionFactory} used to handle the {@literal query} {@literal projection};
|
||||
* must not be {@literal null}.
|
||||
* @param mappingContext {@link MappingContext} used to map {@link Object entities} to Apache Geode and back to
|
||||
* {@link Object entities}; must not be {@literal null}.
|
||||
* @see #GemfireQueryMethod(Method, RepositoryMetadata, ProjectionFactory, MappingContext, QueryMethodEvaluationContextProvider)
|
||||
* @see org.springframework.data.repository.core.RepositoryMetadata
|
||||
* @see org.springframework.data.projection.ProjectionFactory
|
||||
* @see org.springframework.data.mapping.context.MappingContext
|
||||
* @see java.lang.reflect.Method
|
||||
*/
|
||||
public GemfireQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
|
||||
MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext) {
|
||||
public GemfireQueryMethod(@NonNull Method method,
|
||||
@NonNull RepositoryMetadata metadata,
|
||||
@NonNull ProjectionFactory projectionFactory,
|
||||
@NonNull MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext) {
|
||||
|
||||
super(method, metadata, factory);
|
||||
this(method, metadata, projectionFactory, mappingContext, null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link GemfireQueryMethod} from the given {@link Method}
|
||||
* and {@link RepositoryMetadata}.
|
||||
*
|
||||
* @param method {@link Method} object backing the actual {@literal query} for this {@link QueryMethod};
|
||||
* must not be {@literal null}.
|
||||
* @param metadata {@link RepositoryMetadata} containing metadata about the {@link Repository}
|
||||
* to which this {@link QueryMethod} belongs; must not be {@literal null}.
|
||||
* @param projectionFactory {@link ProjectionFactory} used to handle the {@literal query} {@literal projection};
|
||||
* must not be {@literal null}.
|
||||
* @param mappingContext {@link MappingContext} used to map {@link Object entities} to Apache Geode and back to
|
||||
* {@link Object entities}; must not be {@literal null}.
|
||||
* @param evaluationContextProvider {@link QueryMethodEvaluationContextProvider} used to process {@literal SpEL}
|
||||
* expressions.
|
||||
* @see org.springframework.data.repository.query.QueryMethodEvaluationContextProvider
|
||||
* @see org.springframework.data.repository.core.RepositoryMetadata
|
||||
* @see org.springframework.data.projection.ProjectionFactory
|
||||
* @see org.springframework.data.mapping.context.MappingContext
|
||||
* @see java.lang.reflect.Method
|
||||
*/
|
||||
public GemfireQueryMethod(@NonNull Method method,
|
||||
@NonNull RepositoryMetadata metadata,
|
||||
@NonNull ProjectionFactory projectionFactory,
|
||||
@NonNull MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext,
|
||||
@Nullable QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
super(method, metadata, projectionFactory);
|
||||
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null");
|
||||
|
||||
assertNonPagingQueryMethod(method);
|
||||
|
||||
this.method = method;
|
||||
this.entity = mappingContext.getPersistentEntity(getDomainClass());
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the query method is a non-Paging query method since GemFire does not support pagination
|
||||
* as it has no concept of a Cursor.
|
||||
* Determines whether the {@link Method} backing this {@link QueryMethod} is a {@link Pageable} {@link Method},
|
||||
* which requires special logic given Apache Geode does not support pagination since it has no concept of a
|
||||
* {@literal Database Cursor}.
|
||||
*
|
||||
* @param method the query method to be evaluated
|
||||
* @throws java.lang.IllegalStateException if the query method contains a parameter of type Pageable.
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @param method {@literal query} {@link Method} to be evaluate.
|
||||
* @return a boolean value indicating whether the {@link Method} has a parameter of type {@link Pageable}.
|
||||
* @see java.lang.reflect.Method#getParameterTypes()
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
*/
|
||||
private void assertNonPagingQueryMethod(Method method) {
|
||||
private boolean isPageableQueryMethod(@NonNull Method method) {
|
||||
|
||||
for (Class<?> type : method.getParameterTypes()) {
|
||||
if (Pageable.class.isAssignableFrom(type)) {
|
||||
|
||||
String message =
|
||||
String.format("Pagination is not supported by GemFire Repositories; Offending method: %s",
|
||||
method.getName());
|
||||
|
||||
throw new IllegalStateException(message);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link GemfirePersistentEntity} the method deals with.
|
||||
* Returns the {@link Method} reference on which this {@link QueryMethod} is based.
|
||||
*
|
||||
* @return the {@link GemfirePersistentEntity} the method deals with.
|
||||
* @return the {@link Method} reference on which this {@link QueryMethod} is based.
|
||||
* @see java.lang.reflect.Method
|
||||
*/
|
||||
public GemfirePersistentEntity<?> getPersistentEntity() {
|
||||
protected @NonNull Method getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link GemfirePersistentEntity} handled by this {@link QueryMethod}.
|
||||
*
|
||||
* @return the {@link GemfirePersistentEntity} handled by this {@link QueryMethod}.
|
||||
* @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity
|
||||
*/
|
||||
public @NonNull GemfirePersistentEntity<?> getPersistentEntity() {
|
||||
return this.entity;
|
||||
}
|
||||
|
||||
@@ -118,15 +170,16 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the annotated query for the query method if present.
|
||||
* Returns the {@link Query} annotated OQL query value for this {@link QueryMethod} if present.
|
||||
*
|
||||
* @return the annotated query or {@literal null} in case it's empty or not present.
|
||||
* @return the {@link Query} annotated OQL query value or {@literal null} in case it's {@literal null}, empty
|
||||
* or not present.
|
||||
* @see org.springframework.data.gemfire.repository.Query
|
||||
* @see java.lang.reflect.Method#getAnnotation(Class)
|
||||
*/
|
||||
String getAnnotatedQuery() {
|
||||
public @Nullable String getAnnotatedQuery() {
|
||||
|
||||
Query query = this.method.getAnnotation(Query.class);
|
||||
Query query = getMethod().getAnnotation(Query.class);
|
||||
|
||||
String queryString = query != null ? (String) AnnotationUtils.getValue(query) : null;
|
||||
|
||||
@@ -142,7 +195,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#isAnnotationPresent(Class)
|
||||
*/
|
||||
public boolean hasHint() {
|
||||
return this.method.isAnnotationPresent(Hint.class);
|
||||
return getMethod().isAnnotationPresent(Hint.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +207,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
*/
|
||||
public String[] getHints() {
|
||||
|
||||
Hint hint = method.getAnnotation(Hint.class);
|
||||
Hint hint = getMethod().getAnnotation(Hint.class);
|
||||
|
||||
return hint != null ? hint.value() : EMPTY_STRING_ARRAY;
|
||||
}
|
||||
@@ -168,7 +221,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#isAnnotationPresent(Class)
|
||||
*/
|
||||
public boolean hasImport() {
|
||||
return this.method.isAnnotationPresent(Import.class);
|
||||
return getMethod().isAnnotationPresent(Import.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,7 +233,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
*/
|
||||
public String getImport() {
|
||||
|
||||
Import importStatement = method.getAnnotation(Import.class);
|
||||
Import importStatement = getMethod().getAnnotation(Import.class);
|
||||
|
||||
return importStatement != null ? importStatement.value() : null;
|
||||
}
|
||||
@@ -194,7 +247,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#isAnnotationPresent(Class)
|
||||
*/
|
||||
public boolean hasLimit() {
|
||||
return this.method.isAnnotationPresent(Limit.class);
|
||||
return getMethod().isAnnotationPresent(Limit.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,7 +259,7 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
*/
|
||||
public int getLimit() {
|
||||
|
||||
Limit limit = method.getAnnotation(Limit.class);
|
||||
Limit limit = getMethod().getAnnotation(Limit.class);
|
||||
|
||||
return limit != null ? limit.value() : Integer.MAX_VALUE;
|
||||
}
|
||||
@@ -219,6 +272,6 @@ public class GemfireQueryMethod extends QueryMethod {
|
||||
* @see java.lang.reflect.Method#isAnnotationPresent(Class)
|
||||
*/
|
||||
public boolean hasTrace() {
|
||||
return this.method.isAnnotationPresent(Trace.class);
|
||||
return getMethod().isAnnotationPresent(Trace.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,25 +18,28 @@ 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.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class for GemFire specific {@link RepositoryQuery} implementations.
|
||||
* <p>
|
||||
* Abstract base class for Apache Geode specific {@link RepositoryQuery} implementations.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.gemfire.repository.query.GemfireQueryMethod
|
||||
* @see org.springframework.data.repository.Repository
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery
|
||||
* @see org.springframework.data.gemfire.repository.query.GemfireQueryMethod
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public abstract class GemfireRepositoryQuery implements RepositoryQuery {
|
||||
|
||||
private final GemfireQueryMethod queryMethod;
|
||||
|
||||
private QueryPostProcessor<?, String> queryPostProcessor = ProvidedQueryPostProcessor.IDENTITY;
|
||||
private QueryPostProcessor<Repository, String> queryPostProcessor = ProvidedQueryPostProcessor.IDENTITY;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
/**
|
||||
* Constructor used for testing purposes only!
|
||||
*/
|
||||
GemfireRepositoryQuery() {
|
||||
@@ -45,11 +48,11 @@ public abstract class GemfireRepositoryQuery implements RepositoryQuery {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link GemfireRepositoryQuery} initialized with
|
||||
* the given {@link GemfireQueryMethod}.
|
||||
* the given {@link GemfireQueryMethod} implementing the {@link Repository} {@link QueryMethod}.
|
||||
*
|
||||
* @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}.
|
||||
* @param queryMethod {@link GemfireQueryMethod} capturing the metadata and implementation of the {@link Repository}
|
||||
* {@link QueryMethod}; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link GemfireQueryMethod} is {@literal null}.
|
||||
* @see org.springframework.data.gemfire.repository.query.GemfireQueryMethod
|
||||
*/
|
||||
public GemfireRepositoryQuery(GemfireQueryMethod queryMethod) {
|
||||
@@ -59,23 +62,36 @@ public abstract class GemfireRepositoryQuery implements RepositoryQuery {
|
||||
this.queryMethod = queryMethod;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
|
||||
/**
|
||||
* Returns a reference to the {@link Repository} {@link GemfireQueryMethod} modeling the Apache Geode OQL query.
|
||||
*
|
||||
* @return a reference to the {@link Repository} {@link GemfireQueryMethod} modeling the Apache Geode OQL query.
|
||||
* @see org.springframework.data.gemfire.repository.query.GemfireQueryMethod
|
||||
* @see #getQueryMethod()
|
||||
*/
|
||||
protected @NonNull GemfireQueryMethod getGemfireQueryMethod() {
|
||||
return (GemfireQueryMethod) getQueryMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the {@link Repository} {@link QueryMethod} modeling the data store query.
|
||||
*
|
||||
* @return a reference to the {@link Repository} {@link QueryMethod} modeling the data store query.
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
*/
|
||||
@Override
|
||||
public QueryMethod getQueryMethod() {
|
||||
public @NonNull QueryMethod getQueryMethod() {
|
||||
return this.queryMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the composed {@link QueryPostProcessor QueryPostProcessors}, which are applied
|
||||
* to {@literal OQL queries} prior to execution.
|
||||
* Returns a reference to the composed {@link QueryPostProcessor QueryPostProcessors} that are applied to
|
||||
* {@literal OQL queries} prior to execution.
|
||||
*
|
||||
* @return a reference to the composed {@link QueryPostProcessor QueryPostProcessors}.
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryPostProcessor
|
||||
*/
|
||||
protected QueryPostProcessor<?, String> getQueryPostProcessor() {
|
||||
protected @NonNull QueryPostProcessor<Repository, String> getQueryPostProcessor() {
|
||||
return this.queryPostProcessor;
|
||||
}
|
||||
|
||||
@@ -92,11 +108,12 @@ public abstract class GemfireRepositoryQuery implements RepositoryQuery {
|
||||
* @return this {@link GemfireRepositoryQuery}.
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryPostProcessor#processBefore(QueryPostProcessor)
|
||||
*/
|
||||
public GemfireRepositoryQuery register(QueryPostProcessor<?, String> queryPostProcessor) {
|
||||
public GemfireRepositoryQuery register(@Nullable QueryPostProcessor<Repository, String> queryPostProcessor) {
|
||||
this.queryPostProcessor = this.queryPostProcessor.processBefore(queryPostProcessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
enum ProvidedQueryPostProcessor implements QueryPostProcessor<Repository, String> {
|
||||
|
||||
IDENTITY {
|
||||
|
||||
@@ -22,58 +22,90 @@ 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.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link GemfireRepositoryQuery} backed by a {@link PartTree}, deriving an OQL query
|
||||
* from the backing query method's name/signature.
|
||||
* from the backing {@link QueryMethod QueryMethod's} name/signature.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author John Blum
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see org.springframework.data.gemfire.repository.query.GemfireRepositoryQuery
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery
|
||||
* @see org.springframework.data.repository.query.parser.Part
|
||||
* @see org.springframework.data.repository.query.parser.PartTree
|
||||
*/
|
||||
public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
|
||||
private final GemfireQueryMethod method;
|
||||
|
||||
private final GemfireTemplate template;
|
||||
|
||||
private final PartTree tree;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PartTreeGemfireRepositoryQuery} using the given {@link GemfireQueryMethod} and
|
||||
* {@link GemfireTemplate}.
|
||||
* Constructs a new instance of {@link PartTreeGemfireRepositoryQuery} initialized with
|
||||
* the given {@link GemfireQueryMethod} and {@link GemfireTemplate}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param template must not be {@literal null}.
|
||||
* @param queryMethod {@link GemfireQueryMethod} implementing the {@link RepositoryQuery};
|
||||
* must not be {@literal null}.
|
||||
* @param template {@link GemfireTemplate} used to execute {@literal QOL queries};
|
||||
* must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link GemfireQueryMethod} or {@link GemfireTemplate} are {@literal null}.
|
||||
* @see org.springframework.data.gemfire.repository.query.GemfireQueryMethod
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
*/
|
||||
public PartTreeGemfireRepositoryQuery(GemfireQueryMethod method, GemfireTemplate template) {
|
||||
public PartTreeGemfireRepositoryQuery(GemfireQueryMethod queryMethod, GemfireTemplate template) {
|
||||
|
||||
super(method);
|
||||
super(queryMethod);
|
||||
|
||||
Class<?> domainClass = method.getEntityInformation().getJavaType();
|
||||
Assert.notNull(template, "GemfireTemplate must not be null");
|
||||
|
||||
this.method = method;
|
||||
this.template = template;
|
||||
this.tree = new PartTree(method.getName(), domainClass);
|
||||
this.tree = new PartTree(queryMethod.getName(), queryMethod.getEntityInformation().getJavaType());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
|
||||
/**
|
||||
* Returns a {@link PartTree} object consisting of the parts of the (OQL) query.
|
||||
*
|
||||
* @return a {@link PartTree} object consisting of the parts of the (OQL) query.
|
||||
* @see org.springframework.data.repository.query.parser.PartTree
|
||||
*/
|
||||
protected @NonNull PartTree getPartTree() {
|
||||
return this.tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @NonNull GemfireTemplate getTemplate() {
|
||||
return this.template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Object[] arguments) {
|
||||
|
||||
QueryString query = createQuery(this.method, this.tree, arguments);
|
||||
GemfireQueryMethod queryMethod = getGemfireQueryMethod();
|
||||
|
||||
GemfireRepositoryQuery repositoryQuery = newRepositoryQuery(query, this.method, this.template);
|
||||
QueryString query = newQueryString(queryMethod, getPartTree(), arguments);
|
||||
|
||||
GemfireRepositoryQuery repositoryQuery = newRepositoryQuery(query, queryMethod, getTemplate());
|
||||
|
||||
return repositoryQuery.execute(prepareStringParameters(arguments));
|
||||
}
|
||||
|
||||
private QueryString createQuery(GemfireQueryMethod queryMethod, PartTree tree, Object[] arguments) {
|
||||
private QueryString newQueryString(GemfireQueryMethod queryMethod, PartTree tree, Object[] arguments) {
|
||||
|
||||
ParametersParameterAccessor parameterAccessor =
|
||||
new ParametersParameterAccessor(queryMethod.getParameters(), arguments);
|
||||
@@ -86,17 +118,18 @@ public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
private GemfireRepositoryQuery newRepositoryQuery(QueryString query,
|
||||
GemfireQueryMethod queryMethod, GemfireTemplate template) {
|
||||
|
||||
GemfireRepositoryQuery repositoryQuery =
|
||||
StringBasedGemfireRepositoryQuery repositoryQuery =
|
||||
new StringBasedGemfireRepositoryQuery(query.toString(), queryMethod, template);
|
||||
|
||||
repositoryQuery.register(getQueryPostProcessor());
|
||||
repositoryQuery.asDerivedQuery();
|
||||
|
||||
return repositoryQuery;
|
||||
}
|
||||
|
||||
private Object[] prepareStringParameters(Object[] parameters) {
|
||||
|
||||
Iterator<Part> partsIterator = this.tree.getParts().iterator();
|
||||
Iterator<Part> partsIterator = getPartTree().getParts().iterator();
|
||||
|
||||
List<Object> stringParameters = new ArrayList<>(parameters.length);
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import org.apache.geode.cache.Region;
|
||||
@@ -21,6 +20,7 @@ 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;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -39,14 +39,16 @@ class QueryBuilder {
|
||||
|
||||
private final String query;
|
||||
|
||||
/* (non-Javadoc) */
|
||||
static String asQuery(GemfirePersistentEntity<?> entity, PartTree tree) {
|
||||
|
||||
return String.format(SELECT_OQL_TEMPLATE, tree.isDistinct() ? OqlKeyword.DISTINCT : "",
|
||||
entity.getRegionName(), DEFAULT_ALIAS).replaceAll("\\s{2,}", " ");
|
||||
String distinctKeyword = tree.isDistinct() ? OqlKeyword.DISTINCT.toString() : "";
|
||||
String regionName = entity.getRegionName();
|
||||
String query = String.format(SELECT_OQL_TEMPLATE, distinctKeyword, regionName, DEFAULT_ALIAS)
|
||||
.replaceAll("\\s{2,}", " "); // single space tokens
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
static String validateQuery(String query) {
|
||||
Assert.hasText(query, "Query is required");
|
||||
return query;
|
||||
@@ -85,7 +87,7 @@ class QueryBuilder {
|
||||
* @see org.springframework.data.gemfire.repository.query.Predicate
|
||||
* @see #withPredicate(String, Predicate)
|
||||
*/
|
||||
public QueryString create(Predicate predicate) {
|
||||
public QueryString create(@Nullable Predicate predicate) {
|
||||
return new QueryString(withPredicate(this.query, predicate));
|
||||
}
|
||||
|
||||
@@ -98,16 +100,15 @@ class QueryBuilder {
|
||||
* or just a {@link String} containing the query if the {@link Predicate} is {@literal null}.
|
||||
* @see org.springframework.data.gemfire.repository.query.Predicate
|
||||
*/
|
||||
protected String withPredicate(String query, Predicate predicate) {
|
||||
protected String withPredicate(String query, @Nullable Predicate predicate) {
|
||||
|
||||
return predicate != null
|
||||
? String.format(WHERE_CLAUSE_TEMPLATE, query, predicate.toString(DEFAULT_ALIAS))
|
||||
: query;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.gemfire.repository.query;
|
||||
|
||||
import java.util.Properties;
|
||||
@@ -31,24 +30,28 @@ import org.springframework.lang.Nullable;
|
||||
* a given {@link QUERY query} and possibly return a new or modified version of the same {@link QUERY query}.
|
||||
*
|
||||
* {@link QueryPostProcessor QueryPostProcessors} are useful for handling and processing {@link QUERY queries}
|
||||
* generated from {@link Repository} {@link QueryMethod query methods}, and give a developer an opportunity,
|
||||
* derived from {@link Repository} {@link QueryMethod QueryMethods}, and give a developer the opportunity,
|
||||
* via the callback, to further process the generated {@link QUERY query}.
|
||||
*
|
||||
* {@link QueryPostProcessor QueryPostProcessors} can be used on both {@literal generated} {@link QUERY queries}
|
||||
* {@link QueryPostProcessor QueryPostProcessors} can be used on both {@literal derived} {@link QUERY queries}
|
||||
* and {@literal manual} {@link QUERY queries}. {@literal Manual} {@link QUERY queries} are defined as
|
||||
* {@link QUERY queries} specified using SDG's {@link Query @Query} annotation or by defining a {@literal named}
|
||||
* {@link QUERY query} in a module-specific {@link Properties} files.
|
||||
* {@link QUERY query} in a module-specific {@link Properties} files
|
||||
* (e.g. {@literal META-INF/gemfire-named-queries.properties}).
|
||||
*
|
||||
* @author John Blum
|
||||
* @param <T> {@link Class type} identifying the {@link Repository Repositories} to match on during registration.
|
||||
* @param <QUERY> {@link Class type} of the query to process.
|
||||
* @see java.lang.FunctionalInterface
|
||||
* @see org.springframework.core.Ordered
|
||||
* @see org.springframework.data.gemfire.repository.Query
|
||||
* @see org.springframework.data.repository.Repository
|
||||
* @see org.springframework.data.repository.core.NamedQueries
|
||||
* @see org.springframework.data.repository.query.QueryMethod
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@SuppressWarnings("rawtypes")
|
||||
public interface QueryPostProcessor<T extends Repository, QUERY> extends Ordered {
|
||||
|
||||
Object[] EMPTY_ARGUMENTS = new Object[0];
|
||||
@@ -102,10 +105,11 @@ public interface QueryPostProcessor<T extends Repository, QUERY> extends Ordered
|
||||
QUERY postProcess(@NonNull QueryMethod queryMethod, QUERY query, Object... arguments);
|
||||
|
||||
/**
|
||||
* Builder method used to compose, or combine this {@link QueryPostProcessor QueryPostProcessors}
|
||||
* Builder method used to compose this {@link QueryPostProcessor QueryPostProcessor}
|
||||
* with the given {@link QueryPostProcessor}.
|
||||
*
|
||||
* This {@link QueryPostProcessor} will come before the given {@link QueryPostProcessor} in the processing chain.
|
||||
* This {@link QueryPostProcessor} will process the query before the given {@link QueryPostProcessor}
|
||||
* in the processing chain.
|
||||
*
|
||||
* @param queryPostProcessor {@link QueryPostProcessor} to compose with this {@link QueryPostProcessor}.
|
||||
* @return a composed {@link QueryPostProcessor} consisting of this {@link QueryPostProcessor}
|
||||
@@ -113,17 +117,17 @@ public interface QueryPostProcessor<T extends Repository, QUERY> extends Ordered
|
||||
* if the given {@link QueryPostProcessor} is {@literal null}.
|
||||
* @see #processAfter(QueryPostProcessor)
|
||||
*/
|
||||
@NonNull
|
||||
default QueryPostProcessor<?, QUERY> processBefore(@Nullable QueryPostProcessor<?, QUERY> queryPostProcessor) {
|
||||
default @NonNull QueryPostProcessor<T, QUERY> processBefore(@Nullable QueryPostProcessor<T, QUERY> queryPostProcessor) {
|
||||
return queryPostProcessor == null ? this : (queryMethod, query, arguments) ->
|
||||
queryPostProcessor.postProcess(queryMethod, this.postProcess(queryMethod, query, arguments), arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to compose, or combine this {@link QueryPostProcessor QueryPostProcessors}
|
||||
* Builder method used to compose this {@link QueryPostProcessor QueryPostProcessors}
|
||||
* with the given {@link QueryPostProcessor}.
|
||||
*
|
||||
* This {@link QueryPostProcessor} will come after the given {@link QueryPostProcessor} in the processing chain.
|
||||
* This {@link QueryPostProcessor} will process the query after the given {@link QueryPostProcessor}
|
||||
* in the processing chain.
|
||||
*
|
||||
* @param queryPostProcessor {@link QueryPostProcessor} to compose with this {@link QueryPostProcessor}.
|
||||
* @return a composed {@link QueryPostProcessor} consisting of the given {@link QueryPostProcessor}
|
||||
@@ -131,8 +135,7 @@ public interface QueryPostProcessor<T extends Repository, QUERY> extends Ordered
|
||||
* if the given {@link QueryPostProcessor} is {@literal null}.
|
||||
* @see #processBefore(QueryPostProcessor)
|
||||
*/
|
||||
@NonNull
|
||||
default QueryPostProcessor<?, QUERY> processAfter(@Nullable QueryPostProcessor<?, QUERY> queryPostProcessor) {
|
||||
default @NonNull QueryPostProcessor<T, QUERY> processAfter(@Nullable QueryPostProcessor<T, QUERY> queryPostProcessor) {
|
||||
return queryPostProcessor == null ? this : (queryMethod, query, arguments) ->
|
||||
this.postProcess(queryMethod, queryPostProcessor.postProcess(queryMethod, query, arguments), arguments);
|
||||
}
|
||||
|
||||
@@ -13,11 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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;
|
||||
@@ -28,20 +25,26 @@ import org.apache.geode.cache.Region;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.gemfire.repository.query.support.OqlKeyword;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link QueryString} is a utility class used to construct GemFire OQL query statements.
|
||||
* {@link QueryString} is a utility class used to construct Apache Geode OQL query statements.
|
||||
*
|
||||
* This is an internal class used by the SDG {@link Repository} infrastructure extension
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
* @author John Blum
|
||||
* @see java.util.regex.Matcher
|
||||
* @see java.util.regex.Pattern
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.data.domain.Sort
|
||||
* @see org.springframework.data.gemfire.repository.query.support.OqlKeyword
|
||||
* @see org.apache.geode.cache.Region
|
||||
*/
|
||||
public class QueryString {
|
||||
|
||||
@@ -67,37 +70,39 @@ public class QueryString {
|
||||
private static final String STAR_QUERY = "*";
|
||||
|
||||
/**
|
||||
* Factory method used to construct an instance of {@link QueryString} initialized with the given {@link String query}.
|
||||
* Factory method used to construct a new instance of {@link QueryString} initialized with
|
||||
* the given {@link String OQL 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) {
|
||||
public static QueryString of(@NonNull String query) {
|
||||
return new QueryString(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Factory method used to construct a new instance of {@link QueryString} initialized with
|
||||
* the given {@link Class domain type} for which the {@link String OQL 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}.
|
||||
* @param domainType {@link Class application domain model type} for which the {@link String OQL query}
|
||||
* will be created.
|
||||
* @return a new {@link QueryString} for the given {@link Class application domain model type}.
|
||||
* @throws IllegalArgumentException if {@link Class application domain model type} is {@literal null}.
|
||||
* @see #QueryString(Class)
|
||||
*/
|
||||
public static QueryString from(Class<?> domainType) {
|
||||
public static QueryString from(@NonNull 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.
|
||||
* Factory method used to construct a new instance of {@link QueryString} that creates an {@link String OQL query}
|
||||
* counting objects of the specified {@link Class application domain model type}.
|
||||
*
|
||||
* @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}.
|
||||
* @param domainType {@link Class application domain model type} for which the OQL query will be created.
|
||||
* @return a new count {@link QueryString}.
|
||||
* @throws IllegalArgumentException if {@link Class application domain model type} is {@literal null}.
|
||||
* @see #QueryString(Class)
|
||||
*/
|
||||
public static QueryString count(Class<?> domainType) {
|
||||
@@ -125,37 +130,37 @@ public class QueryString {
|
||||
* 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.
|
||||
* @throws IllegalArgumentException if {@link String query} is {@literal null} or {@literal empty}.
|
||||
* @see #validateQuery(String)
|
||||
* @see java.lang.String
|
||||
*/
|
||||
public QueryString(String query) {
|
||||
public QueryString(@NonNull String query) {
|
||||
this.query = validateQuery(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Constructs a new instance of {@link QueryString} initialized with the given
|
||||
* {@link Class application domain model type}, which is used to construct an OQL {@literal SELECT} query statement.
|
||||
*
|
||||
* @param domainType {@link Class application domain type} to query; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link Class domain type} is {@literal null}.
|
||||
* @param domainType {@link Class application domain model type} to query; must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if the {@link Class application domain model type} is {@literal null}.
|
||||
* @see #QueryString(Class, boolean)
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public QueryString(Class<?> domainType) {
|
||||
public QueryString(@NonNull Class<?> domainType) {
|
||||
this(domainType, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Constructs a new instance of {@link QueryString} initialized with the given
|
||||
* {@link Class application domain model type}, which is used to construct an OQL {@literal SELECT} query statement.
|
||||
*
|
||||
* {@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}.
|
||||
* of the objects for the given {@link Class applicatlion domain model 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}.
|
||||
* @param domainType {@link Class application domain model type} to query; must not be {@literal null}.
|
||||
* @param asCountQuery boolean value to indicate if this is a select count query.
|
||||
* @throws IllegalArgumentException if the {@link Class application domain model type} is {@literal null}.
|
||||
* @see #asQuery(Class, boolean)
|
||||
* @see #QueryString(String)
|
||||
*/
|
||||
@@ -201,7 +206,7 @@ public class QueryString {
|
||||
*/
|
||||
public QueryString bindIn(Collection<?> values) {
|
||||
|
||||
if (!nullSafeIsEmpty(values)) {
|
||||
if (!CollectionUtils.nullSafeIsEmpty(values)) {
|
||||
return QueryString.of(this.query.replaceFirst(IN_PATTERN, String.format("(%s)",
|
||||
StringUtils.collectionToDelimitedString(values, ", ", "'", "'"))));
|
||||
}
|
||||
@@ -213,7 +218,7 @@ public class QueryString {
|
||||
* Replaces the {@link Class domain classes} referenced inside the current {@link String query}
|
||||
* with the given {@link Region}.
|
||||
*
|
||||
* @param domainClass {@link Class type} of the persistent entity to query; must not be {@literal null}.
|
||||
* @param domainType {@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}.
|
||||
@@ -221,7 +226,7 @@ public class QueryString {
|
||||
* @see java.lang.Class
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public QueryString fromRegion(Class<?> domainClass, Region<?, ?> region) {
|
||||
public QueryString fromRegion(Class<?> domainType, Region<?, ?> region) {
|
||||
return QueryString.of(this.query.replaceAll(REGION_PATTERN, region.getFullPath()));
|
||||
}
|
||||
|
||||
@@ -284,7 +289,7 @@ public class QueryString {
|
||||
* @param hints array of {@link String Strings} containing query hints.
|
||||
* @return a new {@link QueryString} if hints are not null or empty, or return this {@link QueryString}.
|
||||
*/
|
||||
public QueryString withHints(String... hints) {
|
||||
public QueryString withHints(@NonNull String... hints) {
|
||||
|
||||
if (!ObjectUtils.isEmpty(hints)) {
|
||||
|
||||
@@ -307,9 +312,11 @@ public class QueryString {
|
||||
* @param importExpression {@link String} containing the import clause.
|
||||
* @return a new {@link QueryString} if an import was declared, or return this {@link QueryString}.
|
||||
*/
|
||||
public QueryString withImport(String importExpression) {
|
||||
return StringUtils.hasText(importExpression) ?
|
||||
QueryString.of(String.format(IMPORT_OQL_TEMPLATE, importExpression, this.query)) : this;
|
||||
public QueryString withImport(@NonNull String importExpression) {
|
||||
|
||||
return StringUtils.hasText(importExpression)
|
||||
? QueryString.of(String.format(IMPORT_OQL_TEMPLATE, importExpression, this.query))
|
||||
: this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,8 +325,11 @@ public class QueryString {
|
||||
* @param limit {@link Integer} indicating the number of results to return from the query.
|
||||
* @return a new {@link QueryString} if a limit was specified, or return this {@link QueryString}.
|
||||
*/
|
||||
public QueryString withLimit(Integer limit) {
|
||||
return limit != null ? QueryString.of(String.format(LIMIT_OQL_TEMPLATE, this.query, limit)) : this;
|
||||
public QueryString withLimit(@NonNull Integer limit) {
|
||||
|
||||
return limit != null
|
||||
? QueryString.of(String.format(LIMIT_OQL_TEMPLATE, this.query, limit))
|
||||
: this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
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;
|
||||
@@ -25,13 +24,14 @@ import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.gemfire.repository.Query;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
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.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link GemfireRepositoryQuery} using plain {@link String} based OQL queries.
|
||||
@@ -40,18 +40,18 @@ import org.springframework.util.StringUtils;
|
||||
* @author David Turanski
|
||||
* @author John Blum
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
|
||||
private static final String INVALID_QUERY = "Paging and modifying queries are not supported";
|
||||
private static final String INVALID_QUERY = "Modifying queries are not supported";
|
||||
|
||||
private boolean userDefinedQuery = false;
|
||||
private volatile boolean userDefinedQuery = false;
|
||||
|
||||
private final GemfireTemplate template;
|
||||
|
||||
private final QueryString query;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
/**
|
||||
* Constructor used for testing purposes only!
|
||||
*/
|
||||
StringBasedGemfireRepositoryQuery() {
|
||||
@@ -66,33 +66,29 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringBasedGemfireRepositoryQuery} using the given {@link GemfireQueryMethod} and
|
||||
* {@link GemfireTemplate}. The actual query {@link String} will be looked up from the query method.
|
||||
* Constructs a new instance of {@link StringBasedGemfireRepositoryQuery} initialized with
|
||||
* the given {@link String query}, {@link GemfireQueryMethod} and {@link GemfireTemplate}.
|
||||
*
|
||||
* @param queryMethod must not be {@literal null}.
|
||||
* @param template must not be {@literal null}.
|
||||
*/
|
||||
public StringBasedGemfireRepositoryQuery(GemfireQueryMethod queryMethod, GemfireTemplate template) {
|
||||
this(queryMethod.getAnnotatedQuery(), queryMethod, template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringBasedGemfireRepositoryQuery} using the given query {@link String},
|
||||
* {@link GemfireQueryMethod} and {@link GemfireTemplate}.
|
||||
*
|
||||
* @param query will fall back to the query annotated to the given {@link GemfireQueryMethod} if {@literal null}.
|
||||
* @param queryMethod must not be {@literal null}.
|
||||
* @param template must not be {@literal null}.
|
||||
* @param query {@link String} containing the {@literal OQL query} to execute;
|
||||
* must not be {@literal null} or empty.
|
||||
* @param queryMethod {@link GemfireQueryMethod} implementing the {@link RepositoryQuery};
|
||||
* must not be {@literal null}.
|
||||
* @param template {@link GemfireTemplate} used to execute {@literal QOL queries};
|
||||
* must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if {@link GemfireQueryMethod} or {@link GemfireTemplate} are {@literal null}.
|
||||
* @throws IllegalStateException if the {@link GemfireQueryMethod} represents a modifying query.
|
||||
* @see org.springframework.data.gemfire.repository.query.GemfireQueryMethod
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
*/
|
||||
public StringBasedGemfireRepositoryQuery(String query, GemfireQueryMethod queryMethod, GemfireTemplate template) {
|
||||
|
||||
super(queryMethod);
|
||||
|
||||
Assert.hasText(query, "Query must not be null or empty");
|
||||
Assert.notNull(template, "GemfireTemplate must not be null");
|
||||
Assert.state(!(queryMethod.isModifyingQuery() || queryMethod.isPageQuery()), INVALID_QUERY);
|
||||
Assert.state(!queryMethod.isModifyingQuery(), INVALID_QUERY);
|
||||
|
||||
this.userDefinedQuery |= !StringUtils.hasText(query);
|
||||
this.query = QueryString.of(StringUtils.hasText(query) ? query : queryMethod.getAnnotatedQuery());
|
||||
this.query = QueryString.of(query);
|
||||
this.template = template;
|
||||
|
||||
register(ProvidedQueryPostProcessors.LIMIT
|
||||
@@ -102,21 +98,53 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this {@link RepositoryQuery} to be user-defined.
|
||||
* Builder method used to set this {@link RepositoryQuery} as derived.
|
||||
*
|
||||
* @return a boolean value indicating whether the OQL query was derived from the {@link Repository} infrastructure
|
||||
* {@link QueryMethod} name/signature.
|
||||
* @see #asUserDefinedQuery()
|
||||
*/
|
||||
public @NonNull StringBasedGemfireRepositoryQuery asDerivedQuery() {
|
||||
this.userDefinedQuery = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder method used to set this {@link RepositoryQuery} as user-defined.
|
||||
*
|
||||
* @return this {@link RepositoryQuery}.
|
||||
* @see #isUserDefinedQuery()
|
||||
*/
|
||||
public StringBasedGemfireRepositoryQuery asUserDefinedQuery() {
|
||||
public @NonNull StringBasedGemfireRepositoryQuery asUserDefinedQuery() {
|
||||
this.userDefinedQuery = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the query represented by this {@link RepositoryQuery} is user-defined or was generated by
|
||||
* the Spring Data {@link Repository} infrastructure.
|
||||
* Determines whether the OQL query represented by this {@link RepositoryQuery} is derived from
|
||||
* the {@link Repository} infrastructure {@link QueryMethod} name/signature conventions.
|
||||
*
|
||||
* @return a boolean value indicating whether this {@link RepositoryQuery} is user-defined or was generated by
|
||||
* the Spring Data {@link Repository} infrastructure.
|
||||
* @return a boolean value indicating if the OQL query represented by this {@link RepositoryQuery} is derived.
|
||||
* @see #asDerivedQuery()
|
||||
* @see #isUserDefinedQuery()
|
||||
*/
|
||||
public boolean isDerivedQuery() {
|
||||
return !isUserDefinedQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the OQL query represented by this {@link RepositoryQuery} is user-defined
|
||||
* or was generated by the Spring Data {@link Repository} infrastructure.
|
||||
*
|
||||
* An {@literal OQL query} is user-defined if the query was specified using the {@link Query} annotation on
|
||||
* the {@link Repository} {@link QueryMethod} or was specified in the {@literal <module>-named-queries.properties}
|
||||
* file.
|
||||
*
|
||||
* Derived queries are not user-defined.
|
||||
*
|
||||
* @return a boolean value indicating whether the OQL query represented this {@link RepositoryQuery} is user-defined.
|
||||
* @see #asUserDefinedQuery()
|
||||
* @see #isDerivedQuery()
|
||||
*/
|
||||
public boolean isUserDefinedQuery() {
|
||||
return this.userDefinedQuery;
|
||||
@@ -128,7 +156,7 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
* @return a reference to the {@link QueryString managed query}.
|
||||
* @see org.springframework.data.gemfire.repository.query.QueryString
|
||||
*/
|
||||
protected QueryString getQuery() {
|
||||
protected @NonNull QueryString getQuery() {
|
||||
return this.query;
|
||||
}
|
||||
|
||||
@@ -138,13 +166,12 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
* @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() {
|
||||
protected @NonNull GemfireTemplate getTemplate() {
|
||||
return this.template;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Object[] arguments) {
|
||||
@@ -209,7 +236,9 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
|
||||
|
||||
methodReturnType = methodReturnType != null ? methodReturnType : Void.class;
|
||||
|
||||
return nullSafeSize(result) == 1 && !Void.TYPE.equals(methodReturnType) && !method.isCollectionQuery();
|
||||
return CollectionUtils.nullSafeSize(result) == 1
|
||||
&& !Void.TYPE.equals(methodReturnType)
|
||||
&& !method.isCollectionQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -101,10 +101,9 @@ public enum OqlKeyword {
|
||||
* @see #name()
|
||||
*/
|
||||
public String getKeyword() {
|
||||
return (StringUtils.hasText(this.keyword) ? this.keyword : name());
|
||||
return StringUtils.hasText(this.keyword) ? this.keyword : name();
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@Override
|
||||
public String toString() {
|
||||
return getKeyword();
|
||||
|
||||
@@ -179,7 +179,7 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
return (GemfirePersistentEntity<T>) getMappingContext().getPersistentEntity(domainType);
|
||||
}
|
||||
|
||||
private Region<?, ?> resolveRegion(RepositoryMetadata repositoryMetadata, GemfirePersistentEntity entity) {
|
||||
private Region<?, ?> resolveRegion(RepositoryMetadata repositoryMetadata, GemfirePersistentEntity<?> entity) {
|
||||
|
||||
String resolvedRegionName = getRepositoryRegionName(repositoryMetadata)
|
||||
.orElseGet(() -> getEntityRegionName(repositoryMetadata, entity));
|
||||
@@ -187,31 +187,10 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
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) {
|
||||
@Nullable GemfirePersistentEntity<?> entity) {
|
||||
|
||||
Optional<GemfirePersistentEntity> optionalEntity = Optional.ofNullable(entity);
|
||||
Optional<GemfirePersistentEntity<?>> optionalEntity = Optional.ofNullable(entity);
|
||||
|
||||
return optionalEntity
|
||||
.map(GemfirePersistentEntity::getRegionName)
|
||||
@@ -237,15 +216,30 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
return Optional.ofNullable(getRegions().getRegion(regionNamePath))
|
||||
.orElseThrow(() -> newIllegalStateException(REGION_NOT_FOUND,
|
||||
regionNamePath, repositoryMetadata.getDomainType().getName(),
|
||||
repositoryMetadata.getRepositoryInterface().getName()));
|
||||
repositoryMetadata.getRepositoryInterface().getName()));
|
||||
}
|
||||
|
||||
private Region<?, ?> validate(RepositoryMetadata repositoryMetadata, GemfirePersistentEntity<?> entity,
|
||||
Region<?, ?> region) {
|
||||
|
||||
Assert.notNull(region, "Region must not be null");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport
|
||||
* #getQueryLookupStrategy(Key, EvaluationContextProvider)
|
||||
*/
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
@@ -258,18 +252,17 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
GemfireTemplate template = newTemplate(repositoryMetadata);
|
||||
|
||||
if (queryMethod.hasAnnotatedQuery()) {
|
||||
return new StringBasedGemfireRepositoryQuery(queryMethod, template).asUserDefinedQuery();
|
||||
}
|
||||
|
||||
String namedQueryName = queryMethod.getNamedQueryName();
|
||||
|
||||
if (namedQueries.hasQuery(namedQueryName)) {
|
||||
return new StringBasedGemfireRepositoryQuery(namedQueries.getQuery(namedQueryName),
|
||||
queryMethod, template).asUserDefinedQuery();
|
||||
}
|
||||
// NOTE: Named OQL queries from gemfire-named-queries.properties take precedence over
|
||||
// OQL queries declared in @Query annotated on Repository query methods.
|
||||
String query = namedQueries.hasQuery(namedQueryName) ? namedQueries.getQuery(namedQueryName)
|
||||
: queryMethod.hasAnnotatedQuery() ? queryMethod.getAnnotatedQuery()
|
||||
: null;
|
||||
|
||||
return new PartTreeGemfireRepositoryQuery(queryMethod, template);
|
||||
return StringUtils.hasText(query)
|
||||
? new StringBasedGemfireRepositoryQuery(query, queryMethod, template).asUserDefinedQuery()
|
||||
: new PartTreeGemfireRepositoryQuery(queryMethod, template); // derived query
|
||||
});
|
||||
}
|
||||
|
||||
@@ -277,6 +270,7 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport {
|
||||
protected <T extends QueryMethod> T newQueryMethod(Method method, RepositoryMetadata repositoryMetadata,
|
||||
ProjectionFactory projectionFactory, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
return (T) new GemfireQueryMethod(method, repositoryMetadata, projectionFactory, getMappingContext());
|
||||
return (T) new GemfireQueryMethod(method, repositoryMetadata, projectionFactory, getMappingContext(),
|
||||
evaluationContextProvider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,9 +331,9 @@ public class GemfireRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
|
||||
return this.declaredRepositoryType;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NonNull QueryPostProcessor<?, String> getQueryPostProcessor() {
|
||||
return (QueryPostProcessor<?, String>) this.queryPostProcessor;
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@NonNull QueryPostProcessor<Repository, String> getQueryPostProcessor() {
|
||||
return (QueryPostProcessor<Repository, String>) this.queryPostProcessor;
|
||||
}
|
||||
|
||||
boolean isMatch(Class<?> repositoryInterface) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
import org.apache.geode.cache.CacheTransactionManager;
|
||||
@@ -30,6 +31,9 @@ import org.apache.geode.cache.DataPolicy;
|
||||
import org.apache.geode.cache.Region;
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.gemfire.GemfireCallback;
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
@@ -39,6 +43,7 @@ import org.springframework.data.gemfire.repository.query.QueryString;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.data.gemfire.util.SpringUtils;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.data.util.Streamable;
|
||||
@@ -50,7 +55,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Simple, basic {@link CrudRepository} implementation for Apache Geode.
|
||||
* Simple, basic {@link PagingAndSortingRepository} / {@link CrudRepository} implementation for Apache Geode.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author David Turanski
|
||||
@@ -61,6 +66,7 @@ import org.slf4j.LoggerFactory;
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see org.springframework.data.gemfire.repository.GemfireRepository
|
||||
* @see org.springframework.data.repository.CrudRepository
|
||||
* @see org.springframework.data.repository.PagingAndSortingRepository
|
||||
* @see org.springframework.data.repository.core.EntityInformation
|
||||
*/
|
||||
public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID> {
|
||||
@@ -228,6 +234,16 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
return toList(selectResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<T> findAll(@NonNull Pageable pageable) {
|
||||
|
||||
Assert.notNull(pageable, "Pageable must not be null");
|
||||
|
||||
Iterable<T> results = findAll(pageable.getSort());
|
||||
|
||||
return toPage(results, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Iterable<T> findAll(@NonNull Sort sort) {
|
||||
|
||||
@@ -324,10 +340,41 @@ public class SimpleGemfireRepository<T, ID> implements GemfireRepository<T, ID>
|
||||
region.removeAll(region.keySet());
|
||||
}
|
||||
|
||||
@NonNull List<T> toList(@Nullable Iterable<T> iterable) {
|
||||
|
||||
return iterable instanceof List ? (List<T>) iterable
|
||||
: StreamSupport.stream(CollectionUtils.nullSafeIterable(iterable).spliterator(), false)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@NonNull List<T> toList(@Nullable SelectResults<T> selectResults) {
|
||||
|
||||
return selectResults != null
|
||||
? CollectionUtils.nullSafeList(selectResults.asList())
|
||||
: Collections.emptyList();
|
||||
}
|
||||
|
||||
@NonNull Page<T> toPage(@Nullable Iterable<T> iterable, @NonNull Pageable pageable) {
|
||||
|
||||
Assert.notNull(pageable, "Pageable must not be null");
|
||||
|
||||
int pageNumber = pageable.getPageNumber();
|
||||
int pageSize = pageable.getPageSize();
|
||||
|
||||
Assert.isTrue(pageNumber >= 0, () -> String.format("Page Number [%d] must be greater than equal to 0",
|
||||
pageNumber));
|
||||
|
||||
Assert.isTrue(pageSize > 0, () -> String.format("Page Size [%d] must be greater than equal to 1",
|
||||
pageSize));
|
||||
|
||||
int startIndex = pageNumber * pageSize;
|
||||
int endIndex = startIndex + pageSize;
|
||||
|
||||
List<T> results = toList(iterable);
|
||||
|
||||
int total = results.size();
|
||||
|
||||
return results.isEmpty() || total <= startIndex ? Page.empty()
|
||||
: new PageImpl<>(results.subList(startIndex, Math.min(total, endIndex)), pageable, total);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +51,12 @@ import org.springframework.util.ObjectUtils;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class GemfireQueryMethodUnitTests {
|
||||
|
||||
private GemfireMappingContext context = new GemfireMappingContext();
|
||||
private GemfireMappingContext mappingContext = new GemfireMappingContext();
|
||||
|
||||
private ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
|
||||
private ProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
|
||||
@Mock
|
||||
private RepositoryMetadata metadata;
|
||||
private RepositoryMetadata repositoryMetadata;
|
||||
|
||||
protected void assertQueryHints(GemfireQueryMethod queryMethod, String... expectedHints) {
|
||||
|
||||
@@ -114,149 +114,138 @@ public class GemfireQueryMethodUnitTests {
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
doReturn(Person.class).when(this.metadata).getDomainType();
|
||||
doReturn(Person.class).when(this.metadata).getReturnedDomainClass(any(Method.class));
|
||||
doReturn(ClassTypeInformation.from(Object.class)).when(this.metadata).getReturnType(any(Method.class));
|
||||
doReturn(Person.class).when(this.repositoryMetadata).getDomainType();
|
||||
doReturn(Person.class).when(this.repositoryMetadata).getReturnedDomainClass(any(Method.class));
|
||||
doReturn(ClassTypeInformation.from(Object.class)).when(this.repositoryMetadata).getReturnType(any(Method.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsAnnotatedQueryCorrectly() throws Exception {
|
||||
|
||||
GemfireQueryMethod method =
|
||||
new GemfireQueryMethod(Sample.class.getMethod("annotated"), this.metadata, this.factory, this.context);
|
||||
new GemfireQueryMethod(Sample.class.getMethod("annotated"),
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext);
|
||||
|
||||
assertThat(method.hasAnnotatedQuery()).isTrue();
|
||||
assertThat(method.getAnnotatedQuery()).isEqualTo("foo");
|
||||
|
||||
method = new GemfireQueryMethod(Sample.class.getMethod("annotatedButEmpty"), this.metadata, this.factory, this.context);
|
||||
method = new GemfireQueryMethod(Sample.class.getMethod("annotatedButEmpty"),
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext);
|
||||
|
||||
assertThat(method.hasAnnotatedQuery()).isFalse();
|
||||
assertThat(method.getAnnotatedQuery()).isNull();
|
||||
|
||||
method = new GemfireQueryMethod(Sample.class.getMethod("notAnnotated"), this.metadata, this.factory, this.context);
|
||||
method = new GemfireQueryMethod(Sample.class.getMethod("notAnnotated"),
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext);
|
||||
|
||||
assertThat(method.hasAnnotatedQuery()).isFalse();
|
||||
assertThat(method.getAnnotatedQuery()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://jira.spring.io/browse/SGF-112
|
||||
*/
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void rejectsQueryMethodWithPageableParameter() throws Exception {
|
||||
|
||||
try {
|
||||
new GemfireQueryMethod(Invalid.class.getMethod("someMethod", Pageable.class), this.metadata, this.factory, this.context);
|
||||
}
|
||||
catch (IllegalStateException expected) {
|
||||
|
||||
assertThat(expected)
|
||||
.hasMessageStartingWith("Pagination is not supported by GemFire Repositories; Offending method: someMethod");
|
||||
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
@Test
|
||||
public void acceptsQueryMethodWithPageableParameter() throws Exception {
|
||||
new GemfireQueryMethod(PageablePojo.class.getMethod("pageableMethod", Pageable.class),
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsQueryHintsCorrectly() throws Exception {
|
||||
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"),
|
||||
this.metadata, this.factory, this.context).hasHint()).isTrue();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasHint()).isTrue();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"),
|
||||
this.metadata, this.factory, this.context).hasHint()).isFalse();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasHint()).isFalse();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"),
|
||||
this.metadata, this.factory, this.context).hasHint()).isTrue();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasHint()).isTrue();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("unlimitedQuery"),
|
||||
this.metadata, this.factory, this.context).hasHint()).isFalse();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasHint()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsQueryImportsCorrectly() throws Exception {
|
||||
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"),
|
||||
this.metadata, this.factory, this.context).hasImport()).isFalse();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasImport()).isFalse();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"),
|
||||
this.metadata, this.factory, this.context).hasImport()).isTrue();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasImport()).isTrue();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"),
|
||||
this.metadata, this.factory, this.context).hasImport()).isTrue();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasImport()).isTrue();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("unlimitedQuery"),
|
||||
this.metadata, this.factory, this.context).hasImport()).isFalse();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasImport()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsQueryLimitsCorrectly() throws Exception {
|
||||
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"),
|
||||
this.metadata, this.factory, this.context).hasLimit()).isFalse();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasLimit()).isFalse();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"),
|
||||
this.metadata, this.factory, this.context).hasLimit()).isFalse();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasLimit()).isFalse();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"),
|
||||
this.metadata, this.factory, this.context).hasLimit()).isTrue();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasLimit()).isTrue();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("unlimitedQuery"),
|
||||
this.metadata, this.factory, this.context).hasLimit()).isFalse();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasLimit()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsQueryTracingCorrectly() throws Exception {
|
||||
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"),
|
||||
this.metadata, this.factory, this.context).hasTrace()).isTrue();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasTrace()).isTrue();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"),
|
||||
this.metadata, this.factory, this.context).hasTrace()).isFalse();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasTrace()).isFalse();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"),
|
||||
this.metadata, this.factory, this.context).hasTrace()).isFalse();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasTrace()).isFalse();
|
||||
assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("unlimitedQuery"),
|
||||
this.metadata, this.factory, this.context).hasTrace()).isTrue();
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext).hasTrace()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hintOnQueryWithHint() throws Exception {
|
||||
|
||||
assertQueryHints(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"),
|
||||
this.metadata, this.factory, this.context), "IdIdx", "LastNameIdx");
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext), "IdIdx", "LastNameIdx");
|
||||
|
||||
assertQueryHints(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"),
|
||||
this.metadata, this.factory, this.context), "BirthDateIdx");
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext), "BirthDateIdx");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hintOnQueryWithNoHints() throws Exception {
|
||||
|
||||
assertNoQueryHints(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"),
|
||||
this.metadata, this.factory, this.context));
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importOnQueryWithImport() throws Exception {
|
||||
|
||||
assertImportStatement(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"),
|
||||
this.metadata, this.factory, this.context), "org.example.app.domain.ExampleType");
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext), "org.example.app.domain.ExampleType");
|
||||
|
||||
assertImportStatement(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"),
|
||||
this.metadata, this.factory, this.context), "org.example.app.domain.Person");
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext), "org.example.app.domain.Person");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importOnQueryWithNoImports() throws Exception {
|
||||
|
||||
assertNoImportStatement(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"),
|
||||
this.metadata, this.factory, this.context));
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void limitOnQueryWithLimit() throws Exception {
|
||||
|
||||
assertLimitedQuery(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"),
|
||||
this.metadata, this.factory, this.context), 1024);
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext), 1024);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void limitOnQueryWithNoLimits() throws Exception {
|
||||
|
||||
assertUnlimitedQuery(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("unlimitedQuery"),
|
||||
this.metadata, this.factory, this.context));
|
||||
this.repositoryMetadata, this.projectionFactory, this.mappingContext));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -273,9 +262,9 @@ public class GemfireQueryMethodUnitTests {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
interface Invalid {
|
||||
interface PageablePojo {
|
||||
|
||||
Page<?> someMethod(Pageable pageable);
|
||||
Page<?> pageableMethod(Pageable pageable);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* 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;
|
||||
@@ -28,10 +27,11 @@ import static org.mockito.Mockito.when;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link QueryPostProcessor}.
|
||||
* Unit Tests for {@link QueryPostProcessor}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
@@ -42,15 +42,15 @@ import org.springframework.data.repository.query.QueryMethod;
|
||||
public class QueryPostProcessorUnitTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings({ "rawtypes", "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);
|
||||
QueryPostProcessor<Repository, String> mockQueryPostProcessorOne = mock(QueryPostProcessor.class);
|
||||
QueryPostProcessor<Repository, String> mockQueryPostProcessorTwo = mock(QueryPostProcessor.class);
|
||||
|
||||
when(mockQueryPostProcessorOne.processAfter(any())).thenCallRealMethod();
|
||||
when(mockQueryPostProcessorOne.postProcess(any(QueryMethod.class), anyString(), any())).thenReturn(query);
|
||||
@@ -82,15 +82,15 @@ public class QueryPostProcessorUnitTests {
|
||||
assertThat(mockQueryPostProcessor.processAfter(null)).isSameAs(mockQueryPostProcessor);
|
||||
}
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
@SuppressWarnings({ "rawtypes", "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);
|
||||
QueryPostProcessor<Repository, String> mockQueryPostProcessorOne = mock(QueryPostProcessor.class);
|
||||
QueryPostProcessor<Repository, String> mockQueryPostProcessorTwo = mock(QueryPostProcessor.class);
|
||||
|
||||
when(mockQueryPostProcessorOne.processBefore(any())).thenCallRealMethod();
|
||||
when(mockQueryPostProcessorOne.postProcess(any(QueryMethod.class), anyString(), any())).thenReturn(query);
|
||||
|
||||
@@ -26,11 +26,13 @@ import org.springframework.data.gemfire.mapping.annotation.Region;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* The Person class models a person.
|
||||
* Abstract Data Type (ADT) modeling a Person.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author John Blum
|
||||
* @see java.io.Serializable
|
||||
* @see org.springframework.data.annotation.Id
|
||||
* @see org.springframework.data.gemfire.mapping.annotation.Region
|
||||
*/
|
||||
@Region("simple")
|
||||
@JsonIgnoreProperties("name")
|
||||
@@ -47,8 +49,7 @@ public class Person implements Serializable {
|
||||
public String lastname;
|
||||
|
||||
@PersistenceConstructor
|
||||
public Person() {
|
||||
}
|
||||
public Person() { }
|
||||
|
||||
public Person(Long id) {
|
||||
this.id = id;
|
||||
@@ -113,10 +114,6 @@ public class Person implements Serializable {
|
||||
return String.format("%1$s %2$s", getFirstname(), getLastname());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
@@ -133,10 +130,6 @@ public class Person implements Serializable {
|
||||
return (this.id != null && this.id.equals(that.id));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ObjectUtils.nullSafeHashCode(this.id);
|
||||
@@ -146,5 +139,4 @@ public class Person implements Serializable {
|
||||
public String toString() {
|
||||
return String.format("{ @type = %1$s, id = %2$d, name = %3$s }", getClass().getName(), id, getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -469,26 +469,14 @@ public class GemfireRepositoryFactoryUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @link <a href="https://jira.spring.io/browse/SGF-112">Repositories should reject PagingAndSortingRepository and Pageable parameters</a>
|
||||
*/
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void rejectsInterfacesExtendingPagingAndSortingRepository() {
|
||||
public void acceptsInterfacesExtendingPagingAndSortingRepository() {
|
||||
|
||||
GemfireRepositoryFactory repositoryFactory =
|
||||
new GemfireRepositoryFactory(Collections.singletonList(this.mockRegion), new GemfireMappingContext());
|
||||
|
||||
try {
|
||||
repositoryFactory.getRepository(SamplePagingAndSortingRepository.class);
|
||||
}
|
||||
catch (IllegalStateException expected) {
|
||||
|
||||
assertThat(expected).hasMessageStartingWith("Pagination is not supported by GemFire Repositories");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
repositoryFactory.getRepository(SamplePagingAndSortingRepository.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@@ -34,6 +35,9 @@ import org.apache.geode.cache.util.CacheListenerAdapter;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.gemfire.LocalRegionFactoryBean;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheApplication;
|
||||
@@ -105,6 +109,52 @@ public class SimpleGemfireRepositoryIntegrationTests {
|
||||
assertThat(this.regionClearListener.eventFired).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAllPaged() {
|
||||
|
||||
assertThat(this.repository.count()).isEqualTo(0);
|
||||
|
||||
List<Person> people = Arrays.asList(
|
||||
new Person(1L, "Jon", "Doe"),
|
||||
new Person(2L, "Jane", "Doe"),
|
||||
new Person(3L, "Cookie", "Doe"),
|
||||
new Person(4L, "Pie", "Doe"),
|
||||
new Person(5L, "Sour", "Doe")
|
||||
);
|
||||
|
||||
people.forEach(person -> this.template.put(person.getId(), person));
|
||||
|
||||
assertThat(this.repository.count()).isEqualTo(5);
|
||||
|
||||
Sort orderByFirstNameAscending = Sort.by("firstname").ascending();
|
||||
|
||||
Page<Person> pageOne =
|
||||
this.repository.findAll(PageRequest.of(0, 3, orderByFirstNameAscending));
|
||||
|
||||
assertThat(pageOne).isNotNull();
|
||||
assertThat(pageOne).isNotEmpty();
|
||||
assertThat(pageOne.getNumber()).isEqualTo(0);
|
||||
assertThat(pageOne.getNumberOfElements()).isEqualTo(3);
|
||||
assertThat(pageOne.getSize()).isEqualTo(3);
|
||||
assertThat(pageOne.getSort()).isEqualTo(orderByFirstNameAscending);
|
||||
assertThat(pageOne.getTotalElements()).isEqualTo(people.size());
|
||||
assertThat(pageOne.getTotalPages()).isEqualTo(2);
|
||||
assertThat(pageOne.getContent()).containsExactly(people.get(2), people.get(1), people.get(0));
|
||||
|
||||
Page<Person> pageTwo =
|
||||
this.repository.findAll(PageRequest.of(1, 3, Sort.by("firstname").ascending()));
|
||||
|
||||
assertThat(pageTwo).isNotNull();
|
||||
assertThat(pageTwo).isNotEmpty();
|
||||
assertThat(pageTwo.getNumber()).isEqualTo(1);
|
||||
assertThat(pageTwo.getNumberOfElements()).isEqualTo(2);
|
||||
assertThat(pageTwo.getSize()).isEqualTo(3);
|
||||
assertThat(pageTwo.getSort()).isEqualTo(orderByFirstNameAscending);
|
||||
assertThat(pageTwo.getTotalElements()).isEqualTo(people.size());
|
||||
assertThat(pageTwo.getTotalPages()).isEqualTo(2);
|
||||
assertThat(pageTwo.getContent()).containsExactly(people.get(3), people.get(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAllWithIds() {
|
||||
|
||||
|
||||
@@ -57,15 +57,27 @@ import org.apache.geode.cache.Region;
|
||||
import org.apache.geode.cache.RegionAttributes;
|
||||
import org.apache.geode.cache.query.SelectResults;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.gemfire.GemfireTemplate;
|
||||
import org.springframework.data.gemfire.repository.Wrapper;
|
||||
import org.springframework.data.gemfire.repository.sample.Animal;
|
||||
import org.springframework.data.gemfire.repository.sample.Identifiable;
|
||||
import org.springframework.data.gemfire.util.CollectionUtils;
|
||||
import org.springframework.data.gemfire.util.RegionUtils;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link SimpleGemfireRepository}.
|
||||
*
|
||||
@@ -76,6 +88,9 @@ import org.slf4j.Logger;
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.apache.geode.cache.Cache
|
||||
* @see org.apache.geode.cache.Region
|
||||
* @see org.springframework.data.domain.Page
|
||||
* @see org.springframework.data.domain.Pageable
|
||||
* @see org.springframework.data.domain.Sort
|
||||
* @see org.springframework.data.gemfire.GemfireTemplate
|
||||
* @see org.springframework.data.gemfire.repository.Wrapper
|
||||
* @see org.springframework.data.gemfire.repository.support.SimpleGemfireRepository
|
||||
@@ -87,11 +102,11 @@ public class SimpleGemfireRepositoryUnitTests {
|
||||
|
||||
private final AtomicLong idSequence = new AtomicLong(0L);
|
||||
|
||||
private Map<Long, Animal> asMap(Iterable<Animal> animals) {
|
||||
private @NonNull Map<Long, Animal> asMap(@Nullable Iterable<Animal> animals) {
|
||||
|
||||
Map<Long, Animal> animalMap = new HashMap<>();
|
||||
|
||||
for (Animal animal : animals) {
|
||||
for (Animal animal : CollectionUtils.nullSafeIterable(animals)) {
|
||||
animalMap.put(animal.getId(), animal);
|
||||
}
|
||||
|
||||
@@ -132,8 +147,8 @@ public class SimpleGemfireRepositoryUnitTests {
|
||||
CacheTransactionManager mockCacheTransactionManager = mock(CacheTransactionManager.class,
|
||||
String.format("%s.MockCacheTransactionManager", name));
|
||||
|
||||
when(mockCache.getCacheTransactionManager()).thenReturn(mockCacheTransactionManager);
|
||||
when(mockCacheTransactionManager.exists()).thenReturn(transactionExists);
|
||||
doReturn(mockCacheTransactionManager).when(mockCache).getCacheTransactionManager();
|
||||
doReturn(transactionExists).when(mockCacheTransactionManager).exists();
|
||||
|
||||
return mockCache;
|
||||
}
|
||||
@@ -155,7 +170,7 @@ public class SimpleGemfireRepositoryUnitTests {
|
||||
}
|
||||
|
||||
private Long resolveId(Long id) {
|
||||
return (id != null ? id : idSequence.incrementAndGet());
|
||||
return id != null ? id : idSequence.incrementAndGet();
|
||||
}
|
||||
|
||||
}).when(mockEntityInformation).getRequiredId(any(Animal.class));
|
||||
@@ -171,8 +186,8 @@ public class SimpleGemfireRepositoryUnitTests {
|
||||
|
||||
Region mockRegion = mock(Region.class, String.format("%s.MockRegion", name));
|
||||
|
||||
when(mockRegion.getName()).thenReturn(name);
|
||||
when(mockRegion.getFullPath()).thenReturn(String.format("%1$s%2$s", Region.SEPARATOR, name));
|
||||
doReturn(name).when(mockRegion).getName();
|
||||
doReturn(RegionUtils.toRegionPath(name)).when(mockRegion).getFullPath();
|
||||
|
||||
return mockRegion;
|
||||
}
|
||||
@@ -181,13 +196,13 @@ public class SimpleGemfireRepositoryUnitTests {
|
||||
|
||||
Region mockRegion = mockRegion(name);
|
||||
|
||||
when(mockRegion.getRegionService()).thenReturn(mockCache);
|
||||
doReturn(mockCache).when(mockRegion).getRegionService();
|
||||
|
||||
RegionAttributes mockRegionAttributes = mock(RegionAttributes.class,
|
||||
String.format("%s.MockRegionAttributes", name));
|
||||
|
||||
when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes);
|
||||
when(mockRegionAttributes.getDataPolicy()).thenReturn(dataPolicy);
|
||||
doReturn(mockRegionAttributes).when(mockRegion).getAttributes();
|
||||
doReturn(dataPolicy).when(mockRegionAttributes).getDataPolicy();
|
||||
|
||||
return mockRegion;
|
||||
}
|
||||
@@ -598,6 +613,96 @@ public class SimpleGemfireRepositoryUnitTests {
|
||||
verifyNoMoreInteractions(mockRegion, mockSelectResults, mockTemplate);
|
||||
}
|
||||
|
||||
// Page Numbers 0 based indexed
|
||||
private void assertPage(Page page, int pageNumber, int pageSize, int total, Sort orderBy, User... content) {
|
||||
|
||||
int totalPages = (total / pageSize) + (total % pageSize > 0 ? 1 : 0);
|
||||
|
||||
assertThat(page).isNotNull();
|
||||
assertThat(page).isNotEmpty();
|
||||
assertThat(page.getNumber()).isEqualTo(pageNumber);
|
||||
assertThat(page.getNumberOfElements()).isEqualTo(content.length);
|
||||
assertThat(page.getSize()).isEqualTo(pageSize);
|
||||
assertThat(page.getSort()).isEqualTo(orderBy);
|
||||
assertThat(page.getTotalElements()).isEqualTo(total);
|
||||
assertThat(page.getTotalPages()).isEqualTo(totalPages);
|
||||
assertThat(page.getContent()).containsExactly(content);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAllPagedSuccessfully() {
|
||||
|
||||
Sort orderBy = Sort.by("name");
|
||||
|
||||
Region mockRegion = mockRegion();
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(true).when(mockPageable).isPaged();
|
||||
doReturn(0).when(mockPageable).getPageNumber(); // page 1
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
doReturn(orderBy).when(mockPageable).getSort();
|
||||
|
||||
EntityInformation mockEntityInformation = mockEntityInformation();
|
||||
|
||||
SimpleGemfireRepository repository =
|
||||
spy(new SimpleGemfireRepository(newGemfireTemplate(mockRegion), mockEntityInformation));
|
||||
|
||||
List<User> users = Arrays.asList(
|
||||
User.newUser("Jon Doe"),
|
||||
User.newUser("Jane Doe"),
|
||||
User.newUser("Cookie Doe"),
|
||||
User.newUser("Fro Doe"),
|
||||
User.newUser("Lan Doe"),
|
||||
User.newUser("Pie Doe"),
|
||||
User.newUser("Sour Doe")
|
||||
);
|
||||
|
||||
doReturn(users).when(repository).findAll(eq(orderBy));
|
||||
|
||||
Page pageOne = repository.findAll(mockPageable);
|
||||
|
||||
assertPage(pageOne, 0, 5, users.size(), orderBy,
|
||||
User.newUser("Jon Doe"),
|
||||
User.newUser("Jane Doe"),
|
||||
User.newUser("Cookie Doe"),
|
||||
User.newUser("Fro Doe"),
|
||||
User.newUser("Lan Doe")
|
||||
);
|
||||
|
||||
doReturn(1).when(mockPageable).getPageNumber(); // page 2
|
||||
|
||||
Page pageTwo = repository.findAll(mockPageable);
|
||||
|
||||
assertPage(pageTwo, 1, 5, users.size(), orderBy,
|
||||
User.newUser("Pie Doe"),
|
||||
User.newUser("Sour Doe")
|
||||
);
|
||||
|
||||
doReturn(2).when(mockPageable).getPageNumber(); // page 2
|
||||
|
||||
Page pageThree = repository.findAll(mockPageable);
|
||||
|
||||
assertThat(pageThree).isNotNull();
|
||||
assertThat(pageThree).isEmpty();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void findAllPagedWithNullPageableThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mockEntityInformation())
|
||||
.findAll((Pageable) null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Pageable must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAllByIdSuccessfully() {
|
||||
|
||||
@@ -873,4 +978,238 @@ public class SimpleGemfireRepositoryUnitTests {
|
||||
verify(mockRegion, times(0)).clear();
|
||||
verify(mockRegion, times(1)).removeAll(eq(keys));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toListFromIterable() {
|
||||
|
||||
Set<User> userSet = CollectionUtils.asSet(User.newUser("Jon Doe"), User.newUser("Jane Doe"));
|
||||
|
||||
List<User> userList = new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mockEntityInformation())
|
||||
.toList((Iterable) userSet);
|
||||
|
||||
assertThat(userList).isNotNull();
|
||||
assertThat(userList).isNotSameAs(userSet);
|
||||
assertThat(userList).hasSize(userSet.size());
|
||||
assertThat(userList).containsExactly(userSet.toArray(new User[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toListFromEmptyIterable() {
|
||||
|
||||
List<User> users = new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mockEntityInformation())
|
||||
.toList((Iterable) Collections::emptyIterator);
|
||||
|
||||
assertThat(users).isNotNull();
|
||||
assertThat(users).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toListFromList() {
|
||||
|
||||
List<User> users = Collections.singletonList(User.newUser("Jon Doe"));
|
||||
|
||||
List<User> userList = new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mockEntityInformation())
|
||||
.toList((Iterable) users);
|
||||
|
||||
assertThat(userList).isSameAs(users);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toListFromNullIterableIsNullSafe() {
|
||||
|
||||
List<User> users = new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mockEntityInformation())
|
||||
.toList((Iterable) null);
|
||||
|
||||
assertThat(users).isNotNull();
|
||||
assertThat(users).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toListFromSelectResults() {
|
||||
|
||||
List<User> users = Arrays.asList(User.newUser("Jon Doe"), User.newUser("Jane Doe"));
|
||||
|
||||
SelectResults<User> mockSelectResults = mock(SelectResults.class);
|
||||
|
||||
doReturn(users).when(mockSelectResults).asList();
|
||||
|
||||
List<User> userList = new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mockEntityInformation())
|
||||
.toList((SelectResults) mockSelectResults);
|
||||
|
||||
assertThat(userList).isSameAs(users);
|
||||
|
||||
verify(mockSelectResults, times(1)).asList();
|
||||
verifyNoMoreInteractions(mockSelectResults);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toListFromNullSelectResultsIsNullSafe() {
|
||||
|
||||
List<User> users = new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mockEntityInformation())
|
||||
.toList((SelectResults) null);
|
||||
|
||||
assertThat(users).isNotNull();
|
||||
assertThat(users).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toPageFromIterable() {
|
||||
|
||||
List<User> users = Arrays.asList(User.newUser("Jon Doe"), User.newUser("Jane Doe"));
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(true).when(mockPageable).isPaged();
|
||||
doReturn(0).when(mockPageable).getPageNumber(); // page 1
|
||||
doReturn(1).when(mockPageable).getPageSize();
|
||||
|
||||
SimpleGemfireRepository repository =
|
||||
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mock(EntityInformation.class));
|
||||
|
||||
Page<User> pageOne = repository.toPage(users, mockPageable);
|
||||
|
||||
assertPage(pageOne, 0, 1, 2, null, users.get(0));
|
||||
|
||||
doReturn(1).when(mockPageable).getPageNumber(); // page 2
|
||||
|
||||
Page<User> pageTwo = repository.toPage(users, mockPageable);
|
||||
|
||||
assertPage(pageTwo, 1, 1, 2, null, users.get(1));
|
||||
|
||||
doReturn(2).when(mockPageable).getPageNumber(); // page 3
|
||||
|
||||
Page<User> pageThree = repository.toPage(users, mockPageable);
|
||||
|
||||
assertThat(pageThree).isNotNull();
|
||||
assertThat(pageThree).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toPageFromEmptyIterable() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(0).when(mockPageable).getPageNumber();
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
|
||||
Page<User> page = new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mock(EntityInformation.class))
|
||||
.toPage(Collections.emptyList(), mockPageable);
|
||||
|
||||
assertThat(page).isNotNull();
|
||||
assertThat(page).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toPageFromNullIterableIsNullSafe() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(0).when(mockPageable).getPageNumber();
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
|
||||
Page<User> page = new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mock(EntityInformation.class))
|
||||
.toPage(null, mockPageable);
|
||||
|
||||
assertThat(page).isNotNull();
|
||||
assertThat(page).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toPageWithOverflowFromIterable() {
|
||||
|
||||
List<User> users = Arrays.asList(User.newUser("Jon Doe"), User.newUser("Jane Doe"));
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(true).when(mockPageable).isPaged();
|
||||
doReturn(0).when(mockPageable).getPageNumber(); // page 1
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
|
||||
SimpleGemfireRepository repository =
|
||||
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mock(EntityInformation.class));
|
||||
|
||||
Page<User> pageOne = repository.toPage(users, mockPageable);
|
||||
|
||||
assertPage(pageOne, 0, 5, 2, null, users.toArray(new User[0]));
|
||||
|
||||
doReturn(1).when(mockPageable).getPageNumber(); // page 2
|
||||
|
||||
Page<User> pageTwo = repository.toPage(users, mockPageable);
|
||||
|
||||
assertThat(pageTwo).isNotNull();
|
||||
assertThat(pageTwo).isEmpty();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void toPageWithNullPageableThrowsIllegalArgumentException() {
|
||||
|
||||
try {
|
||||
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mock(EntityInformation.class))
|
||||
.toPage(Collections.emptyList(), null);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Pageable must not be null");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void toPageWithInvalidPageNumberThrowsIllegalArgumentException() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(-1).when(mockPageable).getPageNumber();
|
||||
doReturn(5).when(mockPageable).getPageSize();
|
||||
|
||||
try {
|
||||
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mock(EntityInformation.class))
|
||||
.toPage(Collections.singletonList(User.newUser("Jon Doe")), mockPageable);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Page Number [-1] must be greater than equal to 0");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void toPageWithInvalidPageSizeThrowsIllegalArgumentException() {
|
||||
|
||||
Pageable mockPageable = mock(Pageable.class);
|
||||
|
||||
doReturn(1).when(mockPageable).getPageNumber();
|
||||
doReturn(-5).when(mockPageable).getPageSize();
|
||||
|
||||
try {
|
||||
new SimpleGemfireRepository<>(newGemfireTemplate(mockRegion()), mock(EntityInformation.class))
|
||||
.toPage(Collections.singletonList(User.newUser("Jon Doe")), mockPageable);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Page Size [-5] must be greater than equal to 1");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString(of = "name")
|
||||
@EqualsAndHashCode(of = "name")
|
||||
@RequiredArgsConstructor(staticName = "newUser")
|
||||
@org.springframework.data.gemfire.mapping.annotation.Region("Users")
|
||||
static class User {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
@NonNull
|
||||
final String name;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user