From 780ce8905038e5d076bb86d09c2a4daf3406780b Mon Sep 17 00:00:00 2001 From: John Blum Date: Fri, 31 Jul 2015 18:28:47 -0700 Subject: [PATCH] SGF-392 - Add support for OQL Query statement extensions in Repository Query methods via Annotations. (cherry picked from commit c022f2c00585d9f90243a480486cb38f6d46d2ee) Signed-off-by: John Blum --- .../config/GemfireDataNamespaceHandler.java | 3 +- .../repository/query/GemfireQueryMethod.java | 143 ++++++++++-- .../StringBasedGemfireRepositoryQuery.java | 88 ++++---- .../repository/query/annotation/Hint.java | 45 ++++ .../repository/query/annotation/Import.java | 47 ++++ .../repository/query/annotation/Limit.java | 46 ++++ .../repository/query/annotation/Trace.java | 43 ++++ .../support/GemfireRepositoryFactoryBean.java | 13 +- .../query/GemfireQueryMethodUnitTests.java | 208 ++++++++++++++++-- 9 files changed, 550 insertions(+), 86 deletions(-) create mode 100644 src/main/java/org/springframework/data/gemfire/repository/query/annotation/Hint.java create mode 100644 src/main/java/org/springframework/data/gemfire/repository/query/annotation/Import.java create mode 100644 src/main/java/org/springframework/data/gemfire/repository/query/annotation/Limit.java create mode 100644 src/main/java/org/springframework/data/gemfire/repository/query/annotation/Trace.java diff --git a/src/main/java/org/springframework/data/gemfire/config/GemfireDataNamespaceHandler.java b/src/main/java/org/springframework/data/gemfire/config/GemfireDataNamespaceHandler.java index b3655fb8..6e175f2c 100644 --- a/src/main/java/org/springframework/data/gemfire/config/GemfireDataNamespaceHandler.java +++ b/src/main/java/org/springframework/data/gemfire/config/GemfireDataNamespaceHandler.java @@ -29,6 +29,7 @@ import org.springframework.data.repository.config.RepositoryConfigurationExtensi * @author David Turanski * @author Oliver Gierke */ +@SuppressWarnings("unused") class GemfireDataNamespaceHandler extends NamespaceHandlerSupport { @Override @@ -40,4 +41,4 @@ class GemfireDataNamespaceHandler extends NamespaceHandlerSupport { registerBeanDefinitionParser("datasource", new GemfireDataSourceParser()); registerBeanDefinitionParser("json-region-autoproxy", new GemfireRegionAutoProxyParser()); } -} \ No newline at end of file +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethod.java b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethod.java index 0046b454..3bcc6d09 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethod.java +++ b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethod.java @@ -22,6 +22,10 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; import org.springframework.data.gemfire.mapping.GemfirePersistentProperty; import org.springframework.data.gemfire.repository.Query; +import org.springframework.data.gemfire.repository.query.annotation.Hint; +import org.springframework.data.gemfire.repository.query.annotation.Import; +import org.springframework.data.gemfire.repository.query.annotation.Limit; +import org.springframework.data.gemfire.repository.query.annotation.Trace; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.QueryMethod; @@ -33,8 +37,11 @@ import org.springframework.util.StringUtils; * * @author Oliver Gierke */ +@SuppressWarnings("unused") public class GemfireQueryMethod extends QueryMethod { + protected static final String[] EMPTY_STRING_ARRAY = new String[0]; + private final Method method; private final GemfirePersistentEntity entity; @@ -51,30 +58,33 @@ public class GemfireQueryMethod extends QueryMethod { super(method, metadata); Assert.notNull(context); - - for (Class type : method.getParameterTypes()) { - if (Pageable.class.isAssignableFrom(type)) { - throw new IllegalStateException("Pagination is not supported by Gemfire repositories! Offending method: " - + method.toString()); - } - } + assertNonPagingQueryMethod(method); this.method = method; this.entity = context.getPersistentEntity(getDomainClass()); } /** - * Returns whether the query method contains an annotated, non-empty query. - * - * @return whether the query method contains an annotated, non-empty query. + * Asserts that the query method is a non-Paging query method since GemFire does not support pagination + * as it has no concept of a Cursor. + * + * @param method the query method to be evaluated + * @throws java.lang.IllegalStateException if the query method contains a parameter of type Pageable. + * @see org.springframework.data.domain.Pageable + * @see java.lang.reflect.Method#getParameterTypes() */ - public boolean hasAnnotatedQuery() { - return StringUtils.hasText(getAnnotatedQuery()); + private void assertNonPagingQueryMethod(final Method method) { + for (Class type : method.getParameterTypes()) { + if (Pageable.class.isAssignableFrom(type)) { + throw new IllegalStateException("Pagination is not supported by GemFire Repositories! Offending method: " + + method.toString()); + } + } } /** * Returns the {@link GemfirePersistentEntity} the method deals with. - * + * * @return the {@link GemfirePersistentEntity} the method deals with. */ public GemfirePersistentEntity getPersistentEntity() { @@ -82,15 +92,110 @@ public class GemfireQueryMethod extends QueryMethod { } /** - * Returns the query annotated to the query method. + * Determines whether this query method specifies an annotated, non-empty query. + * + * @return a boolean value indicating whether the query method specifies an annotated, non-empty query. + * @see org.springframework.util.StringUtils#hasText(String) + * @see #getAnnotatedQuery() + */ + public boolean hasAnnotatedQuery() { + return StringUtils.hasText(getAnnotatedQuery()); + } + + /** + * Returns the annotated query for the query method if present. * - * @return the annotated query or {@literal null} in case it's empty or none available. + * @return the annotated query or {@literal null} in case it's empty or not present. + * @see org.springframework.data.gemfire.repository.Query + * @see java.lang.reflect.Method#getAnnotation(Class) */ String getAnnotatedQuery() { - Query query = method.getAnnotation(Query.class); - String queryString = query == null ? null : (String) AnnotationUtils.getValue(query); - - return StringUtils.hasText(queryString) ? queryString : null; + String queryString = (query != null ? (String) AnnotationUtils.getValue(query) : null); + return (StringUtils.hasText(queryString) ? queryString : null); } + + /** + * Determines whether this query method uses a query HINT to tell the GemFire OQL query engine which indexes + * to apply to the query execution. + * + * @return a boolean value to indicate whether this query method uses a query HINT. + * @see org.springframework.data.gemfire.repository.query.annotation.Hint + * @see java.lang.reflect.Method#isAnnotationPresent(Class) + */ + public boolean hasHint() { + return method.isAnnotationPresent(Hint.class); + } + + /** + * Gets the query HINTs for this query method. + * + * @return the query HINTs for this query method or an empty array if this query method has no query HINTs. + * @see org.springframework.data.gemfire.repository.query.annotation.Hint + * @see java.lang.reflect.Method#getAnnotation(Class) + */ + public String[] getHints() { + Hint hint = method.getAnnotation(Hint.class); + return (hint != null ? hint.value() : EMPTY_STRING_ARRAY); + } + + /** + * Determine whether this query method declares an IMPORT statement to qualify application domain object types + * referenced in the query. + * + * @return a boolean value to indicate whether this query method declares an IMPORT statement. + * @see org.springframework.data.gemfire.repository.query.annotation.Import + * @see java.lang.reflect.Method#isAnnotationPresent(Class) + */ + public boolean hasImport() { + return method.isAnnotationPresent(Import.class); + } + + /** + * Gets the IMPORT statement for this query method. + * + * @return the IMPORT statement for this query method or null if this query method does not have an IMPORT statement. + * @see org.springframework.data.gemfire.repository.query.annotation.Import + * @see java.lang.reflect.Method#getAnnotation(Class) + */ + public String getImport() { + Import importStatement = method.getAnnotation(Import.class); + return (importStatement != null ? importStatement.value() : null); + } + + /** + * Determines whether this query method defines a LIMIT on the number of results returned by the query. + * + * @return a boolean value indicating whether this query method defines a LIMIT on the result set + * returned by the query. + * @see org.springframework.data.gemfire.repository.query.annotation.Limit + * @see java.lang.reflect.Method#isAnnotationPresent(Class) + */ + public boolean hasLimit() { + return method.isAnnotationPresent(Limit.class); + } + + /** + * Gets the LIMIT for this query method on the result set returned by the query. + * + * @return the LIMIT for this query method limiting the number of results returned by the query. + * @see org.springframework.data.gemfire.repository.query.annotation.Limit + * @see java.lang.reflect.Method#getAnnotation(Class) + */ + public int getLimit() { + Limit limit = method.getAnnotation(Limit.class); + return (limit != null ? limit.value() : Integer.MAX_VALUE); + } + + /** + * Determines whether this query method has TRACE (i.e. logging) enabled. + * + * @return a boolean value to indicate whether this query method has TRACE enabled. + * @see org.springframework.data.gemfire.repository.query.annotation.Limit + * @see java.lang.reflect.Method#isAnnotationPresent(Class) + */ + public boolean hasTrace() { + return method.isAnnotationPresent(Trace.class); + } + } diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQuery.java b/src/main/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQuery.java index ceca605d..f3eff4c0 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQuery.java +++ b/src/main/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQuery.java @@ -15,19 +15,19 @@ */ package org.springframework.data.gemfire.repository.query; -import java.util.Arrays; 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.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. *

@@ -41,7 +41,6 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { private boolean userDefinedQuery = false; - private final GemfireQueryMethod method; private final GemfireTemplate template; private final QueryString query; @@ -51,7 +50,6 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { */ StringBasedGemfireRepositoryQuery() { query = null; - method = null; template = null; } @@ -59,11 +57,11 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { * Creates a new {@link StringBasedGemfireRepositoryQuery} using the given {@link GemfireQueryMethod} and * {@link GemfireTemplate}. The actual query {@link String} will be looked up from the query method. * - * @param method must not be {@literal null}. + * @param queryMethod must not be {@literal null}. * @param template must not be {@literal null}. */ - public StringBasedGemfireRepositoryQuery(GemfireQueryMethod method, GemfireTemplate template) { - this(method.getAnnotatedQuery(), method, template); + public StringBasedGemfireRepositoryQuery(GemfireQueryMethod queryMethod, GemfireTemplate template) { + this(queryMethod.getAnnotatedQuery(), queryMethod, template); } /** @@ -71,20 +69,19 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { * {@link GemfireQueryMethod} and {@link GemfireTemplate}. * * @param query will fall back to the query annotated to the given {@link GemfireQueryMethod} if {@literal null}. - * @param method must not be {@literal null}. + * @param queryMethod must not be {@literal null}. * @param template must not be {@literal null}. */ - public StringBasedGemfireRepositoryQuery(String query, GemfireQueryMethod method, GemfireTemplate template) { - super(method); + public StringBasedGemfireRepositoryQuery(String query, GemfireQueryMethod queryMethod, GemfireTemplate template) { + super(queryMethod); Assert.notNull(template); this.userDefinedQuery |= !StringUtils.hasText(query); - this.query = new QueryString(StringUtils.hasText(query) ? query : method.getAnnotatedQuery()); - this.method = method; + this.query = new QueryString(StringUtils.hasText(query) ? query : queryMethod.getAnnotatedQuery()); this.template = template; - if (method.isPageQuery() || method.isModifyingQuery()) { + if (queryMethod.isModifyingQuery() || queryMethod.isPageQuery()) { throw new IllegalStateException(INVALID_QUERY); } } @@ -110,10 +107,13 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { */ @Override public Object execute(Object[] parameters) { - ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor(method.getParameters(), parameters); + QueryMethod localQueryMethod = getQueryMethod(); + + ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor( + localQueryMethod.getParameters(), parameters); QueryString query = (isUserDefinedQuery() ? this.query : this.query.forRegion( - method.getEntityInformation().getJavaType(), template.getRegion())); + localQueryMethod.getEntityInformation().getJavaType(), template.getRegion())); for (Integer index : query.getInParameterIndexes()) { query = query.bindIn(toCollection(parameterAccessor.getBindableValue(index - 1))); @@ -121,35 +121,35 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { Collection result = toCollection(template.find(query.toString(), parameters)); - if (method.isCollectionQuery()) { - return result; - } - else if (method.isQueryForEntity()) { - if (result.isEmpty()) { - return null; - } - else if (result.size() == 1) { - return result.iterator().next(); - } - else { - throw new IncorrectResultSizeDataAccessException(1, result.size()); - } - } - else if (isSingleResultNonEntityQuery(method, result)) { - return result.iterator().next(); - } - else { - throw new IllegalStateException("Unsupported query: " + query.toString()); - } - } + if (localQueryMethod.isCollectionQuery()) { + return result; + } + else if (localQueryMethod.isQueryForEntity()) { + if (result.isEmpty()) { + return null; + } + else if (result.size() == 1) { + return result.iterator().next(); + } + else { + throw new IncorrectResultSizeDataAccessException(1, result.size()); + } + } + else if (isSingleResultNonEntityQuery(localQueryMethod, result)) { + return result.iterator().next(); + } + else { + throw new IllegalStateException("Unsupported query: " + query.toString()); + } + } - boolean isSingleResultNonEntityQuery(final GemfireQueryMethod method, final Collection result) { - return (!method.isCollectionQuery() && method.getReturnedObjectType() != null - && !Void.TYPE.equals(method.getReturnedObjectType()) && result != null && result.size() == 1); - } + boolean isSingleResultNonEntityQuery(QueryMethod method, Collection result) { + return (!method.isCollectionQuery() && method.getReturnedObjectType() != null + && !Void.TYPE.equals(method.getReturnedObjectType()) && result != null && result.size() == 1); + } - /** + /** * Returns the given object as a Collection. Collections will be returned as is, Arrays will be converted into a * Collection and all other objects will be wrapped into a single-element Collection. * @@ -158,7 +158,7 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { * @see java.util.Arrays#asList(Object[]) * @see java.util.Collection * @see org.springframework.util.CollectionUtils#arrayToList(Object) - * @see com.gemstone.gemfire.cache.query.SelectResults + * @see com.gemstone.gemfire.cache.query.SelectResults */ Collection toCollection(final Object source) { if (source instanceof SelectResults) { @@ -173,7 +173,7 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { return Collections.emptyList(); } - return (source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Arrays.asList(source)); + return (source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singletonList(source)); } } diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Hint.java b/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Hint.java new file mode 100644 index 00000000..6ff1faff --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Hint.java @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2013 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The Hint class is a annotation type indicating a GemFire OQL Query Hint. + * + * @author John Blum + * @see java.lang.annotation.Documented + * @see java.lang.annotation.Inherited + * @see java.lang.annotation.Retention + * @see java.lang.annotation.Target + * @since 1.7.0 + */ +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +@SuppressWarnings("unused") +public @interface Hint { + + String[] value() default {}; + +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Import.java b/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Import.java new file mode 100644 index 00000000..53c7069d --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Import.java @@ -0,0 +1,47 @@ +/* + * Copyright 2010-2013 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The Import class is a annotation type indicating a GemFire OQL Query Import to refer to the class of an object + * in cases where the same class name resides in two different name scopes (packages), then you must be able to + * differentiate the classes having the same name. + * + * @author John Blum + * @see java.lang.annotation.Documented + * @see java.lang.annotation.Inherited + * @see java.lang.annotation.Retention + * @see java.lang.annotation.Target + * @since 1.7.0 + */ +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +@SuppressWarnings("unused") +public @interface Import { + + String value(); + +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Limit.java b/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Limit.java new file mode 100644 index 00000000..abcd4ac3 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Limit.java @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2013 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The Limit class is an annotation type indicating a GemFire OQL Query Limit on the number of results + * that are returned. + * + * @author John Blum + * @see java.lang.annotation.Documented + * @see java.lang.annotation.Inherited + * @see java.lang.annotation.Retention + * @see java.lang.annotation.Target + * @since 1.7.0 + */ +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +@SuppressWarnings("unused") +public @interface Limit { + + int value() default Integer.MAX_VALUE; + +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Trace.java b/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Trace.java new file mode 100644 index 00000000..c304791f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/repository/query/annotation/Trace.java @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2013 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The Trace class is an annotation type to enable GemFire OQL Query debugging. + * + * @author John Blum + * @see java.lang.annotation.Documented + * @see java.lang.annotation.Inherited + * @see java.lang.annotation.Retention + * @see java.lang.annotation.Target + * @since 1.7.0 + */ +@Documented +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +@SuppressWarnings("unused") +public @interface Trace { + +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryBean.java b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryBean.java index 3fba3505..ec06c4b3 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryBean.java @@ -38,13 +38,14 @@ import com.gemstone.gemfire.cache.Region; * * @author Oliver Gierke */ -public class GemfireRepositoryFactoryBean, S, ID extends Serializable> extends - RepositoryFactoryBeanSupport implements ApplicationContextAware { - - private MappingContext, GemfirePersistentProperty> context; +@SuppressWarnings("unused") +public class GemfireRepositoryFactoryBean, S, ID extends Serializable> + extends RepositoryFactoryBeanSupport implements ApplicationContextAware { private Iterable> regions; + private MappingContext, GemfirePersistentProperty> context; + /* * (non-Javadoc) * @@ -64,8 +65,7 @@ public class GemfireRepositoryFactoryBean, S, ID ext * * @param context the context to set */ - public void setGemfireMappingContext( - MappingContext, GemfirePersistentProperty> context) { + public void setGemfireMappingContext(MappingContext, GemfirePersistentProperty> context) { this.context = context; } @@ -95,4 +95,5 @@ public class GemfireRepositoryFactoryBean, S, ID ext super.afterPropertiesSet(); } + } diff --git a/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethodUnitTests.java b/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethodUnitTests.java index 127c448d..619d8ca9 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethodUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethodUnitTests.java @@ -15,14 +15,19 @@ */ package org.springframework.data.gemfire.repository.query; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; import java.lang.reflect.Method; import org.hamcrest.Matchers; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; @@ -31,8 +36,13 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.gemfire.mapping.GemfireMappingContext; import org.springframework.data.gemfire.repository.Query; +import org.springframework.data.gemfire.repository.query.annotation.Hint; +import org.springframework.data.gemfire.repository.query.annotation.Import; +import org.springframework.data.gemfire.repository.query.annotation.Limit; +import org.springframework.data.gemfire.repository.query.annotation.Trace; import org.springframework.data.gemfire.repository.sample.Person; import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.util.ObjectUtils; /** * Unit tests for {@link GemfireQueryMethod}. @@ -42,47 +52,189 @@ import org.springframework.data.repository.core.RepositoryMetadata; @RunWith(MockitoJUnitRunner.class) public class GemfireQueryMethodUnitTests { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private GemfireMappingContext context = new GemfireMappingContext(); + @Mock - RepositoryMetadata metadata; + private RepositoryMetadata metadata; + + protected void assertQueryHints(GemfireQueryMethod queryMethod, String... expectedHints) { + assertThat(queryMethod, is(not(nullValue()))); + assertThat(queryMethod.hasHint(), is(!ObjectUtils.isEmpty(expectedHints))); + + String[] actualHints = queryMethod.getHints(); + + assertThat(actualHints, is(not(nullValue()))); + assertThat(actualHints.length, is(equalTo(expectedHints.length))); + + for (int index = 0; index < expectedHints.length; index++) { + assertThat(actualHints[index], is(equalTo(expectedHints[index]))); + } + } + + protected void assertNoQueryHints(GemfireQueryMethod queryMethod) { + assertQueryHints(queryMethod); + } + + protected void assertImportStatement(GemfireQueryMethod queryMethod, String expectedImport) { + assertThat(queryMethod, is(not(nullValue()))); + assertThat(queryMethod.hasImport(), is(expectedImport != null)); + + if (expectedImport != null) { + assertThat(queryMethod.getImport(), is(equalTo(expectedImport))); + } + else { + assertThat(queryMethod.getImport(), is(nullValue())); + } + } + + protected void assertNoImportStatement(GemfireQueryMethod queryMethod) { + assertImportStatement(queryMethod, null); + } + + protected void assertLimitedQuery(GemfireQueryMethod queryMethod, Integer expectedLimit) { + assertThat(queryMethod, is(not(nullValue()))); + assertThat(queryMethod.hasLimit(), is(expectedLimit != null)); + + if (expectedLimit != null) { + assertThat(queryMethod.getLimit(), is(equalTo(expectedLimit))); + } + else { + assertThat(queryMethod.getLimit(), is(nullValue())); + } + } + + protected void assertUnlimitedQuery(GemfireQueryMethod queryMethod) { + assertLimitedQuery(queryMethod, null); + } @Test @SuppressWarnings({ "unchecked", "rawtypes" }) public void detectsAnnotatedQueryCorrectly() throws Exception { - - GemfireMappingContext context = new GemfireMappingContext(); when(metadata.getDomainType()).thenReturn((Class) Person.class); when(metadata.getReturnedDomainClass(Mockito.any(Method.class))).thenReturn((Class) Person.class); GemfireQueryMethod method = new GemfireQueryMethod(Sample.class.getMethod("annotated"), metadata, context); + assertThat(method.hasAnnotatedQuery(), is(true)); assertThat(method.getAnnotatedQuery(), is("foo")); method = new GemfireQueryMethod(Sample.class.getMethod("annotatedButEmpty"), metadata, context); + assertThat(method.hasAnnotatedQuery(), is(false)); assertThat(method.getAnnotatedQuery(), is(nullValue())); method = new GemfireQueryMethod(Sample.class.getMethod("notAnnotated"), metadata, context); + assertThat(method.hasAnnotatedQuery(), is(false)); assertThat(method.getAnnotatedQuery(), is(nullValue())); } /** - * @see SGF-112 + * @link http://jira.spring.io/browse/SGF-112 */ @Test public void rejectsQueryMethodWithPageableParameter() throws Exception { + expectedException.expect(IllegalStateException.class); + expectedException.expectCause(is(nullValue(Throwable.class))); + expectedException.expectMessage(Matchers.startsWith("Pagination is not supported by GemFire Repositories!")); - GemfireMappingContext context = new GemfireMappingContext(); - Method method = Invalid.class.getMethod("someMethod", Pageable.class); - - try { - new GemfireQueryMethod(method, metadata, context); - fail("Expected exception!"); - } catch (IllegalStateException e) { - assertThat(e.getMessage(), Matchers.startsWith("Pagination is not supported by Gemfire repositories!")); - } + new GemfireQueryMethod(Invalid.class.getMethod("someMethod", Pageable.class), metadata, context); } + @Test + public void detectsQueryHintsCorrectly() throws Exception { + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"), + metadata, context).hasHint(), is(true)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"), + metadata, context).hasHint(), is(false)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"), + metadata, context).hasHint(), is(true)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("unlimitedQuery"), + metadata, context).hasHint(), is(false)); + } + + @Test + public void detectsQueryImportsCorrectly() throws Exception { + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"), + metadata, context).hasImport(), is(false)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"), + metadata, context).hasImport(), is(true)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"), + metadata, context).hasImport(), is(true)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("unlimitedQuery"), + metadata, context).hasImport(), is(false)); + } + + @Test + public void detectsQueryLimitsCorrectly() throws Exception { + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"), + metadata, context).hasLimit(), is(false)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"), + metadata, context).hasLimit(), is(false)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"), + metadata, context).hasLimit(), is(true)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("unlimitedQuery"), + metadata, context).hasLimit(), is(false)); + } + + @Test + public void detectsQueryTracingCorrectly() throws Exception { + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"), + metadata, context).hasTrace(), is(true)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"), + metadata, context).hasTrace(), is(false)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"), + metadata, context).hasTrace(), is(false)); + assertThat(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("unlimitedQuery"), + metadata, context).hasTrace(), is(true)); + } + + @Test + public void hintOnQueryWithHint() throws Exception { + assertQueryHints(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"), + metadata, context), "IdIdx", "LastNameIdx"); + + assertQueryHints(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"), + metadata, context), "BirthDateIdx"); + } + + @Test + public void hintOnQueryWithNoHints() throws Exception { + assertNoQueryHints(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"), + metadata, context)); + } + + @Test + public void importOnQueryWithImport() throws Exception { + assertImportStatement(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithImport"), + metadata, context), "org.example.app.domain.ExampleType"); + + assertImportStatement(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"), + metadata, context), "org.example.app.domain.Person"); + } + + @Test + public void importOnQueryWithNoImports() throws Exception { + assertNoImportStatement(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("queryWithHint"), + metadata, context)); + } + + @Test + public void limitOnQueryWithLimit() throws Exception { + assertLimitedQuery(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("limitedQuery"), + metadata, context), 1024); + } + + @Test + public void limitOnQueryWithNoLimits() throws Exception { + assertUnlimitedQuery(new GemfireQueryMethod(AnnotatedQueryMethods.class.getMethod("unlimitedQuery"), + metadata, context)); + } + + @SuppressWarnings("unused") interface Sample { @Query("foo") @@ -92,10 +244,34 @@ public class GemfireQueryMethodUnitTests { void annotatedButEmpty(); void notAnnotated(); + } + @SuppressWarnings("unused") interface Invalid { Page someMethod(Pageable pageable); + } + + @SuppressWarnings("unused") + interface AnnotatedQueryMethods { + + @Trace + @Hint({ "IdIdx", "LastNameId" }) + void queryWithHint(); + + @Import("org.example.app.domain.ExampleType") + void queryWithImport(); + + @Hint("BirthDateIdx") + @Import("org.example.app.domain.Person") + @Limit(1024) + void limitedQuery(); + + @Trace + void unlimitedQuery(); + + } + }