24 changed files with 1151 additions and 2 deletions
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.LinkedList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.core.OrderComparator; |
||||
import org.springframework.dao.DataAccessException; |
||||
|
||||
/** |
||||
* Chaining implementation of entity operations that automatically configures itself |
||||
* from a Spring context if available. |
||||
* |
||||
* @author Rod Johnson |
||||
*/ |
||||
public class ChainingEntityOperationsLocator implements EntityOperationsLocator { |
||||
|
||||
private List<EntityOperations> entityOperationsList = new LinkedList<EntityOperations>(); |
||||
|
||||
@Autowired |
||||
public void init(ApplicationContext context) { |
||||
Map<String, EntityOperations> beansOfType = context.getBeansOfType(EntityOperations.class); |
||||
List<EntityOperations> l = new LinkedList<EntityOperations>(); |
||||
for (EntityOperations eo : beansOfType.values()) { |
||||
l.add(eo); |
||||
} |
||||
Collections.sort(l, new OrderComparator()); |
||||
for (EntityOperations eo : l) { |
||||
add(eo); |
||||
} |
||||
} |
||||
|
||||
public void add(EntityOperations ef) { |
||||
entityOperationsList.add(ef); |
||||
} |
||||
|
||||
@Override |
||||
public <T> EntityOperations<?,T> entityOperationsFor(Class<T> entityClass, RelatedEntity fs) |
||||
throws DataAccessException { |
||||
for (EntityOperations eo : entityOperationsList) { |
||||
if (eo.supports(entityClass, fs)) { |
||||
return eo; |
||||
} |
||||
} |
||||
throw new UnknownEntityClassException(entityClass); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.util.Collections; |
||||
import java.util.LinkedList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.core.OrderComparator; |
||||
import org.springframework.dao.DataAccessException; |
||||
|
||||
/** |
||||
* Chaining implementation of ForeignStoreKeyManagerLocator that can be parameterized |
||||
* from a Spring ApplicationContext. |
||||
* |
||||
* @author Rod Johnson |
||||
* |
||||
*/ |
||||
public class ChainingForeignStoreKeyManagerLocator implements ForeignStoreKeyManagerLocator { |
||||
|
||||
private List<ForeignStoreKeyManager> delegates = new LinkedList<ForeignStoreKeyManager>(); |
||||
|
||||
public void add(ForeignStoreKeyManager fskm) { |
||||
delegates.add(fskm); |
||||
} |
||||
|
||||
@Autowired |
||||
public void init(ApplicationContext context) { |
||||
Map<String, ForeignStoreKeyManager> beansOfType = context.getBeansOfType(ForeignStoreKeyManager.class); |
||||
List<ForeignStoreKeyManager> l = new LinkedList<ForeignStoreKeyManager>(); |
||||
for (ForeignStoreKeyManager fskm : beansOfType.values()) { |
||||
l.add(fskm); |
||||
} |
||||
Collections.sort(l, new OrderComparator()); |
||||
for (ForeignStoreKeyManager fskm : l) { |
||||
add(fskm); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public <T> ForeignStoreKeyManager<T> foreignStoreKeyManagerFor(Class<T> entityClass, Field f) throws DataAccessException { |
||||
for (ForeignStoreKeyManager fskm : delegates) { |
||||
if (fskm.isSupportedField(entityClass, f)) { |
||||
return fskm; |
||||
} |
||||
} |
||||
throw new IllegalArgumentException("No ForeignStoreKeyManager for " + entityClass + " on " + f); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Method; |
||||
|
||||
import javax.persistence.Entity; |
||||
import javax.persistence.EntityManager; |
||||
import javax.persistence.PersistenceContext; |
||||
|
||||
import org.springframework.dao.DataAccessException; |
||||
|
||||
/** |
||||
* Implementation of entity operations that works on any entity |
||||
* that adheres to Roo persist() and static finder conventions. |
||||
* Does not depend on Roo, merely on Roo conventions, which can |
||||
* also be implemented by hand. |
||||
* |
||||
* @author Rod Johnson |
||||
*/ |
||||
public class EntityManagerJpaEntityOperations extends OrderedEntityOperations { |
||||
|
||||
@PersistenceContext |
||||
private EntityManager entityManager; |
||||
|
||||
public static Object invoke(Class<?> clazz, String methodName, |
||||
Object target, Class<?>[] argTypes, Object... args) { |
||||
try { |
||||
Method m = clazz.getMethod(methodName, argTypes); |
||||
return m.invoke(target, (Object[]) args); |
||||
} catch (Exception ex) { |
||||
// TODO FIX ME
|
||||
// System.out.println(ex + ": checked exceptions are stupid");
|
||||
throw new IllegalArgumentException(ex); |
||||
} |
||||
} |
||||
|
||||
public static Object invokeNoArgMethod(Class<?> clazz, String methodName, Object target) { |
||||
return invoke(clazz, methodName, target, (Class<?>[]) null, |
||||
(Object[]) null); |
||||
} |
||||
|
||||
@Override |
||||
public Object findEntity(Class entityClass, Object pk) |
||||
throws DataAccessException { |
||||
String findMethod = "find" + entityClass.getSimpleName(); |
||||
Object found = entityManager.find(entityClass, pk); |
||||
log.info("Lookup [" + entityClass.getName() + "] by pk=[" + pk + "] using EntityManager.find() - found [" + found + "]"); |
||||
return found; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Object findUniqueKey(Object entity) throws DataAccessException { |
||||
String idMethodName = "getId"; |
||||
return invokeNoArgMethod(entity.getClass(), idMethodName, entity); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isTransient(Object entity) throws DataAccessException { |
||||
return findUniqueKey(entity) == null; |
||||
} |
||||
|
||||
@Override |
||||
public Object makePersistent(Object owner, Object entity, Field f, RelatedEntity fs) throws DataAccessException { |
||||
if (log.isDebugEnabled()) { |
||||
log.debug("Making entity persistent: BEFORE [" + entity + "]"); |
||||
} |
||||
entityManager.persist(entity); |
||||
Object key = findUniqueKey(entity); |
||||
log.info("Making entity persistent: AFTER [" + entity + "]"); |
||||
return key; |
||||
} |
||||
|
||||
@Override |
||||
public boolean supports(Class entityClass, RelatedEntity fs) { |
||||
return entityClass.isAnnotationPresent(Entity.class); |
||||
} |
||||
|
||||
@Override |
||||
public boolean cacheInEntity() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isTransactional() { |
||||
// TODO Need to have a better test
|
||||
return true; |
||||
}} |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import org.springframework.dao.DataAccessException; |
||||
|
||||
/** |
||||
* Interface to be implemented by classes that can find EntityOperations |
||||
* implementations to handle particular classes. |
||||
* |
||||
* @author Rod Johnson |
||||
*/ |
||||
public interface EntityOperationsLocator { |
||||
|
||||
/** |
||||
* Find the EntityOperations for this class. |
||||
* @param fs ForeignStore annotation (may be null) |
||||
* @param entityClass |
||||
* @return |
||||
* @throws DataAccessException if no EntityOperations can be found. |
||||
*/ |
||||
<T> EntityOperations<?,T> entityOperationsFor(Class<T> entityClass, RelatedEntity fs) throws DataAccessException; |
||||
|
||||
} |
||||
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.dao.DataAccessException; |
||||
|
||||
/** |
||||
* Interface to be implemented to infer or compute foreign store |
||||
* key values and possibly store them. |
||||
* |
||||
* @author Rod Johnson |
||||
* |
||||
*/ |
||||
public interface ForeignStoreKeyManager<T> { |
||||
|
||||
/** |
||||
* Is this entity class one we can store additional |
||||
* state in related to fields annotated with ForeignStore. |
||||
* @param entityClass |
||||
* @param foreignStore |
||||
* @return |
||||
*/ |
||||
boolean isSupportedField(Class<T> entityClass, Field foreignStore); |
||||
|
||||
/** |
||||
* |
||||
* @param entity |
||||
* @param foreignStore |
||||
* @return null if not yet persistent |
||||
* @throws DataAccessException if the key cannot be computed or stored |
||||
*/ |
||||
<K> K findForeignStoreKey(T entity, Field foreignStore, Class<K> keyClass) throws DataAccessException; |
||||
|
||||
/** |
||||
* Can be a NOP if the key is inferred |
||||
* @param entity |
||||
* @param foreignStore |
||||
* @param pk |
||||
* @throws DataAccessException |
||||
*/ |
||||
void storeForeignStoreKey(T entity, Field foreignStore, Object pk) throws DataAccessException; |
||||
|
||||
/** |
||||
* Clear out the foreign key value |
||||
* Can be a NOP if the key is inferred |
||||
* @param entity |
||||
* @param foreignStore |
||||
* @param keyClass class of the key |
||||
* @throws DataAccessException |
||||
*/ |
||||
void clearForeignStoreKey(T entity, Field foreignStore, Class<?> keyClass) throws DataAccessException; |
||||
|
||||
<K> Set<K> findForeignStoreKeySet(T entity, Field foreignStore, Class<K> keyClass) throws DataAccessException; |
||||
|
||||
void storeForeignStoreKeySet(T entity, Field foreignStore, Set<Object> keys) throws DataAccessException; |
||||
|
||||
} |
||||
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.lang.reflect.Field; |
||||
|
||||
import org.springframework.dao.DataAccessException; |
||||
|
||||
/** |
||||
* Interface to be implemented by classes that can find ForeignStoreKeyManager |
||||
* implementations to handle particular entities. |
||||
* |
||||
* @author Rod Johnson |
||||
*/ |
||||
public interface ForeignStoreKeyManagerLocator { |
||||
|
||||
/** |
||||
* Find the ForeignStoreKeyManager for this class. |
||||
* @param f field the RelatedEntity annotation is on |
||||
* @param entityClass |
||||
* @return |
||||
* @throws DataAccessException if no ForeignStoreKeyManager can be found. |
||||
*/ |
||||
<T> ForeignStoreKeyManager<T> foreignStoreKeyManagerFor(Class<T> entityClass, Field f) throws DataAccessException; |
||||
|
||||
} |
||||
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.lang.reflect.Field; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.dao.DataAccessException; |
||||
|
||||
/** |
||||
* Stores keys in generated additional persistent fields |
||||
* that Roo will add. e.g. |
||||
* |
||||
* <pre> |
||||
* atForeignStore |
||||
* Person Person; |
||||
* |
||||
* long person_id; |
||||
* |
||||
* </pre> |
||||
* @author |
||||
* |
||||
*/ |
||||
public class GeneratedFieldForeignStoreKeyManager extends |
||||
OrderedForeignStoreKeyManager<Roo_GeneratedForeignStoreKeys> { |
||||
|
||||
private final Log log = LogFactory.getLog(getClass()); |
||||
|
||||
|
||||
@Override |
||||
public Object findForeignStoreKey(Roo_GeneratedForeignStoreKeys entity, Field foreignStore, Class requiredClass) |
||||
throws DataAccessException { |
||||
String methodName = "get" + propertyName(foreignStore); |
||||
Object key = RooConventionEntityOperations.invokeNoArgMethod(entity.getClass(), methodName, entity); |
||||
log.info("FIND foreign store property " + foreignStore + " <- Entity generated String property [" + methodName + "] returned [" + key + "]"); |
||||
return key; |
||||
} |
||||
|
||||
@Override |
||||
public void storeForeignStoreKey(Roo_GeneratedForeignStoreKeys entity, Field foreignStore, |
||||
Object key) throws DataAccessException { |
||||
String methodName = "set" + propertyName(foreignStore); |
||||
RooConventionEntityOperations.invoke(entity.getClass(), methodName, entity, new Class<?>[] { key.getClass()}, key); |
||||
log.info("STORE foreign store property " + foreignStore + " -> Entity generated String property [" + methodName + "] with key value [" + key + "]"); |
||||
} |
||||
|
||||
@Override |
||||
public void clearForeignStoreKey(Roo_GeneratedForeignStoreKeys entity, Field foreignStore, Class keyClass) throws DataAccessException { |
||||
String methodName = "set" + propertyName(foreignStore); |
||||
RooConventionEntityOperations.invoke(entity.getClass(), methodName, entity, new Class<?>[] { keyClass }, null); |
||||
log.info("CKEAR foreign store property " + foreignStore + " -> Entity generated String property [" + methodName + "]"); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean isSupportedField(Class clazz, Field f) { |
||||
// Check for marker interface
|
||||
return Roo_GeneratedForeignStoreKeys.class.isAssignableFrom(clazz); |
||||
} |
||||
|
||||
|
||||
private String propertyName(Field f) { |
||||
return "_" + f.getName() + "_Id"; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.lang.reflect.Field; |
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException; |
||||
|
||||
/** |
||||
* Exception thrown on an attempt to use a field with an invalid |
||||
* RelatedEntity annotation. |
||||
* |
||||
* @author Rod Johnson |
||||
*/ |
||||
public class InvalidFieldAnnotationException extends |
||||
InvalidDataAccessApiUsageException { |
||||
|
||||
public InvalidFieldAnnotationException(Class<?> entityClass, Field f, String reason) { |
||||
super("Field [" + f.getName() + "] has invalid RelatedEntity annotation: reason='" + reason + "'", null); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import javax.persistence.Transient; |
||||
import javax.persistence.Entity; |
||||
|
||||
/** |
||||
* Aspect to annotate @ForeignStore fields as JPA @Transient to stop |
||||
* JPA trying to manage them itself |
||||
* @author Rod Johnson |
||||
* |
||||
*/ |
||||
public privileged aspect JpaEntityForeignStoreFieldTransience { |
||||
|
||||
declare @field : @RelatedEntity * (@Entity *).* : @Transient; |
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.lang.reflect.Field; |
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException; |
||||
|
||||
/** |
||||
* Interface to validate RelatedAnnotation annotation usage |
||||
* and other mapping constructs. |
||||
* |
||||
* @author Rod Johnson |
||||
* |
||||
*/ |
||||
public interface MappingValidator { |
||||
|
||||
void validateGet(Class<?> entityClass, Field f, RelatedEntity re) throws InvalidDataAccessApiUsageException; |
||||
|
||||
void validateSetTo(Class<?> entityClass, Field f, RelatedEntity re, Object newVal) throws InvalidDataAccessApiUsageException, IllegalArgumentException; |
||||
|
||||
} |
||||
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.util.Set; |
||||
|
||||
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 ForeignStoreKeyManager implementations that adds |
||||
* ordering support. |
||||
* |
||||
* @author Rod Johnson |
||||
*/ |
||||
public abstract class OrderedForeignStoreKeyManager<T> implements ForeignStoreKeyManager<T>, 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; |
||||
} |
||||
|
||||
/** |
||||
* Subclasses can override if they support collection management. |
||||
*/ |
||||
@Override |
||||
public <K> Set<K> findForeignStoreKeySet(T entity, Field foreignStore, Class<K> keyClass) throws DataAccessException { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
@Override |
||||
public void storeForeignStoreKeySet(T entity, Field foreignStore, Set<Object> keys) throws DataAccessException { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.lang.reflect.Field; |
||||
|
||||
import org.springframework.dao.DataAccessException; |
||||
|
||||
/** |
||||
* ForeignStoreKeyManager implementation that uses the key of the present |
||||
* entity. |
||||
* |
||||
* @author Rod Johnson |
||||
* |
||||
*/ |
||||
public class PresentKeyForeignStoreKeyManager extends OrderedForeignStoreKeyManager { |
||||
|
||||
private final EntityOperationsLocator eoLocator; |
||||
|
||||
public PresentKeyForeignStoreKeyManager(EntityOperationsLocator eoLocator) { |
||||
this.eoLocator = eoLocator; |
||||
} |
||||
|
||||
@Override |
||||
public Object findForeignStoreKey(Object entity, Field foreignStore, Class requiredClass) throws DataAccessException { |
||||
EntityOperations eo = eoLocator.entityOperationsFor(entity.getClass(), foreignStore.getAnnotation(RelatedEntity.class)); |
||||
return eo.findUniqueKey(entity); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isSupportedField(Class clazz, Field foreignStore) { |
||||
RelatedEntity fs = foreignStore.getAnnotation(RelatedEntity.class); |
||||
return fs.sameKey(); |
||||
} |
||||
|
||||
@Override |
||||
public void storeForeignStoreKey(Object entity, Field foreignStore, Object pk) throws DataAccessException { |
||||
// Nothing to do
|
||||
} |
||||
|
||||
@Override |
||||
public void clearForeignStoreKey(Object entity, Field foreignStore, Class keyClass) throws DataAccessException { |
||||
// Nothing to do
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.springframework.dao.DataAccessException; |
||||
|
||||
/** |
||||
* Implementation of entity operations that works on any entity |
||||
* that adheres to Roo persist() and static finder conventions. |
||||
* Does not depend on Roo, merely on Roo conventions, which can |
||||
* also be implemented by hand. |
||||
* |
||||
* @author Rod Johnson |
||||
*/ |
||||
public class RooConventionEntityOperations extends OrderedEntityOperations { |
||||
|
||||
/** |
||||
* Utility method |
||||
* |
||||
* @param clazz |
||||
* @param methodName |
||||
* @param target |
||||
* @param args |
||||
* @return |
||||
*/ |
||||
public static Object invoke(Class<?> clazz, String methodName, |
||||
Object target, Class<?>[] argTypes, Object... args) { |
||||
try { |
||||
Method m = clazz.getMethod(methodName, argTypes); |
||||
return m.invoke(target, (Object[]) args); |
||||
} catch (Exception ex) { |
||||
// TODO FIX ME
|
||||
// System.out.println(ex + ": checked exceptions are stupid");
|
||||
throw new IllegalArgumentException(ex); |
||||
} |
||||
} |
||||
|
||||
public static Object invokeNoArgMethod(Class<?> clazz, String methodName, Object target) { |
||||
return invoke(clazz, methodName, target, (Class<?>[]) null, |
||||
(Object[]) null); |
||||
} |
||||
|
||||
@Override |
||||
public Object findEntity(Class entityClass, Object pk) |
||||
throws DataAccessException { |
||||
String findMethod = "find" + entityClass.getSimpleName(); |
||||
Object found = invoke(entityClass, findMethod, entityClass, |
||||
new Class<?>[] { pk.getClass() }, pk); |
||||
log.info("Lookup [" + entityClass.getName() + "] by pk=[" + pk + "] using static finder method '" + |
||||
findMethod + "' found [" + found + "]"); |
||||
return found; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Object findUniqueKey(Object entity) throws DataAccessException { |
||||
String idMethodName = "getId"; |
||||
return invokeNoArgMethod(entity.getClass(), idMethodName, entity); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isTransient(Object entity) throws DataAccessException { |
||||
return findUniqueKey(entity) == null; |
||||
} |
||||
|
||||
@Override |
||||
public Object makePersistent(Object owner, Object entity, Field f, RelatedEntity fs) throws DataAccessException { |
||||
if (log.isDebugEnabled()) { |
||||
log.debug("Making entity persistent: BEFORE [" + entity + "]"); |
||||
} |
||||
String persistMethodName = "persist"; |
||||
invokeNoArgMethod(entity.getClass(), persistMethodName, entity); |
||||
Object key = findUniqueKey(entity); |
||||
log.info("Making entity persistent: AFTER [" + entity + "]"); |
||||
return key; |
||||
} |
||||
|
||||
@Override |
||||
public boolean supports(Class clazz, RelatedEntity fs) { |
||||
try { |
||||
// TODO fix this
|
||||
clazz.getMethod("getId"); |
||||
return true; |
||||
} catch (Exception ex) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean cacheInEntity() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isTransactional() { |
||||
// TODO Need to have a better test
|
||||
return true; |
||||
}} |
||||
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
/** |
||||
* Tag interface introduced to objects that have introduced foreign store keys |
||||
* @author Rod Johnson |
||||
* |
||||
*/ |
||||
public interface Roo_GeneratedForeignStoreKeys { |
||||
|
||||
} |
||||
@ -0,0 +1,172 @@
@@ -0,0 +1,172 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.ParameterizedType; |
||||
import java.lang.reflect.Type; |
||||
import java.util.Collection; |
||||
import java.util.Set; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.aspectj.lang.reflect.FieldSignature; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.persistence.support.DefaultManagedSet; |
||||
import org.springframework.persistence.support.ManagedSet; |
||||
import org.springframework.persistence.support.ManagedSet.ChangeListener; |
||||
|
||||
/** |
||||
* Aspect to handle ForeignStore annotation indicating navigation to a |
||||
* potentially different persistence store. |
||||
* |
||||
* Can be configured via invoking init() method or through Spring |
||||
* autowiring if beans named "entityOperationsLocator" and |
||||
* "foreignStoreKeyManager" are provided. |
||||
* |
||||
* @author Rod Johnson |
||||
*/ |
||||
public privileged aspect StoreSpanning { |
||||
|
||||
private final Log log = LogFactory.getLog(getClass()); |
||||
|
||||
private EntityOperationsLocator entityOperationsLocator; |
||||
|
||||
private ForeignStoreKeyManagerLocator foreignStoreKeyManagerLocator; |
||||
|
||||
private MappingValidator mappingValidator; |
||||
|
||||
@Autowired |
||||
public void init(EntityOperationsLocator eol, ForeignStoreKeyManagerLocator fskml) { |
||||
this.entityOperationsLocator = eol; |
||||
this.foreignStoreKeyManagerLocator = fskml; |
||||
} |
||||
|
||||
@Autowired(required=false) |
||||
public void setMappingValidator(MappingValidator mv) { |
||||
this.mappingValidator = mv; |
||||
} |
||||
|
||||
|
||||
public pointcut foreignEntityFieldGet(Object entity, RelatedEntity fs) : |
||||
get(@RelatedEntity * *) && |
||||
this(entity) && |
||||
@annotation(fs); |
||||
|
||||
public pointcut foreignEntityFieldSet(Object entity, RelatedEntity fs, Object newVal) : |
||||
set(@RelatedEntity * *) && |
||||
this(entity) && |
||||
@annotation(fs) && |
||||
args(newVal); |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
Object around(Object entity, RelatedEntity fs) : foreignEntityFieldGet(entity, fs) { |
||||
Field f = ((FieldSignature) thisJoinPoint.getSignature()).getField(); |
||||
log.info("GET: Handling foreign store " + f); |
||||
if (this.mappingValidator != null) { |
||||
this.mappingValidator.validateGet(entity.getClass(), f, fs); |
||||
} |
||||
|
||||
Object fieldValue = proceed(entity, fs); |
||||
// What if it was set to null? |
||||
if (fieldValue != null) { |
||||
log.info("GET " + f + ": returning actual field value"); |
||||
return fieldValue; |
||||
} |
||||
|
||||
// Must retrieve |
||||
if (Set.class.isAssignableFrom(f.getType())) { |
||||
// TODO empty set, store class |
||||
log.info("GET " + f + ": Retrieving ManagedSet"); |
||||
ForeignStoreKeyManager foreignStoreKeyManager = foreignStoreKeyManagerLocator.foreignStoreKeyManagerFor(entity.getClass(), f); |
||||
|
||||
// TODO fix me, this is fragile |
||||
ParameterizedType genericType = (ParameterizedType) f.getGenericType(); |
||||
Class entityClass = (Class) genericType.getActualTypeArguments()[0]; |
||||
Class keyClass = entityOperationsLocator.entityOperationsFor(entityClass, fs).uniqueKeyType(entityClass); |
||||
Set keySet = foreignStoreKeyManager.findForeignStoreKeySet(entity, f, keyClass); |
||||
ManagedSet managedSet = DefaultManagedSet.fromKeySet(keySet, entityClass, entityOperationsLocator); |
||||
return managedSet; |
||||
} |
||||
else if (Collection.class.isAssignableFrom(f.getType())) { |
||||
throw new UnsupportedOperationException("Unsupported collection type " + f.getType() + " in entity class " + entity.getClass()); |
||||
} |
||||
else { |
||||
return findScalarEntity(entity, f, fs); |
||||
} |
||||
} |
||||
|
||||
private Object findScalarEntity(Object entity, Field f, RelatedEntity fs) { |
||||
EntityOperations eo = entityOperationsLocator.entityOperationsFor(f.getType(), fs); |
||||
Class keyType = eo.uniqueKeyType(f.getType()); |
||||
ForeignStoreKeyManager foreignStoreKeyManager = foreignStoreKeyManagerLocator.foreignStoreKeyManagerFor(entity.getClass(), f); |
||||
Object pk = foreignStoreKeyManager.findForeignStoreKey(entity, f, keyType); |
||||
if (pk != null) { |
||||
log.debug("GET " + f + ": entity find for key=[" + pk + "] of class [" + pk.getClass() + "]"); |
||||
Object found = eo.findEntity(f.getType(), pk); |
||||
log.info("GET " + f + ": entity find for key=[" + pk + "] found [" + found + "]"); |
||||
return found; |
||||
} |
||||
else { |
||||
log.info("GET " + f + ": no key found, returning null"); |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
// TODO handle explicit set to null |
||||
@SuppressWarnings("unchecked") |
||||
Object around(final Object entity, RelatedEntity fs, Object newVal) : foreignEntityFieldSet(entity, fs, newVal) { |
||||
final Field f = ((FieldSignature) thisJoinPoint.getSignature()).getField(); |
||||
if (this.mappingValidator != null) { |
||||
this.mappingValidator.validateSetTo(entity.getClass(), f, fs, newVal); |
||||
} |
||||
log.info("SET: Handling foreign store " + f); |
||||
|
||||
if (newVal != null) { |
||||
if (Set.class.isAssignableFrom(f.getType())) { |
||||
log.info("Setting set: Creating ManagedSet"); |
||||
final ManagedSet managedSet = DefaultManagedSet.fromEntitySet((Set) newVal, entityOperationsLocator); |
||||
final ForeignStoreKeyManager foreignStoreKeyManager = foreignStoreKeyManagerLocator.foreignStoreKeyManagerFor(entity.getClass(), f); |
||||
foreignStoreKeyManager.storeForeignStoreKeySet(entity, f, managedSet.getKeySet()); |
||||
managedSet.addListener(new ChangeListener() { |
||||
@Override |
||||
public void onDirty() { |
||||
foreignStoreKeyManager.storeForeignStoreKeySet(entity, f, managedSet.getKeySet()); |
||||
} |
||||
}); |
||||
return proceed(entity, fs, managedSet); |
||||
} |
||||
else if (Collection.class.isAssignableFrom(f.getType())) { |
||||
throw new UnsupportedOperationException("Unsupported collection type " + f.getType() + " in entity class " + entity.getClass()); |
||||
} |
||||
else { |
||||
EntityOperations eo = handleScalarFieldSet(entity, f, fs, newVal); |
||||
|
||||
// Don't store it in the entity if the entity type doesn't support |
||||
// it, for example |
||||
// because it shouldn't be read repeatedly (as with a stream) |
||||
if (!eo.cacheInEntity()) { |
||||
return null; |
||||
} |
||||
} |
||||
} |
||||
return proceed(entity, fs, newVal); |
||||
} |
||||
|
||||
private EntityOperations handleScalarFieldSet(Object entity, Field f, RelatedEntity fs, Object newVal) { |
||||
EntityOperations eo = entityOperationsLocator.entityOperationsFor(f.getType(), fs); |
||||
Object pk = eo.findUniqueKey(newVal); |
||||
|
||||
System.err.println("TODO: test whether current entity is persistent"); |
||||
if (pk == null) { |
||||
// Entity is transient for now |
||||
log.info("SET " + f + ": no foreign store key to store; entity has no persistent identity, MAKING PERSISTENT"); |
||||
pk = eo.makePersistent(entity,newVal, f, fs); |
||||
} |
||||
|
||||
if (pk != null) { |
||||
ForeignStoreKeyManager foreignStoreKeyManager = foreignStoreKeyManagerLocator.foreignStoreKeyManagerFor(entity.getClass(), f); |
||||
foreignStoreKeyManager.storeForeignStoreKey(entity, f, pk); |
||||
log.info("SET " + f + ": stored foreign store key=[" + pk + "]"); |
||||
} |
||||
return eo; |
||||
} |
||||
} |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
package org.springframework.persistence; |
||||
|
||||
import org.springframework.dao.UncategorizedDataAccessException; |
||||
|
||||
public class UnknownEntityClassException extends |
||||
UncategorizedDataAccessException { |
||||
|
||||
public UnknownEntityClassException(Class<?> entityClass) { |
||||
super("Unknown entity class [" + entityClass.getName() + "]", null); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
package org.springframework.persistence.support; |
||||
|
||||
|
||||
/** |
||||
* Try for a constructor taking a ChangeSet: failing that, try a no-arg |
||||
* constructor and then setChangeSet(). |
||||
* |
||||
* @author Rod Johnson |
||||
*/ |
||||
public class ChangeSetConstructorEntityInstantiator extends AbstractConstructorEntityInstantiator<ChangeSetBacked, ChangeSet>{ |
||||
|
||||
@Override |
||||
protected void setState(ChangeSetBacked entity, ChangeSet cs) { |
||||
entity.setChangeSet(cs); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,94 @@
@@ -0,0 +1,94 @@
|
||||
package org.springframework.persistence.support; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.util.Set; |
||||
|
||||
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.OrderedForeignStoreKeyManager; |
||||
|
||||
/** |
||||
* ForeignStoreKeyManager implementation that backs the foreign key to a |
||||
* ChangeSet. |
||||
* |
||||
* @author Thomas Risberg |
||||
* @author Rod Johnson |
||||
*/ |
||||
public class ChangeSetForeignStoreKeyManager extends OrderedForeignStoreKeyManager<ChangeSetBacked> { |
||||
|
||||
public static final String FOREIGN_STORE_SET_PREFIX = "S"; |
||||
|
||||
protected final Log log = LogFactory.getLog(getClass()); |
||||
|
||||
private String fieldDelimiter = "."; |
||||
|
||||
private final ConversionService conversionService; |
||||
|
||||
public String getFieldDelimiter() { |
||||
return fieldDelimiter; |
||||
} |
||||
|
||||
public void setFieldDelimiter(String fieldDelimiter) { |
||||
this.fieldDelimiter = fieldDelimiter; |
||||
} |
||||
|
||||
@Autowired |
||||
public ChangeSetForeignStoreKeyManager(ConversionService conversionService) { |
||||
this.conversionService = conversionService; |
||||
} |
||||
|
||||
@Override |
||||
public void clearForeignStoreKey(ChangeSetBacked entity, Field foreignStore, Class<?> keyClass) throws DataAccessException { |
||||
String propName = propertyName(entity, foreignStore); |
||||
entity.getChangeSet().removeProperty(propName); |
||||
log.info("CLEAR foreign store property " + foreignStore + " <- ChangeSetBacked foreign key property [" + propName + "]"); |
||||
} |
||||
|
||||
@Override |
||||
public <K> K findForeignStoreKey(ChangeSetBacked entity, Field foreignStore, Class<K> keyClass) throws DataAccessException { |
||||
String propName = propertyName(entity, foreignStore); |
||||
System.err.println("+++ " + entity.getChangeSet().getValues()); |
||||
K key = entity.getChangeSet().get(propName, keyClass, this.conversionService); |
||||
log.info("FIND foreign store property " + foreignStore + " <- ChangeSetBacked foreign key property [" + propName + "] returned [" |
||||
+ key + "]"); |
||||
return key; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isSupportedField(Class<ChangeSetBacked> entityClass, Field foreignStore) { |
||||
return ChangeSetBacked.class.isAssignableFrom(entityClass); |
||||
} |
||||
|
||||
@Override |
||||
public void storeForeignStoreKey(ChangeSetBacked entity, Field foreignStore, Object pk) throws DataAccessException { |
||||
String propName = propertyName(entity, foreignStore); |
||||
entity.getChangeSet().set(propName, pk); |
||||
log.info("STORE foreign store property " + foreignStore + " -> ChangeSetBacked foreign key property [" + propName |
||||
+ "] with key value [" + pk + "]"); |
||||
} |
||||
|
||||
@Override |
||||
public <K> Set<K> findForeignStoreKeySet(ChangeSetBacked entity, Field foreignStore, Class<K> keyClass) throws DataAccessException { |
||||
Set keySet = entity.getChangeSet().get(foreignStoreKeyName(foreignStore), Set.class, this.conversionService); |
||||
// if (keySet != null && !keySet.isEmpty())
|
||||
// System.out.println("KeySET=**************" + keySet + ", 0th type=" + keySet.iterator().next().getClass());
|
||||
return keySet; |
||||
} |
||||
|
||||
private String foreignStoreKeyName(Field foreignStore) { |
||||
return FOREIGN_STORE_SET_PREFIX + getFieldDelimiter() + foreignStore.getName(); |
||||
} |
||||
|
||||
@Override |
||||
public void storeForeignStoreKeySet(ChangeSetBacked entity, Field foreignStore, Set<Object> keys) throws DataAccessException { |
||||
entity.getChangeSet().set(foreignStoreKeyName(foreignStore), keys); |
||||
} |
||||
|
||||
private String propertyName(ChangeSetBacked rb, Field f) { |
||||
return rb.getClass().getSimpleName() + getFieldDelimiter() + f.getName(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,162 @@
@@ -0,0 +1,162 @@
|
||||
package org.springframework.persistence.support; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.HashSet; |
||||
import java.util.Iterator; |
||||
import java.util.LinkedList; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.dao.DataAccessException; |
||||
import org.springframework.persistence.EntityOperations; |
||||
import org.springframework.persistence.EntityOperationsLocator; |
||||
|
||||
public class DefaultManagedSet implements ManagedSet { |
||||
|
||||
public static DefaultManagedSet fromEntitySet(Set entitySet, EntityOperationsLocator eol) { |
||||
DefaultManagedSet dms = new DefaultManagedSet(eol); |
||||
if (entitySet != null) { |
||||
for (Object entity : entitySet) { |
||||
dms.add(entity); |
||||
} |
||||
} |
||||
return dms; |
||||
} |
||||
|
||||
public static DefaultManagedSet fromKeySet(Set keySet, Class<?> entityClass, EntityOperationsLocator eol) throws DataAccessException { |
||||
DefaultManagedSet dms = new DefaultManagedSet(eol); |
||||
if (keySet != null) { |
||||
for (Object key : keySet) { |
||||
dms.keySet.add(key); |
||||
EntityOperations eo = eol.entityOperationsFor(entityClass, null); |
||||
dms.entitySet.add(eo.findEntity(entityClass, key)); |
||||
} |
||||
} |
||||
return dms; |
||||
} |
||||
|
||||
private final Set keySet = new HashSet(); |
||||
|
||||
private final Set entitySet = new HashSet(); |
||||
|
||||
private boolean dirty; |
||||
|
||||
private List<ChangeListener> listeners = new LinkedList<ChangeListener>(); |
||||
|
||||
private final EntityOperationsLocator entityOperationsLocator; |
||||
|
||||
private DefaultManagedSet(EntityOperationsLocator eol) { |
||||
this.entityOperationsLocator = eol; |
||||
} |
||||
|
||||
@Override |
||||
public void addListener(ChangeListener l) { |
||||
this.listeners.add(l); |
||||
} |
||||
|
||||
|
||||
protected void publishEvent() { |
||||
this.dirty = true; |
||||
for (ChangeListener l : listeners) { |
||||
l.onDirty(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Set getKeySet() { |
||||
return this.keySet; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isDirty() { |
||||
return this.dirty; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean add(Object e) { |
||||
if (entitySet.contains(e)) { |
||||
return false; |
||||
} |
||||
EntityOperations eo = entityOperationsLocator.entityOperationsFor(e.getClass(), null); |
||||
Object key = eo.findUniqueKey(e); |
||||
if (key == null) { |
||||
eo.makePersistent(null, e, null, null); |
||||
} |
||||
key = eo.findUniqueKey(e); |
||||
keySet.add(key); |
||||
entitySet.add(e); |
||||
publishEvent(); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean addAll(Collection c) { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
this.entitySet.clear(); |
||||
this.keySet.clear(); |
||||
publishEvent(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean contains(Object o) { |
||||
return entitySet.contains(o); |
||||
} |
||||
|
||||
@Override |
||||
public boolean containsAll(Collection c) { |
||||
return entitySet.containsAll(c); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isEmpty() { |
||||
return keySet.isEmpty(); |
||||
} |
||||
|
||||
@Override |
||||
public Iterator iterator() { |
||||
return entitySet.iterator(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean remove(Object e) { |
||||
if (!entitySet.contains(e)) { |
||||
return false; |
||||
} |
||||
EntityOperations eo = entityOperationsLocator.entityOperationsFor(e.getClass(), null); |
||||
keySet.remove(eo.findUniqueKey(e)); |
||||
entitySet.remove(e); |
||||
publishEvent(); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean removeAll(Collection c) { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean retainAll(Collection c) { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
@Override |
||||
public int size() { |
||||
return keySet.size(); |
||||
} |
||||
|
||||
@Override |
||||
public Object[] toArray() { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
@Override |
||||
public Object[] toArray(Object[] a) { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
package org.springframework.persistence.support; |
||||
|
||||
import java.util.Set; |
||||
|
||||
public interface ManagedSet extends Set { |
||||
|
||||
Set getKeySet(); |
||||
|
||||
void addListener(ChangeListener l); |
||||
|
||||
boolean isDirty(); |
||||
|
||||
interface ChangeListener { |
||||
void onDirty(); |
||||
} |
||||
|
||||
// TODO move into managed collection
|
||||
|
||||
// TODO insertions, deletions
|
||||
// after markSynchronized()
|
||||
|
||||
// This may be wrong, shouldn't it give something back for a ChangeSet?
|
||||
// void retrieve(EntityOperationsLocator eol) throws DataAccessException;
|
||||
//
|
||||
// void persist(EntityOperationsLocator eol) throws DataAccessException;
|
||||
|
||||
} |
||||
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
package org.springframework.persistence.support; |
||||
|
||||
import java.lang.reflect.Field; |
||||
|
||||
import javax.validation.constraints.NotNull; |
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException; |
||||
import org.springframework.persistence.AsynchStoreCompletionListener; |
||||
import org.springframework.persistence.InvalidFieldAnnotationException; |
||||
import org.springframework.persistence.MappingValidator; |
||||
import org.springframework.persistence.RelatedEntity; |
||||
|
||||
// TODO fancier version could discover many rules, with annotations etc.
|
||||
// Also invoke the relevant EntityOperations
|
||||
public class SimpleMappingValidator implements MappingValidator { |
||||
|
||||
@Override |
||||
public void validateGet(Class<?> entityClass, Field f, RelatedEntity re) throws InvalidDataAccessApiUsageException { |
||||
// Validate the annotation
|
||||
if (!AsynchStoreCompletionListener.NONE.class.equals(re.storeCompletionListenerClass()) |
||||
&& !"".equals(re.storeCompletionListenerBeanName())) { |
||||
throw new InvalidFieldAnnotationException(entityClass, f, |
||||
"Can't have storeCompletionListener class and bean name on same annotation"); |
||||
} |
||||
} |
||||
|
||||
public void validateSetTo(Class<?> entityClass, Field f, RelatedEntity re, Object newVal) throws InvalidDataAccessApiUsageException, |
||||
IllegalArgumentException { |
||||
if (newVal == null && f.isAnnotationPresent(NotNull.class)) { |
||||
throw new IllegalArgumentException("Can't set non-null field [" + f.getName() + " to null"); |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue