DATAKV-85 - Ported key-value infrastructure from Spring Data Commons.

This commit is contained in:
Oliver Gierke
2014-11-27 12:44:30 +01:00
parent 2ebac47e31
commit 42a13a7403
56 changed files with 7964 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
/*
* 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.annotation;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.annotation.Persistent;
/**
* Marker interface for methods with {@link Persistent} annotations indicating the presence of a dedicated keyspace the
* entity should reside in. If present the value will be picked up for resolving the keyspace.
*
* <pre>
* <code>
* &#64;Persistent
* &#64;Documented
* &#64;Retention(RetentionPolicy.RUNTIME)
* &#64;Target({ ElementType.TYPE })
* public &#64;interface Document {
*
* &#64;KeySpace
* String collection() default "person";
* }
* </code>
* </pre>
*
* Can also be directly used on types to indicate the keyspace.
*
* <pre>
* <code>
* &#64;KeySpace("persons")
* public class Foo {
*
* }
* </code>
* </pre>
*
* @author Christoph Strobl
* @since 1.10
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = { METHOD, TYPE })
public @interface KeySpace {
String value() default "";
}

View File

@@ -0,0 +1,78 @@
/*
* 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.util.Collection;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
/**
* Base implementation of {@link KeyValueAdapter} holds {@link QueryEngine} to delegate {@literal find} and
* {@literal count} execution to.
*
* @author Christoph Strobl
* @since 1.10
*/
public abstract class AbstractKeyValueAdapter implements KeyValueAdapter {
private final QueryEngine<? extends KeyValueAdapter, ?, ?> engine;
/**
* Creates new {@link AbstractKeyValueAdapter} with using the default query engine.
*/
protected AbstractKeyValueAdapter() {
this(null);
}
/**
* Creates new {@link AbstractKeyValueAdapter} with using the default query engine.
*
* @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);
}
/**
* Get the {@link QueryEngine} used.
*
* @return
*/
protected QueryEngine<? extends KeyValueAdapter, ?, ?> getQueryEngine() {
return engine;
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#find(org.springframework.data.keyvalue.core.query.KeyValueQuery, java.io.Serializable)
*/
@Override
public Collection<?> find(KeyValueQuery<?> query, Serializable keyspace) {
return engine.execute(query, keyspace);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#count(org.springframework.data.keyvalue.core.query.KeyValueQuery, java.io.Serializable)
*/
@Override
public long count(KeyValueQuery<?> query, Serializable keyspace) {
return engine.count(query, keyspace);
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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 org.springframework.data.keyvalue.core.query.KeyValueQuery;
/**
* Resolves the criteria object from given {@link KeyValueQuery}.
*
* @author Christoph Strobl
* @since 1.10
* @param <T>
*/
public interface CriteriaAccessor<T> {
/**
* Checks and reads {@link KeyValueQuery#getCritieria()} of given {@link KeyValueQuery}. Might also apply additional
* transformation to match the desired type.
*
* @param query can be {@literal null}.
* @return the criteria extracted from the query.
* @throws IllegalArgumentException in case the criteria is not valid for usage with specific {@link CriteriaAccessor}
* .
*/
T resolve(KeyValueQuery<?> query);
}

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

@@ -0,0 +1,36 @@
/*
* 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 org.springframework.data.util.TypeInformation;
/**
* API for components generating identifiers.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.10
*/
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

@@ -0,0 +1,105 @@
/*
* 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.util.Collection;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
/**
* {@link KeyValueAdapter} unifies access and shields the underlying key/value specific implementation.
*
* @author Christoph Strobl
* @since 1.10
*/
public interface KeyValueAdapter extends DisposableBean {
/**
* Add object with given id to keyspace.
*
* @param id must not be {@literal null}.
* @param keyspace must not be {@literal null}.
* @return the item previously associated with the id.
*/
Object put(Serializable id, Object item, Serializable keyspace);
/**
* Check if a object with given id exists in keyspace.
*
* @param id must not be {@literal null}.
* @param keyspace must not be {@literal null}.
* @return true if item of type with id exists.
*/
boolean contains(Serializable id, Serializable keyspace);
/**
* Get the object with given id from keyspace.
*
* @param id must not be {@literal null}.
* @param keyspace must not be {@literal null}.
* @return {@literal null} in case no matching item exists.
*/
Object get(Serializable id, Serializable keyspace);
/**
* Delete and return the obect with given type and id.
*
* @param id must not be {@literal null}.
* @param keyspace must not be {@literal null}.
* @return {@literal null} if object could not be found
*/
Object delete(Serializable id, Serializable keyspace);
/**
* Get all elements for given keyspace.
*
* @param keyspace must not be {@literal null}.
* @return empty {@link Collection} if nothing found.
*/
Collection<?> getAllOf(Serializable keyspace);
/**
* Remove all objects of given type.
*
* @param keyspace must not be {@literal null}.
*/
void deleteAllOf(Serializable keyspace);
/**
* Removes all objects.
*/
void clear();
/**
* Find all matching objects within {@literal keyspace}.
*
* @param query
* @param keyspace must not be {@literal null}.
* @return empty {@link Collection} if no match found.
*/
Collection<?> find(KeyValueQuery<?> query, Serializable keyspace);
/**
* Count all matching objects within {@literal keyspace}.
*
* @param query
* @param keyspace must not be {@literal null}.
* @return
*/
long count(KeyValueQuery<?> query, Serializable keyspace);
}

View File

@@ -0,0 +1,36 @@
/*
* 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;
/**
* Generic callback interface for code that operates on a {@link KeyValueAdapter}. This is particularly useful for
* delegating code that needs to work closely on the underlying key/value store implementation.
*
* @author Christoph Strobl
* @since 1.10
* @param <T>
*/
public interface KeyValueCallback<T> {
/**
* Gets called by {@code KeyValueTemplate#execute(KeyValueCallback)}. Allows for returning a result object created
* within the callback, i.e. a domain object or a collection of domain objects.
*
* @param adapter
* @return
*/
T doInKeyValue(KeyValueAdapter adapter);
}

View File

@@ -0,0 +1,178 @@
/*
* 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.util.List;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.data.domain.Sort;
import org.springframework.data.keyvalue.annotation.KeySpace;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
import org.springframework.data.mapping.context.MappingContext;
/**
* Interface that specifies a basic set of key/value operations. Implemented by {@link KeyValueTemplate}.
*
* @author Christoph Strobl
* @since 1.10
*/
public interface KeyValueOperations extends DisposableBean {
/**
* Add given object. Object needs to have id property to which a generated value will be assigned.
*
* @param objectToInsert
* @return
*/
<T> T insert(T objectToInsert);
/**
* Add object with given id.
*
* @param id must not be {@literal null}.
* @param objectToInsert must not be {@literal null}.
*/
void insert(Serializable id, Object objectToInsert);
/**
* Get all elements of given type. Respects {@link KeySpace} if present and therefore returns all elements that can be
* assigned to requested type.
*
* @param type must not be {@literal null}.
* @return empty collection if no elements found.
*/
<T> List<T> findAll(Class<T> type);
/**
* Get all elements ordered by sort. Respects {@link KeySpace} if present and therefore returns all elements that can
* be assigned to requested type.
*
* @param sort must not be {@literal null}.
* @param type must not be {@literal null}.
* @return
*/
<T> List<T> findAll(Sort sort, Class<T> 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}.
* @return null if not found.
*/
<T> T findById(Serializable id, Class<T> type);
/**
* Execute operation against underlying store.
*
* @param action must not be {@literal null}.
* @return
*/
<T> T execute(KeyValueCallback<T> action);
/**
* Get all elements matching the given query. <br />
* Respects {@link KeySpace} if present and therefore returns all elements that can be assigned to requested type..
*
* @param query must not be {@literal null}.
* @param type must not be {@literal null}.
* @return empty collection if no match found.
*/
<T> List<T> find(KeyValueQuery<?> query, Class<T> 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
* @param type must not be {@literal null}.
* @return
*/
<T> List<T> findInRange(int offset, int rows, Class<T> 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
* @param sort
* @param type
* @return
*/
<T> List<T> findInRange(int offset, int rows, Sort sort, Class<T> type);
/**
* @param objectToUpdate must not be {@literal null}.
*/
void update(Object objectToUpdate);
/**
* @param id must not be {@literal null}.
* @param objectToUpdate must not be {@literal null}.
*/
void update(Serializable id, Object objectToUpdate);
/**
* Remove all elements of type. Respects {@link KeySpace} if present and therefore removes all elements that can be
* assigned to requested type.
*
* @param type must not be {@literal null}.
*/
void delete(Class<?> type);
/**
* @param objectToDelete must not be {@literal null}.
* @return
*/
<T> T delete(T objectToDelete);
/**
* Delete item of type with given id.
*
* @param id must not be {@literal null}.
* @param type must not be {@literal null}.
* @return the deleted item or {@literal null} if no match found.
*/
<T> T delete(Serializable id, Class<T> 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
*/
long count(Class<?> type);
/**
* Total number of elements matching given query. Respects {@link KeySpace} if present and therefore counts all
* elements that can be assigned to requested type.
*
* @param query
* @param type
* @return
*/
long count(KeyValueQuery<?> query, Class<?> type);
/**
* @return mapping context in use.
*/
MappingContext<?, ?> getMappingContext();
}

View File

@@ -0,0 +1,54 @@
/*
* 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.util.NoSuchElementException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
/**
* Simple {@link PersistenceExceptionTranslator} implementation for key/value stores that converts the given runtime
* exception to an appropriate exception from the {@code org.springframework.dao} hierarchy.
*
* @author Christoph Strobl
* @since 1.10
*/
public class KeyValuePersistenceExceptionTranslator implements PersistenceExceptionTranslator {
/*
* (non-Javadoc)
* @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException)
*/
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException e) {
if (e == null || e instanceof DataAccessException) {
return (DataAccessException) e;
}
if (e instanceof NoSuchElementException || e instanceof IndexOutOfBoundsException
|| e instanceof IllegalStateException) {
return new DataRetrievalFailureException(e.getMessage(), e);
}
if (e.getClass().getName().startsWith("java")) {
return new UncategorizedKeyValueException(e.getMessage(), e);
}
return null;
}
}

View File

@@ -0,0 +1,452 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.keyvalue.core;
import static org.springframework.data.keyvalue.core.KeySpaceUtils.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.domain.Sort;
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.util.Assert;
import org.springframework.util.ClassUtils;
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 static final PersistenceExceptionTranslator DEFAULT_PERSISTENCE_EXCEPTION_TRANSLATOR = new KeyValuePersistenceExceptionTranslator();
private final KeyValueAdapter adapter;
private final ConcurrentHashMap<Class<?>, String> keySpaceCache = new ConcurrentHashMap<Class<?>, String>();
private final MappingContext<? extends PersistentEntity<?, ? extends PersistentProperty<?>>, ? extends PersistentProperty<?>> mappingContext;
private final IdentifierGenerator identifierGenerator;
private PersistenceExceptionTranslator exceptionTranslator = DEFAULT_PERSISTENCE_EXCEPTION_TRANSLATOR;
/**
* Create new {@link KeyValueTemplate} using the given {@link KeyValueAdapter} with a default
* {@link KeyValueMappingContext}.
*
* @param adapter must not be {@literal null}.
*/
public KeyValueTemplate(KeyValueAdapter adapter) {
this(adapter, new KeyValueMappingContext());
}
/**
* Create new {@link KeyValueTemplate} using the given {@link KeyValueAdapter} and {@link MappingContext}.
*
* @param adapter must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
*/
@SuppressWarnings("rawtypes")
public KeyValueTemplate(
KeyValueAdapter adapter,
MappingContext<? extends PersistentEntity<?, ? extends PersistentProperty>, ? extends PersistentProperty<?>> mappingContext) {
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)
*/
@Override
public <T> T insert(T objectToInsert) {
PersistentEntity<?, ?> entity = this.mappingContext.getPersistentEntity(ClassUtils.getUserClass(objectToInsert));
GeneratingIdAccessor generatingIdAccessor = new GeneratingIdAccessor(entity.getPropertyAccessor(objectToInsert),
entity.getIdProperty(), identifierGenerator);
Object id = generatingIdAccessor.getOrGenerateIdentifier();
insert((Serializable) id, objectToInsert);
return objectToInsert;
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#insert(java.io.Serializable, java.lang.Object)
*/
@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!");
execute(new KeyValueCallback<Void>() {
@Override
public Void doInKeyValue(KeyValueAdapter adapter) {
String typeKey = resolveKeySpace(objectToInsert.getClass());
if (adapter.contains(id, typeKey)) {
throw new InvalidDataAccessApiUsageException("Cannot insert existing object. Please use update.");
}
adapter.put(id, objectToInsert, typeKey);
return null;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#update(java.lang.Object)
*/
@SuppressWarnings("rawtypes")
@Override
public void update(Object objectToUpdate) {
PersistentEntity<?, ? extends PersistentProperty> entity = this.mappingContext.getPersistentEntity(ClassUtils
.getUserClass(objectToUpdate));
if (!entity.hasIdProperty()) {
throw new InvalidDataAccessApiUsageException(String.format("Cannot determine id for type %s",
ClassUtils.getUserClass(objectToUpdate)));
}
update((Serializable) entity.getIdentifierAccessor(objectToUpdate).getIdentifier(), objectToUpdate);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#update(java.io.Serializable, java.lang.Object)
*/
@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!");
execute(new KeyValueCallback<Void>() {
@Override
public Void doInKeyValue(KeyValueAdapter adapter) {
adapter.put(id, objectToUpdate, resolveKeySpace(objectToUpdate.getClass()));
return null;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#findAllOf(java.lang.Class)
*/
@Override
public <T> List<T> findAll(final Class<T> type) {
Assert.notNull(type, "Type to fetch must not be null!");
return execute(new KeyValueCallback<List<T>>() {
@SuppressWarnings("unchecked")
@Override
public List<T> doInKeyValue(KeyValueAdapter adapter) {
Collection<?> x = adapter.getAllOf(resolveKeySpace(type));
if (getKeySpace(type) == null) {
return new ArrayList<T>((Collection<T>) x);
}
ArrayList<T> filtered = new ArrayList<T>();
for (Object candidate : x) {
if (typeCheck(type, candidate)) {
filtered.add((T) candidate);
}
}
return filtered;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#findById(java.io.Serializable, java.lang.Class)
*/
@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!");
return execute(new KeyValueCallback<T>() {
@SuppressWarnings("unchecked")
@Override
public T doInKeyValue(KeyValueAdapter adapter) {
Object result = adapter.get(id, resolveKeySpace(type));
if (result == null || getKeySpace(type) == null || typeCheck(type, result)) {
return (T) result;
}
return null;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#delete(java.lang.Class)
*/
@Override
public void delete(final Class<?> type) {
Assert.notNull(type, "Type to delete must not be null!");
final String typeKey = resolveKeySpace(type);
execute(new KeyValueCallback<Void>() {
@Override
public Void doInKeyValue(KeyValueAdapter adapter) {
adapter.deleteAllOf(typeKey);
return null;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#delete(java.lang.Object)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public <T> T delete(T objectToDelete) {
Class<T> type = (Class<T>) ClassUtils.getUserClass(objectToDelete);
PersistentEntity<?, ? extends PersistentProperty> entity = this.mappingContext.getPersistentEntity(type);
return delete((Serializable) entity.getIdentifierAccessor(objectToDelete).getIdentifier(), type);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#delete(java.io.Serializable, java.lang.Class)
*/
@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!");
return execute(new KeyValueCallback<T>() {
@SuppressWarnings("unchecked")
@Override
public T doInKeyValue(KeyValueAdapter adapter) {
return (T) adapter.delete(id, resolveKeySpace(type));
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#count(java.lang.Class)
*/
@Override
public long count(Class<?> type) {
Assert.notNull(type, "Type for count must not be null!");
return findAll(type).size();
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#execute(org.springframework.data.keyvalue.core.KeyValueCallback)
*/
@Override
public <T> T execute(KeyValueCallback<T> action) {
Assert.notNull(action, "KeyValueCallback must not be null!");
try {
return action.doInKeyValue(this.adapter);
} catch (RuntimeException e) {
throw resolveExceptionIfPossible(e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#find(org.springframework.data.keyvalue.core.query.KeyValueQuery, java.lang.Class)
*/
@Override
public <T> List<T> find(final KeyValueQuery<?> query, final Class<T> type) {
return execute(new KeyValueCallback<List<T>>() {
@SuppressWarnings("unchecked")
@Override
public List<T> doInKeyValue(KeyValueAdapter adapter) {
Collection<?> result = adapter.find(query, resolveKeySpace(type));
if (getKeySpace(type) == null) {
return new ArrayList<T>((Collection<T>) result);
}
List<T> filtered = new ArrayList<T>();
for (Object candidate : result) {
if (typeCheck(type, candidate)) {
filtered.add((T) candidate);
}
}
return filtered;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#findAllOf(org.springframework.data.domain.Sort, java.lang.Class)
*/
@SuppressWarnings("rawtypes")
@Override
public <T> List<T> findAll(Sort sort, Class<T> type) {
return find(new KeyValueQuery(sort), type);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#findInRange(int, int, java.lang.Class)
*/
@SuppressWarnings("rawtypes")
@Override
public <T> List<T> findInRange(int offset, int rows, Class<T> type) {
return find(new KeyValueQuery().skip(offset).limit(rows), type);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#findInRange(int, int, org.springframework.data.domain.Sort, java.lang.Class)
*/
@SuppressWarnings("rawtypes")
@Override
public <T> List<T> findInRange(int offset, int rows, Sort sort, Class<T> type) {
return find(new KeyValueQuery(sort).skip(offset).limit(rows), type);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#count(org.springframework.data.keyvalue.core.query.KeyValueQuery, java.lang.Class)
*/
@Override
public long count(final KeyValueQuery<?> query, final Class<?> type) {
return execute(new KeyValueCallback<Long>() {
@Override
public Long doInKeyValue(KeyValueAdapter adapter) {
return adapter.count(query, resolveKeySpace(type));
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueOperations#getMappingContext()
*/
@Override
public MappingContext<?, ?> getMappingContext() {
return this.mappingContext;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
@Override
public void destroy() throws Exception {
this.adapter.clear();
}
/**
* Set the {@link PersistenceExceptionTranslator} used for converting {@link RuntimeException}.
*
* @param exceptionTranslator must not be {@literal null}.
*/
public void setExceptionTranslator(PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(exceptionTranslator, "ExceptionTranslator must not be null.");
this.exceptionTranslator = exceptionTranslator;
}
protected String resolveKeySpace(Class<?> type) {
Class<?> userClass = ClassUtils.getUserClass(type);
String potentialAlias = keySpaceCache.get(userClass);
if (potentialAlias != null) {
return potentialAlias;
}
String keySpaceString = null;
Object keySpace = getKeySpace(type);
if (keySpace != null) {
keySpaceString = keySpace.toString();
}
if (!StringUtils.hasText(keySpaceString)) {
keySpaceString = userClass.getName();
}
keySpaceCache.put(userClass, keySpaceString);
return keySpaceString;
}
private static boolean typeCheck(Class<?> requiredType, Object candidate) {
return candidate == null ? true : ClassUtils.isAssignable(requiredType, candidate.getClass());
}
private RuntimeException resolveExceptionIfPossible(RuntimeException e) {
DataAccessException translatedException = exceptionTranslator.translateExceptionIfPossible(e);
return translatedException != null ? translatedException : e;
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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.util.Collection;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
/**
* Base implementation for accessing and executing {@link KeyValueQuery} against a {@link KeyValueAdapter}.
*
* @author Christoph Strobl
* @since 1.10
* @param <ADAPTER>
* @param <CRITERIA>
* @param <SORT>
*/
public abstract class QueryEngine<ADAPTER extends KeyValueAdapter, CRITERIA, SORT> {
private final CriteriaAccessor<CRITERIA> criteriaAccessor;
private final SortAccessor<SORT> sortAccessor;
private ADAPTER adapter;
public QueryEngine(CriteriaAccessor<CRITERIA> criteriaAccessor, SortAccessor<SORT> sortAccessor) {
this.criteriaAccessor = criteriaAccessor;
this.sortAccessor = sortAccessor;
}
/**
* Extract query attributes and delegate to concrete execution.
*
* @param query
* @param keyspace
* @return
*/
public Collection<?> execute(KeyValueQuery<?> query, Serializable keyspace) {
CRITERIA criteria = this.criteriaAccessor != null ? this.criteriaAccessor.resolve(query) : null;
SORT sort = this.sortAccessor != null ? this.sortAccessor.resolve(query) : null;
return execute(criteria, sort, query.getOffset(), query.getRows(), keyspace);
}
/**
* Extract query attributes and delegate to concrete execution.
*
* @param query
* @param keyspace
* @return
*/
public long count(KeyValueQuery<?> query, Serializable keyspace) {
CRITERIA criteria = this.criteriaAccessor != null ? this.criteriaAccessor.resolve(query) : null;
return count(criteria, keyspace);
}
/**
* @param criteria
* @param sort
* @param offset
* @param rows
* @param keyspace
* @return
*/
public abstract Collection<?> execute(CRITERIA criteria, SORT sort, int offset, int rows, Serializable keyspace);
/**
* @param criteria
* @param keyspace
* @return
*/
public abstract long count(CRITERIA criteria, Serializable keyspace);
/**
* Get the {@link KeyValueAdapter} used.
*
* @return
*/
protected ADAPTER getAdapter() {
return this.adapter;
}
/**
* @param adapter
*/
@SuppressWarnings("unchecked")
public void registerAdapter(KeyValueAdapter adapter) {
if (this.adapter == null) {
this.adapter = (ADAPTER) adapter;
} else {
throw new IllegalArgumentException("Cannot register more than one adapter for this QueryEngine.");
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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 org.springframework.data.domain.Sort;
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
/**
* Resolves the {@link Sort} object from given {@link KeyValueQuery} and potentially converts it into a store specific
* representation that can be used by the {@link QueryEngine} implementation.
*
* @author Christoph Strobl
* @since 1.10
* @param <T>
*/
public interface SortAccessor<T> {
/**
* Reads {@link KeyValueQuery#getSort()} of given {@link KeyValueQuery} and applies required transformation to match
* the desired type.
*
* @param query can be {@literal null}.
* @return {@literal null} in case {@link Sort} has not been defined on {@link KeyValueQuery}.
*/
T resolve(KeyValueQuery<?> query);
}

View File

@@ -0,0 +1,60 @@
/*
* 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 org.springframework.data.keyvalue.core.query.KeyValueQuery;
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
*/
class SpelCriteriaAccessor implements CriteriaAccessor<SpelExpression> {
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) {
if (query.getCritieria() == null) {
return null;
}
if (query.getCritieria() instanceof SpelExpression) {
return (SpelExpression) query.getCritieria();
}
if (query.getCritieria() instanceof String) {
return parser.parseRaw((String) query.getCritieria());
}
throw new IllegalArgumentException("Cannot create SpelCriteria for " + query.getCritieria());
}
}

View File

@@ -0,0 +1,150 @@
/*
* 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.util.Comparator;
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 SpelPropertyComparator<T> implements Comparator<T> {
private final String path;
private final SpelExpressionParser parser;
private boolean asc = true;
private boolean nullsFirst = true;
private SpelExpression expression;
/**
* Create new {@link SpelPropertyComparator} for the given property path an {@link SpelExpressionParser}..
*
* @param path must not be {@literal null} or empty.
* @param parser must not be {@literal null}.
*/
public SpelPropertyComparator(String path, SpelExpressionParser parser) {
this.path = path;
this.parser = parser;
}
/**
* Sort {@literal ascending}.
*
* @return
*/
public SpelPropertyComparator<T> asc() {
this.asc = true;
return this;
}
/**
* Sort {@literal descending}.
*
* @return
*/
public SpelPropertyComparator<T> desc() {
this.asc = false;
return this;
}
/**
* Sort {@literal null} values first.
*
* @return
*/
public SpelPropertyComparator<T> nullsFirst() {
this.nullsFirst = true;
return this;
}
/**
* Sort {@literal null} values last.
*
* @return
*/
public SpelPropertyComparator<T> nullsLast() {
this.nullsFirst = false;
return this;
}
/**
* Parse values to {@link SpelExpression}
*
* @return
*/
protected SpelExpression getExpression() {
if (this.expression == null) {
this.expression = parser.parseRaw(buildExpressionForPath());
}
return this.expression;
}
/**
* Create the expression raw value.
*
* @return
*/
protected String buildExpressionForPath() {
StringBuilder rawExpression = new StringBuilder(
"new org.springframework.util.comparator.NullSafeComparator(new org.springframework.util.comparator.ComparableComparator(), "
+ Boolean.toString(this.nullsFirst) + ").compare(");
rawExpression.append("#arg1?.");
rawExpression.append(path != null ? path.replace(".", "?.") : "");
rawExpression.append(",");
rawExpression.append("#arg2?.");
rawExpression.append(path != null ? path.replace(".", "?.") : "");
rawExpression.append(")");
return rawExpression.toString();
}
/*
* (non-Javadoc)
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
@Override
public int compare(T arg1, T arg2) {
SpelExpression expressionToUse = getExpression();
expressionToUse.getEvaluationContext().setVariable("arg1", arg1);
expressionToUse.getEvaluationContext().setVariable("arg2", arg2);
return expressionToUse.getValue(Integer.class) * (asc ? 1 : -1);
}
/**
* Get dot path to property.
*
* @return
*/
public String getPath() {
return path;
}
}

View File

@@ -0,0 +1,119 @@
/*
* 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.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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>
*/
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(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();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<?> sortAndFilterMatchingRange(Collection<?> source, SpelExpression criteria, Comparator sort,
int offset, int rows) {
List<?> tmp = new ArrayList(source);
if (sort != null) {
Collections.sort(tmp, sort);
}
return filterMatchingRange(tmp, criteria, offset, rows);
}
private static <S> List<S> filterMatchingRange(Iterable<S> source, SpelExpression criteria, int offset, int rows) {
List<S> result = new ArrayList<S>();
boolean compareOffsetAndRows = 0 < offset || 0 <= rows;
int remainingRows = rows;
int curPos = 0;
for (S candidate : source) {
boolean matches = criteria == null;
if (!matches) {
try {
matches = criteria.getValue(candidate, Boolean.class);
} catch (SpelEvaluationException e) {
criteria.getEvaluationContext().setVariable("it", candidate);
matches = criteria.getValue(Boolean.class);
}
}
if (matches) {
if (compareOffsetAndRows) {
if (curPos >= offset && rows > 0) {
result.add(candidate);
remainingRows--;
if (remainingRows <= 0) {
break;
}
}
curPos++;
} else {
result.add(candidate);
}
}
}
return result;
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.util.Comparator;
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 SpelPropertyComparator}.
*
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.10
*/
class SpelSortAccessor implements SortAccessor<Comparator<?>> {
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) {
if (query == null || query.getSort() == null) {
return null;
}
CompoundComparator compoundComperator = new CompoundComparator();
for (Order order : query.getSort()) {
SpelPropertyComparator<?> spelSort = new SpelPropertyComparator(order.getProperty(), parser);
if (Direction.DESC.equals(order.getDirection())) {
spelSort.desc();
if (order.getNullHandling() != null && !NullHandling.NATIVE.equals(order.getNullHandling())) {
spelSort = NullHandling.NULLS_FIRST.equals(order.getNullHandling()) ? spelSort.nullsFirst() : spelSort
.nullsLast();
}
}
compoundComperator.addComparator(spelSort);
}
return compoundComperator;
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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 org.springframework.dao.UncategorizedDataAccessException;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class UncategorizedKeyValueException extends UncategorizedDataAccessException {
private static final long serialVersionUID = -8087116071859122297L;
public UncategorizedKeyValueException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.SimpleTypeHolder;
/**
* Most trivial implementation of {@link PersistentProperty}.
*
* @author Christoph Strobl
* @since 1.10
*/
public class KeyValuePersistentProperty extends AnnotationBasedPersistentProperty<KeyValuePersistentProperty> {
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<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

@@ -0,0 +1,158 @@
/*
* 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.query;
import org.springframework.data.domain.Sort;
/**
* @author Christoph Strobl
* @since 1.10
* @param <T> Criteria type
*/
public class KeyValueQuery<T> {
private Sort sort;
private int offset = -1;
private int rows = -1;
private T criteria;
/**
* Creates new instance of {@link KeyValueQuery}.
*/
public KeyValueQuery() {}
/**
* Creates new instance of {@link KeyValueQuery} with given criteria.
*
* @param criteria can be {@literal null}.
*/
public KeyValueQuery(T criteria) {
this.criteria = criteria;
}
/**
* Creates new instance of {@link KeyValueQuery} with given {@link Sort}.
*
* @param sort can be {@literal null}.
*/
public KeyValueQuery(Sort sort) {
this.sort = sort;
}
/**
* Get the criteria object.
*
* @return
*/
public T getCritieria() {
return criteria;
}
/**
* Get {@link Sort}.
*
* @return
*/
public Sort getSort() {
return sort;
}
/**
* Number of elements to skip.
*
* @return negative value if not set.
*/
public int getOffset() {
return this.offset;
}
/**
* Number of elements to read.
*
* @return negative value if not set.
*/
public int getRows() {
return this.rows;
}
/**
* Set the number of elements to skip.
*
* @param offset use negative value for none.
*/
public void setOffset(int offset) {
this.offset = offset;
}
/**
* Set the number of elements to read.
*
* @param offset use negative value for all.
*/
public void setRows(int rows) {
this.rows = rows;
}
/**
* Set {@link Sort} to be applied.
*
* @param sort
*/
public void setSort(Sort sort) {
this.sort = sort;
}
/**
* Add given {@link Sort}.
*
* @param sort {@literal null} {@link Sort} will be ignored.
* @return
*/
public KeyValueQuery<T> orderBy(Sort sort) {
if (sort == null) {
return this;
}
if (this.sort != null) {
this.sort.and(sort);
} else {
this.sort = sort;
}
return this;
}
/**
* @see KeyValueQuery#setOffset(int)
* @param offset
* @return
*/
public KeyValueQuery<T> skip(int offset) {
setOffset(offset);
return this;
}
/**
* @see KeyValueQuery#setRows(int)
* @param rows
* @return
*/
public KeyValueQuery<T> limit(int rows) {
setRows(rows);
return this;
}
}

View File

@@ -0,0 +1,205 @@
/*
* 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;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.Page;
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.repository.core.EntityInformation;
import org.springframework.util.Assert;
/**
* @author Christoph Strobl
* @since 1.10
* @param <T>
* @param <ID>
*/
public class BasicKeyValueRepository<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) {
Assert.notNull(metadata, "Cannot initialize repository for 'null' metadata");
Assert.notNull(operations, "Cannot initialize repository for 'null' operations");
this.entityInformation = metadata;
this.operations = operations;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
*/
@Override
public Iterable<T> findAll(Sort sort) {
return operations.findAll(sort, entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Pageable)
*/
@Override
public Page<T> findAll(Pageable pageable) {
List<T> content = null;
if (pageable.getSort() != null) {
content = operations.findInRange(pageable.getOffset(), pageable.getPageSize(), pageable.getSort(),
entityInformation.getJavaType());
} else {
content = operations.findInRange(pageable.getOffset(), pageable.getPageSize(), entityInformation.getJavaType());
}
return new PageImpl<T>(content, pageable, this.operations.count(entityInformation.getJavaType()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
*/
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be 'null' for save.");
if (entityInformation.isNew(entity)) {
operations.insert(entity);
} else {
operations.update(entityInformation.getId(entity), entity);
}
return entity;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
*/
@Override
public <S extends T> Iterable<S> save(Iterable<S> entities) {
for (S entity : entities) {
save(entity);
}
return entities;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findOne(java.io.Serializable)
*/
@Override
public T findOne(ID id) {
return operations.findById(id, entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#exists(java.io.Serializable)
*/
@Override
public boolean exists(ID id) {
return findOne(id) != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll()
*/
@Override
<<<<<<< Updated upstream:src/main/java/org/springframework/data/keyvalue/repository/BasicKeyValueRepository.java
public Iterable<T> findAll() {
return operations.findAllOf(entityInformation.getJavaType());
=======
public List<T> findAll() {
return operations.findAll(entityInformation.getJavaType());
>>>>>>> Stashed changes:src/main/java/org/springframework/data/keyvalue/repository/support/SimpleKeyValueRepository.java
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
*/
@Override
public Iterable<T> findAll(Iterable<ID> ids) {
List<T> result = new ArrayList<T>();
for (ID id : ids) {
T candidate = findOne(id);
if (candidate != null) {
result.add(candidate);
}
}
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#count()
*/
@Override
public long count() {
return operations.count(entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
*/
@Override
public void delete(ID id) {
operations.delete(id, entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
*/
@Override
public void delete(T entity) {
delete(entityInformation.getId(entity));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
*/
@Override
public void delete(Iterable<? extends T> entities) {
for (T entity : entities) {
delete(entity);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#deleteAll()
*/
@Override
public void deleteAll() {
operations.delete(entityInformation.getJavaType());
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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;
import java.io.Serializable;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
* @author Christoph Strobl
* @since 1.10
* @param <T>
* @param <ID>
*/
public interface KeyValueRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
}

View File

@@ -0,0 +1,123 @@
/*
* 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.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.FactoryBean;
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.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;
/**
* Annotation to activate KeyValue 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
*/
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(KeyValueRepositoriesRegistrar.class)
@QueryCreatorType(SpelQueryCreator.class)
public @interface EnableKeyValueRepositories {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
* {@code @EnableJpaRepositories("org.my.pkg")} instead of {@code @EnableJpaRepositories(basePackages="org.my.pkg")}.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this
* attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The
* package of each class specified will be scanned. Consider creating a special no-op marker class or interface in
* each package that serves no purpose other than being referenced by this attribute.
*/
Class<?>[] basePackageClasses() default {};
/**
* Specifies which types are not eligible for component scanning.
*/
Filter[] excludeFilters() default {};
/**
* Specifies which types are eligible for component scanning. Further narrows the set of candidate components from
* everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters.
*/
Filter[] includeFilters() default {};
/**
* Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So
* for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning
* for {@code PersonRepositoryImpl}.
*
* @return
*/
String repositoryImplementationPostfix() default "Impl";
/**
* Configures the location of where to find the Spring Data named queries properties file.
*
* @return
*/
String namedQueriesLocation() default "";
/**
* Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
* {@link Key#CREATE_IF_NOT_FOUND}.
*
* @return
*/
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
/**
* Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to
* {@link KeyValueRepositoryFactoryBean}.
*
* @return
*/
Class<?> repositoryFactoryBeanClass() default KeyValueRepositoryFactoryBean.class;
/**
* Configures the name of the {@link KeyValueOperations} bean to be used with the repositories detected.
*
* @return
*/
String keyValueTemplateRef() default "keyValueTemplate";
/**
* Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the
* repositories infrastructure.
*/
boolean considerNestedRepositories() default false;
}

View File

@@ -0,0 +1,48 @@
/*
* 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.Annotation;
import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
/**
* KeyValue specific {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar}
*
* @author Christoph Strobl
* @since 1.10
*/
public class KeyValueRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation()
*/
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableKeyValueRepositories.class;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension()
*/
@Override
protected RepositoryConfigurationExtension getExtension() {
return new KeyValueRepositoryConfigurationExtension();
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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.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;
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
import org.springframework.data.repository.config.RepositoryConfigurationSource;
/**
* {@link RepositoryConfigurationExtension} for {@link KeyValueRepository}.
*
* @author Christoph Strobl
* @since 1.10
*/
public class KeyValueRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport {
private static final String MAPPING_CONTEXT_BEAN_NAME = "keyValueMappingContext";
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getRepositoryFactoryClassName()
*/
@Override
public String getRepositoryFactoryClassName() {
return KeyValueRepositoryFactoryBean.class.getName();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModuleName()
*/
@Override
public String getModuleName() {
return "KeyValue";
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModulePrefix()
*/
@Override
protected String getModulePrefix() {
return "keyvalue";
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingTypes()
*/
@Override
protected Collection<Class<?>> getIdentifyingTypes() {
return Collections.<Class<?>> singleton(KeyValueRepository.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource)
*/
@Override
public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {
AnnotationAttributes attributes = config.getAttributes();
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");
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#registerBeansForRoot(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.data.repository.config.RepositoryConfigurationSource)
*/
@Override
public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) {
super.registerBeansForRoot(registry, configurationSource);
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

@@ -0,0 +1,206 @@
/*
* 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.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.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
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 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 "";
}
@Override
protected KeyValueQuery<SpelExpression> complete(String criteria, Sort sort) {
KeyValueQuery<SpelExpression> query = new KeyValueQuery<SpelExpression>(this.expression);
if (sort != null) {
query.orderBy(sort);
}
return query;
}
protected SpelExpression toPredicateExpression(PartTree tree) {
int parameterIndex = 0;
StringBuilder sb = new StringBuilder();
for (Iterator<OrPart> orPartIter = tree.iterator(); orPartIter.hasNext();) {
int partCnt = 0;
StringBuilder partBuilder = new StringBuilder();
OrPart orPart = orPartIter.next();
for (Iterator<Part> partIter = orPart.iterator(); partIter.hasNext();) {
Part part = partIter.next();
partBuilder.append("#it?.");
partBuilder.append(part.getProperty().toDotPath().replace(".", "?."));
// TODO: check if we can have caseinsensitive search
if (!part.shouldIgnoreCase().equals(IgnoreCaseType.NEVER)) {
throw new InvalidDataAccessApiUsageException("Ignore case not supported!");
}
switch (part.getType()) {
case TRUE:
partBuilder.append("?.equals(true)");
break;
case FALSE:
partBuilder.append("?.equals(false)");
break;
case SIMPLE_PROPERTY:
partBuilder.append("?.equals(").append("[").append(parameterIndex++).append("])");
break;
case IS_NULL:
partBuilder.append(" == null");
break;
case IS_NOT_NULL:
partBuilder.append(" != null");
break;
case LIKE:
partBuilder.append("?.contains(").append("[").append(parameterIndex++).append("])");
break;
case STARTING_WITH:
partBuilder.append("?.startsWith(").append("[").append(parameterIndex++).append("])");
break;
case AFTER:
case GREATER_THAN:
partBuilder.append(">").append("[").append(parameterIndex++).append("]");
break;
case GREATER_THAN_EQUAL:
partBuilder.append(">=").append("[").append(parameterIndex++).append("]");
break;
case BEFORE:
case LESS_THAN:
partBuilder.append("<").append("[").append(parameterIndex++).append("]");
break;
case LESS_THAN_EQUAL:
partBuilder.append("<=").append("[").append(parameterIndex++).append("]");
break;
case ENDING_WITH:
partBuilder.append("?.endsWith(").append("[").append(parameterIndex++).append("])");
break;
case BETWEEN:
int index = partBuilder.lastIndexOf("#it?.");
partBuilder.insert(index, "(");
partBuilder.append(">").append("[").append(parameterIndex++).append("]");
partBuilder.append("&&");
partBuilder.append("#it?.");
partBuilder.append(part.getProperty().toDotPath().replace(".", "?."));
partBuilder.append("<").append("[").append(parameterIndex++).append("]");
partBuilder.append(")");
break;
case REGEX:
partBuilder.append(" matches ").append("[").append(parameterIndex++).append("]");
break;
case IN:
case CONTAINING:
case NOT_CONTAINING:
case NEGATING_SIMPLE_PROPERTY:
case EXISTS:
default:
throw new InvalidDataAccessApiUsageException(String.format("Found invalid part '%s' in query",
part.getType()));
}
if (partIter.hasNext()) {
partBuilder.append("&&");
}
partCnt++;
}
if (partCnt > 1) {
sb.append("(").append(partBuilder).append(")");
} else {
sb.append(partBuilder);
}
if (orPartIter.hasNext()) {
sb.append("||");
}
}
return PARSER.parseRaw(sb.toString());
}
}

View File

@@ -0,0 +1,135 @@
/*
* 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.support;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.querydsl.QSort;
import org.springframework.util.Assert;
import com.mysema.query.support.Expressions;
import com.mysema.query.types.Expression;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.OrderSpecifier.NullHandling;
import com.mysema.query.types.Path;
import com.mysema.query.types.path.PathBuilder;
/**
* @author Christoph Strobl
* @author Thomas Darimont
*/
abstract class KeyValueQueryDslUtils {
private KeyValueQueryDslUtils() {
// prevent instantiation
}
/**
* Transforms a plain {@link Order} into a QueryDsl specific {@link OrderSpecifier}.
*
* @param sort
* @param builder must not be {@literal null}.
* @return empty {@code OrderSpecifier<?>[]} when sort is {@literal null}.
*/
public static OrderSpecifier<?>[] toOrderSpecifier(Sort sort, PathBuilder<?> builder) {
Assert.notNull(builder, "Builder must not be 'null'.");
if (sort == null) {
return new OrderSpecifier<?>[0];
}
List<OrderSpecifier<?>> specifiers = null;
if (sort instanceof QSort) {
specifiers = ((QSort) sort).getOrderSpecifiers();
} else {
specifiers = new ArrayList<OrderSpecifier<?>>();
for (Order order : sort) {
specifiers.add(toOrderSpecifier(order, builder));
}
}
return specifiers.toArray(new OrderSpecifier<?>[specifiers.size()]);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static OrderSpecifier<?> toOrderSpecifier(Order order, PathBuilder<?> builder) {
return new OrderSpecifier(order.isAscending() ? com.mysema.query.types.Order.ASC
: com.mysema.query.types.Order.DESC, buildOrderPropertyPathFrom(order, builder),
toQueryDslNullHandling(order.getNullHandling()));
}
/**
* Creates an {@link Expression} for the given {@link Order} property.
*
* @param order must not be {@literal null}.
* @param builder must not be {@literal null}.
* @return
*/
private static Expression<?> buildOrderPropertyPathFrom(Order order, PathBuilder<?> builder) {
Assert.notNull(order, "Order must not be null!");
Assert.notNull(builder, "Builder must not be null!");
PropertyPath path = PropertyPath.from(order.getProperty(), builder.getType());
Expression<?> sortPropertyExpression = builder;
while (path != null) {
if (!path.hasNext() && order.isIgnoreCase()) {
// if order is ignore-case we have to treat the last path segment as a String.
sortPropertyExpression = Expressions.stringPath((Path<?>) sortPropertyExpression, path.getSegment()).lower();
} else {
sortPropertyExpression = Expressions.path(path.getType(), (Path<?>) sortPropertyExpression, path.getSegment());
}
path = path.next();
}
return sortPropertyExpression;
}
/**
* Converts the given {@link org.springframework.data.domain.Sort.NullHandling} to the appropriate Querydsl
* {@link NullHandling}.
*
* @param nullHandling must not be {@literal null}.
* @return
*/
private static NullHandling toQueryDslNullHandling(org.springframework.data.domain.Sort.NullHandling nullHandling) {
Assert.notNull(nullHandling, "NullHandling must not be null!");
switch (nullHandling) {
case NULLS_FIRST:
return NullHandling.NullsFirst;
case NULLS_LAST:
return NullHandling.NullsLast;
case NATIVE:
default:
return NullHandling.Default;
}
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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.support;
import static org.springframework.data.querydsl.QueryDslUtils.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.springframework.data.keyvalue.core.KeyValueOperations;
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;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.NamedQueries;
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.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.util.Assert;
import org.springframework.util.ClassUtils;
/**
* {@link RepositoryFactorySupport} specific of handing
* {@link org.springframework.data.keyvalue.repository.KeyValueRepository}.
*
* @author Christoph Strobl
* @since 1.10
*/
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) {
this(keyValueOperations, DEFAULT_QUERY_CREATOR);
}
/**
* 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!");
Assert.notNull(queryCreator, "Query creator type must not be null!");
this.queryCreator = queryCreator;
this.keyValueOperations = keyValueOperations;
this.context = keyValueOperations.getMappingContext();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class)
*/
@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;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata)
*/
@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 SimpleKeyValueRepository(entityInformation, keyValueOperations);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
*/
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return isQueryDslRepository(metadata.getRepositoryInterface()) ? QueryDslKeyValueRepository.class
: SimpleKeyValueRepository.class;
}
/**
* Returns whether the given repository interface requires a QueryDsl specific implementation to be chosen.
*
* @param repositoryInterface must not be {@literal null}.
* @return
*/
private static boolean isQueryDslRepository(Class<?> repositoryInterface) {
return QUERY_DSL_PRESENT && QueryDslPredicateExecutor.class.isAssignableFrom(repositoryInterface);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider)
*/
@Override
protected QueryLookupStrategy getQueryLookupStrategy(Key key, EvaluationContextProvider evaluationContextProvider) {
return new KeyValueQueryLookupStrategy(key, evaluationContextProvider, this.keyValueOperations, this.queryCreator);
}
/**
* @author Christoph Strobl
* @author Oliver Gierke
* @since 1.10
*/
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;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.repository.core.NamedQueries)
*/
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) {
QueryMethod queryMethod = new QueryMethod(method, metadata);
return new KeyValuePartTreeQuery(queryMethod, evaluationContextProvider, this.keyValueOperations,
this.queryCreator);
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.support;
import java.io.Serializable;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.repository.KeyValueRepository;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
/**
* {@link org.springframework.beans.factory.FactoryBean} to create {@link KeyValueRepository}.
*
* @author Christoph Strobl
* @since 1.10
*/
public class KeyValueRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends
RepositoryFactoryBeanSupport<T, S, ID> {
private KeyValueOperations operations;
private Class<? extends AbstractQueryCreator<?, ?>> queryCreator;
public void setKeyValueOperations(KeyValueOperations operations) {
this.operations = operations;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#setMappingContext(org.springframework.data.mapping.context.MappingContext)
*/
@Override
public void setMappingContext(MappingContext<?, ?> mappingContext) {
super.setMappingContext(mappingContext);
}
public void setQueryCreator(Class<? extends AbstractQueryCreator<?, ?>> queryCreator) {
this.queryCreator = queryCreator;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#createRepositoryFactory()
*/
@Override
protected RepositoryFactorySupport createRepositoryFactory() {
return new KeyValueRepositoryFactory(this.operations, this.queryCreator);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
}
}

View File

@@ -0,0 +1,163 @@
/*
* 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.support;
import static org.springframework.data.keyvalue.repository.support.KeyValueQueryDslUtils.*;
import java.io.Serializable;
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;
import com.mysema.query.types.EntityPath;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.Predicate;
import com.mysema.query.types.path.PathBuilder;
/**
* {@link KeyValueRepository} implementation capable of executing {@link Predicate}s using {@link CollQuery}.
*
* @author Christoph Strobl
* @since 1.10
* @param <T>
* @param <ID>
*/
public class QueryDslKeyValueRepository<T, ID extends Serializable> extends SimpleKeyValueRepository<T, ID> implements
QueryDslPredicateExecutor<T> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
/**
* 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);
}
/**
* 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());
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findOne(com.mysema.query.types.Predicate)
*/
@Override
public T findOne(Predicate predicate) {
return prepareQuery(predicate).uniqueResult(builder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate)
*/
@Override
public Iterable<T> findAll(Predicate predicate) {
return prepareQuery(predicate).list(builder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate, com.mysema.query.types.OrderSpecifier[])
*/
@Override
public Iterable<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
ProjectableQuery<?> query = prepareQuery(predicate);
query.orderBy(orders);
return query.list(builder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.Predicate, org.springframework.data.domain.Pageable)
*/
@Override
public Page<T> findAll(Predicate predicate, Pageable pageable) {
ProjectableQuery<?> query = prepareQuery(predicate);
if (pageable != null) {
query.offset(pageable.getOffset());
query.limit(pageable.getPageSize());
if (pageable.getSort() != null) {
query.orderBy(toOrderSpecifier(pageable.getSort(), builder));
}
}
return new PageImpl<T>(query.list(builder), pageable, count(predicate));
}
/*
* (non-Javadoc)
* @see org.springframework.data.querydsl.QueryDslPredicateExecutor#count(com.mysema.query.types.Predicate)
*/
@Override
public long count(Predicate predicate) {
return prepareQuery(predicate).count();
}
/**
* Creates executable query for given {@link Predicate}.
*
* @param predicate
* @return
*/
protected ProjectableQuery<?> prepareQuery(Predicate predicate) {
CollQuery query = new CollQuery();
query.from(builder, findAll());
query.where(predicate);
return query;
}
}

View File

@@ -0,0 +1,214 @@
/*
* 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.support;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.Page;
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 SimpleKeyValueRepository<T, ID extends Serializable> implements KeyValueRepository<T, ID> {
private final KeyValueOperations operations;
private final EntityInformation<T, ID> entityInformation;
/**
* 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, "EntityInformation must not be null!");
Assert.notNull(operations, "KeyValueOperations must not be null!");
this.entityInformation = metadata;
this.operations = operations;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
*/
@Override
public Iterable<T> findAll(Sort sort) {
return operations.findAll(sort, entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Pageable)
*/
@Override
public Page<T> findAll(Pageable pageable) {
if (pageable == null) {
List<T> result = findAll();
return new PageImpl<T>(result, null, result.size());
}
List<T> content = null;
content = operations.findInRange(pageable.getOffset(), pageable.getPageSize(), pageable.getSort(),
entityInformation.getJavaType());
return new PageImpl<T>(content, pageable, this.operations.count(entityInformation.getJavaType()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
*/
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null!");
if (entityInformation.isNew(entity)) {
operations.insert(entity);
} else {
operations.update(entityInformation.getId(entity), entity);
}
return entity;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
*/
@Override
public <S extends T> Iterable<S> save(Iterable<S> entities) {
for (S entity : entities) {
save(entity);
}
return entities;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findOne(java.io.Serializable)
*/
@Override
public T findOne(ID id) {
return operations.findById(id, entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#exists(java.io.Serializable)
*/
@Override
public boolean exists(ID id) {
return findOne(id) != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll()
*/
@Override
public List<T> findAll() {
return operations.findAll(entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
*/
@Override
public Iterable<T> findAll(Iterable<ID> ids) {
List<T> result = new ArrayList<T>();
for (ID id : ids) {
T candidate = findOne(id);
if (candidate != null) {
result.add(candidate);
}
}
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#count()
*/
@Override
public long count() {
return operations.count(entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
*/
@Override
public void delete(ID id) {
operations.delete(id, entityInformation.getJavaType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
*/
@Override
public void delete(T entity) {
delete(entityInformation.getId(entity));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
*/
@Override
public void delete(Iterable<? extends T> entities) {
for (T entity : entities) {
delete(entity);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#deleteAll()
*/
@Override
public void deleteAll() {
operations.delete(entityInformation.getJavaType());
}
}

View File

@@ -0,0 +1,166 @@
/*
* 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.map;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.CollectionFactory;
import org.springframework.data.keyvalue.core.AbstractKeyValueAdapter;
import org.springframework.data.keyvalue.core.KeyValueAdapter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* {@link KeyValueAdapter} implementation for {@link Map}.
*
* @author Christoph Strobl
* @since 1.10
*/
public class MapKeyValueAdapter extends AbstractKeyValueAdapter {
private final Map<Serializable, Map<Serializable, Object>> data;
@SuppressWarnings("rawtypes")//
private final Class<? extends Map> mapType;
/**
* Create new instance of {@link MapKeyValueAdapter} using {@link ConcurrentHashMap}.
*/
public MapKeyValueAdapter() {
this(new ConcurrentHashMap<Serializable, Map<Serializable, Object>>());
}
/**
* Create new instance of {@link MapKeyValueAdapter} using given dataStore for persistence.
*
* @param dataStore must not be {@literal null}.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public MapKeyValueAdapter(Map<Serializable, Map<Serializable, Object>> dataStore) {
Assert.notNull(dataStore, "Cannot initilalize adapter with 'null' datastore.");
this.data = dataStore;
this.mapType = (Class<? extends Map>) ClassUtils.getUserClass(dataStore);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#put(java.io.Serializable, java.lang.Object, java.io.Serializable)
*/
@Override
public Object put(Serializable id, Object item, Serializable keyspace) {
Assert.notNull(id, "Cannot add item with 'null' id.");
Assert.notNull(keyspace, "Cannot add item for 'null' collection.");
return getKeySpaceMap(keyspace).put(id, item);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#contains(java.io.Serializable, java.io.Serializable)
*/
@Override
public boolean contains(Serializable id, Serializable keyspace) {
return get(id, keyspace) != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#get(java.io.Serializable, java.io.Serializable)
*/
@Override
public Object get(Serializable id, Serializable keyspace) {
Assert.notNull(id, "Cannot get item with 'null' id.");
return getKeySpaceMap(keyspace).get(id);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#delete(java.io.Serializable, java.io.Serializable)
*/
@Override
public Object delete(Serializable id, Serializable keyspace) {
Assert.notNull(id, "Cannot delete item with 'null' id.");
return getKeySpaceMap(keyspace).remove(id);
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#getAllOf(java.io.Serializable)
*/
@Override
public Collection<?> getAllOf(Serializable keyspace) {
return getKeySpaceMap(keyspace).values();
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#deleteAllOf(java.io.Serializable)
*/
@Override
public void deleteAllOf(Serializable keyspace) {
getKeySpaceMap(keyspace).clear();
}
/*
* (non-Javadoc)
* @see org.springframework.data.keyvalue.core.KeyValueAdapter#clear()
*/
@Override
public void clear() {
data.clear();
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
@Override
public void destroy() throws Exception {
clear();
}
/**
* Get map associated with given keyspace.
*
* @param keyspace must not be {@literal null}.
* @return
*/
protected Map<Serializable, Object> getKeySpaceMap(Serializable keyspace) {
Assert.notNull(keyspace, "Collection must not be 'null' for lookup.");
Map<Serializable, Object> map = data.get(keyspace);
if (map != null) {
return map;
}
addMapForKeySpace(keyspace);
return data.get(keyspace);
}
private void addMapForKeySpace(Serializable keyspace) {
data.put(keyspace, CollectionFactory.<Serializable, Object> createMap(mapType, 1000));
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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.map;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.CollectionFactory;
import org.springframework.util.Assert;
/**
* @author Christoph Strobl
* @since 1.10
*/
public class MapKeyValueAdapterFactory {
@SuppressWarnings("rawtypes")//
private static final Class<? extends Map> DEFAULT_MAP_TYPE = ConcurrentHashMap.class;
@SuppressWarnings("rawtypes")//
private Class<? extends Map> mapType;
private Map<Serializable, Map<? extends Serializable, ?>> initialValues;
/**
* Creates a new {@link MapKeyValueAdapterFactory}.
*
* @see MapKeyValueAdapterFactory#MapKeyValueAdapterFactory(Class)
*/
public MapKeyValueAdapterFactory() {
this(null);
}
/**
* 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}.
*/
@SuppressWarnings("rawtypes")
public MapKeyValueAdapterFactory(Class<? extends Map> type) {
this.mapType = type;
this.initialValues = new HashMap<Serializable, Map<? extends Serializable, ?>>();
}
/**
* Set values for a given {@literal keyspace} that to populate the adapter after creation.
*
* @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!");
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;
}
/**
* Creates and populates the adapter.
*
* @return
*/
public MapKeyValueAdapter getAdapter() {
MapKeyValueAdapter adapter = createAdapter();
populateAdapter(adapter);
return adapter;
}
private MapKeyValueAdapter createAdapter() {
Class<?> type = this.mapType == null ? DEFAULT_MAP_TYPE : this.mapType;
MapKeyValueAdapter adapter = new MapKeyValueAdapter(
CollectionFactory.<Serializable, Map<Serializable, Object>> createMap(type, 100));
return adapter;
}
private void populateAdapter(MapKeyValueAdapter adapter) {
if (!initialValues.isEmpty()) {
for (Entry<Serializable, Map<? extends Serializable, ?>> entry : initialValues.entrySet()) {
for (Entry<? extends Serializable, ?> obj : entry.getValue().entrySet()) {
adapter.put(obj.getKey(), obj.getValue(), entry.getKey());
}
}
}
}
}