diff --git a/.gitignore b/.gitignore index c960a80b..17c95c54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ .DS_Store -target -bin -build -out +bin/ +build/ +out/ +target/ .gradle .springBeans *.iml diff --git a/src/main/asciidoc/reference/repositories.adoc b/src/main/asciidoc/reference/repositories.adoc index 7d8a2d00..668aa892 100644 --- a/src/main/asciidoc/reference/repositories.adoc +++ b/src/main/asciidoc/reference/repositories.adoc @@ -109,6 +109,10 @@ gets to verbose you can annotate the query methods with `@Query` as seen for met | `findByFirstnameNotIn(Collection x)` | `x.firstname NOT IN SET $1` +| `IgnoreCase` +| `findByFirstnameIgnoreCase(String firstName)` +| `x.firstname.equalsIgnoreCase($1)` + | (No keyword) | `findByFirstname(String name)` | `x.firstname = $1` diff --git a/src/main/java/org/springframework/data/gemfire/mapping/GemfireMappingContext.java b/src/main/java/org/springframework/data/gemfire/mapping/GemfireMappingContext.java index 8703a981..5e445e80 100644 --- a/src/main/java/org/springframework/data/gemfire/mapping/GemfireMappingContext.java +++ b/src/main/java/org/springframework/data/gemfire/mapping/GemfireMappingContext.java @@ -24,7 +24,7 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.TypeInformation; /** - * + * * @author Oliver Gierke * @author John Blum */ @@ -58,7 +58,7 @@ public class GemfireMappingContext extends AbstractMappingContext owner, SimpleTypeHolder simpleTypeHolder) { + return new GemfirePersistentProperty(field, descriptor, owner, simpleTypeHolder); } - } diff --git a/src/main/java/org/springframework/data/gemfire/mapping/Regions.java b/src/main/java/org/springframework/data/gemfire/mapping/Regions.java index 009c5d6c..659c0af5 100644 --- a/src/main/java/org/springframework/data/gemfire/mapping/Regions.java +++ b/src/main/java/org/springframework/data/gemfire/mapping/Regions.java @@ -36,21 +36,22 @@ public class Regions implements Iterable> { private final Map> regions; - private final MappingContext, ?> context; + private final MappingContext, ?> mappingContext; /** * Creates a new {@link Regions} wrapper for the given {@link Region}s and * {@link MappingContext}. - * + * * @param regions must not be {@literal null}. - * @param context must not be {@literal null}. + * @param mappingContext must not be {@literal null}. */ - public Regions(Iterable> regions, MappingContext, ?> context) { + public Regions(Iterable> regions, + MappingContext, ?> mappingContext) { - Assert.notNull(regions); - Assert.notNull(context); + Assert.notNull(regions, "Regions must not be null"); + Assert.notNull(mappingContext, "MappingContext must not be null"); - Map> regionMap = new HashMap>(); + Map> regionMap = new HashMap>(); for (Region region : regions) { regionMap.put(region.getName(), region); @@ -58,7 +59,7 @@ public class Regions implements Iterable> { } this.regions = Collections.unmodifiableMap(regionMap); - this.context = context; + this.mappingContext = mappingContext; } /** @@ -67,15 +68,15 @@ public class Regions implements Iterable> { * information is found. * * @param the Region value class type. - * @param type must not be {@literal null}. + * @param entityType must not be {@literal null}. * @return the {@link Region} the given type is mapped to. */ @SuppressWarnings("unchecked") - public Region getRegion(Class type) { - Assert.notNull(type); + public Region getRegion(Class entityType) { + Assert.notNull(entityType, "entityType must not be null"); - GemfirePersistentEntity entity = context.getPersistentEntity(type); - String regionName = (entity != null ? entity.getRegionName() : type.getSimpleName()); + GemfirePersistentEntity entity = mappingContext.getPersistentEntity(entityType); + String regionName = (entity != null ? entity.getRegionName() : entityType.getSimpleName()); return (Region) regions.get(regionName); } @@ -90,18 +91,18 @@ public class Regions implements Iterable> { */ @SuppressWarnings("unchecked") public Region getRegion(String namePath) { - Assert.notNull(namePath); + Assert.hasText(namePath, "Region name/path is required"); + return (Region) regions.get(namePath); } /* * (non-Javadoc) - * + * * @see java.lang.Iterable#iterator() */ @Override public Iterator> iterator() { return regions.values().iterator(); } - } diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreator.java b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreator.java index 9de897b2..2312f873 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreator.java +++ b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreator.java @@ -27,30 +27,32 @@ import org.springframework.data.repository.query.parser.PartTree; /** * Query creator to create {@link QueryString} instances. - * + * * @author Oliver Gierke + * @author John Blum */ class GemfireQueryCreator extends AbstractQueryCreator { private static final Log LOG = LogFactory.getLog(GemfireQueryCreator.class); - private final QueryBuilder query; private Iterator indexes; + private final QueryBuilder queryBuilder; + /** * Creates a new {@link GemfireQueryCreator} using the given {@link PartTree} and domain class. - * + * * @param tree must not be {@literal null}. * @param entity must not be {@literal null}. */ public GemfireQueryCreator(PartTree tree, GemfirePersistentEntity entity) { super(tree); - this.query = new QueryBuilder(entity, tree); + this.queryBuilder = new QueryBuilder(entity, tree); this.indexes = new IndexProvider(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#createQuery(org.springframework.data.domain.Sort) */ @@ -69,7 +71,7 @@ class GemfireQueryCreator extends AbstractQueryCreator return Predicates.create(part, this.indexes); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator) */ @@ -78,7 +80,7 @@ class GemfireQueryCreator extends AbstractQueryCreator return base.and(Predicates.create(part, this.indexes)); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object) */ @@ -87,21 +89,28 @@ class GemfireQueryCreator extends AbstractQueryCreator return base.or(criteria); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#complete(java.lang.Object, org.springframework.data.domain.Sort) */ @Override protected QueryString complete(Predicates criteria, Sort sort) { - QueryString result = query.create(criteria).orderBy(sort); + QueryString query = queryBuilder.create(criteria).orderBy(sort); if (LOG.isDebugEnabled()) { - LOG.debug(String.format("Created Query '%1$s'", result.toString())); + LOG.debug(String.format("Created Query '%1$s'", query.toString())); } - return result; + return query; } + /** + * {@link IndexProvider} is an {@link Iterator} providing sequentially numbered placeholders (starting at 1), + * in a generated GemFire OQL statement corresponding to all possible arguments passed to + * the query's indexed parameters. + * + * @see java.util.Iterator + */ private static class IndexProvider implements Iterator { private int index; @@ -110,17 +119,17 @@ class GemfireQueryCreator extends AbstractQueryCreator this.index = 1; } - /* + /* * (non-Javadoc) * @see java.util.Iterator#hasNext() */ @Override + @SuppressWarnings("all") public boolean hasNext() { - // TODO really? - return index <= Integer.MAX_VALUE; + return (index <= Integer.MAX_VALUE); } - /* + /* * (non-Javadoc) * @see java.util.Iterator#next() */ @@ -129,7 +138,7 @@ class GemfireQueryCreator extends AbstractQueryCreator return index++; } - /* + /* * (non-Javadoc) * @see java.util.Iterator#remove() */ @@ -138,5 +147,4 @@ class GemfireQueryCreator extends AbstractQueryCreator throw new UnsupportedOperationException(); } } - } 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 c53d2030..4227c318 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 @@ -36,7 +36,7 @@ import org.springframework.util.StringUtils; /** * GemFire specific {@link QueryMethod}. - * + * * @author Oliver Gierke * @author John Blum * @see org.springframework.data.repository.query.QueryMethod @@ -50,7 +50,7 @@ public class GemfireQueryMethod extends QueryMethod { /** * Creates a new {@link GemfireQueryMethod} from the given {@link Method} and {@link RepositoryMetadata}. - * + * * @param method must not be {@literal null}. * @param metadata must not be {@literal null}. * @param factory must not be {@literal null}. @@ -77,11 +77,11 @@ public class GemfireQueryMethod extends QueryMethod { * @see org.springframework.data.domain.Pageable * @see java.lang.reflect.Method#getParameterTypes() */ - private void assertNonPagingQueryMethod(final Method method) { + private void assertNonPagingQueryMethod(Method method) { for (Class type : method.getParameterTypes()) { if (Pageable.class.isAssignableFrom(type)) { - throw new IllegalStateException(String.format("Pagination is not supported by GemFire Repositories!" - + " Offending method: %1$s", method.toString())); + throw new IllegalStateException(String.format("Pagination is not supported by GemFire Repositories;" + + " Offending method: %1$s", method.getName())); } } } @@ -108,7 +108,7 @@ public class GemfireQueryMethod extends QueryMethod { /** * Returns the annotated query for the query method if present. - * + * * @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) diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/Predicate.java b/src/main/java/org/springframework/data/gemfire/repository/query/Predicate.java index c07b5412..9bd42662 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/query/Predicate.java +++ b/src/main/java/org/springframework/data/gemfire/repository/query/Predicate.java @@ -3,4 +3,5 @@ package org.springframework.data.gemfire.repository.query; interface Predicate { String toString(String alias); -} \ No newline at end of file + +} diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/Predicates.java b/src/main/java/org/springframework/data/gemfire/repository/query/Predicates.java index 3149d221..349bd7ec 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/query/Predicates.java +++ b/src/main/java/org/springframework/data/gemfire/repository/query/Predicates.java @@ -27,7 +27,7 @@ class Predicates implements Predicate { /** * Creates a new {@link Predicates} wrapper instance. - * + * * @param predicate must not be {@literal null}. */ private Predicates(Predicate predicate) { @@ -40,23 +40,22 @@ class Predicates implements Predicate { /** * Creates a new Predicate for the given {@link Part} and index iterator. - * + * * @param part must not be {@literal null}. - * @param value must not be {@literal null}. - * @return + * @param indexes must not be {@literal null}. + * @return an instance of {@link Predicates} wrapping the WHERE clause condition expression ({@link Part}). */ - public static Predicates create(Part part, Iterator value) { - return create(new AtomicPredicate(part, value)); + public static Predicates create(Part part, Iterator indexes) { + return create(new AtomicPredicate(part, indexes)); } /** * And-concatenates the given {@link Predicate} to the current one. - * + * * @param predicate must not be {@literal null}. - * @return + * @return an instance of {@link Predicates} wrapping an AND condition. */ public Predicates and(final Predicate predicate) { - return create(new Predicate() { @Override public String toString(String alias) { @@ -67,12 +66,11 @@ class Predicates implements Predicate { /** * Or-concatenates the given {@link Predicate} to the current one. - * + * * @param predicate must not be {@literal null}. - * @return + * @return an instance of {@link Predicates} wrapping an OR condition. */ public Predicates or(final Predicate predicate) { - return create(new Predicate() { @Override public String toString(String alias) { @@ -92,66 +90,96 @@ class Predicates implements Predicate { /** * Predicate to create a predicate expression for a {@link Part}. - * + * * @author Oliver Gierke */ public static class AtomicPredicate implements Predicate { + private final Iterator indexes; + private final Part part; - private final Iterator value; /** * Creates a new {@link AtomicPredicate}. - * + * * @param part must not be {@literal null}. - * @param value must not be {@literal null}. + * @param indexes must not be {@literal null}. */ - public AtomicPredicate(Part part, Iterator value) { - - Assert.notNull(part); - Assert.notNull(value); + public AtomicPredicate(Part part, Iterator indexes) { + Assert.notNull(part, "Query Predicate Part must not be null"); + Assert.notNull(indexes, "Iterator of numeric, indexed query parameter placeholders must not be null"); this.part = part; - this.value = value; + this.indexes = indexes; } - /* - * (non-Javadoc) + /** + * Builds a conditional expression for the entity property in the WHERE clause of the GemFire OQL + * query statement. + * * @see org.springframework.data.gemfire.repository.query.Predicate#toString(java.lang.String) */ @Override public String toString(String alias) { - Type type = part.getType(); + if (isIgnoreCase()) { + return String.format("%s.equalsIgnoreCase($%d)", resolveProperty(alias), indexes.next()); + } + else { + Type partType = part.getType(); - return String.format("%s.%s %s", alias == null ? QueryBuilder.DEFAULT_ALIAS : alias, - part.getProperty().toDotPath(), toClause(type)); - } - - private String toClause(Type type) { - switch (type) { - case FALSE: - case TRUE: - return String.format("%1$s %2$s", getOperator(type), Type.TRUE.equals(type)); - case IS_NULL: - case IS_NOT_NULL: - return String.format("%s NULL", getOperator(type)); - default: - return String.format("%s $%s", getOperator(type), value.next()); + switch (partType) { + case IS_NULL: + case IS_NOT_NULL: + return String.format("%s %s NULL", resolveProperty(alias), resolveOperator(partType)); + case FALSE: + case TRUE: + return String.format("%s %s %s", resolveProperty(alias), resolveOperator(partType), + Type.TRUE.equals(partType)); + default: + return String.format("%s %s $%d", resolveProperty(alias), resolveOperator(partType), + indexes.next()); + } } } + boolean isIgnoreCase() { + switch (part.shouldIgnoreCase()) { + case ALWAYS: + case WHEN_POSSIBLE: + return true; + case NEVER: + default: + return false; + } + } + + String resolveProperty(String alias) { + return String.format("%1$s.%2$s", resolveAlias(alias), part.getProperty().toDotPath()); + } + + String resolveAlias(String alias) { + return (alias != null ? alias : QueryBuilder.DEFAULT_ALIAS); + } + /** - * Maps the given {@link Type} to an OQL operator. - * - * @param type - * @return + * Resolves the given {@link Type} as an GemFire OQL operator. + * + * @param partType the conditional expression (e.g. 'IN') in the query method name. + * @return a GemFire OQL operator. */ - private String getOperator(Type type) { - switch (type) { - case IN: - return "IN SET"; - case NOT_IN: - return "NOT IN SET"; + String resolveOperator(Type partType) { + switch (partType) { + // Equality - Is + case FALSE: + case IS_NULL: + case SIMPLE_PROPERTY: + case TRUE: + return "="; + // Equality - Is Not + case IS_NOT_NULL: + case NEGATING_SIMPLE_PROPERTY: + return "!="; + // Relational Comparison case GREATER_THAN: return ">"; case GREATER_THAN_EQUAL: @@ -160,26 +188,19 @@ class Predicates implements Predicate { return "<"; case LESS_THAN_EQUAL: return "<="; - case IS_NOT_NULL: - case NEGATING_SIMPLE_PROPERTY: - return "!="; - /* - NOTE unfortunately, 'NOT LIKE' operator is not supported by GemFire's Query/OQL syntax - case NOT_LIKE: - return "NOT LIKE"; - */ + // Set Containment + case IN: + return "IN SET"; + case NOT_IN: + return "NOT IN SET"; + // Wildcard Matching case LIKE: case STARTING_WITH: case ENDING_WITH: case CONTAINING: return "LIKE"; - case FALSE: - case IS_NULL: - case SIMPLE_PROPERTY: - case TRUE: - return "="; default: - throw new IllegalArgumentException(String.format("Unsupported operator %s!", type)); + throw new IllegalArgumentException(String.format("Unsupported operator %s!", partType)); } } } diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/QueryBuilder.java b/src/main/java/org/springframework/data/gemfire/repository/query/QueryBuilder.java index c304c627..dead037d 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/query/QueryBuilder.java +++ b/src/main/java/org/springframework/data/gemfire/repository/query/QueryBuilder.java @@ -56,5 +56,4 @@ class QueryBuilder { public String toString() { return query; } - } diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/QueryString.java b/src/main/java/org/springframework/data/gemfire/repository/query/QueryString.java index 908c9be1..9a6a9f86 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/query/QueryString.java +++ b/src/main/java/org/springframework/data/gemfire/repository/query/QueryString.java @@ -30,7 +30,7 @@ import com.gemstone.gemfire.cache.Region; /** * Value object to work with OQL query strings. - * + * * @author Oliver Gierke * @author David Turanski * @author John Blum @@ -58,7 +58,7 @@ public class QueryString { /** * Creates a {@link QueryString} from the given {@link String} query. - * + * * @param source a String containing the OQL Query. */ public QueryString(String source) { @@ -68,7 +68,7 @@ public class QueryString { /** * Creates a {@literal SELECT} query for the given domain class. - * + * * @param domainClass must not be {@literal null}. */ @SuppressWarnings("unused") @@ -78,7 +78,7 @@ public class QueryString { /** * Creates a {@literal SELECT} query for the given domain class. - * + * * @param domainClass must not be {@literal null}. * @param isCountQuery indicates if this is a count query */ @@ -118,7 +118,7 @@ public class QueryString { /** * Returns the parameter indexes used in this query. - * + * * @return the parameter indexes used in this query or an empty {@link Iterable} if none are used. * @see java.lang.Iterable */ @@ -195,5 +195,4 @@ public class QueryString { public String toString() { return query; } - } 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 ebbb5608..523e205e 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 @@ -56,7 +56,7 @@ 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 queryMethod must not be {@literal null}. * @param template must not be {@literal null}. */ @@ -67,7 +67,7 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { /** * Creates a new {@link StringBasedGemfireRepositoryQuery} using the given query {@link String}, * {@link GemfireQueryMethod} and {@link GemfireTemplate}. - * + * * @param query will fall back to the query annotated to the given {@link GemfireQueryMethod} if {@literal null}. * @param queryMethod must not be {@literal null}. * @param template must not be {@literal null}. diff --git a/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java index 487f47f3..10bad37d 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java +++ b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.data.gemfire.repository.support; import java.io.Serializable; import java.lang.reflect.Method; +import com.gemstone.gemfire.cache.Region; + import org.springframework.data.gemfire.GemfireTemplate; import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; import org.springframework.data.gemfire.mapping.GemfirePersistentProperty; @@ -33,42 +36,42 @@ import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import com.gemstone.gemfire.cache.Region; - /** * {@link RepositoryFactorySupport} implementation creating repository proxies * for Gemfire. - * + * * @author Oliver Gierke * @author David Turanski * @author John Blum */ public class GemfireRepositoryFactory extends RepositoryFactorySupport { - private final MappingContext, GemfirePersistentProperty> context; + private final MappingContext, GemfirePersistentProperty> mappingContext; + private final Regions regions; /** * Creates a new {@link GemfireRepositoryFactory}. - * + * * @param regions must not be {@literal null}. - * @param context the {@link MappingContext} used by the constructed Repository for mapping entities + * @param mappingContext the {@link MappingContext} used by the constructed Repository for mapping entities * to the underlying data store, must not be {@literal null}. */ - public GemfireRepositoryFactory(Iterable> regions, MappingContext, - GemfirePersistentProperty> context) { - - Assert.notNull(regions, "Regions must not be null!"); - Assert.notNull(context, "MappingContext must not be null!"); - - this.context = context; - this.regions = new Regions(regions, this.context); + public GemfireRepositoryFactory(Iterable> regions, + MappingContext, GemfirePersistentProperty> mappingContext) { + + Assert.notNull(regions, "Regions must not be null"); + Assert.notNull(mappingContext, "MappingContext must not be null"); + + this.mappingContext = mappingContext; + this.regions = new Regions(regions, this.mappingContext); } /* @@ -78,7 +81,7 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport { @Override @SuppressWarnings("unchecked") public GemfireEntityInformation getEntityInformation(Class domainClass) { - GemfirePersistentEntity entity = (GemfirePersistentEntity) context.getPersistentEntity(domainClass); + GemfirePersistentEntity entity = (GemfirePersistentEntity) mappingContext.getPersistentEntity(domainClass); return new DefaultGemfireEntityInformation(entity); } @@ -96,8 +99,8 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport { return getTargetRepositoryViaReflection(repositoryInformation, gemfireTemplate, entityInformation); } - private GemfireTemplate getTemplate(RepositoryMetadata metadata) { - GemfirePersistentEntity entity = context.getPersistentEntity(metadata.getDomainType()); + GemfireTemplate getTemplate(RepositoryMetadata metadata) { + GemfirePersistentEntity entity = mappingContext.getPersistentEntity(metadata.getDomainType()); String entityRegionName = entity.getRegionName(); String repositoryRegionName = getRepositoryRegionName(metadata.getRepositoryInterface()); @@ -106,9 +109,9 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport { Region region = regions.getRegion(regionName); if (region == null) { - throw new IllegalStateException(String.format("No Region '%1$s' found for domain class %2$s!" - + " Make sure you have configured a GemFire Region of that name in your application context!", - regionName, metadata.getDomainType())); + throw new IllegalStateException(String.format("No Region '%1$s' found for domain class %2$s;" + + " Make sure you have configured a GemFire Region of that name in your application context", + regionName, metadata.getDomainType().getName())); } Class regionKeyType = region.getAttributes().getKeyConstraint(); @@ -116,16 +119,16 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport { if (regionKeyType != null && entity.getIdProperty() != null) { Assert.isTrue(regionKeyType.isAssignableFrom(entityIdType), String.format( - "The Region referenced only supports keys of type %1$s but the entity to be stored has an id of type %2$s!", - regionKeyType, entityIdType)); + "The Region referenced only supports keys of type %1$s, but the entity to be stored has an id of type %2$s", + regionKeyType.getName(), entityIdType.getName())); } return new GemfireTemplate(region); } - private String getRepositoryRegionName(final Class repositoryClass) { - return (repositoryClass.isAnnotationPresent(org.springframework.data.gemfire.mapping.Region.class) ? - repositoryClass.getAnnotation(org.springframework.data.gemfire.mapping.Region.class).value() : null); + String getRepositoryRegionName(Class repositoryInterface) { + return (repositoryInterface.isAnnotationPresent(org.springframework.data.gemfire.mapping.Region.class) ? + repositoryInterface.getAnnotation(org.springframework.data.gemfire.mapping.Region.class).value() : null); } /* @@ -142,38 +145,32 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport { /* * (non-Javadoc) * - * @see springframework.data.repository.core.support.RepositoryFactorySupport - * #getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key) + * @see org.springframework.data.repository.core.support.RepositoryFactorySupport + * #getQueryLookupStrategy(Key, EvaluationContextProvider) */ @Override - protected QueryLookupStrategy getQueryLookupStrategy(Key key) { + protected QueryLookupStrategy getQueryLookupStrategy(Key key, EvaluationContextProvider evaluationContextProvider) { return new QueryLookupStrategy() { - public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) { - return resolveQuery(method, metadata, null, namedQueries); - } - + @Override public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries) { - - GemfireQueryMethod queryMethod = new GemfireQueryMethod(method, metadata, factory, context); + + GemfireQueryMethod queryMethod = new GemfireQueryMethod(method, metadata, factory, mappingContext); GemfireTemplate template = getTemplate(metadata); if (queryMethod.hasAnnotatedQuery()) { return new StringBasedGemfireRepositoryQuery(queryMethod, template).asUserDefinedQuery(); } - String namedQueryName = queryMethod.getNamedQueryName(); - - if (namedQueries.hasQuery(namedQueryName)) { - return new StringBasedGemfireRepositoryQuery(namedQueries.getQuery(namedQueryName), queryMethod, - template).asUserDefinedQuery(); + if (namedQueries.hasQuery(queryMethod.getNamedQueryName())) { + return new StringBasedGemfireRepositoryQuery(namedQueries.getQuery(queryMethod.getNamedQueryName()), + queryMethod, template).asUserDefinedQuery(); } return new PartTreeGemfireRepositoryQuery(queryMethod, template); } }; } - } diff --git a/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreatorUnitTests.java b/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreatorUnitTests.java index 2d1a167c..4dcbc00b 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreatorUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryCreatorUnitTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.gemfire.repository.query; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -27,8 +28,9 @@ import org.springframework.data.repository.query.parser.PartTree; /** * Unit tests for {@link GemfireQueryCreator}. - * + * * @author Oliver Gierke + * @author John Blum */ public class GemfireQueryCreatorUnitTests { @@ -37,18 +39,28 @@ public class GemfireQueryCreatorUnitTests { @Before @SuppressWarnings("unchecked") public void setUp() { - - GemfireMappingContext context = new GemfireMappingContext(); - entity = (GemfirePersistentEntity) context.getPersistentEntity(Person.class); + entity = (GemfirePersistentEntity) new GemfireMappingContext().getPersistentEntity(Person.class); } @Test public void createsQueryForSimplePropertyReferenceCorrectly() { + PartTree partTree = new PartTree("findByLastname", Person.class); - PartTree partTree = new PartTree("findByFirstname", Person.class); - GemfireQueryCreator creator = new GemfireQueryCreator(partTree, entity); + GemfireQueryCreator queryCreator = new GemfireQueryCreator(partTree, entity); - QueryString query = creator.createQuery(); - assertThat(query.toString(), is("SELECT * FROM /simple x WHERE x.firstname = $1")); + QueryString query = queryCreator.createQuery(); + + assertThat(query.toString(), is(equalTo("SELECT * FROM /simple x WHERE x.lastname = $1"))); + } + + @Test + public void createsQueryForNestedPropertyReferenceCorrectly() { + PartTree partTree = new PartTree("findPersonByAddressCity", Person.class); + + GemfireQueryCreator queryCreator = new GemfireQueryCreator(partTree, entity); + + QueryString query = queryCreator.createQuery(); + + assertThat(query.toString(), is(equalTo("SELECT * FROM /simple x WHERE x.address.city = $1"))); } } 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 d83f18f8..d67844d0 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 @@ -16,9 +16,12 @@ 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; @@ -47,7 +50,7 @@ import org.springframework.util.ObjectUtils; /** * Unit tests for {@link GemfireQueryMethod}. - * + * * @author Oliver Gierke * @author John Blum */ @@ -58,7 +61,7 @@ public class GemfireQueryMethodUnitTests { public ExpectedException expectedException = ExpectedException.none(); private GemfireMappingContext context = new GemfireMappingContext(); - + private ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); @Mock @@ -115,6 +118,7 @@ public class GemfireQueryMethodUnitTests { } @Before + @SuppressWarnings("unchecked") public void setup() { when(metadata.getDomainType()).thenReturn((Class) Person.class); when(metadata.getReturnedDomainClass(Mockito.any(Method.class))).thenReturn((Class) Person.class); @@ -146,7 +150,7 @@ public class GemfireQueryMethodUnitTests { 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!")); + expectedException.expectMessage(Matchers.startsWith("Pagination is not supported by GemFire Repositories; Offending method: someMethod")); new GemfireQueryMethod(Invalid.class.getMethod("someMethod", Pageable.class), metadata, factory, context); } @@ -280,5 +284,4 @@ public class GemfireQueryMethodUnitTests { void unlimitedQuery(); } - } diff --git a/src/test/java/org/springframework/data/gemfire/repository/query/PredicatesUnitTests.java b/src/test/java/org/springframework/data/gemfire/repository/query/PredicatesUnitTests.java index 86d36844..a6246eb8 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/query/PredicatesUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/repository/query/PredicatesUnitTests.java @@ -15,8 +15,9 @@ */ package org.springframework.data.gemfire.repository.query; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertNotNull; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import java.util.Arrays; @@ -28,7 +29,8 @@ import org.springframework.data.gemfire.repository.query.Predicates.AtomicPredic import org.springframework.data.repository.query.parser.Part; /** - * + * Unit tests for {@link Predicates}. + * * @author Oliver Gierke * @author John Blum */ @@ -37,49 +39,67 @@ public class PredicatesUnitTests { @Test public void atomicPredicateDefaultsAlias() { - Part part = new Part("firstname", Person.class); - Iterable indexes = Arrays.asList(1); + Iterator indexes = Collections.singletonList(1).iterator(); + + Predicate predicate = new AtomicPredicate(part, indexes); - Predicate predicate = new AtomicPredicate(part, indexes.iterator()); assertThat(predicate.toString(null), is("x.firstname = $1")); } @Test public void concatenatesAndPredicateCorrectly() { - Part left = new Part("firstname", Person.class); Part right = new Part("lastname", Person.class); + Iterator indexes = Arrays.asList(1, 2).iterator(); - Predicates predicate = Predicates.create(left, indexes); - predicate = predicate.and(new AtomicPredicate(right, indexes)); + Predicate predicate = Predicates.create(left, indexes).and(Predicates.create(right, indexes)); + assertThat(predicate, is(notNullValue(Predicate.class))); assertThat(predicate.toString(null), is("x.firstname = $1 AND x.lastname = $2")); } @Test public void concatenatesOrPredicateCorrectly() { - Part left = new Part("firstname", Person.class); Part right = new Part("lastname", Person.class); + Iterator indexes = Arrays.asList(1, 2).iterator(); - Predicates predicate = Predicates.create(left, indexes); - predicate = predicate.or(new AtomicPredicate(right, indexes)); + Predicate predicate = Predicates.create(left, indexes).or(Predicates.create(right, indexes)); - assertThat(predicate.toString(null), is("x.firstname = $1 OR x.lastname = $2")); + assertThat(predicate, is(notNullValue(Predicate.class))); + assertThat(predicate.toString(null), is(equalTo("x.firstname = $1 OR x.lastname = $2"))); } @Test - public void testBooleanBasedPredicate() { + public void handlesBooleanBasedPredicateCorrectly() { Part part = new Part("activeTrue", User.class); - Iterator indexes = Collections.emptyList().iterator(); + + Iterator indexes = Collections.singletonList(1).iterator(); + Predicates predicate = Predicates.create(part, indexes); - assertNotNull(predicate); - assertThat(predicate.toString("user"), is("user.active = true")); + assertThat(predicate, is(notNullValue(Predicate.class))); + assertThat(predicate.toString("user"), is(equalTo("user.active = true"))); + } + + /** + * @link https://jira.spring.io/browse/SGF-507 + */ + @Test + public void handlesIgnoreCasePredicateCorrectly() { + Part left = new Part("firstnameIgnoreCase", Person.class); + Part right = new Part("lastnameIgnoreCase", Person.class); + + Iterator indexes = Arrays.asList(1, 2).iterator(); + + Predicate predicate = Predicates.create(left, indexes).and(Predicates.create(right, indexes)); + + assertThat(predicate, is(notNullValue(Predicate.class))); + assertThat(predicate.toString("person"), is(equalTo("person.firstname.equalsIgnoreCase($1) AND person.lastname.equalsIgnoreCase($2)"))); } static class Person { @@ -87,9 +107,9 @@ public class PredicatesUnitTests { String lastname; } + // TODO refactor Person to include boolean state; remove User static class User { Boolean active; String username; } - } diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/IncompatibleRegionKeyEntityIdAnimalRepositoryTest.java b/src/test/java/org/springframework/data/gemfire/repository/sample/IncompatibleRegionKeyEntityIdAnimalRepositoryTest.java index dc02e99f..1faf3352 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/IncompatibleRegionKeyEntityIdAnimalRepositoryTest.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/IncompatibleRegionKeyEntityIdAnimalRepositoryTest.java @@ -44,7 +44,7 @@ public class IncompatibleRegionKeyEntityIdAnimalRepositoryTest { "IncompatibleRegionKeyEntityIdAnimalRepositoryTest-context.xml"); @Test(expected = IllegalArgumentException.class) - public void testStoreAnimalHavingLongIdInRabbitsRegionWithStringKey() { + public void storeAnimalHavingLongIdInRabbitsRegionWithStringKey() { try { ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext( APPLICATION_CONTEXT_CONFIG_LOCATION); @@ -62,10 +62,9 @@ public class IncompatibleRegionKeyEntityIdAnimalRepositoryTest { catch (BeanCreationException expected) { //expected.printStackTrace(System.err); assertTrue(expected.getCause() instanceof IllegalArgumentException); - assertEquals(String.format("The Region referenced only supports keys of type %1$s but the entity to be stored has an id of type %2$s!", - String.class, Long.class), expected.getCause().getMessage()); + assertEquals(String.format("The Region referenced only supports keys of type %1$s, but the entity to be stored has an id of type %2$s", + String.class.getName(), Long.class.getName()), expected.getCause().getMessage()); throw (IllegalArgumentException) expected.getCause(); } } - } diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/PersonRepository.java b/src/test/java/org/springframework/data/gemfire/repository/sample/PersonRepository.java index da5df52c..a4c28395 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/PersonRepository.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/PersonRepository.java @@ -21,10 +21,11 @@ import java.util.List; import org.springframework.data.domain.Sort; import org.springframework.data.gemfire.repository.GemfireRepository; import org.springframework.data.gemfire.repository.Query; +import org.springframework.data.gemfire.repository.query.annotation.Trace; /** * Sample Repository interface managing {@link Person}s. - * + * * @author Oliver Gierke * @author David Turanski * @author John Blum @@ -39,23 +40,26 @@ public interface PersonRepository extends GemfireRepository { Collection findByFirstname(String firstname); - Collection findByFirstnameIn(Collection firstnames); + Collection findByFirstnameContaining(String firstName); - Collection findByFirstnameIn(String... firstnames); + Collection findByFirstnameIn(Collection firstNames); - Collection findByFirstnameAndLastname(String firstname, String lastname); + Collection findByFirstnameIn(String... firstNames); - Collection findByFirstnameOrLastname(String firstname, String lastname); + Collection findByFirstnameLike(String firstName); - Person findByLastname(String lastname); + Collection findByFirstnameStartingWith(String firstName); - Collection findByFirstnameLike(String firstname); + Collection findByFirstnameAndLastname(String firstName, String lastName); - Collection findByFirstnameStartingWith(String firstname); + @Trace + Collection findByFirstnameIgnoreCaseAndLastnameIgnoreCase(String firstName, String lastName); - Collection findByLastnameEndingWith(String lastname); + Collection findByFirstnameOrLastname(String firstName, String lastName); - Collection findByFirstnameContaining(String firstname); + Person findByLastname(String lastName); + + Collection findByLastnameEndingWith(String lastName); List findDistinctByLastname(String lastName, Sort order); diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/PersonRepositoryTest.java b/src/test/java/org/springframework/data/gemfire/repository/sample/PersonRepositoryTest.java index a0d35eb5..88f0e8f2 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/PersonRepositoryTest.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/PersonRepositoryTest.java @@ -16,19 +16,34 @@ package org.springframework.data.gemfire.repository.sample; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Properties; import java.util.concurrent.atomic.AtomicLong; +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.RegionAttributes; + 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.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; import org.springframework.data.domain.Sort; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.data.gemfire.LocalRegionFactoryBean; +import org.springframework.data.gemfire.RegionAttributesFactoryBean; +import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -44,75 +59,142 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner * @since 1.4.0 */ -@ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = PersonRepositoryTest.GemFireConfiguration.class) @SuppressWarnings("unused") public class PersonRepositoryTest { + private static final String GEMFIRE_LOG_LEVEL = System.getProperty("gemfire.log-level", "warning"); + protected final AtomicLong ID_SEQUENCE = new AtomicLong(0l); - private Person cookieDoe = createPerson("Cookie", "Doe"); - private Person janeDoe = createPerson("Jane", "Doe"); - private Person jonDoe = createPerson("Jon", "Doe"); - private Person pieDoe = createPerson("Pie", "Doe"); - private Person jackHandy = createPerson("Jack", "Handy"); - private Person sandyHandy = createPerson("Sandy", "Handy"); - private Person imaPigg = createPerson("Ima", "Pigg"); + private Person cookieDoe = newPerson("Cookie", "Doe"); + private Person janeDoe = newPerson("Jane", "Doe"); + private Person jonDoe = newPerson("Jon", "Doe"); + private Person pieDoe = newPerson("Pie", "Doe"); + private Person jackHandy = newPerson("Jack", "Handy"); + private Person sandyHandy = newPerson("Sandy", "Handy"); + private Person imaPigg = newPerson("Ima", "Pigg"); @Autowired - private PersonRepository personRepo; + private PersonRepository personRepository; @Before public void setup() { - if (personRepo.count() == 0) { - sandyHandy = personRepo.save(sandyHandy); - jonDoe = personRepo.save(jonDoe); - jackHandy = personRepo.save(jackHandy); - janeDoe = personRepo.save(janeDoe); - pieDoe = personRepo.save(pieDoe); - imaPigg = personRepo.save(imaPigg); - cookieDoe = personRepo.save(cookieDoe); + if (personRepository.count() == 0) { + sandyHandy = personRepository.save(sandyHandy); + jonDoe = personRepository.save(jonDoe); + jackHandy = personRepository.save(jackHandy); + janeDoe = personRepository.save(janeDoe); + pieDoe = personRepository.save(pieDoe); + imaPigg = personRepository.save(imaPigg); + cookieDoe = personRepository.save(cookieDoe); } - assertEquals(7l, personRepo.count()); + assertThat(personRepository.count(), is(equalTo(7L))); } - protected Person createPerson(final String firstName, final String lastName) { + protected Person newPerson(String firstName, String lastName) { return new Person(ID_SEQUENCE.incrementAndGet(), firstName, lastName); } - protected Sort.Order createOrder(final String property) { - return createOrder(property, Sort.Direction.ASC); + protected Sort.Order newOrder(String property) { + return newOrder(property, Sort.Direction.ASC); } - protected Sort.Order createOrder(final String property, final Sort.Direction direction) { + protected Sort.Order newOrder(String property, Sort.Direction direction) { return new Sort.Order(direction, property); } - protected Sort createSort(final Sort.Order... orders) { + protected Sort newSort(Sort.Order... orders) { return new Sort(orders); } @Test - public void testFindDistinctPeopleWithOrder() { - List actualPeople = personRepo.findDistinctPeopleByOrderByLastnameDesc( - createSort(createOrder("firstname"))); + public void findDistinctPeopleOrderedByFirstnameDescending() { + List actualPeople = personRepository.findDistinctPeopleByOrderByLastnameDesc( + newSort(newOrder("firstname"))); - assertNotNull(actualPeople); - assertFalse(actualPeople.isEmpty()); - assertEquals(7, actualPeople.size()); - assertEquals(Arrays.asList(imaPigg, jackHandy, sandyHandy, cookieDoe, janeDoe, jonDoe, pieDoe), actualPeople); + assertThat(actualPeople, is(notNullValue(List.class))); + assertThat(actualPeople.size(), is(equalTo(7))); + assertThat(actualPeople, is(equalTo(Arrays.asList( + imaPigg, jackHandy, sandyHandy, cookieDoe, janeDoe, jonDoe, pieDoe)))); } @Test - public void testFindDistinctPersonWithNoOrder() { - List actualPeople = personRepo.findDistinctByLastname("Pigg", null); + public void findDistinctPersonWithUnordered() { + List actualPeople = personRepository.findDistinctByLastname("Handy", null); - assertNotNull(actualPeople); - assertFalse(actualPeople.isEmpty()); - assertEquals(1, actualPeople.size()); - assertEquals(String.format("Expected '%1$s'; but was '%2$s'", imaPigg, actualPeople.get(0)), - imaPigg, actualPeople.get(0)); + assertThat(actualPeople, is(notNullValue(List.class))); + assertThat(actualPeople.size(), is(equalTo(2))); + assertThat(String.format("Expected '%1$s'; but was '%2$s'", Arrays.asList(jackHandy, sandyHandy), actualPeople), + actualPeople, contains(jackHandy, sandyHandy)); } + @Test + public void findPersonByFirstAndLastNameIgnoringCase() { + Collection people = personRepository.findByFirstnameIgnoreCaseAndLastnameIgnoreCase("jON", "doE"); + + assertThat(people, is(notNullValue(Collection.class))); + assertThat(people.size(), is(equalTo(1))); + assertThat(people.iterator().next(), is(equalTo(jonDoe))); + } + + @Configuration + @EnableGemfireRepositories(basePackages = "org.springframework.data.gemfire.repository.sample", + includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, + value = org.springframework.data.gemfire.repository.sample.PersonRepository.class)) + public static class GemFireConfiguration { + + String applicationName() { + return PersonRepositoryTest.class.getSimpleName(); + } + + String logLevel() { + return GEMFIRE_LOG_LEVEL; + } + + Properties gemfireProperties() { + Properties gemfireProperties = new Properties(); + + gemfireProperties.setProperty("name", applicationName()); + gemfireProperties.setProperty("mcast-port", "0"); + gemfireProperties.setProperty("log-level", logLevel()); + + return gemfireProperties; + } + + @Bean + CacheFactoryBean gemfireCache() { + CacheFactoryBean gemfireCache = new CacheFactoryBean(); + + gemfireCache.setClose(true); + gemfireCache.setProperties(gemfireProperties()); + + return gemfireCache; + } + + @Bean(name = "simple") + LocalRegionFactoryBean simpleRegion(Cache gemfireCache, RegionAttributes simpleRegionAttributes) { + LocalRegionFactoryBean simpleRegion = new LocalRegionFactoryBean(); + + simpleRegion.setAttributes(simpleRegionAttributes); + simpleRegion.setCache(gemfireCache); + simpleRegion.setClose(false); + simpleRegion.setPersistent(false); + + return simpleRegion; + } + + @Bean + @SuppressWarnings("unchecked") + RegionAttributesFactoryBean simpleRegionAttributes() { + RegionAttributesFactoryBean simpleRegionAttributes = new RegionAttributesFactoryBean(); + + simpleRegionAttributes.setKeyConstraint(Long.class); + simpleRegionAttributes.setValueConstraint(Person.class); + + return simpleRegionAttributes; + } + } } diff --git a/src/test/java/org/springframework/data/gemfire/repository/sample/PlantRepositoryTest.java b/src/test/java/org/springframework/data/gemfire/repository/sample/PlantRepositoryTest.java index 2ae52e66..495e5483 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/sample/PlantRepositoryTest.java +++ b/src/test/java/org/springframework/data/gemfire/repository/sample/PlantRepositoryTest.java @@ -43,7 +43,7 @@ public class PlantRepositoryTest { "PlantRepositoryTest-context.xml"); @Test(expected = IllegalArgumentException.class) - public void testStorePlantHavingStringIdInPlantsRegionWithLongKey() { + public void storePlantHavingStringIdInPlantsRegionWithLongKey() { try { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext( APPLICATION_CONTEXT_CONFIG_LOCATION); @@ -54,10 +54,9 @@ public class PlantRepositoryTest { catch (BeanCreationException expected) { //expected.printStackTrace(System.err); assertTrue(expected.getCause() instanceof IllegalArgumentException); - assertEquals(String.format("The Region referenced only supports keys of type %1$s but the entity to be stored has an id of type %2$s!", - Long.class, String.class), expected.getCause().getMessage()); + assertEquals(String.format("The Region referenced only supports keys of type %1$s, but the entity to be stored has an id of type %2$s", + Long.class.getName(), String.class.getName()), expected.getCause().getMessage()); throw (IllegalArgumentException) expected.getCause(); } } - } diff --git a/src/test/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryUnitTests.java b/src/test/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryUnitTests.java index 604accb1..6ee2499b 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactoryUnitTests.java @@ -13,35 +13,46 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.data.gemfire.repository.support; -import static org.junit.Assert.assertSame; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import java.io.Serializable; +import java.util.Arrays; import java.util.Collections; -import org.hamcrest.Matchers; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionAttributes; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; import org.springframework.aop.framework.Advised; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; import org.springframework.data.gemfire.GemfireTemplate; import org.springframework.data.gemfire.mapping.GemfireMappingContext; import org.springframework.data.gemfire.repository.GemfireRepository; import org.springframework.data.gemfire.repository.sample.Person; import org.springframework.data.repository.PagingAndSortingRepository; -import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.EntityInformation; - -import com.gemstone.gemfire.cache.Region; -import com.gemstone.gemfire.cache.RegionAttributes; +import org.springframework.data.repository.core.RepositoryMetadata; /** * Unit tests for {@link GemfireRepositoryFactory}. @@ -54,80 +65,235 @@ import com.gemstone.gemfire.cache.RegionAttributes; @SuppressWarnings("unused") public class GemfireRepositoryFactoryUnitTests { + @Rule + public ExpectedException exception = ExpectedException.none(); + + private GemfireMappingContext gemfireMappingContext = new GemfireMappingContext(); + @Mock - private Region region; + private Region mockRegion; @Mock @SuppressWarnings("rawtypes") - private RegionAttributes attributes; + private RegionAttributes mockRegionAttributes; + + @SuppressWarnings("unchecked") + protected Region configureMockRegion(Region mockRegion, String name, + Class keyType, Class valueType) { + + when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes); + when(mockRegion.getFullPath()).thenReturn(String.format("%1$s%2$s", Region.SEPARATOR, name)); + when(mockRegion.getName()).thenReturn(name); + when(mockRegionAttributes.getKeyConstraint()).thenReturn(keyType); + when(mockRegionAttributes.getValueConstraint()).thenReturn(valueType); + + return mockRegion; + } + + @SuppressWarnings("unchecked") + protected Region mockRegion(String name, Class keyType, Class valueType) { + return configureMockRegion(mock(Region.class, name), name, keyType, valueType); + } + + protected RepositoryMetadata mockRepositoryMetadata(final Class domainType, final Class idType, + final Class repositoryInterface) { + + RepositoryMetadata mockRepositoryMetadata = mock(RepositoryMetadata.class); + + when(mockRepositoryMetadata.getDomainType()).then(new Answer>() { + @Override public Class answer(InvocationOnMock invocation) throws Throwable { + return domainType; + } + }); + + when(mockRepositoryMetadata.getIdType()).then(new Answer>() { + @Override public Class answer(InvocationOnMock invocation) throws Throwable { + return idType; + } + }); + + when(mockRepositoryMetadata.getRepositoryInterface()).then(new Answer>() { + @Override public Class answer(InvocationOnMock invocation) throws Throwable { + return repositoryInterface; + } + }); + + return mockRepositoryMetadata; + } @Before @SuppressWarnings("unchecked") public void setup() { - when(region.getName()).thenReturn("simple"); - when(region.getFullPath()).thenReturn("/simple"); - when(region.getAttributes()).thenReturn(attributes); + configureMockRegion(mockRegion, "simple", Object.class, Object.class); + } + + @Test + public void constructGemfireRepositoryFactoryWithNullMappingContextThrowsIllegalArgumentException() { + exception.expect(IllegalArgumentException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage("MappingContext must not be null"); + + new GemfireRepositoryFactory(Collections.>emptyList(), null); + } + + @Test + public void constructGemfireRepositoryFactoryWithNullRegionsThrowsIllegalArgumentException() { + exception.expect(IllegalArgumentException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage("Regions must not be null"); + + new GemfireRepositoryFactory(null, gemfireMappingContext); + } + + @Test + public void getRepositoryRegionNameFromRepositoryInterfaceWithRegionAnnotation() { + GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory( + Collections.>emptyList(), gemfireMappingContext); + + assertThat(gemfireRepositoryFactory.getRepositoryRegionName(PersonRepository.class), + is(equalTo("People"))); + } + + @Test + public void getRepositoryRegionNameFromRepositoryInterfaceWithoutRegionAnnotation() { + GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory( + Collections.>emptyList(), gemfireMappingContext); + + assertThat(gemfireRepositoryFactory.getRepositoryRegionName(SampleCustomGemfireRepository.class), + is(nullValue(String.class))); + } + + @Test + @SuppressWarnings("unchecked") + public void getTemplateReturnsGemfireTemplateForPeopleRegion() { + RepositoryMetadata mockRepositoryMetadata = mockRepositoryMetadata(Person.class, Long.class, + PersonRepository.class); + + Region mockPeopleRegion = mockRegion("People", Long.class, Person.class); + + Iterable> regions = Arrays.asList(mockRegion, mockPeopleRegion); + + GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory( + regions, gemfireMappingContext); + + GemfireTemplate gemfireTemplate = gemfireRepositoryFactory.getTemplate(mockRepositoryMetadata); + + assertThat(gemfireTemplate, is(notNullValue(GemfireTemplate.class))); + assertThat(gemfireTemplate.getRegion(), is(equalTo(mockPeopleRegion))); + + verify(mockPeopleRegion, times(1)).getAttributes(); + verify(mockPeopleRegion, times(1)).getFullPath(); + verify(mockPeopleRegion, times(1)).getName(); + verify(mockRegionAttributes, times(1)).getKeyConstraint(); + verify(mockRepositoryMetadata, times(1)).getDomainType(); + verify(mockRepositoryMetadata, times(1)).getIdType(); + } + + @Test + public void getTemplateReturnsGemfireTemplateForSimpleRegion() { + RepositoryMetadata mockRepositoryMetadata = mockRepositoryMetadata(Person.class, Long.class, + SampleCustomGemfireRepository.class); + + Iterable> regions = Collections.>singleton(mockRegion); + + GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory( + regions, gemfireMappingContext); + + GemfireTemplate gemfireTemplate = gemfireRepositoryFactory.getTemplate(mockRepositoryMetadata); + + assertThat(gemfireTemplate, is(notNullValue(GemfireTemplate.class))); + assertThat(gemfireTemplate.getRegion(), is(equalTo(mockRegion))); + + verify(mockRepositoryMetadata, times(1)).getDomainType(); + verify(mockRepositoryMetadata, times(1)).getIdType(); + } + + @Test + public void getTemplateThrowsIllegalArgumentExceptionForIncompatibleRegionKeyTypeAndRepositoryIdType() { + RepositoryMetadata mockRepositoryMetadata = mockRepositoryMetadata(Person.class, Long.class, + PersonRepository.class); + + Region mockPeopleRegion = mockRegion("People", String.class, Person.class); + + GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory( + Collections.>singleton(mockPeopleRegion), gemfireMappingContext); + + try { + exception.expect(IllegalArgumentException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage(String.format( + "The Region referenced only supports keys of type %1$s, but the entity to be stored has an id of type %2$s", + String.class.getName(), Long.class.getName())); + + gemfireRepositoryFactory.getTemplate(mockRepositoryMetadata); + } + finally { + verify(mockRepositoryMetadata, times(1)).getDomainType(); + verify(mockRepositoryMetadata, times(1)).getIdType(); + verify(mockPeopleRegion, times(1)).getAttributes(); + verify(mockPeopleRegion, times(1)).getFullPath(); + verify(mockPeopleRegion, times(1)).getName(); + verify(mockRegionAttributes, times(1)).getKeyConstraint(); + } + } + + @Test + public void getTemplateThrowsIllegalStateExceptionForRegionNotFound() { + RepositoryMetadata mockRepositoryMetadata = mockRepositoryMetadata(Person.class, Long.class, + PersonRepository.class); + + GemfireRepositoryFactory gemfireRepositoryFactory = new GemfireRepositoryFactory( + Collections.>singleton(mockRegion), gemfireMappingContext); + + try { + exception.expect(IllegalStateException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage(String.format( + "No Region 'People' found for domain class %s; Make sure you have configured a GemFire Region of that name in your application context", + Person.class.getName())); + + gemfireRepositoryFactory.getTemplate(mockRepositoryMetadata); + } + finally { + verify(mockRepositoryMetadata, times(2)).getDomainType(); + verify(mockRepositoryMetadata, never()).getIdType(); + verify(mockRegion, times(1)).getFullPath(); + verify(mockRegion, times(1)).getName(); + verifyZeroInteractions(mockRegionAttributes); + } } /** * @link https://jira.spring.io/browse/SGF-112 */ - @Test(expected = IllegalStateException.class) + @Test public void rejectsInterfacesExtendingPagingAndSortingRepository() { - GemfireRepositoryFactory repositoryFactory = new GemfireRepositoryFactory( - Collections.>singletonList(region), new GemfireMappingContext()); + exception.expect(IllegalStateException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage(startsWith("Pagination is not supported by GemFire Repositories")); - try { - repositoryFactory.getRepository(SamplePagingAndSortingRepository.class); - //factory.getRepository(SamplePagingRepository.class); - //factory.getRepository(SampleSortingRepository.class); - } - catch (IllegalStateException expected) { - assertThat(expected.getMessage(), Matchers.startsWith( - "Pagination is not supported by GemFire Repositories!")); - throw expected; - } + GemfireRepositoryFactory repositoryFactory = new GemfireRepositoryFactory( + Collections.>singletonList(mockRegion), new GemfireMappingContext()); + + repositoryFactory.getRepository(SamplePagingAndSortingRepository.class); } @Test public void usesConfiguredRepositoryBaseClass() { GemfireRepositoryFactory repositoryFactory = new GemfireRepositoryFactory( - Collections.>singletonList(region), new GemfireMappingContext()); + Collections.>singletonList(mockRegion), new GemfireMappingContext()); repositoryFactory.setRepositoryBaseClass(CustomBaseRepository.class); GemfireRepository gemfireRepository = repositoryFactory.getRepository(SampleCustomGemfireRepository.class, new SampleCustomRepositoryImpl()); - assertSame(CustomBaseRepository.class, ((Advised) gemfireRepository).getTargetClass()); + assertThat(((Advised) gemfireRepository).getTargetClass(), is(equalTo((Class) CustomBaseRepository.class))); } interface SamplePagingAndSortingRepository extends PagingAndSortingRepository { } - interface SamplePagingRepository extends Repository { - Page findAll(Pageable pageable); - } - - interface SampleSortingRepository extends Repository { - Iterable findAll(Sort sort); - } - - interface SampleCustomRepository { - void doCustomUpdate(T entity); - } - - class SampleCustomRepositoryImpl implements SampleCustomRepository { - - @Override - public void doCustomUpdate(final T entity) { - throw new UnsupportedOperationException("Not Implemented!"); - } - } - - interface SampleCustomGemfireRepository extends GemfireRepository, SampleCustomRepository { - } - static class CustomBaseRepository extends SimpleGemfireRepository { public CustomBaseRepository(GemfireTemplate template, EntityInformation entityInformation) { @@ -135,4 +301,21 @@ public class GemfireRepositoryFactoryUnitTests { } } + interface SampleCustomRepository { + void doCustomUpdate(T entity); + } + + class SampleCustomRepositoryImpl implements SampleCustomRepository { + @Override + public void doCustomUpdate(final T entity) { + throw new UnsupportedOperationException("Not Implemented"); + } + } + + interface SampleCustomGemfireRepository extends GemfireRepository, SampleCustomRepository { + } + + @org.springframework.data.gemfire.mapping.Region("People") + interface PersonRepository extends GemfireRepository { + } } diff --git a/src/test/resources/org/springframework/data/gemfire/repository/sample/PersonRepositoryTest-context.xml b/src/test/resources/org/springframework/data/gemfire/repository/sample/PersonRepositoryTest-context.xml deleted file mode 100644 index 6abf9370..00000000 --- a/src/test/resources/org/springframework/data/gemfire/repository/sample/PersonRepositoryTest-context.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - springGemFirePersonRepositoryTest - 0 - warning - - - - - - - - - - -