Merge branch 'master' of github.com:SpringSource/spring-data-commons

This commit is contained in:
Michael Hunger
2011-03-04 02:14:37 +01:00
32 changed files with 868 additions and 109 deletions

View File

@@ -133,7 +133,7 @@
<goal>generate-html</goal>
<goal>generate-pdf</goal>
</goals>
<phase>package</phase>
<phase>pre-site</phase>
</execution>
</executions>
<dependencies>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry including="**/*.aj|**/*.java" kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -1,6 +1,13 @@
#Wed Nov 17 12:20:43 EST 2010
#Tue Mar 01 12:59:14 EST 2011
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
org.eclipse.jdt.core.compiler.compliance=1.5
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.5
org.eclipse.jdt.core.compiler.source=1.6

View File

@@ -81,6 +81,13 @@
<optional>true</optional>
</dependency>
<!-- JPA -->
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
@@ -108,6 +115,11 @@
<name>Springframework Maven SNAPSHOT Repository</name>
<url>http://maven.springframework.org/snapshot</url>
</repository>
<repository>
<id>jboss-repository</id>
<name>JBoss Public Repository</name>
<url>http://repository.jboss.org/nexus/content/groups/public-jboss</url>
</repository>
</repositories>
<build>
<plugins>

View File

@@ -0,0 +1,32 @@
package org.springframework.persistence;
import java.lang.reflect.Field;
/**
* Listener interface for asynchronous storage operations.
* Can be annotated with OnlyOnFailure as an optimization
* if the listener is only interested in compensating transactions
* in the event of write failure.
*
* @author Rod Johnson
*
* @param <V> new value type
*/
public interface AsynchStoreCompletionListener<V> {
/**
* Constant indicating no store completion action
*/
class NONE implements AsynchStoreCompletionListener<Object> {
public void onCompletion(AsynchStoreCompletionListener.StoreResult result, Object newValue, Field foreignStore) {}
}
enum StoreResult {
SUCCESS,
FAILURE,
INDETERMINATE
};
void onCompletion(StoreResult result, V newValue, Field foreignStore);
}

View File

@@ -0,0 +1,78 @@
package org.springframework.persistence;
import java.lang.reflect.Field;
import org.springframework.dao.DataAccessException;
/**
* Interface to be implemented for each persistence technology,
* handling operations for the relevant entity type.
* Parameters: Key=K, Entity class=E
*
* @author Rod Johnson
*/
public interface EntityOperations<K,E> {
/**
* Is this clazz supported by the current EntityOperations?
* @param entityClass
* @param fs ForeignStore annotation, may be null
* @return
*/
boolean supports(Class<?> entityClass, RelatedEntity fs);
/**
* Return null if not found
* @param <T>
* @param entityClass
* @param pk
* @return
* @throws DataAccessException
*/
E findEntity(Class<E> entityClass, K pk) throws DataAccessException;
/**
* Find the unique key for the given entity whose class this EntityOperations
* understands. For example, it might be the id property value.
* @param entity
* @return
* @throws DataAccessException
*/
K findUniqueKey(E entity) throws DataAccessException;
/**
*
* @param entityClass
* @return the type of the unique key for this supported entity
* @throws DataAccessException
*/
Class<?> uniqueKeyType(Class<K> entityClass) throws DataAccessException;
boolean isTransient(E entity) throws DataAccessException;
/**
* Persist. Will cause key to be non-null.
* @param owner Persistent root entity, which has the RelatedEntity field
* @param entity
* @param f Foreign store field for entity being persisted
* @param fs ForeignStore annotation
* @throws DataAccessException
* @return the new unique key
*/
K makePersistent(Object owner, E entity, Field f, RelatedEntity fs) throws DataAccessException;
/**
* Is this type of entity transactional?
* @return
*/
boolean isTransactional();
/**
* Should the field be cached in the entity? For some entity types
* such as streams, there should be no caching, and the value
* should be retrieved from the persistent store every time.
* @return
*/
boolean cacheInEntity();
}

View File

@@ -0,0 +1,38 @@
package org.springframework.persistence;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import org.springframework.dao.DataAccessException;
/**
* Convenient base class for EntityOperations implementations
* that adds ordering support.
* @author Rod Johnson
*
* @param <K>
* @param <E>
*/
public abstract class OrderedEntityOperations<K, E> implements EntityOperations<K, E>, Ordered {
protected final Log log = LogFactory.getLog(getClass());
private int order = Integer.MAX_VALUE;
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
/**
* Convenient default. Subclasses with non-Long key types can override this if they wish.
*/
@Override
public Class<?> uniqueKeyType(Class<K> entityClass) throws DataAccessException {
return Long.class;
}
}

View File

@@ -0,0 +1,65 @@
package org.springframework.persistence;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation indicating that a field may be stored in a foreign store
* and specifying the necessary guarantees. Conceptual rather than
* implementation-specific.
* @see ForeignStoreKeyManager
*
* @author Rod Johnson
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface RelatedEntity {
/**
*
* Optional information as to how to compute or locate the key value.
* Some strategies may take this into account.
*/
String keyExpression() default "";
/**
* Should we use the key of the present entity
* @return
*/
boolean sameKey() default false;
/**
* Policies for persistence
* @return
*/
PersistencePolicy policy() default @PersistencePolicy();
/**
* Name for the preferred data store. Merely a hint. May not be followed.
* @return
*/
String preferredStore() default "";
/**
* Is asynchronous store OK?
* @return
*/
boolean asynchStore() default false;
// TODO - indicates if an asynchronous write should begin
// only after commit of a transaction
boolean afterCommit() default false;
/**
* Completion listener class. Only used if asynchStore is true.
* Must have a no-arg constructor.
* @return
*/
@SuppressWarnings("unchecked")
Class<? extends AsynchStoreCompletionListener> storeCompletionListenerClass() default AsynchStoreCompletionListener.NONE.class;
String storeCompletionListenerBeanName() default "";
}

View File

@@ -0,0 +1,129 @@
package org.springframework.persistence.support;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import org.aspectj.lang.reflect.FieldSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* Aspect that saves field access in a ChangeSet
*
* @author Rod Johnson
* @author Thomas Risberg
*/
public abstract aspect AbstractDeferredUpdateMixinFields<ET extends Annotation> extends AbstractTypeAnnotatingMixinFields<ET, ChangeSetBacked> {
//-------------------------------------------------------------------------
// Configure aspect for whole system.
// init() method can be invoked automatically if the aspect is a Spring
// bean, or called in user code.
//-------------------------------------------------------------------------
// Aspect shared config
private ChangeSetPersister<Object> changeSetPersister;
private ChangeSetSynchronizer<ChangeSetBacked> changeSetManager;
public void setChangeSetConfiguration(ChangeSetConfiguration<Object> changeSetConfiguration) {
this.changeSetPersister = changeSetConfiguration.getChangeSetPersister();
this.changeSetManager = changeSetConfiguration.getChangeSetManager();
}
//-------------------------------------------------------------------------
// Advise user-defined constructors of ChangeSetBacked objects to create a new
// backing ChangeSet
//-------------------------------------------------------------------------
pointcut arbitraryUserConstructorOfChangeSetBackedObject(ChangeSetBacked entity) :
execution((@ET ChangeSetBacked+).new(..)) &&
!execution((@ET ChangeSetBacked+).new(ChangeSet)) &&
this(entity);
// Or could use cflow
pointcut finderConstructorOfChangeSetBackedObject(ChangeSetBacked entity, ChangeSet cs) :
execution((@ET ChangeSetBacked+).new(ChangeSet)) &&
this(entity) &&
args(cs);
before(ChangeSetBacked entity) : arbitraryUserConstructorOfChangeSetBackedObject(entity) {
entity.itdChangeSetPersister = changeSetPersister;
log.info("User-defined constructor called on ChangeSetBacked object of class " + entity.getClass());
// Populate all properties
ChangeSet changeSet = new HashMapChangeSet();
changeSetManager.populateChangeSet(changeSet, entity);
entity.setChangeSet(changeSet);
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
throw new InvalidDataAccessResourceUsageException("No transaction synchronization is active");
}
TransactionSynchronizationManager.registerSynchronization(new ChangedSetBackedTransactionSynchronization(changeSetPersister, entity));
}
before(ChangeSetBacked entity, ChangeSet changeSet) : finderConstructorOfChangeSetBackedObject(entity, changeSet) {
entity.itdChangeSetPersister = changeSetPersister;
changeSetManager.populateEntity(changeSet, entity);
// Now leave an empty ChangeSet to listen only to future changes
entity.setChangeSet(new HashMapChangeSet());
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
throw new InvalidDataAccessResourceUsageException("No transaction synchronization is active");
}
TransactionSynchronizationManager.registerSynchronization(new ChangedSetBackedTransactionSynchronization(changeSetPersister, entity));
}
//-------------------------------------------------------------------------
// ChangeSet-related mixins
//-------------------------------------------------------------------------
// Introduced field
private ChangeSet ChangeSetBacked.changeSet;
private ChangeSetPersister<?> ChangeSetBacked.itdChangeSetPersister;
public void ChangeSetBacked.setChangeSet(ChangeSet cs) {
this.changeSet = cs;
}
public ChangeSet ChangeSetBacked.getChangeSet() {
return changeSet;
}
// Flush the entity state to the persistent store
public void ChangeSetBacked.flush() {
itdChangeSetPersister.persistState(this.getClass(), this.changeSet);
}
public Object ChangeSetBacked.getId() {
return itdChangeSetPersister.getPersistentId(this.getClass(), this.changeSet);
}
//-------------------------------------------------------------------------
// Around advice for field get/set
//-------------------------------------------------------------------------
// Nothing to do on field get unless laziness desired
Object around(ChangeSetBacked entity, Object newVal) : entityFieldSet(entity, newVal) {
Field f = ((FieldSignature) thisJoinPoint.getSignature()).getField();
String propName = f.getName();//getRedisPropertyName(thisJoinPoint.getSignature());
if (newVal instanceof Number) {
log.info("SET " + f + " -> ChangeSet number value property [" + propName + "] with value=[" + newVal + "]");
entity.getChangeSet().set(propName, (Number) newVal);
}
else if (newVal instanceof String) {
log.info("SET " + f + " -> ChangeSet string value property [" + propName + "] with value=[" + newVal + "]");
entity.getChangeSet().set(propName, (String) newVal);
}
else {
log.info("Don't know how to SET " + f + " with value=[" + newVal + "]");
}
return proceed(entity, newVal);
}
}

View File

@@ -0,0 +1,24 @@
package org.springframework.persistence.support;
import java.util.Map;
import org.springframework.core.convert.ConversionService;
/**
* Interface representing the set of changes in an entity.
*
* @author Rod Johnson
* @author Thomas Risberg
*
*/
public interface ChangeSet {
<T> T get(String key, Class<T> requiredClass, ConversionService cs);
void set(String key, Object o);
Map<String, Object> getValues();
Object removeProperty(String k);
}

View File

@@ -0,0 +1,13 @@
package org.springframework.persistence.support;
/**
* Interface introduced to objects exposing ChangeSet information
* @author Rod Johnson
* @author Thomas Risberg
*/
public interface ChangeSetBacked {
ChangeSet getChangeSet();
}

View File

@@ -0,0 +1,28 @@
package org.springframework.persistence.support;
public class ChangeSetConfiguration<T> {
private ChangeSetPersister<T> changeSetPersister;
private ChangeSetSynchronizer<ChangeSetBacked> changeSetManager;
public ChangeSetPersister<T> getChangeSetPersister() {
return changeSetPersister;
}
public void setChangeSetPersister(ChangeSetPersister<T> changeSetPersister) {
this.changeSetPersister = changeSetPersister;
}
public ChangeSetSynchronizer<ChangeSetBacked> getChangeSetManager() {
return changeSetManager;
}
public void setChangeSetManager(
ChangeSetSynchronizer<ChangeSetBacked> changeSetManager) {
this.changeSetManager = changeSetManager;
}
}

View File

