Merge branch 'master' of github.com:SpringSource/spring-data-document

Conflicts:
	spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java
This commit is contained in:
Jon Brisbin
2011-03-30 16:28:01 -05:00
committed by J. Brisbin
5 changed files with 278 additions and 208 deletions

View File

@@ -1,179 +1,207 @@
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.persistence.ChangeSet;
import org.springframework.data.persistence.ChangeSetPersister;
import org.springframework.data.persistence.ChangeSetPersister.NotFoundException;
import org.springframework.data.persistence.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() {
Object id = itdChangeSetPersister.getPersistentId(this, this.changeSet);
itdChangeSetPersister.persistState(this, this.changeSet);
}
public Object DocumentBacked.get_persistent_id() {
return itdChangeSetPersister.getPersistentId(this, 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();
}
}
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.persistence.ChangeSet;
import org.springframework.data.persistence.ChangeSetPersister;
import org.springframework.data.persistence.ChangeSetPersister.NotFoundException;
import org.springframework.data.persistence.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;
// The annotated fields that will be persisted in MongoDB rather than with JPA
declare @field: @Document * (@Entity+ *).*:@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());
// Populate all ITD fields
entity.setChangeSet(new HashMapChangeSet());
entity.itdChangeSetPersister = changeSetPersister;
entity.itdTransactionSynchronization =
new DocumentBackedTransactionSynchronization(changeSetPersister, entity);
registerTransactionSynchronization(entity);
}
private static void registerTransactionSynchronization(DocumentBacked entity) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
if (!TransactionSynchronizationManager.getSynchronizations().contains(entity.itdTransactionSynchronization)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Adding transaction synchronization for " + entity.getClass());
}
TransactionSynchronizationManager.registerSynchronization(entity.itdTransactionSynchronization);
}
else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Transaction synchronization already active for " + entity.getClass());
}
}
}
else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Transaction syncronization is not active for " + entity.getClass());
}
}
}
// -------------------------------------------------------------------------
// ChangeSet-related mixins
// -------------------------------------------------------------------------
// Introduced field
@Transient private ChangeSet DocumentBacked.changeSet;
@Transient private ChangeSetPersister<?> DocumentBacked.itdChangeSetPersister;
@Transient private DocumentBackedTransactionSynchronization DocumentBacked.itdTransactionSynchronization;
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() {
Object id = itdChangeSetPersister.getPersistentId(this, this.changeSet);
itdChangeSetPersister.persistState(this, this.changeSet);
}
public Object DocumentBacked.get_persistent_id() {
return itdChangeSetPersister.getPersistentId(this, this.changeSet);
}
// lifecycle methods
@javax.persistence.PrePersist public void DocumentBacked.prePersist() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("JPA lifecycle called PrePersist: " + this.getClass().getName() + " :: " + this.get_persistent_id());
}
registerTransactionSynchronization(this);
}
@javax.persistence.PreUpdate public void DocumentBacked.preUpdate() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("JPA lifecycle called PreUpdate: " + this.getClass().getName() + " :: " + this.get_persistent_id());
}
registerTransactionSynchronization(this);
}
@javax.persistence.PreRemove public void DocumentBacked.preRemove() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("JPA lifecycle called PreRemove: " + this.getClass().getName() + " :: " + this.get_persistent_id());
}
registerTransactionSynchronization(this);
}
@javax.persistence.PostLoad public void DocumentBacked.postLoad() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("JPA lifecycle called PostLoad: " + this.getClass().getName() + " :: " + this.get_persistent_id());
}
registerTransactionSynchronization(this);
}
/**
* 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);
}
Field field(JoinPoint joinPoint) {
FieldSignature fieldSignature = (FieldSignature) joinPoint.getSignature();
return fieldSignature.getField();
}
}

View File

@@ -13,7 +13,11 @@ import org.springframework.persistence.document.test.Resume;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import com.mongodb.DBCollection;
import com.mongodb.Mongo;
@@ -30,6 +34,9 @@ public class CrossStoreMongoTests {
private EntityManager entityManager;
@Autowired
private PlatformTransactionManager transactionManager;
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
@@ -71,6 +78,7 @@ public class CrossStoreMongoTests {
Assert.assertEquals("DiMark, DBA, 1990-2000" + "; "
+ "VMware, Developer, 2007-", found.getResume().getJobs());
found.getResume().addJob("SpringDeveloper.com, Consultant, 2005-2006");
found.setAge(44);
}
@Test
@@ -87,4 +95,22 @@ public class CrossStoreMongoTests {
+ "VMware, Developer, 2007-" + "; "
+ "SpringDeveloper.com, Consultant, 2005-2006", found.getResume().getJobs());
}
@Test
public void testMergeJpaEntityWithMongoDocument() {
TransactionTemplate txTemplate = new TransactionTemplate(transactionManager);
final Person found = entityManager.find(Person.class, 1L);
found.setAge(77);
found.getResume().addJob("TargetRx, Developer, 2000-2005");
txTemplate.execute(new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus status) {
entityManager.merge(found);
return null;
}
});
final Person updated = entityManager.find(Person.class, 1L);
// assert that the new values are in respective DBs
// TODO: during merge we lose the changeset since JPA creates a new persistent instance -
// we need to move the existing changeset over somehow
}
}

View File

@@ -13,7 +13,7 @@
<name>Spring Data MongoDB Support</name>
<properties>
<mongo.version>2.3</mongo.version>
<mongo.version>2.4</mongo.version>
</properties>
<dependencies>

View File

@@ -24,11 +24,14 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.After;
import com.mongodb.Mongo;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.document.mongodb.MongoDbUtils;
import org.springframework.data.document.mongodb.MongoTemplate;
import org.springframework.data.document.mongodb.query.Criteria;
import org.springframework.data.document.mongodb.query.Query;
@@ -38,28 +41,32 @@ import org.springframework.data.document.mongodb.query.Query;
*/
public class MappingTests {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoDbUtils.class);
ApplicationContext applicationContext;
MongoTemplate template;
MongoMappingContext mappingContext;
@Before
public void setUp() throws InterruptedException {
public void setUp() throws Exception {
Mongo mongo = new Mongo();
mongo.getDB("database").getCollection("person").drop();
applicationContext = new ClassPathXmlApplicationContext("/mapping.xml");
template = applicationContext.getBean(MongoTemplate.class);
}
@After
public void tearDown() {
template.dropCollection("person");
template.dropCollection("account");
mappingContext = applicationContext.getBean(MongoMappingContext.class);
}
@Test
public void testPersonPojo() {
public void testPersonPojo() throws Exception {
LOGGER.info("about to create new personpojo");
PersonPojo p = new PersonPojo(12345, "Person", "Pojo");
LOGGER.info("about to insert");
template.insert(p);
LOGGER.info("done inserting");
assertNotNull(p.getId());
List<PersonPojo> result = template.find(new Query(Criteria.where("ssn").is(12345)), PersonPojo.class);
List<PersonPojo> result = template.find(
new Query(Criteria.where("ssn").is(12345)), PersonPojo.class);
assertThat(result.size(), is(1));
assertThat(result.get(0).getSsn(), is(12345));
}
@@ -69,12 +76,13 @@ public class MappingTests {
PersonCustomIdName p = new PersonCustomIdName(123456, "Custom", "Id");
template.insert(p);
List<PersonCustomIdName> result = template.find(new Query(Criteria.where("ssn").is(123456)), PersonCustomIdName.class);
List<PersonCustomIdName> result = template.find(
new Query(Criteria.where("ssn").is(123456)), PersonCustomIdName.class);
assertThat(result.size(), is(1));
assertNotNull(result.get(0).getCustomId());
}
@Test
@Test
public void testPersonMapProperty() {
PersonMapProperty p = new PersonMapProperty(1234567, "Map", "Property");
Map<String, AccountPojo> accounts = new HashMap<String, AccountPojo>();
@@ -89,10 +97,12 @@ public class MappingTests {
template.insert(p);
assertNotNull(p.getId());
List<PersonMapProperty> result = template.find(new Query(Criteria.where("ssn").is(1234567)), PersonMapProperty.class);
List<PersonMapProperty> result = template.find(
new Query(Criteria.where("ssn").is(1234567)), PersonMapProperty.class);
assertThat(result.size(), is(1));
assertThat(result.get(0).getAccounts().size(), is(2));
assertThat(result.get(0).getAccounts().get("checking").getBalance(), is(1000.0f));
assertThat(result.get(0).getAccounts().get("checking").getBalance(),
is(1000.0f));
}
@Test
@@ -125,7 +135,8 @@ public class MappingTests {
assertNotNull(p.getId());
List<Person> result = template.find(new Query(Criteria.where("ssn").is(123456789)), Person.class);
List<Person> result = template.find(
new Query(Criteria.where("ssn").is(123456789)), Person.class);
assertThat(result.size(), is(1));
assertThat(result.get(0).getAddress().getCountry(), is("USA"));
assertThat(result.get(0).getAccounts(), notNullValue());
@@ -146,13 +157,9 @@ public class MappingTests {
template.insert("person", p1);
template.insert("person", p2);
List<Person> result = template.find(new Query(Criteria.where("ssn").is(1234567890)), Person.class);
List<Person> result = template.find(
new Query(Criteria.where("ssn").is(1234567890)), Person.class);
assertThat(result.size(), is(1));
}
@Test
public void testEvents() {
}
}

View File

@@ -29,13 +29,6 @@ public class QueryTests {
Assert.assertEquals(expected, q.getQueryObject().toString());
}
@Test
public void testSimpleQueryWithChainedCriteria() {
Query q = new Query(where("name").is("Thomas").and("age").lt(80));
String expected = "{ \"name\" : \"Thomas\" , \"age\" : { \"$lt\" : 80}}";
Assert.assertEquals(expected, q.getQueryObject().toString());
}
@Test
public void testQueryWithNot() {
Query q = new Query(where("name").is("Thomas")).and(where("age").not().mod(10, 0));
@@ -81,6 +74,22 @@ public class QueryTests {
Assert.assertEquals(expected, q.getQueryObject().toString());
}
@Test
public void testSimpleQueryWithChainedCriteria() {
Query q = new Query(where("name").is("Thomas").and("age").lt(80));
String expected = "{ \"name\" : \"Thomas\" , \"age\" : { \"$lt\" : 80}}";
Assert.assertEquals(expected, q.getQueryObject().toString());
}
@Test
public void testComplexQueryWithMultipleChainedCriteria() {
Query q1 = new Query(where("name").regex("^T.*").and("age").gt(20).lt(80).and("city").in("Stockholm", "London", "New York"));
Query q2 = new Query(where("name").regex("^T.*").and("age").gt(20).lt(80)).and(where("city").in("Stockholm", "London", "New York"));
Assert.assertEquals(q1.getQueryObject().toString(), q2.getQueryObject().toString());
Query q3 = new Query(where("name").regex("^T.*")).and(where("age").gt(20).lt(80)).and(where("city").in("Stockholm", "London", "New York"));
Assert.assertEquals(q1.getQueryObject().toString(), q3.getQueryObject().toString());
}
@Test
public void testQueryWithElemMatch() {
Query q = new Query(where("openingHours").elemMatch(where("dayOfWeek").is("Monday").and("open").lte("1800")));