Merge branch 'master' of github.com:SpringSource/spring-data-commons
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -119,7 +119,7 @@
|
||||
<!-- available only in the springframework maven repository. see <repositories> section below -->
|
||||
<groupId>org.springframework.build.aws</groupId>
|
||||
<artifactId>org.springframework.build.aws.maven</artifactId>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<version>3.1.0.RELEASE</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
<plugins>
|
||||
@@ -133,7 +133,7 @@
|
||||
<goal>generate-html</goal>
|
||||
<goal>generate-pdf</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<phase>pre-site</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
|
||||
<classpathentry including="**/*.aj|**/*.java" kind="src" output="target/classes" path="src/main/java"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
|
||||
<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
#Wed Nov 17 12:20:43 EST 2010
|
||||
#Tue Mar 08 11:29:43 EST 2011
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
|
||||
org.eclipse.jdt.core.compiler.compliance=1.5
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.5
|
||||
org.eclipse.jdt.core.compiler.source=1.6
|
||||
|
||||
@@ -80,6 +80,24 @@
|
||||
<artifactId>jsr250-api</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
<version>1.0.0.GA</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>4.0.2.GA</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JPA -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.javax.persistence</groupId>
|
||||
<artifactId>hibernate-jpa-2.0-api</artifactId>
|
||||
<version>1.0.0.Final</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
@@ -108,6 +126,11 @@
|
||||
<name>Springframework Maven SNAPSHOT Repository</name>
|
||||
<url>http://maven.springframework.org/snapshot</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jboss-repository</id>
|
||||
<name>JBoss Public Repository</name>
|
||||
<url>http://repository.jboss.org/nexus/content/groups/public-jboss</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.springframework.persistence;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Listener interface for asynchronous storage operations.
|
||||
* Can be annotated with OnlyOnFailure as an optimization
|
||||
* if the listener is only interested in compensating transactions
|
||||
* in the event of write failure.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
*
|
||||
* @param <V> new value type
|
||||
*/
|
||||
public interface AsynchStoreCompletionListener<V> {
|
||||
|
||||
/**
|
||||
* Constant indicating no store completion action
|
||||
*/
|
||||
class NONE implements AsynchStoreCompletionListener<Object> {
|
||||
public void onCompletion(AsynchStoreCompletionListener.StoreResult result, Object newValue, Field foreignStore) {}
|
||||
}
|
||||
|
||||
enum StoreResult {
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
INDETERMINATE
|
||||
};
|
||||
|
||||
void onCompletion(StoreResult result, V newValue, Field foreignStore);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,13 +4,20 @@ Bundle-Vendor: SpringSource
|
||||
Bundle-ManifestVersion: 2
|
||||
Import-Package:
|
||||
sun.reflect;version="0";resolution:=optional
|
||||
Excluded-Imports:
|
||||
org.springframework.persistence,
|
||||
org.springframework.persistence.support
|
||||
Import-Template:
|
||||
org.springframework.beans.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.context.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.core.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.dao.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.orm.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.transaction..*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.util.*;version="[3.0.0, 4.0.0)",
|
||||
org.springframework.data.core.*;version="[1.0.0, 2.0.0)",
|
||||
org.springframework.data.persistence.*;version="[1.0.0, 2.0.0)",
|
||||
javax.validation.*;version="[1.0.0, 2.0.0)";resolution:=optional,
|
||||
javax.persistence.*;version="[1.0.0, 3.0.0)";resolution:=optional,
|
||||
org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional,
|
||||
org.apache.commons.logging.*;version="[1.1.1, 2.0.0)",
|
||||
org.aspectj.*;version="[1.6.5, 2.0.0)",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2010 the original author or authors.
|
||||
* Copyright 2008-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.
|
||||
@@ -22,16 +22,16 @@ import java.io.Serializable;
|
||||
* Simple interface for entities.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @param <PK> the type of the identifier
|
||||
* @param <ID> the type of the identifier
|
||||
*/
|
||||
public interface Persistable<PK extends Serializable> extends Serializable {
|
||||
public interface Persistable<ID extends Serializable> extends Serializable {
|
||||
|
||||
/**
|
||||
* Returns the id of the entity.
|
||||
*
|
||||
* @return the id
|
||||
*/
|
||||
PK getId();
|
||||
ID getId();
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -111,6 +111,24 @@ public class Sort implements
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the order registered for the given property.
|
||||
*
|
||||
* @param property
|
||||
* @return
|
||||
*/
|
||||
public Order getOrderFor(String property) {
|
||||
|
||||
for (Order order : this) {
|
||||
if (order.getProperty().equals(property)) {
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -15,29 +15,32 @@
|
||||
*/
|
||||
package org.springframework.data.repository.query;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* {@link SimpleParameterAccessor} is used to bind method parameters.
|
||||
* {@link ParameterAccessor} implementation using a {@link Parameters} instance
|
||||
* to find special parameters.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class SimpleParameterAccessor {
|
||||
public class ParametersParameterAccessor implements ParameterAccessor {
|
||||
|
||||
private final Parameters parameters;
|
||||
private final Object[] values;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleParameterAccessor}.
|
||||
* Creates a new {@link ParametersParameterAccessor}.
|
||||
*
|
||||
* @param parameters
|
||||
* @param values
|
||||
*/
|
||||
public SimpleParameterAccessor(Parameters parameters, Object[] values) {
|
||||
public ParametersParameterAccessor(Parameters parameters, Object[] values) {
|
||||
|
||||
Assert.notNull(parameters);
|
||||
Assert.notNull(values);
|
||||
@@ -50,11 +53,11 @@ public class SimpleParameterAccessor {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the {@link Pageable} of the parameters, if available. Returns
|
||||
* {@code null} otherwise.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @return
|
||||
* @see
|
||||
* org.springframework.data.repository.query.ParameterAccessor#getPageable()
|
||||
*/
|
||||
public Pageable getPageable() {
|
||||
|
||||
@@ -66,13 +69,11 @@ public class SimpleParameterAccessor {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @return
|
||||
* @see
|
||||
* org.springframework.data.repository.query.ParameterAccessor#getSort()
|
||||
*/
|
||||
public Sort getSort() {
|
||||
|
||||
@@ -88,17 +89,24 @@ public class SimpleParameterAccessor {
|
||||
}
|
||||
|
||||
|
||||
private Object getBindableValue(int index) {
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.query.ParameterAccessor#getBindableValue
|
||||
* (int)
|
||||
*/
|
||||
public Object getBindableValue(int index) {
|
||||
|
||||
return values[parameters.getBindableParameter(index).getIndex()];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a {@link BindableParameterIterator} to traverse all bindable
|
||||
* parameters.
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @return
|
||||
* @see
|
||||
* org.springframework.data.repository.query.ParameterAccessor#iterator()
|
||||
*/
|
||||
public BindableParameterIterator iterator() {
|
||||
|
||||
@@ -111,7 +119,7 @@ public class SimpleParameterAccessor {
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class BindableParameterIterator {
|
||||
private class BindableParameterIterator implements Iterator<Object> {
|
||||
|
||||
private int currentIndex = 0;
|
||||
|
||||
@@ -125,5 +133,27 @@ public class SimpleParameterAccessor {
|
||||
|
||||
return getBindableValue(currentIndex++);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.util.Iterator#hasNext()
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
|
||||
return values.length <= currentIndex;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.util.Iterator#remove()
|
||||
*/
|
||||
public void remove() {
|
||||
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,8 @@ public interface QueryLookupStrategy {
|
||||
* that can be executed afterwards.
|
||||
*
|
||||
* @param method
|
||||
* @param domainClass
|
||||
* @return
|
||||
*/
|
||||
RepositoryQuery resolveQuery(Method method);
|
||||
RepositoryQuery resolveQuery(Method method, Class<?> domainClass);
|
||||
}
|
||||
@@ -23,6 +23,8 @@ import java.util.List;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.support.EntityMetadata;
|
||||
import org.springframework.data.repository.util.ClassUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
@@ -35,6 +37,11 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class QueryMethod {
|
||||
|
||||
public static enum Type {
|
||||
|
||||
SINGLE_ENTITY, PAGING, COLLECTION, MODIFYING;
|
||||
}
|
||||
|
||||
private final Method method;
|
||||
private final Parameters parameters;
|
||||
|
||||
@@ -82,27 +89,21 @@ public class QueryMethod {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the given
|
||||
*
|
||||
* @param number
|
||||
* @return
|
||||
*/
|
||||
public boolean isCorrectNumberOfParameters(int number) {
|
||||
public EntityMetadata<?> getEntityMetadata() {
|
||||
|
||||
return number == parameters.getBindableParameters()
|
||||
.getNumberOfParameters();
|
||||
return new EntityMetadata() {
|
||||
|
||||
public Class<?> getJavaType() {
|
||||
|
||||
return getDomainClass();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the domain class for this method.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Class<?> getDomainClass() {
|
||||
protected Class<?> getDomainClass() {
|
||||
|
||||
return getReturnedDomainClass(method);
|
||||
return ClassUtils.getReturnedDomainClass(method);
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +113,7 @@ public class QueryMethod {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isCollectionQuery() {
|
||||
protected boolean isCollectionQuery() {
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
return org.springframework.util.ClassUtils.isAssignable(List.class,
|
||||
@@ -125,7 +126,7 @@ public class QueryMethod {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isPageQuery() {
|
||||
protected boolean isPageQuery() {
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
return org.springframework.util.ClassUtils.isAssignable(Page.class,
|
||||
@@ -133,6 +134,30 @@ public class QueryMethod {
|
||||
}
|
||||
|
||||
|
||||
public Type getType() {
|
||||
|
||||
if (isModifyingQuery()) {
|
||||
return Type.MODIFYING;
|
||||
}
|
||||
|
||||
if (isPageQuery()) {
|
||||
return Type.PAGING;
|
||||
}
|
||||
|
||||
if (isCollectionQuery()) {
|
||||
return Type.COLLECTION;
|
||||
}
|
||||
|
||||
return Type.SINGLE_ENTITY;
|
||||
}
|
||||
|
||||
|
||||
protected boolean isModifyingQuery() {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the {@link Parameters} wrapper to gain additional information
|
||||
* about {@link Method} parameters.
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.repository.query;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Interface for a query abstraction.
|
||||
*
|
||||
@@ -30,4 +32,12 @@ public interface RepositoryQuery {
|
||||
* @return
|
||||
*/
|
||||
public Object execute(Object[] parameters);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public QueryMethod getQueryMethod();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2010 the original author or authors.
|
||||
* Copyright 2008-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.
|
||||
@@ -15,9 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.repository.query.parser;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.SimpleParameterAccessor;
|
||||
import org.springframework.data.repository.query.SimpleParameterAccessor.BindableParameterIterator;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.parser.PartTree.OrPart;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -32,19 +34,18 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public abstract class AbstractQueryCreator<T, S> {
|
||||
|
||||
private final SimpleParameterAccessor parameters;
|
||||
private final ParameterAccessor parameters;
|
||||
private final PartTree tree;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractQueryCreator} for the given {@link PartTree}
|
||||
* and {@link SimpleParameterAccessor}.
|
||||
* and {@link ParametersParameterAccessor}.
|
||||
*
|
||||
* @param tree
|
||||
* @param parameters
|
||||
*/
|
||||
public AbstractQueryCreator(PartTree tree,
|
||||
SimpleParameterAccessor parameters) {
|
||||
public AbstractQueryCreator(PartTree tree, ParameterAccessor parameters) {
|
||||
|
||||
Assert.notNull(tree);
|
||||
Assert.notNull(parameters);
|
||||
@@ -61,7 +62,10 @@ public abstract class AbstractQueryCreator<T, S> {
|
||||
*/
|
||||
public T createQuery() {
|
||||
|
||||
return complete(createCriteria(tree), tree.getSort());
|
||||
Sort treeSort = tree.getSort();
|
||||
Sort sort = treeSort != null ? treeSort : parameters.getSort();
|
||||
|
||||
return complete(createCriteria(tree), sort);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +79,7 @@ public abstract class AbstractQueryCreator<T, S> {
|
||||
private S createCriteria(PartTree tree) {
|
||||
|
||||
S base = null;
|
||||
BindableParameterIterator iterator = parameters.iterator();
|
||||
Iterator<Object> iterator = parameters.iterator();
|
||||
|
||||
for (OrPart node : tree) {
|
||||
|
||||
@@ -102,7 +106,7 @@ public abstract class AbstractQueryCreator<T, S> {
|
||||
* @param iterator
|
||||
* @return
|
||||
*/
|
||||
protected abstract S create(Part part, BindableParameterIterator iterator);
|
||||
protected abstract S create(Part part, Iterator<Object> iterator);
|
||||
|
||||
|
||||
/**
|
||||
@@ -114,8 +118,7 @@ public abstract class AbstractQueryCreator<T, S> {
|
||||
* @param iterator
|
||||
* @return
|
||||
*/
|
||||
protected abstract S and(Part part, S base,
|
||||
BindableParameterIterator iterator);
|
||||
protected abstract S and(Part part, S base, Iterator<Object> iterator);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -113,6 +113,18 @@ public class Part {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return String.format("%s %s", property.getName(), type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the type
|
||||
*/
|
||||
@@ -129,31 +141,34 @@ public class Part {
|
||||
*/
|
||||
public static enum Type {
|
||||
|
||||
BETWEEN(null, 2, "Between"),
|
||||
BETWEEN(2, "Between"),
|
||||
|
||||
IS_NOT_NULL(null, 0, "IsNotNull", "NotNull"),
|
||||
IS_NOT_NULL(0, "IsNotNull", "NotNull"),
|
||||
|
||||
IS_NULL(null, 0, "IsNull", "Null"),
|
||||
IS_NULL(0, "IsNull", "Null"),
|
||||
|
||||
LESS_THAN("<", "LessThan"),
|
||||
LESS_THAN("LessThan"),
|
||||
|
||||
GREATER_THAN(">", "GreaterThan"),
|
||||
GREATER_THAN("GreaterThan"),
|
||||
|
||||
NOT_LIKE("not like", "NotLike"),
|
||||
NOT_LIKE("NotLike"),
|
||||
|
||||
LIKE("like", "Like"),
|
||||
LIKE("Like"),
|
||||
|
||||
NEGATING_SIMPLE_PROPERTY("<>", "Not"),
|
||||
NOT_IN("NotIn"),
|
||||
|
||||
SIMPLE_PROPERTY("=");
|
||||
IN("In"),
|
||||
|
||||
NEGATING_SIMPLE_PROPERTY("Not"),
|
||||
|
||||
SIMPLE_PROPERTY;
|
||||
|
||||
// Need to list them again explicitly as the order is important
|
||||
// (esp. for IS_NULL, IS_NOT_NULL)
|
||||
private static final List<Part.Type> ALL = Arrays.asList(IS_NOT_NULL,
|
||||
IS_NULL, BETWEEN, LESS_THAN, GREATER_THAN, NOT_LIKE, LIKE,
|
||||
NEGATING_SIMPLE_PROPERTY, SIMPLE_PROPERTY);
|
||||
NOT_IN, IN, NEGATING_SIMPLE_PROPERTY, SIMPLE_PROPERTY);
|
||||
private List<String> keywords;
|
||||
private String operator;
|
||||
private int numberOfArguments;
|
||||
|
||||
|
||||
@@ -166,17 +181,16 @@ public class Part {
|
||||
* @param numberOfArguments
|
||||
* @param keywords
|
||||
*/
|
||||
private Type(String operator, int numberOfArguments, String... keywords) {
|
||||
private Type(int numberOfArguments, String... keywords) {
|
||||
|
||||
this.operator = operator;
|
||||
this.numberOfArguments = numberOfArguments;
|
||||
this.keywords = Arrays.asList(keywords);
|
||||
}
|
||||
|
||||
|
||||
private Type(String operator, String... keywords) {
|
||||
private Type(String... keywords) {
|
||||
|
||||
this(operator, 1, keywords);
|
||||
this(1, keywords);
|
||||
}
|
||||
|
||||
|
||||
@@ -202,12 +216,6 @@ public class Part {
|
||||
}
|
||||
|
||||
|
||||
public String getOperator() {
|
||||
|
||||
return this.operator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the the type supports the given raw property. Default
|
||||
* implementation checks whether the property ends with the registered
|
||||
|
||||
@@ -21,11 +21,13 @@ import static java.util.regex.Pattern.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.parser.PartTree.OrPart;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
||||
/**
|
||||
@@ -40,11 +42,13 @@ import org.springframework.util.Assert;
|
||||
public class PartTree implements Iterable<OrPart> {
|
||||
|
||||
private static final String ORDER_BY = "OrderBy";
|
||||
private static final String[] PREFIXES = new String[] { "findBy", "find",
|
||||
"readBy", "read", "getBy", "get" };
|
||||
private static final String PREFIX_TEMPLATE = "^%s(?=[A-Z]).*";
|
||||
private static final String KEYWORD_TEMPLATE = "(%s)(?=[A-Z])";
|
||||
private static final String DISTINCT = "Distinct";
|
||||
|
||||
private static final Pattern PREFIX_TEMPLATE = Pattern
|
||||
.compile("^(find|read|get)(\\p{Upper}.*?)??By");
|
||||
|
||||
private final boolean distinct;
|
||||
private final OrderBySource orderBySource;
|
||||
private final List<OrPart> nodes = new ArrayList<PartTree.OrPart>();
|
||||
|
||||
@@ -61,6 +65,7 @@ public class PartTree implements Iterable<OrPart> {
|
||||
Assert.notNull(source);
|
||||
Assert.notNull(domainClass);
|
||||
|
||||
this.distinct = detectDistinct(source);
|
||||
String foo = strip(source);
|
||||
String[] parts = split(foo, ORDER_BY);
|
||||
|
||||
@@ -109,6 +114,37 @@ public class PartTree implements Iterable<OrPart> {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether we indicate distinct lookup of entities.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isDistinct() {
|
||||
|
||||
return distinct;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an {@link Iterable} of all parts contained in the
|
||||
* {@link PartTree}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Iterable<Part> getParts() {
|
||||
|
||||
List<Part> result = new ArrayList<Part>();
|
||||
|
||||
for (OrPart orPart : this) {
|
||||
for (Part part : orPart) {
|
||||
result.add(part);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Splits the given text at the given keywords. Expects camelcase style to
|
||||
* only match concrete keywords and not derivatives of it.
|
||||
@@ -135,15 +171,47 @@ public class PartTree implements Iterable<OrPart> {
|
||||
*/
|
||||
private String strip(String methodName) {
|
||||
|
||||
for (String prefix : PREFIXES) {
|
||||
Matcher matcher = PREFIX_TEMPLATE.matcher(methodName);
|
||||
|
||||
String regex = format(PREFIX_TEMPLATE, prefix);
|
||||
if (methodName.matches(regex)) {
|
||||
return methodName.substring(prefix.length());
|
||||
}
|
||||
if (matcher.find()) {
|
||||
return methodName.substring(matcher.group().length());
|
||||
} else {
|
||||
return methodName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the given source string contains the {@link #DISTINCT}
|
||||
* keyword in it's prefix.
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
*/
|
||||
private boolean detectDistinct(String source) {
|
||||
|
||||
Matcher matcher = PREFIX_TEMPLATE.matcher(source);
|
||||
|
||||
if (!matcher.find()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return methodName;
|
||||
String group = matcher.group(2);
|
||||
return group != null && group.contains(DISTINCT);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return String.format("%s %s",
|
||||
StringUtils.collectionToDelimitedString(nodes, " or "),
|
||||
orderBySource.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,6 +244,18 @@ public class PartTree implements Iterable<OrPart> {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return StringUtils.collectionToDelimitedString(children, " and ");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2010 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,17 +16,16 @@
|
||||
package org.springframework.data.repository.support;
|
||||
|
||||
/**
|
||||
* Interface to abstract the ways to retrieve the id of the given entity.
|
||||
* Metadata for entity types.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface IdAware {
|
||||
public interface EntityMetadata<T> {
|
||||
|
||||
/**
|
||||
* Returns the id of the given entity.
|
||||
* Returns the actual domain class type.
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
Object getId(Object entity);
|
||||
}
|
||||
Class<T> getJavaType();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2010 the original author or authors.
|
||||
* Copyright 2008-201 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.
|
||||
@@ -19,13 +19,26 @@ import org.springframework.data.domain.Persistable;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link IsNewAware} that assumes the entity handled
|
||||
* Implementation of {@link EntityMetadata} that assumes the entity handled
|
||||
* implements {@link Persistable} and uses {@link Persistable#isNew()} for the
|
||||
* {@link #isNew(Object)} check.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class PersistableEntityInformation implements IsNewAware, IdAware {
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class PersistableEntityInformation<T extends Persistable> extends
|
||||
AbstractEntityInformation<T> {
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistableEntityInformation}.
|
||||
*
|
||||
* @param domainClass
|
||||
*/
|
||||
public PersistableEntityInformation(Class<T> domainClass) {
|
||||
|
||||
super(domainClass);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -34,9 +47,10 @@ public class PersistableEntityInformation implements IsNewAware, IdAware {
|
||||
* org.springframework.data.repository.support.IsNewAware#isNew(java.lang
|
||||
* .Object)
|
||||
*/
|
||||
public boolean isNew(Object entity) {
|
||||
@Override
|
||||
public boolean isNew(T entity) {
|
||||
|
||||
return ((Persistable<?>) entity).isNew();
|
||||
return entity.isNew();
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +61,8 @@ public class PersistableEntityInformation implements IsNewAware, IdAware {
|
||||
* org.springframework.data.repository.support.IdAware#getId(java.lang.Object
|
||||
* )
|
||||
*/
|
||||
public Object getId(Object entity) {
|
||||
public Object getId(T entity) {
|
||||
|
||||
return ((Persistable<?>) entity).getId();
|
||||
return entity.getId();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2010 the original author or authors.
|
||||
* 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.
|
||||
@@ -15,19 +15,21 @@
|
||||
*/
|
||||
package org.springframework.data.repository.support;
|
||||
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
|
||||
|
||||
/**
|
||||
* Interface to abstract the ways to determine if the given entity is to be
|
||||
* considered as new.
|
||||
* Callback for listeners that want to execute functionality on
|
||||
* {@link RepositoryQuery} creation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface IsNewAware {
|
||||
public interface QueryCreationListener<T extends RepositoryQuery> {
|
||||
|
||||
/**
|
||||
* Returns whether the given entity is considered to be new.
|
||||
* Will be invoked just after the {@link RepositoryQuery} was created.
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
* @param query
|
||||
*/
|
||||
boolean isNew(Object entity);
|
||||
}
|
||||
void onCreation(T query);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2010 the original author or authors.
|
||||
* Copyright 2008-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.
|
||||
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.repository.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Required;
|
||||
@@ -124,24 +121,6 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<?, ?>>
|
||||
|
||||
this.factory = createRepositoryFactory();
|
||||
this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);
|
||||
this.factory.validate(repositoryInterface, customImplementation);
|
||||
|
||||
for (RepositoryProxyPostProcessor processor : getRepositoryPostProcessors()) {
|
||||
this.factory.addRepositoryProxyPostProcessor(processor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all {@link RepositoryProxyPostProcessor} to be added to the
|
||||
* repository factory to be created. Default implementation will return an
|
||||
* empty list.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected List<RepositoryProxyPostProcessor> getRepositoryPostProcessors() {
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,21 +15,18 @@
|
||||
*/
|
||||
package org.springframework.data.repository.support;
|
||||
|
||||
import static org.springframework.data.repository.util.ClassUtils.*;
|
||||
import static org.springframework.util.ReflectionUtils.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
@@ -50,12 +47,11 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public abstract class RepositoryFactorySupport {
|
||||
|
||||
private QueryLookupStrategy.Key queryLookupStrategyKey;
|
||||
|
||||
private final Map<Method, Method> methodCache =
|
||||
new ConcurrentHashMap<Method, Method>();
|
||||
private final List<RepositoryProxyPostProcessor> postProcessors =
|
||||
new ArrayList<RepositoryProxyPostProcessor>();
|
||||
private QueryLookupStrategy.Key queryLookupStrategyKey;
|
||||
private List<QueryCreationListener<?>> queryPostProcessors =
|
||||
new ArrayList<QueryCreationListener<?>>();
|
||||
|
||||
|
||||
/**
|
||||
@@ -69,6 +65,20 @@ public abstract class RepositoryFactorySupport {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a {@link QueryCreationListener} to the factory to plug in
|
||||
* functionality triggered right after creation of {@link RepositoryQuery}
|
||||
* instances.
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public void addQueryCreationListener(QueryCreationListener<?> listener) {
|
||||
|
||||
Assert.notNull(listener);
|
||||
this.queryPostProcessors.add(listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds {@link RepositoryProxyPostProcessor}s to the factory to allow
|
||||
* manipulation of the {@link ProxyFactory} before the proxy gets created.
|
||||
@@ -78,7 +88,7 @@ public abstract class RepositoryFactorySupport {
|
||||
*
|
||||
* @param processor
|
||||
*/
|
||||
protected void addRepositoryProxyPostProcessor(
|
||||
public void addRepositoryProxyPostProcessor(
|
||||
RepositoryProxyPostProcessor processor) {
|
||||
|
||||
Assert.notNull(processor);
|
||||
@@ -109,15 +119,17 @@ public abstract class RepositoryFactorySupport {
|
||||
* @param customImplementation
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Repository<?, ?>> T getRepository(
|
||||
Class<T> repositoryInterface, Object customImplementation) {
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public <T> T getRepository(Class<T> repositoryInterface,
|
||||
Object customImplementation) {
|
||||
|
||||
validate(repositoryInterface, customImplementation);
|
||||
RepositoryMetadata metadata =
|
||||
new DefaultRepositoryMetadata(repositoryInterface,
|
||||
getRepositoryBaseClass(repositoryInterface));
|
||||
|
||||
Class<?> domainClass = getDomainClass(repositoryInterface);
|
||||
RepositorySupport<?, ?> target =
|
||||
getTargetRepository(domainClass, repositoryInterface);
|
||||
validate(metadata, customImplementation);
|
||||
|
||||
Object target = getTargetRepository(metadata);
|
||||
|
||||
// Create proxy
|
||||
ProxyFactory result = new ProxyFactory();
|
||||
@@ -128,32 +140,31 @@ public abstract class RepositoryFactorySupport {
|
||||
processor.postProcess(result);
|
||||
}
|
||||
|
||||
result.addAdvice(new QueryExecuterMethodInterceptor(
|
||||
repositoryInterface, customImplementation, target));
|
||||
result.addAdvice(new QueryExecuterMethodInterceptor(metadata,
|
||||
customImplementation, target));
|
||||
|
||||
return (T) result.getProxy();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link RepositorySupport} instance as backing for the query
|
||||
* proxy.
|
||||
* Create a repository instance as backing for the query proxy.
|
||||
*
|
||||
* @param <T>
|
||||
* @param domainClass
|
||||
* @return
|
||||
*/
|
||||
protected abstract <T, ID extends Serializable> RepositorySupport<T, ID> getTargetRepository(
|
||||
Class<T> domainClass, Class<?> repositoryInterface);
|
||||
protected abstract Object getTargetRepository(RepositoryMetadata metadata);
|
||||
|
||||
|
||||
/**
|
||||
* Determines the base class for the repository to be created.
|
||||
* Returns the base class backing the actual repository instance. Make sure
|
||||
* {@link #getTargetRepository(RepositoryMetadata)} returns an instance of
|
||||
* this class.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected abstract Class<? extends RepositorySupport> getRepositoryClass(
|
||||
protected abstract Class<?> getRepositoryBaseClass(
|
||||
Class<?> repositoryInterface);
|
||||
|
||||
|
||||
@@ -166,165 +177,23 @@ public abstract class RepositoryFactorySupport {
|
||||
protected abstract QueryLookupStrategy getQueryLookupStrategy(Key key);
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private boolean hasCustomMethod(
|
||||
Class<? extends Repository<?, ?>> repositoryInterface) {
|
||||
|
||||
boolean hasCustomMethod = false;
|
||||
|
||||
// No detection required if no typing interface was configured
|
||||
if (isGenericRepositoryInterface(repositoryInterface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Method method : repositoryInterface.getMethods()) {
|
||||
|
||||
if (isCustomMethod(method, repositoryInterface)
|
||||
&& !isBaseClassMethod(method, repositoryInterface)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasCustomMethod;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the given method is considered to be a repository base
|
||||
* class method.
|
||||
*
|
||||
* @param method
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
private boolean isBaseClassMethod(Method method,
|
||||
Class<?> repositoryInterface) {
|
||||
|
||||
Assert.notNull(method);
|
||||
|
||||
if (method.getDeclaringClass().isAssignableFrom(
|
||||
getRepositoryClass(repositoryInterface))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !method.equals(getBaseClassMethod(method, repositoryInterface));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the base class method that is backing the given method. This can
|
||||
* be necessary if a repository interface redeclares a method in
|
||||
* {@link Repository} (e.g. for transaction behaviour customization).
|
||||
* Returns the method itself if the base class does not implement the given
|
||||
* method.
|
||||
*
|
||||
* @param method
|
||||
* @return
|
||||
*/
|
||||
private Method getBaseClassMethod(Method method,
|
||||
Class<?> repositoryInterface) {
|
||||
|
||||
Assert.notNull(method);
|
||||
|
||||
Method result = methodCache.get(method);
|
||||
|
||||
if (null != result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result =
|
||||
getBaseClassMethodFor(method,
|
||||
getRepositoryClass(repositoryInterface),
|
||||
repositoryInterface);
|
||||
methodCache.put(method, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the given method is a custom repository method.
|
||||
*
|
||||
* @param method
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
private boolean isCustomMethod(Method method, Class<?> repositoryInterface) {
|
||||
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
|
||||
boolean isQueryMethod = declaringClass.equals(repositoryInterface);
|
||||
boolean isRepositoryInterface =
|
||||
isGenericRepositoryInterface(declaringClass);
|
||||
boolean isBaseClassMethod =
|
||||
isBaseClassMethod(method, repositoryInterface);
|
||||
|
||||
return !(isRepositoryInterface || isBaseClassMethod || isQueryMethod);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all methods considered to be finder methods.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
private Iterable<Method> getFinderMethods(Class<?> repositoryInterface) {
|
||||
|
||||
Set<Method> result = new HashSet<Method>();
|
||||
|
||||
for (Method method : repositoryInterface.getDeclaredMethods()) {
|
||||
if (!isCustomMethod(method, repositoryInterface)
|
||||
&& !isBaseClassMethod(method, repositoryInterface)) {
|
||||
result.add(method);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates the given repository interface.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
*/
|
||||
private void validate(Class<?> repositoryInterface) {
|
||||
|
||||
Assert.notNull(repositoryInterface);
|
||||
Assert.notNull(
|
||||
getDomainClass(repositoryInterface),
|
||||
"Could not retrieve domain class from interface. Make sure it extends GenericRepository.");
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates the given repository interface as well as the given custom
|
||||
* implementation.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
* @param repositoryMetadata
|
||||
* @param customImplementation
|
||||
*/
|
||||
protected void validate(
|
||||
Class<? extends Repository<?, ?>> repositoryInterface,
|
||||
protected void validate(RepositoryMetadata repositoryMetadata,
|
||||
Object customImplementation) {
|
||||
|
||||
validate(repositoryInterface);
|
||||
|
||||
if (null == customImplementation
|
||||
&& hasCustomMethod(repositoryInterface)) {
|
||||
&& repositoryMetadata.hasCustomMethod()) {
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"You have custom methods in %s but not provided a custom implementation!",
|
||||
repositoryInterface));
|
||||
repositoryMetadata.getRepositoryInterface()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,8 +212,8 @@ public abstract class RepositoryFactorySupport {
|
||||
new ConcurrentHashMap<Method, RepositoryQuery>();
|
||||
|
||||
private final Object customImplementation;
|
||||
private final Class<?> repositoryInterface;
|
||||
private final RepositorySupport<?, ?> target;
|
||||
private final RepositoryMetadata metadata;
|
||||
private final Object target;
|
||||
|
||||
|
||||
/**
|
||||
@@ -352,19 +221,40 @@ public abstract class RepositoryFactorySupport {
|
||||
* of {@link QueryMethod}s to be invoked on execution of repository
|
||||
* interface methods.
|
||||
*/
|
||||
public QueryExecuterMethodInterceptor(Class<?> repositoryInterface,
|
||||
Object customImplementation, RepositorySupport<?, ?> target) {
|
||||
public QueryExecuterMethodInterceptor(
|
||||
RepositoryMetadata repositoryMetadata,
|
||||
Object customImplementation, Object target) {
|
||||
|
||||
this.repositoryInterface = repositoryInterface;
|
||||
this.metadata = repositoryMetadata;
|
||||
this.customImplementation = customImplementation;
|
||||
this.target = target;
|
||||
|
||||
QueryLookupStrategy lookupStrategy =
|
||||
getQueryLookupStrategy(queryLookupStrategyKey);
|
||||
|
||||
for (Method method : getFinderMethods(repositoryInterface)) {
|
||||
for (Method method : metadata.getQueryMethods()) {
|
||||
RepositoryQuery query =
|
||||
lookupStrategy.resolveQuery(method,
|
||||
repositoryMetadata.getDomainClass());
|
||||
invokeListeners(query, metadata);
|
||||
queries.put(method, query);
|
||||
}
|
||||
}
|
||||
|
||||
queries.put(method, lookupStrategy.resolveQuery(method));
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private void invokeListeners(RepositoryQuery query,
|
||||
RepositoryMetadata metadata) {
|
||||
|
||||
for (QueryCreationListener listener : queryPostProcessors) {
|
||||
Class<?> typeArgument =
|
||||
GenericTypeResolver.resolveTypeArgument(
|
||||
listener.getClass(),
|
||||
QueryCreationListener.class);
|
||||
if (typeArgument != null
|
||||
&& typeArgument.isAssignableFrom(query.getClass())) {
|
||||
listener.onCreation(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,8 +283,7 @@ public abstract class RepositoryFactorySupport {
|
||||
|
||||
// Lookup actual method as it might be redeclared in the interface
|
||||
// and we have to use the repository instance nevertheless
|
||||
Method actualMethod =
|
||||
getBaseClassMethod(method, repositoryInterface);
|
||||
Method actualMethod = metadata.getBaseClassMethod(method);
|
||||
return executeMethodOn(target, actualMethod,
|
||||
invocation.getArguments());
|
||||
}
|
||||
@@ -449,7 +338,7 @@ public abstract class RepositoryFactorySupport {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isCustomMethod(invocation.getMethod(), repositoryInterface);
|
||||
return metadata.isCustomMethod(invocation.getMethod());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.repository.support;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
@@ -59,20 +56,32 @@ public abstract class TransactionalRepositoryFactoryBeanSupport<T extends Reposi
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
/**
|
||||
* Delegates {@link RepositoryFactorySupport} creation to
|
||||
* {@link #doCreateRepositoryFactory()} and applies the
|
||||
* {@link TransactionalRepositoryProxyPostProcessor} to the created
|
||||
* instance.
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.support.RepositoryFactoryBeanSupport
|
||||
* #getRepositoryPostProcessors()
|
||||
* @see org.springframework.data.repository.support.RepositoryFactoryBeanSupport
|
||||
* #createRepositoryFactory()
|
||||
*/
|
||||
@Override
|
||||
public List<RepositoryProxyPostProcessor> getRepositoryPostProcessors() {
|
||||
protected final RepositoryFactorySupport createRepositoryFactory() {
|
||||
|
||||
return Arrays.asList(txPostProcessor);
|
||||
RepositoryFactorySupport factory = doCreateRepositoryFactory();
|
||||
factory.addRepositoryProxyPostProcessor(txPostProcessor);
|
||||
return factory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the actual {@link RepositoryFactorySupport} instance.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract RepositoryFactorySupport doCreateRepositoryFactory();
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
||||
@@ -15,17 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.repository.util;
|
||||
|
||||
import static org.springframework.core.GenericTypeResolver.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -38,13 +35,6 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public abstract class ClassUtils {
|
||||
|
||||
@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 constructor to prevent instantiation.
|
||||
*/
|
||||
@@ -53,40 +43,6 @@ public abstract class ClassUtils {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the domain class the given class is declared for. Will introspect
|
||||
* the given class for extensions of {@link Repository} and retrieve the
|
||||
* domain class type from its generics declaration.
|
||||
*
|
||||
* @param clazz
|
||||
* @return the domain class the given class is repository for or
|
||||
* {@code null} if none found.
|
||||
*/
|
||||
public static Class<?> getDomainClass(Class<?> clazz) {
|
||||
|
||||
Class<?>[] arguments = resolveTypeArguments(clazz, Repository.class);
|
||||
return arguments == null ? null : arguments[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the id class the given class is declared for. Will introspect the
|
||||
* given class for extensions of {@link Repository} or and retrieve the
|
||||
* {@link Serializable} type from its generics declaration.
|
||||
*
|
||||
* @param clazz
|
||||
* @return the id class the given class is repository for or {@code null} if
|
||||
* none found.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Class<? extends Serializable> getIdClass(Class<?> clazz) {
|
||||
|
||||
Class<?>[] arguments = resolveTypeArguments(clazz, Repository.class);
|
||||
return (Class<? extends Serializable>) (arguments == null ? null
|
||||
: arguments[1]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the domain class returned by the given {@link Method}. Will
|
||||
* extract the type from {@link Collection}s and
|
||||
@@ -97,15 +53,18 @@ public abstract class ClassUtils {
|
||||
*/
|
||||
public static Class<?> getReturnedDomainClass(Method method) {
|
||||
|
||||
Type type = method.getGenericReturnType();
|
||||
Class<?> returnType = method.getReturnType();
|
||||
|
||||
if (Collection.class.isAssignableFrom(returnType)
|
||||
|| Page.class.isAssignableFrom(returnType)) {
|
||||
|
||||
Type type = method.getGenericReturnType();
|
||||
|
||||
if (type instanceof ParameterizedType) {
|
||||
return (Class<?>) ((ParameterizedType) type)
|
||||
.getActualTypeArguments()[0];
|
||||
|
||||
} else {
|
||||
return method.getReturnType();
|
||||
}
|
||||
|
||||
return returnType;
|
||||
}
|
||||
|
||||
|
||||
@@ -241,114 +200,4 @@ public abstract class ClassUtils {
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the given base class' method if the given method (declared in the
|
||||
* interface) was also declared at the base class. Returns the given method
|
||||
* if the given base class does not declare the method given. Takes generics
|
||||
* into account.
|
||||
*
|
||||
* @param method
|
||||
* @param baseClass
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
public static Method getBaseClassMethodFor(Method method,
|
||||
Class<?> baseClass, Class<?> repositoryInterface) {
|
||||
|
||||
for (Method baseClassMethod : baseClass.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, repositoryInterface)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return baseClassMethod;
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
private static boolean parametersMatch(Method method,
|
||||
Method baseClassMethod, Class<?> repositoryInterface) {
|
||||
|
||||
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],
|
||||
repositoryInterface)) {
|
||||
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
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
private static boolean matchesGenericType(String name,
|
||||
Class<?> parameterType, Class<?> repositoryInterface) {
|
||||
|
||||
Class<?> entityType = getDomainClass(repositoryInterface);
|
||||
Class<?> idClass = getIdClass(repositoryInterface);
|
||||
|
||||
if (ID_TYPE_NAME.equals(name) && parameterType.equals(idClass)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DOMAIN_TYPE_NAME.equals(name) && parameterType.equals(entityType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,13 @@ public abstract class AbstractConstructorEntityInstantiator<BACKING_INTERFACE, S
|
||||
if (noArgConstructor != null) {
|
||||
log.info("Using " + c + " no-arg constructor");
|
||||
StateProvider.setUnderlyingState(n);
|
||||
T t = noArgConstructor.newInstance();
|
||||
setState(t, n);
|
||||
T t;
|
||||
try {
|
||||
t = noArgConstructor.newInstance();
|
||||
setState(t, n);
|
||||
} finally {
|
||||
StateProvider.retrieveState();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.springframework.data.domain.Sort;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SimpleParameterAccessor}.
|
||||
* Unit tests for {@link ParametersParameterAccessor}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@@ -52,43 +52,43 @@ public class SimpleParameterAccessorUnitTests {
|
||||
@Test
|
||||
public void testname() throws Exception {
|
||||
|
||||
new SimpleParameterAccessor(parameters, new Object[] { "test" });
|
||||
new ParametersParameterAccessor(parameters, new Object[] { "test" });
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullParameters() throws Exception {
|
||||
|
||||
new SimpleParameterAccessor(null, new Object[0]);
|
||||
new ParametersParameterAccessor(null, new Object[0]);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullValues() throws Exception {
|
||||
|
||||
new SimpleParameterAccessor(parameters, null);
|
||||
new ParametersParameterAccessor(parameters, null);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsTooLittleNumberOfArguments() throws Exception {
|
||||
|
||||
new SimpleParameterAccessor(parameters, new Object[0]);
|
||||
new ParametersParameterAccessor(parameters, new Object[0]);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsTooManyArguments() throws Exception {
|
||||
|
||||
new SimpleParameterAccessor(parameters, new Object[] { "test", "test" });
|
||||
new ParametersParameterAccessor(parameters, new Object[] { "test", "test" });
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void returnsNullForPageableAndSortIfNoneAvailable() throws Exception {
|
||||
|
||||
SimpleParameterAccessor accessor =
|
||||
new SimpleParameterAccessor(parameters, new Object[] { "test" });
|
||||
ParameterAccessor accessor =
|
||||
new ParametersParameterAccessor(parameters, new Object[] { "test" });
|
||||
assertThat(accessor.getPageable(), is(nullValue()));
|
||||
assertThat(accessor.getSort(), is(nullValue()));
|
||||
}
|
||||
@@ -98,8 +98,8 @@ public class SimpleParameterAccessorUnitTests {
|
||||
public void returnsSortIfAvailable() {
|
||||
|
||||
Sort sort = new Sort("foo");
|
||||
SimpleParameterAccessor accessor =
|
||||
new SimpleParameterAccessor(sortParameters, new Object[] {
|
||||
ParameterAccessor accessor =
|
||||
new ParametersParameterAccessor(sortParameters, new Object[] {
|
||||
"test", sort });
|
||||
assertThat(accessor.getSort(), is(sort));
|
||||
assertThat(accessor.getPageable(), is(nullValue()));
|
||||
@@ -110,8 +110,8 @@ public class SimpleParameterAccessorUnitTests {
|
||||
public void returnsPageableIfAvailable() {
|
||||
|
||||
Pageable pageable = new PageRequest(0, 10);
|
||||
SimpleParameterAccessor accessor =
|
||||
new SimpleParameterAccessor(pageableParameters, new Object[] {
|
||||
ParameterAccessor accessor =
|
||||
new ParametersParameterAccessor(pageableParameters, new Object[] {
|
||||
"test", pageable });
|
||||
assertThat(accessor.getPageable(), is(pageable));
|
||||
assertThat(accessor.getSort(), is(nullValue()));
|
||||
@@ -123,8 +123,8 @@ public class SimpleParameterAccessorUnitTests {
|
||||
|
||||
Sort sort = new Sort("foo");
|
||||
Pageable pageable = new PageRequest(0, 10, sort);
|
||||
SimpleParameterAccessor accessor =
|
||||
new SimpleParameterAccessor(pageableParameters, new Object[] {
|
||||
ParameterAccessor accessor =
|
||||
new ParametersParameterAccessor(pageableParameters, new Object[] {
|
||||
"test", pageable });
|
||||
assertThat(accessor.getPageable(), is(pageable));
|
||||
assertThat(accessor.getSort(), is(sort));
|
||||
|
||||
@@ -82,6 +82,17 @@ public class PartTreeUnitTests {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void parsesCombinedAndAndOrPropertiesCorrectly() throws Exception {
|
||||
|
||||
PartTree tree =
|
||||
new PartTree("firstnameAndLastnameOrLastname", User.class);
|
||||
assertPart(tree, new Part[] { new Part("firstname", User.class),
|
||||
new Part("lastname", User.class) }, new Part[] { new Part(
|
||||
"lastname", User.class) });
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void hasSortIfOrderByIsGiven() throws Exception {
|
||||
|
||||
@@ -91,6 +102,32 @@ public class PartTreeUnitTests {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void detectsDistinctCorrectly() throws Exception {
|
||||
|
||||
PartTree tree = new PartTree("findDistinctByLastname", User.class);
|
||||
assertThat(tree.isDistinct(), is(true));
|
||||
|
||||
tree = new PartTree("findUsersDistinctByLastname", User.class);
|
||||
assertThat(tree.isDistinct(), is(true));
|
||||
|
||||
tree = new PartTree("findDistinctUsersByLastname", User.class);
|
||||
assertThat(tree.isDistinct(), is(true));
|
||||
|
||||
tree = new PartTree("findUsersByLastname", User.class);
|
||||
assertThat(tree.isDistinct(), is(false));
|
||||
|
||||
tree = new PartTree("findByLastname", User.class);
|
||||
assertThat(tree.isDistinct(), is(false));
|
||||
|
||||
// Check it's non-greedy (would strip everything until Order*By*
|
||||
// otherwise)
|
||||
tree = new PartTree("findByLastnameOrderByFirstnameDesc", User.class);
|
||||
assertThat(tree.isDistinct(), is(false));
|
||||
assertThat(tree.getSort(), is(new Sort(Direction.DESC, "firstname")));
|
||||
}
|
||||
|
||||
|
||||
private void assertPart(PartTree tree, Part[]... parts) {
|
||||
|
||||
Iterator<OrPart> iterator = tree.iterator();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.repository.util;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.repository.util.ClassUtils.*;
|
||||
|
||||
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.support.RepositorySupport;
|
||||
|
||||
|
||||
/**
|
||||
@@ -36,23 +33,6 @@ import org.springframework.data.repository.support.RepositorySupport;
|
||||
*/
|
||||
public class ClassUtilsUnitTests {
|
||||
|
||||
@Test
|
||||
public void looksUpDomainClassCorrectly() throws Exception {
|
||||
|
||||
assertEquals(User.class, getDomainClass(UserRepository.class));
|
||||
assertEquals(User.class, getDomainClass(SomeDao.class));
|
||||
assertNull(getDomainClass(Serializable.class));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void looksUpIdClassCorrectly() throws Exception {
|
||||
|
||||
assertEquals(Integer.class, getIdClass(UserRepository.class));
|
||||
assertNull(getIdClass(Serializable.class));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void rejectsInvalidReturnType() throws Exception {
|
||||
|
||||
@@ -62,10 +42,13 @@ public class ClassUtilsUnitTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void findsDomainClassOnExtensionOfDaoInterface() throws Exception {
|
||||
public void determinesReturnType() throws Exception {
|
||||
|
||||
assertEquals(User.class,
|
||||
getDomainClass(ExtensionOfUserCustomExtendedDao.class));
|
||||
getReturnedDomainClass(SomeDao.class.getMethod(
|
||||
"findByFirstname", Pageable.class, String.class)));
|
||||
assertEquals(GenericType.class,
|
||||
getReturnedDomainClass(SomeDao.class.getMethod("someMethod")));
|
||||
}
|
||||
|
||||
|
||||
@@ -77,48 +60,6 @@ public class ClassUtilsUnitTests {
|
||||
assertFalse(hasProperty(User.class, "address"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* References #256.
|
||||
*/
|
||||
@Test
|
||||
public void detectsParameterizedEntitiesCorrectly() {
|
||||
|
||||
assertEquals(GenericEntity.class,
|
||||
getDomainClass(GenericEntityDao.class));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* #301
|
||||
*/
|
||||
@Test
|
||||
public void discoversDaoBaseClassMethod() throws Exception {
|
||||
|
||||
Method method = FooDao.class.getMethod("findById", Integer.class);
|
||||
|
||||
Method reference =
|
||||
getBaseClassMethodFor(method,
|
||||
DummyGenericRepositorySupport.class, FooDao.class);
|
||||
assertEquals(DummyGenericRepositorySupport.class,
|
||||
reference.getDeclaringClass());
|
||||
assertThat(reference.getName(), is("findById"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* #301
|
||||
*/
|
||||
@Test
|
||||
public void discoveresNonDaoBaseClassMethod() throws Exception {
|
||||
|
||||
Method method = FooDao.class.getMethod("readById", Long.class);
|
||||
|
||||
assertThat(
|
||||
getBaseClassMethodFor(method, RepositorySupport.class,
|
||||
FooDao.class), is(method));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private class User {
|
||||
|
||||
@@ -149,63 +90,12 @@ public class ClassUtilsUnitTests {
|
||||
private interface SomeDao extends Serializable, UserRepository {
|
||||
|
||||
Page<User> findByFirstname(Pageable pageable, String firstname);
|
||||
|
||||
|
||||
GenericType<User> someMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample interface to test recursive lookup of domain class.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static interface ExtensionOfUserCustomExtendedDao extends
|
||||
UserCustomExtendedRepository {
|
||||
private class GenericType<T> {
|
||||
|
||||
}
|
||||
|
||||
static interface UserCustomExtendedRepository extends
|
||||
Repository<User, Integer> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to reproduce #256.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class GenericEntity<T> {
|
||||
}
|
||||
|
||||
static interface GenericEntityDao extends
|
||||
Repository<GenericEntity<String>, Long> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample DAO interface to test redeclaration of {@link GenericDao} methods.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static interface FooDao extends Repository<User, Integer> {
|
||||
|
||||
// Redeclared method
|
||||
User findById(Integer primaryKey);
|
||||
|
||||
|
||||
// Not a redeclared method
|
||||
User readById(Long primaryKey);
|
||||
}
|
||||
|
||||
static abstract class DummyGenericRepositorySupport<T, ID extends Serializable>
|
||||
extends RepositorySupport<T, ID> {
|
||||
|
||||
public DummyGenericRepositorySupport(Class<T> domainClass) {
|
||||
|
||||
super(domainClass);
|
||||
}
|
||||
|
||||
|
||||
public T findById(ID id) {
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
-->
|
||||
<groupId>org.springframework.build.aws</groupId>
|
||||
<artifactId>org.springframework.build.aws.maven</artifactId>
|
||||
<version>2.0.0.RELEASE</version>
|
||||
<version>3.1.0.RELEASE</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
<resources>
|
||||
@@ -269,8 +269,8 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.5</source>
|
||||
<target>1.5</target>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<compilerArgument>-Xlint:all</compilerArgument>
|
||||
<showWarnings>true</showWarnings>
|
||||
<showDeprecation>false</showDeprecation>
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
Spring Data Commons Changelog
|
||||
=============================================
|
||||
|
||||
Changes in version 1.0.0.M4
|
||||
----------------------------------------
|
||||
|
||||
Repository
|
||||
* Improved ParameterAccessor infrastructure
|
||||
* Added support for 'Distinct' keyword in finder method names (DATACMNS-15)
|
||||
* Added support for 'In' and 'NotIn' keywords (DATACMNS-16)
|
||||
* Introduced metamodel for entities and repositories (DATACMNS-17)
|
||||
* Fixed returning wrong class PersistableEntityMetadata(DATACMNS-19)
|
||||
|
||||
Changes in version 1.0.0.M3 (2011-02-09)
|
||||
----------------------------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user