Merge branch 'master' of github.com:SpringSource/spring-data-commons
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -133,7 +133,7 @@
|
||||
<goal>generate-html</goal>
|
||||
<goal>generate-pdf</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<phase>pre-site</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 "";
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
----------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user