HHH-10017 - bytecode enhancement - consistent handling of persistent attributes, regardeless of access type
This commit is contained in:
parent
603a410fdc
commit
ed185b9b48
|
@ -34,15 +34,13 @@ public abstract class AttributeTypeDescriptor {
|
|||
final StringBuilder builder = new StringBuilder();
|
||||
try {
|
||||
// should ignore primary keys
|
||||
for ( Object o : currentValue.getType().getAnnotations() ) {
|
||||
if ( o instanceof Id) {
|
||||
return "";
|
||||
}
|
||||
if (PersistentAttributesHelper.hasAnnotation( currentValue, Id.class ) ) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// primitives || enums
|
||||
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
|
||||
else if ( currentValue.getType().getName().startsWith( "java.lang" )
|
||||
|
@ -51,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( "if (%s == null || !%<s.equals($1))", currentValue.getName() ) );
|
||||
builder.append( String.format( " if (%s == null || !%<s.equals($1))", currentValue.getName() ) );
|
||||
}
|
||||
// all other objects
|
||||
else {
|
||||
|
@ -64,15 +62,12 @@ public abstract class AttributeTypeDescriptor {
|
|||
}
|
||||
}
|
||||
}
|
||||
builder.append( String.format( "if (%1$s == null || !%2$s.equals(%1$s, $1))",
|
||||
builder.append( String.format( " if (%1$s == null || !%2$s.equals(%1$s, $1))",
|
||||
currentValue.getName(),
|
||||
EqualsHelper.class.getName() ) );
|
||||
}
|
||||
builder.append( String.format( " { %s(\"%s\"); }", EnhancerConstants.TRACKER_CHANGER_NAME, currentValue.getName() ) );
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -128,22 +123,18 @@ public abstract class AttributeTypeDescriptor {
|
|||
}
|
||||
|
||||
public String buildReadInterceptionBodyFragment(String fieldName) {
|
||||
return String.format( "" +
|
||||
"if ( %3$s() != null ) {%n" +
|
||||
" this.%1$s = (%2$s) %3$s().readObject(this, \"%1$s\", this.%1$s);%n" +
|
||||
"}",
|
||||
return String.format(
|
||||
" if ( %3$s() != null ) { this.%1$s = (%2$s) %3$s().readObject(this, \"%1$s\", this.%1$s); }%n",
|
||||
fieldName,
|
||||
type,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
|
||||
}
|
||||
|
||||
public String buildWriteInterceptionBodyFragment(String fieldName) {
|
||||
return String.format( "" +
|
||||
"%2$s localVar = $1;%n" +
|
||||
"if ( %3$s() != null ) {%n" +
|
||||
" localVar = (%2$s) %3$s().writeObject(this, \"%1$s\", this.%1$s, $1);%n" +
|
||||
"}%n" +
|
||||
"this.%1$s = localVar;",
|
||||
return String.format(
|
||||
" %2$s localVar = $1;%n" +
|
||||
" if ( %3$s() != null ) { localVar = (%2$s) %3$s().writeObject(this, \"%1$s\", this.%1$s, $1); }%n" +
|
||||
" this.%1$s = localVar;",
|
||||
fieldName,
|
||||
type,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
|
||||
|
@ -166,22 +157,18 @@ public abstract class AttributeTypeDescriptor {
|
|||
}
|
||||
|
||||
public String buildReadInterceptionBodyFragment(String fieldName) {
|
||||
return String.format( "" +
|
||||
"if (%3$s() != null ) {%n" +
|
||||
" this.%1$s = %3$s().read%2$s(this, \"%1$s\", this.%1$s);%n" +
|
||||
"}",
|
||||
return String.format(
|
||||
" if (%3$s() != null ) { this.%1$s = %3$s().read%2$s(this, \"%1$s\", this.%1$s); }",
|
||||
fieldName,
|
||||
type,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
|
||||
}
|
||||
|
||||
public String buildWriteInterceptionBodyFragment(String fieldName) {
|
||||
return String.format( "" +
|
||||
"%2$s localVar = $1;%n" +
|
||||
"if ( %4$s() != null ) {%n" +
|
||||
" localVar = %4$s().write%3$s(this, \"%1$s\", this.%1$s, $1);%n" +
|
||||
"}%n" +
|
||||
"this.%1$s = localVar;",
|
||||
return String.format(
|
||||
" %2$s localVar = $1;%n" +
|
||||
" if ( %4$s() != null ) { localVar = %4$s().write%3$s(this, \"%1$s\", this.%1$s, $1); }%n" +
|
||||
" this.%1$s = localVar;",
|
||||
fieldName,
|
||||
type.toLowerCase( Locale.ROOT ),
|
||||
type,
|
||||
|
|
|
@ -17,13 +17,16 @@ import javassist.CtField;
|
|||
import javassist.Modifier;
|
||||
import javassist.NotFoundException;
|
||||
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.CollectionTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.SimpleCollectionTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker;
|
||||
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoader;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||
|
||||
/**
|
||||
|
@ -39,6 +42,7 @@ public class EntityEnhancer extends Enhancer {
|
|||
|
||||
// assuming the number of fields is not very high, SimpleFieldTracker implementation it's the fastest
|
||||
private static final String DIRTY_TRACKER_IMPL = SimpleFieldTracker.class.getName();
|
||||
private static final String COLLECTION_TRACKER_IMPL = SimpleCollectionTracker.class.getName();
|
||||
|
||||
public void enhance(CtClass managedCtClass) {
|
||||
// add the ManagedEntity interface
|
||||
|
@ -110,7 +114,7 @@ public class EntityEnhancer extends Enhancer {
|
|||
|
||||
FieldWriter.addField(
|
||||
managedCtClass,
|
||||
classPool.get( DIRTY_TRACKER_IMPL ),
|
||||
classPool.get( DirtyTracker.class.getName() ),
|
||||
EnhancerConstants.TRACKER_FIELD_NAME
|
||||
);
|
||||
FieldWriter.addField(
|
||||
|
@ -181,6 +185,24 @@ public class EntityEnhancer extends Enhancer {
|
|||
EnhancerConstants.TRACKER_FIELD_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME
|
||||
);
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public void %1$s(boolean f) {%n" +
|
||||
" if (%2$s == null) %2$s = new %3$s();%n %2$s.suspend(f);%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_SUSPEND_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME ,
|
||||
DIRTY_TRACKER_IMPL
|
||||
);
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public %s %s() { return %s; }",
|
||||
CollectionTracker.class.getName(),
|
||||
EnhancerConstants.TRACKER_COLLECTION_GET_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
);
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
cce.printStackTrace();
|
||||
|
@ -197,7 +219,7 @@ public class EntityEnhancer extends Enhancer {
|
|||
}
|
||||
if ( enhancementContext.isPersistentField( ctField ) ) {
|
||||
for ( CtClass ctClass : ctField.getType().getInterfaces() ) {
|
||||
if ( ctClass.getName().equals( Collection.class.getName() ) ) {
|
||||
if ( PersistentAttributesHelper.isAssignable( ctClass, Collection.class.getName() ) ) {
|
||||
collectionList.add( ctField );
|
||||
break;
|
||||
}
|
||||
|
@ -217,9 +239,7 @@ public class EntityEnhancer extends Enhancer {
|
|||
body.append(
|
||||
String.format(
|
||||
"private boolean %1$s() {%n" +
|
||||
" if (%2$s == null) {%n" +
|
||||
" return false;%n" +
|
||||
" }%n",
|
||||
" if (%2$s == null) { return false; }%n%n",
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
)
|
||||
|
@ -231,7 +251,7 @@ public class EntityEnhancer extends Enhancer {
|
|||
String.format(
|
||||
" // collection field [%1$s]%n" +
|
||||
" if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { return true; }%n" +
|
||||
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n",
|
||||
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n%n",
|
||||
ctField.getName(),
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
)
|
||||
|
@ -254,7 +274,7 @@ public class EntityEnhancer extends Enhancer {
|
|||
body.append(
|
||||
String.format(
|
||||
"private void %1$s(%3$s tracker) {%n" +
|
||||
" if (%2$s == null) { return; }%n",
|
||||
" if (%2$s == null) { return; }%n%n",
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME,
|
||||
DirtyTracker.class.getName()
|
||||
|
@ -267,7 +287,7 @@ public class EntityEnhancer extends Enhancer {
|
|||
String.format(
|
||||
" // Collection field [%1$s]%n" +
|
||||
" if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { tracker.add(\"%1$s\"); }%n" +
|
||||
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { tracker.add(\"%1$s\"); }%n",
|
||||
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { tracker.add(\"%1$s\"); }%n%n",
|
||||
ctField.getName(),
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
)
|
||||
|
@ -289,21 +309,35 @@ public class EntityEnhancer extends Enhancer {
|
|||
|
||||
body.append(
|
||||
String.format(
|
||||
"private void %1$s() {%n" +
|
||||
" if (%2$s == null) { %2$s = new %3$s(); }%n",
|
||||
"private void %1$s() {%n" +
|
||||
" if (%2$s == null) { %2$s = new %3$s(); }%n" +
|
||||
" %4$s lazyInterceptor = null;%n",
|
||||
EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME,
|
||||
CollectionTracker.class.getName()
|
||||
COLLECTION_TRACKER_IMPL,
|
||||
LazyAttributeLoader.class.getName()
|
||||
)
|
||||
);
|
||||
|
||||
if ( PersistentAttributesHelper.isAssignable( managedCtClass, PersistentAttributeInterceptable.class.getName() ) ) {
|
||||
body.append(
|
||||
String.format(
|
||||
" if(%1$s != null && %1$s instanceof %2$s) lazyInterceptor = (%2$s) %1$s;%n%n",
|
||||
EnhancerConstants.INTERCEPTOR_FIELD_NAME,
|
||||
LazyAttributeLoader.class.getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
|
||||
if ( !enhancementContext.isMappedCollection( ctField ) ) {
|
||||
body.append(
|
||||
String.format(
|
||||
" // Collection field [%1$s]%n" +
|
||||
" if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%n" +
|
||||
" else { %2$s.add(\"%1$s\", %1$s.size()); }%n",
|
||||
" // collection field [%1$s]%n" +
|
||||
" if (lazyInterceptor == null || lazyInterceptor.isAttributeLoaded(\"%1$s\")) {%n" +
|
||||
" if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%n" +
|
||||
" else { %2$s.add(\"%1$s\", %1$s.size()); }%n" +
|
||||
" }%n%n",
|
||||
ctField.getName(),
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
)
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.bytecode.enhance.internal;
|
||||
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import javassist.CannotCompileException;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
|
@ -13,12 +15,11 @@ import javassist.Modifier;
|
|||
import javassist.bytecode.AnnotationsAttribute;
|
||||
import javassist.bytecode.FieldInfo;
|
||||
import javassist.bytecode.annotation.Annotation;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
import javax.persistence.Transient;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
*/
|
||||
|
@ -50,7 +51,7 @@ public class FieldWriter {
|
|||
|
||||
private static void addPrivateTransient(CtClass target, CtClass type, String name) {
|
||||
addWithModifiers( target, type, name, Modifier.PRIVATE | Modifier.TRANSIENT, Transient.class );
|
||||
log.debugf( "Wrote field into [%s]: @Transient private transient %s %s;%n", target.getName(), type.getName(), name );
|
||||
log.debugf( "Wrote field into [%s]: @Transient private transient %s %s;", target.getName(), type.getName(), name );
|
||||
}
|
||||
|
||||
private static void addWithModifiers(CtClass target, CtClass type, String name, int modifiers, Class<?> ... annotations ) {
|
||||
|
|
|
@ -37,7 +37,7 @@ public class MethodWriter {
|
|||
public static CtMethod write(CtClass target, String format, Object ... args) throws CannotCompileException {
|
||||
final String body = String.format( format, args );
|
||||
// System.out.printf( "writing method into [%s]:%n%s%n", target.getName(), body );
|
||||
log.debugf( "writing method into [%s]:%n%s%n", target.getName(), body );
|
||||
log.debugf( "writing method into [%s]:%n%s", target.getName(), body );
|
||||
final CtMethod method = CtNewMethod.make( body, target );
|
||||
target.addMethod( method );
|
||||
return method;
|
||||
|
@ -47,7 +47,7 @@ public class MethodWriter {
|
|||
|
||||
public static CtMethod addGetter(CtClass target, String field, String name) {
|
||||
try {
|
||||
log.debugf( "Writing getter method [%s] into [%s] for field [%s]%n", name, target.getName(), field );
|
||||
log.debugf( "Writing getter method [%s] into [%s] for field [%s]", name, target.getName(), field );
|
||||
final CtMethod method = CtNewMethod.getter( name, target.getField( field ) );
|
||||
target.addMethod( method );
|
||||
return method;
|
||||
|
@ -64,7 +64,7 @@ public class MethodWriter {
|
|||
|
||||
public static CtMethod addSetter(CtClass target, String field, String name) {
|
||||
try {
|
||||
log.debugf( "Writing setter method [%s] into [%s] for field [%s]%n", name, target.getName(), field );
|
||||
log.debugf( "Writing setter method [%s] into [%s] for field [%s]", name, target.getName(), field );
|
||||
final CtMethod method = CtNewMethod.setter( name, target.getField( field ) );
|
||||
target.addMethod( method );
|
||||
return method;
|
||||
|
|
|
@ -9,7 +9,9 @@ package org.hibernate.bytecode.enhance.internal;
|
|||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
|
@ -26,9 +28,9 @@ import javassist.bytecode.CodeIterator;
|
|||
import javassist.bytecode.ConstPool;
|
||||
import javassist.bytecode.MethodInfo;
|
||||
import javassist.bytecode.Opcode;
|
||||
import javassist.bytecode.SignatureAttribute;
|
||||
import javassist.bytecode.stackmap.MapMaker;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
|
@ -119,13 +121,14 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
|
||||
// read attempts only have to deal lazy-loading support, not dirty checking;
|
||||
// so if the field is not enabled as lazy-loadable return a plain simple getter as the reader
|
||||
if ( !enhancementContext.isLazyLoadable( persistentField ) ) {
|
||||
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass )
|
||||
|| !enhancementContext.isLazyLoadable( persistentField ) ) {
|
||||
return MethodWriter.addGetter( managedCtClass, fieldName, readerName );
|
||||
}
|
||||
|
||||
try {
|
||||
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}",
|
||||
persistentField.getType().getName(),
|
||||
readerName,
|
||||
typeDescriptor.buildReadInterceptionBodyFragment( fieldName ),
|
||||
|
@ -160,13 +163,14 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
try {
|
||||
final CtMethod writer;
|
||||
|
||||
if ( !enhancementContext.isLazyLoadable( persistentField ) ) {
|
||||
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass )
|
||||
|| !enhancementContext.isLazyLoadable( persistentField ) ) {
|
||||
writer = MethodWriter.addSetter( managedCtClass, fieldName, writerName );
|
||||
}
|
||||
else {
|
||||
writer = MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public void %s(%s %s) {%n %s%n}",
|
||||
"public void %s(%s %s) {%n%s%n}",
|
||||
writerName,
|
||||
persistentField.getType().getName(),
|
||||
fieldName,
|
||||
|
@ -174,25 +178,26 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
);
|
||||
}
|
||||
|
||||
if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
writer.insertBefore(
|
||||
String.format(
|
||||
"if (%s != null) { %<s.callOwner(\".%s\"); }%n",
|
||||
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME,
|
||||
fieldName
|
||||
)
|
||||
);
|
||||
}
|
||||
else if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
writer.insertBefore(
|
||||
typeDescriptor.buildInLineDirtyCheckingBodyFragment(
|
||||
enhancementContext,
|
||||
persistentField
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
writer.insertBefore(
|
||||
String.format(
|
||||
" if (%1$s != null) { %1$s.callOwner(\"\"); }%n",
|
||||
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
writer.insertBefore(
|
||||
typeDescriptor.buildInLineDirtyCheckingBodyFragment(
|
||||
enhancementContext,
|
||||
persistentField
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
handleCompositeField( managedCtClass, persistentField, writer );
|
||||
handleCompositeField( managedCtClass, persistentField, writer );
|
||||
}
|
||||
|
||||
if ( enhancementContext.doBiDirectionalAssociationManagement( persistentField ) ) {
|
||||
handleBiDirectionalAssociation( managedCtClass, persistentField, writer );
|
||||
|
@ -219,21 +224,21 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
|
||||
private void handleBiDirectionalAssociation(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
|
||||
throws NotFoundException, CannotCompileException {
|
||||
if ( !isPossibleBiDirectionalAssociation( persistentField ) ) {
|
||||
if ( !PersistentAttributesHelper.isPossibleBiDirectionalAssociation( persistentField ) ) {
|
||||
return;
|
||||
}
|
||||
final CtClass targetEntity = getTargetEntityClass( persistentField );
|
||||
final CtClass targetEntity = PersistentAttributesHelper.getTargetEntityClass( managedCtClass, persistentField );
|
||||
if ( targetEntity == null ) {
|
||||
log.debugf(
|
||||
log.infof(
|
||||
"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.warnf(
|
||||
final String mappedBy = PersistentAttributesHelper.getMappedBy( persistentField, targetEntity, enhancementContext );
|
||||
if ( mappedBy == null || mappedBy.isEmpty() ) {
|
||||
log.infof(
|
||||
"Could not find bi-directional association for field [%s#%s]",
|
||||
managedCtClass.getName(),
|
||||
persistentField.getName()
|
||||
|
@ -244,32 +249,73 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
// create a temporary getter and setter on the target entity to be able to compile our code
|
||||
final String mappedByGetterName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + mappedBy;
|
||||
final String mappedBySetterName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy;
|
||||
MethodWriter.addGetter( targetEntity, mappedBy, mappedByGetterName );
|
||||
MethodWriter.addSetter( targetEntity, mappedBy, mappedBySetterName );
|
||||
CtMethod getter;
|
||||
CtMethod setter;
|
||||
boolean tmpTargetMethods = false;
|
||||
try {
|
||||
getter = targetEntity.getDeclaredMethod( mappedByGetterName );
|
||||
setter = targetEntity.getDeclaredMethod( mappedByGetterName );
|
||||
}
|
||||
catch (NotFoundException nfe){
|
||||
getter = MethodWriter.addGetter( targetEntity, mappedBy, mappedByGetterName );
|
||||
setter = MethodWriter.addSetter( targetEntity, mappedBy, mappedBySetterName );
|
||||
tmpTargetMethods = true;
|
||||
}
|
||||
|
||||
if ( persistentField.hasAnnotation( OneToOne.class ) ) {
|
||||
// code fragments to check loaded state. We don't want to trigger lazy loading in association management code
|
||||
String currentAssociationLoaded = String.format(
|
||||
"%s.isPropertyInitialized(this.%s, \"%s\")",
|
||||
Hibernate.class.getName(),
|
||||
persistentField.getName(),
|
||||
mappedBy
|
||||
);
|
||||
String targetElementLoaded = String.format(
|
||||
"%s.isPropertyInitialized(target, \"%s\")",
|
||||
Hibernate.class.getName(),
|
||||
mappedBy
|
||||
);
|
||||
String newAssociationLoaded = String.format(
|
||||
"%s.isPropertyInitialized($1, \"%s\")",
|
||||
Hibernate.class.getName(),
|
||||
mappedBy
|
||||
);
|
||||
|
||||
if ( PersistentAttributesHelper.hasAnnotation( persistentField, 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",
|
||||
" if (this.%1$s != null && %2$s && $1 != null) { this.%1$s.%3$s(null); }%n",
|
||||
persistentField.getName(),
|
||||
currentAssociationLoaded,
|
||||
mappedBySetterName
|
||||
)
|
||||
);
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
"if ($1 != null && $1.%s() != $0) $1.%s($0);%n",
|
||||
" if ($1 != null && %s && $1.%s() != this) { $1.%s(this); }%n",
|
||||
newAssociationLoaded,
|
||||
mappedByGetterName,
|
||||
mappedBySetterName
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( persistentField.hasAnnotation( OneToMany.class ) ) {
|
||||
if ( PersistentAttributesHelper.hasAnnotation( persistentField, OneToMany.class ) ) {
|
||||
boolean isMap = PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() );
|
||||
String toArrayMethod = isMap ? "values().toArray()" : "toArray()" ;
|
||||
|
||||
// only remove elements not in the new collection or else we would loose those elements
|
||||
// don't use iterator to avoid ConcurrentModException
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
"if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s(null); } }%n",
|
||||
" if (this.%3$s != null && %1$s) {%n" +
|
||||
" Object[] array = this.%3$s.%2$s;%n" +
|
||||
" for (int i = 0; i < array.length; i++) {%n" +
|
||||
" %4$s target = (%4$s) array[i];%n" +
|
||||
" if ($1 == null || !$1.contains(target)) { target.%5$s(null); }%n" +
|
||||
" }%n" +
|
||||
" }%n",
|
||||
currentAssociationLoaded,
|
||||
toArrayMethod,
|
||||
persistentField.getName(),
|
||||
targetEntity.getName(),
|
||||
mappedBySetterName
|
||||
|
@ -277,34 +323,63 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
);
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
"if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if (target.%s() != $0) target.%s((%s)$0); } }%n",
|
||||
" if ($1 != null && %1$s) {%n" +
|
||||
" Object[] array = $1.%2$s;%n" +
|
||||
" for (int i = 0; i < array.length; i++) {%n" +
|
||||
" %4$s target = (%4$s) array[i];%n" +
|
||||
" if (%3$s && target.%5$s() != this) { target.%6$s(this); }%n" +
|
||||
" }%n" +
|
||||
" }%n",
|
||||
newAssociationLoaded,
|
||||
toArrayMethod,
|
||||
targetElementLoaded,
|
||||
targetEntity.getName(),
|
||||
mappedByGetterName,
|
||||
mappedBySetterName,
|
||||
managedCtClass.getName()
|
||||
mappedBySetterName
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( persistentField.hasAnnotation( ManyToOne.class ) ) {
|
||||
if ( PersistentAttributesHelper.hasAnnotation( persistentField, 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",
|
||||
" if (this.%2$s != null && %1$s && this.%2$s.%3$s() != null) { this.%2$s.%3$s().remove(this); }%n",
|
||||
currentAssociationLoaded,
|
||||
persistentField.getName(),
|
||||
mappedByGetterName
|
||||
)
|
||||
);
|
||||
// check .contains($0) to avoid double inserts (but preventing duplicates)
|
||||
// check .contains(this) to avoid double inserts (but preventing duplicates)
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
"if ($1 != null) { java.util.Collection c = $1.%s(); if (c != null && !c.contains($0)) c.add($0); }%n",
|
||||
" if ($1 != null && %s) {%n" +
|
||||
" java.util.Collection c = $1.%s();%n" +
|
||||
" if (c != null && !c.contains(this)) { c.add(this); }%n" +
|
||||
" }%n",
|
||||
newAssociationLoaded,
|
||||
mappedByGetterName
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( persistentField.hasAnnotation( ManyToMany.class ) ) {
|
||||
if ( PersistentAttributesHelper.hasAnnotation( persistentField, ManyToMany.class ) ) {
|
||||
if ( PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() ) ||
|
||||
PersistentAttributesHelper.isAssignable( targetEntity.getField( mappedBy ).getType() , Map.class.getName() ) ) {
|
||||
log.infof(
|
||||
"Bi-directional association for field [%s#%s] not managed: @ManyToMany in java.util.Map attribute not supported ",
|
||||
managedCtClass.getName(),
|
||||
persistentField.getName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
"if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s().remove($0); } }%n",
|
||||
" if (this.%2$s != null && %1$s) {%n" +
|
||||
" Object[] array = this.%2$s.toArray();%n" +
|
||||
" for (int i = 0; i < array.length; i++) {%n" +
|
||||
" %3$s target = (%3$s) array[i];%n" +
|
||||
" if ($1 == null || !$1.contains(target)) { target.%4$s().remove(this); }%n" +
|
||||
" }%n" +
|
||||
" }%n",
|
||||
currentAssociationLoaded,
|
||||
persistentField.getName(),
|
||||
targetEntity.getName(),
|
||||
mappedByGetterName
|
||||
|
@ -312,106 +387,36 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
);
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
"if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; java.util.Collection c = target.%s(); if ( c != $0 && c != null) c.add($0); } }%n",
|
||||
" if ($1 != null && %s) {%n" +
|
||||
" Object[] array = $1.toArray();%n" +
|
||||
" for (int i = 0; i < array.length; i++) {%n" +
|
||||
" %s target = (%<s) array[i];%n" +
|
||||
" if (%s) {%n" +
|
||||
" java.util.Collection c = target.%s();%n" +
|
||||
" if (c != this && c != null) { c.add(this); }%n" +
|
||||
" }%n" +
|
||||
" }%n" +
|
||||
" }%n",
|
||||
newAssociationLoaded,
|
||||
targetEntity.getName(),
|
||||
targetElementLoaded,
|
||||
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() ...
|
||||
}
|
||||
|
||||
private boolean isPossibleBiDirectionalAssociation(CtField persistentField) {
|
||||
return persistentField.hasAnnotation( OneToOne.class ) ||
|
||||
persistentField.hasAnnotation( OneToMany.class ) ||
|
||||
persistentField.hasAnnotation( ManyToOne.class ) ||
|
||||
persistentField.hasAnnotation( ManyToMany.class );
|
||||
}
|
||||
|
||||
private String getMappedBy(CtField persistentField, CtClass targetEntity) {
|
||||
final String local = getMappedByFromAnnotation( persistentField );
|
||||
return local.isEmpty() ? getMappedByFromTargetEntity( persistentField, targetEntity ) : local;
|
||||
}
|
||||
|
||||
private String getMappedByFromAnnotation(CtField persistentField) {
|
||||
try {
|
||||
if ( persistentField.hasAnnotation( OneToOne.class ) ) {
|
||||
return ( (OneToOne) persistentField.getAnnotation( OneToOne.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 ( tmpTargetMethods ) {
|
||||
targetEntity.removeMethod( getter );
|
||||
targetEntity.removeMethod( setter );
|
||||
}
|
||||
catch (ClassNotFoundException ignore) {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
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() ) {
|
||||
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()
|
||||
);
|
||||
return f.getName();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private CtClass getTargetEntityClass(CtField persistentField) throws NotFoundException {
|
||||
// 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( OneToMany.class ) ) {
|
||||
targetClass = ( (OneToMany) persistentField.getAnnotation( OneToMany.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 ( targetClass != null && targetClass != void.class ) {
|
||||
return classPool.get( targetClass.getName() );
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ignore) {
|
||||
}
|
||||
|
||||
// infer targetEntity from generic type signature
|
||||
if ( persistentField.hasAnnotation( OneToOne.class ) || persistentField.hasAnnotation( ManyToOne.class ) ) {
|
||||
return persistentField.getType();
|
||||
}
|
||||
if ( persistentField.hasAnnotation( OneToMany.class ) || persistentField.hasAnnotation( ManyToMany.class ) ) {
|
||||
try {
|
||||
final SignatureAttribute.TypeArgument target = ( (SignatureAttribute.ClassType) SignatureAttribute.toFieldSignature(
|
||||
persistentField.getGenericSignature()
|
||||
) ).getTypeArguments()[0];
|
||||
return persistentField.getDeclaringClass().getClassPool().get( target.toString() );
|
||||
}
|
||||
catch (BadBytecode ignore) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void handleCompositeField(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
|
||||
throws NotFoundException, CannotCompileException {
|
||||
if ( !persistentField.hasAnnotation( Embedded.class ) ) {
|
||||
if ( !enhancementContext.isCompositeClass( persistentField.getType() ) ||
|
||||
!PersistentAttributesHelper.hasAnnotation( persistentField, Embedded.class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -421,9 +426,9 @@ 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, "" +
|
||||
managedCtClass,
|
||||
"public void %1$s(String name) {%n" +
|
||||
" if (%2$s != null) { %2$s.callOwner(\".\" + name) ; }%n}",
|
||||
" if (%2$s != null) { %2$s.callOwner(\".\" + name); }%n}",
|
||||
EnhancerConstants.TRACKER_CHANGER_NAME,
|
||||
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME
|
||||
);
|
||||
|
@ -432,7 +437,6 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
// cleanup previous owner
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
"" +
|
||||
"if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n",
|
||||
persistentField.getName(),
|
||||
CompositeTracker.class.getName(),
|
||||
|
@ -443,7 +447,6 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
// trigger track changes
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
"" +
|
||||
"((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this);%n" +
|
||||
"%5$s(\"%1$s\");",
|
||||
persistentField.getName(),
|
||||
|
@ -563,6 +566,7 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
}
|
||||
if ( targetCtClass == managedCtClass
|
||||
|| !enhancementContext.isPersistentField( targetCtClass.getField( fieldName ) )
|
||||
|| PersistentAttributesHelper.hasAnnotation( targetCtClass, fieldName, Id.class )
|
||||
|| "this$0".equals( fieldName ) ) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,384 @@
|
|||
/*
|
||||
* 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.bytecode.enhance.internal;
|
||||
|
||||
import java.beans.Introspector;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
import javassist.CtMember;
|
||||
import javassist.CtMethod;
|
||||
import javassist.NotFoundException;
|
||||
import javassist.bytecode.BadBytecode;
|
||||
import javassist.bytecode.SignatureAttribute;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
/**
|
||||
* util methods to fetch attribute metadata. consistent for both field and property access types.
|
||||
*
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
* @see org.hibernate.internal.util.ReflectHelper
|
||||
*/
|
||||
public class PersistentAttributesHelper {
|
||||
|
||||
private PersistentAttributesHelper() {
|
||||
}
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributesHelper.class );
|
||||
|
||||
public static boolean hasAnnotation(CtField attribute, Class<? extends Annotation> annotation) {
|
||||
return getAnnotation( attribute, annotation ) != null;
|
||||
}
|
||||
|
||||
public static boolean hasAnnotation(CtClass ctClass, String attributeName, Class<? extends Annotation> annotation) {
|
||||
return getAnnotation( ctClass, attributeName, annotation ) != null;
|
||||
}
|
||||
|
||||
public static <T extends Annotation> T getAnnotation(CtField attribute, Class<T> annotation) {
|
||||
return getAnnotation( attribute.getDeclaringClass(), attribute.getName(), annotation );
|
||||
}
|
||||
|
||||
public static <T extends Annotation> T getAnnotation(CtClass ctClass, String attributeName, Class<T> annotation) {
|
||||
AccessType classAccessType = getAccessTypeOrNull( ctClass );
|
||||
CtField field = findFieldOrNull( ctClass, attributeName );
|
||||
CtMethod getter = findGetterOrNull( ctClass, attributeName );
|
||||
|
||||
if ( classAccessType == AccessType.FIELD || ( field != null && getAccessTypeOrNull( field ) == AccessType.FIELD ) ) {
|
||||
return field == null ? null : getAnnotationOrNull( field, annotation );
|
||||
}
|
||||
if ( classAccessType == AccessType.PROPERTY || ( getter != null && getAccessTypeOrNull( getter ) == AccessType.PROPERTY ) ) {
|
||||
return getter == null ? null : getAnnotationOrNull( getter, annotation );
|
||||
}
|
||||
|
||||
T found = ( getter == null ? null : getAnnotationOrNull( getter, annotation ) );
|
||||
if ( found == null && field != null ) {
|
||||
return getAnnotationOrNull( field, annotation );
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private static <T extends Annotation> T getAnnotationOrNull(CtMember ctMember, Class<T> annotation) {
|
||||
try {
|
||||
if ( ctMember.hasAnnotation( annotation ) ) {
|
||||
return annotation.cast( ctMember.getAnnotation( annotation ) );
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException cnfe) {
|
||||
// should never happen
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static AccessType getAccessTypeOrNull(CtMember ctMember) {
|
||||
Access access = getAnnotationOrNull( ctMember, Access.class );
|
||||
return access == null ? null : access.value();
|
||||
}
|
||||
|
||||
private static AccessType getAccessTypeOrNull(CtClass ctClass) {
|
||||
try {
|
||||
if ( ctClass.hasAnnotation( Access.class ) ) {
|
||||
return ( (Access) ctClass.getAnnotation( Access.class ) ).value();
|
||||
}
|
||||
else {
|
||||
CtClass extendsClass = ctClass.getSuperclass();
|
||||
return extendsClass == null ? null : getAccessTypeOrNull( extendsClass );
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/**
|
||||
* duplicated here to take CtClass instead of Class
|
||||
* @see org.hibernate.internal.util.ReflectHelper#locateField
|
||||
*/
|
||||
private static CtField findFieldOrNull(CtClass ctClass, String propertyName) {
|
||||
if ( ctClass == null ) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return ctClass.getField( propertyName );
|
||||
}
|
||||
catch ( NotFoundException nsfe ) {
|
||||
try {
|
||||
return findFieldOrNull( ctClass.getSuperclass(), propertyName );
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* duplicated here to take CtClass instead of Class
|
||||
* @see org.hibernate.internal.util.ReflectHelper#findGetterMethod
|
||||
*/
|
||||
private static CtMethod findGetterOrNull(CtClass ctClass, String propertyName) {
|
||||
if ( ctClass == null ) {
|
||||
return null;
|
||||
}
|
||||
CtMethod method = getterOrNull( ctClass, propertyName );
|
||||
if ( method != null ) {
|
||||
return method;
|
||||
}
|
||||
try {
|
||||
// check if extends
|
||||
method = findGetterOrNull( ctClass.getSuperclass(), propertyName );
|
||||
if ( method != null ) {
|
||||
return method;
|
||||
}
|
||||
// check if implements
|
||||
for ( CtClass interfaceCtClass : ctClass.getInterfaces() ) {
|
||||
method = getterOrNull( interfaceCtClass, propertyName );
|
||||
if ( method != null ) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NotFoundException nfe) {
|
||||
// give up
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static CtMethod getterOrNull(CtClass containerClass, String propertyName) {
|
||||
for ( CtMethod method : containerClass.getDeclaredMethods() ) {
|
||||
try {
|
||||
// if the method has parameters, skip it
|
||||
if ( method.isEmpty() || method.getParameterTypes().length != 0 ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String methodName = method.getName();
|
||||
|
||||
// try "get"
|
||||
if ( methodName.startsWith( "get" ) ) {
|
||||
String testStdMethod = Introspector.decapitalize( methodName.substring( 3 ) );
|
||||
String testOldMethod = methodName.substring( 3 );
|
||||
if ( testStdMethod.equals( propertyName ) || testOldMethod.equals( propertyName ) ) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
// if not "get", then try "is"
|
||||
if ( methodName.startsWith( "is" ) ) {
|
||||
String testStdMethod = Introspector.decapitalize( methodName.substring( 2 ) );
|
||||
String testOldMethod = methodName.substring( 2 );
|
||||
if ( testStdMethod.equals( propertyName ) || testOldMethod.equals( propertyName ) ) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public static boolean isPossibleBiDirectionalAssociation(CtField persistentField) {
|
||||
return PersistentAttributesHelper.hasAnnotation( persistentField, OneToOne.class ) ||
|
||||
PersistentAttributesHelper.hasAnnotation( persistentField, OneToMany.class ) ||
|
||||
PersistentAttributesHelper.hasAnnotation( persistentField, ManyToOne.class ) ||
|
||||
PersistentAttributesHelper.hasAnnotation( persistentField, ManyToMany.class );
|
||||
}
|
||||
|
||||
public static String getMappedBy(CtField persistentField, CtClass targetEntity, EnhancementContext context) {
|
||||
final String local = getMappedByFromAnnotation( persistentField );
|
||||
return local.isEmpty() ? getMappedByFromTargetEntity( persistentField, targetEntity, context ) : local;
|
||||
}
|
||||
|
||||
private static String getMappedByFromAnnotation(CtField persistentField) {
|
||||
|
||||
OneToOne oto = PersistentAttributesHelper.getAnnotation( persistentField, OneToOne.class );
|
||||
if ( oto != null ) {
|
||||
return oto.mappedBy();
|
||||
}
|
||||
|
||||
OneToMany otm = PersistentAttributesHelper.getAnnotation( persistentField, OneToMany.class );
|
||||
if ( otm != null ) {
|
||||
return otm.mappedBy();
|
||||
}
|
||||
|
||||
// For @ManyToOne associations, mappedBy must come from the @OneToMany side of the association
|
||||
|
||||
ManyToMany mtm = PersistentAttributesHelper.getAnnotation( persistentField, ManyToMany.class );
|
||||
return mtm == null ? "" : mtm.mappedBy();
|
||||
}
|
||||
|
||||
private static String getMappedByFromTargetEntity(
|
||||
CtField persistentField,
|
||||
CtClass targetEntity,
|
||||
EnhancementContext context) {
|
||||
// get mappedBy value by searching in the fields of the target entity class
|
||||
for ( CtField f : targetEntity.getDeclaredFields() ) {
|
||||
if ( context.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()
|
||||
);
|
||||
return f.getName();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static CtClass getTargetEntityClass(CtClass managedCtClass, CtField persistentField) throws NotFoundException {
|
||||
// get targetEntity defined in the annotation
|
||||
try {
|
||||
OneToOne oto = PersistentAttributesHelper.getAnnotation( persistentField, OneToOne.class );
|
||||
OneToMany otm = PersistentAttributesHelper.getAnnotation( persistentField, OneToMany.class );
|
||||
ManyToOne mto = PersistentAttributesHelper.getAnnotation( persistentField, ManyToOne.class );
|
||||
ManyToMany mtm = PersistentAttributesHelper.getAnnotation( persistentField, ManyToMany.class );
|
||||
|
||||
Class<?> targetClass = null;
|
||||
if ( oto != null ) {
|
||||
targetClass = oto.targetEntity();
|
||||
}
|
||||
if ( otm != null ) {
|
||||
targetClass = otm.targetEntity();
|
||||
}
|
||||
if ( mto != null ) {
|
||||
targetClass = mto.targetEntity();
|
||||
}
|
||||
if ( mtm != null ) {
|
||||
targetClass = mtm.targetEntity();
|
||||
}
|
||||
|
||||
if ( targetClass != null && targetClass != void.class ) {
|
||||
return managedCtClass.getClassPool().get( targetClass.getName() );
|
||||
}
|
||||
}
|
||||
catch (NotFoundException ignore) {
|
||||
}
|
||||
|
||||
// infer targetEntity from generic type signature
|
||||
String inferredTypeName = inferTypeName( managedCtClass, persistentField.getName() );
|
||||
return inferredTypeName == null ? null : managedCtClass.getClassPool().get( inferredTypeName );
|
||||
}
|
||||
|
||||
/**
|
||||
* Consistent with hasAnnotation()
|
||||
*/
|
||||
private static String inferTypeName(CtClass ctClass, String attributeName ) {
|
||||
AccessType classAccessType = getAccessTypeOrNull( ctClass );
|
||||
CtField field = findFieldOrNull( ctClass, attributeName );
|
||||
CtMethod getter = findGetterOrNull( ctClass, attributeName );
|
||||
|
||||
if ( classAccessType == AccessType.FIELD || ( field != null && getAccessTypeOrNull( field ) == AccessType.FIELD ) ) {
|
||||
return field == null ? null : inferFieldTypeName( field );
|
||||
}
|
||||
if ( classAccessType == AccessType.PROPERTY || ( getter != null && getAccessTypeOrNull( getter ) == AccessType.PROPERTY ) ) {
|
||||
return getter == null ? null : inferMethodTypeName( getter );
|
||||
}
|
||||
|
||||
String found = ( getter == null ? null : inferMethodTypeName( getter ) );
|
||||
if ( found == null && field != null ) {
|
||||
return inferFieldTypeName( field );
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private static String inferFieldTypeName(CtField field) {
|
||||
try {
|
||||
if ( field.getFieldInfo().getAttribute( SignatureAttribute.tag ) == null ){
|
||||
return field.getType().getName();
|
||||
}
|
||||
return inferGenericTypeName(
|
||||
field.getType(),
|
||||
SignatureAttribute.toTypeSignature( field.getGenericSignature() )
|
||||
);
|
||||
}
|
||||
catch (BadBytecode ignore) {
|
||||
return null;
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String inferMethodTypeName(CtMethod method) {
|
||||
try {
|
||||
if ( method.getMethodInfo().getAttribute( SignatureAttribute.tag ) == null ){
|
||||
return method.getReturnType().getName();
|
||||
}
|
||||
return inferGenericTypeName(
|
||||
method.getReturnType(),
|
||||
SignatureAttribute.toMethodSignature( method.getGenericSignature() ).getReturnType()
|
||||
);
|
||||
}
|
||||
catch (BadBytecode ignore) {
|
||||
return null;
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String inferGenericTypeName(CtClass ctClass, SignatureAttribute.Type genericSignature) {
|
||||
// infer targetEntity from generic type signature
|
||||
if ( isAssignable( ctClass, Collection.class.getName() ) ) {
|
||||
return ( (SignatureAttribute.ClassType) genericSignature ).getTypeArguments()[0].toString();
|
||||
}
|
||||
if ( isAssignable( ctClass, Map.class.getName() ) ) {
|
||||
return ( (SignatureAttribute.ClassType) genericSignature ).getTypeArguments()[1].toString();
|
||||
}
|
||||
return ctClass.getName();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public static boolean isAssignable(CtClass thisCtClass, String targetClassName) {
|
||||
if ( thisCtClass == null ) {
|
||||
return false;
|
||||
}
|
||||
if ( thisCtClass.getName().equals( targetClassName ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// check if extends
|
||||
if ( isAssignable( thisCtClass.getSuperclass(), targetClassName ) ) {
|
||||
return true;
|
||||
}
|
||||
// check if implements
|
||||
for ( CtClass interfaceCtClass : thisCtClass.getInterfaces() ) {
|
||||
if ( isAssignable( interfaceCtClass, targetClassName ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
// keep going
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -22,4 +22,6 @@ public interface DirtyTracker {
|
|||
boolean isEmpty();
|
||||
|
||||
String[] get();
|
||||
|
||||
void suspend(boolean suspend);
|
||||
}
|
||||
|
|
|
@ -8,21 +8,24 @@ package org.hibernate.bytecode.enhance.internal.tracker;
|
|||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
|
||||
|
||||
/**
|
||||
* small low memory class to keep track of the number of elements in a collection
|
||||
*
|
||||
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
public final class CollectionTracker {
|
||||
public final class SimpleCollectionTracker implements CollectionTracker {
|
||||
|
||||
private String[] names;
|
||||
private int[] sizes;
|
||||
|
||||
public CollectionTracker() {
|
||||
public SimpleCollectionTracker() {
|
||||
names = new String[0];
|
||||
sizes = new int[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(String name, int size) {
|
||||
for ( int i = 0; i < names.length; i++ ) {
|
||||
if ( names[i].equals( name ) ) {
|
||||
|
@ -36,6 +39,7 @@ public final class CollectionTracker {
|
|||
sizes[sizes.length - 1] = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(String name) {
|
||||
for ( int i = 0; i < names.length; i++ ) {
|
||||
if ( name.equals( names[i] ) ) {
|
|
@ -19,6 +19,7 @@ import java.util.Arrays;
|
|||
public final class SimpleFieldTracker implements DirtyTracker {
|
||||
|
||||
private String[] names;
|
||||
private boolean suspended;
|
||||
|
||||
public SimpleFieldTracker() {
|
||||
names = new String[0];
|
||||
|
@ -26,6 +27,9 @@ public final class SimpleFieldTracker implements DirtyTracker {
|
|||
|
||||
@Override
|
||||
public void add(String name) {
|
||||
if ( suspended ) {
|
||||
return;
|
||||
}
|
||||
if ( !contains( name ) ) {
|
||||
names = Arrays.copyOf( names, names.length + 1 );
|
||||
names[names.length - 1] = name;
|
||||
|
@ -44,7 +48,9 @@ public final class SimpleFieldTracker implements DirtyTracker {
|
|||
|
||||
@Override
|
||||
public void clear() {
|
||||
names = new String[0];
|
||||
if ( !isEmpty() ) {
|
||||
names = new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -57,4 +63,9 @@ public final class SimpleFieldTracker implements DirtyTracker {
|
|||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suspend(boolean suspend) {
|
||||
this.suspended = suspend;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ package org.hibernate.bytecode.enhance.internal.tracker;
|
|||
public final class SortedFieldTracker implements DirtyTracker {
|
||||
|
||||
private String[] names;
|
||||
private boolean suspended;
|
||||
|
||||
public SortedFieldTracker() {
|
||||
names = new String[0];
|
||||
|
@ -23,6 +24,9 @@ public final class SortedFieldTracker implements DirtyTracker {
|
|||
|
||||
@Override
|
||||
public void add(String name) {
|
||||
if ( suspended ) {
|
||||
return;
|
||||
}
|
||||
// we do a binary search: even if we don't find the name at least we get the position to insert into the array
|
||||
int insert = 0;
|
||||
for ( int low = 0, high = names.length - 1; low <= high; ) {
|
||||
|
@ -70,7 +74,9 @@ public final class SortedFieldTracker implements DirtyTracker {
|
|||
|
||||
@Override
|
||||
public void clear() {
|
||||
names = new String[0];
|
||||
if ( !isEmpty() ) {
|
||||
names = new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -83,4 +89,9 @@ public final class SortedFieldTracker implements DirtyTracker {
|
|||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suspend(boolean suspend) {
|
||||
this.suspended = suspend;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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.bytecode.enhance.spi;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by collection trackers that hold the expected size od collections, a simplified Map<String, int>.
|
||||
*
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
*/
|
||||
public interface CollectionTracker {
|
||||
|
||||
void add(String name, int size);
|
||||
|
||||
int getSize(String name);
|
||||
}
|
|
@ -137,6 +137,16 @@ public final class EnhancerConstants {
|
|||
*/
|
||||
public static final String TRACKER_CLEAR_NAME = "$$_hibernate_clearDirtyAttributes";
|
||||
|
||||
/**
|
||||
* Name of method to suspend dirty tracking
|
||||
*/
|
||||
public static final String TRACKER_SUSPEND_NAME = "$$_hibernate_suspendDirtyTracking";
|
||||
|
||||
/**
|
||||
* Name of method to check if collection fields are dirty
|
||||
*/
|
||||
public static final String TRACKER_COLLECTION_GET_NAME = "$$_hibernate_getCollectionTracker";
|
||||
|
||||
/**
|
||||
* Name of method to check if collection fields are dirty
|
||||
*/
|
||||
|
|
|
@ -7,12 +7,15 @@
|
|||
|
||||
package org.hibernate.bytecode.enhance.spi.interceptor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.LazyInitializationException;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker;
|
||||
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
|
||||
import org.hibernate.bytecode.instrumentation.spi.LazyPropertyInitializer;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
|
||||
/**
|
||||
|
@ -51,11 +54,10 @@ public class LazyAttributeLoader implements PersistentAttributeInterceptor {
|
|||
);
|
||||
|
||||
initializedFields.add( fieldName );
|
||||
takeCollectionSizeSnapshot( target, fieldName, loadedValue );
|
||||
return loadedValue;
|
||||
}
|
||||
else {
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public final void setSession(SessionImplementor session) {
|
||||
|
@ -90,7 +92,17 @@ public class LazyAttributeLoader implements PersistentAttributeInterceptor {
|
|||
return "LazyAttributeLoader(entityName=" + entityName + " ,lazyFields=" + lazyFields + ')';
|
||||
}
|
||||
|
||||
/* --- */
|
||||
//
|
||||
|
||||
private void takeCollectionSizeSnapshot(Object target, String fieldName, Object value) {
|
||||
if ( value != null && value instanceof Collection && target instanceof SelfDirtinessTracker ) {
|
||||
CollectionTracker tracker = ( (SelfDirtinessTracker) target ).$$_hibernate_getCollectionTracker();
|
||||
if ( tracker == null ) {
|
||||
( (SelfDirtinessTracker) target ).$$_hibernate_clearDirtyAttributes();
|
||||
}
|
||||
tracker.add( fieldName, ( (Collection) value ).size() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean(Object obj, String name, boolean oldValue) {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.engine.spi;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -41,4 +43,14 @@ public interface SelfDirtinessTracker {
|
|||
* Clear the stored dirty attributes
|
||||
*/
|
||||
void $$_hibernate_clearDirtyAttributes();
|
||||
|
||||
/**
|
||||
* Temporarily enable / disable dirty tracking
|
||||
*/
|
||||
void $$_hibernate_suspendDirtyTracking(boolean suspend);
|
||||
|
||||
/**
|
||||
* Get access to the CollectionTracker
|
||||
*/
|
||||
CollectionTracker $$_hibernate_getCollectionTracker();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue