matrei commented on code in PR #15118:
URL: https://github.com/apache/grails-core/pull/15118#discussion_r2486557017


##########
grails-datamapping-core/src/main/groovy/grails/gorm/annotation/AutoTimestamp.java:
##########
@@ -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

Review Comment:
   ```suggestion
   @Deprecated(forRemoval = true)
   ```



##########
grails-datamapping-core/src/main/groovy/grails/gorm/annotation/CreatedDate.java:
##########
@@ -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

Review Comment:
   ```suggestion
    * @since 7.1
   ```



##########
grails-datamapping-core/src/main/groovy/grails/gorm/annotation/CreatedBy.java:
##########
@@ -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

Review Comment:
   ```suggestion
    * @since 7.1
   ```



##########
grails-datamapping-core/src/main/groovy/grails/gorm/annotation/LastModifiedDate.java:
##########
@@ -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

Review Comment:
   ```suggestion
    * @since 7.1
   ```



##########
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/AutoTimestampUtils.java:
##########
@@ -0,0 +1,202 @@
+/*
+ *  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 org.grails.datastore.mapping.model;
+
+import java.lang.reflect.Field;
+
+import org.springframework.util.ReflectionUtils;
+
+import grails.util.Environment;
+import org.grails.datastore.mapping.config.Property;
+import org.grails.datastore.mapping.config.Property.AutoTimestampType;
+
+/**
+ * Utility class for detecting and caching auto-timestamp and auditing 
annotations on domain properties.
+ * This avoids repeated reflection calls by storing the annotation type in the 
Property metadata.
+ *
+ * <p>Supports the following annotations (both GORM and Spring Data 
variants):</p>
+ * <ul>
+ *   <li>@CreatedDate / @grails.gorm.annotation.CreatedDate - automatically 
set on insert</li>
+ *   <li>@LastModifiedDate / @grails.gorm.annotation.LastModifiedDate - 
automatically set on insert and update</li>
+ *   <li>@CreatedBy / @grails.gorm.annotation.CreatedBy - automatically 
populated with current auditor on insert</li>
+ *   <li>@LastModifiedBy / @grails.gorm.annotation.LastModifiedBy - 
automatically populated with current auditor on insert and update</li>
+ *   <li>@AutoTimestamp - GORM-specific annotation for backwards 
compatibility</li>
+ * </ul>
+ *
+ * <p>Caching is automatically disabled in development mode ({@link 
Environment#isDevelopmentMode()})
+ * to ensure annotation changes are picked up during class reloading.</p>
+ *
+ * @author Scott Murphy Heiberg
+ * @since 7.0
+ */
+public class AutoTimestampUtils {
+
+    private static final String CREATED_DATE_ANNOTATION = 
"grails.gorm.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_ANNOTATION = 
"grails.gorm.annotation.LastModifiedDate";
+    private static final String AUTO_TIMESTAMP_ANNOTATION = 
"grails.gorm.annotation.AutoTimestamp";
+    private static final String CREATED_BY_ANNOTATION = 
"grails.gorm.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_ANNOTATION = 
"grails.gorm.annotation.LastModifiedBy";
+
+    private static final String CREATED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedDate";
+    private static final String CREATED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedBy";
+
+    /**
+     * Gets the auto-timestamp type for a persistent property, using cached 
metadata when not in development mode.
+     *
+     * <p>In development mode, this method will always perform reflection to 
detect the current
+     * annotation state, ensuring that annotation changes during class 
reloading are immediately
+     * recognized. In production, the result is cached to avoid repeated 
reflection calls.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    public static AutoTimestampType getAutoTimestampType(PersistentProperty<?> 
persistentProperty) {
+        Property mappedForm = persistentProperty.getMapping().getMappedForm();
+
+        // In development mode, always detect fresh to support class reloading
+        if (Environment.isDevelopmentMode()) {
+            return detectAutoTimestampType(persistentProperty);
+        }
+
+        // Return cached value if available
+        if (mappedForm.getAutoTimestampType() != null) {
+            return mappedForm.getAutoTimestampType();
+        }
+
+        // Detect and cache the annotation type
+        AutoTimestampType type = detectAutoTimestampType(persistentProperty);
+        mappedForm.setAutoTimestampType(type);
+        return type;
+    }
+
+    /**
+     * Detects the auto-timestamp annotation type on a property using 
reflection.
+     *
+     * <p>When caching is enabled (production mode), this method is called 
once per property
+     * and the result is cached. When caching is disabled (development mode), 
this method
+     * is called on every access to ensure annotation changes are detected.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    private static AutoTimestampType 
detectAutoTimestampType(PersistentProperty<?> persistentProperty) {
+        try {
+            Field field = ReflectionUtils.findField(
+                persistentProperty.getOwner().getJavaClass(),
+                persistentProperty.getName()
+            );
+
+            if (field != null) {
+                for (java.lang.annotation.Annotation annotation : 
field.getDeclaredAnnotations()) {
+                    String annotationName = 
annotation.annotationType().getName();
+
+                    if (CREATED_DATE_ANNOTATION.equals(annotationName) ||
+                        CREATED_DATE_SPRING_ANNOTATION.equals(annotationName)) 
{
+                        return AutoTimestampType.CREATED;
+                    } else if 
(LAST_MODIFIED_DATE_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_DATE_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED;
+                    } else if (CREATED_BY_ANNOTATION.equals(annotationName) ||
+                               
CREATED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.CREATED_BY;
+                    } else if 
(LAST_MODIFIED_BY_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED_BY;
+                    } else if 
(AUTO_TIMESTAMP_ANNOTATION.equals(annotationName)) {
+                        // For @AutoTimestamp, check the EventType value
+                        try {
+                            Object eventTypeValue = annotation.annotationType()
+                                .getMethod("value")
+                                .invoke(annotation);
+
+                            if (eventTypeValue != null) {
+                                String eventTypeName = 
eventTypeValue.toString();
+                                if (eventTypeName.equals("UPDATED")) {
+                                    return AutoTimestampType.UPDATED;
+                                } else {
+                                    return AutoTimestampType.CREATED;
+                                }
+                            }
+                        } catch (Exception e) {
+                            // If we can't read the value, default to CREATED
+                            return AutoTimestampType.CREATED;
+                        }
+                    }
+                }
+            }
+        } catch (Exception ignored) {
+            // If reflection fails, return NONE
+        }
+
+        return AutoTimestampType.NONE;
+    }
+
+    /**
+     * Checks if a property has any auto-timestamp or auditing annotation.
+     *
+     * @param persistentProperty The persistent property to check
+     * @return true if the property has any supported annotation 
(@CreatedDate, @LastModifiedDate,
+     *         @CreatedBy, @LastModifiedBy, or @AutoTimestamp) from either 
GORM or Spring Data

Review Comment:
   ```suggestion
        * @return true if the property has any supported annotation ({@code 
@CreatedDate}, {@code @LastModifiedDate},
        *         {@code @CreatedBy}, {@code @LastModifiedBy}, or {@code 
@AutoTimestamp}) from either GORM or Spring Data
   ```



##########
grails-fields/src/main/groovy/org/grails/scaffolding/model/DomainModelServiceImpl.groovy:
##########


Review Comment:
   This class is no longer implementing `getInputProperties(PersistentEntity, 
List)` from the `DomainModelService` interface.



##########
grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy:
##########
@@ -618,7 +618,8 @@ class FormFieldsTagLib {
             }
         } else {
             properties = list ? 
domainModelService.getListOutputProperties(domainClass) : 
domainModelService.getInputProperties(domainClass,
-                    exclusionType == ExclusionType.Input ? exclusionsInput : 
exclusionsDisplay)
+                    exclusionType == ExclusionType.Input ? exclusionsInput : 
exclusionsDisplay,
+                    exclusionType == ExclusionType.Input)

Review Comment:
   `getInputProperties(PersistenProperty, List<String>, boolean)` is not part 
of the `DomainModelService` interface.



##########
grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy:
##########
@@ -226,17 +226,47 @@ class GrailsGradlePlugin extends GroovyPlugin {
 
     protected Closure<String> getGroovyCompilerScript(GroovyCompile compile, 
Project project) {
         GrailsExtension grails = project.extensions.findByType(GrailsExtension)
-        if (!grails.importJavaTime) {
+
+        List<String> starImports = []
+
+        // Add java.time if enabled
+        if (grails.importJavaTime) {
+            starImports.add('java.time')
+        }
+
+        // Add Grails annotation packages if enabled and dependencies are 
present
+        if (grails.importGrailsAnnotations) {
+            // Check for grails-datamapping-core (grails.gorm.annotation.*)
+            def datamappingCoreDep = 
project.configurations.getByName('compileClasspath').dependencies.find { 
Dependency d ->
+                d.group == 'org.apache.grails.data' && d.name == 
'grails-datamapping-core'
+            }
+            if (datamappingCoreDep) {
+                starImports.add('grails.gorm.annotation')
+            }
+
+            // Check for grails-scaffolding 
(grails.plugin.scaffolding.annotation.*)
+            def scaffoldingDep = 
project.configurations.getByName('compileClasspath').dependencies.find { 
Dependency d ->
+                d.group == 'org.apache.grails' && d.name == 
'grails-scaffolding'
+            }
+            if (scaffoldingDep) {
+                starImports.add('grails.plugin.scaffolding.annotation')
+            }

Review Comment:
   > what are your thoughts about getting rid of @AutoTimestamp?
   
   @codeconsole Seems reasonable :+1: 



##########
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/AutoTimestampUtils.java:
##########
@@ -0,0 +1,202 @@
+/*
+ *  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 org.grails.datastore.mapping.model;
+
+import java.lang.reflect.Field;
+
+import org.springframework.util.ReflectionUtils;
+
+import grails.util.Environment;
+import org.grails.datastore.mapping.config.Property;
+import org.grails.datastore.mapping.config.Property.AutoTimestampType;
+
+/**
+ * Utility class for detecting and caching auto-timestamp and auditing 
annotations on domain properties.
+ * This avoids repeated reflection calls by storing the annotation type in the 
Property metadata.
+ *
+ * <p>Supports the following annotations (both GORM and Spring Data 
variants):</p>
+ * <ul>
+ *   <li>@CreatedDate / @grails.gorm.annotation.CreatedDate - automatically 
set on insert</li>
+ *   <li>@LastModifiedDate / @grails.gorm.annotation.LastModifiedDate - 
automatically set on insert and update</li>
+ *   <li>@CreatedBy / @grails.gorm.annotation.CreatedBy - automatically 
populated with current auditor on insert</li>
+ *   <li>@LastModifiedBy / @grails.gorm.annotation.LastModifiedBy - 
automatically populated with current auditor on insert and update</li>
+ *   <li>@AutoTimestamp - GORM-specific annotation for backwards 
compatibility</li>
+ * </ul>
+ *
+ * <p>Caching is automatically disabled in development mode ({@link 
Environment#isDevelopmentMode()})
+ * to ensure annotation changes are picked up during class reloading.</p>
+ *
+ * @author Scott Murphy Heiberg
+ * @since 7.0
+ */
+public class AutoTimestampUtils {
+
+    private static final String CREATED_DATE_ANNOTATION = 
"grails.gorm.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_ANNOTATION = 
"grails.gorm.annotation.LastModifiedDate";
+    private static final String AUTO_TIMESTAMP_ANNOTATION = 
"grails.gorm.annotation.AutoTimestamp";
+    private static final String CREATED_BY_ANNOTATION = 
"grails.gorm.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_ANNOTATION = 
"grails.gorm.annotation.LastModifiedBy";
+
+    private static final String CREATED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedDate";
+    private static final String CREATED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedBy";
+
+    /**
+     * Gets the auto-timestamp type for a persistent property, using cached 
metadata when not in development mode.
+     *
+     * <p>In development mode, this method will always perform reflection to 
detect the current
+     * annotation state, ensuring that annotation changes during class 
reloading are immediately
+     * recognized. In production, the result is cached to avoid repeated 
reflection calls.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    public static AutoTimestampType getAutoTimestampType(PersistentProperty<?> 
persistentProperty) {
+        Property mappedForm = persistentProperty.getMapping().getMappedForm();
+
+        // In development mode, always detect fresh to support class reloading
+        if (Environment.isDevelopmentMode()) {
+            return detectAutoTimestampType(persistentProperty);
+        }
+
+        // Return cached value if available
+        if (mappedForm.getAutoTimestampType() != null) {
+            return mappedForm.getAutoTimestampType();
+        }
+
+        // Detect and cache the annotation type
+        AutoTimestampType type = detectAutoTimestampType(persistentProperty);
+        mappedForm.setAutoTimestampType(type);
+        return type;
+    }
+
+    /**
+     * Detects the auto-timestamp annotation type on a property using 
reflection.
+     *
+     * <p>When caching is enabled (production mode), this method is called 
once per property
+     * and the result is cached. When caching is disabled (development mode), 
this method
+     * is called on every access to ensure annotation changes are detected.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    private static AutoTimestampType 
detectAutoTimestampType(PersistentProperty<?> persistentProperty) {
+        try {
+            Field field = ReflectionUtils.findField(
+                persistentProperty.getOwner().getJavaClass(),
+                persistentProperty.getName()
+            );
+
+            if (field != null) {
+                for (java.lang.annotation.Annotation annotation : 
field.getDeclaredAnnotations()) {
+                    String annotationName = 
annotation.annotationType().getName();
+
+                    if (CREATED_DATE_ANNOTATION.equals(annotationName) ||
+                        CREATED_DATE_SPRING_ANNOTATION.equals(annotationName)) 
{
+                        return AutoTimestampType.CREATED;
+                    } else if 
(LAST_MODIFIED_DATE_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_DATE_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED;
+                    } else if (CREATED_BY_ANNOTATION.equals(annotationName) ||
+                               
CREATED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.CREATED_BY;
+                    } else if 
(LAST_MODIFIED_BY_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED_BY;
+                    } else if 
(AUTO_TIMESTAMP_ANNOTATION.equals(annotationName)) {
+                        // For @AutoTimestamp, check the EventType value
+                        try {
+                            Object eventTypeValue = annotation.annotationType()
+                                .getMethod("value")
+                                .invoke(annotation);
+
+                            if (eventTypeValue != null) {
+                                String eventTypeName = 
eventTypeValue.toString();
+                                if (eventTypeName.equals("UPDATED")) {
+                                    return AutoTimestampType.UPDATED;
+                                } else {
+                                    return AutoTimestampType.CREATED;
+                                }
+                            }
+                        } catch (Exception e) {
+                            // If we can't read the value, default to CREATED
+                            return AutoTimestampType.CREATED;
+                        }
+                    }
+                }
+            }
+        } catch (Exception ignored) {
+            // If reflection fails, return NONE
+        }
+
+        return AutoTimestampType.NONE;
+    }
+
+    /**
+     * Checks if a property has any auto-timestamp or auditing annotation.
+     *
+     * @param persistentProperty The persistent property to check
+     * @return true if the property has any supported annotation 
(@CreatedDate, @LastModifiedDate,
+     *         @CreatedBy, @LastModifiedBy, or @AutoTimestamp) from either 
GORM or Spring Data
+     */
+    public static boolean hasAutoTimestampAnnotation(PersistentProperty<?> 
persistentProperty) {
+        return persistentProperty != null && 
getAutoTimestampType(persistentProperty) != AutoTimestampType.NONE;
+    }
+
+    /**
+     * Checks if a property has a @CreatedDate annotation (GORM or Spring 
Data) or @AutoTimestamp(CREATED).

Review Comment:
   ```suggestion
        * Checks if a property has a {@code @CreatedDate} annotation (GORM or 
Spring Data) or {@code @AutoTimestamp(CREATED)}.
   ```



##########
grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/timestamp/AuditorAware.java:
##########
@@ -0,0 +1,58 @@
+/*
+ *  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 org.grails.datastore.gorm.timestamp;
+
+import java.util.Optional;
+
+/**
+ * Interface for components that are aware of the application's current 
auditor.
+ * This will be used to populate @CreatedBy and @LastModifiedBy annotated 
fields
+ * in domain objects.
+ *
+ * <p>Implementations should be registered as Spring beans. The type parameter
+ * should match the type of the auditor field in your domain classes (e.g., 
User,
+ * Long, String, etc.).</p>
+ *
+ * <p>Example implementation:</p>
+ * <pre>
+ * &#64;Component
+ * public class SpringSecurityAuditorAware implements 
AuditorAware&lt;String&gt; {
+ *     &#64;Override
+ *     public Optional&lt;String&gt; getCurrentAuditor() {
+ *         return Optional.ofNullable(SecurityContextHolder.getContext())
+ *                 .map(SecurityContext::getAuthentication)
+ *                 .filter(Authentication::isAuthenticated)
+ *                 .map(Authentication::getName);
+ *     }
+ * }
+ * </pre>
+ *
+ * @param <T> the type of the auditor (e.g., User, Long, String)
+ * @author Scott Murphy Heiberg
+ * @since 7.0

Review Comment:
   ```suggestion
    * @since 7.1
   ```



##########
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/AutoTimestampUtils.java:
##########
@@ -0,0 +1,202 @@
+/*
+ *  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 org.grails.datastore.mapping.model;
+
+import java.lang.reflect.Field;
+
+import org.springframework.util.ReflectionUtils;
+
+import grails.util.Environment;
+import org.grails.datastore.mapping.config.Property;
+import org.grails.datastore.mapping.config.Property.AutoTimestampType;
+
+/**
+ * Utility class for detecting and caching auto-timestamp and auditing 
annotations on domain properties.
+ * This avoids repeated reflection calls by storing the annotation type in the 
Property metadata.
+ *
+ * <p>Supports the following annotations (both GORM and Spring Data 
variants):</p>
+ * <ul>
+ *   <li>@CreatedDate / @grails.gorm.annotation.CreatedDate - automatically 
set on insert</li>
+ *   <li>@LastModifiedDate / @grails.gorm.annotation.LastModifiedDate - 
automatically set on insert and update</li>
+ *   <li>@CreatedBy / @grails.gorm.annotation.CreatedBy - automatically 
populated with current auditor on insert</li>
+ *   <li>@LastModifiedBy / @grails.gorm.annotation.LastModifiedBy - 
automatically populated with current auditor on insert and update</li>
+ *   <li>@AutoTimestamp - GORM-specific annotation for backwards 
compatibility</li>
+ * </ul>
+ *
+ * <p>Caching is automatically disabled in development mode ({@link 
Environment#isDevelopmentMode()})
+ * to ensure annotation changes are picked up during class reloading.</p>
+ *
+ * @author Scott Murphy Heiberg
+ * @since 7.0

Review Comment:
   ```suggestion
    * @since 7.1
   ```



##########
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/AutoTimestampUtils.java:
##########
@@ -0,0 +1,202 @@
+/*
+ *  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 org.grails.datastore.mapping.model;
+
+import java.lang.reflect.Field;
+
+import org.springframework.util.ReflectionUtils;
+
+import grails.util.Environment;
+import org.grails.datastore.mapping.config.Property;
+import org.grails.datastore.mapping.config.Property.AutoTimestampType;
+
+/**
+ * Utility class for detecting and caching auto-timestamp and auditing 
annotations on domain properties.
+ * This avoids repeated reflection calls by storing the annotation type in the 
Property metadata.
+ *
+ * <p>Supports the following annotations (both GORM and Spring Data 
variants):</p>
+ * <ul>
+ *   <li>@CreatedDate / @grails.gorm.annotation.CreatedDate - automatically 
set on insert</li>
+ *   <li>@LastModifiedDate / @grails.gorm.annotation.LastModifiedDate - 
automatically set on insert and update</li>
+ *   <li>@CreatedBy / @grails.gorm.annotation.CreatedBy - automatically 
populated with current auditor on insert</li>
+ *   <li>@LastModifiedBy / @grails.gorm.annotation.LastModifiedBy - 
automatically populated with current auditor on insert and update</li>
+ *   <li>@AutoTimestamp - GORM-specific annotation for backwards 
compatibility</li>
+ * </ul>
+ *
+ * <p>Caching is automatically disabled in development mode ({@link 
Environment#isDevelopmentMode()})
+ * to ensure annotation changes are picked up during class reloading.</p>
+ *
+ * @author Scott Murphy Heiberg
+ * @since 7.0
+ */
+public class AutoTimestampUtils {
+
+    private static final String CREATED_DATE_ANNOTATION = 
"grails.gorm.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_ANNOTATION = 
"grails.gorm.annotation.LastModifiedDate";
+    private static final String AUTO_TIMESTAMP_ANNOTATION = 
"grails.gorm.annotation.AutoTimestamp";
+    private static final String CREATED_BY_ANNOTATION = 
"grails.gorm.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_ANNOTATION = 
"grails.gorm.annotation.LastModifiedBy";
+
+    private static final String CREATED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedDate";
+    private static final String CREATED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedBy";
+
+    /**
+     * Gets the auto-timestamp type for a persistent property, using cached 
metadata when not in development mode.
+     *
+     * <p>In development mode, this method will always perform reflection to 
detect the current
+     * annotation state, ensuring that annotation changes during class 
reloading are immediately
+     * recognized. In production, the result is cached to avoid repeated 
reflection calls.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    public static AutoTimestampType getAutoTimestampType(PersistentProperty<?> 
persistentProperty) {
+        Property mappedForm = persistentProperty.getMapping().getMappedForm();
+
+        // In development mode, always detect fresh to support class reloading
+        if (Environment.isDevelopmentMode()) {
+            return detectAutoTimestampType(persistentProperty);
+        }
+
+        // Return cached value if available
+        if (mappedForm.getAutoTimestampType() != null) {
+            return mappedForm.getAutoTimestampType();
+        }
+
+        // Detect and cache the annotation type
+        AutoTimestampType type = detectAutoTimestampType(persistentProperty);
+        mappedForm.setAutoTimestampType(type);
+        return type;
+    }
+
+    /**
+     * Detects the auto-timestamp annotation type on a property using 
reflection.
+     *
+     * <p>When caching is enabled (production mode), this method is called 
once per property
+     * and the result is cached. When caching is disabled (development mode), 
this method
+     * is called on every access to ensure annotation changes are detected.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    private static AutoTimestampType 
detectAutoTimestampType(PersistentProperty<?> persistentProperty) {
+        try {
+            Field field = ReflectionUtils.findField(
+                persistentProperty.getOwner().getJavaClass(),
+                persistentProperty.getName()
+            );
+
+            if (field != null) {
+                for (java.lang.annotation.Annotation annotation : 
field.getDeclaredAnnotations()) {
+                    String annotationName = 
annotation.annotationType().getName();
+
+                    if (CREATED_DATE_ANNOTATION.equals(annotationName) ||
+                        CREATED_DATE_SPRING_ANNOTATION.equals(annotationName)) 
{
+                        return AutoTimestampType.CREATED;
+                    } else if 
(LAST_MODIFIED_DATE_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_DATE_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED;
+                    } else if (CREATED_BY_ANNOTATION.equals(annotationName) ||
+                               
CREATED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.CREATED_BY;
+                    } else if 
(LAST_MODIFIED_BY_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED_BY;
+                    } else if 
(AUTO_TIMESTAMP_ANNOTATION.equals(annotationName)) {
+                        // For @AutoTimestamp, check the EventType value
+                        try {
+                            Object eventTypeValue = annotation.annotationType()
+                                .getMethod("value")
+                                .invoke(annotation);
+
+                            if (eventTypeValue != null) {
+                                String eventTypeName = 
eventTypeValue.toString();
+                                if (eventTypeName.equals("UPDATED")) {
+                                    return AutoTimestampType.UPDATED;
+                                } else {
+                                    return AutoTimestampType.CREATED;
+                                }
+                            }
+                        } catch (Exception e) {
+                            // If we can't read the value, default to CREATED
+                            return AutoTimestampType.CREATED;
+                        }
+                    }
+                }
+            }
+        } catch (Exception ignored) {
+            // If reflection fails, return NONE
+        }
+
+        return AutoTimestampType.NONE;
+    }
+
+    /**
+     * Checks if a property has any auto-timestamp or auditing annotation.
+     *
+     * @param persistentProperty The persistent property to check
+     * @return true if the property has any supported annotation 
(@CreatedDate, @LastModifiedDate,
+     *         @CreatedBy, @LastModifiedBy, or @AutoTimestamp) from either 
GORM or Spring Data
+     */
+    public static boolean hasAutoTimestampAnnotation(PersistentProperty<?> 
persistentProperty) {
+        return persistentProperty != null && 
getAutoTimestampType(persistentProperty) != AutoTimestampType.NONE;
+    }
+
+    /**
+     * Checks if a property has a @CreatedDate annotation (GORM or Spring 
Data) or @AutoTimestamp(CREATED).
+     *
+     * @param persistentProperty The persistent property to check
+     * @return true if the property represents a creation timestamp
+     */
+    public static boolean isCreatedTimestamp(PersistentProperty<?> 
persistentProperty) {
+        return getAutoTimestampType(persistentProperty) == 
AutoTimestampType.CREATED;
+    }
+
+    /**
+     * Checks if a property has a @LastModifiedDate annotation (GORM or Spring 
Data) or @AutoTimestamp(UPDATED).
+     *
+     * @param persistentProperty The persistent property to check
+     * @return true if the property represents an update timestamp
+     */
+    public static boolean isUpdatedTimestamp(PersistentProperty<?> 
persistentProperty) {
+        return getAutoTimestampType(persistentProperty) == 
AutoTimestampType.UPDATED;
+    }
+
+    /**
+     * Checks if a property has a @CreatedBy annotation (GORM or Spring Data).

Review Comment:
   ```suggestion
        * Checks if a property has a {@code @CreatedBy} annotation (GORM or 
Spring Data).
   ```



##########
grails-datamapping-core/src/main/groovy/grails/gorm/annotation/LastModifiedBy.java:
##########
@@ -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

Review Comment:
   ```suggestion
    * @since 7.1
   ```



##########
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/AutoTimestampUtils.java:
##########
@@ -0,0 +1,202 @@
+/*
+ *  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 org.grails.datastore.mapping.model;
+
+import java.lang.reflect.Field;
+
+import org.springframework.util.ReflectionUtils;
+
+import grails.util.Environment;
+import org.grails.datastore.mapping.config.Property;
+import org.grails.datastore.mapping.config.Property.AutoTimestampType;
+
+/**
+ * Utility class for detecting and caching auto-timestamp and auditing 
annotations on domain properties.
+ * This avoids repeated reflection calls by storing the annotation type in the 
Property metadata.
+ *
+ * <p>Supports the following annotations (both GORM and Spring Data 
variants):</p>
+ * <ul>
+ *   <li>@CreatedDate / @grails.gorm.annotation.CreatedDate - automatically 
set on insert</li>
+ *   <li>@LastModifiedDate / @grails.gorm.annotation.LastModifiedDate - 
automatically set on insert and update</li>
+ *   <li>@CreatedBy / @grails.gorm.annotation.CreatedBy - automatically 
populated with current auditor on insert</li>
+ *   <li>@LastModifiedBy / @grails.gorm.annotation.LastModifiedBy - 
automatically populated with current auditor on insert and update</li>
+ *   <li>@AutoTimestamp - GORM-specific annotation for backwards 
compatibility</li>
+ * </ul>
+ *
+ * <p>Caching is automatically disabled in development mode ({@link 
Environment#isDevelopmentMode()})
+ * to ensure annotation changes are picked up during class reloading.</p>
+ *
+ * @author Scott Murphy Heiberg
+ * @since 7.0
+ */
+public class AutoTimestampUtils {
+
+    private static final String CREATED_DATE_ANNOTATION = 
"grails.gorm.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_ANNOTATION = 
"grails.gorm.annotation.LastModifiedDate";
+    private static final String AUTO_TIMESTAMP_ANNOTATION = 
"grails.gorm.annotation.AutoTimestamp";
+    private static final String CREATED_BY_ANNOTATION = 
"grails.gorm.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_ANNOTATION = 
"grails.gorm.annotation.LastModifiedBy";
+
+    private static final String CREATED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedDate";
+    private static final String CREATED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedBy";
+
+    /**
+     * Gets the auto-timestamp type for a persistent property, using cached 
metadata when not in development mode.
+     *
+     * <p>In development mode, this method will always perform reflection to 
detect the current
+     * annotation state, ensuring that annotation changes during class 
reloading are immediately
+     * recognized. In production, the result is cached to avoid repeated 
reflection calls.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    public static AutoTimestampType getAutoTimestampType(PersistentProperty<?> 
persistentProperty) {
+        Property mappedForm = persistentProperty.getMapping().getMappedForm();
+
+        // In development mode, always detect fresh to support class reloading
+        if (Environment.isDevelopmentMode()) {
+            return detectAutoTimestampType(persistentProperty);
+        }
+
+        // Return cached value if available
+        if (mappedForm.getAutoTimestampType() != null) {
+            return mappedForm.getAutoTimestampType();
+        }
+
+        // Detect and cache the annotation type
+        AutoTimestampType type = detectAutoTimestampType(persistentProperty);
+        mappedForm.setAutoTimestampType(type);
+        return type;
+    }
+
+    /**
+     * Detects the auto-timestamp annotation type on a property using 
reflection.
+     *
+     * <p>When caching is enabled (production mode), this method is called 
once per property
+     * and the result is cached. When caching is disabled (development mode), 
this method
+     * is called on every access to ensure annotation changes are detected.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    private static AutoTimestampType 
detectAutoTimestampType(PersistentProperty<?> persistentProperty) {
+        try {
+            Field field = ReflectionUtils.findField(
+                persistentProperty.getOwner().getJavaClass(),
+                persistentProperty.getName()
+            );
+
+            if (field != null) {
+                for (java.lang.annotation.Annotation annotation : 
field.getDeclaredAnnotations()) {
+                    String annotationName = 
annotation.annotationType().getName();
+
+                    if (CREATED_DATE_ANNOTATION.equals(annotationName) ||
+                        CREATED_DATE_SPRING_ANNOTATION.equals(annotationName)) 
{
+                        return AutoTimestampType.CREATED;
+                    } else if 
(LAST_MODIFIED_DATE_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_DATE_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED;
+                    } else if (CREATED_BY_ANNOTATION.equals(annotationName) ||
+                               
CREATED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.CREATED_BY;
+                    } else if 
(LAST_MODIFIED_BY_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED_BY;
+                    } else if 
(AUTO_TIMESTAMP_ANNOTATION.equals(annotationName)) {
+                        // For @AutoTimestamp, check the EventType value
+                        try {
+                            Object eventTypeValue = annotation.annotationType()
+                                .getMethod("value")
+                                .invoke(annotation);
+
+                            if (eventTypeValue != null) {
+                                String eventTypeName = 
eventTypeValue.toString();
+                                if (eventTypeName.equals("UPDATED")) {
+                                    return AutoTimestampType.UPDATED;
+                                } else {
+                                    return AutoTimestampType.CREATED;
+                                }
+                            }
+                        } catch (Exception e) {
+                            // If we can't read the value, default to CREATED
+                            return AutoTimestampType.CREATED;
+                        }
+                    }
+                }

Review Comment:
   ```suggestion
                       switch (annotation.annotationType().getName()) {
                           case CREATED_DATE_ANNOTATION, 
CREATED_DATE_SPRING_ANNOTATION -> {
                               return AutoTimestampType.CREATED;
                           }
                           case LAST_MODIFIED_DATE_ANNOTATION, 
LAST_MODIFIED_DATE_SPRING_ANNOTATION -> {
                               return AutoTimestampType.UPDATED;
                           }
                           case CREATED_BY_ANNOTATION, 
CREATED_BY_SPRING_ANNOTATION -> {
                               return AutoTimestampType.CREATED_BY;
                           }
                           case LAST_MODIFIED_BY_ANNOTATION, 
LAST_MODIFIED_BY_SPRING_ANNOTATION -> {
                               return AutoTimestampType.UPDATED_BY;
                           }
                           case AUTO_TIMESTAMP_ANNOTATION -> {
                               // For @AutoTimestamp, check the EventType value
                               try {
                                   Object eventTypeValue = 
annotation.annotationType()
                                           .getMethod("value")
                                           .invoke(annotation);
   
                                   if (eventTypeValue != null) {
                                       String eventTypeName = 
eventTypeValue.toString();
                                       if (eventTypeName.equals("UPDATED")) {
                                           return AutoTimestampType.UPDATED;
                                       } else {
                                           return AutoTimestampType.CREATED;
                                       }
                                   }
                               } catch (Exception e) {
                                   // If we can't read the value, default to 
CREATED
                                   return AutoTimestampType.CREATED;
                               }
                           }
                       }
   
   ```



##########
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/AutoTimestampUtils.java:
##########
@@ -0,0 +1,202 @@
+/*
+ *  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 org.grails.datastore.mapping.model;
+
+import java.lang.reflect.Field;
+
+import org.springframework.util.ReflectionUtils;
+
+import grails.util.Environment;
+import org.grails.datastore.mapping.config.Property;
+import org.grails.datastore.mapping.config.Property.AutoTimestampType;
+
+/**
+ * Utility class for detecting and caching auto-timestamp and auditing 
annotations on domain properties.
+ * This avoids repeated reflection calls by storing the annotation type in the 
Property metadata.
+ *
+ * <p>Supports the following annotations (both GORM and Spring Data 
variants):</p>
+ * <ul>
+ *   <li>@CreatedDate / @grails.gorm.annotation.CreatedDate - automatically 
set on insert</li>
+ *   <li>@LastModifiedDate / @grails.gorm.annotation.LastModifiedDate - 
automatically set on insert and update</li>
+ *   <li>@CreatedBy / @grails.gorm.annotation.CreatedBy - automatically 
populated with current auditor on insert</li>
+ *   <li>@LastModifiedBy / @grails.gorm.annotation.LastModifiedBy - 
automatically populated with current auditor on insert and update</li>
+ *   <li>@AutoTimestamp - GORM-specific annotation for backwards 
compatibility</li>
+ * </ul>
+ *
+ * <p>Caching is automatically disabled in development mode ({@link 
Environment#isDevelopmentMode()})
+ * to ensure annotation changes are picked up during class reloading.</p>
+ *
+ * @author Scott Murphy Heiberg
+ * @since 7.0
+ */
+public class AutoTimestampUtils {
+
+    private static final String CREATED_DATE_ANNOTATION = 
"grails.gorm.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_ANNOTATION = 
"grails.gorm.annotation.LastModifiedDate";
+    private static final String AUTO_TIMESTAMP_ANNOTATION = 
"grails.gorm.annotation.AutoTimestamp";
+    private static final String CREATED_BY_ANNOTATION = 
"grails.gorm.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_ANNOTATION = 
"grails.gorm.annotation.LastModifiedBy";
+
+    private static final String CREATED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedDate";
+    private static final String CREATED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedBy";
+
+    /**
+     * Gets the auto-timestamp type for a persistent property, using cached 
metadata when not in development mode.
+     *
+     * <p>In development mode, this method will always perform reflection to 
detect the current
+     * annotation state, ensuring that annotation changes during class 
reloading are immediately
+     * recognized. In production, the result is cached to avoid repeated 
reflection calls.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    public static AutoTimestampType getAutoTimestampType(PersistentProperty<?> 
persistentProperty) {
+        Property mappedForm = persistentProperty.getMapping().getMappedForm();
+
+        // In development mode, always detect fresh to support class reloading
+        if (Environment.isDevelopmentMode()) {
+            return detectAutoTimestampType(persistentProperty);
+        }
+
+        // Return cached value if available
+        if (mappedForm.getAutoTimestampType() != null) {
+            return mappedForm.getAutoTimestampType();
+        }
+
+        // Detect and cache the annotation type
+        AutoTimestampType type = detectAutoTimestampType(persistentProperty);
+        mappedForm.setAutoTimestampType(type);
+        return type;
+    }
+
+    /**
+     * Detects the auto-timestamp annotation type on a property using 
reflection.
+     *
+     * <p>When caching is enabled (production mode), this method is called 
once per property
+     * and the result is cached. When caching is disabled (development mode), 
this method
+     * is called on every access to ensure annotation changes are detected.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    private static AutoTimestampType 
detectAutoTimestampType(PersistentProperty<?> persistentProperty) {
+        try {
+            Field field = ReflectionUtils.findField(
+                persistentProperty.getOwner().getJavaClass(),
+                persistentProperty.getName()
+            );
+
+            if (field != null) {
+                for (java.lang.annotation.Annotation annotation : 
field.getDeclaredAnnotations()) {
+                    String annotationName = 
annotation.annotationType().getName();
+
+                    if (CREATED_DATE_ANNOTATION.equals(annotationName) ||
+                        CREATED_DATE_SPRING_ANNOTATION.equals(annotationName)) 
{
+                        return AutoTimestampType.CREATED;
+                    } else if 
(LAST_MODIFIED_DATE_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_DATE_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED;
+                    } else if (CREATED_BY_ANNOTATION.equals(annotationName) ||
+                               
CREATED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.CREATED_BY;
+                    } else if 
(LAST_MODIFIED_BY_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED_BY;
+                    } else if 
(AUTO_TIMESTAMP_ANNOTATION.equals(annotationName)) {
+                        // For @AutoTimestamp, check the EventType value
+                        try {
+                            Object eventTypeValue = annotation.annotationType()
+                                .getMethod("value")
+                                .invoke(annotation);
+
+                            if (eventTypeValue != null) {
+                                String eventTypeName = 
eventTypeValue.toString();
+                                if (eventTypeName.equals("UPDATED")) {
+                                    return AutoTimestampType.UPDATED;
+                                } else {
+                                    return AutoTimestampType.CREATED;
+                                }
+                            }
+                        } catch (Exception e) {
+                            // If we can't read the value, default to CREATED
+                            return AutoTimestampType.CREATED;
+                        }
+                    }
+                }
+            }
+        } catch (Exception ignored) {
+            // If reflection fails, return NONE
+        }
+
+        return AutoTimestampType.NONE;
+    }
+
+    /**
+     * Checks if a property has any auto-timestamp or auditing annotation.
+     *
+     * @param persistentProperty The persistent property to check
+     * @return true if the property has any supported annotation 
(@CreatedDate, @LastModifiedDate,
+     *         @CreatedBy, @LastModifiedBy, or @AutoTimestamp) from either 
GORM or Spring Data
+     */
+    public static boolean hasAutoTimestampAnnotation(PersistentProperty<?> 
persistentProperty) {
+        return persistentProperty != null && 
getAutoTimestampType(persistentProperty) != AutoTimestampType.NONE;
+    }
+
+    /**
+     * Checks if a property has a @CreatedDate annotation (GORM or Spring 
Data) or @AutoTimestamp(CREATED).
+     *
+     * @param persistentProperty The persistent property to check
+     * @return true if the property represents a creation timestamp
+     */
+    public static boolean isCreatedTimestamp(PersistentProperty<?> 
persistentProperty) {
+        return getAutoTimestampType(persistentProperty) == 
AutoTimestampType.CREATED;
+    }
+
+    /**
+     * Checks if a property has a @LastModifiedDate annotation (GORM or Spring 
Data) or @AutoTimestamp(UPDATED).
+     *
+     * @param persistentProperty The persistent property to check
+     * @return true if the property represents an update timestamp
+     */
+    public static boolean isUpdatedTimestamp(PersistentProperty<?> 
persistentProperty) {
+        return getAutoTimestampType(persistentProperty) == 
AutoTimestampType.UPDATED;
+    }
+
+    /**
+     * Checks if a property has a @CreatedBy annotation (GORM or Spring Data).
+     *
+     * @param persistentProperty The persistent property to check
+     * @return true if the property represents a creation auditor
+     */
+    public static boolean isCreatedBy(PersistentProperty<?> 
persistentProperty) {
+        return getAutoTimestampType(persistentProperty) == 
AutoTimestampType.CREATED_BY;
+    }
+
+    /**
+     * Checks if a property has a @LastModifiedBy annotation (GORM or Spring 
Data).

Review Comment:
   ```suggestion
        * Checks if a property has a {@code @LastModifiedBy} annotation (GORM 
or Spring Data).
   ```



##########
grails-datamapping-validation/src/main/groovy/org/grails/datastore/gorm/validation/constraints/eval/DefaultConstraintEvaluator.java:
##########
@@ -305,6 +306,11 @@ protected boolean 
isConstrainableProperty(PersistentProperty persistentProperty,
             return NameUtils.isNotConfigurational(propertyName);
         }
         else {
+            // Check if property has @CreatedDate or @LastModifiedDate 
annotations

Review Comment:
   The condition below returns true for all "timestamp" annotations?



##########
grails-datamapping-validation/src/main/groovy/org/grails/datastore/gorm/validation/constraints/eval/DefaultConstraintEvaluator.java:
##########
@@ -305,6 +306,11 @@ protected boolean 
isConstrainableProperty(PersistentProperty persistentProperty,
             return NameUtils.isNotConfigurational(propertyName);
         }
         else {
+            // Check if property has @CreatedDate or @LastModifiedDate 
annotations
+            if 
(AutoTimestampUtils.hasAutoTimestampAnnotation(persistentProperty)) {

Review Comment:
   This is true, as auditor can be different types, and constraints influence 
the database schema.



##########
grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/AutoTimestampUtils.java:
##########
@@ -0,0 +1,202 @@
+/*
+ *  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 org.grails.datastore.mapping.model;
+
+import java.lang.reflect.Field;
+
+import org.springframework.util.ReflectionUtils;
+
+import grails.util.Environment;
+import org.grails.datastore.mapping.config.Property;
+import org.grails.datastore.mapping.config.Property.AutoTimestampType;
+
+/**
+ * Utility class for detecting and caching auto-timestamp and auditing 
annotations on domain properties.
+ * This avoids repeated reflection calls by storing the annotation type in the 
Property metadata.
+ *
+ * <p>Supports the following annotations (both GORM and Spring Data 
variants):</p>
+ * <ul>
+ *   <li>@CreatedDate / @grails.gorm.annotation.CreatedDate - automatically 
set on insert</li>
+ *   <li>@LastModifiedDate / @grails.gorm.annotation.LastModifiedDate - 
automatically set on insert and update</li>
+ *   <li>@CreatedBy / @grails.gorm.annotation.CreatedBy - automatically 
populated with current auditor on insert</li>
+ *   <li>@LastModifiedBy / @grails.gorm.annotation.LastModifiedBy - 
automatically populated with current auditor on insert and update</li>
+ *   <li>@AutoTimestamp - GORM-specific annotation for backwards 
compatibility</li>
+ * </ul>
+ *
+ * <p>Caching is automatically disabled in development mode ({@link 
Environment#isDevelopmentMode()})
+ * to ensure annotation changes are picked up during class reloading.</p>
+ *
+ * @author Scott Murphy Heiberg
+ * @since 7.0
+ */
+public class AutoTimestampUtils {
+
+    private static final String CREATED_DATE_ANNOTATION = 
"grails.gorm.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_ANNOTATION = 
"grails.gorm.annotation.LastModifiedDate";
+    private static final String AUTO_TIMESTAMP_ANNOTATION = 
"grails.gorm.annotation.AutoTimestamp";
+    private static final String CREATED_BY_ANNOTATION = 
"grails.gorm.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_ANNOTATION = 
"grails.gorm.annotation.LastModifiedBy";
+
+    private static final String CREATED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedDate";
+    private static final String LAST_MODIFIED_DATE_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedDate";
+    private static final String CREATED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.CreatedBy";
+    private static final String LAST_MODIFIED_BY_SPRING_ANNOTATION = 
"org.springframework.data.annotation.LastModifiedBy";
+
+    /**
+     * Gets the auto-timestamp type for a persistent property, using cached 
metadata when not in development mode.
+     *
+     * <p>In development mode, this method will always perform reflection to 
detect the current
+     * annotation state, ensuring that annotation changes during class 
reloading are immediately
+     * recognized. In production, the result is cached to avoid repeated 
reflection calls.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    public static AutoTimestampType getAutoTimestampType(PersistentProperty<?> 
persistentProperty) {
+        Property mappedForm = persistentProperty.getMapping().getMappedForm();
+
+        // In development mode, always detect fresh to support class reloading
+        if (Environment.isDevelopmentMode()) {
+            return detectAutoTimestampType(persistentProperty);
+        }
+
+        // Return cached value if available
+        if (mappedForm.getAutoTimestampType() != null) {
+            return mappedForm.getAutoTimestampType();
+        }
+
+        // Detect and cache the annotation type
+        AutoTimestampType type = detectAutoTimestampType(persistentProperty);
+        mappedForm.setAutoTimestampType(type);
+        return type;
+    }
+
+    /**
+     * Detects the auto-timestamp annotation type on a property using 
reflection.
+     *
+     * <p>When caching is enabled (production mode), this method is called 
once per property
+     * and the result is cached. When caching is disabled (development mode), 
this method
+     * is called on every access to ensure annotation changes are detected.</p>
+     *
+     * @param persistentProperty The persistent property to check
+     * @return The auto-timestamp type (CREATED, UPDATED, or NONE)
+     */
+    private static AutoTimestampType 
detectAutoTimestampType(PersistentProperty<?> persistentProperty) {
+        try {
+            Field field = ReflectionUtils.findField(
+                persistentProperty.getOwner().getJavaClass(),
+                persistentProperty.getName()
+            );
+
+            if (field != null) {
+                for (java.lang.annotation.Annotation annotation : 
field.getDeclaredAnnotations()) {
+                    String annotationName = 
annotation.annotationType().getName();
+
+                    if (CREATED_DATE_ANNOTATION.equals(annotationName) ||
+                        CREATED_DATE_SPRING_ANNOTATION.equals(annotationName)) 
{
+                        return AutoTimestampType.CREATED;
+                    } else if 
(LAST_MODIFIED_DATE_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_DATE_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED;
+                    } else if (CREATED_BY_ANNOTATION.equals(annotationName) ||
+                               
CREATED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.CREATED_BY;
+                    } else if 
(LAST_MODIFIED_BY_ANNOTATION.equals(annotationName) ||
+                               
LAST_MODIFIED_BY_SPRING_ANNOTATION.equals(annotationName)) {
+                        return AutoTimestampType.UPDATED_BY;
+                    } else if 
(AUTO_TIMESTAMP_ANNOTATION.equals(annotationName)) {
+                        // For @AutoTimestamp, check the EventType value
+                        try {
+                            Object eventTypeValue = annotation.annotationType()
+                                .getMethod("value")
+                                .invoke(annotation);
+
+                            if (eventTypeValue != null) {
+                                String eventTypeName = 
eventTypeValue.toString();
+                                if (eventTypeName.equals("UPDATED")) {
+                                    return AutoTimestampType.UPDATED;
+                                } else {
+                                    return AutoTimestampType.CREATED;
+                                }
+                            }
+                        } catch (Exception e) {
+                            // If we can't read the value, default to CREATED
+                            return AutoTimestampType.CREATED;
+                        }
+                    }
+                }
+            }
+        } catch (Exception ignored) {
+            // If reflection fails, return NONE
+        }
+
+        return AutoTimestampType.NONE;
+    }
+
+    /**
+     * Checks if a property has any auto-timestamp or auditing annotation.
+     *
+     * @param persistentProperty The persistent property to check
+     * @return true if the property has any supported annotation 
(@CreatedDate, @LastModifiedDate,
+     *         @CreatedBy, @LastModifiedBy, or @AutoTimestamp) from either 
GORM or Spring Data
+     */
+    public static boolean hasAutoTimestampAnnotation(PersistentProperty<?> 
persistentProperty) {
+        return persistentProperty != null && 
getAutoTimestampType(persistentProperty) != AutoTimestampType.NONE;
+    }
+
+    /**
+     * Checks if a property has a @CreatedDate annotation (GORM or Spring 
Data) or @AutoTimestamp(CREATED).
+     *
+     * @param persistentProperty The persistent property to check
+     * @return true if the property represents a creation timestamp
+     */
+    public static boolean isCreatedTimestamp(PersistentProperty<?> 
persistentProperty) {
+        return getAutoTimestampType(persistentProperty) == 
AutoTimestampType.CREATED;
+    }
+
+    /**
+     * Checks if a property has a @LastModifiedDate annotation (GORM or Spring 
Data) or @AutoTimestamp(UPDATED).

Review Comment:
   ```suggestion
        * Checks if a property has a {@code @LastModifiedDate} annotation (GORM 
or Spring Data) or {@code @AutoTimestamp(UPDATED)}.
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to