@@ -0,0 +1,47 @@
package org.springframework.persistence.support;
import org.springframework.dao.DataAccessException;
/**
* Interface to be implemented by classes that can synchronize
* between data stores and ChangeSets.
* @author Rod Johnson
*
* @param <K> entity key
*/
public interface ChangeSetPersister<K> {
String ID_KEY = "_id";
String CLASS_KEY = "_class";
/**
* TODO how to tell when not found? throw exception?
*/
void getPersistentState(Class<? extends ChangeSetBacked> entityClass, K key, ChangeSet changeSet) throws DataAccessException, NotFoundException;
/**
* Return id
* @param cs
* @return
* @throws DataAccessException
*/
K getPersistentId(Class<? extends ChangeSetBacked> entityClass, ChangeSet cs) throws DataAccessException;
/**
* Return key
* @param cs Key may be null if not persistent
* @return
* @throws DataAccessException
*/
K persistState(Class<? extends ChangeSetBacked> entityClass, ChangeSet cs) throws DataAccessException;
/**
* Exception thrown in alternate control flow if getPersistentState
* finds no entity data.
*/
class NotFoundException extends Exception {
}
}

View File

@@ -0,0 +1,28 @@
package org.springframework.persistence.support;
import java.util.Map;
import org.springframework.dao.DataAccessException;
/**
* Interface to be implemented by classes that can synchronize
* between entities and ChangeSets.
* @author Rod Johnson
*
* @param <E>
*/
public interface ChangeSetSynchronizer<E extends ChangeSetBacked> {
Map<String, Class<?>> persistentFields(Class<? extends E> entityClassClass);
/**
* Take all entity fields into a changeSet.
* @param entity
* @return
* @throws DataAccessException
*/
void populateChangeSet(ChangeSet changeSet, E entity) throws DataAccessException;
void populateEntity(ChangeSet changeSet, E entity) throws DataAccessException;
}

View File

@@ -0,0 +1,66 @@
package org.springframework.persistence.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.support.TransactionSynchronization;
public class ChangedSetBackedTransactionSynchronization implements TransactionSynchronization {
protected final Log log = LogFactory.getLog(getClass());
private ChangeSetPersister<Object> changeSetPersister;
private ChangeSetBacked entity;
private int changeSetTxStatus = -1;
public ChangedSetBackedTransactionSynchronization(ChangeSetPersister<Object> changeSetPersister, ChangeSetBacked entity) {
this.changeSetPersister = changeSetPersister;
this.entity = entity;
}
@Override
public void afterCommit() {
log.debug("After Commit called for " + entity);
changeSetPersister.persistState(entity.getClass(), entity.getChangeSet());
changeSetTxStatus = 0;
}
@Override
public void afterCompletion(int status) {
log.debug("After Completion called with status = " + status);
if (changeSetTxStatus == 0) {
if (status == STATUS_COMMITTED) {
// this is good
log.debug("ChangedSetBackedTransactionSynchronization completed successfully for " + this.entity);
}
else {
// this could be bad - TODO: compensate
log.error("ChangedSetBackedTransactionSynchronization failed for " + this.entity);
}
}
}
@Override
public void beforeCommit(boolean readOnly) {
}
@Override
public void beforeCompletion() {
}
@Override
public void flush() {
}
@Override
public void resume() {
throw new IllegalStateException("ChangedSetBackedTransactionSynchronization does not support transaction suspension currently.");
}
@Override
public void suspend() {
throw new IllegalStateException("ChangedSetBackedTransactionSynchronization does not support transaction suspension currently.");
}
}

View File

@@ -0,0 +1,51 @@
package org.springframework.persistence.support;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.core.convert.ConversionService;
/**
* Simple ChangeSet implementation backed by a HashMap.
* @author Thomas Risberg
* @author Rod Johnson
*/
public class HashMapChangeSet implements ChangeSet {
private Map<String, Object> values;
public HashMapChangeSet(Map<String,Object> values) {
this.values = values;
}
public HashMapChangeSet() {
this(new HashMap<String, Object>());
}
@Override
public void set(String key, Object o) {
values.put(key, o);
}
@Override
public String toString() {
return "HashMapChangeSet: values=[" + values + "]";
}
@Override
public Map<String, Object> getValues() {
return Collections.unmodifiableMap(values);
}
@Override
public Object removeProperty(String k) {
return this.values.remove(k);
}
@Override
public <T> T get(String key, Class<T> requiredClass, ConversionService conversionService) {
return conversionService.convert(values.get(key), requiredClass);
}
}

View File

