Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
89d77d5
Introduce CreatedDate and LastModifiedDate annotations
codeconsole Oct 6, 2025
841fe22
Introduce GrailsExtension importGrailsAnnotations that will auto impo…
codeconsole Oct 6, 2025
568060d
Merge branch '7.0.x' into 7.0.x-autotimestamp-enhancements
codeconsole Oct 8, 2025
f0de080
Fix for mongodb autotimestamp properties not being marked dirty. Prop…
codeconsole Oct 8, 2025
2c91003
unused import
codeconsole Oct 8, 2025
e935901
Skip null check on AutoTimestamp properties
codeconsole Oct 8, 2025
7fed24c
Hide AutoTimestamp properties from scaffold input/edit views
codeconsole Oct 8, 2025
00a4f03
remove duplicate method
codeconsole Oct 8, 2025
0997abd
Merge branch '7.0.x' into 7.0.x-autotimestamp-enhancements
codeconsole Oct 8, 2025
8019058
Merge branch '7.0.x' into 7.0.x-autotimestamp-enhancements
codeconsole Oct 9, 2025
4d93af3
Merge branch '7.0.x' into 7.0.x-autotimestamp-enhancements
codeconsole Oct 15, 2025
a73aac7
Revert setting properties dirty in AutoTimestampEventListener as this…
codeconsole Oct 15, 2025
809f1d0
Deprecate @AutoTimestamp
codeconsole Oct 15, 2025
f05cee7
Cache annotation lookups when not in development mode
codeconsole Oct 16, 2025
f37dcc1
Add jakarta.validation.constraints.* to common annotation star imports
codeconsole Oct 16, 2025
a052ed1
null check on persistentProperty
codeconsole Oct 16, 2025
8602be4
remove unused imports
codeconsole Oct 17, 2025
df38a80
Support for Spring Data annotations
codeconsole Oct 18, 2025
e1597d9
@CreatedBy and @LastModifiedBy support
codeconsole Oct 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package org.grails.datastore.gorm

import grails.gorm.annotation.AutoTimestamp
import grails.gorm.annotation.CreatedDate
import grails.gorm.annotation.LastModifiedDate
import grails.persistence.Entity
import org.apache.grails.data.simple.core.GrailsDataCoreTckManager
import org.apache.grails.data.testing.tck.base.GrailsDataTckSpec
Expand All @@ -28,7 +30,7 @@ import static grails.gorm.annotation.AutoTimestamp.EventType.CREATED

class CustomAutoTimestampSpec extends GrailsDataTckSpec<GrailsDataCoreTckManager> {
void setupSpec() {
manager.domainClasses.addAll([AutoTimestampedChildEntity, AutoTimestampedParentEntity, Image, RecordCustom])
manager.domainClasses.addAll([AutoTimestampedChildEntity, AutoTimestampedParentEntity, Image, RecordCustom, RecordWithAliases])
}

void "Test when the auto timestamp properties are customized, they are correctly set"() {
Expand Down Expand Up @@ -147,6 +149,69 @@ class CustomAutoTimestampSpec extends GrailsDataTckSpec<GrailsDataCoreTckManager
e.modified != null
e.created != null
}

void "Test @CreatedDate and @LastModifiedDate annotation aliases"() {
when: "An entity with alias annotations is persisted"
def r = new RecordWithAliases(name: "Test")
r.save(flush: true, failOnError: true)
manager.session.clear()
r = RecordWithAliases.get(r.id)
sleep(1) // give the date comparison below a chance diff

then: "the timestamp properties are set"
r.createdAt != null
r.updatedAt != null
r.createdAt.time < new Date().time
r.updatedAt.time < new Date().time

when: "An entity is modified"
Date previousCreated = r.createdAt
Date previousUpdated = r.updatedAt
r.name = "Test 2"
sleep(1) // give the save a chance to set a different time
r.save(flush: true)
manager.session.clear()
r = RecordWithAliases.get(r.id)

then: "the lastModified property is updated and createdDate is not"
r.updatedAt != null
previousUpdated.time < r.updatedAt.time
previousCreated.time == r.createdAt.time
}

void "Test @CreatedDate and @LastModifiedDate with insertOverwrite config"() {
when: "An entity is persisted and insertOverwrite is false"
AutoTimestampEventListener autoTimestampEventListener =
RecordWithAliases.gormPersistentEntity.mappingContext.eventListeners.find { it.class == AutoTimestampEventListener }
autoTimestampEventListener.insertOverwrite = false

def r = new RecordWithAliases(name: "Test")
def now = new Date()
r.createdAt = new Date(now.time)
r.updatedAt = r.createdAt
sleep(1) // give the save a chance to set a different time
r.save(flush: true, failOnError: true)
manager.session.clear()
r = RecordWithAliases.get(r.id)

then: "the timestamp properties are not overwritten"
now.time == r.updatedAt.time
now.time == r.createdAt.time

when: "An entity is modified"
Date previousCreated = r.createdAt
Date previousUpdated = r.updatedAt
r.name = "Test 2"
sleep(1) // give the save a chance to set a different time
r.save(flush: true)
manager.session.clear()
r = RecordWithAliases.get(r.id)

then: "the lastModified property is updated and createdDate is not"
r.updatedAt != null
previousUpdated.time < r.updatedAt.time
previousCreated.time == r.createdAt.time
}
}

