From acea5236070648c25d8724c07fbe6ac7a1bb6365 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 20 May 2015 11:44:31 -0500 Subject: [PATCH] HHH-9806 - Bytecode-enhancement-based dirty tracking does not work because PersistentAttributeInterceptor is never injected --- .../internal/AttributeTypeDescriptor.java | 7 +- .../enhance/internal/EntityEnhancer.java | 181 ++++--- .../PersistentAttributesEnhancer.java | 302 ++++++++---- .../bytecode/enhance/spi/Enhancer.java | 22 +- .../engine/spi/SelfDirtinessTracker.java | 16 +- .../bytecode/enhancement/EnhancerTest.java | 18 +- .../enhancement/EnhancerTestUtils.java | 461 +++++++++--------- 7 files changed, 584 insertions(+), 423 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/AttributeTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/AttributeTypeDescriptor.java index 1bbc2ffe46..7210884e87 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/AttributeTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/AttributeTypeDescriptor.java @@ -37,11 +37,10 @@ public abstract class AttributeTypeDescriptor { return ""; } } - builder.append( String.format( "if (%s() != null", EnhancerConstants.INTERCEPTOR_GETTER_NAME ) ); // primitives || enums if ( currentValue.getType().isPrimitive() || currentValue.getType().isEnum() ) { - builder.append( String.format( " && %s != $1)", currentValue.getName()) ); + builder.append( String.format( "if (%s != $1)", currentValue.getName()) ); } // simple data types else if ( currentValue.getType().getName().startsWith( "java.lang" ) @@ -50,7 +49,7 @@ public abstract class AttributeTypeDescriptor { || currentValue.getType().getName().startsWith( "java.sql.Date" ) || currentValue.getType().getName().startsWith( "java.util.Date" ) || currentValue.getType().getName().startsWith( "java.util.Calendar" ) ) { - builder.append( String.format( " && ((%s == null) || (!% collectCollectionFields(CtClass managedCtClass) { final List collectionList = new LinkedList(); try { @@ -186,21 +206,30 @@ public class EntityEnhancer extends Enhancer { try { final StringBuilder body = new StringBuilder(); - body.append( String.format( "" + - "private boolean %1$s() {%n" + - " if (%2$s() == null || %3$s == null) { return false; }%n", - EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, - EnhancerConstants.INTERCEPTOR_GETTER_NAME, - EnhancerConstants.TRACKER_COLLECTION_NAME ) ); + body.append( + String.format( + "" + + "private boolean %1$s() {%n" + + " if (%2$s == null) {%n" + + " return false;%n" + + " }%n", + EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, + EnhancerConstants.TRACKER_COLLECTION_NAME + ) + ); for ( CtField ctField : collectCollectionFields( managedCtClass ) ) { - if ( !enhancementContext.isMappedCollection( ctField )) { - body.append( String.format( "" + - " // collection field [%1$s]%n" + - " if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { return true; }%n"+ - " if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n", - ctField.getName(), - EnhancerConstants.TRACKER_COLLECTION_NAME ) ); + if ( !enhancementContext.isMappedCollection( ctField ) ) { + body.append( + String.format( + "" + + " // collection field [%1$s]%n" + + " if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { return true; }%n" + + " if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n", + ctField.getName(), + EnhancerConstants.TRACKER_COLLECTION_NAME + ) + ); } } body.append( " return false;%n}" ); @@ -216,21 +245,29 @@ public class EntityEnhancer extends Enhancer { try { final StringBuilder body = new StringBuilder(); - body.append( String.format( "" + - "private void %1$s(%3$s tracker) {%n" + - " if (%2$s == null) { return; }%n", - EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, - EnhancerConstants.TRACKER_COLLECTION_NAME, - TRACKER_IMPL ) ); + body.append( + String.format( + "" + + "private void %1$s(%3$s tracker) {%n" + + " if (%2$s == null) { return; }%n", + EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, + EnhancerConstants.TRACKER_COLLECTION_NAME, + TRACKER_IMPL + ) + ); for ( CtField ctField : collectCollectionFields( managedCtClass ) ) { - if ( !enhancementContext.isMappedCollection( ctField )) { - body.append( String.format( "" + - " // Collection field [%1$s]%n" + - " if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { tracker.add(\"%1$s\"); }%n"+ - " if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { tracker.add(\"%1$s\"); }%n", - ctField.getName(), - EnhancerConstants.TRACKER_COLLECTION_NAME ) ); + if ( !enhancementContext.isMappedCollection( ctField ) ) { + body.append( + String.format( + "" + + " // Collection field [%1$s]%n" + + " if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { tracker.add(\"%1$s\"); }%n" + + " if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { tracker.add(\"%1$s\"); }%n", + ctField.getName(), + EnhancerConstants.TRACKER_COLLECTION_NAME + ) + ); } } body.append( "}" ); @@ -246,21 +283,29 @@ public class EntityEnhancer extends Enhancer { try { final StringBuilder body = new StringBuilder(); - body.append( String.format( "" + - "private void %1$s() {%n" + - " if (%2$s == null) { %2$s = new %3$s(); }%n", - EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, - EnhancerConstants.TRACKER_COLLECTION_NAME, - CollectionTracker.class.getName()) ); + body.append( + String.format( + "" + + "private void %1$s() {%n" + + " if (%2$s == null) { %2$s = new %3$s(); }%n", + EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, + EnhancerConstants.TRACKER_COLLECTION_NAME, + CollectionTracker.class.getName() + ) + ); for ( CtField ctField : collectCollectionFields( managedCtClass ) ) { if ( !enhancementContext.isMappedCollection( ctField ) ) { - body.append( String.format( "" + - " // Collection field [%1$s]%n" + - " if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%n"+ - " else { %2$s.add(\"%1$s\", %1$s.size()); }%n", - ctField.getName(), - EnhancerConstants.TRACKER_COLLECTION_NAME) ); + body.append( + String.format( + "" + + " // Collection field [%1$s]%n" + + " if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%n" + + " else { %2$s.add(\"%1$s\", %1$s.size()); }%n", + ctField.getName(), + EnhancerConstants.TRACKER_COLLECTION_NAME + ) + ); } } body.append( "}" ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/PersistentAttributesEnhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/PersistentAttributesEnhancer.java index 0e95e13647..3264806391 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/PersistentAttributesEnhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/PersistentAttributesEnhancer.java @@ -19,6 +19,7 @@ import javassist.bytecode.MethodInfo; import javassist.bytecode.Opcode; import javassist.bytecode.SignatureAttribute; import javassist.bytecode.stackmap.MapMaker; + import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.Enhancer; @@ -54,16 +55,18 @@ public class PersistentAttributesEnhancer extends Enhancer { final IdentityHashMap attrDescriptorMap = new IdentityHashMap(); for ( CtField persistentField : collectPersistentFields( managedCtClass ) ) { - attrDescriptorMap.put( persistentField.getName(), enhancePersistentAttribute( managedCtClass, persistentField ) ); + attrDescriptorMap.put( + persistentField.getName(), enhancePersistentAttribute( + managedCtClass, + persistentField + ) + ); } // lastly, find all references to the transformed fields and replace with calls to the added reader/writer methods enhanceAttributesAccess( managedCtClass, attrDescriptorMap ); } - /* --- */ - - // TODO: drive this from the Hibernate metamodel instance... private CtField[] collectPersistentFields(CtClass managedCtClass) { final List persistentFieldList = new LinkedList(); for ( CtField ctField : managedCtClass.getDeclaredFields() ) { @@ -75,25 +78,33 @@ public class PersistentAttributesEnhancer extends Enhancer { persistentFieldList.add( ctField ); } } - return enhancementContext.order( persistentFieldList.toArray( new CtField[ persistentFieldList.size() ] ) ); + return enhancementContext.order( persistentFieldList.toArray( new CtField[persistentFieldList.size()] ) ); } - /* --- */ - - private PersistentAttributeAccessMethods enhancePersistentAttribute(CtClass managedCtClass, CtField persistentField) { + private PersistentAttributeAccessMethods enhancePersistentAttribute( + CtClass managedCtClass, + CtField persistentField) { try { final AttributeTypeDescriptor typeDescriptor = AttributeTypeDescriptor.resolve( persistentField ); return new PersistentAttributeAccessMethods( generateFieldReader( managedCtClass, persistentField, typeDescriptor ), - generateFieldWriter( managedCtClass, persistentField, typeDescriptor ) ); + generateFieldWriter( managedCtClass, persistentField, typeDescriptor ) + ); } catch (Exception e) { - final String msg = String.format( "Unable to enhance persistent attribute [%s:%s]", managedCtClass.getName(), persistentField.getName() ); + final String msg = String.format( + "Unable to enhance persistent attribute [%s:%s]", + managedCtClass.getName(), + persistentField.getName() + ); throw new EnhancementException( msg, e ); } } - private CtMethod generateFieldReader(CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) { + private CtMethod generateFieldReader( + CtClass managedCtClass, + CtField persistentField, + AttributeTypeDescriptor typeDescriptor) { final String fieldName = persistentField.getName(); final String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName; @@ -105,23 +116,36 @@ public class PersistentAttributesEnhancer extends Enhancer { // TODO: temporary solution... try { - return MethodWriter.write( managedCtClass, "public %s %s() {%n %s%n return this.%s;%n}", + return MethodWriter.write( + managedCtClass, "public %s %s() {%n %s%n return this.%s;%n}", persistentField.getType().getName(), readerName, typeDescriptor.buildReadInterceptionBodyFragment( fieldName ), - fieldName); + fieldName + ); } catch (CannotCompileException cce) { - final String msg = String.format( "Could not enhance entity class [%s] to add field reader method [%s]", managedCtClass.getName(), readerName ); + final String msg = String.format( + "Could not enhance entity class [%s] to add field reader method [%s]", + managedCtClass.getName(), + readerName + ); throw new EnhancementException( msg, cce ); } catch (NotFoundException nfe) { - final String msg = String.format( "Could not enhance entity class [%s] to add field reader method [%s]", managedCtClass.getName(), readerName ); + final String msg = String.format( + "Could not enhance entity class [%s] to add field reader method [%s]", + managedCtClass.getName(), + readerName + ); throw new EnhancementException( msg, nfe ); } } - private CtMethod generateFieldWriter(CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) { + private CtMethod generateFieldWriter( + CtClass managedCtClass, + CtField persistentField, + AttributeTypeDescriptor typeDescriptor) { final String fieldName = persistentField.getName(); final String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName; @@ -132,20 +156,32 @@ public class PersistentAttributesEnhancer extends Enhancer { writer = MethodWriter.addSetter( managedCtClass, fieldName, writerName ); } else { - writer = MethodWriter.write( managedCtClass, "public void %s(%s %s) {%n %s%n}", + writer = MethodWriter.write( + managedCtClass, + "public void %s(%s %s) {%n %s%n}", writerName, persistentField.getType().getName(), fieldName, - typeDescriptor.buildWriteInterceptionBodyFragment( fieldName ) ); + typeDescriptor.buildWriteInterceptionBodyFragment( fieldName ) + ); } if ( enhancementContext.isCompositeClass( managedCtClass ) ) { - writer.insertBefore( String.format( "if (%s != null) { % targetClass = null; - if (persistentField.hasAnnotation( OneToOne.class )) { - targetClass = ((OneToOne) persistentField.getAnnotation( OneToOne.class )).targetEntity(); + if ( persistentField.hasAnnotation( OneToOne.class ) ) { + targetClass = ( (OneToOne) persistentField.getAnnotation( OneToOne.class ) ).targetEntity(); } - if (persistentField.hasAnnotation( OneToMany.class )) { - targetClass = ((OneToMany) persistentField.getAnnotation( OneToMany.class )).targetEntity(); + if ( persistentField.hasAnnotation( OneToMany.class ) ) { + targetClass = ( (OneToMany) persistentField.getAnnotation( OneToMany.class ) ).targetEntity(); } - if (persistentField.hasAnnotation( ManyToOne.class )) { - targetClass = ((ManyToOne) persistentField.getAnnotation( ManyToOne.class )).targetEntity(); + if ( persistentField.hasAnnotation( ManyToOne.class ) ) { + targetClass = ( (ManyToOne) persistentField.getAnnotation( ManyToOne.class ) ).targetEntity(); } - if (persistentField.hasAnnotation( ManyToMany.class )) { - targetClass = ((ManyToMany) persistentField.getAnnotation( ManyToMany.class )).targetEntity(); + if ( persistentField.hasAnnotation( ManyToMany.class ) ) { + targetClass = ( (ManyToMany) persistentField.getAnnotation( ManyToMany.class ) ).targetEntity(); } if ( targetClass != null && targetClass != void.class ) { return classPool.get( targetClass.getName() ); @@ -295,12 +384,14 @@ public class PersistentAttributesEnhancer extends Enhancer { } // infer targetEntity from generic type signature - if ( persistentField.hasAnnotation( OneToOne.class ) || persistentField.hasAnnotation( ManyToOne.class )) { + if ( persistentField.hasAnnotation( OneToOne.class ) || persistentField.hasAnnotation( ManyToOne.class ) ) { return persistentField.getType(); } - if ( persistentField.hasAnnotation( OneToMany.class ) || persistentField.hasAnnotation( ManyToMany.class )) { + if ( persistentField.hasAnnotation( OneToMany.class ) || persistentField.hasAnnotation( ManyToMany.class ) ) { try { - final SignatureAttribute.TypeArgument target = ((SignatureAttribute.ClassType) SignatureAttribute.toFieldSignature( persistentField.getGenericSignature() )).getTypeArguments()[0]; + final SignatureAttribute.TypeArgument target = ( (SignatureAttribute.ClassType) SignatureAttribute.toFieldSignature( + persistentField.getGenericSignature() + ) ).getTypeArguments()[0]; return persistentField.getDeclaringClass().getClassPool().get( target.toString() ); } catch (BadBytecode ignore) { @@ -309,9 +400,8 @@ public class PersistentAttributesEnhancer extends Enhancer { return null; } - /* --- */ - - private void handleCompositeField(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter) throws NotFoundException, CannotCompileException { + private void handleCompositeField(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter) + throws NotFoundException, CannotCompileException { if ( !persistentField.hasAnnotation( Embedded.class ) ) { return; } @@ -321,34 +411,44 @@ public class PersistentAttributesEnhancer extends Enhancer { if ( enhancementContext.isCompositeClass( managedCtClass ) ) { // if a composite have a embedded field we need to implement the TRACKER_CHANGER_NAME method as well - MethodWriter.write( managedCtClass, "" + + MethodWriter.write( + managedCtClass, "" + "public void %1$s(String name) {%n" + " if (%2$s != null) { %2$s.callOwner(\".\" + name) ; }%n}", EnhancerConstants.TRACKER_CHANGER_NAME, - EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME ); + EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME + ); } // cleanup previous owner - fieldWriter.insertBefore( String.format( "" + - "if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n", - persistentField.getName(), - CompositeTracker.class.getName(), - EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER) ); + fieldWriter.insertBefore( + String.format( + "" + + "if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n", + persistentField.getName(), + CompositeTracker.class.getName(), + EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER + ) + ); // trigger track changes - fieldWriter.insertAfter( String.format( "" + - "((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this);%n" + - "%5$s(\"%1$s\");", - persistentField.getName(), - CompositeTracker.class.getName(), - CompositeOwner.class.getName(), - EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER, - EnhancerConstants.TRACKER_CHANGER_NAME ) ); + fieldWriter.insertAfter( + String.format( + "" + + "((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this);%n" + + "%5$s(\"%1$s\");", + persistentField.getName(), + CompositeTracker.class.getName(), + CompositeOwner.class.getName(), + EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER, + EnhancerConstants.TRACKER_CHANGER_NAME + ) + ); } - /* --- */ - - protected void enhanceAttributesAccess(CtClass managedCtClass, IdentityHashMap attributeDescriptorMap) { + protected void enhanceAttributesAccess( + CtClass managedCtClass, + IdentityHashMap attributeDescriptorMap) { final ConstPool constPool = managedCtClass.getClassFile().getConstPool(); for ( Object oMethod : managedCtClass.getClassFile().getMethods() ) { @@ -392,16 +492,16 @@ public class PersistentAttributesEnhancer extends Enhancer { methodInfo.getCodeAttribute().setAttribute( MapMaker.make( classPool, methodInfo ) ); } catch (BadBytecode bb) { - final String msg = String.format( "Unable to perform field access transformation in method [%s]", methodName ); + final String msg = String.format( + "Unable to perform field access transformation in method [%s]", + methodName + ); throw new EnhancementException( msg, bb ); } } } - /* --- */ - private static class PersistentAttributeAccessMethods { - private final CtMethod reader; private final CtMethod writer; diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java index ee92903a61..d777803b2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java @@ -96,16 +96,6 @@ public class Enhancer { } } - /** - * @deprecated Should use enhance(String, byte[]) and a proper EnhancementContext - */ - @Deprecated( ) - public byte[] enhanceComposite(String className, byte[] originalBytes) throws EnhancementException { - return enhance( className, originalBytes ); - } - - /* --- */ - private ClassPool buildClassPool(EnhancementContext enhancementContext) { final ClassPool classPool = new ClassPool( false ); final ClassLoader loadingClassLoader = enhancementContext.getLoadingClassLoader(); @@ -123,8 +113,6 @@ public class Enhancer { return aClass.getName().replace( '.', File.separatorChar ) + JavaFileObject.Kind.CLASS.extension; } - /* --- */ - private void enhance(CtClass managedCtClass) { // can't effectively enhance interfaces if ( managedCtClass.isInterface() ) { @@ -172,8 +160,6 @@ public class Enhancer { } } - /* --- */ - protected void addInterceptorHandling(CtClass managedCtClass) { // interceptor handling is only needed if either: // a) in-line dirty checking has *not* been requested @@ -191,4 +177,12 @@ public class Enhancer { EnhancerConstants.INTERCEPTOR_SETTER_NAME ); } + + /** + * @deprecated Should use enhance(String, byte[]) and a proper EnhancementContext + */ + @Deprecated( ) + public byte[] enhanceComposite(String className, byte[] originalBytes) throws EnhancementException { + return enhance( className, originalBytes ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SelfDirtinessTracker.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SelfDirtinessTracker.java index 8deda53a35..688e30b8fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SelfDirtinessTracker.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SelfDirtinessTracker.java @@ -9,18 +9,28 @@ package org.hibernate.engine.spi; import java.util.Set; /** - * Specify if an entity class is instrumented to track field changes + * Contract for an entity to report that it tracks the dirtiness of its own state, + * as opposed to needing Hibernate to perform state-diff dirty calculations. + *

