SGF-713 - Override generated OQL from Repository query methods.

This commit is contained in:
John Blum
2018-03-09 14:40:56 -08:00
parent 46a35837bf
commit 0e3ec2a124
10 changed files with 1031 additions and 109 deletions

View File

@@ -0,0 +1,157 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.data.gemfire.repository.query;
import java.util.Properties;
import org.springframework.core.Ordered;
import org.springframework.data.gemfire.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.util.Assert;
/**
* The {@link AbstractQueryPostProcessor} class is an abstract base class for simplifying the implementation
* of {@link QueryPostProcessor QueryPostProcessors}.
*
* {@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,
* via the callback, to further process the generated {@link QUERY query}.
*
* {@link QueryPostProcessor QueryPostProcessors} can be used on both {@literal generated} {@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.
*
* @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 org.springframework.core.Ordered
* @see org.springframework.data.repository.Repository
* @see org.springframework.data.repository.query.QueryMethod
* @since 2.1.0
*/
public abstract class AbstractQueryPostProcessor<T extends Repository, QUERY> implements QueryPostProcessor<T, QUERY> {
protected static final Object[] EMPTY_ARRAY = {};
/**
* Defines the {@link Integer order} of this {@link QueryPostProcessor} relative to
* other {@link QueryPostProcessor QueryPostProcessors} in a sort.
*
* Defaults to the {@link Ordered#LOWEST_PRECEDENCE}.
*
* @return an {@link Integer} value specifying the order of this {@link QueryPostProcessor} relative to
* other {@link QueryPostProcessor QueryPostProcessors} in a sort.
* @see org.springframework.core.Ordered#getOrder()
*/
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
/**
* Callback method invoked by the Spring Data (SD) {@link Repository} framework to allow the user to process
* the given {@link QUERY query} and (possibly) return a new or modified version of the {@link QUERY query}.
*
* This callback is invoked for {@literal queries} generated from a SD {@link Repository} {@link QueryMethod}
* signature as well as {@literal queries} specified and defined in {@link NamedQueries},
* or even using SDG's {@link Query @Query} annotation.
*
* @param query {@link QUERY query} to process.
* @return a new or modified version of the same {@link QUERY query}.
* @see org.springframework.data.repository.query.QueryMethod
* @see #postProcess(QueryMethod, Object, Object...)
*/
@Override
public QUERY postProcess(QueryMethod queryMethod, QUERY query) {
return postProcess(queryMethod, query, EMPTY_ARRAY);
}
/**
* Builder method used to compose, or combine this {@link QueryPostProcessor QueryPostProcessors}
* with the given {@link QueryPostProcessor}.
*
* This {@link QueryPostProcessor} will come 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}
* followed by the given {@link QueryPostProcessor}. Returns this {@link QueryPostProcessor}
* if the given {@link QueryPostProcessor} is {@literal null}.
* @see #processAfter(QueryPostProcessor)
*/
public QueryPostProcessor<?, QUERY> processBefore(QueryPostProcessor<?, QUERY> queryPostProcessor) {
return ComposableQueryPostProcessor.compose(this, queryPostProcessor);
}
/**
* Builder method used to compose, or combine this {@link QueryPostProcessor} with
* the given {@link QueryPostProcessor}.
*
* This {@link QueryPostProcessor} will come 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}
* followed by this {@link QueryPostProcessor}. Returns this {@link QueryPostProcessor}
* if the given {@link QueryPostProcessor} is {@literal null}.
* @see #processBefore(QueryPostProcessor)
*/
public QueryPostProcessor<?, QUERY> processAfter(QueryPostProcessor<?, QUERY> queryPostProcessor) {
return ComposableQueryPostProcessor.compose(queryPostProcessor, this);
}
protected static class ComposableQueryPostProcessor<T extends Repository, QUERY>
extends AbstractQueryPostProcessor<T, QUERY> {
@SuppressWarnings("unchecked")
protected static <QUERY> QueryPostProcessor<?, QUERY> compose(
QueryPostProcessor<?, QUERY> first, QueryPostProcessor<?, QUERY> second) {
return first == null ? second : second == null ? first
: new ComposableQueryPostProcessor<Repository, QUERY>(first, second);
}
private final QueryPostProcessor<?, QUERY> first;
private final QueryPostProcessor<?, QUERY> second;
protected ComposableQueryPostProcessor(QueryPostProcessor<?, QUERY> left, QueryPostProcessor<?, QUERY> second) {
Assert.notNull(left, "The first QueryPostProcessor operand must not be null");
Assert.notNull(second, "The second QueryPostProcessor operand must not be null");
this.first = left;
this.second = second;
}
protected QueryPostProcessor<?, QUERY> getFirst() {
return this.first;
}
protected QueryPostProcessor<?, QUERY> getSecond() {
return this.second;
}
@Override
public QUERY postProcess(QueryMethod queryMethod, QUERY query, Object... arguments) {
return getSecond().postProcess(queryMethod,
getFirst().postProcess(queryMethod, query, arguments), arguments);
}
}
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.gemfire.repository.query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.util.Assert;
@@ -28,10 +29,12 @@ import org.springframework.util.Assert;
* @see org.springframework.data.gemfire.repository.query.GemfireQueryMethod
* @see org.springframework.data.repository.query.RepositoryQuery
*/
abstract class GemfireRepositoryQuery implements RepositoryQuery {
public abstract class GemfireRepositoryQuery implements RepositoryQuery {
private final GemfireQueryMethod queryMethod;
private QueryPostProcessor<?, String> queryPostProcessor = ProvidedQueryPostProcessor.INSTANCE;
/*
* (non-Javadoc)
* Constructor used for testing purposes only!
@@ -42,15 +45,15 @@ abstract class GemfireRepositoryQuery implements RepositoryQuery {
/**
* Creates a new {@link GemfireRepositoryQuery} using the given {@link GemfireQueryMethod}.
*
*
* @param queryMethod must not be {@literal null}.
*/
public GemfireRepositoryQuery(GemfireQueryMethod queryMethod) {
Assert.notNull(queryMethod);
Assert.notNull(queryMethod, "QueryMethod must not be null");
this.queryMethod = queryMethod;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
*/
@@ -59,4 +62,42 @@ abstract class GemfireRepositoryQuery implements RepositoryQuery {
return this.queryMethod;
}
/**
* Returns a reference to the composed {@link QueryPostProcessor QueryPostProcessors}, which 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() {
return this.queryPostProcessor;
}
/**
* Registers the given {@link QueryPostProcessor} to use for processing {@literal OQL queries}
* generated from {@link Repository} {@link QueryMethod query methods}.
*
* Registration always links the given {@link QueryPostProcessor} to the end of the processing chain
* of previously registered {@link QueryPostProcessor QueryPostProcessors}. In other words, the given
* {@link QueryPostProcessor} argument will process {@literal OQL queries} only after all
* {@link QueryPostProcessor QueryPostProcessor} registered before it.
*
* @param queryPostProcessor {@link QueryPostProcessor} to register.
* @return this {@link GemfireRepositoryQuery}.
* @see org.springframework.data.gemfire.repository.query.QueryPostProcessor#processBefore(QueryPostProcessor)
*/
public GemfireRepositoryQuery register(QueryPostProcessor<?, String> queryPostProcessor) {
this.queryPostProcessor = this.queryPostProcessor.processBefore(queryPostProcessor);
return this;
}
static class ProvidedQueryPostProcessor extends AbstractQueryPostProcessor<Repository, String> {
static final ProvidedQueryPostProcessor INSTANCE = new ProvidedQueryPostProcessor();
@Override
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
return query;
}
}
}

View File

@@ -22,14 +22,13 @@ import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.gemfire.GemfireTemplate;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
/**
* {@link GemfireRepositoryQuery} backed by a {@link PartTree} and thus, deriving an OQL query from the backing query
* method's name.
*
*
* @author Oliver Gierke
*/
public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
@@ -41,7 +40,7 @@ public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
/**
* Creates a new {@link PartTreeGemfireRepositoryQuery} using the given {@link GemfireQueryMethod} and
* {@link GemfireTemplate}.
*
*
* @param method must not be {@literal null}.
* @param template must not be {@literal null}.
*/
@@ -56,24 +55,39 @@ public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
this.template = template;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
*/
@Override
public Object execute(Object[] parameters) {
ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(method.getParameters(), parameters);
QueryString query = new GemfireQueryCreator(tree, method.getPersistentEntity())
ParametersParameterAccessor parameterAccessor =
new ParametersParameterAccessor(this.method.getParameters(), parameters);
QueryString query = new GemfireQueryCreator(this.tree, this.method.getPersistentEntity())
.createQuery(parameterAccessor.getSort());
RepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery(query.toString(), method, template);
GemfireRepositoryQuery repositoryQuery = newRepositoryQuery(query, this.method, this.template);
return repositoryQuery.execute(prepareStringParameters(parameters));
}
private GemfireRepositoryQuery newRepositoryQuery(QueryString query,
GemfireQueryMethod queryMethod, GemfireTemplate template) {
GemfireRepositoryQuery repositoryQuery =
new StringBasedGemfireRepositoryQuery(query.toString(), queryMethod, template);
repositoryQuery.register(getQueryPostProcessor());
return repositoryQuery;
}
private Object[] prepareStringParameters(Object[] parameters) {
Iterator<Part> partsIterator = tree.getParts().iterator();
List<Object> stringParameters = new ArrayList<Object>(parameters.length);
for (Object parameter : parameters) {
@@ -99,5 +113,4 @@ public class PartTreeGemfireRepositoryQuery extends GemfireRepositoryQuery {
return stringParameters.toArray();
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.data.gemfire.repository.query;
import java.util.Properties;
import org.springframework.core.Ordered;
import org.springframework.data.gemfire.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.query.QueryMethod;
/**
* The {@link QueryPostProcessor} interface defines a contract for implementations to post process
* 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,
* via the callback, to further process the generated {@link QUERY query}.
*
* {@link QueryPostProcessor QueryPostProcessors} can be used on both generated {@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.
*
* @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 org.springframework.core.Ordered
* @see org.springframework.data.gemfire.repository.Query
* @see org.springframework.data.repository.Repository
* @see org.springframework.data.repository.query.QueryMethod
* @since 2.1.0
*/
public interface QueryPostProcessor<T extends Repository, QUERY> extends Ordered {
/**
* Callback method invoked by the Spring Data (SD) {@link Repository} framework to allow the user to process
* the given {@link QUERY query} and (possibly) return a new or modified version of the {@link QUERY query}.
*
* This callback is invoked for {@literal queries} generated from a SD {@link Repository} {@link QueryMethod}
* signature as well as {@literal queries} specified and defined in {@link NamedQueries},
* or even using SDG's {@link Query @Query} annotation.
*
* @param query {@link QUERY query} to process.
* @return a new or modified version of the same {@link QUERY query}.
* @see org.springframework.data.repository.query.QueryMethod
* @see #postProcess(QueryMethod, Object, Object...)
*/
QUERY postProcess(QueryMethod queryMethod, QUERY query);
/**
* Callback method invoked by the Spring Data (SD) {@link Repository} framework to allow the user to process
* the given {@link QUERY query} and (possibly) return a new or modified version of the {@link QUERY query}.
*
* This callback is invoked for {@literal queries} generated from a SD {@link Repository} {@link QueryMethod}
* signature as well as {@literal queries} specified and defined in {@link NamedQueries},
* or even using SDG's {@link Query @Query} annotation.
*
* @param query {@link QUERY query} to process.
* @param arguments array of {@link Object Objects} containing the arguments to the query parameters.
* @return a new or modified version of the same {@link QUERY query}.
* @see org.springframework.data.repository.query.QueryMethod
* @see #postProcess(QueryMethod, Object)
*/
QUERY postProcess(QueryMethod queryMethod, QUERY query, Object... arguments);
/**
* Builder method used to compose, or combine this {@link QueryPostProcessor QueryPostProcessors}
* with the given {@link QueryPostProcessor}.
*
* This {@link QueryPostProcessor} will come 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}
* followed by the given {@link QueryPostProcessor}. Returns this {@link QueryPostProcessor}
* if the given {@link QueryPostProcessor} is {@literal null}.
* @see #processAfter(QueryPostProcessor)
*/
QueryPostProcessor<?, QUERY> processBefore(QueryPostProcessor<?, QUERY> queryPostProcessor);
/**
* Builder method used to compose, or combine this {@link QueryPostProcessor} with
* the given {@link QueryPostProcessor}.
*
* This {@link QueryPostProcessor} will come 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}
* followed by this {@link QueryPostProcessor}. Returns this {@link QueryPostProcessor}
* if the given {@link QueryPostProcessor} is {@literal null}.
* @see #processBefore(QueryPostProcessor)
*/
QueryPostProcessor<?, QUERY> processAfter(QueryPostProcessor<?, QUERY> queryPostProcessor);
}

View File

@@ -63,6 +63,11 @@ public class QueryString {
validateDomainType(domainType).getSimpleName());
}
/* (non-Javadoc) */
static QueryString of(String query) {
return new QueryString(query);
}
/* (non-Javadoc) */
static <T> Class<T> validateDomainType(Class<T> domainType) {
Assert.notNull(domainType, "domainType must not be null");

View File

@@ -18,16 +18,17 @@ package org.springframework.data.gemfire.repository.query;
import java.util.Collection;
import java.util.Collections;
import com.gemstone.gemfire.cache.query.SelectResults;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.gemfire.GemfireTemplate;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import com.gemstone.gemfire.cache.query.SelectResults;
/**
* {@link GemfireRepositoryQuery} using plain {@link String} based OQL queries.
* <p>
@@ -49,8 +50,14 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
* Constructor used for testing purposes only!
*/
StringBasedGemfireRepositoryQuery() {
query = null;
template = null;
this.query = null;
this.template = null;
register(LimitQueryPostProcessor.INSTANCE
.processBefore(ImportQueryPostProcessor.INSTANCE)
.processBefore(HintQueryPostProcessor.INSTANCE)
.processBefore(TraceQueryPostProcessor.INSTANCE));
}
/**
@@ -73,17 +80,20 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
* @param template must not be {@literal null}.
*/
public StringBasedGemfireRepositoryQuery(String query, GemfireQueryMethod queryMethod, GemfireTemplate template) {
super(queryMethod);
Assert.notNull(template);
Assert.notNull(template, "GemfireTemplate must not be null");
Assert.state(!(queryMethod.isModifyingQuery() || queryMethod.isPageQuery()), INVALID_QUERY);
this.userDefinedQuery |= !StringUtils.hasText(query);
this.query = new QueryString(StringUtils.hasText(query) ? query : queryMethod.getAnnotatedQuery());
this.template = template;
if (queryMethod.isModifyingQuery() || queryMethod.isPageQuery()) {
throw new IllegalStateException(INVALID_QUERY);
}
register(LimitQueryPostProcessor.INSTANCE
.processBefore(ImportQueryPostProcessor.INSTANCE)
.processBefore(HintQueryPostProcessor.INSTANCE)
.processBefore(TraceQueryPostProcessor.INSTANCE));
}
/*
@@ -106,27 +116,30 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
*/
@Override
public Object execute(Object[] parameters) {
QueryMethod localQueryMethod = getQueryMethod();
public Object execute(Object[] arguments) {
QueryString query = (isUserDefinedQuery() ? this.query : this.query.forRegion(
localQueryMethod.getEntityInformation().getJavaType(), template.getRegion()));
QueryMethod queryMethod = getQueryMethod();
ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(
localQueryMethod.getParameters(), parameters);
QueryString query = isUserDefinedQuery() ? this.query
: this.query.forRegion(queryMethod.getEntityInformation().getJavaType(), this.template.getRegion());
ParametersParameterAccessor parameterAccessor =
new ParametersParameterAccessor(queryMethod.getParameters(), arguments);
for (Integer index : query.getInParameterIndexes()) {
query = query.bindIn(toCollection(parameterAccessor.getBindableValue(index - 1)));
}
query = applyQueryAnnotationExtensions(localQueryMethod, query);
String queryString = getQueryPostProcessor().postProcess(queryMethod, query.toString(), arguments);
Collection<?> result = toCollection(template.find(query.toString(), parameters));
SelectResults<?> selectResults = this.template.find(queryString, arguments);
if (localQueryMethod.isCollectionQuery()) {
Collection<?> result = toCollection(selectResults);
if (queryMethod.isCollectionQuery()) {
return result;
}
else if (localQueryMethod.isQueryForEntity()) {
else if (queryMethod.isQueryForEntity()) {
if (result.isEmpty()) {
return null;
}
@@ -137,7 +150,7 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
throw new IncorrectResultSizeDataAccessException(1, result.size());
}
}
else if (isSingleResultNonEntityQuery(localQueryMethod, result)) {
else if (isSingleResultNonEntityQuery(queryMethod, result)) {
return result.iterator().next();
}
else {
@@ -145,31 +158,8 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
}
}
QueryString applyQueryAnnotationExtensions(final QueryMethod queryMethod, final QueryString queryString) {
QueryString resolvedQueryString = queryString;
if (queryMethod instanceof GemfireQueryMethod) {
GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod;
String query = queryString.toString().toUpperCase();
if (gemfireQueryMethod.hasImport() && !QueryString.IMPORT_PATTERN.matcher(query).find()) {
resolvedQueryString = resolvedQueryString.withImport(gemfireQueryMethod.getImport());
}
if (gemfireQueryMethod.hasHint() && !QueryString.HINT_PATTERN.matcher(query).find()) {
resolvedQueryString = resolvedQueryString.withHints(gemfireQueryMethod.getHints());
}
if (gemfireQueryMethod.hasLimit() && !QueryString.LIMIT_PATTERN.matcher(query).find()) {
resolvedQueryString = resolvedQueryString.withLimit(gemfireQueryMethod.getLimit());
}
if (gemfireQueryMethod.hasTrace() && !QueryString.TRACE_PATTERN.matcher(query).find()) {
resolvedQueryString = resolvedQueryString.withTrace();
}
}
return resolvedQueryString;
}
boolean isSingleResultNonEntityQuery(QueryMethod method, Collection<?> result) {
return (!method.isCollectionQuery() && method.getReturnedObjectType() != null
&& !Void.TYPE.equals(method.getReturnedObjectType()) && result != null && result.size() == 1);
}
@@ -186,6 +176,7 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
* @see com.gemstone.gemfire.cache.query.SelectResults
*/
Collection<?> toCollection(final Object source) {
if (source instanceof SelectResults) {
return ((SelectResults) source).asList();
}
@@ -201,4 +192,83 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery {
return (source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singletonList(source));
}
protected static class HintQueryPostProcessor extends AbstractQueryPostProcessor<Repository, String> {
protected static final HintQueryPostProcessor INSTANCE = new HintQueryPostProcessor();
@Override
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
if (queryMethod instanceof GemfireQueryMethod) {
GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod;
if (gemfireQueryMethod.hasHint() && !QueryString.HINT_PATTERN.matcher(query).find()) {
query = QueryString.of(query).withHints(gemfireQueryMethod.getHints()).toString();
}
}
return query;
}
}
protected static class ImportQueryPostProcessor extends AbstractQueryPostProcessor<Repository, String> {
protected static final ImportQueryPostProcessor INSTANCE = new ImportQueryPostProcessor();
@Override
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
if (queryMethod instanceof GemfireQueryMethod) {
GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod;
if (gemfireQueryMethod.hasImport() && !QueryString.IMPORT_PATTERN.matcher(query).find()) {
query = QueryString.of(query).withImport(gemfireQueryMethod.getImport()).toString();
}
}
return query;
}
}
protected static class LimitQueryPostProcessor extends AbstractQueryPostProcessor<Repository, String> {
protected static final LimitQueryPostProcessor INSTANCE = new LimitQueryPostProcessor();
@Override
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
if (queryMethod instanceof GemfireQueryMethod) {
GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod;
if (gemfireQueryMethod.hasLimit() && !QueryString.LIMIT_PATTERN.matcher(query).find()) {
query = QueryString.of(query).withLimit(gemfireQueryMethod.getLimit()).toString();
}
}
return query;
}
}
protected static class TraceQueryPostProcessor extends AbstractQueryPostProcessor<Repository, String> {
protected static final TraceQueryPostProcessor INSTANCE = new TraceQueryPostProcessor();
@Override
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
if (queryMethod instanceof GemfireQueryMethod) {
GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod;
if (gemfireQueryMethod.hasTrace() && !QueryString.TRACE_PATTERN.matcher(query).find()) {
query = QueryString.of(query).withTrace().toString();
}
}
return query;
}
}
}

View File

@@ -16,27 +16,41 @@
package org.springframework.data.gemfire.repository.support;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeList;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.gemfire.mapping.GemfirePersistentEntity;
import org.springframework.data.gemfire.mapping.GemfirePersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.util.Assert;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import com.gemstone.gemfire.cache.Region;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.OrderComparator;
import org.springframework.data.gemfire.mapping.GemfirePersistentEntity;
import org.springframework.data.gemfire.mapping.GemfirePersistentProperty;
import org.springframework.data.gemfire.repository.query.GemfireRepositoryQuery;
import org.springframework.data.gemfire.repository.query.QueryPostProcessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.RepositoryDefinition;
import org.springframework.data.repository.core.support.QueryCreationListener;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
/**
* {@link FactoryBean} adapter for {@link GemfireRepositoryFactory}.
*
*
* @author Oliver Gierke
* @author John Blum
* @see org.springframework.beans.factory.FactoryBean
@@ -54,19 +68,21 @@ import com.gemstone.gemfire.cache.Region;
public class GemfireRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable>
extends RepositoryFactoryBeanSupport<T, S, ID> implements ApplicationContextAware {
private ApplicationContext applicationContext;
private Iterable<Region<?, ?>> regions;
private MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext;
/**
* Creates a new {@link GemfireRepositoryFactoryBean} for the given repository interface.
*
*
* @param repositoryInterface must not be {@literal null}.
*/
public GemfireRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
/**
* Sets a reference to the Spring {@link ApplicationContext} in which this object runs.
*
@@ -77,17 +93,22 @@ public class GemfireRepositoryFactoryBean<T extends Repository<S, ID>, S, ID ext
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
Collection<Region> regions = applicationContext.getBeansOfType(Region.class).values();
this.regions = (Iterable) Collections.unmodifiableCollection(regions);
}
/**
* Configures the {@link MappingContext} used to perform domain object type to store mappings.
*
*
* @param mappingContext the {@link MappingContext} to set.
* @see org.springframework.data.gemfire.mapping.GemfireMappingContext
* @see org.springframework.data.mapping.context.MappingContext
*/
@Autowired(required = false)
public void setGemfireMappingContext(MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext) {
setMappingContext(mappingContext);
this.mappingContext = mappingContext;
@@ -124,10 +145,22 @@ public class GemfireRepositoryFactoryBean<T extends Repository<S, ID>, S, ID ext
*/
@Override
protected RepositoryFactorySupport createRepositoryFactory() {
return new GemfireRepositoryFactory(getRegions(), getGemfireMappingContext());
GemfireRepositoryFactory repositoryFactory =
new GemfireRepositoryFactory(getRegions(), getGemfireMappingContext());
if (this.applicationContext != null) {
QueryCreationListener<GemfireRepositoryQuery> listener =
new QueryPostProcessorRegistrationOnQueryCreationListener(this.applicationContext);
repositoryFactory.addQueryCreationListener(listener);
}
return repositoryFactory;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#afterPropertiesSet()
*/
@@ -136,4 +169,156 @@ public class GemfireRepositoryFactoryBean<T extends Repository<S, ID>, S, ID ext
Assert.state(getGemfireMappingContext() != null, "GemfireMappingContext must not be null");
super.afterPropertiesSet();
}
protected class QueryPostProcessorRegistrationOnQueryCreationListener
implements QueryCreationListener<GemfireRepositoryQuery> {
private Iterable<QueryPostProcessorMetadata> queryPostProcessorsMetadata;
protected QueryPostProcessorRegistrationOnQueryCreationListener(ApplicationContext applicationContext) {
Assert.notNull(applicationContext, "ApplicationContext must not be null");
List<QueryPostProcessor> queryPostProcessors =
new ArrayList<QueryPostProcessor>(applicationContext.getBeansOfType(QueryPostProcessor.class).values());
Collections.sort(queryPostProcessors, OrderComparator.INSTANCE);
List<QueryPostProcessorMetadata> queryPostProcessorsMetadata =
new ArrayList<QueryPostProcessorMetadata>();
for (QueryPostProcessor queryPostProcessor : queryPostProcessors) {
queryPostProcessorsMetadata.add(QueryPostProcessorMetadata.from(queryPostProcessor));
}
this.queryPostProcessorsMetadata = queryPostProcessorsMetadata;
}
protected Iterable<QueryPostProcessorMetadata> getQueryPostProcessorsMetadata() {
return this.queryPostProcessorsMetadata;
}
@Override
public void onCreation(GemfireRepositoryQuery repositoryQuery) {
Class<?> repositoryInterface = getRepositoryInformation().getRepositoryInterface();
for (QueryPostProcessorMetadata metadata : getQueryPostProcessorsMetadata()) {
if (metadata.isMatch(repositoryInterface)) {
metadata.register(repositoryQuery);
}
}
}
}
static class QueryPostProcessorMetadata {
private static final Map<QueryPostProcessorKey, QueryPostProcessorMetadata> cache =
new WeakHashMap<QueryPostProcessorKey, QueryPostProcessorMetadata>();
private final Class<?> declaredRepositoryType;
private final QueryPostProcessor<?, ?> queryPostProcessor;
static synchronized QueryPostProcessorMetadata from(QueryPostProcessor<?, ?> queryPostProcessor) {
QueryPostProcessorKey key = QueryPostProcessorKey.of(queryPostProcessor);
QueryPostProcessorMetadata metadata = cache.get(key);
if (metadata == null) {
metadata = new QueryPostProcessorMetadata(key.getQueryPostProcessor());
cache.put(key, metadata);
}
return metadata;
}
@SuppressWarnings("unchecked")
QueryPostProcessorMetadata(QueryPostProcessor<?, ?> queryPostProcessor) {
Assert.notNull(queryPostProcessor, "QueryPostProcessor must not be null");
this.queryPostProcessor = queryPostProcessor;
List<TypeInformation<?>> typeArguments = ClassTypeInformation.from(queryPostProcessor.getClass())
.getSuperTypeInformation(QueryPostProcessor.class)
.getTypeArguments();
this.declaredRepositoryType = resolveDeclaredRepositoryType(typeArguments);
}
Class<?> resolveDeclaredRepositoryType(List<TypeInformation<?>> typeArguments) {
return !nullSafeList(typeArguments).isEmpty() ? typeArguments.get(0).getType() : Repository.class;
}
Class<?> getDeclaredRepositoryType() {
return this.declaredRepositoryType;
}
@SuppressWarnings("unchecked")
QueryPostProcessor<?, String> getQueryPostProcessor() {
return (QueryPostProcessor<?, String>) this.queryPostProcessor;
}
boolean isMatch(Class<?> repositoryInterface) {
return repositoryInterface != null
&& (getDeclaredRepositoryType().isAssignableFrom(repositoryInterface)
|| repositoryInterface.isAnnotationPresent(RepositoryDefinition.class));
}
GemfireRepositoryQuery register(GemfireRepositoryQuery repositoryQuery) {
repositoryQuery.register(getQueryPostProcessor());
return repositoryQuery;
}
protected static class QueryPostProcessorKey {
private QueryPostProcessor<?, ?> queryPostProcessor;
public static QueryPostProcessorKey of(QueryPostProcessor queryPostProcessor) {
Assert.notNull(queryPostProcessor, "QueryPostProcessor must not be null");
QueryPostProcessorKey key = new QueryPostProcessorKey();
key.queryPostProcessor = queryPostProcessor;
return key;
}
protected QueryPostProcessor<?, ?> getQueryPostProcessor() {
return queryPostProcessor;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof QueryPostProcessorKey)) {
return false;
}
QueryPostProcessorKey that = (QueryPostProcessorKey) obj;
return this.getQueryPostProcessor().equals(that.getQueryPostProcessor());
}
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + getQueryPostProcessor().hashCode();
return hashValue;
}
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.data.gemfire.repository.query;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.anyVararg;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.data.repository.query.QueryMethod;
/**
* Unit tests for {@link AbstractQueryPostProcessor}.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.data.gemfire.repository.query.AbstractQueryPostProcessor
* @since 1.0.0
*/
public class AbstractQueryPostProcessorUnitTests {
@Test
@SuppressWarnings("unchecked")
public void processAfterReturnsCompositeQueryPostProcessorAndPostProcessesInOrder() {
QueryMethod mockQueryMethod = mock(QueryMethod.class);
String query = "SELECT * FROM /Test";
AbstractQueryPostProcessor<?, String> mockQueryPostProcessorOne = mock(AbstractQueryPostProcessor.class);
AbstractQueryPostProcessor<?, String> mockQueryPostProcessorTwo = mock(AbstractQueryPostProcessor.class);
when(mockQueryPostProcessorOne.processAfter(any(QueryPostProcessor.class))).thenCallRealMethod();
when(mockQueryPostProcessorOne.postProcess(any(QueryMethod.class), anyString(), anyVararg())).thenReturn(query);
when(mockQueryPostProcessorTwo.postProcess(any(QueryMethod.class), anyString(), anyVararg())).thenReturn(query);
QueryPostProcessor<?, String> composite = mockQueryPostProcessorOne.processAfter(mockQueryPostProcessorTwo);
assertThat(composite).isNotNull();
assertThat(composite).isNotSameAs(mockQueryPostProcessorOne);
assertThat(composite).isNotSameAs(mockQueryPostProcessorTwo);
assertThat(composite.postProcess(mockQueryMethod, query)).isEqualTo(query);
InOrder inOrder = inOrder(mockQueryPostProcessorOne, mockQueryPostProcessorTwo);
inOrder.verify(mockQueryPostProcessorTwo, times(1))
.postProcess(eq(mockQueryMethod), eq(query), anyVararg());
inOrder.verify(mockQueryPostProcessorOne, times(1))
.postProcess(eq(mockQueryMethod), eq(query), anyVararg());
}
@Test
@SuppressWarnings("unchecked")
public void processAfterReturnsThis() {
AbstractQueryPostProcessor<?, ?> mockQueryPostProcessor = mock(AbstractQueryPostProcessor.class);
when(mockQueryPostProcessor.processAfter(any(AbstractQueryPostProcessor.class))).thenCallRealMethod();
assertThat(mockQueryPostProcessor.processAfter(null)).isSameAs(mockQueryPostProcessor);
}
@Test
@SuppressWarnings("unchecked")
public void processBeforeReturnsCompositeQueryPostProcessorAndPostProcessesInOrder() {
QueryMethod mockQueryMethod = mock(QueryMethod.class);
String query = "SELECT * FROM /Test";
AbstractQueryPostProcessor<?, String> mockQueryPostProcessorOne = mock(AbstractQueryPostProcessor.class);
AbstractQueryPostProcessor<?, String> mockQueryPostProcessorTwo = mock(AbstractQueryPostProcessor.class);
when(mockQueryPostProcessorOne.processBefore(any(QueryPostProcessor.class))).thenCallRealMethod();
when(mockQueryPostProcessorOne.postProcess(any(QueryMethod.class), anyString(), anyVararg())).thenReturn(query);
when(mockQueryPostProcessorTwo.postProcess(any(QueryMethod.class), anyString(), anyVararg())).thenReturn(query);
QueryPostProcessor<?, String> composite = mockQueryPostProcessorOne.processBefore(mockQueryPostProcessorTwo);
assertThat(composite).isNotNull();
assertThat(composite).isNotSameAs(mockQueryPostProcessorOne);
assertThat(composite).isNotSameAs(mockQueryPostProcessorTwo);
assertThat(composite.postProcess(mockQueryMethod, query)).isEqualTo(query);
InOrder inOrder = inOrder(mockQueryPostProcessorOne, mockQueryPostProcessorTwo);
inOrder.verify(mockQueryPostProcessorOne, times(1))
.postProcess(eq(mockQueryMethod), eq(query), anyVararg());
inOrder.verify(mockQueryPostProcessorTwo, times(1))
.postProcess(eq(mockQueryMethod), eq(query), anyVararg());
}
@Test
@SuppressWarnings("unchecked")
public void processBeforeReturnsThis() {
AbstractQueryPostProcessor<?, ?> mockQueryPostProcessor = mock(AbstractQueryPostProcessor.class);
when(mockQueryPostProcessor.processBefore(any(QueryPostProcessor.class))).thenCallRealMethod();
assertThat(mockQueryPostProcessor.processBefore(null)).isSameAs(mockQueryPostProcessor);
}
}

View File

@@ -0,0 +1,232 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.data.gemfire.repository.query;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.mapping.GemfireMappingContext;
import org.springframework.data.gemfire.repository.sample.Person;
import org.springframework.data.gemfire.repository.sample.PersonRepository;
import org.springframework.data.gemfire.repository.sample.User;
import org.springframework.data.gemfire.repository.sample.UserRepository;
import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Integration tests for {@link QueryPostProcessor} framework infrastructure.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.data.gemfire.repository.query.QueryPostProcessor
* @since 1.0.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@SuppressWarnings("unused")
public class QueryPostProcessingIntegrationTests {
private static final AtomicLong idSequence = new AtomicLong(0L);
@Autowired
private FindOrderedLimitedPeopleByFirstNameQueryPostProcessor peopleQueryPostProcessor;
@Autowired
private PersonRepository personRepository;
@Autowired
private RecordingQueryPostProcessor recordingQueryPostProcessor;
@Autowired
private UserRepository userRepository;
private static Person newPerson(String firstName, String lastName) {
Person person = new Person(firstName, lastName);
person.id = idSequence.incrementAndGet();
return person;
}
@Before
public void setup() {
this.personRepository.save(newPerson("Jon", "Doe"));
this.personRepository.save(newPerson("Cookie", "Doe"));
this.personRepository.save(newPerson("Pie", "Doe"));
this.personRepository.save(newPerson("Sour", "Doe"));
this.personRepository.save(newPerson("Jack", "BeNimble"));
this.personRepository.save(newPerson("Jack", "BeQuick"));
this.personRepository.save(newPerson("Jack", "Black"));
this.personRepository.save(newPerson("Jack", "JumpedOverTheCandleStick"));
this.personRepository.save(newPerson("Jack", "Handy"));
this.personRepository.save(newPerson("Jack", "Sparrow"));
this.personRepository.save(newPerson("Agent", "Smith"));
this.userRepository.save(new User("abuser"));
this.userRepository.save(new User("jdoe"));
this.userRepository.save(new User("root"));
}
@Test
public void queryPostProcessingProcessesGeneratedOqlQueries() {
assertThat(this.personRepository.count()).isEqualTo(11);
assertThat(this.userRepository.count()).isEqualTo(3);
assertThat(this.recordingQueryPostProcessor.queries).isEmpty();
List<User> users = this.userRepository.findDistinctByUsernameLike("%doe");
assertThat(users).hasSize(1);
assertThat(this.recordingQueryPostProcessor.queries).hasSize(1);
assertThat(this.recordingQueryPostProcessor.queries)
.containsExactly("SELECT DISTINCT * FROM /Users x WHERE x.username LIKE $1");
Collection<Person> bakingDoes = this.personRepository.findByFirstnameIn("Cookie", "Pie", "Sour");
assertThat(bakingDoes).hasSize(3);
assertThat(this.recordingQueryPostProcessor.queries).hasSize(2);
assertThat(this.recordingQueryPostProcessor.queries).containsExactly(
"SELECT DISTINCT * FROM /Users x WHERE x.username LIKE $1",
"SELECT * FROM /simple x WHERE x.firstname IN SET ('Cookie', 'Pie', 'Sour')"
);
Collection<Person> jacks = this.personRepository.findByFirstname("Jack");
assertThat(jacks).hasSize(1);
assertThat(jacks.iterator().next().getName()).isEqualTo("Jack Sparrow");
assertThat(this.recordingQueryPostProcessor.queries).hasSize(3);
assertThat(this.recordingQueryPostProcessor.queries).containsExactly(
"SELECT DISTINCT * FROM /Users x WHERE x.username LIKE $1",
"SELECT * FROM /simple x WHERE x.firstname IN SET ('Cookie', 'Pie', 'Sour')",
"SELECT DISTINCT * FROM /simple x WHERE x.firstname = $1 ORDER BY lastname DESC LIMIT 1"
);
}
@ClientCacheApplication
@SuppressWarnings("unused")
static class TestConfiguration {
@Bean("simple")
public ClientRegionFactoryBean<Object, Object> peopleRegion(GemFireCache gemfireCache) {
ClientRegionFactoryBean<Object, Object> clientRegion = new ClientRegionFactoryBean<Object, Object>();
clientRegion.setCache(gemfireCache);
clientRegion.setClose(false);
clientRegion.setShortcut(ClientRegionShortcut.LOCAL);
return clientRegion;
}
@Bean("Users")
public ClientRegionFactoryBean<Object, Object> usersRegion(GemFireCache gemfireCache) {
ClientRegionFactoryBean<Object, Object> clientRegion = new ClientRegionFactoryBean<Object, Object>();
clientRegion.setCache(gemfireCache);
clientRegion.setClose(false);
clientRegion.setShortcut(ClientRegionShortcut.LOCAL);
return clientRegion;
}
@Bean
GemfireMappingContext mappingContext() {
return new GemfireMappingContext();
}
@Bean
GemfireRepositoryFactoryBean<PersonRepository, Person, Long> personRepository() {
return new GemfireRepositoryFactoryBean<PersonRepository, Person, Long>(PersonRepository.class);
}
@Bean
GemfireRepositoryFactoryBean<UserRepository, User, String> userRepository() {
return new GemfireRepositoryFactoryBean<UserRepository, User, String>(UserRepository.class);
}
@Bean
FindOrderedLimitedPeopleByFirstNameQueryPostProcessor personQueryPostProcess() {
return new FindOrderedLimitedPeopleByFirstNameQueryPostProcessor(1);
}
@Bean
RecordingQueryPostProcessor recordingQueryPostProcessor() {
return new RecordingQueryPostProcessor();
}
}
static class FindOrderedLimitedPeopleByFirstNameQueryPostProcessor extends AbstractQueryPostProcessor<PersonRepository, String> {
private final int limit;
FindOrderedLimitedPeopleByFirstNameQueryPostProcessor(int limit) {
this.limit = limit;
}
@Override
public int getOrder() {
return 0;
}
@Override
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
return "findByFirstname".equals(queryMethod.getName())
? query.trim().replace("SELECT", "SELECT DISTINCT")
.concat(" ORDER BY lastname DESC").concat(String.format(" LIMIT %d", this.limit))
: query;
}
}
static class RecordingQueryPostProcessor extends AbstractQueryPostProcessor<Repository, String> {
List<String> queries = new CopyOnWriteArrayList<String>();
@Override
public int getOrder() {
return 1;
}
@Override
public String postProcess(QueryMethod queryMethod, String query, Object... arguments) {
this.queries.add(query);
return query;
}
}
}

View File

@@ -18,9 +18,6 @@ package org.springframework.data.gemfire.repository.query;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -39,11 +36,11 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import com.gemstone.gemfire.cache.query.SelectResults;
import com.gemstone.gemfire.cache.query.internal.ResultsBag;
import org.junit.Test;
/**
* The SpringBasedGemfireRepositoryQueryTest class is a test suite of test cases testing the contract and functionality
* of the StringBasedGemfireRepositoryQuery class.
@@ -122,6 +119,7 @@ public class StringBasedGemfireRepositoryQueryTest {
@Test
public void applyAllQueryAnnotationExtensions() {
GemfireQueryMethod mockQueryMethod = mock(GemfireQueryMethod.class, "MockGemfireQueryMethod");
when(mockQueryMethod.hasHint()).thenReturn(true);
@@ -132,18 +130,13 @@ public class StringBasedGemfireRepositoryQueryTest {
when(mockQueryMethod.getLimit()).thenReturn(10);
when(mockQueryMethod.hasTrace()).thenReturn(true);
QueryString queryString = new QueryString("SELECT * FROM /Example");
assertThat(queryString.toString(), is(equalTo("SELECT * FROM /Example")));
StringBasedGemfireRepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery();
QueryString actualQueryString = repositoryQuery.applyQueryAnnotationExtensions(mockQueryMethod, queryString);
String query = repositoryQuery.getQueryPostProcessor()
.postProcess(mockQueryMethod, "SELECT * FROM /Example");
assertThat(actualQueryString, is(notNullValue()));
assertThat(actualQueryString, is(not(sameInstance(queryString))));
assertThat(actualQueryString.toString(), is(equalTo(
"<TRACE> <HINT 'IdIdx', 'NameIdx'> IMPORT org.example.domain.Type; SELECT * FROM /Example LIMIT 10")));
assertThat(query,
is(equalTo("<TRACE> <HINT 'IdIdx', 'NameIdx'> IMPORT org.example.domain.Type; SELECT * FROM /Example LIMIT 10")));
verify(mockQueryMethod, times(1)).hasHint();
verify(mockQueryMethod, times(1)).getHints();
@@ -156,6 +149,7 @@ public class StringBasedGemfireRepositoryQueryTest {
@Test
public void applyHintLimitAndTraceQueryAnnotationExtensionsWithExistingHintAndLimit() {
GemfireQueryMethod mockQueryMethod = mock(GemfireQueryMethod.class, "MockGemfireQueryMethod");
when(mockQueryMethod.hasHint()).thenReturn(true);
@@ -165,18 +159,12 @@ public class StringBasedGemfireRepositoryQueryTest {
when(mockQueryMethod.getLimit()).thenReturn(50);
when(mockQueryMethod.hasTrace()).thenReturn(true);
QueryString queryString = new QueryString("<HINT 'LastNameIdx'> SELECT * FROM /Example LIMIT 25");
assertThat(queryString.toString(), is(equalTo("<HINT 'LastNameIdx'> SELECT * FROM /Example LIMIT 25")));
StringBasedGemfireRepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery();
QueryString actualQueryString = repositoryQuery.applyQueryAnnotationExtensions(mockQueryMethod, queryString);
String query = repositoryQuery.getQueryPostProcessor()
.postProcess(mockQueryMethod, "<HINT 'LastNameIdx'> SELECT * FROM /Example LIMIT 25");
assertThat(actualQueryString, is(notNullValue()));
assertThat(actualQueryString, is(not(sameInstance(queryString))));
assertThat(actualQueryString.toString(), is(equalTo(
"<TRACE> <HINT 'LastNameIdx'> SELECT * FROM /Example LIMIT 25")));
assertThat(query, is(equalTo("<TRACE> <HINT 'LastNameIdx'> SELECT * FROM /Example LIMIT 25")));
verify(mockQueryMethod, times(1)).hasHint();
verify(mockQueryMethod, never()).getHints();
@@ -189,6 +177,7 @@ public class StringBasedGemfireRepositoryQueryTest {
@Test
public void applyImportAndTraceQueryAnnotationExtensionsWithExistingTrace() {
GemfireQueryMethod mockQueryMethod = mock(GemfireQueryMethod.class, "MockGemfireQueryMethod");
when(mockQueryMethod.hasHint()).thenReturn(false);
@@ -197,18 +186,12 @@ public class StringBasedGemfireRepositoryQueryTest {
when(mockQueryMethod.hasLimit()).thenReturn(false);
when(mockQueryMethod.hasTrace()).thenReturn(true);
QueryString queryString = new QueryString("<TRACE> SELECT * FROM /Example");
assertThat(queryString.toString(), is(equalTo("<TRACE> SELECT * FROM /Example")));
StringBasedGemfireRepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery();
QueryString actualQueryString = repositoryQuery.applyQueryAnnotationExtensions(mockQueryMethod, queryString);
String query = repositoryQuery.getQueryPostProcessor()
.postProcess(mockQueryMethod, "<TRACE> SELECT * FROM /Example");
assertThat(actualQueryString, is(notNullValue()));
assertThat(actualQueryString, is(not(sameInstance(queryString))));
assertThat(actualQueryString.toString(), is(equalTo(
"IMPORT org.example.domain.Type; <TRACE> SELECT * FROM /Example")));
assertThat(query, is(equalTo("IMPORT org.example.domain.Type; <TRACE> SELECT * FROM /Example")));
verify(mockQueryMethod, times(1)).hasHint();
verify(mockQueryMethod, never()).getHints();
@@ -218,5 +201,4 @@ public class StringBasedGemfireRepositoryQueryTest {
verify(mockQueryMethod, never()).getLimit();
verify(mockQueryMethod, times(1)).hasTrace();
}
}