DATADOC-48 moving over cross-store code from previous prototype
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user