73 changed files with 3425 additions and 940 deletions
@ -1,7 +1,7 @@ |
|||||||
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||||||
<classpath> |
<classpath> |
||||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/> |
<classpathentry including="**/*.aj|**/*.java" kind="src" output="target/classes" path="src/main/java"/> |
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/> |
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> |
||||||
<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/> |
<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/> |
||||||
<classpathentry kind="output" path="target/classes"/> |
<classpathentry kind="output" path="target/classes"/> |
||||||
</classpath> |
</classpath> |
||||||
|
|||||||
@ -1,6 +1,13 @@ |
|||||||
#Wed Nov 17 12:20:43 EST 2010 |
#Tue Mar 08 11:29:43 EST 2011 |
||||||
eclipse.preferences.version=1 |
eclipse.preferences.version=1 |
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 |
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled |
||||||
org.eclipse.jdt.core.compiler.compliance=1.5 |
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.problem.forbiddenReference=warning |
||||||
org.eclipse.jdt.core.compiler.source=1.5 |
org.eclipse.jdt.core.compiler.source=1.6 |
||||||
|
|||||||
@ -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,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 @@ |
|||||||
|
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 @@ |
|||||||
|
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,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,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 @@ |
|||||||
|
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 @@ |
|||||||
|
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 @@ |
|||||||
|
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 @@ |
|||||||
|
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 @@ |
|||||||
|
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 @@ |
|||||||
|
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,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,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 @@ |
|||||||
|
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,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,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 @@ |
|||||||
|
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 @@ |
|||||||
|
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 @@ |
|||||||
|
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,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,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 @@ |
|||||||
|
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,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,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,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,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 @@ |
|||||||
|
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"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,73 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.repository.query; |
||||||
|
|
||||||
|
import java.util.Iterator; |
||||||
|
|
||||||
|
import org.springframework.data.domain.Pageable; |
||||||
|
import org.springframework.data.domain.Sort; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Interface to access method parameters. Allows dedicated access to parameters |
||||||
|
* of special types |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
public interface ParameterAccessor extends Iterable<Object> { |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the {@link Pageable} of the parameters, if available. Returns |
||||||
|
* {@code null} otherwise. |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Pageable getPageable(); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the sort instance to be used for query creation. Will use a |
||||||
|
* {@link Sort} parameter if available or the {@link Sort} contained in a |
||||||
|
* {@link Pageable} if available. Returns {@code null} if no {@link Sort} |
||||||
|
* can be found. |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Sort getSort(); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the bindable value with the given index. Bindable means, that |
||||||
|
* {@link Pageable} and {@link Sort} values are skipped without noticed in |
||||||
|
* the index. For a method signature taking {@link String}, {@link Pageable} |
||||||
|
* , {@link String}, {@code #getBindableParameter(1)} would return the |
||||||
|
* second {@link String} value. |
||||||
|
* |
||||||
|
* @param index |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Object getBindableValue(int index); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns an iterator over all <em>bindable</em> parameters. This means |
||||||
|
* parameters implementing {@link Pageable} or {@link Sort} will not be |
||||||
|
* included in this {@link Iterator}. |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Iterator<Object> iterator(); |
||||||
|
} |
||||||
@ -0,0 +1,70 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.repository.support; |
||||||
|
|
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Base class for implementations of {@link EntityInformation}. Considers an |
||||||
|
* entity to be new whenever {@link #getId(Object)} returns {@literal null}. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
public abstract class AbstractEntityInformation<T> implements |
||||||
|
EntityInformation<T> { |
||||||
|
|
||||||
|
private final Class<T> domainClass; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link AbstractEntityInformation} from the given domain |
||||||
|
* class. |
||||||
|
* |
||||||
|
* @param domainClass |
||||||
|
*/ |
||||||
|
public AbstractEntityInformation(Class<T> domainClass) { |
||||||
|
|
||||||
|
Assert.notNull(domainClass); |
||||||
|
this.domainClass = domainClass; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see |
||||||
|
* org.springframework.data.repository.support.IsNewAware#isNew(java.lang |
||||||
|
* .Object) |
||||||
|
*/ |
||||||
|
public boolean isNew(T entity) { |
||||||
|
|
||||||
|
return getId(entity) == null; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see |
||||||
|
* org.springframework.data.repository.support.EntityInformation#getJavaType |
||||||
|
* () |
||||||
|
*/ |
||||||
|
public Class<T> getJavaType() { |
||||||
|
|
||||||
|
return this.domainClass; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,328 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.repository.support; |
||||||
|
|
||||||
|
import static org.springframework.core.GenericTypeResolver.*; |
||||||
|
import static org.springframework.data.repository.util.ClassUtils.*; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.lang.reflect.Type; |
||||||
|
import java.lang.reflect.TypeVariable; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||||
|
|
||||||
|
import org.springframework.data.repository.Repository; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Default implementation of {@link RepositoryMetadata}. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
public class DefaultRepositoryMetadata implements RepositoryMetadata { |
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes") |
||||||
|
private static final TypeVariable<Class<Repository>>[] PARAMETERS = |
||||||
|
Repository.class.getTypeParameters(); |
||||||
|
private static final String DOMAIN_TYPE_NAME = PARAMETERS[0].getName(); |
||||||
|
private static final String ID_TYPE_NAME = PARAMETERS[1].getName(); |
||||||
|
|
||||||
|
private final Map<Method, Method> methodCache = |
||||||
|
new ConcurrentHashMap<Method, Method>(); |
||||||
|
|
||||||
|
private final Class<?> repositoryInterface; |
||||||
|
private final Class<?> repositoryBaseClass; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link DefaultRepositoryMetadata} for the given repository |
||||||
|
* interface and repository base class. |
||||||
|
* |
||||||
|
* @param repositoryInterface |
||||||
|
*/ |
||||||
|
public DefaultRepositoryMetadata(Class<?> repositoryInterface, |
||||||
|
Class<?> repositoryBaseClass) { |
||||||
|
|
||||||
|
Assert.notNull(repositoryInterface); |
||||||
|
Assert.notNull(repositoryBaseClass); |
||||||
|
this.repositoryInterface = repositoryInterface; |
||||||
|
this.repositoryBaseClass = repositoryBaseClass; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata# |
||||||
|
* getRepositoryInterface() |
||||||
|
*/ |
||||||
|
public Class<?> getRepositoryInterface() { |
||||||
|
|
||||||
|
return repositoryInterface; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata# |
||||||
|
* getRepositoryBaseClass() |
||||||
|
*/ |
||||||
|
public Class<?> getRepositoryBaseClass() { |
||||||
|
|
||||||
|
return this.repositoryBaseClass; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see |
||||||
|
* org.springframework.data.repository.support.RepositoryMetadata#getDomainClass |
||||||
|
* () |
||||||
|
*/ |
||||||
|
public Class<?> getDomainClass() { |
||||||
|
|
||||||
|
Class<?>[] arguments = |
||||||
|
resolveTypeArguments(repositoryInterface, Repository.class); |
||||||
|
return arguments == null ? null : arguments[0]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see |
||||||
|
* org.springframework.data.repository.support.RepositoryMetadata#getIdClass |
||||||
|
* () |
||||||
|
*/ |
||||||
|
public Class<?> getIdClass() { |
||||||
|
|
||||||
|
Class<?>[] arguments = |
||||||
|
resolveTypeArguments(repositoryInterface, Repository.class); |
||||||
|
return arguments == null ? null : arguments[1]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata# |
||||||
|
* getBaseClassMethod(java.lang.reflect.Method) |
||||||
|
*/ |
||||||
|
public Method getBaseClassMethod(Method method) { |
||||||
|
|
||||||
|
Assert.notNull(method); |
||||||
|
|
||||||
|
Method result = methodCache.get(method); |
||||||
|
|
||||||
|
if (null != result) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
result = getBaseClassMethodFor(method); |
||||||
|
methodCache.put(method, result); |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns whether the given method is considered to be a repository base |
||||||
|
* class method. |
||||||
|
* |
||||||
|
* @param method |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private boolean isBaseClassMethod(Method method) { |
||||||
|
|
||||||
|
Assert.notNull(method); |
||||||
|
|
||||||
|
if (method.getDeclaringClass().isAssignableFrom(repositoryBaseClass)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return !method.equals(getBaseClassMethod(method)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata# |
||||||
|
* getFinderMethods() |
||||||
|
*/ |
||||||
|
public Iterable<Method> getQueryMethods() { |
||||||
|
|
||||||
|
Set<Method> result = new HashSet<Method>(); |
||||||
|
|
||||||
|
for (Method method : repositoryInterface.getDeclaredMethods()) { |
||||||
|
if (!isCustomMethod(method) && !isBaseClassMethod(method)) { |
||||||
|
result.add(method); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see |
||||||
|
* org.springframework.data.repository.support.RepositoryMetadata#isCustomMethod |
||||||
|
* (java.lang.reflect.Method) |
||||||
|
*/ |
||||||
|
public boolean isCustomMethod(Method method) { |
||||||
|
|
||||||
|
Class<?> declaringClass = method.getDeclaringClass(); |
||||||
|
|
||||||
|
boolean isQueryMethod = declaringClass.equals(repositoryInterface); |
||||||
|
boolean isRepositoryInterface = |
||||||
|
isGenericRepositoryInterface(declaringClass); |
||||||
|
boolean isBaseClassMethod = isBaseClassMethod(method); |
||||||
|
|
||||||
|
return !(isRepositoryInterface || isBaseClassMethod || isQueryMethod); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the given base class' method if the given method (declared in the |
||||||
|
* repository interface) was also declared at the repository base class. |
||||||
|
* Returns the given method if the given base class does not declare the |
||||||
|
* method given. Takes generics into account. |
||||||
|
* |
||||||
|
* @param method |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Method getBaseClassMethodFor(Method method) { |
||||||
|
|
||||||
|
for (Method baseClassMethod : repositoryBaseClass.getMethods()) { |
||||||
|
|
||||||
|
// Wrong name
|
||||||
|
if (!method.getName().equals(baseClassMethod.getName())) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// Wrong number of arguments
|
||||||
|
if (!(method.getParameterTypes().length == baseClassMethod |
||||||
|
.getParameterTypes().length)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// Check whether all parameters match
|
||||||
|
if (!parametersMatch(method, baseClassMethod)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
return baseClassMethod; |
||||||
|
} |
||||||
|
|
||||||
|
return method; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata# |
||||||
|
* hasCustomMethod() |
||||||
|
*/ |
||||||
|
public boolean hasCustomMethod() { |
||||||
|
|
||||||
|
// No detection required if no typing interface was configured
|
||||||
|
if (isGenericRepositoryInterface(repositoryInterface)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
for (Method method : repositoryInterface.getMethods()) { |
||||||
|
if (isCustomMethod(method) && !isBaseClassMethod(method)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Checks the given method's parameters to match the ones of the given base |
||||||
|
* class method. Matches generic arguments agains the ones bound in the |
||||||
|
* given repository interface. |
||||||
|
* |
||||||
|
* @param method |
||||||
|
* @param baseClassMethod |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private boolean parametersMatch(Method method, Method baseClassMethod) { |
||||||
|
|
||||||
|
Type[] genericTypes = baseClassMethod.getGenericParameterTypes(); |
||||||
|
Class<?>[] types = baseClassMethod.getParameterTypes(); |
||||||
|
Class<?>[] methodParameters = method.getParameterTypes(); |
||||||
|
|
||||||
|
for (int i = 0; i < genericTypes.length; i++) { |
||||||
|
|
||||||
|
Type type = genericTypes[i]; |
||||||
|
|
||||||
|
if (type instanceof TypeVariable<?>) { |
||||||
|
|
||||||
|
String name = ((TypeVariable<?>) type).getName(); |
||||||
|
|
||||||
|
if (!matchesGenericType(name, methodParameters[i])) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
if (!types[i].equals(methodParameters[i])) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Checks whether the given parameter type matches the generic type of the |
||||||
|
* given parameter. Thus when {@literal PK} is declared, the method ensures |
||||||
|
* that given method parameter is the primary key type declared in the given |
||||||
|
* repository interface e.g. |
||||||
|
* |
||||||
|
* @param name |
||||||
|
* @param parameterType |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private boolean matchesGenericType(String name, Class<?> parameterType) { |
||||||
|
|
||||||
|
Class<?> entityType = getDomainClass(); |
||||||
|
Class<?> idClass = getIdClass(); |
||||||
|
|
||||||
|
if (ID_TYPE_NAME.equals(name) && parameterType.equals(idClass)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
if (DOMAIN_TYPE_NAME.equals(name) && parameterType.equals(entityType)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.repository.support; |
||||||
|
|
||||||
|
/** |
||||||
|
* Extension of {@link EntityMetadata} to add functionality to query information |
||||||
|
* of entity instances. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
public interface EntityInformation<T> extends EntityMetadata<T> { |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns whether the given entity is considered to be new. |
||||||
|
* |
||||||
|
* @param entity must never be {@literal null} |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
boolean isNew(T entity); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the id of the given entity. |
||||||
|
* |
||||||
|
* @param entity must never be {@literal null} |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Object getId(T entity); |
||||||
|
} |
||||||
@ -1,146 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2008-2010 the original author or authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
package org.springframework.data.repository.support; |
|
||||||
|
|
||||||
import java.lang.annotation.Annotation; |
|
||||||
import java.lang.reflect.AnnotatedElement; |
|
||||||
import java.lang.reflect.Field; |
|
||||||
import java.lang.reflect.Method; |
|
||||||
|
|
||||||
import org.springframework.util.Assert; |
|
||||||
import org.springframework.util.ReflectionUtils; |
|
||||||
import org.springframework.util.ReflectionUtils.FieldCallback; |
|
||||||
import org.springframework.util.ReflectionUtils.MethodCallback; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* {@link IsNewAware} and {@link IdAware} implementation that reflectively |
|
||||||
* checks a {@link Field} or {@link Method} annotated with the given |
|
||||||
* annotations. Subclasses usually simply have to provide the persistence |
|
||||||
* technology specific annotations. |
|
||||||
* |
|
||||||
* @author Oliver Gierke |
|
||||||
*/ |
|
||||||
public class ReflectiveEntityInformationSupport implements IsNewAware, IdAware { |
|
||||||
|
|
||||||
private Field field; |
|
||||||
private Method method; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new {@link ReflectiveEntityInformationSupport} by inspecting |
|
||||||
* the given class for a {@link Field} or {@link Method} for and {@link Id} |
|
||||||
* annotation. |
|
||||||
* |
|
||||||
* @param domainClass not {@literal null}, must be annotated with |
|
||||||
* {@link Entity} and carry an anootation defining the id |
|
||||||
* property. |
|
||||||
*/ |
|
||||||
public ReflectiveEntityInformationSupport(Class<?> domainClass, |
|
||||||
final Class<? extends Annotation>... annotationsToScanFor) { |
|
||||||
|
|
||||||
Assert.notNull(domainClass); |
|
||||||
|
|
||||||
ReflectionUtils.doWithFields(domainClass, new FieldCallback() { |
|
||||||
|
|
||||||
public void doWith(Field field) { |
|
||||||
|
|
||||||
if (ReflectiveEntityInformationSupport.this.field != null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if (hasAnnotation(field, annotationsToScanFor)) { |
|
||||||
ReflectiveEntityInformationSupport.this.field = field; |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
if (field != null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
ReflectionUtils.doWithMethods(domainClass, new MethodCallback() { |
|
||||||
|
|
||||||
public void doWith(Method method) { |
|
||||||
|
|
||||||
if (ReflectiveEntityInformationSupport.this.method != null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if (hasAnnotation(method, annotationsToScanFor)) { |
|
||||||
ReflectiveEntityInformationSupport.this.method = method; |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
Assert.isTrue(this.field != null || this.method != null, |
|
||||||
"No id method or field found!"); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Checks whether the given {@link AnnotatedElement} carries one of the |
|
||||||
* given {@link Annotation}s. |
|
||||||
* |
|
||||||
* @param annotatedElement |
|
||||||
* @param annotations |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
private boolean hasAnnotation(AnnotatedElement annotatedElement, |
|
||||||
Class<? extends Annotation>... annotations) { |
|
||||||
|
|
||||||
for (Class<? extends Annotation> annotation : annotations) { |
|
||||||
|
|
||||||
if (annotatedElement.getAnnotation(annotation) != null) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/* |
|
||||||
* (non-Javadoc) |
|
||||||
* |
|
||||||
* @see |
|
||||||
* org.springframework.data.repository.support.RepositorySupport.IsNewAware |
|
||||||
* #isNew(java.lang.Object) |
|
||||||
*/ |
|
||||||
public boolean isNew(Object entity) { |
|
||||||
|
|
||||||
return getId(entity) == null; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/* |
|
||||||
* (non-Javadoc) |
|
||||||
* |
|
||||||
* @see |
|
||||||
* org.springframework.data.repository.support.RepositorySupport.IdAware |
|
||||||
* #getId(java.lang.Object) |
|
||||||
*/ |
|
||||||
public Object getId(Object entity) { |
|
||||||
|
|
||||||
if (field != null) { |
|
||||||
ReflectionUtils.makeAccessible(field); |
|
||||||
return ReflectionUtils.getField(field, entity); |
|
||||||
} |
|
||||||
|
|
||||||
ReflectionUtils.makeAccessible(method); |
|
||||||
return ReflectionUtils.invokeMethod(method, entity); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,104 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.repository.support; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Metadata for repository interfaces. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
public interface RepositoryMetadata { |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the repository interface. |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Class<?> getRepositoryInterface(); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the base class to be used to create the proxy backing instance. |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Class<?> getRepositoryBaseClass(); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns if the configured repository interface has custom methods, that |
||||||
|
* might have to be delegated to a custom implementation. This is used to |
||||||
|
* verify repository configuration. |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
boolean hasCustomMethod(); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns whether the given method is a custom repository method. |
||||||
|
* |
||||||
|
* @param method |
||||||
|
* @param baseClass |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
boolean isCustomMethod(Method method); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all methods considered to be query methods. |
||||||
|
* |
||||||
|
* @param repositoryInterface |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Iterable<Method> getQueryMethods(); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the base class method that is backing the given method. This can |
||||||
|
* be necessary if a repository interface redeclares a method of the core |
||||||
|
* repository interface (e.g. for transaction behaviour customization). |
||||||
|
* Returns the method itself if the base class does not implement the given |
||||||
|
* method. |
||||||
|
* |
||||||
|
* @param method |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Method getBaseClassMethod(Method method); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the domain class the repository is declared for. |
||||||
|
* |
||||||
|
* @param clazz |
||||||
|
* @return the domain class the repository is handling or {@code null} if |
||||||
|
* none found. |
||||||
|
*/ |
||||||
|
Class<?> getDomainClass(); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the id class the given class is declared for. |
||||||
|
* |
||||||
|
* @param clazz |
||||||
|
* @return the id class of the entity managed by the repository for or |
||||||
|
* {@code null} if none found. |
||||||
|
*/ |
||||||
|
Class<?> getIdClass(); |
||||||
|
} |
||||||
@ -1,86 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2008-2010 the original author or authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
package org.springframework.data.repository.support; |
|
||||||
|
|
||||||
import java.io.Serializable; |
|
||||||
|
|
||||||
import org.springframework.data.domain.Persistable; |
|
||||||
import org.springframework.data.repository.Repository; |
|
||||||
import org.springframework.util.Assert; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Abstract base class for generic repositories. Captures information about the |
|
||||||
* domain class to be managed. |
|
||||||
* |
|
||||||
* @author Oliver Gierke |
|
||||||
* @param <T> the type of entity to be handled |
|
||||||
*/ |
|
||||||
public abstract class RepositorySupport<T, ID extends Serializable> implements |
|
||||||
Repository<T, ID> { |
|
||||||
|
|
||||||
private final Class<T> domainClass; |
|
||||||
private final IsNewAware isNewStrategy; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new {@link RepositorySupport}. |
|
||||||
* |
|
||||||
* @param domainClass |
|
||||||
*/ |
|
||||||
public RepositorySupport(Class<T> domainClass) { |
|
||||||
|
|
||||||
Assert.notNull(domainClass); |
|
||||||
this.domainClass = domainClass; |
|
||||||
this.isNewStrategy = createIsNewStrategy(domainClass); |
|
||||||
Assert.notNull(isNewStrategy); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the domain class to handle. |
|
||||||
* |
|
||||||
* @return the domain class
|
|
||||||
*/ |
|
||||||
protected Class<T> getDomainClass() { |
|
||||||
|
|
||||||
return domainClass; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Return whether the given entity is to be regarded as new. Default |
|
||||||
* implementation will inspect the given domain class and use either |
|
||||||
* {@link PersistableEntityInformation} if the class implements |
|
||||||
* {@link Persistable} or {@link ReflectiveEntityInformation} otherwise. |
|
||||||
* |
|
||||||
* @param entity |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
protected abstract IsNewAware createIsNewStrategy(Class<?> domainClass); |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the strategy how to determine whether an entity is to be regarded |
|
||||||
* as new. |
|
||||||
* |
|
||||||
* @return the isNewStrategy |
|
||||||
*/ |
|
||||||
protected IsNewAware getIsNewStrategy() { |
|
||||||
|
|
||||||
return isNewStrategy; |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,68 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.repository.support; |
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.*; |
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Unit tests for {@link AbstractEntityInformation}. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
public class AbstractEntityInformationUnitTests { |
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class) |
||||||
|
public void rejectsNullDomainClass() throws Exception { |
||||||
|
|
||||||
|
new DummyAbstractEntityInformation(null); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void considersEntityNewIfGetIdReturnsNull() throws Exception { |
||||||
|
|
||||||
|
EntityInformation<Object> metadata = |
||||||
|
new DummyAbstractEntityInformation(Object.class); |
||||||
|
assertThat(metadata.isNew(null), is(true)); |
||||||
|
assertThat(metadata.isNew(new Object()), is(false)); |
||||||
|
} |
||||||
|
|
||||||
|
private static class DummyAbstractEntityInformation extends |
||||||
|
AbstractEntityInformation<Object> { |
||||||
|
|
||||||
|
public DummyAbstractEntityInformation(Class<Object> domainClass) { |
||||||
|
|
||||||
|
super(domainClass); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see |
||||||
|
* org.springframework.data.repository.support.EntityMetadata#getId( |
||||||
|
* java.lang.Object) |
||||||
|
*/ |
||||||
|
public Object getId(Object entity) { |
||||||
|
|
||||||
|
return entity == null ? null : entity.toString(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,187 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.repository.support; |
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*; |
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
import java.io.Serializable; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.data.domain.Page; |
||||||
|
import org.springframework.data.domain.Pageable; |
||||||
|
import org.springframework.data.repository.Repository; |
||||||
|
import org.springframework.data.repository.util.ClassUtils; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Unit tests for {@link DefaultRepositoryMetadata}. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
public class DefaultRepositoryMetadataUnitTests { |
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes") |
||||||
|
static final Class<DummyGenericRepositorySupport> REPOSITORY = |
||||||
|
DummyGenericRepositorySupport.class; |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void looksUpDomainClassCorrectly() throws Exception { |
||||||
|
|
||||||
|
RepositoryMetadata metadata = |
||||||
|
new DefaultRepositoryMetadata(UserRepository.class, REPOSITORY); |
||||||
|
assertEquals(User.class, metadata.getDomainClass()); |
||||||
|
|
||||||
|
metadata = new DefaultRepositoryMetadata(SomeDao.class, REPOSITORY); |
||||||
|
assertEquals(User.class, metadata.getDomainClass()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void findsDomainClassOnExtensionOfDaoInterface() throws Exception { |
||||||
|
|
||||||
|
RepositoryMetadata metadata = |
||||||
|
new DefaultRepositoryMetadata( |
||||||
|
ExtensionOfUserCustomExtendedDao.class, REPOSITORY); |
||||||
|
assertEquals(User.class, metadata.getDomainClass()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void detectsParameterizedEntitiesCorrectly() { |
||||||
|
|
||||||
|
RepositoryMetadata metadata = |
||||||
|
new DefaultRepositoryMetadata(GenericEntityRepository.class, |
||||||
|
REPOSITORY); |
||||||
|
assertEquals(GenericEntity.class, metadata.getDomainClass()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void looksUpIdClassCorrectly() throws Exception { |
||||||
|
|
||||||
|
RepositoryMetadata metadata = |
||||||
|
new DefaultRepositoryMetadata(UserRepository.class, REPOSITORY); |
||||||
|
|
||||||
|
assertEquals(Integer.class, metadata.getIdClass()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void discoversRepositoryBaseClassMethod() throws Exception { |
||||||
|
|
||||||
|
Method method = FooDao.class.getMethod("findById", Integer.class); |
||||||
|
DefaultRepositoryMetadata metadata = |
||||||
|
new DefaultRepositoryMetadata(FooDao.class, REPOSITORY); |
||||||
|
|
||||||
|
Method reference = metadata.getBaseClassMethodFor(method); |
||||||
|
assertEquals(REPOSITORY, reference.getDeclaringClass()); |
||||||
|
assertThat(reference.getName(), is("findById")); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void discoveresNonRepositoryBaseClassMethod() throws Exception { |
||||||
|
|
||||||
|
Method method = FooDao.class.getMethod("readById", Long.class); |
||||||
|
|
||||||
|
DefaultRepositoryMetadata metadata = |
||||||
|
new DefaultRepositoryMetadata(FooDao.class, Repository.class); |
||||||
|
|
||||||
|
assertThat(metadata.getBaseClassMethodFor(method), is(method)); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unused") |
||||||
|
private class User { |
||||||
|
|
||||||
|
private String firstname; |
||||||
|
|
||||||
|
|
||||||
|
public String getAddress() { |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static interface UserRepository extends Repository<User, Integer> { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sample interface to serve two purposes: |
||||||
|
* <ol> |
||||||
|
* <li>Check that {@link ClassUtils#getDomainClass(Class)} skips non |
||||||
|
* {@link GenericDao} interfaces</li> |
||||||
|
* <li>Check that {@link ClassUtils#getDomainClass(Class)} traverses |
||||||
|
* interface hierarchy</li> |
||||||
|
* </ol> |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
private interface SomeDao extends Serializable, UserRepository { |
||||||
|
|
||||||
|
Page<User> findByFirstname(Pageable pageable, String firstname); |
||||||
|
} |
||||||
|
|
||||||
|
private static interface FooDao extends Repository<User, Integer> { |
||||||
|
|
||||||
|
// Redeclared method
|
||||||
|
User findById(Integer primaryKey); |
||||||
|
|
||||||
|
|
||||||
|
// Not a redeclared method
|
||||||
|
User readById(Long primaryKey); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sample interface to test recursive lookup of domain class. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
static interface ExtensionOfUserCustomExtendedDao extends |
||||||
|
UserCustomExtendedRepository { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
static interface UserCustomExtendedRepository extends |
||||||
|
Repository<User, Integer> { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
static abstract class DummyGenericRepositorySupport<T, ID extends Serializable> |
||||||
|
implements Repository<T, ID> { |
||||||
|
|
||||||
|
public T findById(ID id) { |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Helper class to reproduce #256. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
static class GenericEntity<T> { |
||||||
|
} |
||||||
|
|
||||||
|
static interface GenericEntityRepository extends |
||||||
|
Repository<GenericEntity<String>, Long> { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
@ -1,81 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2008-2010 the original author or authors. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
package org.springframework.data.repository.support; |
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.*; |
|
||||||
import static org.junit.Assert.*; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.springframework.data.domain.Persistable; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Unit test for {@link PersistableEntityInformation}. |
|
||||||
* |
|
||||||
* @author Oliver Gierke |
|
||||||
*/ |
|
||||||
public class PersistableEntityInformationTests { |
|
||||||
|
|
||||||
@Test |
|
||||||
public void detectsPersistableCorrectly() throws Exception { |
|
||||||
|
|
||||||
PersistableEntityInformation info = new PersistableEntityInformation(); |
|
||||||
|
|
||||||
assertNewAndNoId(info, new PersistableEntity(null)); |
|
||||||
assertNotNewAndId(info, new PersistableEntity(1L), 1L); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
private <T extends IdAware & IsNewAware> void assertNewAndNoId(T info, |
|
||||||
Object entity) { |
|
||||||
|
|
||||||
assertThat(info.isNew(entity), is(true)); |
|
||||||
assertThat(info.getId(entity), is(nullValue())); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
private <T extends IdAware & IsNewAware> void assertNotNewAndId(T info, |
|
||||||
Object entity, Object id) { |
|
||||||
|
|
||||||
assertThat(info.isNew(entity), is(false)); |
|
||||||
assertThat(info.getId(entity), is(id)); |
|
||||||
} |
|
||||||
|
|
||||||
static class PersistableEntity implements Persistable<Long> { |
|
||||||
|
|
||||||
private static final long serialVersionUID = -5898780128204716452L; |
|
||||||
|
|
||||||
private final Long id; |
|
||||||
|
|
||||||
|
|
||||||
public PersistableEntity(Long id) { |
|
||||||
|
|
||||||
this.id = id; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public Long getId() { |
|
||||||
|
|
||||||
return id; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public boolean isNew() { |
|
||||||
|
|
||||||
return id == null; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,88 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.repository.support; |
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.*; |
||||||
|
import static org.junit.Assert.*; |
||||||
|
import static org.mockito.Mockito.*; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.runners.MockitoJUnitRunner; |
||||||
|
import org.springframework.data.domain.Persistable; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Unit tests for {@link PersistableEntityMetadata}. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class PersistableEntityInformationUnitTests { |
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes") |
||||||
|
static final PersistableEntityInformation<Persistable> metadata = |
||||||
|
new PersistableEntityInformation<Persistable>(Persistable.class); |
||||||
|
|
||||||
|
@Mock |
||||||
|
Persistable<Long> persistable; |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void usesPersistablesGetId() throws Exception { |
||||||
|
|
||||||
|
when(persistable.getId()).thenReturn(2L, 1L, 3L); |
||||||
|
assertEquals(2L, metadata.getId(persistable)); |
||||||
|
assertEquals(1L, metadata.getId(persistable)); |
||||||
|
assertEquals(3L, metadata.getId(persistable)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void usesPersistablesIsNew() throws Exception { |
||||||
|
|
||||||
|
when(persistable.isNew()).thenReturn(true, false); |
||||||
|
assertThat(metadata.isNew(persistable), is(true)); |
||||||
|
assertThat(metadata.isNew(persistable), is(false)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void returnsGivenClassAsEntityType() throws Exception { |
||||||
|
|
||||||
|
PersistableEntityInformation<PersistableEntity> info = |
||||||
|
new PersistableEntityInformation<PersistableEntity>( |
||||||
|
PersistableEntity.class); |
||||||
|
|
||||||
|
assertEquals(PersistableEntity.class, info.getJavaType()); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("serial") |
||||||
|
static class PersistableEntity implements Persistable<Long> { |
||||||
|
|
||||||
|
public Long getId() { |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public boolean isNew() { |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,115 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package org.springframework.data.repository.support; |
||||||
|
|
||||||
|
import static org.mockito.Matchers.*; |
||||||
|
import static org.mockito.Mockito.*; |
||||||
|
|
||||||
|
import java.io.Serializable; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.runners.MockitoJUnitRunner; |
||||||
|
import org.springframework.data.repository.Repository; |
||||||
|
import org.springframework.data.repository.query.QueryLookupStrategy; |
||||||
|
import org.springframework.data.repository.query.QueryLookupStrategy.Key; |
||||||
|
import org.springframework.data.repository.query.RepositoryQuery; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Unit tests for {@link RepositoryFactorySupport}. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class RepositoryFactorySupportUnitTests { |
||||||
|
|
||||||
|
RepositoryFactorySupport factory = new DummyRepositoryFactory(); |
||||||
|
|
||||||
|
@Mock |
||||||
|
MyQueryCreationListener listener; |
||||||
|
@Mock |
||||||
|
PlainQueryCreationListener otherListener; |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void invokesCustomQueryCreationListenerForSpecialRepositoryQueryOnly() |
||||||
|
throws Exception { |
||||||
|
|
||||||
|
factory.addQueryCreationListener(listener); |
||||||
|
factory.addQueryCreationListener(otherListener); |
||||||
|
|
||||||
|
factory.getRepository(ObjectRepository.class); |
||||||
|
|
||||||
|
verify(listener, times(1)).onCreation(any(MyRepositoryQuery.class)); |
||||||
|
verify(otherListener, times(2)).onCreation(any(RepositoryQuery.class)); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class DummyRepositoryFactory extends RepositoryFactorySupport { |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Object getTargetRepository(RepositoryMetadata metadata) { |
||||||
|
|
||||||
|
return new Object(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
protected Class<?> getRepositoryBaseClass(Class<?> repositoryInterface) { |
||||||
|
|
||||||
|
return Object.class; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
protected QueryLookupStrategy getQueryLookupStrategy(Key key) { |
||||||
|
|
||||||
|
MyRepositoryQuery queryOne = mock(MyRepositoryQuery.class); |
||||||
|
RepositoryQuery queryTwo = mock(RepositoryQuery.class); |
||||||
|
|
||||||
|
QueryLookupStrategy strategy = mock(QueryLookupStrategy.class); |
||||||
|
when(strategy.resolveQuery(any(Method.class), any(Class.class))) |
||||||
|
.thenReturn(queryOne, queryTwo); |
||||||
|
|
||||||
|
return strategy; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
interface ObjectRepository extends Repository<Object, Serializable> { |
||||||
|
|
||||||
|
Object findByClass(Class<?> clazz); |
||||||
|
|
||||||
|
|
||||||
|
Object findByFoo(); |
||||||
|
} |
||||||
|
|
||||||
|
interface PlainQueryCreationListener extends |
||||||
|
QueryCreationListener<RepositoryQuery> { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
interface MyQueryCreationListener extends |
||||||
|
QueryCreationListener<MyRepositoryQuery> { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
interface MyRepositoryQuery extends RepositoryQuery { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue