DATADOC-48 simplified cross-store persistence of @Document annotated fields in a JPA entity
This commit is contained in:
@@ -21,20 +21,30 @@
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-tx</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aspects</artifactId>
|
||||
<version>${org.springframework.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-orm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Data -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons-aspects</artifactId>
|
||||
</dependency>
|
||||
</dependency> -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
@@ -197,10 +207,10 @@
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aspects</artifactId>
|
||||
</aspectLibrary>
|
||||
<aspectLibrary>
|
||||
<!-- <aspectLibrary>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons-aspects</artifactId>
|
||||
</aspectLibrary>
|
||||
</aspectLibrary> -->
|
||||
</aspectLibraries>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.springframework.data.persistence.document;
|
||||
|
||||
import org.springframework.data.support.ChangeSetBacked;
|
||||
|
||||
public interface DocumentBacked extends ChangeSetBacked {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.springframework.data.persistence.document;
|
||||
|
||||
//public class DocumentBackedTransactionSynchronization {
|
||||
|
||||
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 DocumentBackedTransactionSynchronization implements TransactionSynchronization {
|
||||
|
||||
protected final Log log = LogFactory.getLog(getClass());
|
||||
|
||||
private ChangeSetPersister<Object> changeSetPersister;
|
||||
|
||||
private ChangeSetBacked entity;
|
||||
|
||||
private int changeSetTxStatus = -1;
|
||||
|
||||
public DocumentBackedTransactionSynchronization(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,138 @@
|
||||
package org.springframework.data.persistence.document.mongo;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.MongoException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.document.mongodb.CollectionCallback;
|
||||
import org.springframework.data.document.mongodb.MongoTemplate;
|
||||
import org.springframework.data.support.ChangeSet;
|
||||
import org.springframework.data.support.ChangeSetBacked;
|
||||
import org.springframework.data.support.ChangeSetPersister;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
public class MongoChangeSetPersister implements ChangeSetPersister<Object> {
|
||||
|
||||
private static final String ENTITY_CLASS = "_entity_class";
|
||||
|
||||
private static final String ENTITY_ID = "_entity_id";
|
||||
|
||||
private static final String ENTITY_FIELD_NAME = "_entity_field_name";
|
||||
|
||||
private static final String ENTITY_FIELD_CLASS = "_entity_field_class";
|
||||
|
||||
protected final Log log = LogFactory.getLog(getClass());
|
||||
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
public void setMongoTemplate(MongoTemplate mongoTemplate) {
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPersistentState(Class<? extends ChangeSetBacked> entityClass,
|
||||
Object id, final ChangeSet changeSet) throws DataAccessException,
|
||||
NotFoundException {
|
||||
String collName = getCollectionNameForEntity(entityClass);
|
||||
|
||||
final DBObject dbk = new BasicDBObject();
|
||||
dbk.put(ENTITY_ID, id);
|
||||
dbk.put(ENTITY_CLASS, entityClass.getName());
|
||||
mongoTemplate.execute(collName, new CollectionCallback<Object>() {
|
||||
@Override
|
||||
public Object doInCollection(DBCollection collection)
|
||||
throws MongoException, DataAccessException {
|
||||
for (DBObject dbo : collection.find(dbk)) {
|
||||
String key = (String) dbo.get(ENTITY_FIELD_NAME);
|
||||
String className = (String) dbo.get(ENTITY_FIELD_CLASS);
|
||||
if (className == null) {
|
||||
throw new DataIntegrityViolationException(
|
||||
"Unble to convert property " + key
|
||||
+ ": Invalid metadata, " + ENTITY_FIELD_CLASS + " not available");
|
||||
}
|
||||
Class<?> clazz = null;
|
||||
try {
|
||||
clazz = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new DataIntegrityViolationException(
|
||||
"Unble to convert property " + key + " of type " + className, e);
|
||||
}
|
||||
Object value = mongoTemplate.getConverter().read(clazz, dbo);
|
||||
changeSet.set(key, value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPersistentId(Class<? extends ChangeSetBacked> entityClass,
|
||||
ChangeSet cs) throws DataAccessException {
|
||||
log.debug("getPersistentId called on " + entityClass);
|
||||
if (cs == null) {
|
||||
return null;
|
||||
}
|
||||
if (cs.getValues().get(ChangeSetPersister.ID_KEY) == null) {
|
||||
// Not yet persistent
|
||||
return null;
|
||||
}
|
||||
Object o = cs.getValues().get(ChangeSetPersister.ID_KEY);
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object persistState(Class<? extends ChangeSetBacked> entityClass,
|
||||
ChangeSet cs) throws DataAccessException {
|
||||
log.debug("Flush: changeset: " + cs.getValues().keySet());
|
||||
|
||||
String collName = getCollectionNameForEntity(entityClass);
|
||||
DBCollection dbc = mongoTemplate.getCollection(collName);
|
||||
if (dbc == null) {
|
||||
dbc = mongoTemplate.createCollection(collName);
|
||||
}
|
||||
for (String key : cs.getValues().keySet()) {
|
||||
if (key != null && !key.startsWith("_") && !key.equals(ChangeSetPersister.ID_KEY)) {
|
||||
Object value = cs.getValues().get(key);
|
||||
final DBObject dbQuery = new BasicDBObject();
|
||||
dbQuery.put(ENTITY_ID, cs.getValues().get(ChangeSetPersister.ID_KEY));
|
||||
dbQuery.put(ENTITY_CLASS, entityClass.getName());
|
||||
dbQuery.put(ENTITY_FIELD_NAME, key);
|
||||
dbQuery.put(ENTITY_FIELD_CLASS, value.getClass().getName());
|
||||
DBObject dbId = mongoTemplate.execute(collName,
|
||||
new CollectionCallback<DBObject>() {
|
||||
@Override
|
||||
public DBObject doInCollection(DBCollection collection)
|
||||
throws MongoException, DataAccessException {
|
||||
return collection.findOne(dbQuery);
|
||||
}
|
||||
});
|
||||
final DBObject dbDoc = new BasicDBObject();
|
||||
mongoTemplate.getConverter().write(value, dbDoc);
|
||||
dbDoc.putAll(dbQuery);
|
||||
if (dbId != null) {
|
||||
dbDoc.put("_id", dbId.get("_id"));
|
||||
}
|
||||
mongoTemplate.execute(collName, new CollectionCallback<Object>() {
|
||||
@Override
|
||||
public Object doInCollection(DBCollection collection)
|
||||
throws MongoException, DataAccessException {
|
||||
collection.save(dbDoc);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return 0L;
|
||||
}
|
||||
|
||||
private String getCollectionNameForEntity(
|
||||
Class<? extends ChangeSetBacked> entityClass) {
|
||||
return ClassUtils.getQualifiedName(entityClass);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package org.springframework.data.persistence.document.mongo;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.persistence.Transient;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.reflect.FieldSignature;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
||||
import org.springframework.data.document.mongodb.mapping.Document;
|
||||
|
||||
import org.springframework.data.persistence.document.DocumentBacked;
|
||||
import org.springframework.data.persistence.document.DocumentBackedTransactionSynchronization;
|
||||
import org.springframework.data.support.ChangeSet;
|
||||
import org.springframework.data.support.ChangeSetPersister;
|
||||
import org.springframework.data.support.ChangeSetPersister.NotFoundException;
|
||||
import org.springframework.data.support.HashMapChangeSet;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
/**
|
||||
* Aspect to turn an object annotated with @Document into a persistent document
|
||||
* using Mongo.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
*/
|
||||
public aspect MongoDocumentBacking {
|
||||
|
||||
private static final Log LOGGER = LogFactory
|
||||
.getLog(MongoDocumentBacking.class);
|
||||
|
||||
// Aspect shared config
|
||||
private ChangeSetPersister<Object> changeSetPersister;
|
||||
|
||||
public void setChangeSetPersister(
|
||||
ChangeSetPersister<Object> changeSetPersister) {
|
||||
this.changeSetPersister = changeSetPersister;
|
||||
}
|
||||
|
||||
// ITD to introduce N state to Annotated objects
|
||||
declare parents : (@Entity *) implements DocumentBacked;
|
||||
|
||||
// declare @type: DocumentBacked+: @Configurable;
|
||||
|
||||
declare @field: @Document * (@Entity+ *).*:@Transient;
|
||||
declare @field: ChangeSet (DocumentBacked+).*:@Transient;
|
||||
declare @field: ChangeSetPersister (DocumentBacked+).*:@Transient;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Advise user-defined constructors of ChangeSetBacked objects to create a new
|
||||
// backing ChangeSet
|
||||
// -------------------------------------------------------------------------
|
||||
pointcut arbitraryUserConstructorOfChangeSetBackedObject(DocumentBacked entity) :
|
||||
execution((DocumentBacked+).new(..)) &&
|
||||
!execution((DocumentBacked+).new(ChangeSet)) &&
|
||||
this(entity);
|
||||
|
||||
pointcut finderConstructorOfChangeSetBackedObject(DocumentBacked entity,
|
||||
ChangeSet cs) :
|
||||
execution((DocumentBacked+).new(ChangeSet)) &&
|
||||
this(entity) &&
|
||||
args(cs);
|
||||
|
||||
protected pointcut entityFieldGet(DocumentBacked entity) :
|
||||
get(@Document * DocumentBacked+.*) &&
|
||||
this(entity) &&
|
||||
!get(* DocumentBacked.*);
|
||||
|
||||
protected pointcut entityFieldSet(DocumentBacked entity, Object newVal) :
|
||||
set(@Document * DocumentBacked+.*) &&
|
||||
this(entity) &&
|
||||
args(newVal) &&
|
||||
!set(* DocumentBacked.*);
|
||||
|
||||
protected pointcut entityIdSet(DocumentBacked entity, Object newVal) :
|
||||
set(@Id * DocumentBacked+.*) &&
|
||||
this(entity) &&
|
||||
args(newVal) &&
|
||||
!set(* DocumentBacked.*);
|
||||
|
||||
before(DocumentBacked entity) : arbitraryUserConstructorOfChangeSetBackedObject(entity) {
|
||||
LOGGER
|
||||
.debug("User-defined constructor called on DocumentBacked object of class "
|
||||
+ entity.getClass());
|
||||
entity.itdChangeSetPersister = changeSetPersister;
|
||||
// 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 DocumentBackedTransactionSynchronization(
|
||||
changeSetPersister, entity));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ChangeSet-related mixins
|
||||
// -------------------------------------------------------------------------
|
||||
// Introduced field
|
||||
private ChangeSet DocumentBacked.changeSet;
|
||||
|
||||
private ChangeSetPersister<?> DocumentBacked.itdChangeSetPersister;
|
||||
|
||||
public void DocumentBacked.setChangeSet(ChangeSet cs) {
|
||||
this.changeSet = cs;
|
||||
}
|
||||
|
||||
public ChangeSet DocumentBacked.getChangeSet() {
|
||||
return changeSet;
|
||||
}
|
||||
|
||||
// Flush the entity state to the persistent store
|
||||
public void DocumentBacked.flush() {
|
||||
itdChangeSetPersister.persistState(this.getClass(), this.changeSet);
|
||||
}
|
||||
|
||||
public Object DocumentBacked.get_persistent_id() {
|
||||
return itdChangeSetPersister.getPersistentId(this.getClass(),
|
||||
this.changeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* delegates field reads to the state accessors instance
|
||||
*/
|
||||
Object around(DocumentBacked entity): entityFieldGet(entity) {
|
||||
Field f = field(thisJoinPoint);
|
||||
String propName = f.getName();
|
||||
LOGGER.trace("GET " + f + " -> ChangeSet value property [" + propName
|
||||
+ "] using: " + entity.getChangeSet());
|
||||
if (entity.getChangeSet().getValues().get(propName) == null) {
|
||||
try {
|
||||
this.changeSetPersister.getPersistentState(entity.getClass(),
|
||||
entity.get_persistent_id(), entity.getChangeSet());
|
||||
} catch (NotFoundException e) {
|
||||
}
|
||||
}
|
||||
Object fValue = entity.getChangeSet().getValues().get(propName);
|
||||
if (fValue != null) {
|
||||
return fValue;
|
||||
}
|
||||
return proceed(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* delegates field writes to the state accessors instance
|
||||
*/
|
||||
Object around(DocumentBacked entity, Object newVal) : entityFieldSet(entity, newVal) {
|
||||
Field f = field(thisJoinPoint);
|
||||
String propName = f.getName();
|
||||
LOGGER.trace("SET " + f + " -> ChangeSet number value property [" + propName
|
||||
+ "] with value=[" + newVal + "]");
|
||||
entity.getChangeSet().set(propName, newVal);
|
||||
return proceed(entity, newVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* delegates field writes to the state accessors instance
|
||||
*/
|
||||
Object around(DocumentBacked entity, Object newVal) : entityIdSet(entity, newVal) {
|
||||
Field f = field(thisJoinPoint);
|
||||
String propName = f.getName();
|
||||
LOGGER.trace("SET @Id -> ChangeSet @Id property [" + propName
|
||||
+ "] with value=[" + newVal + "]");
|
||||
entity.getChangeSet().set("_id", newVal);
|
||||
return proceed(entity, newVal);
|
||||
}
|
||||
|
||||
Field field(JoinPoint joinPoint) {
|
||||
FieldSignature fieldSignature = (FieldSignature) joinPoint.getSignature();
|
||||
return fieldSignature.getField();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package org.springframework.persistence.document;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to denote an object that should be transparently persisted
|
||||
* using MongoDB
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
*/
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface DocumentEntity {
|
||||
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package org.springframework.persistence.document;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.MongoException;
|
||||
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.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.data.document.mongodb.MongoTemplate;
|
||||
import org.springframework.persistence.support.ChangeSet;
|
||||
import org.springframework.persistence.support.ChangeSetBacked;
|
||||
import org.springframework.persistence.support.ChangeSetPersister;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
//import edu.emory.mathcs.backport.java.util.Arrays;
|
||||
|
||||
public class MongoChangeSetPersister implements ChangeSetPersister<Object> {
|
||||
|
||||
protected final Log log = LogFactory.getLog(getClass());
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Autowired
|
||||
private ConversionService conversionService;
|
||||
|
||||
@Override
|
||||
public void getPersistentState(Class<? extends ChangeSetBacked> entityClass, Object id, ChangeSet changeSet)
|
||||
throws DataAccessException, NotFoundException {
|
||||
String collection = ClassUtils.getShortName(entityClass).toLowerCase();
|
||||
DBObject q = new BasicDBObject();
|
||||
q.put("_id", id);
|
||||
try {
|
||||
DBObject dbo = mongoTemplate.getCollection(collection).findOne(q);
|
||||
log.debug("Found DBObject: " + dbo);
|
||||
if (dbo == null) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
String classShortName = ClassUtils.getShortName(entityClass);
|
||||
for (Object property : dbo.toMap().keySet()) {
|
||||
String propertyKey = (String) property;
|
||||
String propertyName = propertyKey.startsWith(classShortName) ? propertyKey.substring(propertyKey.indexOf(classShortName)
|
||||
+ classShortName.length() + 1) : propertyKey;
|
||||
// System.err.println("Mongo persisted property [" + propertyName + "] :: " + propertyKey + " = " + dbo.get(propertyKey));
|
||||
if (propertyKey.startsWith("_")) {
|
||||
// Id or class
|
||||
changeSet.set(propertyKey, dbo.get(propertyKey));
|
||||
} else {
|
||||
//throw new IllegalStateException("Unknown property [" + propertyName + "] found in MongoDB store");
|
||||
changeSet.set(propertyKey, dbo.get(propertyKey));
|
||||
}
|
||||
}
|
||||
} catch (MongoException ex) {
|
||||
throw new DataAccessResourceFailureException("Can't read from Mongo", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPersistentId(Class<? extends ChangeSetBacked> entityClass,
|
||||
ChangeSet cs) throws DataAccessException {
|
||||
log.debug("getPersistentId called on " + entityClass);
|
||||
if (cs == null) {
|
||||
return null;
|
||||
}
|
||||
if (cs.getValues().get(ChangeSetPersister.ID_KEY) == null) {
|
||||
// Not yet persistent
|
||||
return null;
|
||||
}
|
||||
Object o = cs.getValues().get(ChangeSetPersister.ID_KEY);
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object persistState(Class<? extends ChangeSetBacked> entityClass, ChangeSet cs) throws DataAccessException {
|
||||
log.info("PERSIST::" + cs);
|
||||
cs.set(CLASS_KEY, entityClass.getName());
|
||||
String idstr = cs.get(ID_KEY, String.class, this.conversionService);
|
||||
Object id = null;
|
||||
if (idstr != null) {
|
||||
id = idstr;
|
||||
}
|
||||
if (id == null) {
|
||||
log.info("Flush: entity make persistent; data store will assign id");
|
||||
cs.set("_class", entityClass.getName());
|
||||
String collection = entityClass.getSimpleName().toLowerCase();
|
||||
DBCollection dbc = mongoTemplate.getCollection(collection);
|
||||
DBObject dbo = mapChangeSetToDbObject(cs);
|
||||
if (dbc == null) {
|
||||
dbc = mongoTemplate.createCollection(collection);
|
||||
}
|
||||
dbc.save(dbo);
|
||||
id = dbo.get(ID_KEY);
|
||||
log.info("Data store assigned id: " + id);
|
||||
} else {
|
||||
log.info("Flush: entity already persistent with id=" + id);
|
||||
String collection = entityClass.getName();
|
||||
DBCollection dbc = mongoTemplate.getCollection(collection);
|
||||
DBObject dbo = mapChangeSetToDbObject(cs);
|
||||
if (dbc == null) {
|
||||
throw new DataAccessResourceFailureException("Expected to find a collection named '" + collection + "'. It was not found, so ChangeSet can't be persisted.");
|
||||
}
|
||||
dbc.save(dbo);
|
||||
}
|
||||
|
||||
return 0L;
|
||||
}
|
||||
|
||||
private DBObject mapChangeSetToDbObject(ChangeSet cs) {
|
||||
BasicDBObject dbo = new BasicDBObject();
|
||||
for (String property : cs.getValues().keySet()) {
|
||||
dbo.put(property, cs.getValues().get(property));
|
||||
}
|
||||
return dbo;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.springframework.persistence.document;
|
||||
|
||||
import org.springframework.persistence.support.AbstractDeferredUpdateMixinFields;
|
||||
|
||||
/**
|
||||
* Aspect to turn an object annotated with DocumentEntity into a document entity using Mongo.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
*/
|
||||
public aspect MongoDocumentBacking extends AbstractDeferredUpdateMixinFields<DocumentEntity> {
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package org.springframework.persistence.document;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.document.mongodb.MongoTemplate;
|
||||
import org.springframework.persistence.OrderedEntityOperations;
|
||||
import org.springframework.persistence.RelatedEntity;
|
||||
import org.springframework.persistence.support.ChangeSet;
|
||||
import org.springframework.persistence.support.ChangeSetBacked;
|
||||
import org.springframework.persistence.support.ChangeSetPersister.NotFoundException;
|
||||
import org.springframework.persistence.support.EntityInstantiator;
|
||||
import org.springframework.persistence.support.HashMapChangeSet;
|
||||
|
||||
public class MongoEntityOperations extends OrderedEntityOperations<Object, ChangeSetBacked> {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
private EntityInstantiator<ChangeSetBacked, ChangeSet> entityInstantiator;
|
||||
|
||||
private MongoChangeSetPersister changeSetPersister;
|
||||
|
||||
public void setEntityInstantiator(EntityInstantiator<ChangeSetBacked, ChangeSet> entityInstantiator) {
|
||||
this.entityInstantiator = entityInstantiator;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setChangeSetPersister(MongoChangeSetPersister changeSetPersister) {
|
||||
this.changeSetPersister = changeSetPersister;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean cacheInEntity() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeSetBacked findEntity(Class<ChangeSetBacked> entityClass, Object key) throws DataAccessException {
|
||||
try {
|
||||
ChangeSet cs = new HashMapChangeSet();
|
||||
changeSetPersister.getPersistentState(entityClass, key, cs);
|
||||
return entityInstantiator.createEntityFromState(cs, entityClass);
|
||||
} catch (NotFoundException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object findUniqueKey(ChangeSetBacked entity) throws DataAccessException {
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransactional() {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransient(ChangeSetBacked entity) throws DataAccessException {
|
||||
return entity.getId() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object makePersistent(Object owner, ChangeSetBacked entity, Field f, RelatedEntity fs) throws DataAccessException {
|
||||
changeSetPersister.persistState(entity.getClass(), entity.getChangeSet());
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> entityClass, RelatedEntity fs) {
|
||||
return entityClass.isAnnotationPresent(DocumentEntity.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,14 +3,11 @@ package org.springframework.data.document.persistence;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import com.mongodb.*;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.document.mongodb.MongoTemplate;
|
||||
import org.springframework.persistence.document.test.Account;
|
||||
import org.springframework.persistence.document.test.MongoPerson;
|
||||
import org.springframework.persistence.document.test.Person;
|
||||
import org.springframework.persistence.document.test.Resume;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
@@ -18,6 +15,9 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.Mongo;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:/META-INF/spring/applicationContext.xml")
|
||||
public class CrossStoreMongoTests {
|
||||
@@ -30,8 +30,6 @@ public class CrossStoreMongoTests {
|
||||
|
||||
private EntityManager entityManager;
|
||||
|
||||
private String colName = MongoPerson.class.getSimpleName().toLowerCase();
|
||||
|
||||
|
||||
@PersistenceContext
|
||||
public void setEntityManager(EntityManager entityManager) {
|
||||
@@ -45,65 +43,6 @@ public class CrossStoreMongoTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
@Rollback(false)
|
||||
public void testUserConstructor() {
|
||||
clearData(colName);
|
||||
int age = 33;
|
||||
MongoPerson p = new MongoPerson("Thomas", age);
|
||||
Assert.assertEquals(age, p.getAge());
|
||||
p.birthday();
|
||||
Assert.assertEquals(1 + age, p.getAge());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testInstantiatedFinder() throws MongoException {
|
||||
DBCollection col = this.mongoTemplate.getCollection(colName);
|
||||
DBObject dbo = col.findOne();
|
||||
Object _id = dbo.get("_id");
|
||||
MongoPerson found = MongoPerson.findPerson(_id);
|
||||
Assert.assertNotNull(found);
|
||||
Assert.assertEquals(_id, found.getId());
|
||||
System.out.println("Loaded MongoPerson data: " + found);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
@Rollback(false)
|
||||
public void testCreateMongoToJpaEntityRelationship() {
|
||||
clearData(colName);
|
||||
Account a = new Account();
|
||||
a.setName("My Account");
|
||||
a.setFriendlyName("My Test Acct.");
|
||||
a.setBalance(123.45F);
|
||||
a.setId(2L);
|
||||
MongoPerson p = new MongoPerson("Jack", 22);
|
||||
entityManager.persist(a);
|
||||
p.setAccount(a);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testReadMongoToJpaEntityRelationship() {
|
||||
DBCollection col = this.mongoTemplate.getCollection(colName);
|
||||
DBCursor dbc = col.find();
|
||||
Object _id = null;
|
||||
for (DBObject dbo : dbc) {
|
||||
System.out.println(dbo);
|
||||
if ("Jack".equals(dbo.get("name"))) {
|
||||
_id = dbo.get("_id");
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.out.println(_id);
|
||||
MongoPerson found = MongoPerson.findPerson(_id);
|
||||
System.out.println(found);
|
||||
if (found != null)
|
||||
System.out.println(found.getAccount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
@Rollback(false)
|
||||
@@ -122,12 +61,20 @@ public class CrossStoreMongoTests {
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
@Rollback(false)
|
||||
public void testReadJpaToMongoEntityRelationship() {
|
||||
Person found = entityManager.find(Person.class, 1L);
|
||||
System.out.println(found);
|
||||
// TODO: This part isn't working yet - there is no reference to the Momgo _id stored in the db
|
||||
// if (found != null)
|
||||
// System.out.println(found.getResume());
|
||||
}
|
||||
Assert.assertNotNull(found);
|
||||
|
||||
// TODO: This part isn't quite working yet - need to intercept the id population from JPA EM
|
||||
found.setId(found.getId());
|
||||
|
||||
Assert.assertEquals(Long.valueOf(1), found.getId());
|
||||
Assert.assertNotNull(found);
|
||||
Assert.assertEquals(Long.valueOf(1), found.getId());
|
||||
Assert.assertNotNull(found.getResume());
|
||||
Assert.assertEquals("DiMark, DBA, 1990-2000" + "; " + "VMware, Developer, 2007-",
|
||||
found.getResume().getJobs());
|
||||
found.getResume().addJob("Developer, SpringDeveloper.com, 2005-2006");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package org.springframework.persistence.document.test;
|
||||
|
||||
import org.springframework.persistence.RelatedEntity;
|
||||
import org.springframework.persistence.document.DocumentEntity;
|
||||
|
||||
@DocumentEntity
|
||||
public class MongoPerson {
|
||||
|
||||
// TODO only public because of AspectJ bug
|
||||
public String name;
|
||||
|
||||
public int age;
|
||||
|
||||
public java.util.Date birthDate;
|
||||
|
||||
// TODO only public to check weaving bug--
|
||||
// seems to work whereas private didn't
|
||||
@RelatedEntity
|
||||
public Account account;
|
||||
|
||||
public MongoPerson(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
this.birthDate = new java.util.Date();
|
||||
}
|
||||
|
||||
public void birthday() {
|
||||
++age;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public java.util.Date getBirthDate() {
|
||||
return birthDate;
|
||||
}
|
||||
|
||||
public void setBirthDate(java.util.Date birthDate) {
|
||||
this.birthDate = birthDate;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setAccount(Account account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package org.springframework.persistence.document.test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Configurable;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.persistence.support.*;
|
||||
import org.springframework.persistence.support.ChangeSetPersister.NotFoundException;
|
||||
|
||||
/**
|
||||
* EXAMPLE OF CODE THAT SHOULD BE GENERATED BY ROO BESIDES EACH MONGOENTITY CLASS
|
||||
*
|
||||
* Note: Combines X_Roo_Entity with X_Roo_Finder, as
|
||||
* we need only a single aspect for entities.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
*
|
||||
*/
|
||||
privileged aspect MongoPerson_Roo_Mongo_Entity {
|
||||
|
||||
private static ChangeSetPersister<Object> changeSetPersister() {
|
||||
return new MongoConfigurationHolder().changeSetConfig.getChangeSetPersister();
|
||||
}
|
||||
|
||||
private static ChangeSetSynchronizer<ChangeSetBacked> changeSetManager() {
|
||||
return new MongoConfigurationHolder().changeSetConfig.getChangeSetManager();
|
||||
}
|
||||
|
||||
@Configurable
|
||||
public static class MongoConfigurationHolder {
|
||||
@Autowired
|
||||
@Qualifier("mongoChangeSetConfiguration")
|
||||
public ChangeSetConfiguration<Object> changeSetConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add constructor that takes ChangeSet.
|
||||
* @param ChangeSet
|
||||
*/
|
||||
public MongoPerson.new(ChangeSet cs) {
|
||||
super();
|
||||
setChangeSet(cs);
|
||||
}
|
||||
|
||||
public static MongoPerson MongoPerson.findPerson(Object id) {
|
||||
ChangeSet rv = new HashMapChangeSet();
|
||||
try {
|
||||
changeSetPersister().getPersistentState(MongoPerson.class, id, rv);
|
||||
return new MongoPerson(rv);
|
||||
} catch (NotFoundException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package org.springframework.persistence.document.test;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import org.springframework.persistence.RelatedEntity;
|
||||
import org.springframework.data.document.mongodb.mapping.Document;
|
||||
|
||||
@Entity
|
||||
public class Person {
|
||||
@@ -17,8 +17,7 @@ public class Person {
|
||||
|
||||
private java.util.Date birthDate;
|
||||
|
||||
// @Document // need to decide what the annotation here should be
|
||||
@RelatedEntity
|
||||
@Document
|
||||
public Resume resume;
|
||||
|
||||
public Person() {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.springframework.persistence.document.test;
|
||||
|
||||
import org.springframework.persistence.document.DocumentEntity;
|
||||
|
||||
@DocumentEntity
|
||||
//@DocumentEntity
|
||||
public class Resume {
|
||||
|
||||
private String education = "";
|
||||
|
||||
@@ -12,41 +12,9 @@
|
||||
<context:spring-configured/>
|
||||
|
||||
<context:component-scan base-package="org.springframework.persistence.test">
|
||||
<context:exclude-filter expression=".*_Roo_.*" type="regex"/>
|
||||
<context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
|
||||
</context:component-scan>
|
||||
|
||||
<!-- Store spanning config -->
|
||||
|
||||
<bean class="org.springframework.persistence.StoreSpanning"
|
||||
factory-method="aspectOf">
|
||||
<property name="mappingValidator">
|
||||
<bean class="org.springframework.persistence.support.SimpleMappingValidator"/>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="entityOperationsLocator" class="org.springframework.persistence.ChainingEntityOperationsLocator"/>
|
||||
|
||||
<bean class="org.springframework.persistence.ChainingForeignStoreKeyManagerLocator"/>
|
||||
|
||||
<bean class="org.springframework.persistence.PresentKeyForeignStoreKeyManager" autowire="constructor"/>
|
||||
|
||||
<bean class="org.springframework.persistence.GeneratedFieldForeignStoreKeyManager"/>
|
||||
|
||||
<bean class="org.springframework.persistence.document.MongoEntityOperations">
|
||||
<property name="order" value="5"/>
|
||||
<property name="entityInstantiator">
|
||||
<bean class="org.springframework.persistence.support.ChangeSetConstructorEntityInstantiator"/>
|
||||
</property>
|
||||
<property name="changeSetPersister" ref="mongoChangeSetPersister"/>
|
||||
</bean>
|
||||
|
||||
<bean class="org.springframework.persistence.EntityManagerJpaEntityOperations"/>
|
||||
|
||||
<bean class="org.springframework.persistence.support.ChangeSetForeignStoreKeyManager">
|
||||
<property name="fieldDelimiter" value="#"/>
|
||||
</bean>
|
||||
|
||||
<!-- Mongo config -->
|
||||
<bean id="mongo" class="org.springframework.data.document.mongodb.MongoFactoryBean">
|
||||
<property name="host" value="localhost"/>
|
||||
@@ -61,19 +29,14 @@
|
||||
<bean class="org.springframework.data.document.mongodb.MongoExceptionTranslator"/>
|
||||
|
||||
<!-- Mongo aspect config -->
|
||||
<bean class="org.springframework.persistence.document.MongoDocumentBacking"
|
||||
<bean class="org.springframework.data.persistence.document.mongo.MongoDocumentBacking"
|
||||
factory-method="aspectOf">
|
||||
<property name="changeSetConfiguration" ref="mongoChangeSetConfiguration"/>
|
||||
</bean>
|
||||
<bean id="mongoChangeSetPersister" class="org.springframework.persistence.document.MongoChangeSetPersister"/>
|
||||
<bean id="mongoChangeSetSynchronizer"
|
||||
class="org.springframework.persistence.support.SimpleReflectiveChangeSetSynchronizer"/>
|
||||
<bean id="mongoChangeSetConfiguration" class="org.springframework.persistence.support.ChangeSetConfiguration">
|
||||
<property name="changeSetPersister" ref="mongoChangeSetPersister"/>
|
||||
<property name="changeSetManager" ref="mongoChangeSetSynchronizer"/>
|
||||
</bean>
|
||||
<!-- Needed for ChangeSet persistence -->
|
||||
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"/>
|
||||
<bean id="mongoChangeSetPersister"
|
||||
class="org.springframework.data.persistence.document.mongo.MongoChangeSetPersister">
|
||||
<property name="mongoTemplate" ref="mongoTemplate"/>
|
||||
</bean>
|
||||
|
||||
<jdbc:embedded-database id="dataSource" type="HSQL">
|
||||
</jdbc:embedded-database>
|
||||
|
||||
@@ -5,8 +5,7 @@ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
|
||||
|
||||
log4j.category.org.springframework=INFO
|
||||
log4j.category.org.springframework.data=DEBUG
|
||||
log4j.category.org.springframework.persistence=DEBUG
|
||||
log4j.category.org.springframework.data=TRACE
|
||||
|
||||
log4j.category.org.hibernate.SQL=DEBUG
|
||||
# for debugging datasource initialization
|
||||
|
||||
Reference in New Issue
Block a user