From d67e1caac498cda364f9876a23fb47bb95c9d7b2 Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Sun, 8 Dec 2024 20:05:21 -0800 Subject: [PATCH] Allow customization of auto timestamp dateCreated and lastUpdated properties --- .../datastore/mapping/config/Entity.groovy | 3 + .../gorm/tests/CustomAutotimeStampSpec.groovy | 47 +++++++ .../events/AutoTimestampEventListener.java | 127 +++++++----------- 3 files changed, 99 insertions(+), 78 deletions(-) create mode 100644 grails-datastore-gorm-tck/src/main/groovy/grails/gorm/tests/CustomAutotimeStampSpec.groovy diff --git a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/config/Entity.groovy b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/config/Entity.groovy index e80dc417d1e..dc819ace172 100644 --- a/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/config/Entity.groovy +++ b/grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/config/Entity.groovy @@ -56,6 +56,9 @@ class Entity

{ * @return Whether automatic time stamps should be applied to 'lastUpdate' and 'dateCreated' properties */ boolean autoTimestamp = true + String dateCreated = null + String lastUpdated = null + /** * @return Whether the entity should be autowired */ diff --git a/grails-datastore-gorm-tck/src/main/groovy/grails/gorm/tests/CustomAutotimeStampSpec.groovy b/grails-datastore-gorm-tck/src/main/groovy/grails/gorm/tests/CustomAutotimeStampSpec.groovy new file mode 100644 index 00000000000..a611a614a5e --- /dev/null +++ b/grails-datastore-gorm-tck/src/main/groovy/grails/gorm/tests/CustomAutotimeStampSpec.groovy @@ -0,0 +1,47 @@ +package grails.gorm.tests + +import grails.persistence.Entity + +class CustomAutotimeStampSpec extends GormDatastoreSpec{ + + void "Test when the auto timestamp properties are customized, they are correctly set"() { + when:"An entity is persisted" + def r = new RecordCustom(name: "Test") + r.save(flush:true) + session.clear() + r = RecordCustom.get(r.id) + + then:"the custom lastUpdated and dateCreated are set" + r.modified != null && r.modified < new Date() + r.created != null && r.created < new Date() + + when:"An entity is modified" + Date previousCreated = r.created + Date previousModified = r.modified + r.name = "Test 2" + r.save(flush:true) + session.clear() + r = RecordCustom.get(r.id) + + then:"the custom lastUpdated property is updated and dateCreated is not" + r.modified != null && previousModified > r.modified + previousCreated == r.created + } + @Override + List getDomainClasses() { + [RecordCustom] + } +} + +@Entity +class RecordCustom { + Long id + String name + Date created + Date modified + + static mapping = { + dateCreated 'created' + lastUpdated 'modified' + } +} diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/events/AutoTimestampEventListener.java b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/events/AutoTimestampEventListener.java index fddff3a7876..1781f93dec6 100644 --- a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/events/AutoTimestampEventListener.java +++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/events/AutoTimestampEventListener.java @@ -45,9 +45,9 @@ public class AutoTimestampEventListener extends AbstractPersistenceEventListener public static final String DATE_CREATED_PROPERTY = "dateCreated"; public static final String LAST_UPDATED_PROPERTY = "lastUpdated"; - protected Map entitiesWithDateCreated = new ConcurrentHashMap(); - protected Map entitiesWithLastUpdated = new ConcurrentHashMap(); - protected Collection uninitializedEntities = new ConcurrentLinkedQueue(); + protected Map entitiesWithDateCreated = new ConcurrentHashMap<>(); + protected Map entitiesWithLastUpdated = new ConcurrentHashMap<>(); + protected Collection uninitializedEntities = new ConcurrentLinkedQueue<>(); private TimestampProvider timestampProvider = new DefaultTimestampProvider(); @@ -80,8 +80,7 @@ protected void onPersistenceEvent(final AbstractPersistenceEvent event) { if (event.getEventType() == EventType.PreInsert) { beforeInsert(event.getEntity(), event.getEntityAccess()); - } - else if (event.getEventType() == EventType.PreUpdate) { + } else if (event.getEventType() == EventType.PreUpdate) { beforeUpdate(event.getEntity(), event.getEntityAccess()); } } @@ -96,82 +95,69 @@ public boolean beforeInsert(PersistentEntity entity, EntityAccess ea) { initializeIfNecessary(entity, name); Class dateCreatedType = null; Object timestamp = null; - if (hasDateCreated(name)) { - dateCreatedType = ea.getPropertyType(DATE_CREATED_PROPERTY); + String prop = getDateCreatedPropertyName(name); + if (prop != null) { + dateCreatedType = ea.getPropertyType(prop); timestamp = timestampProvider.createTimestamp(dateCreatedType); - ea.setProperty(DATE_CREATED_PROPERTY, timestamp); + ea.setProperty(prop, timestamp); } - if (hasLastUpdated(name)) { - Class lastUpdateType = ea.getPropertyType(LAST_UPDATED_PROPERTY); - if(dateCreatedType == null || !lastUpdateType.isAssignableFrom(dateCreatedType)) { + prop = getLastUpdatedPropertyName(name); + if (prop != null) { + Class lastUpdateType = ea.getPropertyType(prop); + if (dateCreatedType == null || !lastUpdateType.isAssignableFrom(dateCreatedType)) { timestamp = timestampProvider.createTimestamp(lastUpdateType); } - ea.setProperty(LAST_UPDATED_PROPERTY, timestamp); + ea.setProperty(prop, timestamp); } return true; } private void initializeIfNecessary(PersistentEntity entity, String name) { - if(uninitializedEntities.contains(name)) { + if (uninitializedEntities.contains(name)) { storeDateCreatedAndLastUpdatedInfo(entity); uninitializedEntities.remove(name); } } public boolean beforeUpdate(PersistentEntity entity, EntityAccess ea) { - if (hasLastUpdated(entity.getName())) { - Class lastUpdateType = ea.getPropertyType(LAST_UPDATED_PROPERTY); + String prop = getLastUpdatedPropertyName(entity.getName()); + if (prop != null) { + Class lastUpdateType = ea.getPropertyType(prop); Object timestamp = timestampProvider.createTimestamp(lastUpdateType); - ea.setProperty(LAST_UPDATED_PROPERTY, timestamp); + ea.setProperty(prop, timestamp); } return true; } - /** - * Here for binary compatibility. Deprecated. - * - * @deprecated Use {@link #hasLastUpdated(String)} instead - */ - @Deprecated - protected boolean hasLastUpdated(PersistentEntity entity) { - return hasLastUpdated(entity.getName()); + protected String getLastUpdatedPropertyName(String n) { + return entitiesWithLastUpdated.get(n); } - protected boolean hasLastUpdated(String n) { - return entitiesWithLastUpdated.containsKey(n) && entitiesWithLastUpdated.get(n); - } - - /** - * Here for binary compatibility. Deprecated. - * - * @deprecated Use {@link #hasDateCreated(String)} instead - */ - @Deprecated - protected boolean hasDateCreated(PersistentEntity entity) { - return hasDateCreated(entity.getName()); - } - - protected boolean hasDateCreated(String n) { - return entitiesWithDateCreated.containsKey(n)&& entitiesWithDateCreated.get(n); + protected String getDateCreatedPropertyName(String n) { + return entitiesWithDateCreated.get(n); } protected void storeDateCreatedAndLastUpdatedInfo(PersistentEntity persistentEntity) { - if(persistentEntity.isInitialized()) { - + if (persistentEntity.isInitialized()) { ClassMapping classMapping = persistentEntity.getMapping(); Entity mappedForm = classMapping.getMappedForm(); - if(mappedForm == null || mappedForm.isAutoTimestamp()) { - storeTimestampAvailability(entitiesWithDateCreated, persistentEntity, persistentEntity.getPropertyByName(DATE_CREATED_PROPERTY)); - storeTimestampAvailability(entitiesWithLastUpdated, persistentEntity, persistentEntity.getPropertyByName(LAST_UPDATED_PROPERTY)); + if (mappedForm == null || mappedForm.isAutoTimestamp()) { + storeTimestampAvailability(entitiesWithDateCreated, persistentEntity, + persistentEntity.getPropertyByName(mappedForm.getDateCreated() != null? + mappedForm.getDateCreated() : DATE_CREATED_PROPERTY)); + storeTimestampAvailability(entitiesWithLastUpdated, persistentEntity, + persistentEntity.getPropertyByName(mappedForm.getLastUpdated() != null? + mappedForm.getLastUpdated() : LAST_UPDATED_PROPERTY)); } - } - else { + } else { uninitializedEntities.add(persistentEntity.getName()); } } - protected void storeTimestampAvailability(Map timestampAvailabilityMap, PersistentEntity persistentEntity, PersistentProperty property) { - timestampAvailabilityMap.put(persistentEntity.getName(), property != null && timestampProvider.supportsCreating(property.getType())); + protected void storeTimestampAvailability(Map timestampAvailabilityMap, PersistentEntity persistentEntity, PersistentProperty property) { + if (property != null && timestampProvider.supportsCreating(property.getType())) { + timestampAvailabilityMap.put(persistentEntity.getName(), property.getName()); + } } public void persistentEntityAdded(PersistentEntity entity) { @@ -186,25 +172,25 @@ public void setTimestampProvider(TimestampProvider timestampProvider) { this.timestampProvider = timestampProvider; } - private void processAllEntries(final Set> entries, final Runnable runnable) { - Map originalValues = new LinkedHashMap(); - for(Map.Entry entry: entries) { + private void processAllEntries(final Set> entries, final Runnable runnable) { + Map originalValues = new LinkedHashMap<>(); + for (Map.Entry entry: entries) { originalValues.put(entry.getKey(), entry.getValue()); - entry.setValue(false); + entry.setValue(null); } runnable.run(); - for(Map.Entry entry: entries) { + for (Map.Entry entry: entries) { entry.setValue(originalValues.get(entry.getKey())); } } - private void processEntries(final List classes, Map entities, final Runnable runnable) { - Set> entries = new HashSet<>(); + private void processEntries(final List classes, Map entities, final Runnable runnable) { + Set> entries = new HashSet<>(); final List classNames = new ArrayList<>(classes.size()); - for(Class clazz: classes) { + for (Class clazz: classes) { classNames.add(clazz.getName()); } - for (Map.Entry entry: entities.entrySet()) { + for (Map.Entry entry: entities.entrySet()) { if (classNames.contains(entry.getKey())) { entries.add(entry); } @@ -238,7 +224,7 @@ public void withoutLastUpdated(final List classes, final Runnable runnabl * @param runnable The code to execute while the last updated listener is disabled */ public void withoutLastUpdated(final Class clazz, final Runnable runnable) { - ArrayList list = new ArrayList(1); + ArrayList list = new ArrayList<>(1); list.add(clazz); withoutLastUpdated(list, runnable); } @@ -269,7 +255,7 @@ public void withoutDateCreated(final List classes, final Runnable runnabl * @param runnable The code to execute while the date created listener is disabled */ public void withoutDateCreated(final Class clazz, final Runnable runnable) { - ArrayList list = new ArrayList(1); + ArrayList list = new ArrayList<>(1); list.add(clazz); withoutDateCreated(list, runnable); } @@ -280,12 +266,7 @@ public void withoutDateCreated(final Class clazz, final Runnable runnable) { * @param runnable The code to execute while the timestamp listeners are disabled */ public void withoutTimestamps(final Runnable runnable) { - withoutDateCreated(new Runnable() { - @Override - public void run() { - withoutLastUpdated(runnable); - } - }); + withoutDateCreated(() -> withoutLastUpdated(runnable)); } /** @@ -295,12 +276,7 @@ public void run() { * @param runnable The code to execute while the timestamp listeners are disabled */ public void withoutTimestamps(final List classes, final Runnable runnable) { - withoutDateCreated(classes, new Runnable() { - @Override - public void run() { - withoutLastUpdated(classes, runnable); - } - }); + withoutDateCreated(classes, () -> withoutLastUpdated(classes, runnable)); } /** @@ -310,12 +286,7 @@ public void run() { * @param runnable The code to execute while the timestamp listeners are disabled */ public void withoutTimestamps(final Class clazz, final Runnable runnable) { - withoutDateCreated(clazz, new Runnable() { - @Override - public void run() { - withoutLastUpdated(clazz, runnable); - } - }); + withoutDateCreated(clazz, () -> withoutLastUpdated(clazz, runnable)); } }