@@ -0,0 +1,96 @@
package org.springframework.persistence.support;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.dao.DataAccessException;
import org.springframework.persistence.RelatedEntity;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;
/**
* Synchronizes fields to ChangeSets, regardless of visibility.
*
* @author Rod Johnson
*/
public class SimpleReflectiveChangeSetSynchronizer implements ChangeSetSynchronizer<ChangeSetBacked> {
/**
* Filter matching infrastructure fields, so they can be excluded
*/
private static FieldFilter PERSISTABLE_FIELDS = new FieldFilter() {
@Override
public boolean matches(Field f) {
return !(
f.isSynthetic() ||
Modifier.isStatic(f.getModifiers()) ||
Modifier.isTransient(f.getModifiers()) ||
f.getName().startsWith("ajc$") ||
f.isAnnotationPresent(RelatedEntity.class)
);
}
};
private final Log log = LogFactory.getLog(getClass());
private final ConversionService conversionService;
@Autowired
public SimpleReflectiveChangeSetSynchronizer(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public Map<String, Class<?>> persistentFields(Class<? extends ChangeSetBacked> entityClass) {
final Map<String, Class<?>> fields = new HashMap<String, Class<?>>();
ReflectionUtils.doWithFields(entityClass, new FieldCallback() {
@Override
public void doWith(Field f) throws IllegalArgumentException, IllegalAccessException {
fields.put(f.getName(), f.getType());
}
}, PERSISTABLE_FIELDS);
return fields;
}
@Override
public void populateChangeSet(final ChangeSet changeSet, final ChangeSetBacked entity) throws DataAccessException {
ReflectionUtils.doWithFields(entity.getClass(), new FieldCallback() {
@Override
public void doWith(Field f) throws IllegalArgumentException, IllegalAccessException {
f.setAccessible(true);
if (log.isDebugEnabled()) {
log.debug("POPULATE ChangeSet value from entity field: " + f);
}
changeSet.set(f.getName(), f.get(entity));
}
}, PERSISTABLE_FIELDS);
String classShortName = ClassUtils.getShortName(entity.getClass());
changeSet.set("_class", classShortName);
}
@Override
public void populateEntity(final ChangeSet changeSet, final ChangeSetBacked entity) throws DataAccessException {
ReflectionUtils.doWithFields(entity.getClass(), new FieldCallback() {
@Override
public void doWith(Field f) throws IllegalArgumentException, IllegalAccessException {
if (changeSet.getValues().containsKey(f.getName())) {
f.setAccessible(true);
if (log.isDebugEnabled()) {
log.debug("POPULATE entity from ChangeSet for field: " + f);
}
Object val = changeSet.get(f.getName(), f.getType(), conversionService);
f.set(entity, val);
}
}
}, PERSISTABLE_FIELDS);
}
}

View File

@@ -4,13 +4,15 @@ Bundle-Vendor: SpringSource
Bundle-ManifestVersion: 2
Import-Package:
sun.reflect;version="0";resolution:=optional
Excluded-Imports:
org.springframework.persistence
Import-Template:
org.springframework.beans.*;version="[3.0.0, 4.0.0)",
org.springframework.core.*;version="[3.0.0, 4.0.0)",
org.springframework.dao.*;version="[3.0.0, 4.0.0)",
org.springframework.transaction..*;version="[3.0.0, 4.0.0)",
org.springframework.util.*;version="[3.0.0, 4.0.0)",
org.springframework.data.core.*;version="[1.0.0, 2.0.0)",
org.springframework.data.persistence.*;version="[1.0.0, 2.0.0)",
org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional,
org.apache.commons.logging.*;version="[1.1.1, 2.0.0)",
org.aspectj.*;version="[1.6.5, 2.0.0)",

View File

@@ -49,6 +49,19 @@ public interface ParameterAccessor extends Iterable<Object> {
Sort getSort();
/**
* Returns the bindable value with the given index. Bindable means, that
* {@link Pageable} and {@link Sort} values are skipped without noticed in
* the index. For a method signature taking {@link String}, {@link Pageable}
* , {@link String}, {@code #getBindableParameter(1)} would return the
* second {@link String} value.
*
* @param index
* @return
*/
Object getBindableValue(int index);
/**
* Returns an iterator over all <em>bindable</em> parameters. This means
* parameters implementing {@link Pageable} or {@link Sort} will not be

View File

@@ -89,7 +89,14 @@ public class ParametersParameterAccessor implements ParameterAccessor {
}
private Object getBindableValue(int index) {
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.repository.query.ParameterAccessor#getBindableValue
* (int)
*/
public Object getBindableValue(int index) {
return values[parameters.getBindableParameter(index).getIndex()];
}

View File

@@ -54,7 +54,8 @@ public interface QueryLookupStrategy {
* that can be executed afterwards.
*
* @param method
* @param domainClass
* @return
*/
RepositoryQuery resolveQuery(Method method);
RepositoryQuery resolveQuery(Method method, Class<?> domainClass);
}

View File

@@ -62,7 +62,10 @@ public abstract class AbstractQueryCreator<T, S> {
*/
public T createQuery() {
return complete(createCriteria(tree), tree.getSort());
Sort treeSort = tree.getSort();
Sort sort = treeSort != null ? treeSort : parameters.getSort();
return complete(createCriteria(tree), sort);
}

View File

@@ -113,6 +113,18 @@ public class Part {
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("%s %s", property.getName(), type);
}
/**
* @return the type
*/

View File

@@ -27,6 +27,7 @@ import java.util.regex.Pattern;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.parser.PartTree.OrPart;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -179,6 +180,20 @@ public class PartTree implements Iterable<OrPart> {
return group != null && group.contains(DISTINCT);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("%s %s",
StringUtils.collectionToDelimitedString(nodes, " or "),
orderBySource.toString());
}
/**
* A part of the parsed source that results from splitting up the resource
* ar {@literal Or} keywords. Consists of {@link Part}s that have to be
@@ -209,6 +224,18 @@ public class PartTree implements Iterable<OrPart> {
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return StringUtils.collectionToDelimitedString(children, " and ");
}
/*
* (non-Javadoc)
*

View File

@@ -26,17 +26,17 @@ import org.springframework.data.domain.Persistable;
* @author Oliver Gierke
*/
@SuppressWarnings("rawtypes")
public class PersistableEntityMetadata extends
AbstractEntityMetadata<Persistable> {
public class PersistableEntityMetadata<T extends Persistable> extends
AbstractEntityMetadata<T> {
/**
* Creates a new {@link PersistableEntityMetadata}.
*
* @param domainClass
*/
public PersistableEntityMetadata() {
public PersistableEntityMetadata(Class<T> domainClass) {
super(Persistable.class);
super(domainClass);
}
@@ -48,7 +48,7 @@ public class PersistableEntityMetadata extends
* .Object)
*/
@Override
public boolean isNew(Persistable entity) {
public boolean isNew(T entity) {
return entity.isNew();
}
@@ -61,7 +61,7 @@ public class PersistableEntityMetadata extends
* org.springframework.data.repository.support.IdAware#getId(java.lang.Object
* )
*/
public Object getId(Persistable entity) {
public Object getId(T entity) {
return entity.getId();
}

View File

@@ -216,7 +216,10 @@ public abstract class RepositoryFactorySupport {
getQueryLookupStrategy(queryLookupStrategyKey);
for (Method method : metadata.getQueryMethods()) {
queries.put(method, lookupStrategy.resolveQuery(method));
queries.put(
method,
lookupStrategy.resolveQuery(method,
repositoryInterface.getDomainClass()));
}
}

View File

@@ -22,6 +22,7 @@ import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import org.springframework.data.domain.Page;
import org.springframework.data.repository.Repository;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@@ -52,15 +53,18 @@ public abstract class ClassUtils {
*/
public static Class<?> getReturnedDomainClass(Method method) {
Type type = method.getGenericReturnType();
Class<?> returnType = method.getReturnType();
if (Collection.class.isAssignableFrom(returnType)
|| Page.class.isAssignableFrom(returnType)) {
Type type = method.getGenericReturnType();
if (type instanceof ParameterizedType) {
return (Class<?>) ((ParameterizedType) type)
.getActualTypeArguments()[0];
} else {
return method.getReturnType();
}
return returnType;
}

View File

@@ -82,6 +82,17 @@ public class PartTreeUnitTests {
}
@Test
public void parsesCombinedAndAndOrPropertiesCorrectly() throws Exception {
PartTree tree =
new PartTree("firstnameAndLastnameOrLastname", User.class);
assertPart(tree, new Part[] { new Part("firstname", User.class),
new Part("lastname", User.class) }, new Part[] { new Part(
"lastname", User.class) });
}
@Test
public void hasSortIfOrderByIsGiven() throws Exception {

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2008-2010 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.repository.support;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.domain.Persistable;
/**
* Unit test for {@link PersistableEntityMetadata}.
*
* @author Oliver Gierke
*/
public class PersistableEntityInformationTests {
@Test
public void detectsPersistableCorrectly() throws Exception {
PersistableEntityMetadata info = new PersistableEntityMetadata();
assertNewAndNoId(info, new PersistableEntity(null));
assertNotNewAndId(info, new PersistableEntity(1L), 1L);
}
@SuppressWarnings("rawtypes")
private <S extends EntityMetadata<Persistable>> void assertNewAndNoId(
S info, Persistable entity) {
assertThat(info.isNew(entity), is(true));
assertThat(info.getId(entity), is(nullValue()));
}
@SuppressWarnings("rawtypes")
private <S extends EntityMetadata<Persistable>> void assertNotNewAndId(
S info, Persistable entity, Object id) {
assertThat(info.isNew(entity), is(false));
assertThat(info.getId(entity), is(id));
}
static class PersistableEntity implements Persistable<Long> {
private static final long serialVersionUID = -5898780128204716452L;
private final Long id;
public PersistableEntity(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public boolean isNew() {
return id == null;
}
}
}

View File

@@ -34,15 +34,15 @@ import org.springframework.data.domain.Persistable;
@RunWith(MockitoJUnitRunner.class)
public class PersistableEntityMetadataUnitTests {
static final PersistableEntityMetadata metadata =
new PersistableEntityMetadata();
@SuppressWarnings("rawtypes")
static final PersistableEntityMetadata<Persistable> metadata =
new PersistableEntityMetadata<Persistable>(Persistable.class);
@Mock
Persistable<Long> persistable;
@Test
@SuppressWarnings("serial")
public void usesPersistablesGetId() throws Exception {
when(persistable.getId()).thenReturn(2L, 1L, 3L);
@@ -59,4 +59,30 @@ public class PersistableEntityMetadataUnitTests {
assertThat(metadata.isNew(persistable), is(true));
assertThat(metadata.isNew(persistable), is(false));
}
@Test
public void returnsGivenClassAsEntityType() throws Exception {
PersistableEntityMetadata<PersistableEntity> info =
new PersistableEntityMetadata<PersistableEntity>(
PersistableEntity.class);
assertEquals(PersistableEntity.class, info.getJavaType());
}
@SuppressWarnings("serial")
static class PersistableEntity implements Persistable<Long> {
public Long getId() {
return null;
}
public boolean isNew() {
return false;
}
}
}

View File

@@ -41,6 +41,17 @@ public class ClassUtilsUnitTests {
}
@Test
public void determinesReturnType() throws Exception {
assertEquals(User.class,
getReturnedDomainClass(SomeDao.class.getMethod(
"findByFirstname", Pageable.class, String.class)));
assertEquals(GenericType.class,
getReturnedDomainClass(SomeDao.class.getMethod("someMethod")));
}
@Test
public void determinesValidFieldsCorrectly() {
@@ -79,5 +90,12 @@ public class ClassUtilsUnitTests {
private interface SomeDao extends Serializable, UserRepository {
Page<User> findByFirstname(Pageable pageable, String firstname);
GenericType<User> someMethod();
}
private class GenericType<T> {
}
}

View File

@@ -9,6 +9,7 @@ Repository
* Added support for 'Distinct' keyword in finder method names (DATACMNS-15)
* Added support for 'In' and 'NotIn' keywords (DATACMNS-16)
* Introduced metamodel for entities and repositories (DATACMNS-17)
* Fixed returning wrong class PersistableEntityMetadata(DATACMNS-19)
Changes in version 1.0.0.M3 (2011-02-09)
----------------------------------------