HHH-9938 - bytecode enhancer - field access enhancement feature
This commit is contained in:
parent
6d77ac39c5
commit
417baffa13
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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?
|
||||||
*
|
*
|
||||||
|
|
|
@ -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" );
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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" )
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 ) );
|
||||||
|
|
Loading…
Reference in New Issue