+ * Entity classes are free to implement this contract themselves. This contract is + * also introduced into the entity when using bytecode enhancement and requesting + * that entities track there own dirtiness. * * @author Ståle W. Pedersen */ public interface SelfDirtinessTracker { /** - * Return true if any fields has been changed + * Have any of the entity's persistent attributes changed? + * + * @return {@code true} indicates one or more persistent attributes have changed; {@code false} + * indicates none have changed. */ boolean $$_hibernate_hasDirtyAttributes(); /** - * Get the field names of all the fields thats been changed + * Retrieve the names of all the persistent attributes whose values have changed. + * + * @return The set of changed persistent attribute names */ Set $$_hibernate_getDirtyAttributes(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java index 8e66aa8c75..781b314c86 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java @@ -15,7 +15,6 @@ import java.util.Set; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.ManagedEntity; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.test.bytecode.enhancement.entity.Address; @@ -67,19 +66,9 @@ public class EnhancerTest extends BaseUnitTestCase { Method nextGetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_GETTER_NAME); Method nextSetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class); - nextSetter.invoke(entityInstance, entityInstance); - assertSame(entityInstance, nextGetter.invoke(entityInstance)); + nextSetter.invoke( entityInstance, entityInstance ); + assertSame( entityInstance, nextGetter.invoke( entityInstance ) ); - // add an attribute interceptor... - Method interceptorGetter = entityClass.getMethod(EnhancerConstants.INTERCEPTOR_GETTER_NAME); - assertNull(interceptorGetter.invoke(entityInstance)); - entityClass.getMethod("getId").invoke(entityInstance); - - Method interceptorSetter = entityClass.getMethod(EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class); - interceptorSetter.invoke(entityInstance, new EnhancerTestUtils.LocalPersistentAttributeInterceptor()); - assertNotNull(interceptorGetter.invoke(entityInstance)); - - // dirty checking is unfortunately just printlns for now... just verify the test output entityClass.getMethod("getId").invoke(entityInstance); entityClass.getMethod("setId", Long.class).invoke(entityInstance, entityClass.getMethod("getId").invoke(entityInstance)); entityClass.getMethod("setId", Long.class).invoke(entityInstance, 1L); @@ -109,9 +98,6 @@ public class EnhancerTest extends BaseUnitTestCase { entityClass = EnhancerTestUtils.enhanceAndDecompile(entityClassToEnhance, cl); entityInstance = entityClass.newInstance(); - interceptorSetter = entityClass.getMethod(EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class); - interceptorSetter.invoke(entityInstance, new EnhancerTestUtils.LocalPersistentAttributeInterceptor()); - List strings = new ArrayList(); strings.add("FooBar"); entityClass.getMethod("setSomeStrings", List.class).invoke(entityInstance, strings); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTestUtils.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTestUtils.java index 9acacaa9d6..8b830d9cfc 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTestUtils.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTestUtils.java @@ -60,252 +60,279 @@ public abstract class EnhancerTestUtils extends BaseUnitTestCase { private static EnhancementContext enhancementContext = new DefaultEnhancementContext(); - private static String workingDir = System.getProperty("java.io.tmpdir"); + private static String workingDir = System.getProperty( "java.io.tmpdir" ); - private static final CoreMessageLogger log = CoreLogging.messageLogger(EnhancerTestUtils.class); + private static final CoreMessageLogger log = CoreLogging.messageLogger( EnhancerTestUtils.class ); - /** - * method that performs the enhancement of a class - * also checks the signature of enhanced entities methods using 'javap' decompiler - */ - static Class enhanceAndDecompile(Class classToEnhance, ClassLoader cl) throws Exception { - CtClass entityCtClass = generateCtClassForAnEntity(classToEnhance); + /** + * method that performs the enhancement of a class + * also checks the signature of enhanced entities methods using 'javap' decompiler + */ + static Class enhanceAndDecompile(Class classToEnhance, ClassLoader cl) throws Exception { + CtClass entityCtClass = generateCtClassForAnEntity( classToEnhance ); - byte[] original = entityCtClass.toBytecode(); - byte[] enhanced = new Enhancer(enhancementContext).enhance(entityCtClass.getName(), original); - assertFalse("entity was not enhanced", Arrays.equals(original, enhanced)); - log.infof("enhanced entity [%s]", entityCtClass.getName()); + byte[] original = entityCtClass.toBytecode(); + byte[] enhanced = new Enhancer( enhancementContext ).enhance( entityCtClass.getName(), original ); + assertFalse( "entity was not enhanced", Arrays.equals( original, enhanced ) ); + log.infof( "enhanced entity [%s]", entityCtClass.getName() ); - ClassPool cp = new ClassPool(false); - cp.appendClassPath(new LoaderClassPath(cl)); - CtClass enhancedCtClass = cp.makeClass(new ByteArrayInputStream(enhanced)); + ClassPool cp = new ClassPool( false ); + cp.appendClassPath( new LoaderClassPath( cl ) ); + CtClass enhancedCtClass = cp.makeClass( new ByteArrayInputStream( enhanced ) ); - enhancedCtClass.debugWriteFile(workingDir); - decompileDumpedClass(classToEnhance.getName()); + enhancedCtClass.debugWriteFile( workingDir ); + decompileDumpedClass( classToEnhance.getName() ); - Class enhancedClass = enhancedCtClass.toClass(cl, EnhancerTestUtils.class.getProtectionDomain()); - assertNotNull(enhancedClass); - return enhancedClass; - } + Class enhancedClass = enhancedCtClass.toClass( cl, EnhancerTestUtils.class.getProtectionDomain() ); + assertNotNull( enhancedClass ); + return enhancedClass; + } - private static void decompileDumpedClass(String className) { - try { - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); - fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singletonList(new File(workingDir))); + private static void decompileDumpedClass(String className) { + try { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); + fileManager.setLocation( + StandardLocation.CLASS_OUTPUT, + Collections.singletonList( new File( workingDir ) ) + ); - JavapTask javapTask = new JavapTask(); - for (JavaFileObject jfo : fileManager.getJavaFileObjects(workingDir + File.separator + getFilenameForClassName(className))) { - try { - Set interfaceNames = new HashSet(); - Set fieldNames = new HashSet(); - Set methodNames = new HashSet(); + JavapTask javapTask = new JavapTask(); + for ( JavaFileObject jfo : fileManager.getJavaFileObjects( + workingDir + File.separator + getFilenameForClassName( + className + ) + ) ) { + try { + Set interfaceNames = new HashSet(); + Set fieldNames = new HashSet(); + Set methodNames = new HashSet(); - JavapTask.ClassFileInfo info = javapTask.read(jfo); + JavapTask.ClassFileInfo info = javapTask.read( jfo ); - log.infof("decompiled class [%s]", info.cf.getName()); + log.infof( "decompiled class [%s]", info.cf.getName() ); - for (int i : info.cf.interfaces) { - interfaceNames.add(info.cf.constant_pool.getClassInfo(i).getName()); - log.debugf("declared iFace = ", info.cf.constant_pool.getClassInfo(i).getName()); - } - for (com.sun.tools.classfile.Field f : info.cf.fields) { - fieldNames.add(f.getName(info.cf.constant_pool)); - log.debugf("declared field = ", f.getName(info.cf.constant_pool)); - } - for (com.sun.tools.classfile.Method m : info.cf.methods) { - methodNames.add(m.getName(info.cf.constant_pool)); - log.debugf("declared method = ", m.getName(info.cf.constant_pool)); - } + for ( int i : info.cf.interfaces ) { + interfaceNames.add( info.cf.constant_pool.getClassInfo( i ).getName() ); + log.debugf( "declared iFace = ", info.cf.constant_pool.getClassInfo( i ).getName() ); + } + for ( com.sun.tools.classfile.Field f : info.cf.fields ) { + fieldNames.add( f.getName( info.cf.constant_pool ) ); + log.debugf( "declared field = ", f.getName( info.cf.constant_pool ) ); + } + for ( com.sun.tools.classfile.Method m : info.cf.methods ) { + methodNames.add( m.getName( info.cf.constant_pool ) ); + log.debugf( "declared method = ", m.getName( info.cf.constant_pool ) ); + } - // checks signature against known interfaces - if (interfaceNames.contains(PersistentAttributeInterceptor.class.getName())) { - assertTrue(fieldNames.contains(EnhancerConstants.INTERCEPTOR_FIELD_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.INTERCEPTOR_GETTER_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.INTERCEPTOR_SETTER_NAME)); - } - if (interfaceNames.contains(ManagedEntity.class.getName())) { - assertTrue(methodNames.contains(EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME)); + // checks signature against known interfaces + if ( interfaceNames.contains( PersistentAttributeInterceptor.class.getName() ) ) { + assertTrue( fieldNames.contains( EnhancerConstants.INTERCEPTOR_FIELD_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.INTERCEPTOR_GETTER_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.INTERCEPTOR_SETTER_NAME ) ); + } + if ( interfaceNames.contains( ManagedEntity.class.getName() ) ) { + assertTrue( methodNames.contains( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME ) ); - assertTrue(fieldNames.contains(EnhancerConstants.ENTITY_ENTRY_FIELD_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.ENTITY_ENTRY_GETTER_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.ENTITY_ENTRY_SETTER_NAME)); + assertTrue( fieldNames.contains( EnhancerConstants.ENTITY_ENTRY_FIELD_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.ENTITY_ENTRY_GETTER_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.ENTITY_ENTRY_SETTER_NAME ) ); - assertTrue(fieldNames.contains(EnhancerConstants.PREVIOUS_FIELD_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.PREVIOUS_GETTER_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.PREVIOUS_SETTER_NAME)); + assertTrue( fieldNames.contains( EnhancerConstants.PREVIOUS_FIELD_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.PREVIOUS_GETTER_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.PREVIOUS_SETTER_NAME ) ); - assertTrue(fieldNames.contains(EnhancerConstants.NEXT_FIELD_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.NEXT_GETTER_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.NEXT_SETTER_NAME)); - } - if (interfaceNames.contains(SelfDirtinessTracker.class.getName())) { - assertTrue(fieldNames.contains(EnhancerConstants.TRACKER_FIELD_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.TRACKER_GET_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.TRACKER_CLEAR_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.TRACKER_HAS_CHANGED_NAME)); - } - if (interfaceNames.contains(CompositeTracker.class.getName())) { - assertTrue(fieldNames.contains(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER)); - assertTrue(methodNames.contains(EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER)); - } - if (interfaceNames.contains(CompositeOwner.class.getName())) { - assertTrue(fieldNames.contains(EnhancerConstants.TRACKER_CHANGER_NAME)); - assertTrue(methodNames.contains(EnhancerConstants.TRACKER_CHANGER_NAME)); - } - } catch (ConstantPoolException e) { - e.printStackTrace(); - } - } - } catch (IOException ioe) { - assertNull("Failed to open class file", ioe); - } catch (RuntimeException re) { - log.warnf(re, "WARNING: UNABLE DECOMPILE DUE TO %s", re.getMessage()); - } - } + assertTrue( fieldNames.contains( EnhancerConstants.NEXT_FIELD_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.NEXT_GETTER_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.NEXT_SETTER_NAME ) ); + } + if ( interfaceNames.contains( SelfDirtinessTracker.class.getName() ) ) { + assertTrue( fieldNames.contains( EnhancerConstants.TRACKER_FIELD_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.TRACKER_GET_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.TRACKER_CLEAR_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.TRACKER_HAS_CHANGED_NAME ) ); + } + if ( interfaceNames.contains( CompositeTracker.class.getName() ) ) { + assertTrue( fieldNames.contains( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER ) ); + assertTrue( methodNames.contains( EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER ) ); + } + if ( interfaceNames.contains( CompositeOwner.class.getName() ) ) { + assertTrue( fieldNames.contains( EnhancerConstants.TRACKER_CHANGER_NAME ) ); + assertTrue( methodNames.contains( EnhancerConstants.TRACKER_CHANGER_NAME ) ); + } + } + catch (ConstantPoolException e) { + e.printStackTrace(); + } + } + } + catch (IOException ioe) { + assertNull( "Failed to open class file", ioe ); + } + catch (RuntimeException re) { + log.warnf( re, "WARNING: UNABLE DECOMPILE DUE TO %s", re.getMessage() ); + } + } - private static CtClass generateCtClassForAnEntity(Class entityClassToEnhance) throws Exception { - ClassPool cp = new ClassPool(false); - return cp.makeClass(EnhancerTestUtils.class.getClassLoader().getResourceAsStream(getFilenameForClassName(entityClassToEnhance.getName()))); - } + private static CtClass generateCtClassForAnEntity(Class entityClassToEnhance) throws Exception { + ClassPool cp = new ClassPool( false ); + return cp.makeClass( + EnhancerTestUtils.class.getClassLoader().getResourceAsStream( + getFilenameForClassName( + entityClassToEnhance.getName() + ) + ) + ); + } - private static String getFilenameForClassName(String className) { - return className.replace('.', File.separatorChar) + JavaFileObject.Kind.CLASS.extension; - } + private static String getFilenameForClassName(String className) { + return className.replace( '.', File.separatorChar ) + JavaFileObject.Kind.CLASS.extension; + } - /** - * clears the dirty set for an entity - */ - public static void clearDirtyTracking (Object entityInstance) { - try { - entityInstance.getClass().getMethod(EnhancerConstants.TRACKER_CLEAR_NAME).invoke(entityInstance); - checkDirtyTracking(entityInstance); - } catch (InvocationTargetException e) { - assertNull("Exception in clear dirty tracking", e); - } catch (NoSuchMethodException e) { - assertNull("Exception in clear dirty tracking", e); - } catch (IllegalAccessException e) { - assertNull("Exception in clear dirty tracking", e); - } - } + /** + * clears the dirty set for an entity + */ + public static void clearDirtyTracking(Object entityInstance) { + ( (SelfDirtinessTracker) entityInstance ).$$_hibernate_clearDirtyAttributes(); + } - /** - * compares the dirty fields of an entity with a set of expected values - */ - public static void checkDirtyTracking (Object entityInstance, String ... dirtyFields) { - try { - assertTrue((dirtyFields.length == 0) != (Boolean) entityInstance.getClass().getMethod(EnhancerConstants.TRACKER_HAS_CHANGED_NAME).invoke(entityInstance)); - Set tracked = (Set) entityInstance.getClass().getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance); - assertEquals(dirtyFields.length, tracked.size()); - assertTrue(tracked.containsAll(Arrays.asList(dirtyFields))); - } catch (InvocationTargetException e) { - assertNull("Exception while checking dirty tracking", e); - } catch (NoSuchMethodException e) { - assertNull("Exception while checking dirty tracking", e); - } catch (IllegalAccessException e) { - assertNull("Exception while checking dirty tracking", e); - } - } + /** + * compares the dirty fields of an entity with a set of expected values + */ + public static void checkDirtyTracking(Object entityInstance, String... dirtyFields) { + final SelfDirtinessTracker selfDirtinessTracker = (SelfDirtinessTracker) entityInstance; + assertEquals( dirtyFields.length > 0, selfDirtinessTracker.$$_hibernate_hasDirtyAttributes() ); + Set tracked = selfDirtinessTracker.$$_hibernate_getDirtyAttributes(); + assertEquals( dirtyFields.length, tracked.size() ); + assertTrue( tracked.containsAll( Arrays.asList( dirtyFields ) ) ); + } - static EntityEntry makeEntityEntry() { - return MutableEntityEntryFactory.INSTANCE.createEntityEntry( - Status.MANAGED, - null, - null, - 1, - null, - LockMode.NONE, - false, - null, - false, - false, - null - ); - } + static EntityEntry makeEntityEntry() { + return MutableEntityEntryFactory.INSTANCE.createEntityEntry( + Status.MANAGED, + null, + null, + 1, + null, + LockMode.NONE, + false, + null, + false, + false, + null + ); + } - public static class LocalPersistentAttributeInterceptor implements PersistentAttributeInterceptor { + public static class LocalPersistentAttributeInterceptor implements PersistentAttributeInterceptor { - @Override public boolean readBoolean(Object obj, String name, boolean oldValue) { - log.infof( "Reading boolean [%s]" , name ); - return oldValue; - } - @Override public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { - log.infof( "Writing boolean []", name ); - return newValue; - } + @Override + public boolean readBoolean(Object obj, String name, boolean oldValue) { + log.infof( "Reading boolean [%s]", name ); + return oldValue; + } - @Override public byte readByte(Object obj, String name, byte oldValue) { - log.infof( "Reading byte [%s]", name ); - return oldValue; - } - @Override public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { - log.infof( "Writing byte [%s]", name ); - return newValue; - } + @Override + public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { + log.infof( "Writing boolean []", name ); + return newValue; + } - @Override public char readChar(Object obj, String name, char oldValue) { - log.infof( "Reading char [%s]", name ); - return oldValue; - } - @Override public char writeChar(Object obj, String name, char oldValue, char newValue) { - log.infof( "Writing char [%s]", name ); - return newValue; - } + @Override + public byte readByte(Object obj, String name, byte oldValue) { + log.infof( "Reading byte [%s]", name ); + return oldValue; + } - @Override public short readShort(Object obj, String name, short oldValue) { - log.infof( "Reading short [%s]", name ); - return oldValue; - } - @Override public short writeShort(Object obj, String name, short oldValue, short newValue) { - log.infof( "Writing short [%s]", name ); - return newValue; - } + @Override + public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { + log.infof( "Writing byte [%s]", name ); + return newValue; + } - @Override public int readInt(Object obj, String name, int oldValue) { - log.infof( "Reading int [%s]", name ); - return oldValue; - } - @Override public int writeInt(Object obj, String name, int oldValue, int newValue) { - log.infof( "Writing int [%s]", name ); - return newValue; - } + @Override + public char readChar(Object obj, String name, char oldValue) { + log.infof( "Reading char [%s]", name ); + return oldValue; + } - @Override public float readFloat(Object obj, String name, float oldValue) { - log.infof( "Reading float [%s]", name ); - return oldValue; - } - @Override public float writeFloat(Object obj, String name, float oldValue, float newValue) { - log.infof( "Writing float [%s]", name ); - return newValue; - } + @Override + public char writeChar(Object obj, String name, char oldValue, char newValue) { + log.infof( "Writing char [%s]", name ); + return newValue; + } - @Override public double readDouble(Object obj, String name, double oldValue) { - log.infof( "Reading double [%s]", name ); - return oldValue; - } - @Override public double writeDouble(Object obj, String name, double oldValue, double newValue) { - log.infof( "Writing double [%s]", name ); - return newValue; - } + @Override + public short readShort(Object obj, String name, short oldValue) { + log.infof( "Reading short [%s]", name ); + return oldValue; + } - @Override public long readLong(Object obj, String name, long oldValue) { - log.infof( "Reading long [%s]", name ); - return oldValue; - } - @Override public long writeLong(Object obj, String name, long oldValue, long newValue) { - log.infof( "Writing long [%s]", name ); - return newValue; - } + @Override + public short writeShort(Object obj, String name, short oldValue, short newValue) { + log.infof( "Writing short [%s]", name ); + return newValue; + } - @Override public Object readObject(Object obj, String name, Object oldValue) { - log.infof( "Reading Object [%s]", name ); - return oldValue; - } - @Override public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { - log.infof( "Writing Object [%s]", name ); - return newValue; - } - } + @Override + public int readInt(Object obj, String name, int oldValue) { + log.infof( "Reading int [%s]", name ); + return oldValue; + } + + @Override + public int writeInt(Object obj, String name, int oldValue, int newValue) { + log.infof( "Writing int [%s]", name ); + return newValue; + } + + @Override + public float readFloat(Object obj, String name, float oldValue) { + log.infof( "Reading float [%s]", name ); + return oldValue; + } + + @Override + public float writeFloat(Object obj, String name, float oldValue, float newValue) { + log.infof( "Writing float [%s]", name ); + return newValue; + } + + @Override + public double readDouble(Object obj, String name, double oldValue) { + log.infof( "Reading double [%s]", name ); + return oldValue; + } + + @Override + public double writeDouble(Object obj, String name, double oldValue, double newValue) { + log.infof( "Writing double [%s]", name ); + return newValue; + } + + @Override + public long readLong(Object obj, String name, long oldValue) { + log.infof( "Reading long [%s]", name ); + return oldValue; + } + + @Override + public long writeLong(Object obj, String name, long oldValue, long newValue) { + log.infof( "Writing long [%s]", name ); + return newValue; + } + + @Override + public Object readObject(Object obj, String name, Object oldValue) { + log.infof( "Reading Object [%s]", name ); + return oldValue; + } + + @Override + public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { + log.infof( "Writing Object [%s]", name ); + return newValue; + } + } }