Switched to use new project layout; updated build; moved files around
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
package org.springframework.data.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.data.support;
|
||||
|
||||
|
||||
/**
|
||||
* Interface introduced to objects exposing ChangeSet information
|
||||
* @author Rod Johnson
|
||||
* @author Thomas Risberg
|
||||
*/
|
||||
public interface ChangeSetBacked {
|
||||
|
||||
ChangeSet getChangeSet();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.springframework.data.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.data.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,51 @@
|
||||
package org.springframework.data.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,69 @@
|
||||
package org.springframework.data.transaction;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.data.support.ChangeSetBacked;
|
||||
import org.springframework.data.support.ChangeSetPersister;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
|
||||
public class ChangeSetBackedTransactionSynchronization implements TransactionSynchronization {
|
||||
|
||||
protected final Log log = LogFactory.getLog(getClass());
|
||||
|
||||
private ChangeSetPersister<Object> changeSetPersister;
|
||||
|
||||
private ChangeSetBacked entity;
|
||||
|
||||
private int changeSetTxStatus = -1;
|
||||
|
||||
public ChangeSetBackedTransactionSynchronization(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,13 @@
|
||||
package org.springframework.datastore.serialization;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface SerializationStore {
|
||||
|
||||
}
|
||||
@@ -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,34 @@
|
||||
package org.springframework.persistence;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Persistence policies that can be attached to entities or relationship
|
||||
* fields.
|
||||
* @author rodjohnson
|
||||
*
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.FIELD})
|
||||
public @interface PersistencePolicy {
|
||||
|
||||
enum LATENCY_SENSITIVITY { NONE, MEDIUM, HIGH };
|
||||
|
||||
boolean largeObject() default false;
|
||||
|
||||
boolean queryable() default true;
|
||||
|
||||
boolean immutable() default false;
|
||||
|
||||
boolean transactional() default true;
|
||||
|
||||
boolean lossAcceptable() default false;
|
||||
|
||||
// TODO freshness, or should this be handled separately in a caching annotation
|
||||
|
||||
LATENCY_SENSITIVITY latencySensitivity() default LATENCY_SENSITIVITY.HIGH;
|
||||
|
||||
}
|
||||
@@ -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,73 @@
|
||||
package org.springframework.persistence.support;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Try for a constructor taking state: failing that, try a no-arg
|
||||
* constructor and then setUnderlyingNode().
|
||||
*
|
||||
* @author Rod Johnson
|
||||
*/
|
||||
public abstract class AbstractConstructorEntityInstantiator<BACKING_INTERFACE, STATE> implements EntityInstantiator<BACKING_INTERFACE, STATE> {
|
||||
|
||||
private final Log log = LogFactory.getLog(getClass());
|
||||
|
||||
final public <T extends BACKING_INTERFACE> T createEntityFromState(STATE n, Class<T> c) {
|
||||
try {
|
||||
return fromStateInternal(n, c);
|
||||
} catch (InstantiationException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
final private <T extends BACKING_INTERFACE> T fromStateInternal(STATE n, Class<T> c) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
|
||||
// TODO this is fragile
|
||||
Class<? extends STATE> stateInterface = (Class<? extends STATE>) n.getClass().getInterfaces()[0];
|
||||
Constructor<T> nodeConstructor = ClassUtils.getConstructorIfAvailable(c, stateInterface);
|
||||
if (nodeConstructor != null) {
|
||||
// TODO is this the correct way to instantiate or does Spring have a preferred way?
|
||||
log.info("Using " + c + " constructor taking " + stateInterface);
|
||||
return nodeConstructor.newInstance(n);
|
||||
}
|
||||
|
||||
Constructor<T> noArgConstructor = ClassUtils.getConstructorIfAvailable(c);
|
||||
if (noArgConstructor == null) noArgConstructor = getDeclaredConstructor(c);
|
||||
if (noArgConstructor != null) {
|
||||
log.info("Using " + c + " no-arg constructor");
|
||||
StateProvider.setUnderlyingState(n);
|
||||
T t = noArgConstructor.newInstance();
|
||||
setState(t, n);
|
||||
return t;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(getClass().getSimpleName() + ": entity " + c + " must have either a constructor taking [" + stateInterface +
|
||||
"] or a no-arg constructor and state set method");
|
||||
}
|
||||
|
||||
private <T> Constructor<T> getDeclaredConstructor(Class<T> c) {
|
||||
try {
|
||||
final Constructor<T> declaredConstructor = c.getDeclaredConstructor();
|
||||
declaredConstructor.setAccessible(true);
|
||||
return declaredConstructor;
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses must implement to set state
|
||||
* @param entity
|
||||
* @param s
|
||||
*/
|
||||
protected abstract void setState(BACKING_INTERFACE entity, STATE s);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.springframework.persistence.support;
|
||||
|
||||
|
||||
/**
|
||||
* Interface to be implemented by classes that can instantiate and
|
||||
* configure entities.
|
||||
* The framework must do this when creating objects resulting from finders,
|
||||
* even when there may be no no-arg constructor supplied by the user.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
*/
|
||||
public interface EntityInstantiator<BACKING_INTERFACE,STATE> {
|
||||
|
||||
/*
|
||||
* The best solution if available is to add a constructor that takes Node
|
||||
* to each GraphEntity. This means generating an aspect beside every
|
||||
* class as Roo presently does.
|
||||
*
|
||||
* An alternative that does not require Roo
|
||||
* is a user-authored constructor taking Node and calling setUnderlyingNode()
|
||||
* but this is less elegant and pollutes the domain object.
|
||||
*
|
||||
* If the user supplies a no-arg constructor, instantiation can occur by invoking it
|
||||
* prior to calling setUnderlyingNode().
|
||||
*
|
||||
* If the user does NOT supply a no-arg constructor, we must rely on Sun-specific
|
||||
* code to instantiate entities without invoking a constructor.
|
||||
*/
|
||||
|
||||
<T extends BACKING_INTERFACE> T createEntityFromState(STATE s, Class<T> c);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.persistence.support;
|
||||
|
||||
/**
|
||||
* @author Michael Hunger
|
||||
* @since 24.09.2010
|
||||
*/
|
||||
public abstract class StateProvider {
|
||||
private final static ThreadLocal stateHolder=new ThreadLocal();
|
||||
|
||||
private StateProvider() {}
|
||||
|
||||
public static <STATE> void setUnderlyingState(STATE state) {
|
||||
if (stateHolder.get()!=null) throw new IllegalStateException("StateHolder already contains state "+stateHolder.get()+" in thread "+Thread.currentThread());
|
||||
stateHolder.set(state);
|
||||
}
|
||||
public static <STATE> STATE retrieveState() {
|
||||
STATE result= (STATE) stateHolder.get();
|
||||
stateHolder.remove();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.springframework.persistence.transaction;
|
||||
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.DefaultTransactionStatus;
|
||||
|
||||
public class NaiveDoubleTransactionManager implements PlatformTransactionManager {
|
||||
Map<TransactionStatus,TransactionStatus> status=new IdentityHashMap<TransactionStatus, TransactionStatus>();
|
||||
private final PlatformTransactionManager a;
|
||||
|
||||
private final PlatformTransactionManager b;
|
||||
|
||||
public NaiveDoubleTransactionManager(PlatformTransactionManager a, PlatformTransactionManager b) {
|
||||
System.err.println("WARNING: Naive JTA/Neo4j Spring transaction manager--must implement properly");
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(TransactionStatus ts) throws TransactionException {
|
||||
try {
|
||||
final TransactionStatus tsb = copyTransactionStatus(status.get(ts));
|
||||
try {
|
||||
a.commit(ts);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
System.err.println("Continuing to commit tx despite this:" + t);
|
||||
}
|
||||
try {
|
||||
b.commit(tsb);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
System.err.println("Can't commit tx" + t);
|
||||
throw new TransactionException(t.getMessage(), t) {};
|
||||
}
|
||||
} finally {
|
||||
status.remove(ts);
|
||||
}
|
||||
}
|
||||
|
||||
private TransactionStatus copyTransactionStatus(TransactionStatus ts) {
|
||||
Object t = (ts instanceof DefaultTransactionStatus) ? ((DefaultTransactionStatus) ts).getTransaction() : null;
|
||||
return new DefaultTransactionStatus(t,ts.isNewTransaction(), false, false, false, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionStatus getTransaction(TransactionDefinition td)
|
||||
throws TransactionException {
|
||||
TransactionStatus atx = a.getTransaction(td);
|
||||
TransactionStatus btx = b.getTransaction(td);
|
||||
status.put(atx, btx);
|
||||
return atx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(TransactionStatus ts) throws TransactionException {
|
||||
final TransactionStatus tsb = copyTransactionStatus(status.remove(ts));
|
||||
try {
|
||||
a.rollback(ts);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
System.err.println("Continuing to rollback tx despite this:" + t);
|
||||
}
|
||||
try {
|
||||
b.rollback(tsb);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
System.err.println("Can't rollback tx" + t);
|
||||
throw new TransactionException(t.getMessage(), t) {};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user