HHH-9938 - bytecode enhancer - field access enhancement feature

This commit is contained in:
barreiro 2015-07-21 03:24:47 +01:00 committed by Steve Ebersole
parent 6d77ac39c5
commit 417baffa13
13 changed files with 406 additions and 70 deletions

View File

@ -6,15 +6,17 @@
*/ */
package org.hibernate.bytecode.enhance.internal; package org.hibernate.bytecode.enhance.internal;
import java.util.Collection;
import java.util.Locale;
import javax.persistence.Id;
import javassist.CtClass; import javassist.CtClass;
import javassist.CtField; import javassist.CtField;
import javassist.NotFoundException; import javassist.NotFoundException;
import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
import org.hibernate.internal.util.compare.EqualsHelper;
import javax.persistence.Id;
import java.util.Collection;
import java.util.Locale;
/** /**
* utility class to generate interceptor methods * utility class to generate interceptor methods
@ -40,7 +42,7 @@ public abstract class AttributeTypeDescriptor {
// primitives || enums // primitives || enums
if ( currentValue.getType().isPrimitive() || currentValue.getType().isEnum() ) { if ( currentValue.getType().isPrimitive() || currentValue.getType().isEnum() ) {
builder.append( String.format( "if (%s != $1)", currentValue.getName()) ); builder.append( String.format( "if (%s != $1)", currentValue.getName() ) );
} }
// simple data types // simple data types
else if ( currentValue.getType().getName().startsWith( "java.lang" ) else if ( currentValue.getType().getName().startsWith( "java.lang" )
@ -62,10 +64,11 @@ public abstract class AttributeTypeDescriptor {
} }
} }
} }
// TODO: for now just call equals, should probably do something else here builder.append( String.format( "if (%1$s == null || !%2$s.equals(%1$s, $1))",
builder.append( String.format( "if (%s == null || !%<s.equals($1))", currentValue.getName() ) ); currentValue.getName(),
EqualsHelper.class.getName() ) );
} }
builder.append( String.format( " { %s(\"%s\"); }", EnhancerConstants.TRACKER_CHANGER_NAME, currentValue.getName() ) ); builder.append( String.format( " { %s(\"%s\"); }", EnhancerConstants.TRACKER_CHANGER_NAME, currentValue.getName() ) );
} }
catch (ClassNotFoundException e) { catch (ClassNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
@ -131,7 +134,7 @@ public abstract class AttributeTypeDescriptor {
"}", "}",
fieldName, fieldName,
type, type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME); EnhancerConstants.INTERCEPTOR_GETTER_NAME );
} }
public String buildWriteInterceptionBodyFragment(String fieldName) { public String buildWriteInterceptionBodyFragment(String fieldName) {
@ -143,7 +146,7 @@ public abstract class AttributeTypeDescriptor {
"this.%1$s = localVar;", "this.%1$s = localVar;",
fieldName, fieldName,
type, type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME); EnhancerConstants.INTERCEPTOR_GETTER_NAME );
} }
} }
@ -159,7 +162,7 @@ public abstract class AttributeTypeDescriptor {
throw new IllegalArgumentException( "Primitive attribute type descriptor can only be used on primitive types" ); throw new IllegalArgumentException( "Primitive attribute type descriptor can only be used on primitive types" );
} }
// capitalize first letter // capitalize first letter
this.type = primitiveType.getSimpleName().substring( 0, 1 ).toUpperCase(Locale.ROOT) + primitiveType.getSimpleName().substring( 1 ); this.type = primitiveType.getSimpleName().substring( 0, 1 ).toUpperCase( Locale.ROOT ) + primitiveType.getSimpleName().substring( 1 );
} }
public String buildReadInterceptionBodyFragment(String fieldName) { public String buildReadInterceptionBodyFragment(String fieldName) {
@ -180,7 +183,7 @@ public abstract class AttributeTypeDescriptor {
"}%n" + "}%n" +
"this.%1$s = localVar;", "this.%1$s = localVar;",
fieldName, fieldName,
type.toLowerCase(Locale.ROOT ), type.toLowerCase( Locale.ROOT ),
type, type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME EnhancerConstants.INTERCEPTOR_GETTER_NAME
); );

View File

@ -63,8 +63,13 @@ public class PersistentAttributesEnhancer extends Enhancer {
); );
} }
// lastly, find all references to the transformed fields and replace with calls to the added reader/writer methods // find all references to the transformed fields and replace with calls to the added reader/writer methods
enhanceAttributesAccess( managedCtClass, attrDescriptorMap ); enhanceAttributesAccess( managedCtClass, attrDescriptorMap );
// same thing for direct access to fields of other entities
if ( this.enhancementContext.doFieldAccessEnhancement( managedCtClass ) ) {
enhanceFieldAccess( managedCtClass );
}
} }
private CtField[] collectPersistentFields(CtClass managedCtClass) { private CtField[] collectPersistentFields(CtClass managedCtClass) {
@ -74,6 +79,10 @@ public class PersistentAttributesEnhancer extends Enhancer {
if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) { if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) {
continue; continue;
} }
// skip outer reference in inner classes
if ( "this$0".equals( ctField.getName() ) ) {
continue;
}
if ( enhancementContext.isPersistentField( ctField ) ) { if ( enhancementContext.isPersistentField( ctField ) ) {
persistentFieldList.add( ctField ); persistentFieldList.add( ctField );
} }
@ -114,7 +123,6 @@ public class PersistentAttributesEnhancer extends Enhancer {
return MethodWriter.addGetter( managedCtClass, fieldName, readerName ); return MethodWriter.addGetter( managedCtClass, fieldName, readerName );
} }
// TODO: temporary solution...
try { try {
return MethodWriter.write( return MethodWriter.write(
managedCtClass, "public %s %s() {%n %s%n return this.%s;%n}", managedCtClass, "public %s %s() {%n %s%n return this.%s;%n}",
@ -520,4 +528,84 @@ public class PersistentAttributesEnhancer extends Enhancer {
} }
} }
/**
* Replace access to fields of entities (for example, entity.field) with a call to the enhanced getter / setter
* (in this example, entity.$$_hibernate_read_field()). It's assumed that the target entity is enhanced as well.
*
* @param managedCtClass Class to enhance
*/
public void enhanceFieldAccess(CtClass managedCtClass) {
final ConstPool constPool = managedCtClass.getClassFile().getConstPool();
for ( Object oMethod : managedCtClass.getClassFile().getMethods() ) {
final MethodInfo methodInfo = (MethodInfo) oMethod;
final String methodName = methodInfo.getName();
// skip methods added by enhancement and abstract methods (methods without any code)
if ( methodName.startsWith( "$$_hibernate_" ) || methodInfo.getCodeAttribute() == null ) {
continue;
}
try {
final CodeIterator itr = methodInfo.getCodeAttribute().iterator();
while ( itr.hasNext() ) {
int index = itr.next();
int op = itr.byteAt( index );
if ( op != Opcode.PUTFIELD && op != Opcode.GETFIELD ) {
continue;
}
String fieldName = constPool.getFieldrefName( itr.u16bitAt( index + 1 ) );
String fieldClassName = constPool.getClassInfo( constPool.getFieldrefClass( itr.u16bitAt( index + 1 ) ) );
CtClass targetCtClass = this.classPool.getCtClass( fieldClassName );
if ( !enhancementContext.isEntityClass( targetCtClass ) && !enhancementContext.isCompositeClass( targetCtClass ) ) {
continue;
}
if ( targetCtClass == managedCtClass
|| !enhancementContext.isPersistentField( targetCtClass.getField( fieldName ) )
|| "this$0".equals( fieldName ) ) {
continue;
}
log.debugf( "Transforming access to field [%s] from method [%s]", fieldName, methodName );
if ( op == Opcode.GETFIELD ) {
int fieldReaderMethodIndex = constPool.addMethodrefInfo(
constPool.addClassInfo( fieldClassName ),
EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName,
"()" + constPool.getFieldrefType( itr.u16bitAt( index + 1 ) )
);
itr.writeByte( Opcode.INVOKEVIRTUAL, index );
itr.write16bit( fieldReaderMethodIndex, index + 1 );
}
else {
int fieldWriterMethodIndex = constPool.addMethodrefInfo(
constPool.addClassInfo( fieldClassName ),
EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName,
"(" + constPool.getFieldrefType( itr.u16bitAt( index + 1 ) ) + ")V"
);
itr.writeByte( Opcode.INVOKEVIRTUAL, index );
itr.write16bit( fieldWriterMethodIndex, index + 1 );
}
}
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
);
throw new EnhancementException( msg, bb );
}
catch (NotFoundException nfe) {
final String msg = String.format(
"Unable to perform field access transformation in method [%s]",
methodName
);
throw new EnhancementException( msg, nfe );
}
}
}
} }

