HHH-8584 - Prefer returning UNKNOWN from PersistenceUtilHelper
This commit is contained in:
parent
20756e3f9d
commit
3e24ee50b6
|
@ -1,20 +1,17 @@
|
|||
package org.hibernate.jpa.internal.util;
|
||||
|
||||
import javax.persistence.spi.LoadState;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.spi.LoadState;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.bytecode.instrumentation.internal.FieldInterceptionHelper;
|
||||
import org.hibernate.bytecode.instrumentation.spi.FieldInterceptor;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
|
@ -22,107 +19,61 @@ import org.hibernate.proxy.HibernateProxy;
|
|||
import org.hibernate.proxy.LazyInitializer;
|
||||
|
||||
/**
|
||||
* Central delegate for handling calls from:<ul>
|
||||
* <li>{@link javax.persistence.PersistenceUtil#isLoaded(Object)}</li>
|
||||
* <li>{@link javax.persistence.PersistenceUtil#isLoaded(Object, String)}</li>
|
||||
* <li>{@link javax.persistence.spi.ProviderUtil#isLoaded(Object)}</li>
|
||||
* <li>{@link javax.persistence.spi.ProviderUtil#isLoadedWithReference(Object, String)}</li>
|
||||
* <li>{@link javax.persistence.spi.ProviderUtil#isLoadedWithoutReference(Object, String)}li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Emmanuel Bernard
|
||||
* @author Hardy Ferentschik
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class PersistenceUtilHelper {
|
||||
public static LoadState isLoadedWithoutReference(Object proxy, String property, MetadataCache cache) {
|
||||
Object entity;
|
||||
boolean sureFromUs = false;
|
||||
if ( proxy instanceof HibernateProxy ) {
|
||||
LazyInitializer li = ( ( HibernateProxy ) proxy ).getHibernateLazyInitializer();
|
||||
if ( li.isUninitialized() ) {
|
||||
return LoadState.NOT_LOADED;
|
||||
}
|
||||
else {
|
||||
entity = li.getImplementation();
|
||||
}
|
||||
sureFromUs = true;
|
||||
}
|
||||
else {
|
||||
entity = proxy;
|
||||
}
|
||||
|
||||
//we are instrumenting but we can't assume we are the only ones
|
||||
if ( FieldInterceptionHelper.isInstrumented( entity ) ) {
|
||||
FieldInterceptor interceptor = FieldInterceptionHelper.extractFieldInterceptor( entity );
|
||||
final boolean isInitialized = interceptor == null || interceptor.isInitialized( property );
|
||||
LoadState state;
|
||||
if (isInitialized && interceptor != null) {
|
||||
//property is loaded according to bytecode enhancement, but is it loaded as far as association?
|
||||
//it's ours, we can read
|
||||
state = isLoaded( get( entity, property, cache ) );
|
||||
//it's ours so we know it's loaded
|
||||
if (state == LoadState.UNKNOWN) state = LoadState.LOADED;
|
||||
}
|
||||
else if ( interceptor != null ) {
|
||||
state = LoadState.NOT_LOADED;
|
||||
}
|
||||
else if ( sureFromUs ) { //interceptor == null
|
||||
//property is loaded according to bytecode enhancement, but is it loaded as far as association?
|
||||
//it's ours, we can read
|
||||
state = isLoaded( get( entity, property, cache ) );
|
||||
//it's ours so we know it's loaded
|
||||
if (state == LoadState.UNKNOWN) state = LoadState.LOADED;
|
||||
}
|
||||
else {
|
||||
state = LoadState.UNKNOWN;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
else {
|
||||
//can't do sureFromUs ? LoadState.LOADED : LoadState.UNKNOWN;
|
||||
//is that an association?
|
||||
return LoadState.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public static LoadState isLoadedWithReference(Object proxy, String property, MetadataCache cache) {
|
||||
//for sure we don't instrument and for sure it's not a lazy proxy
|
||||
Object object = get(proxy, property, cache);
|
||||
return isLoaded( object );
|
||||
}
|
||||
|
||||
private static Object get(Object proxy, String property, MetadataCache cache) {
|
||||
final Class<?> clazz = proxy.getClass();
|
||||
|
||||
try {
|
||||
Member member = cache.getMember( clazz, property );
|
||||
if (member instanceof Field) {
|
||||
return ( (Field) member ).get( proxy );
|
||||
}
|
||||
else if (member instanceof Method) {
|
||||
return ( (Method) member ).invoke( proxy );
|
||||
}
|
||||
else {
|
||||
throw new AssertionFailure( "Member object neither Field nor Method: " + member);
|
||||
}
|
||||
}
|
||||
catch ( IllegalAccessException e ) {
|
||||
throw new PersistenceException( "Unable to access field or method: "
|
||||
+ clazz + "#"
|
||||
+ property, e);
|
||||
}
|
||||
catch ( InvocationTargetException e ) {
|
||||
throw new PersistenceException( "Unable to access field or method: "
|
||||
+ clazz + "#"
|
||||
+ property, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setAccessibility(Member member) {
|
||||
//Sun's ease of use, sigh...
|
||||
( ( AccessibleObject ) member ).setAccessible( true );
|
||||
}
|
||||
|
||||
public static LoadState isLoaded(Object o) {
|
||||
if ( o instanceof HibernateProxy ) {
|
||||
final boolean isInitialized = !( ( HibernateProxy ) o ).getHibernateLazyInitializer().isUninitialized();
|
||||
/**
|
||||
* Determine if the given object reference represents loaded state. The reference may be to an entity or a
|
||||
* persistent collection.
|
||||
* <p/>
|
||||
* Return is defined as follows:<ol>
|
||||
* <li>
|
||||
* If the reference is a {@link HibernateProxy}, we return {@link LoadState#LOADED} if
|
||||
* {@link org.hibernate.proxy.LazyInitializer#isUninitialized()} returns {@code false}; else we return
|
||||
* {@link LoadState#NOT_LOADED}
|
||||
* </li>
|
||||
* <li>
|
||||
* If the reference is an enhanced (by Hibernate) entity, we return {@link LoadState#LOADED} if
|
||||
* {@link org.hibernate.bytecode.instrumentation.spi.FieldInterceptor#isInitialized()} returns {@code true};
|
||||
* else we return {@link LoadState#NOT_LOADED}
|
||||
* </li>
|
||||
* <li>
|
||||
* If the reference is a {@link PersistentCollection}, we return {@link LoadState#LOADED} if
|
||||
* {@link org.hibernate.collection.spi.PersistentCollection#wasInitialized()} returns {@code true}; else
|
||||
* we return {@link LoadState#NOT_LOADED}
|
||||
* </li>
|
||||
* <li>
|
||||
* In all other cases we return {@link LoadState#UNKNOWN}
|
||||
* </li>
|
||||
* </ol>
|
||||
*
|
||||
*
|
||||
* @param reference The object reference to check.
|
||||
*
|
||||
* @return The appropriate LoadState (see above)
|
||||
*/
|
||||
public static LoadState isLoaded(Object reference) {
|
||||
if ( reference instanceof HibernateProxy ) {
|
||||
final boolean isInitialized = !( (HibernateProxy) reference ).getHibernateLazyInitializer().isUninitialized();
|
||||
return isInitialized ? LoadState.LOADED : LoadState.NOT_LOADED;
|
||||
}
|
||||
else if ( o instanceof PersistentCollection ) {
|
||||
final boolean isInitialized = ( (PersistentCollection) o ).wasInitialized();
|
||||
else if ( FieldInterceptionHelper.isInstrumented( reference ) ) {
|
||||
FieldInterceptor interceptor = FieldInterceptionHelper.extractFieldInterceptor( reference );
|
||||
final boolean isInitialized = interceptor == null || interceptor.isInitialized();
|
||||
return isInitialized ? LoadState.LOADED : LoadState.NOT_LOADED;
|
||||
}
|
||||
else if ( reference instanceof PersistentCollection ) {
|
||||
final boolean isInitialized = ( (PersistentCollection) reference ).wasInitialized();
|
||||
return isInitialized ? LoadState.LOADED : LoadState.NOT_LOADED;
|
||||
}
|
||||
else {
|
||||
|
@ -130,24 +81,297 @@ public class PersistenceUtilHelper {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given attribute (by name) loaded? This form must take care to not access the attribute (trigger
|
||||
* initialization).
|
||||
*
|
||||
* @param entity The entity
|
||||
* @param attributeName The name of the attribute to check
|
||||
* @param cache The cache we maintain of attribute resolutions
|
||||
*
|
||||
* @return The LoadState
|
||||
*/
|
||||
public static LoadState isLoadedWithoutReference(Object entity, String attributeName, MetadataCache cache) {
|
||||
boolean sureFromUs = false;
|
||||
if ( entity instanceof HibernateProxy ) {
|
||||
LazyInitializer li = ( (HibernateProxy) entity ).getHibernateLazyInitializer();
|
||||
if ( li.isUninitialized() ) {
|
||||
// we have an uninitialized proxy, the attribute cannot be loaded
|
||||
return LoadState.NOT_LOADED;
|
||||
}
|
||||
else {
|
||||
// swap the proxy with target (for proper class name resolution)
|
||||
entity = li.getImplementation();
|
||||
}
|
||||
sureFromUs = true;
|
||||
}
|
||||
|
||||
// we are instrumenting but we can't assume we are the only ones
|
||||
if ( FieldInterceptionHelper.isInstrumented( entity ) ) {
|
||||
FieldInterceptor interceptor = FieldInterceptionHelper.extractFieldInterceptor( entity );
|
||||
final boolean isInitialized = interceptor == null || interceptor.isInitialized( attributeName );
|
||||
LoadState state;
|
||||
if (isInitialized && interceptor != null) {
|
||||
// attributeName is loaded according to bytecode enhancement, but is it loaded as far as association?
|
||||
// it's ours, we can read
|
||||
try {
|
||||
final Class entityClass = entity.getClass();
|
||||
final Object attributeValue = cache.getClassMetadata( entityClass )
|
||||
.getAttributeAccess( attributeName )
|
||||
.extractValue( entity );
|
||||
state = isLoaded( attributeValue );
|
||||
|
||||
// it's ours so we know it's loaded
|
||||
if ( state == LoadState.UNKNOWN ) {
|
||||
state = LoadState.LOADED;
|
||||
}
|
||||
}
|
||||
catch (AttributeExtractionException ignore) {
|
||||
state = LoadState.UNKNOWN;
|
||||
}
|
||||
}
|
||||
else if ( interceptor != null ) {
|
||||
state = LoadState.NOT_LOADED;
|
||||
}
|
||||
else if ( sureFromUs ) {
|
||||
// property is loaded according to bytecode enhancement, but is it loaded as far as association?
|
||||
// it's ours, we can read
|
||||
try {
|
||||
final Class entityClass = entity.getClass();
|
||||
final Object attributeValue = cache.getClassMetadata( entityClass )
|
||||
.getAttributeAccess( attributeName )
|
||||
.extractValue( entity );
|
||||
state = isLoaded( attributeValue );
|
||||
|
||||
// it's ours so we know it's loaded
|
||||
if ( state == LoadState.UNKNOWN ) {
|
||||
state = LoadState.LOADED;
|
||||
}
|
||||
}
|
||||
catch (AttributeExtractionException ignore) {
|
||||
state = LoadState.UNKNOWN;
|
||||
}
|
||||
}
|
||||
else {
|
||||
state = LoadState.UNKNOWN;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
else {
|
||||
return LoadState.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the given attribute (by name) loaded? This form must take care to not access the attribute (trigger
|
||||
* initialization).
|
||||
*
|
||||
* @param entity The entity
|
||||
* @param attributeName The name of the attribute to check
|
||||
* @param cache The cache we maintain of attribute resolutions
|
||||
*
|
||||
* @return The LoadState
|
||||
*/
|
||||
public static LoadState isLoadedWithReference(Object entity, String attributeName, MetadataCache cache) {
|
||||
if ( entity instanceof HibernateProxy ) {
|
||||
final LazyInitializer li = ( (HibernateProxy) entity ).getHibernateLazyInitializer();
|
||||
if ( li.isUninitialized() ) {
|
||||
// we have an uninitialized proxy, the attribute cannot be loaded
|
||||
return LoadState.NOT_LOADED;
|
||||
}
|
||||
else {
|
||||
// swap the proxy with target (for proper class name resolution)
|
||||
entity = li.getImplementation();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
final Class entityClass = entity.getClass();
|
||||
final Object attributeValue = cache.getClassMetadata( entityClass )
|
||||
.getAttributeAccess( attributeName )
|
||||
.extractValue( entity );
|
||||
return isLoaded( attributeValue );
|
||||
}
|
||||
catch (AttributeExtractionException ignore) {
|
||||
return LoadState.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class AttributeExtractionException extends HibernateException {
|
||||
public AttributeExtractionException(String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public AttributeExtractionException(String message, Throwable cause) {
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
||||
|
||||
public static interface AttributeAccess {
|
||||
public Object extractValue(Object owner) throws AttributeExtractionException;
|
||||
}
|
||||
|
||||
public static class FieldAttributeAccess implements AttributeAccess {
|
||||
private final String name;
|
||||
private final Field field;
|
||||
|
||||
public FieldAttributeAccess(Field field) {
|
||||
this.name = field.getName();
|
||||
try {
|
||||
field.setAccessible( true );
|
||||
}
|
||||
catch (Exception e) {
|
||||
this.field = null;
|
||||
return;
|
||||
}
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object extractValue(Object owner) {
|
||||
if ( field == null ) {
|
||||
throw new AttributeExtractionException( "Attribute (field) " + name + " is not accessible" );
|
||||
}
|
||||
|
||||
try {
|
||||
return field.get( owner );
|
||||
}
|
||||
catch ( IllegalAccessException e ) {
|
||||
throw new AttributeExtractionException(
|
||||
"Unable to access attribute (field): " + field.getDeclaringClass().getName() + "#" + name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class MethodAttributeAccess implements AttributeAccess {
|
||||
private final String name;
|
||||
private final Method method;
|
||||
|
||||
public MethodAttributeAccess(String attributeName, Method method) {
|
||||
this.name = attributeName;
|
||||
try {
|
||||
method.setAccessible( true );
|
||||
}
|
||||
catch (Exception e) {
|
||||
this.method = null;
|
||||
return;
|
||||
}
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object extractValue(Object owner) {
|
||||
if ( method == null ) {
|
||||
throw new AttributeExtractionException( "Attribute (method) " + name + " is not accessible" );
|
||||
}
|
||||
|
||||
try {
|
||||
return method.invoke( owner );
|
||||
}
|
||||
catch ( IllegalAccessException e ) {
|
||||
throw new AttributeExtractionException(
|
||||
"Unable to access attribute (method): " + method.getDeclaringClass().getName() + "#" + name,
|
||||
e
|
||||
);
|
||||
}
|
||||
catch ( InvocationTargetException e ) {
|
||||
throw new AttributeExtractionException(
|
||||
"Unable to access attribute (method): " + method.getDeclaringClass().getName() + "#" + name,
|
||||
e.getCause()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class NoSuchAttributeAccess implements AttributeAccess {
|
||||
private final Class clazz;
|
||||
private final String attributeName;
|
||||
|
||||
public NoSuchAttributeAccess(Class clazz, String attributeName) {
|
||||
this.clazz = clazz;
|
||||
this.attributeName = attributeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object extractValue(Object owner) throws AttributeExtractionException {
|
||||
throw new AttributeExtractionException( "No such attribute : " + clazz.getName() + "#" + attributeName );
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClassMetadataCache {
|
||||
private final Class specifiedClass;
|
||||
private List<Class<?>> classHierarchy;
|
||||
private Map<String, AttributeAccess> attributeAccessMap = new HashMap<String, AttributeAccess>();
|
||||
|
||||
public ClassMetadataCache(Class<?> clazz) {
|
||||
this.specifiedClass = clazz;
|
||||
this.classHierarchy = findClassHierarchy( clazz );
|
||||
}
|
||||
|
||||
private static List<Class<?>> findClassHierarchy(Class<?> clazz) {
|
||||
List<Class<?>> classes = new ArrayList<Class<?>>();
|
||||
Class<?> current = clazz;
|
||||
do {
|
||||
classes.add( current );
|
||||
current = current.getSuperclass();
|
||||
} while ( current != null );
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
public AttributeAccess getAttributeAccess(String attributeName) {
|
||||
AttributeAccess attributeAccess = attributeAccessMap.get( attributeName );
|
||||
if ( attributeAccess == null ) {
|
||||
attributeAccess = buildAttributeAccess( attributeName );
|
||||
attributeAccessMap.put( attributeName, attributeAccess );
|
||||
}
|
||||
return attributeAccess;
|
||||
}
|
||||
|
||||
private AttributeAccess buildAttributeAccess(String attributeName) {
|
||||
for ( Class clazz : classHierarchy ) {
|
||||
try {
|
||||
final Field field = clazz.getDeclaredField( attributeName );
|
||||
if ( field != null ) {
|
||||
return new FieldAttributeAccess( field );
|
||||
}
|
||||
}
|
||||
catch ( NoSuchFieldException e ) {
|
||||
final Method method = getMethod( clazz, attributeName );
|
||||
if ( method != null ) {
|
||||
return new MethodAttributeAccess( attributeName, method );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//we could not find any match
|
||||
return new NoSuchAttributeAccess( specifiedClass, attributeName );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method with the specified name or <code>null</code> if it does not exist.
|
||||
*
|
||||
* @param clazz The class to check.
|
||||
* @param methodName The method name.
|
||||
* @param attributeName The attribute name.
|
||||
*
|
||||
* @return Returns the method with the specified name or <code>null</code> if it does not exist.
|
||||
*/
|
||||
private static Method getMethod(Class<?> clazz, String methodName) {
|
||||
private static Method getMethod(Class<?> clazz, String attributeName) {
|
||||
try {
|
||||
char string[] = methodName.toCharArray();
|
||||
char string[] = attributeName.toCharArray();
|
||||
string[0] = Character.toUpperCase( string[0] );
|
||||
methodName = new String( string );
|
||||
String casedAttributeName = new String( string );
|
||||
try {
|
||||
return clazz.getDeclaredMethod( "get" + methodName );
|
||||
return clazz.getDeclaredMethod( "get" + casedAttributeName );
|
||||
}
|
||||
catch ( NoSuchMethodException e ) {
|
||||
return clazz.getDeclaredMethod( "is" + methodName );
|
||||
return clazz.getDeclaredMethod( "is" + casedAttributeName );
|
||||
}
|
||||
}
|
||||
catch ( NoSuchMethodException e ) {
|
||||
|
@ -160,78 +384,20 @@ public class PersistenceUtilHelper {
|
|||
*/
|
||||
//TODO not really thread-safe
|
||||
public static class MetadataCache implements Serializable {
|
||||
private transient Map<Class<?>, ClassCache> classCache = new WeakHashMap<Class<?>, ClassCache>();
|
||||
private transient Map<Class<?>, ClassMetadataCache> classCache = new WeakHashMap<Class<?>, ClassMetadataCache>();
|
||||
|
||||
|
||||
private void readObject(java.io.ObjectInputStream stream) {
|
||||
classCache = new WeakHashMap<Class<?>, ClassCache>();
|
||||
classCache = new WeakHashMap<Class<?>, ClassMetadataCache>();
|
||||
}
|
||||
|
||||
Member getMember(Class<?> clazz, String property) {
|
||||
ClassCache cache = classCache.get( clazz );
|
||||
if (cache == null) {
|
||||
cache = new ClassCache(clazz);
|
||||
classCache.put( clazz, cache );
|
||||
}
|
||||
Member member = cache.members.get( property );
|
||||
if ( member == null ) {
|
||||
member = findMember( clazz, property );
|
||||
cache.members.put( property, member );
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
private Member findMember(Class<?> clazz, String property) {
|
||||
final List<Class<?>> classes = getClassHierarchy( clazz );
|
||||
|
||||
for (Class current : classes) {
|
||||
final Field field;
|
||||
try {
|
||||
field = current.getDeclaredField( property );
|
||||
setAccessibility( field );
|
||||
return field;
|
||||
}
|
||||
catch ( NoSuchFieldException e ) {
|
||||
final Method method = getMethod( current, property );
|
||||
if (method != null) {
|
||||
setAccessibility( method );
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
//we could not find any match
|
||||
throw new PersistenceException( "Unable to find field or method: "
|
||||
+ clazz + "#"
|
||||
+ property);
|
||||
}
|
||||
|
||||
private List<Class<?>> getClassHierarchy(Class<?> clazz) {
|
||||
ClassCache cache = classCache.get( clazz );
|
||||
if (cache == null) {
|
||||
cache = new ClassCache(clazz);
|
||||
classCache.put( clazz, cache );
|
||||
}
|
||||
return cache.classHierarchy;
|
||||
}
|
||||
|
||||
private static List<Class<?>> findClassHierarchy(Class<?> clazz) {
|
||||
List<Class<?>> classes = new ArrayList<Class<?>>();
|
||||
Class<?> current = clazz;
|
||||
do {
|
||||
classes.add( current );
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
while ( current != null );
|
||||
return classes;
|
||||
}
|
||||
|
||||
private static class ClassCache {
|
||||
List<Class<?>> classHierarchy;
|
||||
Map<String, Member> members = new HashMap<String, Member>();
|
||||
|
||||
public ClassCache(Class<?> clazz) {
|
||||
classHierarchy = findClassHierarchy( clazz );
|
||||
ClassMetadataCache getClassMetadata(Class<?> clazz) {
|
||||
ClassMetadataCache classMetadataCache = classCache.get( clazz );
|
||||
if ( classMetadataCache == null ) {
|
||||
classMetadataCache = new ClassMetadataCache( clazz );
|
||||
classCache.put( clazz, classMetadataCache );
|
||||
}
|
||||
return classMetadataCache;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue