From 1f71a284084a8af9bf805764477e01315bc78f2c Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 26 Nov 2014 16:45:04 +0100 Subject: [PATCH] DATACMNS-525 - Polishing. Extracted KeySpaceUtils from KeyValueTemplate and pulled in the copy of MetaAnnotationUtils to hide it from the outside. Separated related test cases into dedicated test class. Renamed BasicMappingContext to KeyValueMappingContext and BasicPersistentProperty to KeyValuePersistentProperty. Fixed spelling in SpelQueryCreatorUnitTests. Moved MappingContext into separate package to satisfy architectural guidelines. Created dedicated KeyValueRepositoryNamespaceHandler to avoid a circular reference between commons and the key-value subsystem. Made the query creator type an annotation on @EnableKeyValueRepositories so that it's not exposed to the user to be configured. Added the necessary ImportBeanDefinitionRegistrars to the Hazelcast and EhCache implementation. Removed @since tags from those modules at they're not going to make it into Spring Data Commons. Renamed EnableEhCacheRepsotoriesUnitTests to EnableEhCacheRepositoriesIntegrationTests as well as the same for the Hazelcast specific test. The KeyValueRepositoryConfigurationExtension now explicitly registers a KeyValueMappingContext unless one is already registered. AnnotationRepositoryConfigurationSource now explicitly exposes an AnnotationMetadata instance for the annotation that triggered the configuration. Turned SpelCriteriaAccessor and SpelPropertyComparator into classes to be able to pass them a SpelExpressionParser to be able to get rid of SpelExpressionFactory eventually. Made both classes as well as SpelQueryEngine package protected for now. Moved repository implementations into support package. Updated architcture description JavaDoc polish, spacing, blank lines. Added TODOs. Original pull request: #95. --- Spring Data Commons.sonargraph | 516 +++++++++++------- .../core/AbstractKeyValueAdapter.java | 2 +- .../core/DefaultIdentifierGenerator.java | 71 +++ .../keyvalue/core/GeneratingIdAccessor.java | 86 +++ .../data/keyvalue/core/IdAccessor.java | 105 ---- ...enerator.java => IdentifierGenerator.java} | 16 +- .../data/keyvalue/core/KeySpaceUtils.java | 398 ++++++++++++++ .../data/keyvalue/core/KeyValueAdapter.java | 1 - .../keyvalue/core/KeyValueOperations.java | 31 +- .../data/keyvalue/core/KeyValueTemplate.java | 135 ++--- .../keyvalue/core/MetaAnnotationUtils.java | 354 ------------ .../keyvalue/core/SpelCriteriaAccessor.java | 19 +- ...rator.java => SpelPropertyComparator.java} | 28 +- .../data/keyvalue/core/SpelQueryEngine.java | 24 +- .../data/keyvalue/core/SpelSortAccessor.java | 25 +- .../core/mapping/BasicMappingContext.java | 47 -- ...y.java => KeyValuePersistentProperty.java} | 15 +- .../context/KeyValueMappingContext.java | 53 ++ .../core/spel/SpelExpressionFactory.java | 149 ----- .../data/keyvalue/ehcache/ListConverter.java | 1 - .../config/EhCacheRepositoriesRegistrar.java | 38 ++ .../config/EnableEhCacheRepositories.java | 20 +- .../repository/query/EhCacheQueryCreator.java | 1 - .../hazelcast/HazelcastQueryEngine.java | 5 +- .../config/EnableHazelcastRepositories.java | 20 +- .../HazelcastRepositoriesRegistrar.java | 38 ++ .../query/HazelcastQueryCreator.java | 1 - .../data/keyvalue/map/MapKeyValueAdapter.java | 2 +- .../map/MapKeyValueAdapterFactory.java | 22 +- .../config/EnableKeyValueRepositories.java | 11 +- .../config/KeyValueRepositoriesRegistrar.java | 1 - ...ValueRepositoryConfigurationExtension.java | 43 +- .../repository/config/QueryCreatorType.java | 37 ++ .../config/RepositoryNameSpaceHandler.java | 42 ++ .../query/KeyValuePartTreeQuery.java | 144 +++++ .../repository/query/SpelQueryCreator.java | 41 +- .../support/KeyValueRepositoryFactory.java | 171 ++---- .../KeyValueRepositoryFactoryBean.java | 9 - .../QueryDslKeyValueRepository.java | 41 +- .../SimpleKeyValueRepository.java} | 26 +- ...notationRepositoryConfigurationSource.java | 22 +- .../config/RepositoryNameSpaceHandler.java | 5 - src/main/resources/META-INF/spring.handlers | 1 + .../keyvalue/core/KeySpaceUtilsUnitTests.java | 132 +++++ .../keyvalue/core/KeyValueTemplateTests.java | 24 +- .../core/KeyValueTemplateUnitTests.java | 91 +-- .../core/SpelPropertyComperatorUnitTests.java | 63 ++- ...eEhCacheRepositoriesIntegrationTests.java} | 2 +- ...azelcastRepositoriesIntegrationTests.java} | 2 +- .../map/QueryDslMapRepositoryUnitTests.java | 2 +- ...=> SimpleKeyValueRepositoryUnitTests.java} | 2 +- .../BasicKeyValueRepositoryUnitTests.java | 7 +- ...eRepositoryRegistrarIntegrationTests.java} | 2 +- ...ts.java => SpelQueryCreatorUnitTests.java} | 23 +- 54 files changed, 1777 insertions(+), 1390 deletions(-) create mode 100644 src/main/java/org/springframework/data/keyvalue/core/DefaultIdentifierGenerator.java create mode 100644 src/main/java/org/springframework/data/keyvalue/core/GeneratingIdAccessor.java delete mode 100644 src/main/java/org/springframework/data/keyvalue/core/IdAccessor.java rename src/main/java/org/springframework/data/keyvalue/core/{IdGenerator.java => IdentifierGenerator.java} (65%) create mode 100644 src/main/java/org/springframework/data/keyvalue/core/KeySpaceUtils.java delete mode 100644 src/main/java/org/springframework/data/keyvalue/core/MetaAnnotationUtils.java rename src/main/java/org/springframework/data/keyvalue/core/{SpelPropertyComperator.java => SpelPropertyComparator.java} (78%) delete mode 100644 src/main/java/org/springframework/data/keyvalue/core/mapping/BasicMappingContext.java rename src/main/java/org/springframework/data/keyvalue/core/mapping/{BasicPersistentProperty.java => KeyValuePersistentProperty.java} (69%) create mode 100644 src/main/java/org/springframework/data/keyvalue/core/mapping/context/KeyValueMappingContext.java delete mode 100644 src/main/java/org/springframework/data/keyvalue/core/spel/SpelExpressionFactory.java create mode 100644 src/main/java/org/springframework/data/keyvalue/ehcache/repository/config/EhCacheRepositoriesRegistrar.java create mode 100644 src/main/java/org/springframework/data/keyvalue/hazelcast/repository/config/HazelcastRepositoriesRegistrar.java create mode 100644 src/main/java/org/springframework/data/keyvalue/repository/config/QueryCreatorType.java create mode 100644 src/main/java/org/springframework/data/keyvalue/repository/config/RepositoryNameSpaceHandler.java create mode 100644 src/main/java/org/springframework/data/keyvalue/repository/query/KeyValuePartTreeQuery.java rename src/main/java/org/springframework/data/keyvalue/repository/{ => support}/QueryDslKeyValueRepository.java (81%) rename src/main/java/org/springframework/data/keyvalue/repository/{BasicKeyValueRepository.java => support/SimpleKeyValueRepository.java} (84%) create mode 100644 src/test/java/org/springframework/data/keyvalue/core/KeySpaceUtilsUnitTests.java rename src/test/java/org/springframework/data/keyvalue/ehcache/repository/config/{EnableEhCacheRepositoriesUnitTests.java => EnableEhCacheRepositoriesIntegrationTests.java} (98%) rename src/test/java/org/springframework/data/keyvalue/hazelcast/repository/config/{EnableHazelcastRepositoriesUnitTests.java => EnableHazelcastRepositoriesIntegrationTests.java} (97%) rename src/test/java/org/springframework/data/keyvalue/map/{MapBackedKeyValueRepositoryUnitTests.java => SimpleKeyValueRepositoryUnitTests.java} (98%) rename src/test/java/org/springframework/data/keyvalue/repository/config/{KeyValueRepositoryRegistrarUnitTests.java => KeyValueRepositoryRegistrarIntegrationTests.java} (97%) rename src/test/java/org/springframework/data/keyvalue/repository/query/{SpELQueryCreatorUnitTests.java => SpelQueryCreatorUnitTests.java} (95%) diff --git a/Spring Data Commons.sonargraph b/Spring Data Commons.sonargraph index 171fe2174..0e9e1d0bd 100644 --- a/Spring Data Commons.sonargraph +++ b/Spring Data Commons.sonargraph @@ -1,216 +1,112 @@ + + + - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -241,6 +137,22 @@ + + + + + + + + + + + + + + + + @@ -331,6 +243,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/springframework/data/keyvalue/core/AbstractKeyValueAdapter.java b/src/main/java/org/springframework/data/keyvalue/core/AbstractKeyValueAdapter.java index 1c7198008..748c31e2f 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/AbstractKeyValueAdapter.java +++ b/src/main/java/org/springframework/data/keyvalue/core/AbstractKeyValueAdapter.java @@ -44,6 +44,7 @@ public abstract class AbstractKeyValueAdapter implements KeyValueAdapter { * @param engine will be defaulted to {@link SpelQueryEngine} if {@literal null}. */ protected AbstractKeyValueAdapter(QueryEngine engine) { + this.engine = engine != null ? engine : new SpelQueryEngine(); this.engine.registerAdapter(this); } @@ -74,5 +75,4 @@ public abstract class AbstractKeyValueAdapter implements KeyValueAdapter { public long count(KeyValueQuery query, Serializable keyspace) { return engine.count(query, keyspace); } - } diff --git a/src/main/java/org/springframework/data/keyvalue/core/DefaultIdentifierGenerator.java b/src/main/java/org/springframework/data/keyvalue/core/DefaultIdentifierGenerator.java new file mode 100644 index 000000000..95dc05467 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/core/DefaultIdentifierGenerator.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014 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.keyvalue.core; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.UUID; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.ClassUtils; + +/** + * Default implementation of {@link IdentifierGenerator} to generate identifiers of types {@link UUID}, String, + * + * @author Christoph Strobl + * @author Oliver Gierke + */ +enum DefaultIdentifierGenerator implements IdentifierGenerator { + + INSTANCE; + + private static final String ALGORITHM = "NativePRNGBlocking"; + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.IdentifierGenerator#newIdForType(org.springframework.data.util.TypeInformation) + */ + @Override + @SuppressWarnings("unchecked") + public T generateIdentifierOfType(TypeInformation identifierType) { + + Class type = identifierType.getType(); + + if (ClassUtils.isAssignable(UUID.class, type)) { + return (T) UUID.randomUUID(); + } else if (ClassUtils.isAssignable(String.class, type)) { + return (T) UUID.randomUUID().toString(); + } else if (ClassUtils.isAssignable(Integer.class, type)) { + + try { + return (T) SecureRandom.getInstance(ALGORITHM); + } catch (NoSuchAlgorithmException e) { + throw new InvalidDataAccessApiUsageException("Could not create SecureRandom instance.", e); + } + + } else if (ClassUtils.isAssignable(Long.class, type)) { + + try { + return (T) Long.valueOf(SecureRandom.getInstance(ALGORITHM).nextLong()); + } catch (NoSuchAlgorithmException e) { + throw new InvalidDataAccessApiUsageException("Could not create SecureRandom instance.", e); + } + } + + throw new InvalidDataAccessApiUsageException("Non gereratable id type...."); + } +} diff --git a/src/main/java/org/springframework/data/keyvalue/core/GeneratingIdAccessor.java b/src/main/java/org/springframework/data/keyvalue/core/GeneratingIdAccessor.java new file mode 100644 index 000000000..ab0098901 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/core/GeneratingIdAccessor.java @@ -0,0 +1,86 @@ +/* + * Copyright 2014 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.keyvalue.core; + +import java.io.Serializable; + +import org.springframework.data.mapping.IdentifierAccessor; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.util.Assert; + +/** + * {@link IdentifierAccessor} adding a {@link #getOrGenerateIdentifier()} to automatically generate an identifier and + * set it on the underling bean instance. + * + * @author Oliver Gierke + * @see #getOrGenerateIdentifier() + */ +class GeneratingIdAccessor implements IdentifierAccessor { + + private final PersistentPropertyAccessor accessor; + private final PersistentProperty identifierProperty; + private final IdentifierGenerator generator; + + /** + * Creates a new {@link GeneratingIdAccessor} using the given {@link PersistentPropertyAccessor}, identifier property + * and {@link IdentifierGenerator}. + * + * @param accessor must not be {@literal null}. + * @param identifierProperty must not be {@literal null}. + * @param generator must not be {@literal null}. + */ + public GeneratingIdAccessor(PersistentPropertyAccessor accessor, PersistentProperty identifierProperty, + IdentifierGenerator generator) { + + Assert.notNull(accessor, "PersistentPropertyAccessor must not be null!"); + Assert.notNull(identifierProperty, "Identifier property must not be null!"); + Assert.notNull(generator, "IdentifierGenerator must not be null!"); + + this.accessor = accessor; + this.identifierProperty = identifierProperty; + this.generator = generator; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.IdentifierAccessor#getIdentifier() + */ + @Override + public Object getIdentifier() { + return accessor.getProperty(identifierProperty); + } + + /** + * Returns the identifier value of the backing bean or generates a new one using the configured + * {@link IdentifierGenerator}. + * + * @return + */ + public Object getOrGenerateIdentifier() { + + Serializable existingIdentifier = (Serializable) getIdentifier(); + + if (existingIdentifier != null) { + return existingIdentifier; + } + + Object generatedIdentifier = generator.generateIdentifierOfType(identifierProperty.getTypeInformation()); + accessor.setProperty(identifierProperty, generatedIdentifier); + + return generatedIdentifier; + } +} diff --git a/src/main/java/org/springframework/data/keyvalue/core/IdAccessor.java b/src/main/java/org/springframework/data/keyvalue/core/IdAccessor.java deleted file mode 100644 index ae282b989..000000000 --- a/src/main/java/org/springframework/data/keyvalue/core/IdAccessor.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2014 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.keyvalue.core; - -import java.io.Serializable; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.UUID; - -import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.mapping.PersistentProperty; -import org.springframework.data.mapping.model.BeanWrapper; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -/** - * @author Christoph Strobl - * @since 1.10 - */ -public class IdAccessor { - - private final PersistentEntity entity; - private final IdGenerator idGenerator; - private final BeanWrapper wrapper; - - @SuppressWarnings("rawtypes") - public IdAccessor(PersistentEntity entity, BeanWrapper wrapper) { - this(entity, wrapper, DefaultIdGenerator.INSTANCE); - } - - @SuppressWarnings("rawtypes") - public IdAccessor(PersistentEntity entity, BeanWrapper wrapper, - IdGenerator idGenerator) { - - Assert.notNull(entity, "PersistentEntity must not be 'null'"); - Assert.notNull(wrapper, "BeanWrapper must not be 'null'."); - - this.idGenerator = idGenerator != null ? idGenerator : DefaultIdGenerator.INSTANCE; - this.entity = entity; - this.wrapper = wrapper; - } - - public T getId() { - - if (!entity.hasIdProperty()) { - throw new InvalidDataAccessApiUsageException(String.format("Cannot determine id for type %s", entity.getType())); - } - - PersistentProperty idProperty = entity.getIdProperty(); - Object value = wrapper.getProperty(idProperty); - - if (value == null) { - Serializable id = idGenerator.newIdForType(idProperty.getActualType()); - wrapper.setProperty(idProperty, id); - return (T) id; - } - return (T) value; - } - - /** - * @author Christoph Strobl - */ - static enum DefaultIdGenerator implements IdGenerator { - - INSTANCE; - - @Override - public Serializable newIdForType(Class idType) { - - if (ClassUtils.isAssignable(String.class, idType)) { - return UUID.randomUUID().toString(); - } else if (ClassUtils.isAssignable(Integer.class, idType)) { - try { - return SecureRandom.getInstance("NativePRNGBlocking"); - } catch (NoSuchAlgorithmException e) { - throw new InvalidDataAccessApiUsageException("Could not create SecureRandom instance.", e); - } - } else if (ClassUtils.isAssignable(Long.class, idType)) { - try { - return SecureRandom.getInstance("NativePRNGBlocking").nextLong(); - } catch (NoSuchAlgorithmException e) { - throw new InvalidDataAccessApiUsageException("Could not create SecureRandom instance.", e); - } - } - - throw new InvalidDataAccessApiUsageException("Non gereratable id type...."); - } - - } - -} diff --git a/src/main/java/org/springframework/data/keyvalue/core/IdGenerator.java b/src/main/java/org/springframework/data/keyvalue/core/IdentifierGenerator.java similarity index 65% rename from src/main/java/org/springframework/data/keyvalue/core/IdGenerator.java rename to src/main/java/org/springframework/data/keyvalue/core/IdentifierGenerator.java index 91b0a0991..25361d2c1 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/IdGenerator.java +++ b/src/main/java/org/springframework/data/keyvalue/core/IdentifierGenerator.java @@ -15,14 +15,22 @@ */ package org.springframework.data.keyvalue.core; -import java.io.Serializable; +import org.springframework.data.util.TypeInformation; /** + * API for components generating identifiers. + * * @author Christoph Strobl + * @author Oliver Gierke * @since 1.10 */ -public interface IdGenerator { - - Serializable newIdForType(Class actualType); +public interface IdentifierGenerator { + /** + * Creates an identifier of the given type. + * + * @param type must not be {@literal null}. + * @return an identifier of the given type. + */ + T generateIdentifierOfType(TypeInformation type); } diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeySpaceUtils.java b/src/main/java/org/springframework/data/keyvalue/core/KeySpaceUtils.java new file mode 100644 index 000000000..640c2c6e2 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/core/KeySpaceUtils.java @@ -0,0 +1,398 @@ +/* + * Copyright 2014 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.keyvalue.core; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.style.ToStringCreator; +import org.springframework.data.annotation.Persistent; +import org.springframework.data.keyvalue.annotation.KeySpace; +import org.springframework.data.keyvalue.core.KeySpaceUtils.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * @author Christoph Strobl + * @author Oliver Gierke + */ +abstract class KeySpaceUtils { + + private KeySpaceUtils() {} + + /** + * Looks up {@link Persistent} when used as meta annotation to find the {@link KeySpace} attribute. + * + * @return + * @since 1.10 + */ + public static Object getKeySpace(Class type) { + + KeySpace keyspace = AnnotationUtils.findAnnotation(type, KeySpace.class); + + if (keyspace != null) { + return AnnotationUtils.getValue(keyspace); + } + + AnnotationDescriptor descriptor = KeySpaceUtils.MetaAnnotationUtils.findAnnotationDescriptor(type, + Persistent.class); + + if (descriptor != null && descriptor.getComposedAnnotation() != null) { + + Annotation composed = descriptor.getComposedAnnotation(); + + for (Method method : descriptor.getComposedAnnotationType().getDeclaredMethods()) { + + keyspace = AnnotationUtils.findAnnotation(method, KeySpace.class); + + if (keyspace != null) { + return AnnotationUtils.getValue(composed, method.getName()); + } + } + } + return null; + } + + /** + * {@code MetaAnnotationUtils} is a collection of utility methods that complements the standard support already + * available in {@link AnnotationUtils}. + *

+ * Whereas {@code AnnotationUtils} provides utilities for getting or finding an annotation, + * {@code MetaAnnotationUtils} goes a step further by providing support for determining the root class on + * which an annotation is declared, either directly or indirectly via a composed + * annotation. This additional information is encapsulated in an {@link AnnotationDescriptor}. + *

+ * The additional information provided by an {@code AnnotationDescriptor} is required by the + * Spring TestContext Framework in order to be able to support class hierarchy traversals for annotations + * such as {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration}, + * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}, and + * {@link org.springframework.test.context.ActiveProfiles @ActiveProfiles} which offer support for merging and + * overriding various inherited annotation attributes (e.g., + * {@link org.springframework.test.context.ContextConfiguration#inheritLocations}). + * + * @author Sam Brannen + * @since 4.0 + * @see AnnotationUtils + * @see AnnotationDescriptor + */ + static abstract class MetaAnnotationUtils { + + private MetaAnnotationUtils() { + /* no-op */ + } + + /** + * Find the {@link AnnotationDescriptor} for the supplied {@code annotationType} on the supplied {@link Class}, + * traversing its annotations and superclasses if no annotation can be found on the given class itself. + *

+ * This method explicitly handles class-level annotations which are not declared as + * {@linkplain java.lang.annotation.Inherited inherited} as + * well as meta-annotations. + *

+ * The algorithm operates as follows: + *

    + *
  1. Search for the annotation on the given class and return a corresponding {@code AnnotationDescriptor} if + * found. + *
  2. Recursively search through all annotations that the given class declares. + *
  3. Recursively search through the superclass hierarchy of the given class. + *
+ *

+ * In this context, the term recursively means that the search process continues by returning to step #1 + * with the current annotation or superclass as the class to look for annotations on. + *

+ * If the supplied {@code clazz} is an interface, only the interface itself will be checked; the inheritance + * hierarchy for interfaces will not be traversed. + * + * @param clazz the class to look for annotations on + * @param annotationType the type of annotation to look for + * @return the corresponding annotation descriptor if the annotation was found; otherwise {@code null} + * @see AnnotationUtils#findAnnotationDeclaringClass(Class, Class) + * @see #findAnnotationDescriptorForTypes(Class, Class...) + */ + public static AnnotationDescriptor findAnnotationDescriptor(Class clazz, + Class annotationType) { + return findAnnotationDescriptor(clazz, new HashSet(), annotationType); + } + + /** + * Perform the search algorithm for {@link #findAnnotationDescriptor(Class, Class)}, avoiding endless recursion by + * tracking which annotations have already been visited. + * + * @param clazz the class to look for annotations on + * @param visited the set of annotations that have already been visited + * @param annotationType the type of annotation to look for + * @return the corresponding annotation descriptor if the annotation was found; otherwise {@code null} + */ + private static AnnotationDescriptor findAnnotationDescriptor(Class clazz, + Set visited, Class annotationType) { + + Assert.notNull(annotationType, "Annotation type must not be null"); + + if (clazz == null || clazz.equals(Object.class)) { + return null; + } + + // Declared locally? + if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) { + return new AnnotationDescriptor(clazz, clazz.getAnnotation(annotationType)); + } + + // Declared on a composed annotation (i.e., as a meta-annotation)? + for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { + AnnotationDescriptor descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(), visited, + annotationType); + if (descriptor != null) { + return new AnnotationDescriptor(clazz, descriptor.getDeclaringClass(), composedAnnotation, + descriptor.getAnnotation()); + } + } + } + + // Declared on a superclass? + return findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType); + } + + /** + * Find the {@link UntypedAnnotationDescriptor} for the first {@link Class} in the inheritance hierarchy of the + * specified {@code clazz} (including the specified {@code clazz} itself) which declares at least one of the + * specified {@code annotationTypes}. + *

+ * This method traverses the annotations and superclasses of the specified {@code clazz} if no annotation can be + * found on the given class itself. + *

+ * This method explicitly handles class-level annotations which are not declared as + * {@linkplain java.lang.annotation.Inherited inherited} as + * well as meta-annotations. + *

+ * The algorithm operates as follows: + *

    + *
  1. Search for a local declaration of one of the annotation types on the given class and return a corresponding + * {@code UntypedAnnotationDescriptor} if found. + *
  2. Recursively search through all annotations that the given class declares. + *
  3. Recursively search through the superclass hierarchy of the given class. + *
+ *

+ * In this context, the term recursively means that the search process continues by returning to step #1 + * with the current annotation or superclass as the class to look for annotations on. + *

+ * If the supplied {@code clazz} is an interface, only the interface itself will be checked; the inheritance + * hierarchy for interfaces will not be traversed. + * + * @param clazz the class to look for annotations on + * @param annotationTypes the types of annotations to look for + * @return the corresponding annotation descriptor if one of the annotations was found; otherwise {@code null} + * @see AnnotationUtils#findAnnotationDeclaringClassForTypes(java.util.List, Class) + * @see #findAnnotationDescriptor(Class, Class) + */ + public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class clazz, + Class... annotationTypes) { + return findAnnotationDescriptorForTypes(clazz, new HashSet(), annotationTypes); + } + + /** + * Perform the search algorithm for {@link #findAnnotationDescriptorForTypes(Class, Class...)}, avoiding endless + * recursion by tracking which annotations have already been visited. + * + * @param clazz the class to look for annotations on + * @param visited the set of annotations that have already been visited + * @param annotationTypes the types of annotations to look for + * @return the corresponding annotation descriptor if one of the annotations was found; otherwise {@code null} + */ + private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class clazz, + Set visited, Class... annotationTypes) { + + assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty"); + + if (clazz == null || clazz.equals(Object.class)) { + return null; + } + + // Declared locally? + for (Class annotationType : annotationTypes) { + if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) { + return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType)); + } + } + + // Declared on a composed annotation (i.e., as a meta-annotation)? + for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + composedAnnotation.annotationType(), visited, annotationTypes); + if (descriptor != null) { + return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), composedAnnotation, + descriptor.getAnnotation()); + } + } + } + + // Declared on a superclass? + return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes); + } + + /** + * Descriptor for an {@link Annotation}, including the {@linkplain #getDeclaringClass() class} on which the + * annotation is declared as well as the actual {@linkplain #getAnnotation() annotation} instance. + *

+ * If the annotation is used as a meta-annotation, the descriptor also includes the + * {@linkplain #getComposedAnnotation() composed annotation} on which the annotation is present. In such cases, the + * root declaring class is not directly annotated with the annotation but rather indirectly via the + * composed annotation. + *

+ * Given the following example, if we are searching for the {@code @Transactional} annotation on the + * {@code TransactionalTests} class, then the properties of the {@code AnnotationDescriptor} would be as follows. + *

    + *
  • rootDeclaringClass: {@code TransactionalTests} class object
  • + *
  • declaringClass: {@code TransactionalTests} class object
  • + *
  • composedAnnotation: {@code null}
  • + *
  • annotation: instance of the {@code Transactional} annotation
  • + *
+ * + *
+		 * @Transactional
+		 * @ContextConfiguration({ "/test-datasource.xml", "/repository-config.xml" })
+		 * public class TransactionalTests {}
+		 * 
+ *

+ * Given the following example, if we are searching for the {@code @Transactional} annotation on the + * {@code UserRepositoryTests} class, then the properties of the {@code AnnotationDescriptor} would be as follows. + *

    + *
  • rootDeclaringClass: {@code UserRepositoryTests} class object
  • + *
  • declaringClass: {@code RepositoryTests} class object
  • + *
  • composedAnnotation: instance of the {@code RepositoryTests} annotation
  • + *
  • annotation: instance of the {@code Transactional} annotation
  • + *
+ * + *
+		 * @Transactional
+		 * @ContextConfiguration({ "/test-datasource.xml", "/repository-config.xml" })
+		 * @Retention(RetentionPolicy.RUNTIME)
+		 * public @interface RepositoryTests {
+		 * }
+		 * 
+		 * @RepositoryTests
+		 * public class UserRepositoryTests {}
+		 * 
+ * + * @author Sam Brannen + * @since 4.0 + */ + public static class AnnotationDescriptor { + + private final Class rootDeclaringClass; + private final Class declaringClass; + private final Annotation composedAnnotation; + private final T annotation; + private final AnnotationAttributes annotationAttributes; + + public AnnotationDescriptor(Class rootDeclaringClass, T annotation) { + this(rootDeclaringClass, rootDeclaringClass, null, annotation); + } + + public AnnotationDescriptor(Class rootDeclaringClass, Class declaringClass, Annotation composedAnnotation, + T annotation) { + Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null"); + Assert.notNull(annotation, "annotation must not be null"); + + this.rootDeclaringClass = rootDeclaringClass; + this.declaringClass = declaringClass; + this.composedAnnotation = composedAnnotation; + this.annotation = annotation; + this.annotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(rootDeclaringClass, annotation + .annotationType().getName()); + } + + public Class getRootDeclaringClass() { + return this.rootDeclaringClass; + } + + public Class getDeclaringClass() { + return this.declaringClass; + } + + public T getAnnotation() { + return this.annotation; + } + + public Class getAnnotationType() { + return this.annotation.annotationType(); + } + + public AnnotationAttributes getAnnotationAttributes() { + return this.annotationAttributes; + } + + public Annotation getComposedAnnotation() { + return this.composedAnnotation; + } + + public Class getComposedAnnotationType() { + return this.composedAnnotation == null ? null : this.composedAnnotation.annotationType(); + } + + /** + * Provide a textual representation of this {@code AnnotationDescriptor}. + */ + @Override + public String toString() { + return new ToStringCreator(this)// + .append("rootDeclaringClass", rootDeclaringClass)// + .append("declaringClass", declaringClass)// + .append("composedAnnotation", composedAnnotation)// + .append("annotation", annotation)// + .toString(); + } + } + + /** + * Untyped extension of {@code AnnotationDescriptor} that is used to describe the declaration of one of + * several candidate annotation types where the actual annotation type cannot be predetermined. + * + * @author Sam Brannen + * @since 4.0 + */ + public static class UntypedAnnotationDescriptor extends AnnotationDescriptor { + + public UntypedAnnotationDescriptor(Class rootDeclaringClass, Annotation annotation) { + this(rootDeclaringClass, rootDeclaringClass, null, annotation); + } + + public UntypedAnnotationDescriptor(Class rootDeclaringClass, Class declaringClass, + Annotation composedAnnotation, Annotation annotation) { + super(rootDeclaringClass, declaringClass, composedAnnotation, annotation); + } + } + + private static void assertNonEmptyAnnotationTypeArray(Class[] annotationTypes, String message) { + if (ObjectUtils.isEmpty(annotationTypes)) { + throw new IllegalArgumentException(message); + } + + for (Class clazz : annotationTypes) { + if (!Annotation.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException("Array elements must be of type Annotation"); + } + } + } + + } +} diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValueAdapter.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValueAdapter.java index a2aee0a3f..42be9c4ff 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/KeyValueAdapter.java +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValueAdapter.java @@ -102,5 +102,4 @@ public interface KeyValueAdapter extends DisposableBean { * @return */ long count(KeyValueQuery query, Serializable keyspace); - } diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java index c3fd2022b..3495408b7 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValueOperations.java @@ -33,8 +33,7 @@ import org.springframework.data.mapping.context.MappingContext; public interface KeyValueOperations extends DisposableBean { /** - * Add given object.
- * Object needs to have id property to which a generated value will be assigned. + * Add given object. Object needs to have id property to which a generated value will be assigned. * * @param objectToInsert * @return @@ -50,8 +49,8 @@ public interface KeyValueOperations extends DisposableBean { void insert(Serializable id, Object objectToInsert); /** - * Get all elements of given type.
- * Respects {@link KeySpace} if present and therefore returns all elements that can be assigned to requested type. + * Get all elements of given type. Respects {@link KeySpace} if present and therefore returns all elements that can be + * assigned to requested type. * * @param type must not be {@literal null}. * @return empty collection if no elements found. @@ -69,8 +68,8 @@ public interface KeyValueOperations extends DisposableBean { List findAll(Sort sort, Class type); /** - * Get element of given type with given id.
- * Respects {@link KeySpace} if present and therefore returns all elements that can be assigned to requested type. + * Get element of given type with given id. Respects {@link KeySpace} if present and therefore returns all elements + * that can be assigned to requested type. * * @param id must not be {@literal null}. * @param type must not be {@literal null}. @@ -97,8 +96,8 @@ public interface KeyValueOperations extends DisposableBean { List find(KeyValueQuery query, Class type); /** - * Get all elements in given range.
- * Respects {@link KeySpace} if present and therefore returns all elements that can be assigned to requested type. + * Get all elements in given range. Respects {@link KeySpace} if present and therefore returns all elements that can + * be assigned to requested type. * * @param offset * @param rows @@ -108,8 +107,8 @@ public interface KeyValueOperations extends DisposableBean { List findInRange(int offset, int rows, Class type); /** - * Get all elements in given range ordered by sort.
- * Respects {@link KeySpace} if present and therefore returns all elements that can be assigned to requested type. + * Get all elements in given range ordered by sort. Respects {@link KeySpace} if present and therefore returns all + * elements that can be assigned to requested type. * * @param offset * @param rows @@ -131,8 +130,8 @@ public interface KeyValueOperations extends DisposableBean { void update(Serializable id, Object objectToUpdate); /** - * Remove all elements of type.
- * Respects {@link KeySpace} if present and therefore removes all elements that can be assigned to requested type. + * Remove all elements of type. Respects {@link KeySpace} if present and therefore removes all elements that can be + * assigned to requested type. * * @param type must not be {@literal null}. */ @@ -154,8 +153,8 @@ public interface KeyValueOperations extends DisposableBean { T delete(Serializable id, Class type); /** - * Total number of elements with given type available.
- * Respects {@link KeySpace} if present and therefore counts all elements that can be assigned to requested type. + * Total number of elements with given type available. Respects {@link KeySpace} if present and therefore counts all + * elements that can be assigned to requested type. * * @param type must not be {@literal null}. * @return @@ -163,8 +162,8 @@ public interface KeyValueOperations extends DisposableBean { long count(Class type); /** - * Total number of elements matching given query.
- * Respects {@link KeySpace} if present and therefore counts all elements that can be assigned to requested type. + * Total number of elements matching given query. Respects {@link KeySpace} if present and therefore counts all + * elements that can be assigned to requested type. * * @param query * @param type diff --git a/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java b/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java index f50f2daa4..b0a53bef9 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java +++ b/src/main/java/org/springframework/data/keyvalue/core/KeyValueTemplate.java @@ -15,26 +15,21 @@ */ package org.springframework.data.keyvalue.core; +import static org.springframework.data.keyvalue.core.KeySpaceUtils.*; + import java.io.Serializable; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.data.annotation.Persistent; import org.springframework.data.domain.Sort; -import org.springframework.data.keyvalue.annotation.KeySpace; -import org.springframework.data.keyvalue.core.MetaAnnotationUtils.AnnotationDescriptor; -import org.springframework.data.keyvalue.core.mapping.BasicMappingContext; +import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; import org.springframework.data.keyvalue.core.query.KeyValueQuery; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.mapping.model.BeanWrapper; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -43,24 +38,24 @@ import org.springframework.util.StringUtils; * Basic implementation of {@link KeyValueOperations}. * * @author Christoph Strobl + * @author Oliver Gierke * @since 1.10 */ public class KeyValueTemplate implements KeyValueOperations { private final KeyValueAdapter adapter; - private ConcurrentHashMap, String> keySpaceCache = new ConcurrentHashMap, String>(); - - @SuppressWarnings("rawtypes")// - private MappingContext, ? extends PersistentProperty> mappingContext; + private final ConcurrentHashMap, String> keySpaceCache = new ConcurrentHashMap, String>(); + private final MappingContext>, ? extends PersistentProperty> mappingContext; + private final IdentifierGenerator identifierGenerator; /** * Create new {@link KeyValueTemplate} using the given {@link KeyValueAdapter} with a default - * {@link BasicMappingContext}. + * {@link KeyValueMappingContext}. * * @param adapter must not be {@literal null}. */ public KeyValueTemplate(KeyValueAdapter adapter) { - this(adapter, new BasicMappingContext()); + this(adapter, new KeyValueMappingContext()); } /** @@ -74,27 +69,28 @@ public class KeyValueTemplate implements KeyValueOperations { KeyValueAdapter adapter, MappingContext, ? extends PersistentProperty> mappingContext) { - Assert.notNull(adapter, "Adapter must not be 'null' when intializing KeyValueTemplate."); - Assert.notNull(mappingContext, "MappingContext must not be 'null' when intializing KeyValueTemplate."); + Assert.notNull(adapter, "Adapter must not be null!"); + Assert.notNull(mappingContext, "MappingContext must not be null!"); this.adapter = adapter; this.mappingContext = mappingContext; + this.identifierGenerator = DefaultIdentifierGenerator.INSTANCE; } /* * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#insert(java.lang.Object) */ - @SuppressWarnings("rawtypes") @Override public T insert(T objectToInsert) { - PersistentEntity entity = this.mappingContext.getPersistentEntity(ClassUtils - .getUserClass(objectToInsert)); + PersistentEntity entity = this.mappingContext.getPersistentEntity(ClassUtils.getUserClass(objectToInsert)); - Serializable id = new IdAccessor(entity, BeanWrapper.create(objectToInsert, null)).getId(); + GeneratingIdAccessor generatingIdAccessor = new GeneratingIdAccessor(entity.getPropertyAccessor(objectToInsert), + entity.getIdProperty(), identifierGenerator); + Object id = generatingIdAccessor.getOrGenerateIdentifier(); - insert(id, objectToInsert); + insert((Serializable) id, objectToInsert); return objectToInsert; } @@ -105,8 +101,8 @@ public class KeyValueTemplate implements KeyValueOperations { @Override public void insert(final Serializable id, final Object objectToInsert) { - Assert.notNull(id, "Id for object to be inserted must not be 'null'."); - Assert.notNull(objectToInsert, "Object to be inserted must not be 'null'."); + Assert.notNull(id, "Id for object to be inserted must not be null!"); + Assert.notNull(objectToInsert, "Object to be inserted must not be null!"); execute(new KeyValueCallback() { @@ -141,8 +137,7 @@ public class KeyValueTemplate implements KeyValueOperations { ClassUtils.getUserClass(objectToUpdate))); } - Serializable id = BeanWrapper.create(objectToUpdate, null).getProperty(entity.getIdProperty(), Serializable.class); - update(id, objectToUpdate); + update((Serializable) entity.getIdentifierAccessor(objectToUpdate).getIdentifier(), objectToUpdate); } /* @@ -152,8 +147,8 @@ public class KeyValueTemplate implements KeyValueOperations { @Override public void update(final Serializable id, final Object objectToUpdate) { - Assert.notNull(id, "Id for object to be inserted must not be 'null'."); - Assert.notNull(objectToUpdate, "Object to be updated must not be 'null'. Use delete to remove."); + Assert.notNull(id, "Id for object to be inserted must not be null!"); + Assert.notNull(objectToUpdate, "Object to be updated must not be null!"); execute(new KeyValueCallback() { @@ -169,11 +164,10 @@ public class KeyValueTemplate implements KeyValueOperations { * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#findAllOf(java.lang.Class) */ - @SuppressWarnings("rawtypes") @Override public List findAll(final Class type) { - Assert.notNull(type, "Type to fetch must not be 'null'."); + Assert.notNull(type, "Type to fetch must not be null!"); return execute(new KeyValueCallback>() { @@ -200,15 +194,14 @@ public class KeyValueTemplate implements KeyValueOperations { } /* - *(non-Javadoc) + * (non-Javadoc) * @see org.springframework.data.keyvalue.core.KeyValueOperations#findById(java.io.Serializable, java.lang.Class) */ - @SuppressWarnings("rawtypes") @Override public T findById(final Serializable id, final Class type) { - Assert.notNull(id, "Id for object to be inserted must not be 'null'."); - Assert.notNull(type, "Type to fetch must not be 'null'."); + Assert.notNull(id, "Id for object to be inserted must not be null!"); + Assert.notNull(type, "Type to fetch must not be null!"); return execute(new KeyValueCallback() { @@ -234,7 +227,7 @@ public class KeyValueTemplate implements KeyValueOperations { @Override public void delete(final Class type) { - Assert.notNull(type, "Type to delete must not be 'null'."); + Assert.notNull(type, "Type to delete must not be null!"); final String typeKey = resolveKeySpace(type); @@ -260,8 +253,7 @@ public class KeyValueTemplate implements KeyValueOperations { Class type = (Class) ClassUtils.getUserClass(objectToDelete); PersistentEntity entity = this.mappingContext.getPersistentEntity(type); - Serializable id = new IdAccessor(entity, BeanWrapper.create(objectToDelete, null)).getId(); - return delete(id, type); + return delete((Serializable) entity.getIdentifierAccessor(objectToDelete).getIdentifier(), type); } /* @@ -271,8 +263,8 @@ public class KeyValueTemplate implements KeyValueOperations { @Override public T delete(final Serializable id, final Class type) { - Assert.notNull(id, "Id for object to be inserted must not be 'null'."); - Assert.notNull(type, "Type to delete must not be 'null'."); + Assert.notNull(id, "Id for object to be inserted must not be null!"); + Assert.notNull(type, "Type to delete must not be null!"); return execute(new KeyValueCallback() { @@ -302,13 +294,13 @@ public class KeyValueTemplate implements KeyValueOperations { @Override public T execute(KeyValueCallback action) { - Assert.notNull(action, "KeyValueCallback must not be 'null'."); + Assert.notNull(action, "KeyValueCallback must not be null!"); try { return action.doInKeyValue(this.adapter); } catch (RuntimeException e) { - // TODO: potentially convert runtime exception? + // TODO: Use PersistenceExceptionTranslator to translate exception throw e; } } @@ -332,7 +324,8 @@ public class KeyValueTemplate implements KeyValueOperations { return new ArrayList((Collection) result); } - ArrayList filtered = new ArrayList(); + List filtered = new ArrayList(); + for (Object candidate : result) { if (typeCheck(type, candidate)) { filtered.add((T) candidate); @@ -399,6 +392,15 @@ public class KeyValueTemplate implements KeyValueOperations { return this.mappingContext; } + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + @Override + public void destroy() throws Exception { + this.adapter.clear(); + } + protected String resolveKeySpace(Class type) { Class userClass = ClassUtils.getUserClass(type); @@ -411,6 +413,7 @@ public class KeyValueTemplate implements KeyValueOperations { String keySpaceString = null; Object keySpace = getKeySpace(type); + if (keySpace != null) { keySpaceString = keySpace.toString(); } @@ -423,53 +426,7 @@ public class KeyValueTemplate implements KeyValueOperations { return keySpaceString; } - /** - * Looks up {@link Persistent} when used as meta annotation to find the {@link KeySpace} attribute. - * - * @return - * @since 1.10 - */ - - Object getKeySpace(Class type) { - - KeySpace keyspace = AnnotationUtils.findAnnotation(type, KeySpace.class); - if (keyspace != null) { - return AnnotationUtils.getValue(keyspace); - } - - AnnotationDescriptor descriptor = MetaAnnotationUtils.findAnnotationDescriptor(type, Persistent.class); - - if (descriptor != null && descriptor.getComposedAnnotation() != null) { - - Annotation composed = descriptor.getComposedAnnotation(); - - for (Method method : descriptor.getComposedAnnotationType().getDeclaredMethods()) { - - keyspace = AnnotationUtils.findAnnotation(method, KeySpace.class); - - if (keyspace != null) { - return AnnotationUtils.getValue(composed, method.getName()); - } - } - } - return null; + private static boolean typeCheck(Class requiredType, Object candidate) { + return candidate == null ? true : ClassUtils.isAssignable(requiredType, candidate.getClass()); } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.DisposableBean#destroy() - */ - @Override - public void destroy() throws Exception { - this.adapter.clear(); - } - - private boolean typeCheck(Class requiredType, Object candidate) { - - if (candidate == null) { - return true; - } - return ClassUtils.isAssignable(requiredType, candidate.getClass()); - } - } diff --git a/src/main/java/org/springframework/data/keyvalue/core/MetaAnnotationUtils.java b/src/main/java/org/springframework/data/keyvalue/core/MetaAnnotationUtils.java deleted file mode 100644 index 640540537..000000000 --- a/src/main/java/org/springframework/data/keyvalue/core/MetaAnnotationUtils.java +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright 2002-2014 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.keyvalue.core; - -import java.lang.annotation.Annotation; -import java.util.HashSet; -import java.util.Set; - -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.style.ToStringCreator; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; - -/** - * {@code MetaAnnotationUtils} is a collection of utility methods that complements the standard support already - * available in {@link AnnotationUtils}. - *

- * Whereas {@code AnnotationUtils} provides utilities for getting or finding an annotation, - * {@code MetaAnnotationUtils} goes a step further by providing support for determining the root class on which - * an annotation is declared, either directly or indirectly via a composed - * annotation. This additional information is encapsulated in an {@link AnnotationDescriptor}. - *

- * The additional information provided by an {@code AnnotationDescriptor} is required by the - * Spring TestContext Framework in order to be able to support class hierarchy traversals for annotations such - * as {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration}, - * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}, and - * {@link org.springframework.test.context.ActiveProfiles @ActiveProfiles} which offer support for merging and - * overriding various inherited annotation attributes (e.g., - * {@link org.springframework.test.context.ContextConfiguration#inheritLocations}). - * - * @author Sam Brannen - * @since 4.0 - * @see AnnotationUtils - * @see AnnotationDescriptor - */ -public abstract class MetaAnnotationUtils { - - private MetaAnnotationUtils() { - /* no-op */ - } - - /** - * Find the {@link AnnotationDescriptor} for the supplied {@code annotationType} on the supplied {@link Class}, - * traversing its annotations and superclasses if no annotation can be found on the given class itself. - *

- * This method explicitly handles class-level annotations which are not declared as - * {@linkplain java.lang.annotation.Inherited inherited} as - * well as meta-annotations. - *

- * The algorithm operates as follows: - *

    - *
  1. Search for the annotation on the given class and return a corresponding {@code AnnotationDescriptor} if found. - *
  2. Recursively search through all annotations that the given class declares. - *
  3. Recursively search through the superclass hierarchy of the given class. - *
- *

- * In this context, the term recursively means that the search process continues by returning to step #1 with - * the current annotation or superclass as the class to look for annotations on. - *

- * If the supplied {@code clazz} is an interface, only the interface itself will be checked; the inheritance hierarchy - * for interfaces will not be traversed. - * - * @param clazz the class to look for annotations on - * @param annotationType the type of annotation to look for - * @return the corresponding annotation descriptor if the annotation was found; otherwise {@code null} - * @see AnnotationUtils#findAnnotationDeclaringClass(Class, Class) - * @see #findAnnotationDescriptorForTypes(Class, Class...) - */ - public static AnnotationDescriptor findAnnotationDescriptor(Class clazz, - Class annotationType) { - return findAnnotationDescriptor(clazz, new HashSet(), annotationType); - } - - /** - * Perform the search algorithm for {@link #findAnnotationDescriptor(Class, Class)}, avoiding endless recursion by - * tracking which annotations have already been visited. - * - * @param clazz the class to look for annotations on - * @param visited the set of annotations that have already been visited - * @param annotationType the type of annotation to look for - * @return the corresponding annotation descriptor if the annotation was found; otherwise {@code null} - */ - private static AnnotationDescriptor findAnnotationDescriptor(Class clazz, - Set visited, Class annotationType) { - - Assert.notNull(annotationType, "Annotation type must not be null"); - - if (clazz == null || clazz.equals(Object.class)) { - return null; - } - - // Declared locally? - if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) { - return new AnnotationDescriptor(clazz, clazz.getAnnotation(annotationType)); - } - - // Declared on a composed annotation (i.e., as a meta-annotation)? - for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { - if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { - AnnotationDescriptor descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(), visited, - annotationType); - if (descriptor != null) { - return new AnnotationDescriptor(clazz, descriptor.getDeclaringClass(), composedAnnotation, - descriptor.getAnnotation()); - } - } - } - - // Declared on a superclass? - return findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType); - } - - /** - * Find the {@link UntypedAnnotationDescriptor} for the first {@link Class} in the inheritance hierarchy of the - * specified {@code clazz} (including the specified {@code clazz} itself) which declares at least one of the specified - * {@code annotationTypes}. - *

- * This method traverses the annotations and superclasses of the specified {@code clazz} if no annotation can be found - * on the given class itself. - *

- * This method explicitly handles class-level annotations which are not declared as - * {@linkplain java.lang.annotation.Inherited inherited} as - * well as meta-annotations. - *

- * The algorithm operates as follows: - *

    - *
  1. Search for a local declaration of one of the annotation types on the given class and return a corresponding - * {@code UntypedAnnotationDescriptor} if found. - *
  2. Recursively search through all annotations that the given class declares. - *
  3. Recursively search through the superclass hierarchy of the given class. - *
- *

- * In this context, the term recursively means that the search process continues by returning to step #1 with - * the current annotation or superclass as the class to look for annotations on. - *

- * If the supplied {@code clazz} is an interface, only the interface itself will be checked; the inheritance hierarchy - * for interfaces will not be traversed. - * - * @param clazz the class to look for annotations on - * @param annotationTypes the types of annotations to look for - * @return the corresponding annotation descriptor if one of the annotations was found; otherwise {@code null} - * @see AnnotationUtils#findAnnotationDeclaringClassForTypes(java.util.List, Class) - * @see #findAnnotationDescriptor(Class, Class) - */ - @SuppressWarnings("unchecked") - public static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class clazz, - Class... annotationTypes) { - return findAnnotationDescriptorForTypes(clazz, new HashSet(), annotationTypes); - } - - /** - * Perform the search algorithm for {@link #findAnnotationDescriptorForTypes(Class, Class...)}, avoiding endless - * recursion by tracking which annotations have already been visited. - * - * @param clazz the class to look for annotations on - * @param visited the set of annotations that have already been visited - * @param annotationTypes the types of annotations to look for - * @return the corresponding annotation descriptor if one of the annotations was found; otherwise {@code null} - */ - @SuppressWarnings("unchecked") - private static UntypedAnnotationDescriptor findAnnotationDescriptorForTypes(Class clazz, Set visited, - Class... annotationTypes) { - - assertNonEmptyAnnotationTypeArray(annotationTypes, "The list of annotation types must not be empty"); - - if (clazz == null || clazz.equals(Object.class)) { - return null; - } - - // Declared locally? - for (Class annotationType : annotationTypes) { - if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) { - return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType)); - } - } - - // Declared on a composed annotation (i.e., as a meta-annotation)? - for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { - if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { - UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(composedAnnotation.annotationType(), - visited, annotationTypes); - if (descriptor != null) { - return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), composedAnnotation, - descriptor.getAnnotation()); - } - } - } - - // Declared on a superclass? - return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes); - } - - /** - * Descriptor for an {@link Annotation}, including the {@linkplain #getDeclaringClass() class} on which the annotation - * is declared as well as the actual {@linkplain #getAnnotation() annotation} instance. - *

- * If the annotation is used as a meta-annotation, the descriptor also includes the - * {@linkplain #getComposedAnnotation() composed annotation} on which the annotation is present. In such cases, the - * root declaring class is not directly annotated with the annotation but rather indirectly via the composed - * annotation. - *

- * Given the following example, if we are searching for the {@code @Transactional} annotation on the - * {@code TransactionalTests} class, then the properties of the {@code AnnotationDescriptor} would be as follows. - *

    - *
  • rootDeclaringClass: {@code TransactionalTests} class object
  • - *
  • declaringClass: {@code TransactionalTests} class object
  • - *
  • composedAnnotation: {@code null}
  • - *
  • annotation: instance of the {@code Transactional} annotation
  • - *
- * - *
-	 * @Transactional
-	 * @ContextConfiguration({ "/test-datasource.xml", "/repository-config.xml" })
-	 * public class TransactionalTests {}
-	 * 
- *

- * Given the following example, if we are searching for the {@code @Transactional} annotation on the - * {@code UserRepositoryTests} class, then the properties of the {@code AnnotationDescriptor} would be as follows. - *

    - *
  • rootDeclaringClass: {@code UserRepositoryTests} class object
  • - *
  • declaringClass: {@code RepositoryTests} class object
  • - *
  • composedAnnotation: instance of the {@code RepositoryTests} annotation
  • - *
  • annotation: instance of the {@code Transactional} annotation
  • - *
- * - *
-	 * @Transactional
-	 * @ContextConfiguration({ "/test-datasource.xml", "/repository-config.xml" })
-	 * @Retention(RetentionPolicy.RUNTIME)
-	 * public @interface RepositoryTests {
-	 * }
-	 * 
-	 * @RepositoryTests
-	 * public class UserRepositoryTests {}
-	 * 
- * - * @author Sam Brannen - * @since 4.0 - */ - public static class AnnotationDescriptor { - - private final Class rootDeclaringClass; - private final Class declaringClass; - private final Annotation composedAnnotation; - private final T annotation; - private final AnnotationAttributes annotationAttributes; - - public AnnotationDescriptor(Class rootDeclaringClass, T annotation) { - this(rootDeclaringClass, rootDeclaringClass, null, annotation); - } - - public AnnotationDescriptor(Class rootDeclaringClass, Class declaringClass, Annotation composedAnnotation, - T annotation) { - Assert.notNull(rootDeclaringClass, "rootDeclaringClass must not be null"); - Assert.notNull(annotation, "annotation must not be null"); - - this.rootDeclaringClass = rootDeclaringClass; - this.declaringClass = declaringClass; - this.composedAnnotation = composedAnnotation; - this.annotation = annotation; - this.annotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(rootDeclaringClass, annotation - .annotationType().getName()); - } - - public Class getRootDeclaringClass() { - return this.rootDeclaringClass; - } - - public Class getDeclaringClass() { - return this.declaringClass; - } - - public T getAnnotation() { - return this.annotation; - } - - public Class getAnnotationType() { - return this.annotation.annotationType(); - } - - public AnnotationAttributes getAnnotationAttributes() { - return this.annotationAttributes; - } - - public Annotation getComposedAnnotation() { - return this.composedAnnotation; - } - - public Class getComposedAnnotationType() { - return this.composedAnnotation == null ? null : this.composedAnnotation.annotationType(); - } - - /** - * Provide a textual representation of this {@code AnnotationDescriptor}. - */ - @Override - public String toString() { - return new ToStringCreator(this)// - .append("rootDeclaringClass", rootDeclaringClass)// - .append("declaringClass", declaringClass)// - .append("composedAnnotation", composedAnnotation)// - .append("annotation", annotation)// - .toString(); - } - } - - /** - * Untyped extension of {@code AnnotationDescriptor} that is used to describe the declaration of one of - * several candidate annotation types where the actual annotation type cannot be predetermined. - * - * @author Sam Brannen - * @since 4.0 - */ - public static class UntypedAnnotationDescriptor extends AnnotationDescriptor { - - public UntypedAnnotationDescriptor(Class rootDeclaringClass, Annotation annotation) { - this(rootDeclaringClass, rootDeclaringClass, null, annotation); - } - - public UntypedAnnotationDescriptor(Class rootDeclaringClass, Class declaringClass, - Annotation composedAnnotation, Annotation annotation) { - super(rootDeclaringClass, declaringClass, composedAnnotation, annotation); - } - } - - private static void assertNonEmptyAnnotationTypeArray(Class[] annotationTypes, String message) { - if (ObjectUtils.isEmpty(annotationTypes)) { - throw new IllegalArgumentException(message); - } - - for (Class clazz : annotationTypes) { - if (!Annotation.class.isAssignableFrom(clazz)) { - throw new IllegalArgumentException("Array elements must be of type Annotation"); - } - } - } - -} diff --git a/src/main/java/org/springframework/data/keyvalue/core/SpelCriteriaAccessor.java b/src/main/java/org/springframework/data/keyvalue/core/SpelCriteriaAccessor.java index 8ebb91930..9da2d713f 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/SpelCriteriaAccessor.java +++ b/src/main/java/org/springframework/data/keyvalue/core/SpelCriteriaAccessor.java @@ -16,18 +16,29 @@ package org.springframework.data.keyvalue.core; import org.springframework.data.keyvalue.core.query.KeyValueQuery; -import org.springframework.data.keyvalue.core.spel.SpelExpressionFactory; import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; /** * {@link CriteriaAccessor} implementation capable of {@link SpelExpression}s. * * @author Christoph Strobl + * @author Oliver Gierke * @since 1.10 */ -public enum SpelCriteriaAccessor implements CriteriaAccessor { +class SpelCriteriaAccessor implements CriteriaAccessor { - INSTANCE; + private final SpelExpressionParser parser; + + /** + * @param parser must not be {@literal null}. + */ + public SpelCriteriaAccessor(SpelExpressionParser parser) { + + Assert.notNull(parser, "SpelExpressionParser must not be null!"); + this.parser = parser; + } @Override public SpelExpression resolve(KeyValueQuery query) { @@ -41,7 +52,7 @@ public enum SpelCriteriaAccessor implements CriteriaAccessor { } if (query.getCritieria() instanceof String) { - return SpelExpressionFactory.parseRaw((String) query.getCritieria()); + return parser.parseRaw((String) query.getCritieria()); } throw new IllegalArgumentException("Cannot create SpelCriteria for " + query.getCritieria()); diff --git a/src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComperator.java b/src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComparator.java similarity index 78% rename from src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComperator.java rename to src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComparator.java index 1f81ab96e..b1458600a 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComperator.java +++ b/src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComparator.java @@ -17,31 +17,36 @@ package org.springframework.data.keyvalue.core; import java.util.Comparator; -import org.springframework.data.keyvalue.core.spel.SpelExpressionFactory; import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; /** * {@link Comparator} implementation using {@link SpelExpression}. * * @author Christoph Strobl + * @author Oliver Gierke * @since 1.10 * @param */ -public class SpelPropertyComperator implements Comparator { +public class SpelPropertyComparator implements Comparator { private final String path; - private SpelExpression expression; + private final SpelExpressionParser parser; private boolean asc = true; private boolean nullsFirst = true; + private SpelExpression expression; /** - * Create new {@link SpelPropertyComperator} comparing given property path. + * Create new {@link SpelPropertyComparator} for the given property path an {@link SpelExpressionParser}.. * - * @param path + * @param path must not be {@literal null} or empty. + * @param parser must not be {@literal null}. */ - public SpelPropertyComperator(String path) { + public SpelPropertyComparator(String path, SpelExpressionParser parser) { + this.path = path; + this.parser = parser; } /** @@ -49,7 +54,7 @@ public class SpelPropertyComperator implements Comparator { * * @return */ - public SpelPropertyComperator asc() { + public SpelPropertyComparator asc() { this.asc = true; return this; } @@ -59,7 +64,7 @@ public class SpelPropertyComperator implements Comparator { * * @return */ - public SpelPropertyComperator desc() { + public SpelPropertyComparator desc() { this.asc = false; return this; } @@ -69,7 +74,7 @@ public class SpelPropertyComperator implements Comparator { * * @return */ - public SpelPropertyComperator nullsFirst() { + public SpelPropertyComparator nullsFirst() { this.nullsFirst = true; return this; } @@ -79,7 +84,7 @@ public class SpelPropertyComperator implements Comparator { * * @return */ - public SpelPropertyComperator nullsLast() { + public SpelPropertyComparator nullsLast() { this.nullsFirst = false; return this; } @@ -92,7 +97,7 @@ public class SpelPropertyComperator implements Comparator { protected SpelExpression getExpression() { if (this.expression == null) { - this.expression = SpelExpressionFactory.parseRaw(buildExpressionForPath()); + this.expression = parser.parseRaw(buildExpressionForPath()); } return this.expression; @@ -142,5 +147,4 @@ public class SpelPropertyComperator implements Comparator { public String getPath() { return path; } - } diff --git a/src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java b/src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java index 73167c25d..bc5cd76bc 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java +++ b/src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java @@ -25,27 +25,41 @@ import java.util.List; import org.springframework.data.keyvalue.core.query.KeyValueQuery; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; /** * {@link QueryEngine} implementation specific for executing {@link SpelExpression} based {@link KeyValueQuery} against * {@link KeyValueAdapter}. * * @author Christoph Strobl + * @author Oliver Gierke * @since 1.10 * @param */ -public class SpelQueryEngine extends - QueryEngine> { +class SpelQueryEngine extends QueryEngine> { + private static final SpelExpressionParser PARSER = new SpelExpressionParser(); + + /** + * Creates a new {@link SpelQueryEngine}. + */ public SpelQueryEngine() { - super(SpelCriteriaAccessor.INSTANCE, SpelSortAccessor.INSTNANCE); + super(new SpelCriteriaAccessor(PARSER), new SpelSortAccessor(PARSER)); } + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.QueryEngine#execute(java.lang.Object, java.lang.Object, int, int, java.io.Serializable) + */ @Override public Collection execute(SpelExpression criteria, Comparator sort, int offset, int rows, Serializable keyspace) { return sortAndFilterMatchingRange(getAdapter().getAllOf(keyspace), criteria, sort, offset, rows); } + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.QueryEngine#count(java.lang.Object, java.io.Serializable) + */ @Override public long count(SpelExpression criteria, Serializable keyspace) { return filterMatchingRange(getAdapter().getAllOf(keyspace), criteria, -1, -1).size(); @@ -63,7 +77,7 @@ public class SpelQueryEngine extends return filterMatchingRange(tmp, criteria, offset, rows); } - private List filterMatchingRange(Iterable source, SpelExpression criteria, int offset, int rows) { + private static List filterMatchingRange(Iterable source, SpelExpression criteria, int offset, int rows) { List result = new ArrayList(); @@ -99,7 +113,7 @@ public class SpelQueryEngine extends } } } + return result; } - } diff --git a/src/main/java/org/springframework/data/keyvalue/core/SpelSortAccessor.java b/src/main/java/org/springframework/data/keyvalue/core/SpelSortAccessor.java index 8daa3d4b4..3f4a419ef 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/SpelSortAccessor.java +++ b/src/main/java/org/springframework/data/keyvalue/core/SpelSortAccessor.java @@ -21,17 +21,34 @@ import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.NullHandling; import org.springframework.data.domain.Sort.Order; import org.springframework.data.keyvalue.core.query.KeyValueQuery; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; import org.springframework.util.comparator.CompoundComparator; /** - * {@link SortAccessor} implementation capable of creating {@link SpelPropertyComperator}. + * {@link SortAccessor} implementation capable of creating {@link SpelPropertyComparator}. * * @author Christoph Strobl + * @author Oliver Gierke + * @since 1.10 */ -public enum SpelSortAccessor implements SortAccessor> { +class SpelSortAccessor implements SortAccessor> { - INSTNANCE; + private final SpelExpressionParser parser; + /** + * @param parser must not be {@literal null}. + */ + public SpelSortAccessor(SpelExpressionParser parser) { + + Assert.notNull(parser, "SpelExpressionParser must not be null!"); + this.parser = parser; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.core.SortAccessor#resolve(org.springframework.data.keyvalue.core.query.KeyValueQuery) + */ @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public Comparator resolve(KeyValueQuery query) { @@ -43,7 +60,7 @@ public enum SpelSortAccessor implements SortAccessor> { CompoundComparator compoundComperator = new CompoundComparator(); for (Order order : query.getSort()) { - SpelPropertyComperator spelSort = new SpelPropertyComperator(order.getProperty()); + SpelPropertyComparator spelSort = new SpelPropertyComparator(order.getProperty(), parser); if (Direction.DESC.equals(order.getDirection())) { diff --git a/src/main/java/org/springframework/data/keyvalue/core/mapping/BasicMappingContext.java b/src/main/java/org/springframework/data/keyvalue/core/mapping/BasicMappingContext.java deleted file mode 100644 index 90496437c..000000000 --- a/src/main/java/org/springframework/data/keyvalue/core/mapping/BasicMappingContext.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2014 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.keyvalue.core.mapping; - -import java.beans.PropertyDescriptor; -import java.lang.reflect.Field; - -import org.springframework.data.mapping.context.AbstractMappingContext; -import org.springframework.data.mapping.model.BasicPersistentEntity; -import org.springframework.data.mapping.model.SimpleTypeHolder; -import org.springframework.data.util.TypeInformation; - -/** - * @author Christoph Strobl - * @since 1.10 - */ -public class BasicMappingContext extends - AbstractMappingContext, BasicPersistentProperty> { - - @Override - protected BasicPersistentEntity createPersistentEntity( - TypeInformation typeInformation) { - - return new BasicPersistentEntity(typeInformation); - } - - @Override - protected BasicPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, - BasicPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { - - return new BasicPersistentProperty(field, descriptor, owner, simpleTypeHolder); - } - -} diff --git a/src/main/java/org/springframework/data/keyvalue/core/mapping/BasicPersistentProperty.java b/src/main/java/org/springframework/data/keyvalue/core/mapping/KeyValuePersistentProperty.java similarity index 69% rename from src/main/java/org/springframework/data/keyvalue/core/mapping/BasicPersistentProperty.java rename to src/main/java/org/springframework/data/keyvalue/core/mapping/KeyValuePersistentProperty.java index 7a40e147c..453b0013b 100644 --- a/src/main/java/org/springframework/data/keyvalue/core/mapping/BasicPersistentProperty.java +++ b/src/main/java/org/springframework/data/keyvalue/core/mapping/KeyValuePersistentProperty.java @@ -30,16 +30,19 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; * @author Christoph Strobl * @since 1.10 */ -public class BasicPersistentProperty extends AnnotationBasedPersistentProperty { +public class KeyValuePersistentProperty extends AnnotationBasedPersistentProperty { - public BasicPersistentProperty(Field field, PropertyDescriptor propertyDescriptor, - PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + public KeyValuePersistentProperty(Field field, PropertyDescriptor propertyDescriptor, + PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { super(field, propertyDescriptor, owner, simpleTypeHolder); } + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation() + */ @Override - protected Association createAssociation() { - return new Association(this, null); + protected Association createAssociation() { + return new Association(this, null); } - } diff --git a/src/main/java/org/springframework/data/keyvalue/core/mapping/context/KeyValueMappingContext.java b/src/main/java/org/springframework/data/keyvalue/core/mapping/context/KeyValueMappingContext.java new file mode 100644 index 000000000..f3266fed2 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/core/mapping/context/KeyValueMappingContext.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 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.keyvalue.core.mapping.context; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; + +import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty; +import org.springframework.data.mapping.context.AbstractMappingContext; +import org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.util.TypeInformation; + +/** + * @author Christoph Strobl + * @since 1.10 + */ +public class KeyValueMappingContext extends + AbstractMappingContext, KeyValuePersistentProperty> { + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation) + */ + @Override + protected BasicPersistentEntity createPersistentEntity( + TypeInformation typeInformation) { + return new BasicPersistentEntity(typeInformation); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder) + */ + @Override + protected KeyValuePersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, + BasicPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { + return new KeyValuePersistentProperty(field, descriptor, owner, simpleTypeHolder); + } +} diff --git a/src/main/java/org/springframework/data/keyvalue/core/spel/SpelExpressionFactory.java b/src/main/java/org/springframework/data/keyvalue/core/spel/SpelExpressionFactory.java deleted file mode 100644 index 2bffeeae5..000000000 --- a/src/main/java/org/springframework/data/keyvalue/core/spel/SpelExpressionFactory.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2014 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.keyvalue.core.spel; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeanUtils; -import org.springframework.expression.ExpressionException; -import org.springframework.expression.spel.SpelParserConfiguration; -import org.springframework.expression.spel.standard.SpelExpression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.util.ClassUtils; -import org.springframework.util.MethodInvoker; - -/** - * Factory capable of parsing raw expression strings taking Spring 4.1 compiled expressions into concern. Will fall back - * to non compiled ones in case lower than 4.1 Spring version is detected or expression compilation fails. - * - * @author Christoph Strobl - * @since 1.10 - */ -public class SpelExpressionFactory { - - private static final Logger LOGGER = LoggerFactory.getLogger(SpelExpressionFactory.class); - - private static final boolean IS_SPEL_COMPILER_PRESENT = ClassUtils.isPresent( - "org.springframework.expression.spel.standard.SpelCompiler", SpelExpressionFactory.class.getClassLoader()); - - private static final SpelParserConfiguration DEFAULT_PARSER_CONFIG = new SpelParserConfiguration(false, false); - - private static SpelExpressionParser compiledModeExpressionParser; - private static SpelExpressionParser expressionParser; - - static { - - expressionParser = new SpelExpressionParser(DEFAULT_PARSER_CONFIG); - - if (IS_SPEL_COMPILER_PRESENT) { - SpelExpressionParser parser = new SpelExpressionParser(silentlyInitializeCompiledMode("IMMEDIATE")); - - if (usesPatchedSpelCompilerThatAllowsReferenceToContextVariables(parser)) { - compiledModeExpressionParser = parser; - } - - } - } - - /** - * @param expressionString - * @return - */ - public static SpelExpression parseRaw(String expressionString) { - - if (compiledModeExpressionParser != null) { - try { - return compileSpelExpression(compiledModeExpressionParser.parseRaw(expressionString)); - } catch (ExpressionException e) { - LOGGER.info(e.getMessage(), e); - } - } - - return expressionParser.parseRaw(expressionString); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private static SpelParserConfiguration silentlyInitializeCompiledMode(String mode) { - - try { - Class compilerMode = ClassUtils.forName("org.springframework.expression.spel.SpelCompilerMode", - SpelExpressionFactory.class.getClassLoader()); - - Constructor constructor = ClassUtils.getConstructorIfAvailable( - SpelParserConfiguration.class, compilerMode, ClassLoader.class); - if (constructor != null) { - return BeanUtils.instantiateClass(constructor, Enum.valueOf(compilerMode, mode.toUpperCase()), - SpelExpressionFactory.class.getClassLoader()); - } - } catch (Exception e) { - LOGGER.info(String.format("Could not create SpelParserConfiguration for mode '%s'.", mode), e); - } - - return DEFAULT_PARSER_CONFIG; - } - - private static SpelExpression compileSpelExpression(SpelExpression compilableExpression) { - - try { - - MethodInvoker mi = new MethodInvoker(); - mi.setTargetObject(compilableExpression); - mi.setTargetMethod("compileExpression"); - mi.prepare(); - mi.invoke(); - - return compilableExpression; - - } catch (ExpressionException ex) { - - throw new ExpressionException(String.format("Could parse expression %s in compiled mode. Using fallback.", - compilableExpression.getExpressionString()), ex); - } catch (IllegalAccessException ex) { - throw new ExpressionException("o_O failed to invoke compileExpression. Are you using at least Spring 4.1?", ex); - } catch (NoSuchMethodException ex) { - throw new ExpressionException("o_O missing method compileExpression. Using fallback.", ex); - } catch (ClassNotFoundException ex) { - throw new ExpressionException("o_O missing class SpelExpression.", ex); - } catch (InvocationTargetException ex) { - throw new ExpressionException("o_O failed to invoke compileExpression. Are you using at least Spring 4.1?", ex); - } - } - - /** - * @see SPR-12326, SPR-12359 - * @param parser - * @return - */ - private static boolean usesPatchedSpelCompilerThatAllowsReferenceToContextVariables(SpelExpressionParser parser) { - - SpelExpression ex = parser.parseRaw("#foo == 1"); - ex.getEvaluationContext().setVariable("foo", 1); - - try { - for (int i = 0; i < 3; i++) { - ex.getValue(Boolean.class); - } - return true; - } catch (Exception e) { - LOGGER.info("Compiled SpEL sanity check failed. Falling back to non compiled mode"); - } - return false; - } - -} diff --git a/src/main/java/org/springframework/data/keyvalue/ehcache/ListConverter.java b/src/main/java/org/springframework/data/keyvalue/ehcache/ListConverter.java index 263f53709..ae0a58580 100644 --- a/src/main/java/org/springframework/data/keyvalue/ehcache/ListConverter.java +++ b/src/main/java/org/springframework/data/keyvalue/ehcache/ListConverter.java @@ -24,7 +24,6 @@ import org.springframework.core.convert.converter.Converter; * @author Christoph Strobl * @param * @param - * @since 1.10 */ public class ListConverter implements Converter, List> { diff --git a/src/main/java/org/springframework/data/keyvalue/ehcache/repository/config/EhCacheRepositoriesRegistrar.java b/src/main/java/org/springframework/data/keyvalue/ehcache/repository/config/EhCacheRepositoriesRegistrar.java new file mode 100644 index 000000000..708fe713c --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/ehcache/repository/config/EhCacheRepositoriesRegistrar.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 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.keyvalue.ehcache.repository.config; + +import java.lang.annotation.Annotation; + +import org.springframework.data.keyvalue.repository.config.KeyValueRepositoriesRegistrar; + +/** + * Special {@link KeyValueRepositoriesRegistrar} to point the infrastructure to evaluate + * {@link EnableEhCacheRepositories}. + * + * @author Oliver Gierke + */ +class EhCacheRepositoriesRegistrar extends KeyValueRepositoriesRegistrar { + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.repository.config.KeyValueRepositoriesRegistrar#getAnnotation() + */ + @Override + protected Class getAnnotation() { + return EnableEhCacheRepositories.class; + } +} diff --git a/src/main/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositories.java b/src/main/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositories.java index 8579b14ca..981760edb 100644 --- a/src/main/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositories.java +++ b/src/main/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositories.java @@ -27,26 +27,23 @@ import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Import; import org.springframework.data.keyvalue.core.KeyValueOperations; import org.springframework.data.keyvalue.ehcache.repository.query.EhCacheQueryCreator; -import org.springframework.data.keyvalue.repository.config.EnableKeyValueRepositories; -import org.springframework.data.keyvalue.repository.config.KeyValueRepositoriesRegistrar; +import org.springframework.data.keyvalue.repository.config.QueryCreatorType; import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; -import org.springframework.data.repository.query.parser.AbstractQueryCreator; /** * Annotation to activate EhCache repositories. If no base package is configured through either {@link #value()}, * {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class. * * @author Christoph Strobl - * @since 1.10 */ -@EnableKeyValueRepositories -@Target({ ElementType.TYPE }) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Import(KeyValueRepositoriesRegistrar.class) +@Import(EhCacheRepositoriesRegistrar.class) +@QueryCreatorType(EhCacheQueryCreator.class) public @interface EnableEhCacheRepositories { /** @@ -123,13 +120,4 @@ public @interface EnableEhCacheRepositories { * repositories infrastructure. */ boolean considerNestedRepositories() default false; - - /** - * Configures the query creator to be used for deriving queries from - * {@link org.springframework.data.repository.query.parser.PartTree}. - * - * @return - */ - Class> queryCreator() default EhCacheQueryCreator.class; - } diff --git a/src/main/java/org/springframework/data/keyvalue/ehcache/repository/query/EhCacheQueryCreator.java b/src/main/java/org/springframework/data/keyvalue/ehcache/repository/query/EhCacheQueryCreator.java index a2f3d93c4..6b4ef27ab 100644 --- a/src/main/java/org/springframework/data/keyvalue/ehcache/repository/query/EhCacheQueryCreator.java +++ b/src/main/java/org/springframework/data/keyvalue/ehcache/repository/query/EhCacheQueryCreator.java @@ -32,7 +32,6 @@ import org.springframework.data.repository.query.parser.PartTree; /** * @author Christoph Strobl - * @since 1.10 */ public class EhCacheQueryCreator extends AbstractQueryCreator, Criteria> { diff --git a/src/main/java/org/springframework/data/keyvalue/hazelcast/HazelcastQueryEngine.java b/src/main/java/org/springframework/data/keyvalue/hazelcast/HazelcastQueryEngine.java index b81c922de..5c1d74bda 100644 --- a/src/main/java/org/springframework/data/keyvalue/hazelcast/HazelcastQueryEngine.java +++ b/src/main/java/org/springframework/data/keyvalue/hazelcast/HazelcastQueryEngine.java @@ -31,12 +31,11 @@ import com.hazelcast.query.PredicateBuilder; /** * @author Christoph Strobl - * @since 1.10 */ public class HazelcastQueryEngine extends QueryEngine, Comparator> { public HazelcastQueryEngine() { - super(HazelcastCriteriaAccessor.INSTANCE, HazelcastSortAccessor.INSTNANCE); + super(HazelcastCriteriaAccessor.INSTANCE, HazelcastSortAccessor.INSTANCE); } @Override @@ -91,7 +90,7 @@ public class HazelcastQueryEngine extends QueryEngine> { - INSTNANCE; + INSTANCE; @Override public Comparator resolve(KeyValueQuery query) { diff --git a/src/main/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositories.java b/src/main/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositories.java index 362957815..8749032bf 100644 --- a/src/main/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositories.java +++ b/src/main/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositories.java @@ -27,26 +27,23 @@ import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Import; import org.springframework.data.keyvalue.core.KeyValueOperations; import org.springframework.data.keyvalue.hazelcast.repository.query.HazelcastQueryCreator; -import org.springframework.data.keyvalue.repository.config.EnableKeyValueRepositories; -import org.springframework.data.keyvalue.repository.config.KeyValueRepositoriesRegistrar; +import org.springframework.data.keyvalue.repository.config.QueryCreatorType; import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; -import org.springframework.data.repository.query.parser.AbstractQueryCreator; /** * Annotation to activate Hazelcast repositories. If no base package is configured through either {@link #value()}, * {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class. * * @author Christoph Strobl - * @since 1.10 */ -@EnableKeyValueRepositories -@Target({ ElementType.TYPE }) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Import(KeyValueRepositoriesRegistrar.class) +@Import(HazelcastRepositoriesRegistrar.class) +@QueryCreatorType(HazelcastQueryCreator.class) public @interface EnableHazelcastRepositories { /** @@ -123,13 +120,4 @@ public @interface EnableHazelcastRepositories { * repositories infrastructure. */ boolean considerNestedRepositories() default false; - - /** - * Configures the query creator to be used for deriving queries from - * {@link org.springframework.data.repository.query.parser.PartTree}. - * - * @return - */ - Class> queryCreator() default HazelcastQueryCreator.class; - } diff --git a/src/main/java/org/springframework/data/keyvalue/hazelcast/repository/config/HazelcastRepositoriesRegistrar.java b/src/main/java/org/springframework/data/keyvalue/hazelcast/repository/config/HazelcastRepositoriesRegistrar.java new file mode 100644 index 000000000..68b943b1a --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/hazelcast/repository/config/HazelcastRepositoriesRegistrar.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 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.keyvalue.hazelcast.repository.config; + +import java.lang.annotation.Annotation; + +import org.springframework.data.keyvalue.repository.config.KeyValueRepositoriesRegistrar; + +/** + * Special {@link KeyValueRepositoriesRegistrar} to point the infrastructure to inspect + * {@link EnableHazelcastRepositories}. + * + * @author Oliver Gierke + */ +class HazelcastRepositoriesRegistrar extends KeyValueRepositoriesRegistrar { + + /* + * (non-Javadoc) + * @see org.springframework.data.keyvalue.repository.config.KeyValueRepositoriesRegistrar#getAnnotation() + */ + @Override + protected Class getAnnotation() { + return EnableHazelcastRepositories.class; + } +} diff --git a/src/main/java/org/springframework/data/keyvalue/hazelcast/repository/query/HazelcastQueryCreator.java b/src/main/java/org/springframework/data/keyvalue/hazelcast/repository/query/HazelcastQueryCreator.java index 064d235ae..ffda72f51 100644 --- a/src/main/java/org/springframework/data/keyvalue/hazelcast/repository/query/HazelcastQueryCreator.java +++ b/src/main/java/org/springframework/data/keyvalue/hazelcast/repository/query/HazelcastQueryCreator.java @@ -32,7 +32,6 @@ import com.hazelcast.query.PredicateBuilder; /** * @author Christoph Strobl - * @since 1.10 */ public class HazelcastQueryCreator extends AbstractQueryCreator>, Predicate> { diff --git a/src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapter.java b/src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapter.java index b09dcd193..b55efc213 100644 --- a/src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapter.java +++ b/src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapter.java @@ -151,6 +151,7 @@ public class MapKeyValueAdapter extends AbstractKeyValueAdapter { Assert.notNull(keyspace, "Collection must not be 'null' for lookup."); Map map = data.get(keyspace); + if (map != null) { return map; } @@ -162,5 +163,4 @@ public class MapKeyValueAdapter extends AbstractKeyValueAdapter { private void addMapForKeySpace(Serializable keyspace) { data.put(keyspace, CollectionFactory. createMap(mapType, 1000)); } - } diff --git a/src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapterFactory.java b/src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapterFactory.java index ecf91f825..6a97c7232 100644 --- a/src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapterFactory.java +++ b/src/main/java/org/springframework/data/keyvalue/map/MapKeyValueAdapterFactory.java @@ -38,6 +38,8 @@ public class MapKeyValueAdapterFactory { private Map> initialValues; /** + * Creates a new {@link MapKeyValueAdapterFactory}. + * * @see MapKeyValueAdapterFactory#MapKeyValueAdapterFactory(Class) */ public MapKeyValueAdapterFactory() { @@ -45,6 +47,8 @@ public class MapKeyValueAdapterFactory { } /** + * Creates a new MKVAF with the given {@link Map} type to be used to hold the values in. + * * @param type any {@link Class} of type {@link Map}. Can be {@literal null} and will be defaulted to * {@link ConcurrentHashMap}. */ @@ -58,18 +62,27 @@ public class MapKeyValueAdapterFactory { /** * Set values for a given {@literal keyspace} that to populate the adapter after creation. * - * @param keyspace - * @param values + * @param keyspace must not be {@literal null}. + * @param values must not be {@literal null}. */ public void setInitialValuesForKeyspace(Serializable keyspace, Map values) { - Assert.notNull(keyspace, "KeySpace must not be 'null'."); - Assert.notNull(values, "Values must not be 'null'."); + Assert.notNull(keyspace, "KeySpace must not be null!"); + Assert.notNull(values, "Values must not be null!"); + initialValues.put(keyspace, values); } + /** + * Configures the {@link Map} type to be used as backing store. + * + * @param mapType must not be {@literal null}. + */ @SuppressWarnings("rawtypes") public void setMapType(Class mapType) { + + Assert.notNull(mapType, "May type must not be null!"); + this.mapType = mapType; } @@ -82,6 +95,7 @@ public class MapKeyValueAdapterFactory { MapKeyValueAdapter adapter = createAdapter(); populateAdapter(adapter); + return adapter; } diff --git a/src/main/java/org/springframework/data/keyvalue/repository/config/EnableKeyValueRepositories.java b/src/main/java/org/springframework/data/keyvalue/repository/config/EnableKeyValueRepositories.java index f2346b81c..1b32a7894 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/config/EnableKeyValueRepositories.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/config/EnableKeyValueRepositories.java @@ -30,7 +30,6 @@ import org.springframework.data.keyvalue.repository.query.SpelQueryCreator; import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; -import org.springframework.data.repository.query.parser.AbstractQueryCreator; /** * Annotation to activate KeyValue repositories. If no base package is configured through either {@link #value()}, @@ -44,6 +43,7 @@ import org.springframework.data.repository.query.parser.AbstractQueryCreator; @Documented @Inherited @Import(KeyValueRepositoriesRegistrar.class) +@QueryCreatorType(SpelQueryCreator.class) public @interface EnableKeyValueRepositories { /** @@ -120,13 +120,4 @@ public @interface EnableKeyValueRepositories { * repositories infrastructure. */ boolean considerNestedRepositories() default false; - - /** - * Configures the query creator to be used for deriving queries from - * {@link org.springframework.data.repository.query.parser.PartTree}. - * - * @return - */ - Class> queryCreator() default SpelQueryCreator.class; - } diff --git a/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoriesRegistrar.java b/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoriesRegistrar.java index 6e3335d90..dba368914 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoriesRegistrar.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoriesRegistrar.java @@ -45,5 +45,4 @@ public class KeyValueRepositoriesRegistrar extends RepositoryBeanDefinitionRegis protected RepositoryConfigurationExtension getExtension() { return new KeyValueRepositoryConfigurationExtension(); } - } diff --git a/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java index 554e7516c..4130c45b5 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java @@ -17,11 +17,16 @@ package org.springframework.data.keyvalue.repository.config; import java.util.Collection; import java.util.Collections; +import java.util.Map; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; import org.springframework.data.keyvalue.repository.KeyValueRepository; +import org.springframework.data.keyvalue.repository.query.SpelQueryCreator; import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean; import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; import org.springframework.data.repository.config.RepositoryConfigurationExtension; @@ -36,7 +41,7 @@ import org.springframework.data.repository.config.RepositoryConfigurationSource; */ public class KeyValueRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport { - private boolean mappingContextAvailable = false; + private static final String MAPPING_CONTEXT_BEAN_NAME = "keyValueMappingContext"; /* * (non-Javadoc) @@ -82,12 +87,31 @@ public class KeyValueRepositoryConfigurationExtension extends RepositoryConfigur public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) { AnnotationAttributes attributes = config.getAttributes(); - builder.addPropertyReference("keyValueOperations", attributes.getString("keyValueTemplateRef")); - builder.addPropertyValue("queryCreator", attributes.getClass("queryCreator")); - if (mappingContextAvailable) { - builder.addPropertyReference("mappingContext", "keyValueMappingContext"); + builder.addPropertyReference("keyValueOperations", attributes.getString("keyValueTemplateRef")); + builder.addPropertyValue("queryCreator", getQueryCreatorType(config)); + builder.addPropertyReference("mappingContext", MAPPING_CONTEXT_BEAN_NAME); + } + + /** + * Detects the query creator type to be used for the factory to set. Will lookup a {@link QueryCreatorType} annotation + * on the {@code @Enable}-annotation or use {@link SpelQueryCreator} if not found. + * + * @param config + * @return + */ + private static Class getQueryCreatorType(AnnotationRepositoryConfigurationSource config) { + + AnnotationMetadata metadata = config.getEnableAnnotationMetadata(); + + Map queryCreatorFoo = metadata.getAnnotationAttributes(QueryCreatorType.class.getName()); + + if (queryCreatorFoo == null) { + return SpelQueryCreator.class; } + + AnnotationAttributes queryCreatorAttributes = new AnnotationAttributes(queryCreatorFoo); + return queryCreatorAttributes.getClass("value"); } /* @@ -98,7 +122,12 @@ public class KeyValueRepositoryConfigurationExtension extends RepositoryConfigur public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) { super.registerBeansForRoot(registry, configurationSource); - this.mappingContextAvailable = registry.containsBeanDefinition("keyValueMappingContext"); - } + if (!registry.containsBeanDefinition(MAPPING_CONTEXT_BEAN_NAME)) { + + RootBeanDefinition mappingContextDefinition = new RootBeanDefinition(KeyValueMappingContext.class); + mappingContextDefinition.setSource(configurationSource.getSource()); + registry.registerBeanDefinition(MAPPING_CONTEXT_BEAN_NAME, mappingContextDefinition); + } + } } diff --git a/src/main/java/org/springframework/data/keyvalue/repository/config/QueryCreatorType.java b/src/main/java/org/springframework/data/keyvalue/repository/config/QueryCreatorType.java new file mode 100644 index 000000000..44b05a271 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/repository/config/QueryCreatorType.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014 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.keyvalue.repository.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.data.repository.query.parser.AbstractQueryCreator; + +/** + * Annotation to customize the query creator type to be used for a specific store. + * + * @author Oliver Gierke + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +public @interface QueryCreatorType { + + Class> value(); +} diff --git a/src/main/java/org/springframework/data/keyvalue/repository/config/RepositoryNameSpaceHandler.java b/src/main/java/org/springframework/data/keyvalue/repository/config/RepositoryNameSpaceHandler.java new file mode 100644 index 000000000..6efd4a53b --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/repository/config/RepositoryNameSpaceHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012 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.keyvalue.repository.config; + +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.NamespaceHandler; +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; +import org.springframework.data.repository.config.RepositoryBeanDefinitionParser; + +/** + * {@link NamespaceHandler} to register {@link BeanDefinitionParser}s for key-value repositories. + * + * @author Oliver Gierke + * @author Christoph Strobl + * @since 1.4 + */ +public class RepositoryNameSpaceHandler extends NamespaceHandlerSupport { + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.xml.NamespaceHandler#init() + */ + public void init() { + + KeyValueRepositoryConfigurationExtension extension = new KeyValueRepositoryConfigurationExtension(); + RepositoryBeanDefinitionParser repositoryBeanDefinitionParser = new RepositoryBeanDefinitionParser(extension); + registerBeanDefinitionParser("repositories", repositoryBeanDefinitionParser); + } +} diff --git a/src/main/java/org/springframework/data/keyvalue/repository/query/KeyValuePartTreeQuery.java b/src/main/java/org/springframework/data/keyvalue/repository/query/KeyValuePartTreeQuery.java new file mode 100644 index 000000000..f0c55afbd --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/repository/query/KeyValuePartTreeQuery.java @@ -0,0 +1,144 @@ +/* + * Copyright 2014 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.keyvalue.repository.query; + +import java.lang.reflect.Constructor; +import java.util.List; + +import org.springframework.beans.BeanUtils; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.keyvalue.core.KeyValueOperations; +import org.springframework.data.keyvalue.core.query.KeyValueQuery; +import org.springframework.data.repository.query.EvaluationContextProvider; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.parser.AbstractQueryCreator; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; + +/** + * {@link RepositoryQuery} implementation deriving queries from {@link PartTree} using a predefined + * {@link AbstractQueryCreator}. + * + * @author Christoph Strobl + * @author Oliver Gierke + * @since 1.10 + */ +public class KeyValuePartTreeQuery implements RepositoryQuery { + + private final EvaluationContextProvider evaluationContextProvider; + private final QueryMethod queryMethod; + private final KeyValueOperations keyValueOperations; + private final Class> queryCreator; + + private KeyValueQuery query; + + public KeyValuePartTreeQuery(QueryMethod queryMethod, EvaluationContextProvider evalContextProvider, + KeyValueOperations keyValueOperations, Class> queryCreator) { + + this.queryMethod = queryMethod; + this.keyValueOperations = keyValueOperations; + this.evaluationContextProvider = evalContextProvider; + this.queryCreator = queryCreator; + } + + @Override + public QueryMethod getQueryMethod() { + return queryMethod; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Object execute(Object[] parameters) { + + KeyValueQuery query = prepareQuery(parameters); + + if (queryMethod.isPageQuery() || queryMethod.isSliceQuery()) { + + Pageable page = (Pageable) parameters[queryMethod.getParameters().getPageableIndex()]; + query.setOffset(page.getOffset()); + query.setRows(page.getPageSize()); + + List result = this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType()); + + long count = queryMethod.isSliceQuery() ? 0 : keyValueOperations.count(query, queryMethod.getEntityInformation() + .getJavaType()); + + return new PageImpl(result, page, count); + + } else if (queryMethod.isCollectionQuery()) { + + return this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType()); + + } else if (queryMethod.isQueryForEntity()) { + + List result = this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType()); + return CollectionUtils.isEmpty(result) ? null : result.get(0); + + } + + throw new UnsupportedOperationException("Query method not supported."); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private KeyValueQuery prepareQuery(Object[] parameters) { + + ParametersParameterAccessor accessor = new ParametersParameterAccessor(getQueryMethod().getParameters(), parameters); + + if (this.query == null) { + this.query = createQuery(accessor); + } + + KeyValueQuery q = new KeyValueQuery(this.query.getCritieria()); + + if (accessor.getPageable() != null) { + q.setOffset(accessor.getPageable().getOffset()); + q.setRows(accessor.getPageable().getPageSize()); + } else { + q.setOffset(-1); + q.setRows(-1); + } + + if (accessor.getSort() != null) { + q.setSort(accessor.getSort()); + } else { + q.setSort(this.query.getSort()); + } + + if (q.getCritieria() instanceof SpelExpression) { + EvaluationContext context = this.evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(), + parameters); + ((SpelExpression) q.getCritieria()).setEvaluationContext(context); + } + + return q; + } + + public KeyValueQuery createQuery(ParametersParameterAccessor accessor) { + + PartTree tree = new PartTree(getQueryMethod().getName(), getQueryMethod().getEntityInformation().getJavaType()); + + Constructor> constructor = (Constructor>) ClassUtils + .getConstructorIfAvailable(queryCreator, PartTree.class, ParameterAccessor.class); + return (KeyValueQuery) BeanUtils.instantiateClass(constructor, tree, accessor).createQuery(); + } +} diff --git a/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java b/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java index 502b97280..cf1e71469 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java @@ -20,7 +20,6 @@ import java.util.Iterator; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Sort; import org.springframework.data.keyvalue.core.query.KeyValueQuery; -import org.springframework.data.keyvalue.core.spel.SpelExpressionFactory; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; @@ -28,31 +27,56 @@ import org.springframework.data.repository.query.parser.Part.IgnoreCaseType; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.repository.query.parser.PartTree.OrPart; import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; /** + * {@link AbstractQueryCreator} to create {@link SpelExpression} based {@link KeyValueQuery}s. + * * @author Christoph Strobl + * @author Oliver Gierke * @since 1.10 */ public class SpelQueryCreator extends AbstractQueryCreator, String> { - private SpelExpression expression; + private static final SpelExpressionParser PARSER = new SpelExpressionParser(); + private final SpelExpression expression; + + /** + * Creates a new {@link SpelQueryCreator} for the given {@link PartTree} and {@link ParameterAccessor}. + * + * @param tree must not be {@literal null}. + * @param parameters must not be {@literal null}. + */ public SpelQueryCreator(PartTree tree, ParameterAccessor parameters) { super(tree, parameters); + this.expression = toPredicateExpression(tree); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator) + */ @Override protected String create(Part part, Iterator iterator) { return ""; } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator) + */ @Override protected String and(Part part, String base, Iterator iterator) { return ""; } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object) + */ @Override protected String or(String base, String criteria) { return ""; @@ -62,9 +86,11 @@ public class SpelQueryCreator extends AbstractQueryCreator complete(String criteria, Sort sort) { KeyValueQuery query = new KeyValueQuery(this.expression); + if (sort != null) { query.orderBy(sort); } + return query; } @@ -75,10 +101,10 @@ public class SpelQueryCreator extends AbstractQueryCreator orPartIter = tree.iterator(); orPartIter.hasNext();) { - OrPart orPart = orPartIter.next(); - int partCnt = 0; StringBuilder partBuilder = new StringBuilder(); + OrPart orPart = orPartIter.next(); + for (Iterator partIter = orPart.iterator(); partIter.hasNext();) { Part part = partIter.next(); @@ -87,7 +113,7 @@ public class SpelQueryCreator extends AbstractQueryCreator DEFAULT_QUERY_CREATOR = SpelQueryCreator.class; + private final KeyValueOperations keyValueOperations; private final MappingContext context; - private final Class> queryCreator; /** + * Creates a new {@link KeyValueRepositoryFactory} for the given {@link KeyValueOperations}. + * * @param keyValueOperations must not be {@literal null}. */ public KeyValueRepositoryFactory(KeyValueOperations keyValueOperations) { @@ -76,14 +65,19 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport { } /** + * Creates a new {@link KeyValueRepositoryFactory} for the given {@link KeyValueOperations} and + * {@link AbstractQueryCreator}-type. + * * @param keyValueOperations must not be {@literal null}. * @param queryCreator defaulted to {@link #DEFAULT_QUERY_CREATOR} if {@literal null}. */ public KeyValueRepositoryFactory(KeyValueOperations keyValueOperations, Class> queryCreator) { - Assert.notNull(keyValueOperations, "KeyValueOperations must not be 'null' when creating factory."); - this.queryCreator = queryCreator != null ? queryCreator : DEFAULT_QUERY_CREATOR; + Assert.notNull(keyValueOperations, "KeyValueOperations must not be null!"); + Assert.notNull(queryCreator, "Query creator type must not be null!"); + + this.queryCreator = queryCreator; this.keyValueOperations = keyValueOperations; this.context = keyValueOperations.getMappingContext(); } @@ -92,12 +86,13 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport { * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class) */ - @SuppressWarnings("unchecked") @Override + @SuppressWarnings("unchecked") public EntityInformation getEntityInformation(Class domainClass) { PersistentEntity entity = (PersistentEntity) context.getPersistentEntity(domainClass); PersistentEntityInformation entityInformation = new PersistentEntityInformation(entity); + return entityInformation; } @@ -105,15 +100,17 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport { * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata) */ - @SuppressWarnings({ "unchecked", "rawtypes" }) @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) protected Object getTargetRepository(RepositoryMetadata metadata) { EntityInformation entityInformation = getEntityInformation(metadata.getDomainType()); + if (ClassUtils.isAssignable(QueryDslPredicateExecutor.class, metadata.getRepositoryInterface())) { return new QueryDslKeyValueRepository(entityInformation, keyValueOperations); } - return new BasicKeyValueRepository(entityInformation, keyValueOperations); + + return new SimpleKeyValueRepository(entityInformation, keyValueOperations); } /* @@ -123,13 +120,13 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport { @Override protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { return isQueryDslRepository(metadata.getRepositoryInterface()) ? QueryDslKeyValueRepository.class - : BasicKeyValueRepository.class; + : SimpleKeyValueRepository.class; } /** * Returns whether the given repository interface requires a QueryDsl specific implementation to be chosen. * - * @param repositoryInterface + * @param repositoryInterface must not be {@literal null}. * @return */ private static boolean isQueryDslRepository(Class repositoryInterface) { @@ -147,17 +144,34 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport { /** * @author Christoph Strobl + * @author Oliver Gierke * @since 1.10 */ - static class KeyValueQueryLookupStrategy implements QueryLookupStrategy { + private static class KeyValueQueryLookupStrategy implements QueryLookupStrategy { private EvaluationContextProvider evaluationContextProvider; private KeyValueOperations keyValueOperations; private Class> queryCreator; + /** + * Creates a new {@link KeyValueQueryLookupStrategy} for the given {@link Key}, {@link EvaluationContextProvider}, + * {@link KeyValueOperations} and query creator type. + *

+ * TODO: Key is not considered. Should it? + * + * @param key + * @param evaluationContextProvider must not be {@literal null}. + * @param keyValueOperations must not be {@literal null}. + * @param queryCreator must not be {@literal null}. + */ public KeyValueQueryLookupStrategy(Key key, EvaluationContextProvider evaluationContextProvider, KeyValueOperations keyValueOperations, Class> queryCreator) { + + Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!"); + Assert.notNull(keyValueOperations, "KeyValueOperations must not be null!"); + Assert.notNull(queryCreator, "Query creator type must not be null!"); + this.evaluationContextProvider = evaluationContextProvider; this.keyValueOperations = keyValueOperations; this.queryCreator = queryCreator; @@ -170,119 +184,8 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport { public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) { QueryMethod queryMethod = new QueryMethod(method, metadata); - return doResolveQuery(queryMethod, evaluationContextProvider, this.keyValueOperations); - } - - protected RepositoryQuery doResolveQuery(QueryMethod queryMethod, EvaluationContextProvider contextProvider, - KeyValueOperations keyValueOps) { return new KeyValuePartTreeQuery(queryMethod, evaluationContextProvider, this.keyValueOperations, this.queryCreator); } } - - /** - * {@link RepositoryQuery} implementation deriving queries from {@link PartTree} using a predefined - * {@link AbstractQueryCreator}. - * - * @author Christoph Strobl - * @since 1.10 - */ - public static class KeyValuePartTreeQuery implements RepositoryQuery { - - private EvaluationContextProvider evaluationContextProvider; - private final QueryMethod queryMethod; - private final KeyValueOperations keyValueOperations; - private KeyValueQuery query; - - private Class> queryCreator; - - public KeyValuePartTreeQuery(QueryMethod queryMethod, EvaluationContextProvider evalContextProvider, - KeyValueOperations keyValueOperations, Class> queryCreator) { - - this.queryMethod = queryMethod; - this.keyValueOperations = keyValueOperations; - this.evaluationContextProvider = evalContextProvider; - this.queryCreator = queryCreator; - } - - @Override - public QueryMethod getQueryMethod() { - return queryMethod; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Override - public Object execute(Object[] parameters) { - - KeyValueQuery query = prepareQuery(parameters); - - if (queryMethod.isPageQuery() || queryMethod.isSliceQuery()) { - - Pageable page = (Pageable) parameters[queryMethod.getParameters().getPageableIndex()]; - query.setOffset(page.getOffset()); - query.setRows(page.getPageSize()); - - List result = this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType()); - - long count = queryMethod.isSliceQuery() ? 0 : keyValueOperations.count(query, queryMethod - .getEntityInformation().getJavaType()); - - return new PageImpl(result, page, count); - } - if (queryMethod.isCollectionQuery()) { - - return this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType()); - } - if (queryMethod.isQueryForEntity()) { - - List result = this.keyValueOperations.find(query, queryMethod.getEntityInformation().getJavaType()); - return CollectionUtils.isEmpty(result) ? null : result.get(0); - } - throw new UnsupportedOperationException("Query method not supported."); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private KeyValueQuery prepareQuery(Object[] parameters) { - - ParametersParameterAccessor accessor = new ParametersParameterAccessor(getQueryMethod().getParameters(), - parameters); - - if (this.query == null) { - this.query = createQuery(accessor); - } - - KeyValueQuery q = new KeyValueQuery(this.query.getCritieria()); - if (accessor.getPageable() != null) { - q.setOffset(accessor.getPageable().getOffset()); - q.setRows(accessor.getPageable().getPageSize()); - } else { - q.setOffset(-1); - q.setRows(-1); - } - - if (accessor.getSort() != null) { - q.setSort(accessor.getSort()); - } else { - q.setSort(this.query.getSort()); - } - - if (q.getCritieria() instanceof SpelExpression) { - - EvaluationContext context = this.evaluationContextProvider.getEvaluationContext(getQueryMethod() - .getParameters(), parameters); - ((SpelExpression) q.getCritieria()).setEvaluationContext(context); - } - return q; - } - - public KeyValueQuery createQuery(ParametersParameterAccessor accessor) { - - PartTree tree = new PartTree(getQueryMethod().getName(), getQueryMethod().getEntityInformation().getJavaType()); - - Constructor> constructor = (Constructor>) ClassUtils - .getConstructorIfAvailable(queryCreator, PartTree.class, ParameterAccessor.class); - return (KeyValueQuery) BeanUtils.instantiateClass(constructor, tree, accessor).createQuery(); - } - } - } diff --git a/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactoryBean.java b/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactoryBean.java index a1802760b..5ead4a99b 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactoryBean.java @@ -35,7 +35,6 @@ public class KeyValueRepositoryFactoryBean, S, ID ex RepositoryFactoryBeanSupport { private KeyValueOperations operations; - private boolean mappingContextAvailable = false; private Class> queryCreator; public void setKeyValueOperations(KeyValueOperations operations) { @@ -48,9 +47,7 @@ public class KeyValueRepositoryFactoryBean, S, ID ex */ @Override public void setMappingContext(MappingContext mappingContext) { - super.setMappingContext(mappingContext); - this.mappingContextAvailable = mappingContext != null; } public void setQueryCreator(Class> queryCreator) { @@ -72,12 +69,6 @@ public class KeyValueRepositoryFactoryBean, S, ID ex */ @Override public void afterPropertiesSet() { - - if (!mappingContextAvailable) { - super.setMappingContext(operations.getMappingContext()); - } - super.afterPropertiesSet(); } - } diff --git a/src/main/java/org/springframework/data/keyvalue/repository/QueryDslKeyValueRepository.java b/src/main/java/org/springframework/data/keyvalue/repository/support/QueryDslKeyValueRepository.java similarity index 81% rename from src/main/java/org/springframework/data/keyvalue/repository/QueryDslKeyValueRepository.java rename to src/main/java/org/springframework/data/keyvalue/repository/support/QueryDslKeyValueRepository.java index d794fcc5f..aa853891f 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/QueryDslKeyValueRepository.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/QueryDslKeyValueRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.keyvalue.repository; +package org.springframework.data.keyvalue.repository.support; import static org.springframework.data.querydsl.QueryDslUtils.*; @@ -23,10 +23,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.keyvalue.core.KeyValueOperations; +import org.springframework.data.keyvalue.repository.KeyValueRepository; import org.springframework.data.querydsl.EntityPathResolver; import org.springframework.data.querydsl.QueryDslPredicateExecutor; import org.springframework.data.querydsl.SimpleEntityPathResolver; import org.springframework.data.repository.core.EntityInformation; +import org.springframework.util.Assert; import com.mysema.query.collections.CollQuery; import com.mysema.query.support.ProjectableQuery; @@ -43,7 +45,7 @@ import com.mysema.query.types.path.PathBuilder; * @param * @param */ -public class QueryDslKeyValueRepository extends BasicKeyValueRepository implements +public class QueryDslKeyValueRepository extends SimpleKeyValueRepository implements QueryDslPredicateExecutor { private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE; @@ -52,22 +54,31 @@ public class QueryDslKeyValueRepository extends Basi private final PathBuilder builder; /** - * @param entityInformation - * @param operations + * Creates a new {@link QueryDslKeyValueRepository} for the given {@link EntityInformation} and + * {@link KeyValueOperations}. + * + * @param entityInformation must not be {@literal null}. + * @param operations must not be {@literal null}. */ public QueryDslKeyValueRepository(EntityInformation entityInformation, KeyValueOperations operations) { this(entityInformation, operations, DEFAULT_ENTITY_PATH_RESOLVER); } /** - * @param entityInformation - * @param operations - * @param resolver + * Creates a new {@link QueryDslKeyValueRepository} for the given {@link EntityInformation}, + * {@link KeyValueOperations} and {@link EntityPathResolver}. + * + * @param entityInformation must not be {@literal null}. + * @param operations must not be {@literal null}. + * @param resolver must not be {@literal null}. */ public QueryDslKeyValueRepository(EntityInformation entityInformation, KeyValueOperations operations, EntityPathResolver resolver) { super(entityInformation, operations); + + Assert.notNull(resolver, "EntityPathResolver must not be null!"); + this.path = resolver.createPath(entityInformation.getJavaType()); this.builder = new PathBuilder(path.getType(), path.getMetadata()); } @@ -78,9 +89,7 @@ public class QueryDslKeyValueRepository extends Basi */ @Override public T findOne(Predicate predicate) { - - ProjectableQuery query = prepareQuery(predicate); - return query.uniqueResult(builder); + return prepareQuery(predicate).uniqueResult(builder); } /* @@ -89,9 +98,7 @@ public class QueryDslKeyValueRepository extends Basi */ @Override public Iterable findAll(Predicate predicate) { - - ProjectableQuery query = prepareQuery(predicate); - return query.list(builder); + return prepareQuery(predicate).list(builder); } /* @@ -103,6 +110,7 @@ public class QueryDslKeyValueRepository extends Basi ProjectableQuery query = prepareQuery(predicate); query.orderBy(orders); + return query.list(builder); } @@ -116,6 +124,7 @@ public class QueryDslKeyValueRepository extends Basi ProjectableQuery query = prepareQuery(predicate); if (pageable != null) { + query.offset(pageable.getOffset()); query.limit(pageable.getPageSize()); @@ -133,9 +142,7 @@ public class QueryDslKeyValueRepository extends Basi */ @Override public long count(Predicate predicate) { - - ProjectableQuery query = prepareQuery(predicate); - return query.count(); + return prepareQuery(predicate).count(); } /** @@ -147,10 +154,10 @@ public class QueryDslKeyValueRepository extends Basi protected ProjectableQuery prepareQuery(Predicate predicate) { CollQuery query = new CollQuery(); + query.from(builder, findAll()); query.where(predicate); return query; } - } diff --git a/src/main/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepository.java b/src/main/java/org/springframework/data/keyvalue/repository/support/SimpleKeyValueRepository.java similarity index 84% rename from src/main/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepository.java rename to src/main/java/org/springframework/data/keyvalue/repository/support/SimpleKeyValueRepository.java index f6d88a9c0..cf609e2d5 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepository.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/SimpleKeyValueRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.keyvalue.repository; +package org.springframework.data.keyvalue.repository.support; import java.io.Serializable; import java.util.ArrayList; @@ -24,24 +24,33 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.keyvalue.core.KeyValueOperations; +import org.springframework.data.keyvalue.repository.KeyValueRepository; import org.springframework.data.repository.core.EntityInformation; import org.springframework.util.Assert; /** * @author Christoph Strobl + * @author Oliver Gierke * @since 1.10 * @param * @param */ -public class BasicKeyValueRepository implements KeyValueRepository { +public class SimpleKeyValueRepository implements KeyValueRepository { private final KeyValueOperations operations; private final EntityInformation entityInformation; - public BasicKeyValueRepository(EntityInformation metadata, KeyValueOperations operations) { + /** + * Creates a new {@link SimpleKeyValueRepository} for the given {@link EntityInformation} and + * {@link KeyValueOperations}. + * + * @param metadata must not be {@literal null}. + * @param operations must not be {@literal null}. + */ + public SimpleKeyValueRepository(EntityInformation metadata, KeyValueOperations operations) { - Assert.notNull(metadata, "Cannot initialize repository for 'null' metadata"); - Assert.notNull(operations, "Cannot initialize repository for 'null' operations"); + Assert.notNull(metadata, "EntityInformation must not be null!"); + Assert.notNull(operations, "KeyValueOperations must not be null!"); this.entityInformation = metadata; this.operations = operations; @@ -64,6 +73,8 @@ public class BasicKeyValueRepository implements KeyV public Page findAll(Pageable pageable) { List content = null; + + // TODO: do we need that guard? Can't findInRange(…) be able to deal with null sorts? if (pageable.getSort() != null) { content = operations.findInRange(pageable.getOffset(), pageable.getPageSize(), pageable.getSort(), entityInformation.getJavaType()); @@ -81,7 +92,7 @@ public class BasicKeyValueRepository implements KeyV @Override public S save(S entity) { - Assert.notNull(entity, "Entity must not be 'null' for save."); + Assert.notNull(entity, "Entity must not be null!"); if (entityInformation.isNew(entity)) { operations.insert(entity); @@ -101,6 +112,7 @@ public class BasicKeyValueRepository implements KeyV for (S entity : entities) { save(entity); } + return entities; } @@ -141,7 +153,9 @@ public class BasicKeyValueRepository implements KeyV List result = new ArrayList(); for (ID id : ids) { + T candidate = findOne(id); + if (candidate != null) { result.add(candidate); } diff --git a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java index df5f704a5..1c96d6beb 100644 --- a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java @@ -30,6 +30,7 @@ import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AspectJTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; @@ -55,7 +56,8 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura private static final String REPOSITORY_FACTORY_BEAN_CLASS = "repositoryFactoryBeanClass"; private static final String CONSIDER_NESTED_REPOSITORIES = "considerNestedRepositories"; - private final AnnotationMetadata metadata; + private final AnnotationMetadata configMetadata; + private final AnnotationMetadata enableAnnotationMetadata; private final AnnotationAttributes attributes; private final ResourceLoader resourceLoader; private final boolean hasExplicitFilters; @@ -64,7 +66,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura * Creates a new {@link AnnotationRepositoryConfigurationSource} from the given {@link AnnotationMetadata} and * annotation. * - * @param metadata must not be {@literal null}. + * @param configMetadata must not be {@literal null}. * @param annotation must not be {@literal null}. * @param resourceLoader must not be {@literal null}. * @param environment @@ -79,7 +81,8 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura Assert.notNull(resourceLoader); this.attributes = new AnnotationAttributes(metadata.getAnnotationAttributes(annotation.getName())); - this.metadata = metadata; + this.enableAnnotationMetadata = new StandardAnnotationMetadata(annotation); + this.configMetadata = metadata; this.resourceLoader = resourceLoader; this.hasExplicitFilters = hasExplicitFilters(attributes); } @@ -114,7 +117,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura // Default configuration - return package of annotated class if (value.length == 0 && basePackages.length == 0 && basePackageClasses.length == 0) { - String className = metadata.getClassName(); + String className = configMetadata.getClassName(); return Collections.singleton(ClassUtils.getPackageName(className)); } @@ -158,7 +161,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getSource() */ public Object getSource() { - return metadata; + return configMetadata; } /* @@ -219,6 +222,15 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura return attributes; } + /** + * Returns the {@link AnnotationMetadata} for the {@code @Enable} annotation that triggered the configuration. + * + * @return the enableAnnotationMetadata + */ + public AnnotationMetadata getEnableAnnotationMetadata() { + return enableAnnotationMetadata; + } + /** * Copy of {@code ComponentScanAnnotationParser#typeFiltersFor}. * diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryNameSpaceHandler.java b/src/main/java/org/springframework/data/repository/config/RepositoryNameSpaceHandler.java index 998e48085..31dabb6e7 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryNameSpaceHandler.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryNameSpaceHandler.java @@ -18,7 +18,6 @@ package org.springframework.data.repository.config; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.NamespaceHandler; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; -import org.springframework.data.keyvalue.repository.config.KeyValueRepositoryConfigurationExtension; /** * {@link NamespaceHandler} to register {@link BeanDefinitionParser}s for repository initializers. @@ -40,9 +39,5 @@ public class RepositoryNameSpaceHandler extends NamespaceHandlerSupport { registerBeanDefinitionParser("unmarshaller-populator", PARSER); registerBeanDefinitionParser("jackson-populator", PARSER); registerBeanDefinitionParser("jackson2-populator", PARSER); - - KeyValueRepositoryConfigurationExtension extension = new KeyValueRepositoryConfigurationExtension(); - RepositoryBeanDefinitionParser repositoryBeanDefinitionParser = new RepositoryBeanDefinitionParser(extension); - registerBeanDefinitionParser("repositories", repositoryBeanDefinitionParser); } } diff --git a/src/main/resources/META-INF/spring.handlers b/src/main/resources/META-INF/spring.handlers index 685424493..acaf9d128 100644 --- a/src/main/resources/META-INF/spring.handlers +++ b/src/main/resources/META-INF/spring.handlers @@ -1 +1,2 @@ http\://www.springframework.org/schema/data/repository=org.springframework.data.repository.config.RepositoryNameSpaceHandler +http\://www.springframework.org/schema/data/keyvalue=org.springframework.data.keyvalue.repository.config.KeyValueRepositoryNameSpaceHandler diff --git a/src/test/java/org/springframework/data/keyvalue/core/KeySpaceUtilsUnitTests.java b/src/test/java/org/springframework/data/keyvalue/core/KeySpaceUtilsUnitTests.java new file mode 100644 index 000000000..ccbe4eac1 --- /dev/null +++ b/src/test/java/org/springframework/data/keyvalue/core/KeySpaceUtilsUnitTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2014 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.keyvalue.core; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.data.keyvalue.core.KeySpaceUtils.*; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.Test; +import org.springframework.data.annotation.Persistent; +import org.springframework.data.keyvalue.annotation.KeySpace; +import org.springframework.data.keyvalue.core.KeyValueTemplateUnitTests.AliasedEntity; +import org.springframework.data.keyvalue.core.KeyValueTemplateUnitTests.ClassWithDirectKeySpaceAnnotation; +import org.springframework.data.keyvalue.core.KeyValueTemplateUnitTests.EntityWithPersistentAnnotation; +import org.springframework.data.keyvalue.core.KeyValueTemplateUnitTests.Foo; + +/** + * Unit tests for {@link KeySpaceUtils}. + * + * @author Christoph Strobl + * @author Oliver Gierke + */ +public class KeySpaceUtilsUnitTests { + + /** + * @see DATACMNS-525 + */ + @Test + public void shouldResolveKeySpaceDefaultValueCorrectly() { + assertThat(getKeySpace(EntityWithDefaultKeySpace.class), is((Object) "daenerys")); + } + + /** + * @see DATACMNS-525 + */ + @Test + public void shouldResolveKeySpaceCorrectly() { + assertThat(getKeySpace(EntityWithSetKeySpace.class), is((Object) "viserys")); + } + + /** + * @see DATACMNS-525 + */ + @Test + public void shouldReturnNullWhenNoKeySpaceFoundOnComposedPersistentAnnotation() { + assertThat(getKeySpace(AliasedEntity.class), nullValue()); + } + + /** + * @see DATACMNS-525 + */ + @Test + public void shouldReturnNullWhenPersistentIsFoundOnNonComposedAnnotation() { + assertThat(getKeySpace(EntityWithPersistentAnnotation.class), nullValue()); + } + + /** + * @see DATACMNS-525 + */ + @Test + public void shouldReturnNullWhenPersistentIsNotFound() { + assertThat(getKeySpace(Foo.class), nullValue()); + } + + /** + * @see DATACMNS-525 + */ + @Test + public void shouldResolveInheritedKeySpaceCorrectly() { + assertThat(getKeySpace(EntityWithInheritedKeySpace.class), is((Object) "viserys")); + } + + /** + * @see DATACMNS-525 + */ + @Test + public void shouldResolveDirectKeySpaceAnnotationCorrectly() { + assertThat(getKeySpace(ClassWithDirectKeySpaceAnnotation.class), is((Object) "rhaegar")); + } + + @PersistentAnnotationWithExplicitKeySpace + static class EntityWithDefaultKeySpace { + + } + + @PersistentAnnotationWithExplicitKeySpace(firstname = "viserys") + static class EntityWithSetKeySpace { + + } + + static class EntityWithInheritedKeySpace extends EntityWithSetKeySpace { + + } + + @Persistent + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE }) + static @interface PersistentAnnotationWithExplicitKeySpace { + + @KeySpace + String firstname() default "daenerys"; + + String lastnamne() default "targaryen"; + } + + @Persistent + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE }) + static @interface ExplicitKeySpace { + + @KeySpace + String name() default ""; + } +} diff --git a/src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateTests.java b/src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateTests.java index 16713341c..9428d0539 100644 --- a/src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateTests.java +++ b/src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateTests.java @@ -24,7 +24,6 @@ import static org.hamcrest.core.IsSame.*; import static org.junit.Assert.*; import java.io.Serializable; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -44,19 +43,19 @@ import org.springframework.util.ObjectUtils; /** * @author Christoph Strobl + * @author Oliver Gierke */ public class KeyValueTemplateTests { - private static final Foo FOO_ONE = new Foo("one"); - private static final Foo FOO_TWO = new Foo("two"); - private static final Foo FOO_THREE = new Foo("three"); - private static final Bar BAR_ONE = new Bar("one"); - private static final ClassWithTypeAlias ALIASED = new ClassWithTypeAlias("super"); - private static final SubclassOfAliasedType SUBCLASS_OF_ALIASED = new SubclassOfAliasedType("sub"); + static final Foo FOO_ONE = new Foo("one"); + static final Foo FOO_TWO = new Foo("two"); + static final Foo FOO_THREE = new Foo("three"); + static final Bar BAR_ONE = new Bar("one"); + static final ClassWithTypeAlias ALIASED = new ClassWithTypeAlias("super"); + static final SubclassOfAliasedType SUBCLASS_OF_ALIASED = new SubclassOfAliasedType("sub"); + static final KeyValueQuery STRING_QUERY = new KeyValueQuery("foo == 'two'"); - private static final KeyValueQuery STRING_QUERY = new KeyValueQuery("foo == 'two'"); - - private KeyValueTemplate operations; + KeyValueTemplate operations; @Before public void setUp() throws InstantiationException, IllegalAccessException { @@ -252,7 +251,7 @@ public class KeyValueTemplateTests { /** * @see DATACMNS-525 */ - @Test(expected = InvalidDataAccessApiUsageException.class) + @Test(expected = IllegalArgumentException.class) public void deleteThrowsExceptionWhenIdCannotBeExctracted() { operations.delete(FOO_ONE); } @@ -455,11 +454,10 @@ public class KeyValueTemplateTests { } - @Documented @Persistent @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) - private static @interface ExplicitKeySpace { + static @interface ExplicitKeySpace { @KeySpace String name() default ""; diff --git a/src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateUnitTests.java b/src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateUnitTests.java index 7c8890079..8ba65805f 100644 --- a/src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateUnitTests.java +++ b/src/test/java/org/springframework/data/keyvalue/core/KeyValueTemplateUnitTests.java @@ -22,7 +22,6 @@ import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import java.io.Serializable; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -30,14 +29,12 @@ import java.lang.annotation.Target; import java.util.Arrays; import java.util.Collection; -import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Persistent; @@ -146,7 +143,7 @@ public class KeyValueTemplateUnitTests { /** * @see DATACMNS-525 */ - @Test(expected = DataAccessException.class) + @Test(expected = IllegalArgumentException.class) public void insertShouldThrowErrorWhenIdCannotBeResolved() { template.insert(FOO_ONE); } @@ -328,7 +325,7 @@ public class KeyValueTemplateUnitTests { /** * @see DATACMNS-525 */ - @Test(expected = DataAccessException.class) + @Test(expected = IllegalArgumentException.class) public void deleteThrowsExceptionWhenIdCannotBeExctracted() { template.delete(FOO_ONE); } @@ -407,62 +404,6 @@ public class KeyValueTemplateUnitTests { assertThat(template.findById("1", SUBCLASS_OF_ALIASED.getClass()), nullValue()); } - /** - * @see DATACMNS-525 - */ - @Test - public void shouldResolveKeySpaceDefaultValueCorrectly() { - assertThat(template.getKeySpace(EntityWithDefaultKeySpace.class), Is. is("daenerys")); - } - - /** - * @see DATACMNS-525 - */ - @Test - public void shouldResolveKeySpaceCorrectly() { - assertThat(template.getKeySpace(EntityWithSetKeySpace.class), Is. is("viserys")); - } - - /** - * @see DATACMNS-525 - */ - @Test - public void shouldReturnNullWhenNoKeySpaceFoundOnComposedPersistentAnnotation() { - assertThat(template.getKeySpace(AliasedEntity.class), nullValue()); - } - - /** - * @see DATACMNS-525 - */ - @Test - public void shouldReturnNullWhenPersistentIsFoundOnNonComposedAnnotation() { - assertThat(template.getKeySpace(EntityWithPersistentAnnotation.class), nullValue()); - } - - /** - * @see DATACMNS-525 - */ - @Test - public void shouldReturnNullWhenPersistentIsNotFound() { - assertThat(template.getKeySpace(Foo.class), nullValue()); - } - - /** - * @see DATACMNS-525 - */ - @Test - public void shouldResolveInheritedKeySpaceCorrectly() { - assertThat(template.getKeySpace(EntityWithInheritedKeySpace.class), Is. is("viserys")); - } - - /** - * @see DATACMNS-525 - */ - @Test - public void shouldResolveDirectKeySpaceAnnotationCorrectly() { - assertThat(template.getKeySpace(ClassWithDirectKeySpaceAnnotation.class), Is. is("rhaegar")); - } - static class Foo { String foo; @@ -639,20 +580,6 @@ public class KeyValueTemplateUnitTests { } - @PersistentAnnotationWithExplicitKeySpace - private static class EntityWithDefaultKeySpace { - - } - - @PersistentAnnotationWithExplicitKeySpace(firstname = "viserys") - private static class EntityWithSetKeySpace { - - } - - private static class EntityWithInheritedKeySpace extends EntityWithSetKeySpace { - - } - @TypeAlias("foo") static class AliasedEntity { @@ -663,19 +590,6 @@ public class KeyValueTemplateUnitTests { } - @Documented - @Persistent - @Retention(RetentionPolicy.RUNTIME) - @Target({ ElementType.TYPE }) - private static @interface PersistentAnnotationWithExplicitKeySpace { - - @KeySpace - String firstname() default "daenerys"; - - String lastnamne() default "targaryen"; - } - - @Documented @Persistent @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) @@ -683,6 +597,5 @@ public class KeyValueTemplateUnitTests { @KeySpace String name() default ""; - } } diff --git a/src/test/java/org/springframework/data/keyvalue/core/SpelPropertyComperatorUnitTests.java b/src/test/java/org/springframework/data/keyvalue/core/SpelPropertyComperatorUnitTests.java index dd17433e7..15d12e7ae 100644 --- a/src/test/java/org/springframework/data/keyvalue/core/SpelPropertyComperatorUnitTests.java +++ b/src/test/java/org/springframework/data/keyvalue/core/SpelPropertyComperatorUnitTests.java @@ -15,18 +15,26 @@ */ package org.springframework.data.keyvalue.core; -import static org.hamcrest.core.Is.*; -import static org.hamcrest.core.IsEqual.*; +import static java.lang.Integer.*; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.number.OrderingComparison.*; import static org.junit.Assert.*; +import java.util.Comparator; + import org.junit.Test; +import org.springframework.expression.spel.standard.SpelExpressionParser; /** + * Unit tests for {@link SpelPropertyComparator}. + * * @author Christoph Strobl + * @author Oliver Gierke */ public class SpelPropertyComperatorUnitTests { + private static final SpelExpressionParser PARSER = new SpelExpressionParser(); + private static final SomeType ONE = new SomeType("one", Integer.valueOf(1), 1); private static final SomeType TWO = new SomeType("two", Integer.valueOf(2), 2); private static final WrapperType WRAPPER_ONE = new WrapperType("w-one", ONE); @@ -38,8 +46,8 @@ public class SpelPropertyComperatorUnitTests { @Test public void shouldCompareStringAscCorrectly() { - assertThat(new SpelPropertyComperator("stringProperty").compare(ONE, TWO), is(ONE.getStringProperty() - .compareTo(TWO.getStringProperty()))); + Comparator comparator = new SpelPropertyComparator("stringProperty", PARSER); + assertThat(comparator.compare(ONE, TWO), is(ONE.getStringProperty().compareTo(TWO.getStringProperty()))); } /** @@ -48,8 +56,8 @@ public class SpelPropertyComperatorUnitTests { @Test public void shouldCompareStringDescCorrectly() { - assertThat(new SpelPropertyComperator("stringProperty").desc().compare(ONE, TWO), is(TWO - .getStringProperty().compareTo(ONE.getStringProperty()))); + Comparator comparator = new SpelPropertyComparator("stringProperty", PARSER).desc(); + assertThat(comparator.compare(ONE, TWO), is(TWO.getStringProperty().compareTo(ONE.getStringProperty()))); } /** @@ -58,8 +66,8 @@ public class SpelPropertyComperatorUnitTests { @Test public void shouldCompareIntegerAscCorrectly() { - assertThat(new SpelPropertyComperator("integerProperty").compare(ONE, TWO), is(ONE.getIntegerProperty() - .compareTo(TWO.getIntegerProperty()))); + Comparator comparator = new SpelPropertyComparator("integerProperty", PARSER); + assertThat(comparator.compare(ONE, TWO), is(ONE.getIntegerProperty().compareTo(TWO.getIntegerProperty()))); } /** @@ -68,8 +76,8 @@ public class SpelPropertyComperatorUnitTests { @Test public void shouldCompareIntegerDescCorrectly() { - assertThat(new SpelPropertyComperator("integerProperty").desc().compare(ONE, TWO), is(TWO - .getIntegerProperty().compareTo(ONE.getIntegerProperty()))); + Comparator comparator = new SpelPropertyComparator("integerProperty", PARSER).desc(); + assertThat(comparator.compare(ONE, TWO), is(TWO.getIntegerProperty().compareTo(ONE.getIntegerProperty()))); } /** @@ -78,8 +86,9 @@ public class SpelPropertyComperatorUnitTests { @Test public void shouldComparePrimitiveIntegerAscCorrectly() { - assertThat(new SpelPropertyComperator("primitiveProperty").compare(ONE, TWO), - is(Integer.valueOf(ONE.getPrimitiveProperty()).compareTo(Integer.valueOf(TWO.getPrimitiveProperty())))); + Comparator comparator = new SpelPropertyComparator("primitiveProperty", PARSER); + assertThat(comparator.compare(ONE, TWO), + is(valueOf(ONE.getPrimitiveProperty()).compareTo(valueOf(TWO.getPrimitiveProperty())))); } /** @@ -87,7 +96,9 @@ public class SpelPropertyComperatorUnitTests { */ @Test public void shouldNotFailOnNullValues() { - new SpelPropertyComperator("stringProperty").compare(ONE, new SomeType(null, null, 2)); + + Comparator comparator = new SpelPropertyComparator("stringProperty", PARSER); + assertThat(comparator.compare(ONE, new SomeType(null, null, 2)), is(1)); } /** @@ -96,8 +107,9 @@ public class SpelPropertyComperatorUnitTests { @Test public void shouldComparePrimitiveIntegerDescCorrectly() { - assertThat(new SpelPropertyComperator("primitiveProperty").desc().compare(ONE, TWO), - is(Integer.valueOf(TWO.getPrimitiveProperty()).compareTo(Integer.valueOf(ONE.getPrimitiveProperty())))); + Comparator comparator = new SpelPropertyComparator("primitiveProperty", PARSER).desc(); + assertThat(comparator.compare(ONE, TWO), + is(valueOf(TWO.getPrimitiveProperty()).compareTo(valueOf(ONE.getPrimitiveProperty())))); } /** @@ -105,9 +117,8 @@ public class SpelPropertyComperatorUnitTests { */ @Test public void shouldSortNullsFirstCorrectly() { - assertThat( - new SpelPropertyComperator("stringProperty").nullsFirst().compare(ONE, new SomeType(null, null, 2)), - equalTo(1)); + Comparator comparator = new SpelPropertyComparator("stringProperty", PARSER).nullsFirst(); + assertThat(comparator.compare(ONE, new SomeType(null, null, 2)), equalTo(1)); } /** @@ -115,9 +126,9 @@ public class SpelPropertyComperatorUnitTests { */ @Test public void shouldSortNullsLastCorrectly() { - assertThat( - new SpelPropertyComperator("stringProperty").nullsLast().compare(ONE, new SomeType(null, null, 2)), - equalTo(-1)); + + Comparator comparator = new SpelPropertyComparator("stringProperty", PARSER).nullsLast(); + assertThat(comparator.compare(ONE, new SomeType(null, null, 2)), equalTo(-1)); } /** @@ -126,8 +137,9 @@ public class SpelPropertyComperatorUnitTests { @Test public void shouldCompareNestedTypesCorrectly() { - assertThat(new SpelPropertyComperator("nestedType.stringProperty").compare(WRAPPER_ONE, WRAPPER_TWO), - is(WRAPPER_ONE.getNestedType().getStringProperty().compareTo(WRAPPER_TWO.getNestedType().getStringProperty()))); + Comparator comparator = new SpelPropertyComparator("nestedType.stringProperty", PARSER); + assertThat(comparator.compare(WRAPPER_ONE, WRAPPER_TWO), is(WRAPPER_ONE.getNestedType().getStringProperty() + .compareTo(WRAPPER_TWO.getNestedType().getStringProperty()))); } /** @@ -136,8 +148,9 @@ public class SpelPropertyComperatorUnitTests { @Test public void shouldCompareNestedTypesCorrectlyWhenOneOfThemHasNullValue() { - assertThat(new SpelPropertyComperator("nestedType.stringProperty").compare(WRAPPER_ONE, - new WrapperType("two", null)), is(greaterThanOrEqualTo(1))); + SpelPropertyComparator comparator = new SpelPropertyComparator( + "nestedType.stringProperty", PARSER); + assertThat(comparator.compare(WRAPPER_ONE, new WrapperType("two", null)), is(greaterThanOrEqualTo(1))); } static class WrapperType { diff --git a/src/test/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositoriesUnitTests.java b/src/test/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositoriesIntegrationTests.java similarity index 98% rename from src/test/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositoriesUnitTests.java rename to src/test/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositoriesIntegrationTests.java index 3837bd8b8..2a9eae8be 100644 --- a/src/test/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositoriesUnitTests.java +++ b/src/test/java/org/springframework/data/keyvalue/ehcache/repository/config/EnableEhCacheRepositoriesIntegrationTests.java @@ -44,7 +44,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -public class EnableEhCacheRepositoriesUnitTests { +public class EnableEhCacheRepositoriesIntegrationTests { @Configuration @EnableEhCacheRepositories(considerNestedRepositories = true) diff --git a/src/test/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositoriesUnitTests.java b/src/test/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositoriesIntegrationTests.java similarity index 97% rename from src/test/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositoriesUnitTests.java rename to src/test/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositoriesIntegrationTests.java index a11e9ecfc..25bfed5a1 100644 --- a/src/test/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositoriesUnitTests.java +++ b/src/test/java/org/springframework/data/keyvalue/hazelcast/repository/config/EnableHazelcastRepositoriesIntegrationTests.java @@ -41,7 +41,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -public class EnableHazelcastRepositoriesUnitTests { +public class EnableHazelcastRepositoriesIntegrationTests { @Configuration @EnableHazelcastRepositories(considerNestedRepositories = true) diff --git a/src/test/java/org/springframework/data/keyvalue/map/QueryDslMapRepositoryUnitTests.java b/src/test/java/org/springframework/data/keyvalue/map/QueryDslMapRepositoryUnitTests.java index 876e0f151..4927f33a7 100644 --- a/src/test/java/org/springframework/data/keyvalue/map/QueryDslMapRepositoryUnitTests.java +++ b/src/test/java/org/springframework/data/keyvalue/map/QueryDslMapRepositoryUnitTests.java @@ -33,7 +33,7 @@ import org.springframework.data.querydsl.QueryDslPredicateExecutor; /** * @author Christoph Strobl */ -public class QueryDslMapRepositoryUnitTests extends MapBackedKeyValueRepositoryUnitTests { +public class QueryDslMapRepositoryUnitTests extends SimpleKeyValueRepositoryUnitTests { /** * @see DATACMNS-525 diff --git a/src/test/java/org/springframework/data/keyvalue/map/MapBackedKeyValueRepositoryUnitTests.java b/src/test/java/org/springframework/data/keyvalue/map/SimpleKeyValueRepositoryUnitTests.java similarity index 98% rename from src/test/java/org/springframework/data/keyvalue/map/MapBackedKeyValueRepositoryUnitTests.java rename to src/test/java/org/springframework/data/keyvalue/map/SimpleKeyValueRepositoryUnitTests.java index 6b89148d1..d5575727f 100644 --- a/src/test/java/org/springframework/data/keyvalue/map/MapBackedKeyValueRepositoryUnitTests.java +++ b/src/test/java/org/springframework/data/keyvalue/map/SimpleKeyValueRepositoryUnitTests.java @@ -41,7 +41,7 @@ import org.springframework.data.repository.CrudRepository; /** * @author Christoph Strobl */ -public class MapBackedKeyValueRepositoryUnitTests { +public class SimpleKeyValueRepositoryUnitTests { protected static final Person CERSEI = new Person("cersei", 19); protected static final Person JAIME = new Person("jaime", 19); diff --git a/src/test/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepositoryUnitTests.java b/src/test/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepositoryUnitTests.java index 65b0b7f3a..bec01472c 100644 --- a/src/test/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepositoryUnitTests.java +++ b/src/test/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepositoryUnitTests.java @@ -28,6 +28,7 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Persistent; import org.springframework.data.keyvalue.core.KeyValueOperations; +import org.springframework.data.keyvalue.repository.support.SimpleKeyValueRepository; import org.springframework.data.repository.core.support.ReflectionEntityInformation; /** @@ -36,14 +37,14 @@ import org.springframework.data.repository.core.support.ReflectionEntityInformat @RunWith(MockitoJUnitRunner.class) public class BasicKeyValueRepositoryUnitTests { - private BasicKeyValueRepository repo; + private SimpleKeyValueRepository repo; private @Mock KeyValueOperations opsMock; @Before public void setUp() { ReflectionEntityInformation ei = new ReflectionEntityInformation(Foo.class); - repo = new BasicKeyValueRepository(ei, opsMock); + repo = new SimpleKeyValueRepository(ei, opsMock); } /** @@ -54,7 +55,7 @@ public class BasicKeyValueRepositoryUnitTests { ReflectionEntityInformation ei = new ReflectionEntityInformation( WithNumericId.class); - BasicKeyValueRepository temp = new BasicKeyValueRepository(ei, + SimpleKeyValueRepository temp = new SimpleKeyValueRepository(ei, opsMock); WithNumericId foo = temp.save(new WithNumericId()); diff --git a/src/test/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryRegistrarUnitTests.java b/src/test/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryRegistrarIntegrationTests.java similarity index 97% rename from src/test/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryRegistrarUnitTests.java rename to src/test/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryRegistrarIntegrationTests.java index 1e4ae87b5..84ead9488 100644 --- a/src/test/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryRegistrarUnitTests.java +++ b/src/test/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryRegistrarIntegrationTests.java @@ -38,7 +38,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration -public class KeyValueRepositoryRegistrarUnitTests { +public class KeyValueRepositoryRegistrarIntegrationTests { @Configuration @EnableKeyValueRepositories(considerNestedRepositories = true) diff --git a/src/test/java/org/springframework/data/keyvalue/repository/query/SpELQueryCreatorUnitTests.java b/src/test/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreatorUnitTests.java similarity index 95% rename from src/test/java/org/springframework/data/keyvalue/repository/query/SpELQueryCreatorUnitTests.java rename to src/test/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreatorUnitTests.java index dcc13e899..20c0a6171 100644 --- a/src/test/java/org/springframework/data/keyvalue/repository/query/SpELQueryCreatorUnitTests.java +++ b/src/test/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreatorUnitTests.java @@ -42,21 +42,21 @@ import org.springframework.util.ObjectUtils; * @author Christoph Strobl */ @RunWith(MockitoJUnitRunner.class) -public class SpELQueryCreatorUnitTests { +public class SpelQueryCreatorUnitTests { - private static final DateTimeFormatter FORMATTER = ISODateTimeFormat.dateTimeNoMillis().withZoneUTC(); + static final DateTimeFormatter FORMATTER = ISODateTimeFormat.dateTimeNoMillis().withZoneUTC(); - private static final Person RICKON = new Person("rickon", 4); - private static final Person BRAN = new Person("bran", 9)// + static final Person RICKON = new Person("rickon", 4); + static final Person BRAN = new Person("bran", 9)// .skinChanger(true)// .bornAt(FORMATTER.parseDateTime("2013-01-31T06:00:00Z").toDate()); - private static final Person ARYA = new Person("arya", 13); - private static final Person ROBB = new Person("robb", 16)// + static final Person ARYA = new Person("arya", 13); + static final Person ROBB = new Person("robb", 16)// .named("stark")// .bornAt(FORMATTER.parseDateTime("2010-09-20T06:00:00Z").toDate()); - private static final Person JON = new Person("jon", 17).named("snow"); + static final Person JON = new Person("jon", 17).named("snow"); - private @Mock RepositoryMetadata metadataMock; + @Mock RepositoryMetadata metadataMock; /** * @see DATACMNS-525 @@ -378,7 +378,7 @@ public class SpELQueryCreatorUnitTests { assertThat(evaluate("findByLastnameMatches", "^s.*w$").against(ROBB), is(false)); } - Evaluation evaluate(String methodName, Object... args) throws Exception { + private Evaluation evaluate(String methodName, Object... args) throws Exception { return new Evaluation((SpelExpression) createQueryForMethodWithArgs(methodName, args).getCritieria()); } @@ -391,8 +391,8 @@ public class SpELQueryCreatorUnitTests { for (int i = 0; i < args.length; i++) { argTypes[i] = args[i].getClass(); } - } + Method method = PersonRepository.class.getMethod(methodName, argTypes); PartTree partTree = new PartTree(method.getName(), method.getReturnType()); @@ -401,6 +401,7 @@ public class SpELQueryCreatorUnitTests { KeyValueQuery q = creator.createQuery(); q.getCritieria().setEvaluationContext(new StandardEvaluationContext(args)); + return q; } @@ -561,7 +562,5 @@ public class SpELQueryCreatorUnitTests { this.birthday = date; return this; } - } - }