View File

@ -6,9 +6,6 @@
*/ */
package org.hibernate.bytecode.enhance.spi; package org.hibernate.bytecode.enhance.spi;
import javassist.CtClass;
import javassist.CtField;
import javax.persistence.ElementCollection; import javax.persistence.ElementCollection;
import javax.persistence.Embeddable; import javax.persistence.Embeddable;
import javax.persistence.Entity; import javax.persistence.Entity;
@ -16,6 +13,9 @@ import javax.persistence.ManyToMany;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.Transient; import javax.persistence.Transient;
import javassist.CtClass;
import javassist.CtField;
/** /**
* default implementation of EnhancementContext. May be sub-classed as needed. * default implementation of EnhancementContext. May be sub-classed as needed.
* *
@ -58,6 +58,13 @@ public class DefaultEnhancementContext implements EnhancementContext {
return true; return true;
} }
/**
* @return false
*/
public boolean doFieldAccessEnhancement(CtClass classDescriptor) {
return false;
}
/** /**
* @return true * @return true
*/ */

View File

@ -73,6 +73,16 @@ public interface EnhancementContext {
*/ */
public boolean doDirtyCheckingInline(CtClass classDescriptor); public boolean doDirtyCheckingInline(CtClass classDescriptor);
/**
* Should we enhance field access to entities from this class?
*
* @param classDescriptor The descriptor of the class to check.
*
* @return {@code true} indicates that any direct access to fields of entities should be routed to the enhanced
* getter / setter method.
*/
public boolean doFieldAccessEnhancement(CtClass classDescriptor);
/** /**
* Does the given class define any lazy loadable attributes? * Does the given class define any lazy loadable attributes?
* *

View File

@ -20,6 +20,7 @@ import org.hibernate.HibernateException;
import org.hibernate.bytecode.enhance.internal.CompositeEnhancer; import org.hibernate.bytecode.enhance.internal.CompositeEnhancer;
import org.hibernate.bytecode.enhance.internal.EntityEnhancer; import org.hibernate.bytecode.enhance.internal.EntityEnhancer;
import org.hibernate.bytecode.enhance.internal.FieldWriter; import org.hibernate.bytecode.enhance.internal.FieldWriter;
import org.hibernate.bytecode.enhance.internal.PersistentAttributesEnhancer;
import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedComposite; import org.hibernate.engine.spi.ManagedComposite;
import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.ManagedEntity;
@ -143,6 +144,10 @@ public class Enhancer {
log.debugf( "Enhancing [%s] as Composite", managedCtClass.getName() ); log.debugf( "Enhancing [%s] as Composite", managedCtClass.getName() );
new CompositeEnhancer( enhancementContext ).enhance( managedCtClass ); new CompositeEnhancer( enhancementContext ).enhance( managedCtClass );
} }
else if ( enhancementContext.doFieldAccessEnhancement( managedCtClass ) ) {
log.debugf( "Enhancing field access in [%s]", managedCtClass.getName() );
new PersistentAttributesEnhancer( enhancementContext ).enhanceFieldAccess( managedCtClass );
}
else { else {
log.debug( "Skipping enhancement: not entity or composite" ); log.debug( "Skipping enhancement: not entity or composite" );
} }

View File

@ -6,25 +6,6 @@
*/ */
package org.hibernate.tool.enhance; package org.hibernate.tool.enhance;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.Enhancer;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -32,6 +13,25 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.Enhancer;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
/** /**
* Ant task for performing build-time enhancement of entities and component/embeddable classes. * Ant task for performing build-time enhancement of entities and component/embeddable classes.
@ -184,6 +184,11 @@ public class EnhancementTask extends Task implements EnhancementContext {
return true; return true;
} }
@Override
public boolean doFieldAccessEnhancement(CtClass classDescriptor) {
return false;
}
@Override @Override
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) { public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
return true; return true;

View File

@ -14,6 +14,8 @@ import org.hibernate.test.bytecode.enhancement.association.OneToManyAssociationT
import org.hibernate.test.bytecode.enhancement.association.OneToOneAssociationTestTask; import org.hibernate.test.bytecode.enhancement.association.OneToOneAssociationTestTask;
import org.hibernate.test.bytecode.enhancement.basic.BasicEnhancementTestTask; import org.hibernate.test.bytecode.enhancement.basic.BasicEnhancementTestTask;
import org.hibernate.test.bytecode.enhancement.dirty.DirtyTrackingTestTask; import org.hibernate.test.bytecode.enhancement.dirty.DirtyTrackingTestTask;
import org.hibernate.test.bytecode.enhancement.field.FieldAccessBidirectionalTestTasK;
import org.hibernate.test.bytecode.enhancement.field.FieldAccessEnhancementTestTask;
import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask1; import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask1;
import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask2; import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask2;
import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask3; import org.hibernate.test.bytecode.enhancement.join.HHH3949TestTask3;
@ -51,6 +53,12 @@ public class EnhancerTest extends BaseUnitTestCase {
EnhancerTestUtils.runEnhancerTestTask( LazyLoadingIntegrationTestTask.class ); EnhancerTestUtils.runEnhancerTestTask( LazyLoadingIntegrationTestTask.class );
} }
@Test
public void testFieldAccess() {
EnhancerTestUtils.runEnhancerTestTask( FieldAccessEnhancementTestTask.class );
EnhancerTestUtils.runEnhancerTestTask( FieldAccessBidirectionalTestTasK.class );
}
@Test @Test
@TestForIssue( jiraKey = "HHH-3949" ) @TestForIssue( jiraKey = "HHH-3949" )
@FailureExpected( jiraKey = "HHH-3949" ) @FailureExpected( jiraKey = "HHH-3949" )

View File

@ -0,0 +1,24 @@
/*
* 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.test.bytecode.enhancement;
import javassist.CtClass;
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
/**
* Enhancement context used in tests
*
* @author Luis Barreiro
*/
public class EnhancerTestContext extends DefaultEnhancementContext {
@Override
public boolean doFieldAccessEnhancement(CtClass classDescriptor) {
return true;
}
}

View File

@ -9,15 +9,14 @@ package org.hibernate.test.bytecode.enhancement;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.Arrays; import java.util.Arrays;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.tools.JavaFileObject;
import javassist.ClassPool; import javassist.ClassPool;
import javassist.CtClass; import javassist.CtClass;
@ -25,7 +24,6 @@ import javassist.LoaderClassPath;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.Enhancer; import org.hibernate.bytecode.enhance.spi.Enhancer;
import org.hibernate.engine.internal.MutableEntityEntryFactory; import org.hibernate.engine.internal.MutableEntityEntryFactory;
@ -41,6 +39,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/** /**
* utility class to use in bytecode enhancement tests * utility class to use in bytecode enhancement tests
@ -49,7 +48,7 @@ import static org.junit.Assert.assertTrue;
*/ */
public abstract class EnhancerTestUtils extends BaseUnitTestCase { public abstract class EnhancerTestUtils extends BaseUnitTestCase {
private static EnhancementContext enhancementContext = new DefaultEnhancementContext(); private static EnhancementContext enhancementContext = new EnhancerTestContext();
private static String workingDir = System.getProperty( "java.io.tmpdir" ); private static String workingDir = System.getProperty( "java.io.tmpdir" );
@ -82,13 +81,8 @@ public abstract class EnhancerTestUtils extends BaseUnitTestCase {
private static CtClass generateCtClassForAnEntity(Class<?> entityClassToEnhance) throws Exception { private static CtClass generateCtClassForAnEntity(Class<?> entityClassToEnhance) throws Exception {
ClassPool cp = new ClassPool( false ); ClassPool cp = new ClassPool( false );
ClassLoader cl = EnhancerTestUtils.class.getClassLoader(); ClassLoader cl = EnhancerTestUtils.class.getClassLoader();
return cp.makeClass( cl.getResourceAsStream( getFilenameForClassName( entityClassToEnhance.getName() ) ) ); return cp.makeClass( cl.getResourceAsStream( entityClassToEnhance.getName().replace( '.', '/' ) + ".class" ) );
} }
private static String getFilenameForClassName(String className) {
return className.replace( '.', File.separatorChar ) + JavaFileObject.Kind.CLASS.extension;
}
/* --- */ /* --- */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -145,7 +139,7 @@ public abstract class EnhancerTestUtils extends BaseUnitTestCase {
return c; return c;
} }
final InputStream is = this.getResourceAsStream( getFilenameForClassName( name ) ); final InputStream is = this.getResourceAsStream( name.replace( '.', '/' ) + ".class" );
if ( is == null ) { if ( is == null ) {
throw new ClassNotFoundException( name + " not found" ); throw new ClassNotFoundException( name + " not found" );
} }
@ -154,28 +148,42 @@ public abstract class EnhancerTestUtils extends BaseUnitTestCase {
final byte[] original = new byte[is.available()]; final byte[] original = new byte[is.available()];
new BufferedInputStream( is ).read( original ); new BufferedInputStream( is ).read( original );
// Only enhance classes annotated with Entity or Embeddable final byte[] enhanced = new Enhancer( enhancementContext ).enhance( name, original );
final Class p = getParent().loadClass( name );
if ( p.getAnnotation( Entity.class ) != null || p.getAnnotation( Embeddable.class ) != null ) {
final byte[] enhanced = new Enhancer( enhancementContext ).enhance( name, original );
Path debugOutput = Paths.get( workingDir + File.separator + getFilenameForClassName( name ) ); Path debugOutput = Paths.get( workingDir + File.separator + name.replace( '.', '/' ) + ".class" );
Files.createDirectories( debugOutput.getParent() ); Files.createDirectories( debugOutput.getParent() );
Files.write( debugOutput, enhanced, StandardOpenOption.CREATE ); Files.write( debugOutput, enhanced, StandardOpenOption.CREATE );
return defineClass( name, enhanced, 0, enhanced.length ); return defineClass( name, enhanced, 0, enhanced.length );
}
else {
return defineClass( name, original, 0, original.length );
}
} }
catch (Throwable t) { catch (Throwable t) {
throw new ClassNotFoundException( name + " not found", t ); throw new ClassNotFoundException( name + " not found", t );
} finally {
try {
is.close();
}
catch (IOException e) { // ignore
}
} }
} }
}; };
} }
public static Object getFieldByReflection(Object entity, String fieldName) {
try {
Field field = entity.getClass().getDeclaredField( fieldName );
field.setAccessible( true );
return field.get( entity );
}
catch (NoSuchFieldException e) {
fail( "Fail to get field '" + fieldName + "' in entity " + entity );
}
catch (IllegalAccessException e) {
fail( "Fail to get field '" + fieldName + "' in entity " + entity );
}
return null;
}
/** /**
* clears the dirty set for an entity * clears the dirty set for an entity
*/ */

View File

@ -56,13 +56,15 @@ public class BasicEnhancementTestTask extends AbstractEnhancerTestTask {
interceptableEntity.$$_hibernate_setInterceptor( new ObjectAttributeMarkerInterceptor() ); interceptableEntity.$$_hibernate_setInterceptor( new ObjectAttributeMarkerInterceptor() );
assertNotNull( interceptableEntity.$$_hibernate_getInterceptor() ); assertNotNull( interceptableEntity.$$_hibernate_getInterceptor() );
assertNull( entity.anUnspecifiedObject );
entity.setAnObject( new Object() );
assertSame( entity.anUnspecifiedObject, ObjectAttributeMarkerInterceptor.WRITE_MARKER );
assertSame( entity.getAnObject(), ObjectAttributeMarkerInterceptor.READ_MARKER );
entity.setAnObject( null );
assertSame( entity.anUnspecifiedObject, ObjectAttributeMarkerInterceptor.WRITE_MARKER );
assertNull( EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" ) );
entity.setAnObject( new Object() );
assertSame( EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" ), ObjectAttributeMarkerInterceptor.WRITE_MARKER );
assertSame( entity.getAnObject(), ObjectAttributeMarkerInterceptor.READ_MARKER );
entity.setAnObject( null );
assertSame( EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" ), ObjectAttributeMarkerInterceptor.WRITE_MARKER );
} }
protected void cleanup() { protected void cleanup() {

View File

@ -0,0 +1,82 @@
/*
* 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.test.bytecode.enhancement.field;
import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask;
import org.hibernate.test.bytecode.enhancement.EnhancerTestUtils;
import org.junit.Assert;
/**
* @author Luis Barreiro
*/
public class FieldAccessBidirectionalTestTasK extends AbstractEnhancerTestTask {
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {Customer.class, User.class};
}
public void prepare() {
}
public void execute() {
User user = new User();
user.login = UUID.randomUUID().toString();
Customer customer = new Customer();
customer.user = user;
Assert.assertEquals( customer, EnhancerTestUtils.getFieldByReflection( user, "customer" ) );
// check dirty tracking is set automatically with bi-directional association management
EnhancerTestUtils.checkDirtyTracking( user, "login", "customer" );
User anotherUser = new User();
anotherUser.login = UUID.randomUUID().toString();
customer.user = anotherUser;
Assert.assertNull( user.customer );
Assert.assertEquals( customer, EnhancerTestUtils.getFieldByReflection( anotherUser, "customer" ) );
user.customer = new Customer();
Assert.assertEquals( user, user.customer.user );
}
protected void cleanup() {
}
@Entity public class Customer {
@Id public int id;
@OneToOne(fetch = FetchType.LAZY) public User user;
public String firstName;
public String lastName;
public int version;
}
@Entity public class User {
@Id public int id;
public String login;
public String password;
@OneToOne(mappedBy = "user", fetch = FetchType.LAZY) public Customer customer;
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.test.bytecode.enhancement.field;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask;
import org.hibernate.test.bytecode.enhancement.EnhancerTestUtils;
import org.hibernate.test.bytecode.enhancement.basic.ObjectAttributeMarkerInterceptor;
import org.junit.Assert;
/**
* @author Luis Barreiro
*/
public class FieldAccessEnhancementTestTask extends AbstractEnhancerTestTask {
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {SimpleEntity.class};
}
public void prepare() {
}
public void execute() {
// test uses ObjectAttributeMarkerInterceptor to ensure that field access is routed through enhanced methods
SimpleEntity entity = new SimpleEntity();
( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( new ObjectAttributeMarkerInterceptor() );
Object decoy = new Object();
entity.anUnspecifiedObject = decoy;
Object gotByReflection = EnhancerTestUtils.getFieldByReflection( entity, "anUnspecifiedObject" );
Assert.assertNotSame( gotByReflection, decoy );
Assert.assertSame( gotByReflection, ObjectAttributeMarkerInterceptor.WRITE_MARKER );
Object entityObject = entity.anUnspecifiedObject;
Assert.assertNotSame( entityObject, decoy );
Assert.assertSame( entityObject, ObjectAttributeMarkerInterceptor.READ_MARKER );
// do some more calls on the various types, without the interceptor
( (PersistentAttributeInterceptable) entity ).$$_hibernate_setInterceptor( null );
entity.id = 1234567890l;
Assert.assertEquals( entity.id, 1234567890l );
entity.name = "Entity Name";
Assert.assertSame( entity.name, "Entity Name" );
entity.active = true;
Assert.assertTrue( entity.active );
entity.someStrings = Arrays.asList( "A", "B", "C", "D" );
Assert.assertArrayEquals( new String[] { "A", "B", "C", "D" }, entity.someStrings.toArray() );
}
protected void cleanup() {
}
@Entity public class SimpleEntity {
@Id public long id;
public String name;
public boolean active;
public long someNumber;
public int anInt;
public Object anUnspecifiedObject;
public List<String> someStrings;
@OneToMany public Set<Integer> someInts;
}
}

View File

@ -62,15 +62,18 @@ public class LazyLoadingTestTask extends AbstractEnhancerTestTask {
Child loadedChild = s.load( Child.class, lastChildID ); Child loadedChild = s.load( Child.class, lastChildID );
Assert.assertNull( "Lazy field 'parent' is initialized", loadedChild.parent ); Object parentByReflection = EnhancerTestUtils.getFieldByReflection( loadedChild, "parent" );
Assert.assertNull( "Lazy field 'parent' is initialized", parentByReflection );
Assert.assertFalse( loadedChild instanceof HibernateProxy ); Assert.assertFalse( loadedChild instanceof HibernateProxy );
Parent loadedParent = loadedChild.getParent(); Parent loadedParent = loadedChild.getParent();
EnhancerTestUtils.checkDirtyTracking( loadedChild ); EnhancerTestUtils.checkDirtyTracking( loadedChild );
Assert.assertNotNull( "Lazy field 'parent' is not loaded", loadedChild.parent ); parentByReflection = EnhancerTestUtils.getFieldByReflection( loadedChild, "parent" );
Assert.assertNull( "Lazy field 'children' is initialized", loadedParent.children ); Object childrenByReflection = EnhancerTestUtils.getFieldByReflection( loadedParent, "children" );
Assert.assertNotNull( "Lazy field 'parent' is not loaded", parentByReflection );
Assert.assertNull( "Lazy field 'children' is initialized", childrenByReflection );
Assert.assertFalse( loadedParent instanceof HibernateProxy ); Assert.assertFalse( loadedParent instanceof HibernateProxy );
Assert.assertTrue( parentID.equals( loadedParent.id ) ); Assert.assertTrue( parentID.equals( loadedParent.id ) );
@ -79,7 +82,8 @@ public class LazyLoadingTestTask extends AbstractEnhancerTestTask {
EnhancerTestUtils.checkDirtyTracking( loadedChild ); EnhancerTestUtils.checkDirtyTracking( loadedChild );
EnhancerTestUtils.checkDirtyTracking( loadedParent ); EnhancerTestUtils.checkDirtyTracking( loadedParent );
Assert.assertNotNull( "Lazy field 'children' is not loaded", loadedParent.children ); childrenByReflection = EnhancerTestUtils.getFieldByReflection( loadedParent, "children" );
Assert.assertNotNull( "Lazy field 'children' is not loaded", childrenByReflection );
Assert.assertFalse( loadedChildren instanceof HibernateProxy ); Assert.assertFalse( loadedChildren instanceof HibernateProxy );
Assert.assertEquals( CHILDREN_SIZE, loadedChildren.size() ); Assert.assertEquals( CHILDREN_SIZE, loadedChildren.size() );
Assert.assertTrue( loadedChildren.contains( loadedChild ) ); Assert.assertTrue( loadedChildren.contains( loadedChild ) );