SGF-713 - Override generated OQL from Repository query methods.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user