This is an automated email from the ASF dual-hosted git repository.
borinquenkid pushed a commit to branch 8.0.x-hibernate7-dev
in repository https://gitbox.apache.org/repos/asf/grails-core.git
The following commit(s) were added to refs/heads/8.0.x-hibernate7-dev by this
push:
new 4b38b08a97 hibernate7: cleanup TraitPropertyAccessStrategy
4b38b08a97 is described below
commit 4b38b08a97d3729834cd08976326b3beccfa5f71
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Mon Mar 9 15:54:09 2026 -0500
hibernate7: cleanup TraitPropertyAccessStrategy
---
.../access/TraitPropertyAccessStrategy.java | 118 ++++++++++++---------
.../access/TraitPropertyAccessStrategySpec.groovy | 28 +++++
2 files changed, 93 insertions(+), 53 deletions(-)
diff --git
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategy.java
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategy.java
index fa59f130ff..265fb7a93e 100644
---
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategy.java
+++
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategy.java
@@ -25,6 +25,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.UnknownKeyFor;
import org.codehaus.groovy.transform.trait.Traits;
import org.grails.datastore.mapping.reflect.NameUtils;
+import org.hibernate.MappingException;
import org.hibernate.property.access.spi.*;
import org.springframework.util.ReflectionUtils;
@@ -38,6 +39,19 @@ import org.springframework.util.ReflectionUtils;
public class TraitPropertyAccessStrategy implements PropertyAccessStrategy {
public PropertyAccess buildPropertyAccess(Class containerJavaType, String
propertyName) {
+ return buildPropertyAccess(containerJavaType, propertyName, true);
+ }
+
+ protected String getTraitFieldName(Class traitClass, String fieldName) {
+ return traitClass.getName().replace('.', '_') + "__" + fieldName;
+ }
+
+ @java.lang.Override
+ public @UnknownKeyFor @NonNull @Initialized PropertyAccess
buildPropertyAccess(
+ java.lang.@UnknownKeyFor @NonNull @Initialized Class<@UnknownKeyFor
@NonNull @Initialized ?>
+ containerJavaType,
+ java.lang.@UnknownKeyFor @NonNull @Initialized String propertyName,
+ @UnknownKeyFor @Initialized boolean setterRequired) {
Method readMethod =
ReflectionUtils.findMethod(containerJavaType,
NameUtils.getGetterName(propertyName));
if (readMethod == null) {
@@ -59,72 +73,70 @@ public class TraitPropertyAccessStrategy implements
PropertyAccessStrategy {
+ "] of class ["
+ containerJavaType.getName()
+ "] that is not provided by a trait!");
+ }
+
+ Traits.Implemented traitImplemented =
readMethod.getAnnotation(Traits.Implemented.class);
+ final String traitFieldName;
+ if (traitImplemented == null) {
+ Traits.TraitBridge traitBridge =
readMethod.getAnnotation(Traits.TraitBridge.class);
+ if (traitBridge != null) {
+ traitFieldName = getTraitFieldName(traitBridge.traitClass(),
propertyName);
+ } else {
+ throw new IllegalStateException(
+ "TraitPropertyAccessStrategy used on property ["
+ + propertyName
+ + "] of class ["
+ + containerJavaType.getName()
+ + "] that is not provided by a trait!");
+ }
} else {
+ traitFieldName = getTraitFieldName(readMethod.getDeclaringClass(),
propertyName);
+ }
- Traits.Implemented traitImplemented =
readMethod.getAnnotation(Traits.Implemented.class);
- final String traitFieldName;
- if (traitImplemented == null) {
- Traits.TraitBridge traitBridge =
readMethod.getAnnotation(Traits.TraitBridge.class);
- if (traitBridge != null) {
- traitFieldName = getTraitFieldName(traitBridge.traitClass(),
propertyName);
- } else {
- throw new IllegalStateException(
+ Field field = ReflectionUtils.findField(containerJavaType, traitFieldName);
+ final Getter getter;
+ final Setter setter;
+ if (field == null) {
+ getter = new GetterMethodImpl(containerJavaType, propertyName,
readMethod);
+ Method writeMethod =
+ ReflectionUtils.findMethod(
+ containerJavaType,
+ NameUtils.getSetterName(propertyName),
+ readMethod.getReturnType());
+ if (writeMethod == null) {
+ if (setterRequired) {
+ throw new MappingException(
"TraitPropertyAccessStrategy used on property ["
+ propertyName
+ "] of class ["
+ containerJavaType.getName()
- + "] that is not provided by a trait!");
+ + "] that has no setter!");
}
+ setter = null;
} else {
- traitFieldName = getTraitFieldName(readMethod.getDeclaringClass(),
propertyName);
- }
-
- Field field = ReflectionUtils.findField(containerJavaType,
traitFieldName);
- final Getter getter;
- final Setter setter;
- if (field == null) {
- getter = new GetterMethodImpl(containerJavaType, propertyName,
readMethod);
- Method writeMethod =
- ReflectionUtils.findMethod(
- containerJavaType,
- NameUtils.getSetterName(propertyName),
- readMethod.getReturnType());
setter = new SetterMethodImpl(containerJavaType, propertyName,
writeMethod);
- } else {
-
- getter = new GetterFieldImpl(containerJavaType, propertyName, field);
- setter = new SetterFieldImpl(containerJavaType, propertyName, field);
}
+ } else {
- return new PropertyAccess() {
- @Override
- public PropertyAccessStrategy getPropertyAccessStrategy() {
- return TraitPropertyAccessStrategy.this;
- }
-
- @Override
- public Getter getGetter() {
- return getter;
- }
-
- @Override
- public Setter getSetter() {
- return setter;
- }
- };
+ getter = new GetterFieldImpl(containerJavaType, propertyName, field);
+ setter = new SetterFieldImpl(containerJavaType, propertyName, field);
}
- }
- protected String getTraitFieldName(Class traitClass, String fieldName) {
- return traitClass.getName().replace('.', '_') + "__" + fieldName;
- }
+ return new PropertyAccess() {
+ @Override
+ public PropertyAccessStrategy getPropertyAccessStrategy() {
+ return TraitPropertyAccessStrategy.this;
+ }
- @java.lang.Override
- public @UnknownKeyFor @NonNull @Initialized PropertyAccess
buildPropertyAccess(
- java.lang.@UnknownKeyFor @NonNull @Initialized Class<@UnknownKeyFor
@NonNull @Initialized ?>
- containerJavaType,
- java.lang.@UnknownKeyFor @NonNull @Initialized String propertyName,
- @UnknownKeyFor @NonNull @Initialized boolean setterRequired) {
- return buildPropertyAccess(containerJavaType, propertyName);
+ @Override
+ public Getter getGetter() {
+ return getter;
+ }
+
+ @Override
+ public Setter getSetter() {
+ return setter;
+ }
+ };
}
}
diff --git
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategySpec.groovy
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategySpec.groovy
index 540338b416..017c7703e1 100644
---
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategySpec.groovy
+++
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/access/TraitPropertyAccessStrategySpec.groovy
@@ -18,6 +18,7 @@
*/
package org.grails.orm.hibernate.access
+import org.hibernate.MappingException
import org.hibernate.property.access.spi.GetterFieldImpl
import org.hibernate.property.access.spi.GetterMethodImpl
import org.hibernate.property.access.spi.SetterFieldImpl
@@ -39,6 +40,10 @@ trait HasFlag {
Boolean flag
}
+trait HasComputed {
+ String getComputed() { "foo" }
+}
+
/** Plain Groovy class — no trait involvement. */
class PlainPerson {
String plain
@@ -53,6 +58,9 @@ class ActiveEntity implements HasActive {}
/** Groovy class implementing a boxed-Boolean trait. */
class FlaggedEntity implements HasFlag {}
+/** Groovy class implementing a computed-property trait. */
+class ComputedEntity implements HasComputed {}
+
// ─── Spec
─────────────────────────────────────────────────────────────────────
class TraitPropertyAccessStrategySpec extends Specification {
@@ -258,5 +266,25 @@ class TraitPropertyAccessStrategySpec extends
Specification {
nameAccess.getter.returnTypeClass == String
activeAccess.getter.returnTypeClass == boolean
}
+
+ // ─── Read-only property (no field, no setter) ───────────────────────────
+
+ void "buildPropertyAccess for computed property returns method-based
getter and no setter if not required"() {
+ when:
+ def access = strategy.buildPropertyAccess(ComputedEntity, 'computed',
false)
+
+ then:
+ access != null
+ access.getter instanceof GetterMethodImpl
+ access.setter == null
+ }
+
+ void "buildPropertyAccess for computed property throws MappingException if
setter is required"() {
+ when:
+ strategy.buildPropertyAccess(ComputedEntity, 'computed', true)
+
+ then:
+ thrown(MappingException)
+ }
}