@Entity
Expand Down Expand Up @@ -184,4 +249,14 @@ class AutoTimestampedParentEntity {
@Entity
class AutoTimestampedChildEntity extends AutoTimestampedParentEntity {
String name
}

@Entity
class RecordWithAliases {
Long id
String name
@CreatedDate
Date createdAt
@LastModifiedDate
Date updatedAt
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
*
* @author Scott Murphy Heiberg
* @since 7.0
* @deprecated Use {@link CreatedDate} for creation timestamps or {@link LastModifiedDate} for update timestamps instead
*/
@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface AutoTimestamp {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* https://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 grails.gorm.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* A property annotation used to automatically populate a field with the current auditor
* upon GORM insert events. The current auditor is retrieved from an {@link org.grails.datastore.gorm.timestamp.AuditorAware}
* bean registered in the Spring application context.
*
* <p>Example usage:</p>
* <pre>
* class Book {
* &#64;CreatedBy
* String createdBy
*
* &#64;CreatedBy
* User creator
*
* &#64;CreatedBy
* Long creatorId
* }
* </pre>
*
* <p>The field type should match the type parameter of your {@link org.grails.datastore.gorm.timestamp.AuditorAware}
* implementation (e.g., String, Long, User, etc.).</p>
*
* @author Scott Murphy Heiberg
* @since 7.0
* @see org.grails.datastore.gorm.timestamp.AuditorAware
* @see LastModifiedBy
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface CreatedBy {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* https://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 grails.gorm.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* A property annotation used to apply auto-timestamping on a field
* upon gorm insert events. This is an alias for @AutoTimestamp(EventType.CREATED).
*
* @author Scott Murphy Heiberg
* @since 7.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface CreatedDate {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* https://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 grails.gorm.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* A property annotation used to automatically populate a field with the current auditor
* upon GORM insert and update events. The current auditor is retrieved from an
* {@link org.grails.datastore.gorm.timestamp.AuditorAware} bean registered in the Spring application context.
*
* <p>Example usage:</p>
* <pre>
* class Book {
* &#64;LastModifiedBy
* String lastModifiedBy
*
* &#64;LastModifiedBy
* User lastModifier
*
* &#64;LastModifiedBy
* Long lastModifierId
* }
* </pre>
*
* <p>The field type should match the type parameter of your {@link org.grails.datastore.gorm.timestamp.AuditorAware}
* implementation (e.g., String, Long, User, etc.).</p>
*
* @author Scott Murphy Heiberg
* @since 7.0
* @see org.grails.datastore.gorm.timestamp.AuditorAware
* @see CreatedBy
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface LastModifiedBy {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* https://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 grails.gorm.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* A property annotation used to apply auto-timestamping on a field
* upon gorm insert and update events. This is an alias for @AutoTimestamp(EventType.UPDATED).
*
* @author Scott Murphy Heiberg
* @since 7.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface LastModifiedDate {
}
Loading
Loading