diff --git a/spring-data-commons-aspects/.classpath b/spring-data-commons-aspects/.classpath index 16f01e2ee..19ed14911 100644 --- a/spring-data-commons-aspects/.classpath +++ b/spring-data-commons-aspects/.classpath @@ -1,7 +1,7 @@ - - + + diff --git a/spring-data-commons-aspects/.settings/org.eclipse.jdt.core.prefs b/spring-data-commons-aspects/.settings/org.eclipse.jdt.core.prefs index e78b07017..c4dacddd4 100644 --- a/spring-data-commons-aspects/.settings/org.eclipse.jdt.core.prefs +++ b/spring-data-commons-aspects/.settings/org.eclipse.jdt.core.prefs @@ -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 diff --git a/spring-data-commons-aspects/pom.xml b/spring-data-commons-aspects/pom.xml index 7d0c99b3a..9bbc0d239 100644 --- a/spring-data-commons-aspects/pom.xml +++ b/spring-data-commons-aspects/pom.xml @@ -81,6 +81,13 @@ true + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + 1.0.0.Final + + org.mockito mockito-all @@ -108,6 +115,11 @@ Springframework Maven SNAPSHOT Repository http://maven.springframework.org/snapshot + + jboss-repository + JBoss Public Repository + http://repository.jboss.org/nexus/content/groups/public-jboss + diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/AsynchStoreCompletionListener.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/AsynchStoreCompletionListener.java new file mode 100644 index 000000000..4f417bb57 --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/AsynchStoreCompletionListener.java @@ -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 new value type + */ +public interface AsynchStoreCompletionListener { + + /** + * Constant indicating no store completion action + */ + class NONE implements AsynchStoreCompletionListener { + public void onCompletion(AsynchStoreCompletionListener.StoreResult result, Object newValue, Field foreignStore) {} + } + + enum StoreResult { + SUCCESS, + FAILURE, + INDETERMINATE + }; + + void onCompletion(StoreResult result, V newValue, Field foreignStore); + +} diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/EntityOperations.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/EntityOperations.java new file mode 100644 index 000000000..df6aa2f49 --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/EntityOperations.java @@ -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 { + + /** + * 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 + * @param entityClass + * @param pk + * @return + * @throws DataAccessException + */ + E findEntity(Class 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 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(); + +} diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/OrderedEntityOperations.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/OrderedEntityOperations.java new file mode 100644 index 000000000..e1cf11a83 --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/OrderedEntityOperations.java @@ -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 + * @param + */ +public abstract class OrderedEntityOperations implements EntityOperations, 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 entityClass) throws DataAccessException { + return Long.class; + } +} \ No newline at end of file diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/RelatedEntity.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/RelatedEntity.java new file mode 100644 index 000000000..f9e538c82 --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/RelatedEntity.java @@ -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 storeCompletionListenerClass() default AsynchStoreCompletionListener.NONE.class; + + String storeCompletionListenerBeanName() default ""; + +} diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/AbstractDeferredUpdateMixinFields.aj b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/AbstractDeferredUpdateMixinFields.aj new file mode 100644 index 000000000..bdfe6ce5b --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/AbstractDeferredUpdateMixinFields.aj @@ -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 extends AbstractTypeAnnotatingMixinFields { + + //------------------------------------------------------------------------- + // 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 changeSetPersister; + + private ChangeSetSynchronizer changeSetManager; + + public void setChangeSetConfiguration(ChangeSetConfiguration 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); + } + +} \ No newline at end of file diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSet.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSet.java new file mode 100644 index 000000000..2fa0ff0d6 --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSet.java @@ -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 get(String key, Class requiredClass, ConversionService cs); + + void set(String key, Object o); + + Map getValues(); + + Object removeProperty(String k); + +} diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetBacked.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetBacked.java new file mode 100644 index 000000000..cf1994e74 --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetBacked.java @@ -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(); + +} diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetConfiguration.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetConfiguration.java new file mode 100644 index 000000000..3bdd97f1b --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetConfiguration.java @@ -0,0 +1,28 @@ +package org.springframework.persistence.support; + +public class ChangeSetConfiguration { + + private ChangeSetPersister changeSetPersister; + + private ChangeSetSynchronizer changeSetManager; + + public ChangeSetPersister getChangeSetPersister() { + return changeSetPersister; + } + + public void setChangeSetPersister(ChangeSetPersister changeSetPersister) { + this.changeSetPersister = changeSetPersister; + } + + public ChangeSetSynchronizer getChangeSetManager() { + return changeSetManager; + } + + public void setChangeSetManager( + ChangeSetSynchronizer changeSetManager) { + this.changeSetManager = changeSetManager; + } + + + +} diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetPersister.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetPersister.java new file mode 100644 index 000000000..2f63414b2 --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetPersister.java @@ -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 entity key + */ +public interface ChangeSetPersister { + + String ID_KEY = "_id"; + + String CLASS_KEY = "_class"; + + /** + * TODO how to tell when not found? throw exception? + */ + void getPersistentState(Class entityClass, K key, ChangeSet changeSet) throws DataAccessException, NotFoundException; + + /** + * Return id + * @param cs + * @return + * @throws DataAccessException + */ + K getPersistentId(Class entityClass, ChangeSet cs) throws DataAccessException; + + /** + * Return key + * @param cs Key may be null if not persistent + * @return + * @throws DataAccessException + */ + K persistState(Class entityClass, ChangeSet cs) throws DataAccessException; + + /** + * Exception thrown in alternate control flow if getPersistentState + * finds no entity data. + */ + class NotFoundException extends Exception { + + } + +} diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetSynchronizer.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetSynchronizer.java new file mode 100644 index 000000000..098365e1a --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangeSetSynchronizer.java @@ -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 + */ +public interface ChangeSetSynchronizer { + + Map> persistentFields(Class 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; + +} diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangedSetBackedTransactionSynchronization.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangedSetBackedTransactionSynchronization.java new file mode 100644 index 000000000..82cf1c3a6 --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/ChangedSetBackedTransactionSynchronization.java @@ -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 changeSetPersister; + + private ChangeSetBacked entity; + + private int changeSetTxStatus = -1; + + public ChangedSetBackedTransactionSynchronization(ChangeSetPersister 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."); + } + +} diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/HashMapChangeSet.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/HashMapChangeSet.java new file mode 100644 index 000000000..e9fe86104 --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/HashMapChangeSet.java @@ -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 values; + + public HashMapChangeSet(Map values) { + this.values = values; + } + + public HashMapChangeSet() { + this(new HashMap()); + } + + @Override + public void set(String key, Object o) { + values.put(key, o); + } + + @Override + public String toString() { + return "HashMapChangeSet: values=[" + values + "]"; + } + + @Override + public Map getValues() { + return Collections.unmodifiableMap(values); + } + + @Override + public Object removeProperty(String k) { + return this.values.remove(k); + } + + @Override + public T get(String key, Class requiredClass, ConversionService conversionService) { + return conversionService.convert(values.get(key), requiredClass); + } + +} diff --git a/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/SimpleReflectiveChangeSetSynchronizer.java b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/SimpleReflectiveChangeSetSynchronizer.java new file mode 100644 index 000000000..43b6f6b51 --- /dev/null +++ b/spring-data-commons-aspects/src/main/java/org/springframework/persistence/support/SimpleReflectiveChangeSetSynchronizer.java @@ -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 { + + /** + * 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> persistentFields(Class entityClass) { + final Map> fields = new HashMap>(); + 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); + } + +} diff --git a/spring-data-commons-aspects/template.mf b/spring-data-commons-aspects/template.mf index 1acd51461..84c7abd31 100644 --- a/spring-data-commons-aspects/template.mf +++ b/spring-data-commons-aspects/template.mf @@ -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)",