HHH-16542 - Bad get/is handling with bytecode enhancement

This commit is contained in:
Steve Ebersole 2023-05-08 13:05:57 -05:00
parent c3f25c83c5
commit da71d54833
23 changed files with 642 additions and 141 deletions

View File

@ -17,8 +17,6 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Locale;
import java.util.regex.Pattern;
import jakarta.persistence.Transient;
import org.hibernate.AssertionFailure;
import org.hibernate.MappingException;
@ -32,6 +30,8 @@ import org.hibernate.type.BasicType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.java.spi.PrimitiveJavaType;
import jakarta.persistence.Transient;
/**
* Utility class for various reflection operations.
*
@ -41,10 +41,6 @@ import org.hibernate.type.descriptor.java.spi.PrimitiveJavaType;
*/
@SuppressWarnings("unchecked")
public final class ReflectHelper {
private static final Pattern JAVA_CONSTANT_PATTERN = Pattern.compile(
"[a-z\\d]+\\.([A-Z]+[a-z\\d]+)+\\$?([A-Z]{1}[a-z\\d]+)*\\.[A-Z_\\$]+", Pattern.UNICODE_CHARACTER_CLASS);
public static final Class[] NO_PARAM_SIGNATURE = ArrayHelper.EMPTY_CLASS_ARRAY;
public static final Object[] NO_PARAMS = ArrayHelper.EMPTY_OBJECT_ARRAY;
@ -516,7 +512,17 @@ public final class ReflectHelper {
return getter;
}
private static Method getGetterOrNull(Class containerClass, String propertyName) {
/**
* Find the method that can be used as the setter for this property.
*
* @param containerClass The Class which contains the property
* @param propertyName The name of the property
*
* @return The getter method, or {@code null} if there is none.
*
* @throws MappingException If the {@code containerClass} has both a get- and an is- form.
*/
public static Method getGetterOrNull(Class containerClass, String propertyName) {
if ( isRecord( containerClass ) ) {
try {
return containerClass.getMethod( propertyName, NO_PARAM_SIGNATURE );
@ -525,6 +531,7 @@ public final class ReflectHelper {
// Ignore
}
}
for ( Method method : containerClass.getDeclaredMethods() ) {
// if the method has parameters, skip it
if ( method.getParameterCount() != 0 ) {
@ -562,6 +569,8 @@ public final class ReflectHelper {
final String stemName = methodName.substring( 2 );
String decapitalizedStemName = Introspector.decapitalize( stemName );
if ( stemName.equals( propertyName ) || decapitalizedStemName.equals( propertyName ) ) {
// not sure that this can ever really happen given the handling of "get" above.
// but be safe
verifyNoGetVariantExists( containerClass, propertyName, method, stemName );
return method;
}
@ -571,8 +580,8 @@ public final class ReflectHelper {
return null;
}
private static void verifyNoIsVariantExists(
Class containerClass,
public static void verifyNoIsVariantExists(
Class<?> containerClass,
String propertyName,
Method getMethod,
String stemName) {
@ -589,7 +598,8 @@ public final class ReflectHelper {
}
}
private static void checkGetAndIsVariants(
public static void checkGetAndIsVariants(
Class containerClass,
String propertyName,
Method getMethod,
@ -611,7 +621,7 @@ public final class ReflectHelper {
}
}
private static void verifyNoGetVariantExists(
public static void verifyNoGetVariantExists(
Class containerClass,
String propertyName,
Method isMethod,

View File

@ -13,19 +13,19 @@ import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.property.access.spi.PropertyAccessSerializationException;
/**
* Abstract serialization replacement for field based Getter and Setter impls.
* Base Serializable form for field (used as Getter or Setter)
*
* @author Steve Ebersole
*/
public abstract class AbstractFieldSerialForm implements Serializable {
private final Class declaringClass;
private final Class<?> declaringClass;
private final String fieldName;
protected AbstractFieldSerialForm(Field field) {
this( field.getDeclaringClass(), field.getName() );
}
protected AbstractFieldSerialForm(Class declaringClass, String fieldName) {
protected AbstractFieldSerialForm(Class<?> declaringClass, String fieldName) {
this.declaringClass = declaringClass;
this.fieldName = fieldName;
}

View File

@ -0,0 +1,69 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.property.access.internal;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.property.access.spi.PropertyAccessSerializationException;
/**
* Base Serializable form for setter methods
*
* @author Steve Ebersole
*/
public abstract class AbstractSetterMethodSerialForm implements Serializable {
private final Class<?> containerClass;
private final String propertyName;
private final Class<?> declaringClass;
private final String methodName;
private final Class<?> argumentType;
public AbstractSetterMethodSerialForm(Class<?> containerClass, String propertyName, Method method) {
this.containerClass = containerClass;
this.propertyName = propertyName;
this.declaringClass = method.getDeclaringClass();
this.methodName = method.getName();
this.argumentType = method.getParameterTypes()[0];
}
public Class<?> getContainerClass() {
return containerClass;
}
public String getPropertyName() {
return propertyName;
}
public Class<?> getDeclaringClass() {
return declaringClass;
}
public String getMethodName() {
return methodName;
}
public Class<?> getArgumentType() {
return argumentType;
}
protected Method resolveMethod() {
try {
final Method method = declaringClass.getDeclaredMethod( methodName, argumentType );
ReflectHelper.ensureAccessibility( method );
return method;
}
catch (NoSuchMethodException e) {
throw new PropertyAccessSerializationException(
"Unable to resolve setter method on deserialization : " + declaringClass.getName() + "#"
+ methodName + "(" + argumentType.getName() + ")"
);
}
}
}

View File

@ -0,0 +1,208 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.property.access.internal;
import java.beans.Introspector;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Locale;
import org.hibernate.MappingException;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
import org.hibernate.engine.spi.CompositeOwner;
import org.hibernate.engine.spi.CompositeTracker;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Transient;
import static org.hibernate.engine.internal.ManagedTypeHelper.asCompositeOwner;
import static org.hibernate.engine.internal.ManagedTypeHelper.asCompositeTracker;
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
import static org.hibernate.engine.internal.ManagedTypeHelper.isCompositeTracker;
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptableType;
import static org.hibernate.internal.util.ReflectHelper.NO_PARAM_SIGNATURE;
import static org.hibernate.internal.util.ReflectHelper.findField;
import static org.hibernate.internal.util.ReflectHelper.isRecord;
/**
* @author Steve Ebersole
*/
public class AccessStrategyHelper {
public static final int COMPOSITE_TRACKER_MASK = 1;
public static final int COMPOSITE_OWNER = 2;
public static final int PERSISTENT_ATTRIBUTE_INTERCEPTABLE_MASK = 4;
public static Field fieldOrNull(Class<?> containerJavaType, String propertyName) {
try {
return findField( containerJavaType, propertyName );
}
catch (PropertyNotFoundException e) {
return null;
}
}
public static AccessType getAccessType(Class<?> containerJavaType, String propertyName) {
final Field field = fieldOrNull( containerJavaType, propertyName );
final AccessType explicitAccessType = getExplicitAccessType( containerJavaType, propertyName, field );
if ( explicitAccessType != null ) {
return explicitAccessType;
}
// No @Access on property or field; check to see if containerJavaType has an explicit @Access
AccessType classAccessType = getAccessTypeOrNull( containerJavaType );
if ( classAccessType != null ) {
return classAccessType;
}
// prefer using the field for getting if we can
return field != null ? AccessType.FIELD : AccessType.PROPERTY;
}
public static AccessType getExplicitAccessType(Class<?> containerClass, String propertyName, Field field) {
if ( isRecord( containerClass ) ) {
try {
containerClass.getMethod( propertyName, NO_PARAM_SIGNATURE );
return AccessType.PROPERTY;
}
catch (NoSuchMethodException e) {
// Ignore
}
}
if ( field != null
&& field.isAnnotationPresent( Access.class )
&& !field.isAnnotationPresent( Transient.class )
&& !Modifier.isStatic( field.getModifiers() ) ) {
return AccessType.FIELD;
}
for ( Method method : containerClass.getDeclaredMethods() ) {
// if the method has parameters, skip it
if ( method.getParameterCount() != 0 ) {
continue;
}
// if the method is a "bridge", skip it
if ( method.isBridge() ) {
continue;
}
if ( method.isAnnotationPresent( Transient.class ) ) {
continue;
}
if ( Modifier.isStatic( method.getModifiers() ) ) {
continue;
}
final String methodName = method.getName();
// try "get"
if ( methodName.startsWith( "get" ) ) {
final String stemName = methodName.substring( 3 );
final String decapitalizedStemName = Introspector.decapitalize( stemName );
if ( stemName.equals( propertyName ) || decapitalizedStemName.equals( propertyName ) ) {
if ( method.isAnnotationPresent( Access.class ) ) {
return AccessType.PROPERTY;
}
else {
checkIsMethodVariant( containerClass, propertyName, method, stemName );
}
}
}
// if not "get", then try "is"
if ( methodName.startsWith( "is" ) ) {
final String stemName = methodName.substring( 2 );
String decapitalizedStemName = Introspector.decapitalize( stemName );
if ( stemName.equals( propertyName ) || decapitalizedStemName.equals( propertyName ) ) {
if ( method.isAnnotationPresent( Access.class ) ) {
return AccessType.PROPERTY;
}
}
}
}
return null;
}
private static void checkIsMethodVariant(
Class<?> containerClass,
String propertyName,
Method method,
String stemName) {
final Method isMethodVariant = findIsMethodVariant( containerClass, stemName );
if ( isMethodVariant == null ) {
return;
}
if ( !isMethodVariant.isAnnotationPresent( Access.class ) ) {
throw new MappingException(
String.format(
Locale.ROOT,
"In trying to locate getter for property [%s], Class [%s] defined " +
"both a `get` [%s] and `is` [%s] variant",
propertyName,
containerClass.getName(),
method.toString(),
isMethodVariant.toString()
)
);
}
}
public static Method findIsMethodVariant(Class<?> containerClass, String stemName) {
// verify that the Class does not also define a method with the same stem name with 'is'
try {
final Method isMethod = containerClass.getDeclaredMethod( "is" + stemName );
if ( !Modifier.isStatic( isMethod.getModifiers() ) && isMethod.getAnnotation( Transient.class ) == null ) {
return isMethod;
}
}
catch (NoSuchMethodException ignore) {
}
return null;
}
protected static AccessType getAccessTypeOrNull(AnnotatedElement element) {
if ( element == null ) {
return null;
}
Access elementAccess = element.getAnnotation( Access.class );
return elementAccess == null ? null : elementAccess.value();
}
public static int determineEnhancementState(Class<?> containerClass, Class<?> attributeType) {
return ( CompositeOwner.class.isAssignableFrom( containerClass ) ? AccessStrategyHelper.COMPOSITE_OWNER : 0 )
| ( CompositeTracker.class.isAssignableFrom( attributeType ) ? AccessStrategyHelper.COMPOSITE_TRACKER_MASK : 0 )
| ( isPersistentAttributeInterceptableType( containerClass ) ? AccessStrategyHelper.PERSISTENT_ATTRIBUTE_INTERCEPTABLE_MASK : 0 );
}
public static void handleEnhancedInjection(Object target, Object value, int enhancementState, String propertyName) {
// This sets the component relation for dirty tracking purposes
if ( ( enhancementState & COMPOSITE_OWNER ) != 0
&& ( ( enhancementState & COMPOSITE_TRACKER_MASK ) != 0
&& value != null
|| isCompositeTracker( value ) ) ) {
asCompositeTracker( value ).$$_hibernate_setOwner( propertyName, asCompositeOwner( target ) );
}
// This marks the attribute as initialized, so it doesn't get lazily loaded afterward
if ( ( enhancementState & PERSISTENT_ATTRIBUTE_INTERCEPTABLE_MASK ) != 0 ) {
PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( target ).$$_hibernate_getInterceptor();
if ( interceptor instanceof BytecodeLazyAttributeInterceptor ) {
( (BytecodeLazyAttributeInterceptor) interceptor ).attributeInitialized( propertyName );
}
}
}
}

View File

@ -51,6 +51,7 @@ public class ChainedPropertyAccessImpl implements PropertyAccess, Getter, Setter
return owner;
}
@SuppressWarnings("rawtypes")
@Override
public Object getForInsert(Object owner, Map mergeMap, SharedSessionContractImplementor session) {
for ( int i = 0; i < propertyAccesses.length; i++ ) {

View File

@ -55,6 +55,7 @@ public class PropertyAccessCompositeUserTypeImpl implements PropertyAccess, Gett
return strategy.compositeUserType.getPropertyValue( owner, propertyIndex );
}
@SuppressWarnings("rawtypes")
@Override
public Object getForInsert(Object owner, Map mergeMap, SharedSessionContractImplementor session) {
return get( owner );

View File

@ -67,6 +67,7 @@ public class PropertyAccessEmbeddedImpl implements PropertyAccess {
return owner;
}
@SuppressWarnings("rawtypes")
@Override
public Object getForInsert(Object owner, Map mergeMap, SharedSessionContractImplementor session) {
return owner;

View File

@ -7,11 +7,24 @@
package org.hibernate.property.access.internal;
import org.hibernate.property.access.spi.EnhancedSetterImpl;
import org.hibernate.property.access.spi.EnhancedSetterMethodImpl;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.GetterFieldImpl;
import org.hibernate.property.access.spi.GetterMethodImpl;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.property.access.spi.PropertyAccessBuildingException;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.property.access.spi.SetterMethodImpl;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import jakarta.persistence.AccessType;
import static org.hibernate.internal.util.ReflectHelper.findSetterMethod;
import static org.hibernate.internal.util.ReflectHelper.getterMethodOrNull;
import static org.hibernate.property.access.internal.AccessStrategyHelper.fieldOrNull;
/**
* A {@link PropertyAccess} for byte code enhanced entities. Enhanced setter methods ( if available ) are used for
@ -20,17 +33,77 @@ import java.lang.reflect.Field;
* @author Steve Ebersole
* @author Luis Barreiro
*/
public class PropertyAccessEnhancedImpl extends PropertyAccessMixedImpl {
public class PropertyAccessEnhancedImpl implements PropertyAccess {
private final PropertyAccessStrategy strategy;
private final Getter getter;
private final Setter setter;
public PropertyAccessEnhancedImpl(
PropertyAccessStrategy strategy,
Class<?> containerJavaType,
String propertyName) {
super( strategy, containerJavaType, propertyName );
String propertyName,
AccessType getterAccessType) {
this.strategy = strategy;
final AccessType propertyAccessType = resolveAccessType( getterAccessType, containerJavaType, propertyName );
switch ( propertyAccessType ) {
case FIELD: {
final Field field = fieldOrNull( containerJavaType, propertyName );
if ( field == null ) {
throw new PropertyAccessBuildingException(
"Could not locate field for property named [" + containerJavaType.getName() + "#" + propertyName + "]"
);
}
this.getter = new GetterFieldImpl( containerJavaType, propertyName, field );
this.setter = new EnhancedSetterImpl( containerJavaType, propertyName, field );
break;
}
case PROPERTY: {
final Method getterMethod = getterMethodOrNull( containerJavaType, propertyName );
if ( getterMethod == null ) {
throw new PropertyAccessBuildingException(
"Could not locate getter for property named [" + containerJavaType.getName() + "#" + propertyName + "]"
);
}
final Method setterMethod = findSetterMethod( containerJavaType, propertyName, getterMethod.getReturnType() );
this.getter = new GetterMethodImpl( containerJavaType, propertyName, getterMethod );
this.setter = new EnhancedSetterMethodImpl( containerJavaType, propertyName, setterMethod );
break;
}
default: {
throw new PropertyAccessBuildingException(
"Invalid access type " + propertyAccessType + " for property named [" + containerJavaType.getName() + "#" + propertyName + "]"
);
}
}
}
private AccessType resolveAccessType(AccessType getterAccessType, Class<?> containerJavaType, String propertyName) {
if ( getterAccessType != null ) {
// this should indicate FIELD access
return getterAccessType;
}
// prefer using the field for getting if we can
final Field field = AccessStrategyHelper.fieldOrNull( containerJavaType, propertyName );
return field != null ? AccessType.FIELD : AccessType.PROPERTY;
}
@Override
protected Setter fieldSetter(Class<?> containerJavaType, String propertyName, Field field) {
return new EnhancedSetterImpl( containerJavaType, propertyName, field );
public PropertyAccessStrategy getPropertyAccessStrategy() {
return strategy;
}
@Override
public Getter getGetter() {
return getter;
}
@Override
public Setter getSetter() {
return setter;
}
}

View File

@ -58,6 +58,7 @@ public class PropertyAccessMapImpl implements PropertyAccess {
}
@Override
@SuppressWarnings("rawtypes")
public Object get(Object owner) {
return ( (Map) owner ).get( propertyName );
}

View File

@ -6,14 +6,9 @@
*/
package org.hibernate.property.access.internal;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.GetterFieldImpl;
import org.hibernate.property.access.spi.GetterMethodImpl;
@ -24,8 +19,10 @@ import org.hibernate.property.access.spi.Setter;
import org.hibernate.property.access.spi.SetterFieldImpl;
import org.hibernate.property.access.spi.SetterMethodImpl;
import static org.hibernate.internal.util.ReflectHelper.getterMethodOrNull;
import jakarta.persistence.AccessType;
import static org.hibernate.internal.util.ReflectHelper.findSetterMethod;
import static org.hibernate.internal.util.ReflectHelper.getterMethodOrNull;
/**
* A {@link PropertyAccess} based on mix of getter/setter method and/or field.
@ -38,20 +35,16 @@ public class PropertyAccessMixedImpl implements PropertyAccess {
private final Getter getter;
private final Setter setter;
public PropertyAccessMixedImpl(
PropertyAccessStrategy strategy,
Class<?> containerJavaType,
String propertyName) {
public PropertyAccessMixedImpl(PropertyAccessStrategy strategy, Class<?> containerJavaType, String propertyName) {
this.strategy = strategy;
AccessType propertyAccessType = getAccessType( containerJavaType, propertyName );
final AccessType propertyAccessType = AccessStrategyHelper.getAccessType( containerJavaType, propertyName );
switch ( propertyAccessType ) {
case FIELD: {
Field field = fieldOrNull( containerJavaType, propertyName );
Field field = AccessStrategyHelper.fieldOrNull( containerJavaType, propertyName );
if ( field == null ) {
throw new PropertyAccessBuildingException(
"Could not locate field for property named [" + containerJavaType.getName() + "#" + propertyName + "]"
"Could not locate field for property named [" + containerJavaType.getName() + "#" + propertyName + "]"
);
}
this.getter = fieldGetter( containerJavaType, propertyName, field );
@ -62,7 +55,7 @@ public class PropertyAccessMixedImpl implements PropertyAccess {
Method getterMethod = getterMethodOrNull( containerJavaType, propertyName );
if ( getterMethod == null ) {
throw new PropertyAccessBuildingException(
"Could not locate getter for property named [" + containerJavaType.getName() + "#" + propertyName + "]"
"Could not locate getter for property named [" + containerJavaType.getName() + "#" + propertyName + "]"
);
}
Method setterMethod = findSetterMethod( containerJavaType, propertyName, getterMethod.getReturnType() );
@ -73,47 +66,12 @@ public class PropertyAccessMixedImpl implements PropertyAccess {
}
default: {
throw new PropertyAccessBuildingException(
"Invalid access type " + propertyAccessType + " for property named [" + containerJavaType.getName() + "#" + propertyName + "]"
"Invalid access type " + propertyAccessType + " for property named [" + containerJavaType.getName() + "#" + propertyName + "]"
);
}
}
}
protected static Field fieldOrNull(Class<?> containerJavaType, String propertyName) {
try {
return ReflectHelper.findField( containerJavaType, propertyName );
}
catch (PropertyNotFoundException e) {
return null;
}
}
protected static AccessType getAccessType(Class<?> containerJavaType, String propertyName) {
Field field = fieldOrNull( containerJavaType, propertyName );
AccessType fieldAccessType = getAccessTypeOrNull( field );
if ( fieldAccessType != null ) {
return fieldAccessType;
}
AccessType methodAccessType = getAccessTypeOrNull( getterMethodOrNull( containerJavaType, propertyName ) );
if ( methodAccessType != null ) {
return methodAccessType;
}
// No @Access on property or field; check to see if containerJavaType has an explicit @Access
AccessType classAccessType = getAccessTypeOrNull( containerJavaType );
if ( classAccessType != null ) {
return classAccessType;
}
return field != null ? AccessType.FIELD : AccessType.PROPERTY;
}
private static AccessType getAccessTypeOrNull(AnnotatedElement element) {
if ( element == null ) {
return null;
}
Access elementAccess = element.getAnnotation( Access.class );
return elementAccess == null ? null : elementAccess.value();
}
// --- //
protected Getter fieldGetter(Class<?> containerJavaType, String propertyName, Field field) {

View File

@ -92,6 +92,7 @@ public class PropertyAccessStrategyBackRefImpl implements PropertyAccessStrategy
}
@Override
@SuppressWarnings("rawtypes")
public Object getForInsert(Object owner, Map mergeMap, SharedSessionContractImplementor session) {
if ( session == null ) {
return UNKNOWN;

View File

@ -6,9 +6,12 @@
*/
package org.hibernate.property.access.internal;
import org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.property.access.spi.PropertyAccessStrategy;
import jakarta.persistence.AccessType;
/**
* Defines a strategy for accessing property values via a get/set pair, which may be nonpublic. This
* is the default (and recommended) strategy.
@ -17,13 +20,24 @@ import org.hibernate.property.access.spi.PropertyAccessStrategy;
* @author Gavin King
*/
public class PropertyAccessStrategyEnhancedImpl implements PropertyAccessStrategy {
/**
* Singleton access
*/
public static final PropertyAccessStrategyEnhancedImpl INSTANCE = new PropertyAccessStrategyEnhancedImpl();
public static PropertyAccessStrategyEnhancedImpl with(AccessType getterAccessType) {
if ( getterAccessType == AccessType.FIELD ) {
return FIELD;
}
return STANDARD;
}
private final AccessType getterAccessType;
public static PropertyAccessStrategyEnhancedImpl STANDARD = new PropertyAccessStrategyEnhancedImpl( null );
public static PropertyAccessStrategyEnhancedImpl FIELD = new PropertyAccessStrategyEnhancedImpl( AccessType.FIELD );
public PropertyAccessStrategyEnhancedImpl(AccessType getterAccessType) {
this.getterAccessType = getterAccessType;
}
@Override
public PropertyAccess buildPropertyAccess(Class<?> containerJavaType, final String propertyName, boolean setterRequired) {
return new PropertyAccessEnhancedImpl( this, containerJavaType, propertyName );
return new PropertyAccessEnhancedImpl( this, containerJavaType, propertyName, getterAccessType );
}
}

View File

@ -74,6 +74,7 @@ public class PropertyAccessStrategyIndexBackRefImpl implements PropertyAccessStr
return PropertyAccessStrategyBackRefImpl.UNKNOWN;
}
@SuppressWarnings("rawtypes")
@Override
public Object getForInsert(Object owner, Map mergeMap, SharedSessionContractImplementor session) {
if ( session == null ) {

View File

@ -67,6 +67,7 @@ public class PropertyAccessStrategyNoopImpl implements PropertyAccessStrategy {
}
@Override
@SuppressWarnings("rawtypes")
public Object getForInsert(Object owner, Map mergeMap, SharedSessionContractImplementor session) {
return null;
}

View File

@ -15,6 +15,8 @@ import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.property.access.spi.PropertyAccessStrategyResolver;
import org.hibernate.service.ServiceRegistry;
import jakarta.persistence.AccessType;
import static org.hibernate.engine.internal.ManagedTypeHelper.isManagedType;
/**
@ -40,8 +42,10 @@ public class PropertyAccessStrategyResolverStandardImpl implements PropertyAcces
|| BuiltInPropertyAccessStrategies.MIXED.getExternalName().equals( explicitAccessStrategyName ) ) {
//type-cache-pollution agent: it's crucial to use the ManagedTypeHelper rather than attempting a direct cast
if ( isManagedType( containerClass ) ) {
// PROPERTY (BASIC) and MIXED are not valid for bytecode enhanced entities...
return PropertyAccessStrategyEnhancedImpl.INSTANCE;
if ( BuiltInPropertyAccessStrategies.FIELD.getExternalName().equals( explicitAccessStrategyName ) ) {
return PropertyAccessStrategyEnhancedImpl.FIELD;
}
return PropertyAccessStrategyEnhancedImpl.STANDARD;
}
}

View File

@ -9,18 +9,11 @@ package org.hibernate.property.access.spi;
import java.io.Serializable;
import java.lang.reflect.Field;
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
import org.hibernate.engine.internal.ManagedTypeHelper;
import org.hibernate.engine.spi.CompositeOwner;
import org.hibernate.engine.spi.CompositeTracker;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.Internal;
import org.hibernate.property.access.internal.AbstractFieldSerialForm;
import org.hibernate.property.access.internal.AccessStrategyHelper;
import static org.hibernate.engine.internal.ManagedTypeHelper.asCompositeOwner;
import static org.hibernate.engine.internal.ManagedTypeHelper.asCompositeTracker;
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
import static org.hibernate.engine.internal.ManagedTypeHelper.isCompositeTracker;
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptableType;
import static org.hibernate.property.access.internal.AccessStrategyHelper.determineEnhancementState;
/**
* A specialized Setter implementation for handling setting values into
@ -31,39 +24,21 @@ import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttrib
* @author Steve Ebersole
* @author Luis Barreiro
*/
@Internal
public class EnhancedSetterImpl extends SetterFieldImpl {
private static final int COMPOSITE_TRACKER_MASK = 1;
private static final int COMPOSITE_OWNER = 2;
private static final int PERSISTENT_ATTRIBUTE_INTERCEPTABLE_MASK = 4;
private final String propertyName;
private final int enhancementState;
public EnhancedSetterImpl(Class<?> containerClass, String propertyName, Field field) {
super( containerClass, propertyName, field );
this.propertyName = propertyName;
this.enhancementState = ( CompositeOwner.class.isAssignableFrom( containerClass ) ? COMPOSITE_OWNER : 0 )
| ( CompositeTracker.class.isAssignableFrom( field.getType() ) ? COMPOSITE_TRACKER_MASK : 0 )
| ( isPersistentAttributeInterceptableType( containerClass ) ? PERSISTENT_ATTRIBUTE_INTERCEPTABLE_MASK : 0 );
this.enhancementState = determineEnhancementState( containerClass, field.getType() );
}
@Override
public void set(Object target, Object value) {
super.set( target, value );
// This sets the component relation for dirty tracking purposes
if ( ( enhancementState & COMPOSITE_OWNER ) != 0 && ( ( enhancementState & COMPOSITE_TRACKER_MASK ) != 0 && value != null || isCompositeTracker( value ) ) ) {
asCompositeTracker( value ).$$_hibernate_setOwner( propertyName, asCompositeOwner( target ) );
}
// This marks the attribute as initialized, so it doesn't get lazily loaded afterwards
if ( ( enhancementState & PERSISTENT_ATTRIBUTE_INTERCEPTABLE_MASK ) != 0 ) {
PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( target ).$$_hibernate_getInterceptor();
if ( interceptor instanceof BytecodeLazyAttributeInterceptor ) {
( (BytecodeLazyAttributeInterceptor) interceptor ).attributeInitialized( propertyName );
}
}
AccessStrategyHelper.handleEnhancedInjection( target, value, enhancementState, propertyName );
}
@ -74,13 +49,12 @@ public class EnhancedSetterImpl extends SetterFieldImpl {
return new SerialForm( getContainerClass(), propertyName, getField() );
}
@SuppressWarnings("rawtypes")
private static class SerialForm extends AbstractFieldSerialForm implements Serializable {
private final Class containerClass;
private final Class<?> containerClass;
private final String propertyName;
private SerialForm(Class containerClass, String propertyName, Field field) {
private SerialForm(Class<?> containerClass, String propertyName, Field field) {
super( field );
this.containerClass = containerClass;
this.propertyName = propertyName;

View File

@ -0,0 +1,61 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.property.access.spi;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.hibernate.Internal;
import org.hibernate.property.access.internal.AbstractSetterMethodSerialForm;
import org.hibernate.property.access.internal.AccessStrategyHelper;
import static org.hibernate.property.access.internal.AccessStrategyHelper.determineEnhancementState;
/**
* A specialized Setter implementation for handling setting values into a bytecode-enhanced Class
* using a setter method. The reason we need specialized handling is to render the fact that we
* need to account for certain enhancement features during the setting process.
*
* @author Steve Ebersole
* @author Luis Barreiro
*/
@Internal
public class EnhancedSetterMethodImpl extends SetterMethodImpl {
private final String propertyName;
private final int enhancementState;
public EnhancedSetterMethodImpl(Class<?> containerClass, String propertyName, Method setterMethod) {
super( containerClass, propertyName, setterMethod );
this.propertyName = propertyName;
this.enhancementState = determineEnhancementState( containerClass, setterMethod.getReturnType() );
}
@Override
public void set(Object target, Object value) {
super.set( target, value );
AccessStrategyHelper.handleEnhancedInjection( target, value, enhancementState, propertyName );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// serialization
private Object writeReplace() {
return new SerialForm( getContainerClass(), propertyName, getMethod() );
}
private static class SerialForm extends AbstractSetterMethodSerialForm implements Serializable {
private SerialForm(Class<?> containerClass, String propertyName, Method method) {
super( containerClass, propertyName, method );
}
private Object readResolve() {
return new EnhancedSetterMethodImpl( getContainerClass(), getPropertyName(), resolveMethod() );
}
}
}

View File

@ -15,6 +15,7 @@ import java.lang.reflect.Type;
import java.util.Locale;
import java.util.Map;
import org.hibernate.Internal;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.property.access.internal.AbstractFieldSerialForm;
@ -24,6 +25,7 @@ import org.hibernate.property.access.internal.AbstractFieldSerialForm;
*
* @author Steve Ebersole
*/
@Internal
public class GetterFieldImpl implements Getter {
private final Class<?> containerClass;
private final String propertyName;
@ -58,6 +60,7 @@ public class GetterFieldImpl implements Getter {
}
}
@SuppressWarnings("rawtypes")
@Override
public Object getForInsert(Object owner, Map mergeMap, SharedSessionContractImplementor session) {
return get( owner );

View File

@ -14,6 +14,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;
import org.hibernate.Internal;
import org.hibernate.PropertyAccessException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreMessageLogger;
@ -25,6 +26,7 @@ import static org.hibernate.internal.CoreLogging.messageLogger;
/**
* @author Steve Ebersole
*/
@Internal
public class GetterMethodImpl implements Getter {
private static final CoreMessageLogger LOG = messageLogger( GetterMethodImpl.class );
@ -74,6 +76,7 @@ public class GetterMethodImpl implements Getter {
}
}
@SuppressWarnings("rawtypes")
@Override
public Object getForInsert(Object owner, Map mergeMap, SharedSessionContractImplementor session) {
return get( owner );

View File

@ -11,6 +11,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Locale;
import org.hibernate.Internal;
import org.hibernate.PropertyAccessException;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.property.access.internal.AbstractFieldSerialForm;
@ -22,6 +23,7 @@ import org.hibernate.proxy.LazyInitializer;
*
* @author Steve Ebersole
*/
@Internal
public class SetterFieldImpl implements Setter {
private final Class<?> containerClass;
private final String propertyName;
@ -73,9 +75,12 @@ public class SetterFieldImpl implements Setter {
if ( lazyInitializer != null ) {
valueType = lazyInitializer.getEntityName();
}
else {
else if ( value != null ) {
valueType = value.getClass().getTypeName();
}
else {
valueType = "<unknown>";
}
throw new PropertyAccessException(
e,
String.format(

View File

@ -10,16 +10,18 @@ import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.hibernate.Internal;
import org.hibernate.PropertyAccessException;
import org.hibernate.PropertySetterAccessException;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.property.access.internal.AbstractSetterMethodSerialForm;
import static org.hibernate.internal.CoreLogging.messageLogger;
/**
* @author Steve Ebersole
*/
@Internal
public class SetterMethodImpl implements Setter {
private static final CoreMessageLogger LOG = messageLogger( SetterMethodImpl.class );
@ -107,6 +109,10 @@ public class SetterMethodImpl implements Setter {
}
}
public Class<?> getContainerClass() {
return containerClass;
}
@Override
public String getMethodName() {
return setterMethod.getName();
@ -121,38 +127,13 @@ public class SetterMethodImpl implements Setter {
return new SerialForm( containerClass, propertyName, setterMethod );
}
private static class SerialForm implements Serializable {
private final Class<?> containerClass;
private final String propertyName;
private final Class<?> declaringClass;
private final String methodName;
private final Class<?> argumentType;
private static class SerialForm extends AbstractSetterMethodSerialForm implements Serializable {
private SerialForm(Class<?> containerClass, String propertyName, Method method) {
this.containerClass = containerClass;
this.propertyName = propertyName;
this.declaringClass = method.getDeclaringClass();
this.methodName = method.getName();
this.argumentType = method.getParameterTypes()[0];
super( containerClass, propertyName, method );
}
private Object readResolve() {
return new SetterMethodImpl( containerClass, propertyName, resolveMethod() );
}
private Method resolveMethod() {
try {
final Method method = declaringClass.getDeclaredMethod( methodName, argumentType );
ReflectHelper.ensureAccessibility( method );
return method;
}
catch (NoSuchMethodException e) {
throw new PropertyAccessSerializationException(
"Unable to resolve setter method on deserialization : " + declaringClass.getName() + "#"
+ methodName + "(" + argumentType.getName() + ")"
);
}
return new SetterMethodImpl( getContainerClass(), getPropertyName(), resolveMethod() );
}
}
}

View File

@ -0,0 +1,63 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.orm.test.property;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
/**
* @author Steve Ebersole
*/
@DomainModel(annotatedClasses = FieldMappingWithGetterAndIsTest.Tester.class)
@SessionFactory
public class FieldMappingWithGetterAndIsTest {
@Test
public void testResolution(DomainModelScope modelScope, SessionFactoryScope factoryScope) {
final PersistentClass entityBinding = modelScope.getEntityBinding( Tester.class );
factoryScope.getCollectingStatementInspector();
}
@Entity(name="Tester")
@Table(name="Tester")
public static class Tester {
@Id
private Integer id;
@Basic
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public boolean isName() {
return name != null;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@ -0,0 +1,68 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.orm.test.property;
import org.hibernate.boot.MetadataSources;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.EnhancementOptions;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
/**
* @author Steve Ebersole
*/
@RunWith(BytecodeEnhancerRunner.class)
@EnhancementOptions( inlineDirtyChecking = true, lazyLoading = true )
public class FieldMappingWithGetterAndIsTest2 extends BaseNonConfigCoreFunctionalTestCase {
@Override
protected void applyMetadataSources(MetadataSources sources) {
super.applyMetadataSources( sources );
sources.addAnnotatedClass( Tester.class );
}
@Test
public void testResolution() {
sessionFactory();
}
@Entity(name="Tester")
@Table(name="Tester")
public static class Tester {
@Id
private Integer id;
@Basic
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public boolean isName() {
return name != null;
}
public void setName(String name) {
this.name = name;
}
}
}