DATACMNS-525 - Polishing.

Extracted KeySpaceUtils from KeyValueTemplate and pulled in the copy of MetaAnnotationUtils to hide it from the outside. Separated related test cases into dedicated test class.

Renamed BasicMappingContext to KeyValueMappingContext and BasicPersistentProperty to KeyValuePersistentProperty. Fixed spelling in SpelQueryCreatorUnitTests.

Moved MappingContext into separate package to satisfy architectural guidelines. Created dedicated KeyValueRepositoryNamespaceHandler to avoid a circular reference between commons and the key-value subsystem.

Made the query creator type an annotation on @EnableKeyValueRepositories so that it's not exposed to the user to be configured. Added the necessary ImportBeanDefinitionRegistrars to the Hazelcast and EhCache implementation. Removed @since tags from those modules at they're not going to make it into Spring Data Commons. Renamed EnableEhCacheRepsotoriesUnitTests to EnableEhCacheRepositoriesIntegrationTests as well as the same for the Hazelcast specific test.

The KeyValueRepositoryConfigurationExtension now explicitly registers a KeyValueMappingContext unless one is already registered. AnnotationRepositoryConfigurationSource now explicitly exposes an AnnotationMetadata instance for the annotation that triggered the configuration.

Turned SpelCriteriaAccessor and SpelPropertyComparator into classes to be able to pass them a SpelExpressionParser to be able to get rid of SpelExpressionFactory eventually. Made both classes as well as SpelQueryEngine package protected for now.

Moved repository implementations into support package. Updated architcture description JavaDoc polish, spacing, blank lines. Added TODOs.

Original pull request: #95.
This commit is contained in:
Oliver Gierke
2014-11-26 16:45:04 +01:00
parent e2358f3bf8
commit 1f71a28408
54 changed files with 1777 additions and 1390 deletions

View File

@@ -44,6 +44,7 @@ public abstract class AbstractKeyValueAdapter implements KeyValueAdapter {
* @param engine will be defaulted to {@link SpelQueryEngine} if {@literal null}.
*/
protected AbstractKeyValueAdapter(QueryEngine<? extends KeyValueAdapter, ?, ?> engine) {
this.engine = engine != null ? engine : new SpelQueryEngine<KeyValueAdapter>();
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);
}
}

View File

@@ -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> T generateIdentifierOfType(TypeInformation<T> 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....");
}
}

View File

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

View File

@@ -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<?, ? extends PersistentProperty> entity, BeanWrapper<?> wrapper) {
this(entity, wrapper, DefaultIdGenerator.INSTANCE);
}
@SuppressWarnings("rawtypes")
public IdAccessor(PersistentEntity<?, ? extends PersistentProperty> 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> 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....");
}
}
}

View File

@@ -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> T generateIdentifierOfType(TypeInformation<T> type);
}

View File

