Skip to content
Browse files

Support for auto-timestamping and domain events

  • Loading branch information...
1 parent 5dffbaa commit 22d9ad5ddd9ae0753507a949dbbce0efe8b37d94 @graemerocher graemerocher committed Sep 3, 2010
Showing with 505 additions and 39 deletions.
  1. +4 −1 grails-datastore-gorm-redis/src/test/groovy/org/grails/datastore/gorm/redis/RedisSuite.groovy
  2. +1 −0 grails-datastore-gorm-tck/grails-datastore-gorm-tck.iml
  3. +167 −0 grails-datastore-gorm-tck/src/main/groovy/grails/gorm/tests/DomainEventsSpec.groovy
  4. +1 −1 grails-datastore-gorm-tck/src/main/groovy/grails/gorm/tests/GormDatastoreSpec.groovy
  5. +95 −0 grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/events/AutoTimestampInterceptor.java
  6. +91 −0 grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/events/DomainEventInterceptor.java
  7. +5 −0 grails-plugins/redis/src/groovy/org/grails/plugins/redis/RedisDatastoreFactoryBean.groovy
  8. +1 −1 settings.gradle
  9. +7 −6 spring-datastore-appengine/appengine.iml
  10. +44 −0 spring-datastore-core/src/main/groovy/org/springframework/datastore/engine/EmptyInterceptor.java
  11. +6 −6 spring-datastore-core/src/main/groovy/org/springframework/datastore/engine/EntityInterceptor.java
  12. +6 −3 spring-datastore-core/src/main/groovy/org/springframework/datastore/engine/EntityPersister.java
  13. +26 −8 ...src/main/groovy/org/springframework/datastore/keyvalue/engine/AbstractKeyValueEntityPesister.java
  14. +9 −0 ...-datastore-core/src/main/groovy/org/springframework/datastore/mapping/AbstractMappingContext.java
  15. +7 −0 ...atastore-core/src/main/groovy/org/springframework/datastore/mapping/AbstractPersistentEntity.java
  16. +19 −0 spring-datastore-core/src/main/groovy/org/springframework/datastore/mapping/MappingContext.java
  17. +9 −0 spring-datastore-core/src/main/groovy/org/springframework/datastore/mapping/PersistentEntity.java
  18. +7 −13 ...atastore-core/src/main/groovy/org/springframework/datastore/validation/ValidatingInterceptor.java
