HHH-9806 - Bytecode-enhancement-based dirty tracking does not work because PersistentAttributeInterceptor is never injected
This commit is contained in:
parent
14fc8859fe
commit
acea523607
|
@ -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) || (!%<s.equals($1))))", currentValue.getName() ) );
|
||||
builder.append( String.format( "if (%s == null || !%<s.equals($1))", currentValue.getName() ) );
|
||||
}
|
||||
// all other objects
|
||||
else {
|
||||
|
@ -64,7 +63,7 @@ public abstract class AttributeTypeDescriptor {
|
|||
}
|
||||
}
|
||||
// TODO: for now just call equals, should probably do something else here
|
||||
builder.append( String.format( " && ((%s == null) || (!%<s.equals($1))))", currentValue.getName() ) );
|
||||
builder.append( String.format( "if (%s == null || !%<s.equals($1))", currentValue.getName() ) );
|
||||
}
|
||||
builder.append( String.format( " { %s(\"%s\"); }", EnhancerConstants.TRACKER_CHANGER_NAME, currentValue.getName() ) );
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import javassist.CtClass;
|
|||
import javassist.CtField;
|
||||
import javassist.Modifier;
|
||||
import javassist.NotFoundException;
|
||||
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.CollectionTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.SimpleDirtyTracker;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
|
@ -22,6 +23,7 @@ import org.hibernate.engine.spi.SelfDirtinessTracker;
|
|||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* enhancer for regular entities
|
||||
|
@ -54,49 +56,63 @@ public class EntityEnhancer extends Enhancer {
|
|||
new PersistentAttributesEnhancer( enhancementContext ).enhance( managedCtClass );
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
private void addEntityInstanceHandling(CtClass managedCtClass) {
|
||||
try {
|
||||
MethodWriter.write( managedCtClass, "public Object %s() { return this; }", EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME );
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public Object %s() { return this; }",
|
||||
EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME
|
||||
);
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
final String msg = String.format( "Could not enhance entity class [%s] to add EntityEntry getter", managedCtClass.getName() );
|
||||
throw new EnhancementException(msg, cce);
|
||||
throw new EnhancementException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Could not enhance entity class [%s] to add EntityEntry getter",
|
||||
managedCtClass.getName()
|
||||
),
|
||||
cce
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
private void addEntityEntryHandling(CtClass managedCtClass) {
|
||||
FieldWriter.addFieldWithGetterAndSetter( managedCtClass, entityEntryCtClass,
|
||||
FieldWriter.addFieldWithGetterAndSetter(
|
||||
managedCtClass, entityEntryCtClass,
|
||||
EnhancerConstants.ENTITY_ENTRY_FIELD_NAME,
|
||||
EnhancerConstants.ENTITY_ENTRY_GETTER_NAME,
|
||||
EnhancerConstants.ENTITY_ENTRY_SETTER_NAME );
|
||||
EnhancerConstants.ENTITY_ENTRY_SETTER_NAME
|
||||
);
|
||||
}
|
||||
|
||||
private void addLinkedPreviousHandling(CtClass managedCtClass) {
|
||||
FieldWriter.addFieldWithGetterAndSetter( managedCtClass, managedEntityCtClass,
|
||||
FieldWriter.addFieldWithGetterAndSetter(
|
||||
managedCtClass, managedEntityCtClass,
|
||||
EnhancerConstants.PREVIOUS_FIELD_NAME,
|
||||
EnhancerConstants.PREVIOUS_GETTER_NAME,
|
||||
EnhancerConstants.PREVIOUS_SETTER_NAME );
|
||||
EnhancerConstants.PREVIOUS_SETTER_NAME
|
||||
);
|
||||
}
|
||||
|
||||
private void addLinkedNextHandling(CtClass managedCtClass) {
|
||||
FieldWriter.addFieldWithGetterAndSetter( managedCtClass, managedEntityCtClass,
|
||||
FieldWriter.addFieldWithGetterAndSetter(
|
||||
managedCtClass, managedEntityCtClass,
|
||||
EnhancerConstants.NEXT_FIELD_NAME,
|
||||
EnhancerConstants.NEXT_GETTER_NAME,
|
||||
EnhancerConstants.NEXT_SETTER_NAME );
|
||||
EnhancerConstants.NEXT_SETTER_NAME
|
||||
);
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
private void addInLineDirtyHandling(CtClass managedCtClass) {
|
||||
try {
|
||||
managedCtClass.addInterface( classPool.get( SelfDirtinessTracker.class.getName() ) );
|
||||
|
||||
FieldWriter.addField( managedCtClass, classPool.get( TRACKER_IMPL ), EnhancerConstants.TRACKER_FIELD_NAME );
|
||||
FieldWriter.addField( managedCtClass, classPool.get( CollectionTracker.class.getName() ), EnhancerConstants.TRACKER_COLLECTION_NAME );
|
||||
FieldWriter.addField(
|
||||
managedCtClass,
|
||||
classPool.get( CollectionTracker.class.getName() ),
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
);
|
||||
|
||||
createDirtyTrackerMethods( managedCtClass );
|
||||
}
|
||||
|
@ -107,24 +123,23 @@ public class EntityEnhancer extends Enhancer {
|
|||
|
||||
private void createDirtyTrackerMethods(CtClass managedCtClass) {
|
||||
try {
|
||||
MethodWriter.write( managedCtClass, "" +
|
||||
MethodWriter.write(
|
||||
managedCtClass, "" +
|
||||
"public void %1$s(String name) {%n" +
|
||||
" if (%2$s == null) { %2$s = new %3$s(); }%n" +
|
||||
" %2$s.add(name);%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_CHANGER_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME,
|
||||
TRACKER_IMPL );
|
||||
|
||||
/* --- */
|
||||
TRACKER_IMPL
|
||||
);
|
||||
|
||||
createCollectionDirtyCheckMethod( managedCtClass );
|
||||
createCollectionDirtyCheckGetFieldsMethod( managedCtClass );
|
||||
createClearDirtyCollectionMethod( managedCtClass );
|
||||
|
||||
/* --- */
|
||||
|
||||
MethodWriter.write( managedCtClass, "" +
|
||||
MethodWriter.write(
|
||||
managedCtClass, "" +
|
||||
"public java.util.Set %1$s() {%n" +
|
||||
" if (%2$s == null) { %2$s = new %4$s(); }%n" +
|
||||
" %3$s(%2$s);%n" +
|
||||
|
@ -133,32 +148,37 @@ public class EntityEnhancer extends Enhancer {
|
|||
EnhancerConstants.TRACKER_GET_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME,
|
||||
TRACKER_IMPL );
|
||||
TRACKER_IMPL
|
||||
);
|
||||
|
||||
MethodWriter.write( managedCtClass, "" +
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"" +
|
||||
"public boolean %1$s() {%n" +
|
||||
" return (%2$s != null && !%2$s.isEmpty()) || %3$s();%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_HAS_CHANGED_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME );
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME
|
||||
);
|
||||
|
||||
MethodWriter.write( managedCtClass, "" +
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"" +
|
||||
"public void %1$s() {%n" +
|
||||
" if (%2$s != null) { %2$s.clear(); }%n" +
|
||||
" %3$s();%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_CLEAR_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME );
|
||||
EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME
|
||||
);
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
cce.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
private List<CtField> collectCollectionFields(CtClass managedCtClass) {
|
||||
final List<CtField> collectionList = new LinkedList<CtField>();
|
||||
try {
|
||||
|
@ -186,21 +206,30 @@ public class EntityEnhancer extends Enhancer {
|
|||
try {
|
||||
final StringBuilder body = new StringBuilder();
|
||||
|
||||
body.append( String.format( "" +
|
||||
body.append(
|
||||
String.format(
|
||||
"" +
|
||||
"private boolean %1$s() {%n" +
|
||||
" if (%2$s() == null || %3$s == null) { return false; }%n",
|
||||
" if (%2$s == null) {%n" +
|
||||
" return false;%n" +
|
||||
" }%n",
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME ) );
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
)
|
||||
);
|
||||
|
||||
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
|
||||
if ( !enhancementContext.isMappedCollection( ctField )) {
|
||||
body.append( String.format( "" +
|
||||
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) { return true; }%n" +
|
||||
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n",
|
||||
ctField.getName(),
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME ) );
|
||||
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( "" +
|
||||
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 ) );
|
||||
TRACKER_IMPL
|
||||
)
|
||||
);
|
||||
|
||||
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
|
||||
if ( !enhancementContext.isMappedCollection( ctField )) {
|
||||
body.append( String.format( "" +
|
||||
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) { 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 ) );
|
||||
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( "" +
|
||||
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()) );
|
||||
CollectionTracker.class.getName()
|
||||
)
|
||||
);
|
||||
|
||||
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
|
||||
if ( !enhancementContext.isMappedCollection( ctField ) ) {
|
||||
body.append( String.format( "" +
|
||||
body.append(
|
||||
String.format(
|
||||
"" +
|
||||
" // Collection field [%1$s]%n" +
|
||||
" if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%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) );
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
body.append( "}" );
|
||||
|
|
|
@ -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<String, PersistentAttributeAccessMethods> attrDescriptorMap = new IdentityHashMap<String, PersistentAttributeAccessMethods>();
|
||||
|
||||
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<CtField> persistentFieldList = new LinkedList<CtField>();
|
||||
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) { %<s.callOwner(\".%s\"); }%n",
|
||||
writer.insertBefore(
|
||||
String.format(
|
||||
"if (%s != null) { %<s.callOwner(\".%s\"); }%n",
|
||||
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME,
|
||||
fieldName ) );
|
||||
fieldName
|
||||
)
|
||||
);
|
||||
}
|
||||
else if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( enhancementContext, persistentField ) );
|
||||
writer.insertBefore(
|
||||
typeDescriptor.buildInLineDirtyCheckingBodyFragment(
|
||||
enhancementContext,
|
||||
persistentField
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
handleCompositeField( managedCtClass, persistentField, writer );
|
||||
|
@ -156,29 +192,44 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
return writer;
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
final String msg = String.format( "Could not enhance entity class [%s] to add field writer method [%s]", managedCtClass.getName(), writerName );
|
||||
final String msg = String.format(
|
||||
"Could not enhance entity class [%s] to add field writer method [%s]",
|
||||
managedCtClass.getName(),
|
||||
writerName
|
||||
);
|
||||
throw new EnhancementException( msg, cce );
|
||||
}
|
||||
catch (NotFoundException nfe) {
|
||||
final String msg = String.format( "Could not enhance entity class [%s] to add field writer method [%s]", managedCtClass.getName(), writerName );
|
||||
final String msg = String.format(
|
||||
"Could not enhance entity class [%s] to add field writer method [%s]",
|
||||
managedCtClass.getName(),
|
||||
writerName
|
||||
);
|
||||
throw new EnhancementException( msg, nfe );
|
||||
}
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
private void handleBiDirectionalAssociation(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter) throws NotFoundException, CannotCompileException {
|
||||
private void handleBiDirectionalAssociation(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
|
||||
throws NotFoundException, CannotCompileException {
|
||||
if ( !isPossibleBiDirectionalAssociation( persistentField ) ) {
|
||||
return;
|
||||
}
|
||||
final CtClass targetEntity = getTargetEntityClass( persistentField );
|
||||
if ( targetEntity == null ) {
|
||||
log.debugf( "Could not find type of bi-directional association for field [%s#%s]", managedCtClass.getName(), persistentField.getName() );
|
||||
log.debugf(
|
||||
"Could not find type of bi-directional association for field [%s#%s]",
|
||||
managedCtClass.getName(),
|
||||
persistentField.getName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
final String mappedBy = getMappedBy( persistentField, targetEntity );
|
||||
if ( mappedBy.isEmpty() ) {
|
||||
log.debugf( "Could not find bi-directional association for field [%s#%s]", managedCtClass.getName(), persistentField.getName() );
|
||||
log.debugf(
|
||||
"Could not find bi-directional association for field [%s#%s]",
|
||||
managedCtClass.getName(),
|
||||
persistentField.getName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -190,41 +241,73 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
|
||||
if ( persistentField.hasAnnotation( OneToOne.class ) ) {
|
||||
// only unset when $1 != null to avoid recursion
|
||||
fieldWriter.insertBefore( String.format( "if ($0.%s != null && $1 != null) $0.%<s.%s(null);%n",
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
"if ($0.%s != null && $1 != null) $0.%<s.%s(null);%n",
|
||||
persistentField.getName(),
|
||||
mappedBySetterName));
|
||||
fieldWriter.insertAfter( String.format( "if ($1 != null && $1.%s() != $0) $1.%s($0);%n",
|
||||
mappedBySetterName
|
||||
)
|
||||
);
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
"if ($1 != null && $1.%s() != $0) $1.%s($0);%n",
|
||||
mappedByGetterName,
|
||||
mappedBySetterName) );
|
||||
mappedBySetterName
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( persistentField.hasAnnotation( OneToMany.class ) ) {
|
||||
// only remove elements not in the new collection or else we would loose those elements
|
||||
fieldWriter.insertBefore( String.format( "if ($0.%s != null) for (java.util.Iterator itr = $0.%<s.iterator(); itr.hasNext(); ) { %s target = (%<s) itr.next(); if ($1 == null || !$1.contains(target)) target.%s(null); }%n",
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
"if ($0.%s != null) for (java.util.Iterator itr = $0.%<s.iterator(); itr.hasNext(); ) { %s target = (%<s) itr.next(); if ($1 == null || !$1.contains(target)) target.%s(null); }%n",
|
||||
persistentField.getName(),
|
||||
targetEntity.getName(),
|
||||
mappedBySetterName ) );
|
||||
fieldWriter.insertAfter( String.format( "if ($1 != null) for (java.util.Iterator itr = $1.iterator(); itr.hasNext(); ) { %s target = (%<s) itr.next(); if (target.%s() != $0) target.%s((%s)$0); }%n",
|
||||
mappedBySetterName
|
||||
)
|
||||
);
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
"if ($1 != null) for (java.util.Iterator itr = $1.iterator(); itr.hasNext(); ) { %s target = (%<s) itr.next(); if (target.%s() != $0) target.%s((%s)$0); }%n",
|
||||
targetEntity.getName(),
|
||||
mappedByGetterName,
|
||||
mappedBySetterName,
|
||||
managedCtClass.getName() ) );
|
||||
managedCtClass.getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( persistentField.hasAnnotation( ManyToOne.class ) ) {
|
||||
fieldWriter.insertBefore( String.format( "if ($0.%1$s != null && $0.%1$s.%2$s() != null) $0.%1$s.%2$s().remove($0);%n",
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
"if ($0.%1$s != null && $0.%1$s.%2$s() != null) $0.%1$s.%2$s().remove($0);%n",
|
||||
persistentField.getName(),
|
||||
mappedByGetterName) );
|
||||
mappedByGetterName
|
||||
)
|
||||
);
|
||||
// check .contains($0) to avoid double inserts (but preventing duplicates)
|
||||
fieldWriter.insertAfter( String.format( "if ($1 != null && $1.%s() != null && !$1.%<s().contains($0) ) $1.%<s().add($0);%n",
|
||||
mappedByGetterName));
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
"if ($1 != null && $1.%s() != null && !$1.%<s().contains($0) ) $1.%<s().add($0);%n",
|
||||
mappedByGetterName
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( persistentField.hasAnnotation( ManyToMany.class ) ) {
|
||||
fieldWriter.insertBefore( String.format( "if ($0.%s != null) for (java.util.Iterator itr = $0.%<s.iterator(); itr.hasNext(); ) { %s target = (%<s) itr.next(); if ($1 == null || !$1.contains(target)) target.%s().remove($0); }%n",
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
"if ($0.%s != null) for (java.util.Iterator itr = $0.%<s.iterator(); itr.hasNext(); ) { %s target = (%<s) itr.next(); if ($1 == null || !$1.contains(target)) target.%s().remove($0); }%n",
|
||||
persistentField.getName(),
|
||||
targetEntity.getName(),
|
||||
mappedByGetterName));
|
||||
fieldWriter.insertAfter( String.format( "if ($1 != null) for (java.util.Iterator itr = $1.iterator(); itr.hasNext(); ) { %s target = (%<s) itr.next(); if (target.%s() != $0 && target.%<s() != null) target.%<s().add($0); }%n",
|
||||
mappedByGetterName
|
||||
)
|
||||
);
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
"if ($1 != null) for (java.util.Iterator itr = $1.iterator(); itr.hasNext(); ) { %s target = (%<s) itr.next(); if (target.%s() != $0 && target.%<s() != null) target.%<s().add($0); }%n",
|
||||
targetEntity.getName(),
|
||||
mappedByGetterName ) );
|
||||
mappedByGetterName
|
||||
)
|
||||
);
|
||||
}
|
||||
// implementation note: association management @OneToMany and @ManyToMay works for add() operations but for remove() a snapshot of the collection is needed so we know what associations to break.
|
||||
// another approach that could force that behavior would be to return Collections.unmodifiableCollection() ...
|
||||
|
@ -233,7 +316,7 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
private boolean isPossibleBiDirectionalAssociation(CtField persistentField) {
|
||||
return persistentField.hasAnnotation( OneToOne.class ) ||
|
||||
persistentField.hasAnnotation( OneToMany.class ) ||
|
||||
persistentField.hasAnnotation( ManyToOne.class) ||
|
||||
persistentField.hasAnnotation( ManyToOne.class ) ||
|
||||
persistentField.hasAnnotation( ManyToMany.class );
|
||||
}
|
||||
|
||||
|
@ -244,15 +327,15 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
|
||||
private String getMappedByFromAnnotation(CtField persistentField) {
|
||||
try {
|
||||
if (persistentField.hasAnnotation( OneToOne.class )) {
|
||||
return ((OneToOne) persistentField.getAnnotation( OneToOne.class )).mappedBy();
|
||||
if ( persistentField.hasAnnotation( OneToOne.class ) ) {
|
||||
return ( (OneToOne) persistentField.getAnnotation( OneToOne.class ) ).mappedBy();
|
||||
}
|
||||
if (persistentField.hasAnnotation( OneToMany.class )) {
|
||||
return ((OneToMany) persistentField.getAnnotation( OneToMany.class )).mappedBy();
|
||||
if ( persistentField.hasAnnotation( OneToMany.class ) ) {
|
||||
return ( (OneToMany) persistentField.getAnnotation( OneToMany.class ) ).mappedBy();
|
||||
}
|
||||
// For @ManyToOne associations, mappedBy must come from the @OneToMany side of the association
|
||||
if (persistentField.hasAnnotation( ManyToMany.class )) {
|
||||
return ((ManyToMany) persistentField.getAnnotation( ManyToMany.class )).mappedBy();
|
||||
if ( persistentField.hasAnnotation( ManyToMany.class ) ) {
|
||||
return ( (ManyToMany) persistentField.getAnnotation( ManyToMany.class ) ).mappedBy();
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ignore) {
|
||||
|
@ -262,9 +345,15 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
|
||||
private String getMappedByFromTargetEntity(CtField persistentField, CtClass targetEntity) {
|
||||
// get mappedBy value by searching in the fields of the target entity class
|
||||
for (CtField f : targetEntity.getDeclaredFields() ) {
|
||||
for ( CtField f : targetEntity.getDeclaredFields() ) {
|
||||
if ( enhancementContext.isPersistentField( f ) && getMappedByFromAnnotation( f ).equals( persistentField.getName() ) ) {
|
||||
log.debugf( "mappedBy association for field [%s:%s] is [%s:%s]", persistentField.getDeclaringClass().getName(), persistentField.getName(), targetEntity.getName(), f.getName() );
|
||||
log.debugf(
|
||||
"mappedBy association for field [%s:%s] is [%s:%s]",
|
||||
persistentField.getDeclaringClass().getName(),
|
||||
persistentField.getName(),
|
||||
targetEntity.getName(),
|
||||
f.getName()
|
||||
);
|
||||
return f.getName();
|
||||
}
|
||||
}
|
||||
|
@ -275,17 +364,17 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
// get targetEntity defined in the annotation
|
||||
try {
|
||||
Class<?> 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( "" +
|
||||
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) );
|
||||
EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER
|
||||
)
|
||||
);
|
||||
|
||||
// trigger track changes
|
||||
fieldWriter.insertAfter( String.format( "" +
|
||||
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 ) );
|
||||
EnhancerConstants.TRACKER_CHANGER_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
protected void enhanceAttributesAccess(CtClass managedCtClass, IdentityHashMap<String, PersistentAttributeAccessMethods> attributeDescriptorMap) {
|
||||
protected void enhanceAttributesAccess(
|
||||
CtClass managedCtClass,
|
||||
IdentityHashMap<String, PersistentAttributeAccessMethods> 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;
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
* <p/>
|
||||
* 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 <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
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<String> $$_hibernate_getDirtyAttributes();
|
||||
|
||||
|
|
|
@ -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<String> strings = new ArrayList<String>();
|
||||
strings.add("FooBar");
|
||||
entityClass.getMethod("setSomeStrings", List.class).invoke(entityInstance, strings);
|
||||
|
|
|
@ -60,152 +60,152 @@ 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);
|
||||
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[] 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);
|
||||
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)));
|
||||
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))) {
|
||||
for ( JavaFileObject jfo : fileManager.getJavaFileObjects(
|
||||
workingDir + File.separator + getFilenameForClassName(
|
||||
className
|
||||
)
|
||||
) ) {
|
||||
try {
|
||||
Set<String> interfaceNames = new HashSet<String>();
|
||||
Set<String> fieldNames = new HashSet<String>();
|
||||
Set<String> methodNames = new HashSet<String>();
|
||||
|
||||
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 ( 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.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 ( 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( 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));
|
||||
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));
|
||||
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( 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( 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));
|
||||
if ( interfaceNames.contains( CompositeOwner.class.getName() ) ) {
|
||||
assertTrue( fieldNames.contains( EnhancerConstants.TRACKER_CHANGER_NAME ) );
|
||||
assertTrue( methodNames.contains( EnhancerConstants.TRACKER_CHANGER_NAME ) );
|
||||
}
|
||||
} catch (ConstantPoolException e) {
|
||||
}
|
||||
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());
|
||||
}
|
||||
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())));
|
||||
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;
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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() {
|
||||
|
@ -226,83 +226,110 @@ public abstract class EnhancerTestUtils extends BaseUnitTestCase {
|
|||
|
||||
public static class LocalPersistentAttributeInterceptor implements PersistentAttributeInterceptor {
|
||||
|
||||
@Override public boolean readBoolean(Object obj, String name, boolean oldValue) {
|
||||
log.infof( "Reading boolean [%s]" , name );
|
||||
@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) {
|
||||
|
||||
@Override
|
||||
public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) {
|
||||
log.infof( "Writing boolean []", name );
|
||||
return newValue;
|
||||
}
|
||||
|
||||
@Override public byte readByte(Object obj, String name, byte 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) {
|
||||
|
||||
@Override
|
||||
public byte writeByte(Object obj, String name, byte oldValue, byte newValue) {
|
||||
log.infof( "Writing byte [%s]", name );
|
||||
return newValue;
|
||||
}
|
||||
|
||||
@Override public char readChar(Object obj, String name, char oldValue) {
|
||||
@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) {
|
||||
|
||||
@Override
|
||||
public char writeChar(Object obj, String name, char oldValue, char newValue) {
|
||||
log.infof( "Writing char [%s]", name );
|
||||
return newValue;
|
||||
}
|
||||
|
||||
@Override public short readShort(Object obj, String name, short 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) {
|
||||
|
||||
@Override
|
||||
public short writeShort(Object obj, String name, short oldValue, short newValue) {
|
||||
log.infof( "Writing short [%s]", name );
|
||||
return newValue;
|
||||
}
|
||||
|
||||
@Override public int readInt(Object obj, String name, int oldValue) {
|
||||
@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) {
|
||||
|
||||
@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) {
|
||||
@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) {
|
||||
|
||||
@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) {
|
||||
@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) {
|
||||
|
||||
@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) {
|
||||
@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) {
|
||||
|
||||
@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) {
|
||||
@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) {
|
||||
|
||||
@Override
|
||||
public Object writeObject(Object obj, String name, Object oldValue, Object newValue) {
|
||||
log.infof( "Writing Object [%s]", name );
|
||||
return newValue;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue