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; } - } - }