@@ -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<Persistent> 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}.
* <p>
* Whereas {@code AnnotationUtils} provides utilities for <em>getting</em> or <em>finding</em> an annotation,
* {@code MetaAnnotationUtils} goes a step further by providing support for determining the <em>root class</em> on
* which an annotation is declared, either directly or indirectly via a <em>composed
* annotation</em>. This additional information is encapsulated in an {@link AnnotationDescriptor}.
* <p>
* The additional information provided by an {@code AnnotationDescriptor} is required by the
* <em>Spring TestContext Framework</em> 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 <em>inherited</em> 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.
* <p>
* This method explicitly handles class-level annotations which are not declared as
* {@linkplain java.lang.annotation.Inherited inherited} <em>as
* well as meta-annotations</em>.
* <p>
* The algorithm operates as follows:
* <ol>
* <li>Search for the annotation on the given class and return a corresponding {@code AnnotationDescriptor} if
* found.
* <li>Recursively search through all annotations that the given class declares.
* <li>Recursively search through the superclass hierarchy of the given class.
* </ol>
* <p>
* In this context, the term <em>recursively</em> 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.
* <p>
* 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 <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
Class<T> annotationType) {
return findAnnotationDescriptor(clazz, new HashSet<Annotation>(), annotationType);
}
/**
* Perform the search algorithm for {@link #findAnnotationDescriptor(Class, Class)}, avoiding endless recursion by
* tracking which annotations have already been <em>visited</em>.
*
* @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 <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
Set<Annotation> visited, Class<T> 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<T>(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<T> descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(), visited,
annotationType);
if (descriptor != null) {
return new AnnotationDescriptor<T>(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}.
* <p>
* This method traverses the annotations and superclasses of the specified {@code clazz} if no annotation can be
* found on the given class itself.
* <p>
* This method explicitly handles class-level annotations which are not declared as
* {@linkplain java.lang.annotation.Inherited inherited} <em>as
* well as meta-annotations</em>.
* <p>
* The algorithm operates as follows:
* <ol>
* <li>Search for a local declaration of one of the annotation types on the given class and return a corresponding
* {@code UntypedAnnotationDescriptor} if found.
* <li>Recursively search through all annotations that the given class declares.
* <li>Recursively search through the superclass hierarchy of the given class.
* </ol>
* <p>
* In this context, the term <em>recursively</em> 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.
* <p>
* 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<? extends Annotation>... annotationTypes) {
return findAnnotationDescriptorForTypes(clazz, new HashSet<Annotation>(), annotationTypes);
}
/**
* Perform the search algorithm for {@link #findAnnotationDescriptorForTypes(Class, Class...)}, avoiding endless
* recursion by tracking which annotations have already been <em>visited</em>.
*
* @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<Annotation> visited, Class<? extends Annotation>... 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<? extends Annotation> 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 <em>declared</em> as well as the actual {@linkplain #getAnnotation() annotation} instance.
* <p>
* 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
* <em>root declaring class</em> is not directly annotated with the annotation but rather indirectly via the
* composed annotation.
* <p>
* Given the following example, if we are searching for the {@code @Transactional} annotation <em>on</em> the
* {@code TransactionalTests} class, then the properties of the {@code AnnotationDescriptor} would be as follows.
* <ul>
* <li>rootDeclaringClass: {@code TransactionalTests} class object</li>
* <li>declaringClass: {@code TransactionalTests} class object</li>
* <li>composedAnnotation: {@code null}</li>
* <li>annotation: instance of the {@code Transactional} annotation</li>
* </ul>
*
* <pre style="code">
* &#064;Transactional
* &#064;ContextConfiguration({ &quot;/test-datasource.xml&quot;, &quot;/repository-config.xml&quot; })
* public class TransactionalTests {}
* </pre>
* <p>
* Given the following example, if we are searching for the {@code @Transactional} annotation <em>on</em> the
* {@code UserRepositoryTests} class, then the properties of the {@code AnnotationDescriptor} would be as follows.
* <ul>
* <li>rootDeclaringClass: {@code UserRepositoryTests} class object</li>
* <li>declaringClass: {@code RepositoryTests} class object</li>
* <li>composedAnnotation: instance of the {@code RepositoryTests} annotation</li>
* <li>annotation: instance of the {@code Transactional} annotation</li>
* </ul>
*
* <pre style="code">
* &#064;Transactional
* &#064;ContextConfiguration({ &quot;/test-datasource.xml&quot;, &quot;/repository-config.xml&quot; })
* &#064;Retention(RetentionPolicy.RUNTIME)
* public @interface RepositoryTests {
* }
*
* &#064;RepositoryTests
* public class UserRepositoryTests {}
* </pre>
*
* @author Sam Brannen
* @since 4.0
*/
public static class AnnotationDescriptor<T extends Annotation> {
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<? extends Annotation> getAnnotationType() {
return this.annotation.annotationType();
}
public AnnotationAttributes getAnnotationAttributes() {
return this.annotationAttributes;
}
public Annotation getComposedAnnotation() {
return this.composedAnnotation;
}
public Class<? extends Annotation> 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();
}
}
/**
* <em>Untyped</em> 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<Annotation> {
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");
}
}
}
}
}

View File

@@ -102,5 +102,4 @@ public interface KeyValueAdapter extends DisposableBean {
* @return
*/
long count(KeyValueQuery<?> query, Serializable keyspace);
}