View
5 ...ls-datastore-gorm-redis/src/test/groovy/org/grails/datastore/gorm/redis/RedisSuite.groovy
@@ -7,6 +7,9 @@ import grails.gorm.tests.WithTransactionSpec
import grails.gorm.tests.InheritanceSpec
import grails.gorm.tests.FindByMethodSpec
import grails.gorm.tests.ListOrderBySpec
+import grails.gorm.tests.ProxyLoadingSpec
+import grails.gorm.tests.GroovyProxySpec
+import grails.gorm.tests.DomainEventsSpec
/**
* Created by IntelliJ IDEA.
@@ -17,7 +20,7 @@ import grails.gorm.tests.ListOrderBySpec
*/
@RunWith(Suite)
@SuiteClasses([
- ListOrderBySpec
+ DomainEventsSpec
])
class RedisSuite {
}
View
1 grails-datastore-gorm-tck/grails-datastore-gorm-tck.iml
@@ -17,6 +17,7 @@
<orderEntry type="library" name="Java Persistence" level="project" />
<orderEntry type="library" name="Spock" level="project" />
<orderEntry type="module" module-name="core" />
+ <orderEntry type="module" module-name="grails-datastore-gorm" />
</component>
</module>
View
167 grails-datastore-gorm-tck/src/main/groovy/grails/gorm/tests/DomainEventsSpec.groovy
@@ -0,0 +1,167 @@
+package grails.gorm.tests
+
+import org.grails.datastore.gorm.events.DomainEventInterceptor
+import org.grails.datastore.gorm.events.AutoTimestampInterceptor
+import org.springframework.datastore.core.Session
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: graemerocher
+ * Date: Sep 3, 2010
+ * Time: 12:15:38 PM
+ * To change this template use File | Settings | File Templates.
+ */
+class DomainEventsSpec extends GormDatastoreSpec{
+
+
+
+ Session setupEventsSession() {
+ def datastore = session.datastore
+ datastore.addEntityInterceptor(new DomainEventInterceptor())
+ datastore.addEntityInterceptor(new AutoTimestampInterceptor())
+ session= datastore.connect()
+ }
+
+
+ void "Test auto time stamping working"() {
+
+ given:
+ session = setupEventsSession()
+
+ def p = new PersonEvent()
+
+ p.name = "Fred"
+ p.save(flush:true)
+ session.clear()
+
+ when:
+ p = PersonEvent.get(1)
+
+ then:
+ sleep(2000)
+
+ p.dateCreated == p.lastUpdated
+
+ when:
+ p.name = "Wilma"
+ p.save(flush:true)
+
+ then:
+ p.dateCreated.before(p.lastUpdated) == true
+ }
+
+// void testOnloadEvent() {
+// def personClass = ga.getDomainClass("PersonEvent")
+// def p = personClass.newInstance()
+//
+// p.name = "Fred"
+// p.save()
+// session.flush()
+// session.clear()
+//
+// p = personClass.clazz.get(1)
+// assertEquals "Bob", p.name
+// }
+
+ void "Test before delete event"() {
+ given:
+ session = setupEventsSession()
+ PersonEvent.resetStore()
+ def p = new PersonEvent()
+ p.name = "Fred"
+ p.save(flush:true)
+ session.clear()
+
+ when:
+ p.delete(flush:true)
+
+ then:
+ assert PersonEvent.STORE['deleted'] == true
+ }
+
+ void "Test before update event"() {
+ given:
+ session = setupEventsSession()
+ PersonEvent.resetStore()
+
+
+ def p = new PersonEvent()
+
+ p.name = "Fred"
+ p.save(flush:true)
+ session.clear()
+
+ when:
+
+ p = PersonEvent.get(p.id)
+
+ then:
+ "Fred" == p.name
+ 0 == PersonEvent.STORE['updated']
+
+
+ when:
+ p.name = "Bob"
+ p.save(flush:true)
+ session.clear()
+ p = PersonEvent.get(p.id)
+ then:
+ "Bob" == p.name
+ 1 == PersonEvent.STORE['updated']
+ }
+
+ void "Test before insert event"() {
+ given:
+ session = setupEventsSession()
+ PersonEvent.resetStore()
+ def p = new PersonEvent()
+
+ p.name = "Fred"
+ p.save(flush:true)
+ session.clear()
+
+ when:
+ p = PersonEvent.get(p.id)
+
+ then:
+ "Fred" == p.name
+ 0 == PersonEvent.STORE['updated']
+ 1 == PersonEvent.STORE['inserted']
+
+ when:
+ p.name = "Bob"
+ p.save(flush:true)
+ session.clear()
+ p = PersonEvent.get(p.id)
+
+ then:
+ "Bob" == p.name
+ 1 == PersonEvent.STORE['updated']
+ 1 == PersonEvent.STORE['inserted']
+
+ }
+
+}
+class PersonEvent {
+ Long id
+ Long version
+ String name
+ Date dateCreated
+ Date lastUpdated
+
+ static STORE = [updated:0, inserted:0]
+
+ static void resetStore() { STORE = [updated:0, inserted:0] }
+
+ def beforeDelete() {
+ STORE["deleted"] = true
+
+ }
+ def beforeUpdate() {
+ STORE["updated"]++
+ }
+ def beforeInsert() {
+ STORE["inserted"]++
+ }
+
+}
View
2 grails-datastore-gorm-tck/src/main/groovy/grails/gorm/tests/GormDatastoreSpec.groovy
@@ -18,7 +18,7 @@ import org.springframework.datastore.core.Session
abstract class GormDatastoreSpec extends Specification {
private static final SETUP_CLASS_NAME = 'org.grails.datastore.gorm.Setup'
- private static final TEST_CLASSES = [Book, Highway,TestEntity, ChildEntity,CommonTypes, Location, City, Country, PlantCategory, Publication]
+ private static final TEST_CLASSES = [PersonEvent, Book, Highway,TestEntity, ChildEntity,CommonTypes, Location, City, Country, PlantCategory, Publication]
@Shared Class setupClass
View
95 ...store-gorm/src/main/groovy/org/grails/datastore/gorm/events/AutoTimestampInterceptor.java
@@ -0,0 +1,95 @@
+/* Copyright (C) 2010 SpringSource
+ *
+ * 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.grails.datastore.gorm.events;
+
+import org.springframework.datastore.core.Datastore;
+import org.springframework.datastore.engine.EmptyInterceptor;
+import org.springframework.datastore.engine.EntityAccess;
+import org.springframework.datastore.mapping.MappingContext;
+import org.springframework.datastore.mapping.PersistentEntity;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * An interceptor that adds support for GORM-style auto-timestamping
+ *
+ * @author Graeme Rocher
+ * @since 1.0
+ */
+public class AutoTimestampInterceptor extends EmptyInterceptor implements MappingContext.Listener {
+ public static final String DATE_CREATED_PROPERTY = "dateCreated";
+ public static final String LAST_UPDATED_PROPERTY = "lastUpdated";
+
+ private Map<PersistentEntity, Boolean> entitiesWithDateCreated = new ConcurrentHashMap<PersistentEntity, Boolean>();
+ private Map<PersistentEntity, Boolean> entitiesWithLastUpdated = new ConcurrentHashMap<PersistentEntity, Boolean>();
+
+ @Override
+ public boolean beforeInsert(PersistentEntity entity, EntityAccess ea) {
+ if(hasDateCreated(entity)) {
+ final Date now = new Date();
+ ea.setProperty(DATE_CREATED_PROPERTY, now);
+
+ if(hasLastupdated(entity)) {
+ ea.setProperty(LAST_UPDATED_PROPERTY, now);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean beforeUpdate(PersistentEntity entity, EntityAccess ea) {
+ if(hasLastupdated(entity)) {
+ ea.setProperty(LAST_UPDATED_PROPERTY, new Date());
+ }
+ return true;
+ }
+
+
+ private boolean hasLastupdated(PersistentEntity entity) {
+ return entitiesWithLastUpdated.containsKey(entity) && entitiesWithLastUpdated.get(entity);
+ }
+
+ private boolean hasDateCreated(PersistentEntity entity) {
+ return entitiesWithDateCreated.containsKey(entity)&& entitiesWithDateCreated.get(entity);
+ }
+
+
+
+ @Override
+ public void setDatastore(Datastore datastore) {
+ super.setDatastore(datastore);
+ for (PersistentEntity persistentEntity : datastore.getMappingContext().getPersistentEntities()) {
+ storeDateCreatedInfo(persistentEntity);
+ storeLastUpdatedInfo(persistentEntity);
+ }
+
+ datastore.getMappingContext().addMappingContextListener(this);
+ }
+
+ private void storeLastUpdatedInfo(PersistentEntity persistentEntity) {
+ entitiesWithLastUpdated.put(persistentEntity, persistentEntity.hasProperty(LAST_UPDATED_PROPERTY, Date.class));
+ }
+
+ private void storeDateCreatedInfo(PersistentEntity persistentEntity) {
+ entitiesWithDateCreated.put(persistentEntity, persistentEntity.hasProperty(DATE_CREATED_PROPERTY, Date.class));
+ }
+
+ public void persistentEntityAdded(PersistentEntity entity) {
+ storeDateCreatedInfo(entity);
+ storeLastUpdatedInfo(entity);
+ }
+}
View
91 ...tastore-gorm/src/main/groovy/org/grails/datastore/gorm/events/DomainEventInterceptor.java
@@ -0,0 +1,91 @@
+/* Copyright (C) 2010 SpringSource
+ *
+ * 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.grails.datastore.gorm.events;
+
+import org.springframework.datastore.core.Datastore;
+import org.springframework.datastore.engine.EmptyInterceptor;
+import org.springframework.datastore.engine.EntityAccess;
+import org.springframework.datastore.mapping.PersistentEntity;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * An interceptor that provides support for GORM domain events
+ *
+ * @author Graeme Rocher
+ * @since 1.0
+ */
+public class DomainEventInterceptor extends EmptyInterceptor {
+
+ private Map<PersistentEntity, Map<String, Method>> entityEvents = new ConcurrentHashMap<PersistentEntity, Map<String, Method>>();
+ public static final Class[] ZERO_PARAMS = new Class[0];
+ public static final String EVENT_BEFORE_INSERT = "beforeInsert";
+ private static final String EVENT_BEFORE_UPDATE = "beforeUpdate";
+ private static final String EVENT_BEFORE_DELETE = "beforeDelete";
+
+ @Override
+ public boolean beforeInsert(PersistentEntity entity, EntityAccess ea) {
+ return invokeEvent(EVENT_BEFORE_INSERT, entity, ea);
+ }
+
+ @Override
+ public boolean beforeUpdate(PersistentEntity entity, EntityAccess ea) {
+ return invokeEvent(EVENT_BEFORE_UPDATE, entity, ea);
+ }
+
+ @Override
+ public boolean beforeDelete(PersistentEntity entity, EntityAccess ea) {
+ return invokeEvent(EVENT_BEFORE_DELETE, entity, ea);
+ }
+
+ private boolean invokeEvent(String eventName, PersistentEntity entity, EntityAccess ea) {
+ final Map<String, Method> events = entityEvents.get(entity);
+ if(events != null) {
+ final Method eventMethod = events.get(eventName);
+ if(eventMethod != null) {
+ final Object result = ReflectionUtils.invokeMethod(eventMethod, ea.getEntity());
+ if(result instanceof Boolean) {
+ return ((Boolean)result).booleanValue();
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void setDatastore(Datastore datastore) {
+ super.setDatastore(datastore);
+
+ for (PersistentEntity entity : datastore.getMappingContext().getPersistentEntities()) {
+ Class javaClass = entity.getJavaClass();
+ final ConcurrentHashMap<String, Method> events = new ConcurrentHashMap<String, Method>();
+ entityEvents.put(entity, events);
+
+ findAndCacheEvent(EVENT_BEFORE_INSERT, javaClass, events);
+ findAndCacheEvent(EVENT_BEFORE_UPDATE, javaClass, events);
+ findAndCacheEvent(EVENT_BEFORE_DELETE, javaClass, events);
+ }
+ }
+
+ private void findAndCacheEvent(String event, Class javaClass, Map<String, Method> events) {
+ final Method method = ReflectionUtils.findMethod(javaClass, event);
+ if(method != null) {
+ events.put(event, method);
+ }
+ }
+}
View
5 grails-plugins/redis/src/groovy/org/grails/plugins/redis/RedisDatastoreFactoryBean.groovy
@@ -25,6 +25,8 @@ import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty
import org.grails.datastore.gorm.GormStaticApi
import org.grails.datastore.gorm.GormInstanceApi
import org.grails.datastore.gorm.redis.RedisGormStaticApi
+import org.grails.datastore.gorm.events.AutoTimestampInterceptor
+import org.grails.datastore.gorm.events.DomainEventInterceptor
/**
* Constructs a RedisDatastore instance
@@ -41,6 +43,9 @@ class RedisDatastoreFactoryBean implements FactoryBean<RedisDatastore>{
RedisDatastore getObject() {
def datastore = new RedisDatastore(mappingContext, config)
+ datastore.addEntityInterceptor(new DomainEventInterceptor())
+ datastore.addEntityInterceptor(new AutoTimestampInterceptor())
+
def enhancer = transactionManager ?
new RedisGormEnhancer(datastore, transactionManager) :
new RedisGormEnhancer(datastore)
View
2 settings.gradle
@@ -1,5 +1,5 @@
include "spring-datastore-core",
- "spring-datastore-appengine",
+/* "spring-datastore-appengine",*/
"spring-datastore-redis",
"spring-datastore-mock",
"spring-datastore-web",
View
13 spring-datastore-appengine/appengine.iml
@@ -23,12 +23,12 @@
<orderEntry type="module-library">
<library name="AppEngine">
<CLASSES>
- <root url="jar://$GRADLE_CACHE$/com.google.appengine/appengine-api-stubs/jars/appengine-api-stubs-1.3.4.jar!/" />
- <root url="jar://$GRADLE_CACHE$/com.google.appengine/appengine-api-1.0-sdk/jars/appengine-api-1.0-sdk-1.3.4.jar!/" />
- <root url="jar://$GRADLE_CACHE$/com.google.appengine/appengine-tools-api/jars/appengine-tools-api-1.3.4.jar!/" />
- <root url="jar://$GRADLE_CACHE$/com.google.appengine/appengine-testing/jars/appengine-testing-1.3.4.jar!/" />
- <root url="jar://$GRADLE_CACHE$/com.google.appengine/appengine-local-runtime/jars/appengine-local-runtime-1.3.4.jar!/" />
- <root url="jar://$GRADLE_CACHE$/com.google.appengine/appengine-api-labs/jars/appengine-api-labs-1.3.4.jar!/" />
+ <root url="jar://$GRADLE_REPOSITORY$/com.google.appengine/appengine-api-stubs/jars/appengine-api-stubs-1.3.4.jar!/" />
+ <root url="jar://$GRADLE_REPOSITORY$/com.google.appengine/appengine-api-1.0-sdk/jars/appengine-api-1.0-sdk-1.3.4.jar!/" />
+ <root url="jar://$GRADLE_REPOSITORY$/com.google.appengine/appengine-tools-api/jars/appengine-tools-api-1.3.4.jar!/" />
+ <root url="jar://$GRADLE_REPOSITORY$/com.google.appengine/appengine-testing/jars/appengine-testing-1.3.4.jar!/" />
+ <root url="jar://$GRADLE_REPOSITORY$/com.google.appengine/appengine-local-runtime/jars/appengine-local-runtime-1.3.4.jar!/" />
+ <root url="jar://$GRADLE_REPOSITORY$/com.google.appengine/appengine-api-labs/jars/appengine-api-labs-1.3.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
@@ -40,6 +40,7 @@
<orderEntry type="library" name="Spring Transaction" level="project" />
<orderEntry type="library" scope="TEST" name="JUnit" level="project" />
<orderEntry type="library" name="BeanUtils" level="project" />
+ <orderEntry type="module" module-name="grails-datastore-gorm" />
</component>
</module>
View
44 ...datastore-core/src/main/groovy/org/springframework/datastore/engine/EmptyInterceptor.java
@@ -0,0 +1,44 @@
+/* Copyright (C) 2010 SpringSource
+ *
+ * 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.datastore.engine;
+
+import org.springframework.datastore.core.Datastore;
+import org.springframework.datastore.mapping.PersistentEntity;
+
+/**
+ * An interceptor that does nothing
+ *
+ * @author Graeme Rocher
+ * @since 1.0
+ */
+public class EmptyInterceptor implements EntityInterceptor{
+ protected Datastore datastore;
+
+ public boolean beforeInsert(PersistentEntity entity, EntityAccess ea) {
+ return true;
+ }
+
+ public boolean beforeUpdate(PersistentEntity entity, EntityAccess ea) {
+ return true;
+ }
+
+ public boolean beforeDelete(PersistentEntity entity, EntityAccess ea) {
+ return true;
+ }
+
+ public void setDatastore(Datastore datastore) {
+ this.datastore = datastore;
+ }
+}
View
12 ...atastore-core/src/main/groovy/org/springframework/datastore/engine/EntityInterceptor.java
@@ -29,28 +29,28 @@
* Called before an entity is inserted
*
* @param entity The entity
- * @param o The object
+ * @param entityAccess The object
* @return False if the operation should be cancelled
*/
- boolean beforeInsert(PersistentEntity entity, Object o);
+ boolean beforeInsert(PersistentEntity entity, EntityAccess entityAccess);
/**
* Called before an entity is updated
*
* @param entity The entity
- * @param o The object
+ * @param entityAccess The object
* @return False if the operation should be cancelled
*/
- boolean beforeUpdate(PersistentEntity entity, Object o);
+ boolean beforeUpdate(PersistentEntity entity, EntityAccess entityAccess);
/**
* Called before an entity is deleted
*
* @param entity The entity
- * @param obj The object
+ * @param entityAccess The object
* @return False if the operation should be cancelled
*/
- boolean beforeDelete(PersistentEntity entity, Object obj);
+ boolean beforeDelete(PersistentEntity entity, EntityAccess entityAccess);
// TODO: Add more interception hooks
View
9 ...-datastore-core/src/main/groovy/org/springframework/datastore/engine/EntityPersister.java
@@ -95,7 +95,7 @@ public Serializable getObjectIdentifier(Object obj) {
public final Serializable persist(Object obj) {
if(!persistentEntity.isInstance(obj)) throw new IllegalArgumentException("Object ["+obj+"] is not an instance supported by the persister for class ["+getType().getName()+"]");
- return persistEntity(getPersistentEntity(), new EntityAccess(getPersistentEntity(), obj));
+ return persistEntity(getPersistentEntity(), obj);
}
public List<Serializable> persist(Iterable objs) {
@@ -134,10 +134,10 @@ public final Object retrieve(Serializable key) {
* Persist the given persistent entity
*
* @param persistentEntity The PersistentEntity
- * @param entityAccess An object that allows easy access to the entities properties
+ * @param obj
* @return The generated key
*/
- protected abstract Serializable persistEntity(PersistentEntity persistentEntity, EntityAccess entityAccess);
+ protected abstract Serializable persistEntity(PersistentEntity persistentEntity, Object obj);
public final void delete(Iterable objects) {
if(objects != null) {
@@ -156,5 +156,8 @@ public void delete(Object obj) {
protected abstract void deleteEntities(PersistentEntity persistentEntity, Iterable objects);
+ protected EntityAccess createEntityAccess(PersistentEntity persistentEntity, Object obj) {
+ return new EntityAccess(persistentEntity, obj);
+ }
}
View
34 .../groovy/org/springframework/datastore/keyvalue/engine/AbstractKeyValueEntityPesister.java
@@ -80,7 +80,7 @@ protected String getKeyspace(ClassMapping<Family> cm, String defaultValue) {
protected void deleteEntity(PersistentEntity persistentEntity, Object obj) {
if(obj != null) {
for (EntityInterceptor interceptor : interceptors) {
- if(!interceptor.beforeDelete(persistentEntity, obj)) return;
+ if(!interceptor.beforeDelete(persistentEntity, createEntityAccess(persistentEntity, obj))) return;
}
K key = readIdentifierFromObject(obj);
@@ -90,6 +90,21 @@ protected void deleteEntity(PersistentEntity persistentEntity, Object obj) {
}
}
+ @Override
+ protected EntityAccess createEntityAccess(PersistentEntity persistentEntity, Object obj) {
+ return new EntityAccess(persistentEntity, obj);
+ }
+
+ protected EntityAccess createEntityAccess(PersistentEntity persistentEntity, Object obj, final T nativeEntry) {
+ return new EntityAccess(persistentEntity, obj) {
+ @Override
+ public void setProperty(String name, Object value) {
+ super.setProperty(name, value);
+ setEntryValue(nativeEntry, name, value);
+ }
+ };
+ }
+
/**
* Deletes a single entry
*
@@ -114,7 +129,7 @@ protected final void deleteEntities(PersistentEntity persistentEntity, Iterable
}
private K readIdentifierFromObject(Object object) {
- EntityAccess access = new EntityAccess(getPersistentEntity(), object);
+ EntityAccess access = createEntityAccess(getPersistentEntity(), object);
access.setConversionService(getMappingContext().getConversionService());
final Object idValue = access.getIdentifier();
K key = null;
@@ -156,7 +171,7 @@ public boolean isLocked(Object o) {
}
public void unlock(Object o) {
- unlockEntry(getPersistentEntity(), entityFamily, (Serializable) new EntityAccess(getPersistentEntity(), o).getIdentifier());
+ unlockEntry(getPersistentEntity(), entityFamily, (Serializable) createEntityAccess(getPersistentEntity(), o).getIdentifier());
}
/**
@@ -191,7 +206,7 @@ public Object proxy(Serializable key) {
public Serializable refresh(Object o) {
final PersistentEntity entity = getPersistentEntity();
- EntityAccess ea = new EntityAccess(entity, o);
+ EntityAccess ea = createEntityAccess(entity, o);
Serializable identifier = (Serializable) ea.getIdentifier();
if(identifier != null) {
@@ -210,7 +225,7 @@ protected Object createObjectFromNativeEntry(PersistentEntity persistentEntity,
}
protected void refreshObjectStateFromNativeEntry(PersistentEntity persistentEntity, Object obj, Serializable nativeKey, T nativeEntry) {
- EntityAccess ea = new EntityAccess(persistentEntity, obj);
+ EntityAccess ea = createEntityAccess(persistentEntity, obj, nativeEntry);
ea.setConversionService(getMappingContext().getConversionService());
String idName = ea.getIdentifierName();
ea.setProperty(idName, nativeKey);
@@ -270,6 +285,8 @@ else if(Set.class.isAssignableFrom(association.getType())) {
}
}
+
+
/**
* Subclasses should override to customize how entities in hierarchies are discriminated
* @param persistentEntity The PersistentEntity
@@ -292,11 +309,12 @@ private boolean isLazyAssociation(PropertyMapping<KeyValue> associationPropertyM
}
@Override
- protected final Serializable persistEntity(final PersistentEntity persistentEntity, final EntityAccess entityAccess) {
+ protected final Serializable persistEntity(final PersistentEntity persistentEntity, Object obj) {
ClassMapping<Family> cm = persistentEntity.getMapping();
String family = entityFamily;
final T e = createNewEntry(family);
+ final EntityAccess entityAccess = createEntityAccess(persistentEntity, obj, e );
K k = readObjectIdentifier(entityAccess, cm);
boolean isUpdate = k != null;
@@ -385,7 +403,7 @@ else if(prop instanceof ToOne) {
si.getPendingInserts().add(new Runnable() {
public void run() {
for (EntityInterceptor interceptor : interceptors) {
- if(!interceptor.beforeInsert(persistentEntity, entityAccess.getEntity())) return;
+ if(!interceptor.beforeInsert(persistentEntity, entityAccess)) return;
}
storeEntry(persistentEntity, updateId, e);
updateOneToManyIndices(updateId, oneToManyKeys);
@@ -407,7 +425,7 @@ public void run() {
public void run() {
for (EntityInterceptor interceptor : interceptors) {
- if(!interceptor.beforeUpdate(persistentEntity, entityAccess.getEntity())) return;
+ if(!interceptor.beforeUpdate(persistentEntity, entityAccess)) return;
}
updateEntry(persistentEntity, updateId, e);
updateOneToManyIndices(updateId, oneToManyKeys);
View
9 ...re-core/src/main/groovy/org/springframework/datastore/mapping/AbstractMappingContext.java
@@ -38,6 +38,7 @@
protected Map<String,PersistentEntity> persistentEntitiesByName = new ConcurrentHashMap<String,PersistentEntity>();
protected Map<PersistentEntity,Map<String,PersistentEntity>> persistentEntitiesByDiscriminator = new ConcurrentHashMap<PersistentEntity,Map<String,PersistentEntity>>();
protected Map<PersistentEntity,Validator> entityValidators = new ConcurrentHashMap<PersistentEntity, Validator>();
+ protected Collection<Listener> eventListeners = new ConcurrentLinkedQueue<Listener>();
protected GenericConversionService conversionService = new GenericConversionService();
protected ProxyFactory proxyFactory;
@@ -52,6 +53,11 @@ public ProxyFactory getProxyFactory() {
return proxyFactory;
}
+ public void addMappingContextListener(Listener listener) {
+ if(listener != null)
+ eventListeners.add(listener);
+ }
+
public void setProxyFactory(ProxyFactory factory) {
if(factory != null) {
this.proxyFactory = factory;
@@ -101,6 +107,9 @@ public final PersistentEntity addPersistentEntity(Class javaClass) {
}
children.put(entity.getDiscriminator(), entity);
}
+ for (Listener eventListener : eventListeners) {
+ eventListener.persistentEntityAdded(entity);
+ }
}
return entity;
View
7 ...-core/src/main/groovy/org/springframework/datastore/mapping/AbstractPersistentEntity.java
@@ -14,12 +14,14 @@
*/
package org.springframework.datastore.mapping;
+import org.springframework.beans.BeanUtils;
import org.springframework.datastore.core.EntityCreationException;
import org.springframework.datastore.mapping.lifecycle.Initializable;
import org.springframework.datastore.mapping.types.Association;
import org.springframework.datastore.mapping.types.OneToMany;
import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
import java.lang.reflect.Modifier;
import java.util.*;
@@ -81,6 +83,11 @@ public void initialize() {
getMapping().getMappedForm(); // initialize mapping
}
+ public boolean hasProperty(String name, Class type) {
+ final PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(getJavaClass(), name);
+ return pd != null && pd.getPropertyType().equals(type);
+ }
+
public PersistentEntity getParentEntity() {
return parentEntity;
}
View
19 ...-datastore-core/src/main/groovy/org/springframework/datastore/mapping/MappingContext.java
@@ -139,4 +139,23 @@
* @param factory The proxy factory
*/
void setProxyFactory(ProxyFactory factory);
+
+ /**
+ * Adds a new mapping context listener instance
+ * @param listener The listener
+ */
+ void addMappingContextListener(Listener listener);
+
+ /**
+ * Implementors can register for events when the mapping context changes
+ *
+ */
+ public static interface Listener {
+
+ /**
+ * Fired when a new entity is added
+ * @param entity The entity
+ */
+ void persistentEntityAdded(PersistentEntity entity);
+ }
}
View
9 ...atastore-core/src/main/groovy/org/springframework/datastore/mapping/PersistentEntity.java
@@ -3,6 +3,7 @@
import org.springframework.datastore.mapping.lifecycle.Initializable;
import org.springframework.datastore.mapping.types.Association;
+import java.util.Date;
import java.util.List;
/**
@@ -126,4 +127,12 @@
* @return The MappingContext instance
*/
MappingContext getMappingContext();
+
+ /**
+ * Checks whether an entity has a bean property of the given name and type
+ * @param name The name
+ * @param type The type
+ * @return True if it does
+ */
+ boolean hasProperty(String name, Class type);
}
View
20 ...-core/src/main/groovy/org/springframework/datastore/validation/ValidatingInterceptor.java
@@ -15,6 +15,8 @@
package org.springframework.datastore.validation;
import org.springframework.datastore.core.Datastore;
+import org.springframework.datastore.engine.EmptyInterceptor;
+import org.springframework.datastore.engine.EntityAccess;
import org.springframework.datastore.engine.EntityInterceptor;
import org.springframework.datastore.mapping.PersistentEntity;
import org.springframework.validation.BeanPropertyBindingResult;
@@ -29,20 +31,15 @@
* @author Graeme Rocher
* @since 1.0
*/
-public class ValidatingInterceptor implements EntityInterceptor{
- private Datastore datastore;
-
- public boolean beforeInsert(PersistentEntity entity, Object o) {
- return doValidate(entity, o);
+public class ValidatingInterceptor extends EmptyInterceptor{
+ public boolean beforeInsert(PersistentEntity entity, EntityAccess e) {
+ return doValidate(entity, e.getEntity());
}
- public boolean beforeUpdate(PersistentEntity entity, Object o) {
- return doValidate(entity, o);
+ public boolean beforeUpdate(PersistentEntity entity, EntityAccess e) {
+ return doValidate(entity, e.getEntity());
}
- public boolean beforeDelete(PersistentEntity entity, Object obj) {
- return true; // do nothing for deletes
- }
private boolean doValidate(PersistentEntity entity, Object o) {
Validator v = datastore.getMappingContext().getEntityValidator(entity);
@@ -67,7 +64,4 @@ protected void onErrors(Object object, Errors errors) {
// do nothing
}
- public void setDatastore(Datastore datastore) {
- this.datastore = datastore;
- }
}

0 comments on commit 22d9ad5

Please sign in to comment.
Something went wrong with that request. Please try again.