View File

@@ -33,8 +33,7 @@ import org.springframework.data.mapping.context.MappingContext;
public interface KeyValueOperations extends DisposableBean {
/**
* Add given object. <br />
* 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. <br />
* 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 {
<T> List<T> findAll(Sort sort, Class<T> type);
/**
* Get element of given type with given id. <br />
* 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 {
<T> List<T> find(KeyValueQuery<?> query, Class<T> type);
/**
* Get all elements in given range. <br />
* 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 {
<T> List<T> findInRange(int offset, int rows, Class<T> type);
/**
* Get all elements in given range ordered by sort. <br />
* 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. <br />
* 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> T delete(Serializable id, Class<T> type);
/**
* Total number of elements with given type available. <br />
* 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.<br />
* 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

View File

@@ -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<Class<?>, String> keySpaceCache = new ConcurrentHashMap<Class<?>, String>();
@SuppressWarnings("rawtypes")//
private MappingContext<? extends PersistentEntity<?, ? extends PersistentProperty>, ? extends PersistentProperty<?>> mappingContext;
private final ConcurrentHashMap<Class<?>, String> keySpaceCache = new ConcurrentHashMap<Class<?>, String>();
private final MappingContext<? extends PersistentEntity<?, ? extends PersistentProperty<?>>, ? 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 PersistentEntity<?, ? extends PersistentProperty>, ? 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> T insert(T objectToInsert) {
PersistentEntity<?, ? extends PersistentProperty> 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<Void>() {
@@ -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<Void>() {
@@ -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 <T> List<T> findAll(final Class<T> 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<List<T>>() {
@@ -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> T findById(final Serializable id, final Class<T> 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<T>() {
@@ -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<T> type = (Class<T>) ClassUtils.getUserClass(objectToDelete);
PersistentEntity<?, ? extends PersistentProperty> 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> T delete(final Serializable id, final Class<T> 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<T>() {
@@ -302,13 +294,13 @@ public class KeyValueTemplate implements KeyValueOperations {
@Override
public <T> T execute(KeyValueCallback<T> 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<T>((Collection<T>) result);
}
ArrayList<T> filtered = new ArrayList<T>();
List<T> filtered = new ArrayList<T>();
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<Persistent> 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());
}
}

View File

@@ -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}.
* <p>
* Whereas {@code AnnotationUtils} provides utilities for <em>getting</em> or <em>finding</em> an annotation,
* {@code MetaAnnotationUtils} goes a step further by providing support for determining the <em>root class</em> on which
* an annotation is declared, either directly or indirectly via a <em>composed
* annotation</em>. This additional information is encapsulated in an {@link AnnotationDescriptor}.
* <p>
* The additional information provided by an {@code AnnotationDescriptor} is required by the
* <em>Spring TestContext Framework</em> 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 <em>inherited</em> 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.
* <p>
* This method explicitly handles class-level annotations which are not declared as
* {@linkplain java.lang.annotation.Inherited inherited} <em>as
* well as meta-annotations</em>.
* <p>
* The algorithm operates as follows:
* <ol>
* <li>Search for the annotation on the given class and return a corresponding {@code AnnotationDescriptor} if found.
* <li>Recursively search through all annotations that the given class declares.
* <li>Recursively search through the superclass hierarchy of the given class.
* </ol>
* <p>
* In this context, the term <em>recursively</em> 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.
* <p>
* 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 <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
Class<T> annotationType) {
return findAnnotationDescriptor(clazz, new HashSet<Annotation>(), annotationType);
}
/**
* Perform the search algorithm for {@link #findAnnotationDescriptor(Class, Class)}, avoiding endless recursion by
* tracking which annotations have already been <em>visited</em>.
*
* @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 <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz,
Set<Annotation> visited, Class<T> 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<T>(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<T> descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(), visited,
annotationType);
if (descriptor != null) {
return new AnnotationDescriptor<T>(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}.
* <p>
* This method traverses the annotations and superclasses of the specified {@code clazz} if no annotation can be found
* on the given class itself.
* <p>
* This method explicitly handles class-level annotations which are not declared as
* {@linkplain java.lang.annotation.Inherited inherited} <em>as
* well as meta-annotations</em>.
* <p>
* The algorithm operates as follows:
* <ol>
* <li>Search for a local declaration of one of the annotation types on the given class and return a corresponding
* {@code UntypedAnnotationDescriptor} if found.
* <li>Recursively search through all annotations that the given class declares.
* <li>Recursively search through the superclass hierarchy of the given class.
* </ol>
* <p>
* In this context, the term <em>recursively</em> 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.
* <p>
* 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<? extends Annotation>... annotationTypes) {
return findAnnotationDescriptorForTypes(clazz, new HashSet<Annotation>(), annotationTypes);
}
/**
* Perform the search algorithm for {@link #findAnnotationDescriptorForTypes(Class, Class...)}, avoiding endless
* recursion by tracking which annotations have already been <em>visited</em>.
*
* @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<Annotation> visited,
Class<? extends Annotation>... 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<? extends Annotation> 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 <em>declared</em> as well as the actual {@linkplain #getAnnotation() annotation} instance.
* <p>
* 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
* <em>root declaring class</em> is not directly annotated with the annotation but rather indirectly via the composed
* annotation.
* <p>
* Given the following example, if we are searching for the {@code @Transactional} annotation <em>on</em> the
* {@code TransactionalTests} class, then the properties of the {@code AnnotationDescriptor} would be as follows.
* <ul>
* <li>rootDeclaringClass: {@code TransactionalTests} class object</li>
* <li>declaringClass: {@code TransactionalTests} class object</li>
* <li>composedAnnotation: {@code null}</li>
* <li>annotation: instance of the {@code Transactional} annotation</li>
* </ul>
*
* <pre style="code">
* &#064;Transactional
* &#064;ContextConfiguration({ &quot;/test-datasource.xml&quot;, &quot;/repository-config.xml&quot; })
* public class TransactionalTests {}
* </pre>
* <p>
* Given the following example, if we are searching for the {@code @Transactional} annotation <em>on</em> the
* {@code UserRepositoryTests} class, then the properties of the {@code AnnotationDescriptor} would be as follows.
* <ul>
* <li>rootDeclaringClass: {@code UserRepositoryTests} class object</li>
* <li>declaringClass: {@code RepositoryTests} class object</li>
* <li>composedAnnotation: instance of the {@code RepositoryTests} annotation</li>
* <li>annotation: instance of the {@code Transactional} annotation</li>
* </ul>
*
* <pre style="code">
* &#064;Transactional
* &#064;ContextConfiguration({ &quot;/test-datasource.xml&quot;, &quot;/repository-config.xml&quot; })
* &#064;Retention(RetentionPolicy.RUNTIME)
* public @interface RepositoryTests {
* }
*
* &#064;RepositoryTests
* public class UserRepositoryTests {}
* </pre>
*
* @author Sam Brannen
* @since 4.0
*/
public static class AnnotationDescriptor<T extends Annotation> {
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<? extends Annotation> getAnnotationType() {
return this.annotation.annotationType();
}
public AnnotationAttributes getAnnotationAttributes() {
return this.annotationAttributes;
}
public Annotation getComposedAnnotation() {
return this.composedAnnotation;
}
public Class<? extends Annotation> 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();
}
}
/**
* <em>Untyped</em> 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<Annotation> {
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");
}
}
}
}

View File

@@ -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<SpelExpression> {
class SpelCriteriaAccessor implements CriteriaAccessor<SpelExpression> {
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<SpelExpression> {
}
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());

View File

@@ -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 <T>
*/
public class SpelPropertyComperator<T> implements Comparator<T> {
public class SpelPropertyComparator<T> implements Comparator<T> {
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<T> implements Comparator<T> {
*
* @return
*/
public SpelPropertyComperator<T> asc() {
public SpelPropertyComparator<T> asc() {
this.asc = true;
return this;
}
@@ -59,7 +64,7 @@ public class SpelPropertyComperator<T> implements Comparator<T> {
*
* @return
*/
public SpelPropertyComperator<T> desc() {
public SpelPropertyComparator<T> desc() {
this.asc = false;
return this;
}
@@ -69,7 +74,7 @@ public class SpelPropertyComperator<T> implements Comparator<T> {
*
* @return
*/
public SpelPropertyComperator<T> nullsFirst() {
public SpelPropertyComparator<T> nullsFirst() {
this.nullsFirst = true;
return this;
}
@@ -79,7 +84,7 @@ public class SpelPropertyComperator<T> implements Comparator<T> {
*
* @return
*/
public SpelPropertyComperator<T> nullsLast() {
public SpelPropertyComparator<T> nullsLast() {
this.nullsFirst = false;
return this;
}
@@ -92,7 +97,7 @@ public class SpelPropertyComperator<T> implements Comparator<T> {
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<T> implements Comparator<T> {
public String getPath() {
return path;
}
}

View File

@@ -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 <T>
*/
public class SpelQueryEngine<T extends KeyValueAdapter> extends
QueryEngine<KeyValueAdapter, SpelExpression, Comparator<?>> {
class SpelQueryEngine<T extends KeyValueAdapter> extends QueryEngine<KeyValueAdapter, SpelExpression, Comparator<?>> {
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<T extends KeyValueAdapter> extends
return filterMatchingRange(tmp, criteria, offset, rows);
}
private <S> List<S> filterMatchingRange(Iterable<S> source, SpelExpression criteria, int offset, int rows) {
private static <S> List<S> filterMatchingRange(Iterable<S> source, SpelExpression criteria, int offset, int rows) {
List<S> result = new ArrayList<S>();
@@ -99,7 +113,7 @@ public class SpelQueryEngine<T extends KeyValueAdapter> extends
}
}
}
return result;
}
}

View File

@@ -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<Comparator<?>> {
class SpelSortAccessor implements SortAccessor<Comparator<?>> {
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<Comparator<?>> {
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())) {

View File

@@ -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<BasicPersistentEntity<?, BasicPersistentProperty>, BasicPersistentProperty> {
@Override
protected <T> BasicPersistentEntity<?, BasicPersistentProperty> createPersistentEntity(
TypeInformation<T> typeInformation) {
return new BasicPersistentEntity<T, BasicPersistentProperty>(typeInformation);
}
@Override
protected BasicPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
BasicPersistentEntity<?, BasicPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
return new BasicPersistentProperty(field, descriptor, owner, simpleTypeHolder);
}
}

View File

@@ -30,16 +30,19 @@ import org.springframework.data.mapping.model.SimpleTypeHolder;
* @author Christoph Strobl
* @since 1.10
*/
public class BasicPersistentProperty extends AnnotationBasedPersistentProperty<BasicPersistentProperty> {
public class KeyValuePersistentProperty extends AnnotationBasedPersistentProperty<KeyValuePersistentProperty> {
public BasicPersistentProperty(Field field, PropertyDescriptor propertyDescriptor,
PersistentEntity<?, BasicPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
public KeyValuePersistentProperty(Field field, PropertyDescriptor propertyDescriptor,
PersistentEntity<?, KeyValuePersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
super(field, propertyDescriptor, owner, simpleTypeHolder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation()
*/
@Override
protected Association<BasicPersistentProperty> createAssociation() {
return new Association<BasicPersistentProperty>(this, null);
protected Association<KeyValuePersistentProperty> createAssociation() {
return new Association<KeyValuePersistentProperty>(this, null);
}
}

View File

@@ -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<BasicPersistentEntity<?, KeyValuePersistentProperty>, KeyValuePersistentProperty> {
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation)
*/
@Override
protected <T> BasicPersistentEntity<?, KeyValuePersistentProperty> createPersistentEntity(
TypeInformation<T> typeInformation) {
return new BasicPersistentEntity<T, KeyValuePersistentProperty>(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<?, KeyValuePersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
return new KeyValuePersistentProperty(field, descriptor, owner, simpleTypeHolder);
}
}

View File

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

View File

@@ -24,7 +24,6 @@ import org.springframework.core.convert.converter.Converter;
* @author Christoph Strobl
* @param <S>
* @param <T>
* @since 1.10
*/
public class ListConverter<S, T> implements Converter<Iterable<S>, List<T>> {

View File

@@ -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<? extends Annotation> getAnnotation() {
return EnableEhCacheRepositories.class;
}
}

View File

@@ -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<? extends AbstractQueryCreator<?, ?>> queryCreator() default EhCacheQueryCreator.class;
}

View File

@@ -32,7 +32,6 @@ import org.springframework.data.repository.query.parser.PartTree;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class EhCacheQueryCreator extends AbstractQueryCreator<KeyValueQuery<Criteria>, Criteria> {

View File

@@ -31,12 +31,11 @@ import com.hazelcast.query.PredicateBuilder;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class HazelcastQueryEngine extends QueryEngine<HazelcastKeyValueAdapter, Predicate<?, ?>, Comparator<Entry>> {
public HazelcastQueryEngine() {
super(HazelcastCriteriaAccessor.INSTANCE, HazelcastSortAccessor.INSTNANCE);
super(HazelcastCriteriaAccessor.INSTANCE, HazelcastSortAccessor.INSTANCE);
}
@Override
@@ -91,7 +90,7 @@ public class HazelcastQueryEngine extends QueryEngine<HazelcastKeyValueAdapter,
static enum HazelcastSortAccessor implements SortAccessor<Comparator<Entry>> {
INSTNANCE;
INSTANCE;
@Override
public Comparator<Entry> resolve(KeyValueQuery<?> query) {

View File

@@ -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<? extends AbstractQueryCreator<?, ?>> queryCreator() default HazelcastQueryCreator.class;
}

View File

@@ -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<? extends Annotation> getAnnotation() {
return EnableHazelcastRepositories.class;
}
}

View File

@@ -32,7 +32,6 @@ import com.hazelcast.query.PredicateBuilder;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class HazelcastQueryCreator extends AbstractQueryCreator<KeyValueQuery<Predicate<?, ?>>, Predicate<?, ?>> {

View File

@@ -151,6 +151,7 @@ public class MapKeyValueAdapter extends AbstractKeyValueAdapter {
Assert.notNull(keyspace, "Collection must not be 'null' for lookup.");
Map<Serializable, Object> 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.<Serializable, Object> createMap(mapType, 1000));
}
}

View File

@@ -38,6 +38,8 @@ public class MapKeyValueAdapterFactory {
private Map<Serializable, Map<? extends Serializable, ?>> 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<? extends Serializable, ?> 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<? extends Map> 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;
}

View File

@@ -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<? extends AbstractQueryCreator<?, ?>> queryCreator() default SpelQueryCreator.class;
}

View File

@@ -45,5 +45,4 @@ public class KeyValueRepositoriesRegistrar extends RepositoryBeanDefinitionRegis
protected RepositoryConfigurationExtension getExtension() {
return new KeyValueRepositoryConfigurationExtension();
}
}

View File

@@ -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<String, Object> 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);
}
}
}

View File

@@ -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<? extends AbstractQueryCreator<?, ?>> value();
}

View File

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

View File

@@ -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<? extends AbstractQueryCreator<?, ?>> queryCreator;
private KeyValueQuery<?> query;
public KeyValuePartTreeQuery(QueryMethod queryMethod, EvaluationContextProvider evalContextProvider,
KeyValueOperations keyValueOperations, Class<? extends AbstractQueryCreator<?, ?>> 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<? extends AbstractQueryCreator<?, ?>> constructor = (Constructor<? extends AbstractQueryCreator<?, ?>>) ClassUtils
.getConstructorIfAvailable(queryCreator, PartTree.class, ParameterAccessor.class);
return (KeyValueQuery<?>) BeanUtils.instantiateClass(constructor, tree, accessor).createQuery();
}
}

View File

@@ -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<KeyValueQuery<SpelExpression>, 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<Object> 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<Object> 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<KeyValueQuery<SpelExp
protected KeyValueQuery<SpelExpression> complete(String criteria, Sort sort) {
KeyValueQuery<SpelExpression> query = new KeyValueQuery<SpelExpression>(this.expression);
if (sort != null) {
query.orderBy(sort);
}
return query;
}
@@ -75,10 +101,10 @@ public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExp
for (Iterator<OrPart> orPartIter = tree.iterator(); orPartIter.hasNext();) {
OrPart orPart = orPartIter.next();
int partCnt = 0;
StringBuilder partBuilder = new StringBuilder();
OrPart orPart = orPartIter.next();
for (Iterator<Part> partIter = orPart.iterator(); partIter.hasNext();) {
Part part = partIter.next();
@@ -87,7 +113,7 @@ public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExp
// TODO: check if we can have caseinsensitive search
if (!part.shouldIgnoreCase().equals(IgnoreCaseType.NEVER)) {
throw new InvalidDataAccessApiUsageException("Ignore case not supported");
throw new InvalidDataAccessApiUsageException("Ignore case not supported!");
}
switch (part.getType()) {
@@ -140,7 +166,9 @@ public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExp
partBuilder.append(part.getProperty().toDotPath().replace(".", "?."));
partBuilder.append("<").append("[").append(parameterIndex++).append("]");
partBuilder.append(")");
break;
case REGEX:
partBuilder.append(" matches ").append("[").append(parameterIndex++).append("]");
@@ -171,9 +199,8 @@ public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExp
if (orPartIter.hasNext()) {
sb.append("||");
}
}
return SpelExpressionFactory.parseRaw(sb.toString());
return PARSER.parseRaw(sb.toString());
}
}

View File

@@ -18,17 +18,10 @@ package org.springframework.data.keyvalue.repository.support;
import static org.springframework.data.querydsl.QueryDslUtils.*;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
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.keyvalue.repository.BasicKeyValueRepository;
import org.springframework.data.keyvalue.repository.QueryDslKeyValueRepository;
import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery;
import org.springframework.data.keyvalue.repository.query.SpelQueryCreator;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.MappingContext;
@@ -39,19 +32,13 @@ import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.PersistentEntityInformation;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
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.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
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.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
/**
* {@link RepositoryFactorySupport} specific of handing
@@ -63,12 +50,14 @@ import org.springframework.util.CollectionUtils;
public class KeyValueRepositoryFactory extends RepositoryFactorySupport {
private static final Class<SpelQueryCreator> DEFAULT_QUERY_CREATOR = SpelQueryCreator.class;
private final KeyValueOperations keyValueOperations;
private final MappingContext<?, ?> context;
private final Class<? extends AbstractQueryCreator<?, ?>> 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<? extends AbstractQueryCreator<?, ?>> 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 <T, ID extends Serializable> EntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
PersistentEntity<T, ?> entity = (PersistentEntity<T, ?>) context.getPersistentEntity(domainClass);
PersistentEntityInformation<T, ID> entityInformation = new PersistentEntityInformation<T, ID>(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<?, Serializable> 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<? extends AbstractQueryCreator<?, ?>> queryCreator;
/**
* Creates a new {@link KeyValueQueryLookupStrategy} for the given {@link Key}, {@link EvaluationContextProvider},
* {@link KeyValueOperations} and query creator type.
* <p>
* 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<? extends AbstractQueryCreator<?, ?>> 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<? extends AbstractQueryCreator<?, ?>> queryCreator;
public KeyValuePartTreeQuery(QueryMethod queryMethod, EvaluationContextProvider evalContextProvider,
KeyValueOperations keyValueOperations, Class<? extends AbstractQueryCreator<?, ?>> 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<? extends AbstractQueryCreator<?, ?>> constructor = (Constructor<? extends AbstractQueryCreator<?, ?>>) ClassUtils
.getConstructorIfAvailable(queryCreator, PartTree.class, ParameterAccessor.class);
return (KeyValueQuery<?>) BeanUtils.instantiateClass(constructor, tree, accessor).createQuery();
}
}
}

View File

@@ -35,7 +35,6 @@ public class KeyValueRepositoryFactoryBean<T extends Repository<S, ID>, S, ID ex
RepositoryFactoryBeanSupport<T, S, ID> {
private KeyValueOperations operations;
private boolean mappingContextAvailable = false;
private Class<? extends AbstractQueryCreator<?, ?>> queryCreator;
public void setKeyValueOperations(KeyValueOperations operations) {
@@ -48,9 +47,7 @@ public class KeyValueRepositoryFactoryBean<T extends Repository<S, ID>, S, ID ex
*/
@Override
public void setMappingContext(MappingContext<?, ?> mappingContext) {
super.setMappingContext(mappingContext);
this.mappingContextAvailable = mappingContext != null;
}
public void setQueryCreator(Class<? extends AbstractQueryCreator<?, ?>> queryCreator) {
@@ -72,12 +69,6 @@ public class KeyValueRepositoryFactoryBean<T extends Repository<S, ID>, S, ID ex
*/
@Override
public void afterPropertiesSet() {
if (!mappingContextAvailable) {
super.setMappingContext(operations.getMappingContext());
}
super.afterPropertiesSet();
}
}

View File

@@ -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 <T>
* @param <ID>
*/
public class QueryDslKeyValueRepository<T, ID extends Serializable> extends BasicKeyValueRepository<T, ID> implements
public class QueryDslKeyValueRepository<T, ID extends Serializable> extends SimpleKeyValueRepository<T, ID> implements
QueryDslPredicateExecutor<T> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
@@ -52,22 +54,31 @@ public class QueryDslKeyValueRepository<T, ID extends Serializable> extends Basi
private final PathBuilder<T> 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<T, ID> 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<T, ID> 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<T>(path.getType(), path.getMetadata());
}
@@ -78,9 +89,7 @@ public class QueryDslKeyValueRepository<T, ID extends Serializable> 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<T, ID extends Serializable> extends Basi
*/
@Override
public Iterable<T> findAll(Predicate predicate) {
ProjectableQuery<?> query = prepareQuery(predicate);
return query.list(builder);
return prepareQuery(predicate).list(builder);
}
/*
@@ -103,6 +110,7 @@ public class QueryDslKeyValueRepository<T, ID extends Serializable> extends Basi
ProjectableQuery<?> query = prepareQuery(predicate);
query.orderBy(orders);
return query.list(builder);
}
@@ -116,6 +124,7 @@ public class QueryDslKeyValueRepository<T, ID extends Serializable> extends Basi
ProjectableQuery<?> query = prepareQuery(predicate);
if (pageable != null) {
query.offset(pageable.getOffset());
query.limit(pageable.getPageSize());
@@ -133,9 +142,7 @@ public class QueryDslKeyValueRepository<T, ID extends Serializable> 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<T, ID extends Serializable> extends Basi
protected ProjectableQuery<?> prepareQuery(Predicate predicate) {
CollQuery query = new CollQuery();
query.from(builder, findAll());
query.where(predicate);
return query;
}
}

View File

@@ -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 <T>
* @param <ID>
*/
public class BasicKeyValueRepository<T, ID extends Serializable> implements KeyValueRepository<T, ID> {
public class SimpleKeyValueRepository<T, ID extends Serializable> implements KeyValueRepository<T, ID> {
private final KeyValueOperations operations;
private final EntityInformation<T, ID> entityInformation;
public BasicKeyValueRepository(EntityInformation<T, ID> 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<T, ID> 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<T, ID extends Serializable> implements KeyV
public Page<T> findAll(Pageable pageable) {
List<T> 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<T, ID extends Serializable> implements KeyV
@Override
public <S extends T> 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<T, ID extends Serializable> implements KeyV
for (S entity : entities) {
save(entity);
}
return entities;
}
@@ -141,7 +153,9 @@ public class BasicKeyValueRepository<T, ID extends Serializable> implements KeyV
List<T> result = new ArrayList<T>();
for (ID id : ids) {
T candidate = findOne(id);
if (candidate != null) {
result.add(candidate);
}

View File

@@ -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}.
*

View File

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