package org.hibernate.bytecode.enhance.spi;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.ConstPool;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.bytecode.SignatureAttribute;
import javassist.bytecode.StackMapTable;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.stackmap.MapMaker;
import org.hibernate.HibernateException;
import org.hibernate.bytecode.enhance.EnhancementException;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedComposite;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SelfDirtinessTracker;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
public class Enhancer {
private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class );
private final EnhancementContext enhancementContext;
private final ClassPool classPool;
private final CtClass managedEntityCtClass;
private final CtClass managedCompositeCtClass;
private final CtClass attributeInterceptorCtClass;
private final CtClass attributeInterceptableCtClass;
private final CtClass entityEntryCtClass;
private final CtClass objectCtClass;
private boolean isComposite;
public Enhancer(EnhancementContext enhancementContext) {
this.enhancementContext = enhancementContext;
this.classPool = buildClassPool( enhancementContext );
try {
this.managedEntityCtClass = classPool.makeClass(
ManagedEntity.class.getClassLoader().getResourceAsStream(
ManagedEntity.class.getName().replace( '.', '/' ) + ".class"
)
);
this.managedCompositeCtClass = classPool.makeClass(
ManagedComposite.class.getClassLoader().getResourceAsStream(
ManagedComposite.class.getName().replace( '.', '/' ) + ".class"
)
);
this.attributeInterceptableCtClass = classPool.makeClass(
PersistentAttributeInterceptable.class.getClassLoader().getResourceAsStream(
PersistentAttributeInterceptable.class.getName().replace( '.', '/' ) + ".class"
)
);
this.attributeInterceptorCtClass = classPool.makeClass(
PersistentAttributeInterceptor.class.getClassLoader().getResourceAsStream(
PersistentAttributeInterceptor.class.getName().replace( '.', '/' ) + ".class"
)
);
this.entityEntryCtClass = classPool.makeClass( EntityEntry.class.getName() );
}
catch (IOException e) {
throw new EnhancementException( "Could not prepare Javassist ClassPool", e );
}
try {
this.objectCtClass = classPool.getCtClass( Object.class.getName() );
}
catch (NotFoundException e) {
throw new EnhancementException( "Could not prepare Javassist ClassPool", e );
}
}
private ClassPool buildClassPool(EnhancementContext enhancementContext) {
final ClassPool classPool = new ClassPool( false );
final ClassLoader loadingClassLoader = enhancementContext.getLoadingClassLoader();
if ( loadingClassLoader != null ) {
classPool.appendClassPath( new LoaderClassPath( loadingClassLoader ) );
}
return classPool;
}
public byte[] enhance(String className, byte[] originalBytes) throws EnhancementException {
final CtClass managedCtClass;
try {
managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) );
}
catch (IOException e) {
log.unableToBuildEnhancementMetamodel( className );
return originalBytes;
}
enhance( managedCtClass, false );
return getByteCode( managedCtClass );
}
public byte[] enhanceComposite(String className, byte[] originalBytes) throws EnhancementException {
final CtClass managedCtClass;
try {
managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) );
}
catch (IOException e) {
log.unableToBuildEnhancementMetamodel( className );
return originalBytes;
}
enhance( managedCtClass, true );
return getByteCode( managedCtClass );
}
private byte[] getByteCode(CtClass managedCtClass) {
final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
final DataOutputStream out;
try {
out = new DataOutputStream( byteStream );
try {
managedCtClass.toBytecode( out );
return byteStream.toByteArray();
}
finally {
try {
out.close();
}
catch (IOException e) {
}
}
}
catch (Exception e) {
log.unableToTransformClass( e.getMessage() );
throw new HibernateException( "Unable to transform class: " + e.getMessage() );
}
}
private void enhance(CtClass managedCtClass, boolean isComposite) {
this.isComposite = isComposite;
final String className = managedCtClass.getName();
log.debugf( "Enhancing %s", className );
if ( managedCtClass.isInterface() ) {
log.debug( "skipping enhancement : interface" );
return;
}
final String[] interfaceNames = managedCtClass.getClassFile2().getInterfaces();
for ( String interfaceName : interfaceNames ) {
if ( ManagedEntity.class.getName().equals( interfaceName )
|| ManagedComposite.class.getName().equals( interfaceName ) ) {
log.debug( "skipping enhancement : already enhanced" );
return;
}
}
if ( !isComposite && enhancementContext.isEntityClass( managedCtClass ) ) {
enhanceAsEntity( managedCtClass );
}
else if ( isComposite || enhancementContext.isCompositeClass( managedCtClass ) ) {
enhanceAsComposite( managedCtClass );
}
else {
log.debug( "skipping enhancement : not entity or composite" );
}
}
private void enhanceAsEntity(CtClass managedCtClass) {
managedCtClass.addInterface( managedEntityCtClass );
enhancePersistentAttributes( managedCtClass );
addEntityInstanceHandling( managedCtClass );
addEntityEntryHandling( managedCtClass );
addLinkedPreviousHandling( managedCtClass );
addLinkedNextHandling( managedCtClass );
}
private void enhanceAsComposite(CtClass managedCtClass) {
enhancePersistentAttributes( managedCtClass );
}
private void addEntityInstanceHandling(CtClass managedCtClass) {
try {
managedCtClass.addMethod(
CtNewMethod.make(
objectCtClass,
EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME,
new CtClass[0],
new CtClass[0],
"{ return this; }",
managedCtClass
)
);
}
catch (CannotCompileException e) {
throw new EnhancementException(
String.format(
"Could not enhance entity class [%s] to add EntityEntry getter",
managedCtClass.getName()
),
e
);
}
}
private void addEntityEntryHandling(CtClass managedCtClass) {
addFieldWithGetterAndSetter(
managedCtClass,
entityEntryCtClass,
EnhancerConstants.ENTITY_ENTRY_FIELD_NAME,
EnhancerConstants.ENTITY_ENTRY_GETTER_NAME,
EnhancerConstants.ENTITY_ENTRY_SETTER_NAME
);
}
private void addLinkedPreviousHandling(CtClass managedCtClass) {
addFieldWithGetterAndSetter(
managedCtClass,
managedEntityCtClass,
EnhancerConstants.PREVIOUS_FIELD_NAME,
EnhancerConstants.PREVIOUS_GETTER_NAME,
EnhancerConstants.PREVIOUS_SETTER_NAME
);
}
private void addLinkedNextHandling(CtClass managedCtClass) {
addFieldWithGetterAndSetter(
managedCtClass,
managedEntityCtClass,
EnhancerConstants.NEXT_FIELD_NAME,
EnhancerConstants.NEXT_GETTER_NAME,
EnhancerConstants.NEXT_SETTER_NAME
);
}
private AnnotationsAttribute getVisibleAnnotations(FieldInfo fieldInfo) {
AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) fieldInfo.getAttribute( AnnotationsAttribute.visibleTag );
if ( annotationsAttribute == null ) {
annotationsAttribute = new AnnotationsAttribute(
fieldInfo.getConstPool(),
AnnotationsAttribute.visibleTag
);
fieldInfo.addAttribute( annotationsAttribute );
}
return annotationsAttribute;
}
private void enhancePersistentAttributes(CtClass managedCtClass) {
addInterceptorHandling( managedCtClass );
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
addInLineDirtyHandling( managedCtClass );
}
final IdentityHashMap<String, PersistentAttributeDescriptor> attrDescriptorMap
= new IdentityHashMap<String, PersistentAttributeDescriptor>();
for ( CtField persistentField : collectPersistentFields( managedCtClass ) ) {
attrDescriptorMap.put(
persistentField.getName(),
enhancePersistentAttribute( managedCtClass, persistentField )
);
}
transformFieldAccessesIntoReadsAndWrites( managedCtClass, attrDescriptorMap );
}
private PersistentAttributeDescriptor enhancePersistentAttribute(CtClass managedCtClass, CtField persistentField) {
try {
final AttributeTypeDescriptor typeDescriptor = resolveAttributeTypeDescriptor( persistentField );
return new PersistentAttributeDescriptor(
persistentField,
generateFieldReader( managedCtClass, persistentField, typeDescriptor ),
generateFieldWriter( managedCtClass, persistentField, typeDescriptor ),
typeDescriptor
);
}
catch (Exception e) {
throw new EnhancementException(
String.format(
"Unable to enhance persistent attribute [%s:%s]",
managedCtClass.getName(),
persistentField.getName()
),
e
);
}
}
private CtField[] collectPersistentFields(CtClass managedCtClass) {
final List<CtField> persistentFieldList = new ArrayList<CtField>();
for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
if ( Modifier.isStatic( ctField.getModifiers() ) ) {
continue;
}
if ( ctField.getName().startsWith( "$" ) ) {
continue;
}
if ( enhancementContext.isPersistentField( ctField ) ) {
persistentFieldList.add( ctField );
}
}
return enhancementContext.order( persistentFieldList.toArray( new CtField[persistentFieldList.size()] ) );
}
private List<CtField> collectCollectionFields(CtClass managedCtClass) {
final List<CtField> collectionList = new ArrayList<CtField>();
try {
for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
if ( Modifier.isStatic( ctField.getModifiers() ) ) {
continue;
}
if ( ctField.getName().startsWith( "$" ) ) {
continue;
}
if ( enhancementContext.isPersistentField( ctField ) ) {
for ( CtClass ctClass : ctField.getType().getInterfaces() ) {
if ( ctClass.getName().equals( "java.util.Collection" ) ) {
collectionList.add( ctField );
break;
}
}
}
}
}
catch (NotFoundException ignored) {
}
return collectionList;
}
private void addInterceptorHandling(CtClass managedCtClass) {
if ( enhancementContext.doDirtyCheckingInline( managedCtClass )
&& !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) {
return;
}
log.debug( "Weaving in PersistentAttributeInterceptable implementation" );
managedCtClass.addInterface( attributeInterceptableCtClass );
addFieldWithGetterAndSetter(
managedCtClass,
attributeInterceptorCtClass,
EnhancerConstants.INTERCEPTOR_FIELD_NAME,
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
EnhancerConstants.INTERCEPTOR_SETTER_NAME
);
}
private boolean isClassAlreadyTrackingDirtyStatus(CtClass managedCtClass) {
try {
for ( CtClass ctInterface : managedCtClass.getInterfaces() ) {
if ( ctInterface.getName().equals( SelfDirtinessTracker.class.getName() ) ) {
return true;
}
}
}
catch (NotFoundException e) {
e.printStackTrace();
}
return false;
}
private void addInLineDirtyHandling(CtClass managedCtClass) {
try {
if ( isComposite ) {
managedCtClass.addInterface( classPool.get( "org.hibernate.engine.spi.CompositeTracker" ) );
CtClass compositeCtType = classPool.get( "org.hibernate.bytecode.enhance.spi.CompositeOwnerTracker" );
addField( managedCtClass, compositeCtType, EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, true );
createCompositeTrackerMethod( managedCtClass );
}
else {
managedCtClass.addInterface( classPool.get( "org.hibernate.engine.spi.SelfDirtinessTracker" ) );
CtClass trackerCtType = classPool.get( "java.util.Set" );
addField( managedCtClass, trackerCtType, EnhancerConstants.TRACKER_FIELD_NAME, true );
CtClass collectionTrackerCtType = classPool.get( "org.hibernate.bytecode.enhance.spi.CollectionTracker" );
addField( managedCtClass, collectionTrackerCtType, EnhancerConstants.TRACKER_COLLECTION_NAME, true );
createDirtyTrackerMethods( managedCtClass );
}
}
catch (NotFoundException e) {
e.printStackTrace();
}
}
private void createDirtyTrackerMethods(CtClass managedCtClass) {
try {
String trackerChangeMethod =
"public void " + EnhancerConstants.TRACKER_CHANGER_NAME + "(String name) {" +
" if(" + EnhancerConstants.TRACKER_FIELD_NAME + " == null) {" +
" " + EnhancerConstants.TRACKER_FIELD_NAME + " = new java.util.HashSet();" +
" }" +
" if(!" + EnhancerConstants.TRACKER_FIELD_NAME + ".contains(name)) {" +
" " + EnhancerConstants.TRACKER_FIELD_NAME + ".add(name);" +
" }" +
"}";
managedCtClass.addMethod( CtNewMethod.make( trackerChangeMethod, managedCtClass ) );
createCollectionDirtyCheckMethod( managedCtClass );
createCollectionDirtyCheckGetFieldsMethod( managedCtClass );
createHasDirtyAttributesMethod( managedCtClass );
createClearDirtyCollectionMethod( managedCtClass );
createClearDirtyMethod( managedCtClass );
String trackerGetMethod =
"public java.util.List " + EnhancerConstants.TRACKER_GET_NAME + "() { " +
"if(" + EnhancerConstants.TRACKER_FIELD_NAME + " == null) " +
EnhancerConstants.TRACKER_FIELD_NAME + " = new java.util.HashSet();" +
EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME + "(" +
EnhancerConstants.TRACKER_FIELD_NAME + ");" +
"return " + EnhancerConstants.TRACKER_FIELD_NAME + "; }";
CtMethod getMethod = CtNewMethod.make( trackerGetMethod, managedCtClass );
MethodInfo methodInfo = getMethod.getMethodInfo();
SignatureAttribute signatureAttribute =
new SignatureAttribute( methodInfo.getConstPool(), "()Ljava/util/Set<Ljava/lang/String;>;" );
methodInfo.addAttribute( signatureAttribute );
managedCtClass.addMethod( getMethod );
}
catch (CannotCompileException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private void createTrackChangeCompositeMethod(CtClass managedCtClass) {
StringBuilder builder = new StringBuilder();
builder.append( "public void " )
.append( EnhancerConstants.TRACKER_CHANGER_NAME )
.append( "(String name) {" )
.append( "if (" )
.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
.append( " != null) " )
.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
.append( ".callOwner(\".\"+name); }" );
System.out.println( "COMPOSITE METHOD: " + builder.toString() );
try {
managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );
}
catch (CannotCompileException e) {
}
}
private void createCompositeTrackerMethod(CtClass managedCtClass) {
try {
StringBuilder builder = new StringBuilder();
builder.append( "public void " )
.append( EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER )
.append( "(String name, org.hibernate.engine.spi.CompositeOwner tracker) {" )
.append( "if(" )
.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
.append( " == null) " )
.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
.append( " = new org.hibernate.bytecode.enhance.spi.CompositeOwnerTracker();" )
.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
.append( ".add(name, tracker); }" );
managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );
builder = new StringBuilder();
builder.append( "public void " )
.append( EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER )
.append( "(String name) {" )
.append( " if(" )
.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
.append( " != null)" )
.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
.append( ".removeOwner(name);}" );
managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );
}
catch (CannotCompileException e) {
e.printStackTrace();
}
}
private void createHasDirtyAttributesMethod(CtClass managedCtClass) throws CannotCompileException {
String trackerHasChangedMethod =
"public boolean " + EnhancerConstants.TRACKER_HAS_CHANGED_NAME + "() { return (" +
EnhancerConstants.TRACKER_FIELD_NAME + " != null && !" +
EnhancerConstants.TRACKER_FIELD_NAME + ".isEmpty()) || " +
EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME + "(); } ";
managedCtClass.addMethod( CtNewMethod.make( trackerHasChangedMethod, managedCtClass ) );
}
private void createClearDirtyMethod(CtClass managedCtClass) throws CannotCompileException, ClassNotFoundException {
StringBuilder builder = new StringBuilder();
builder.append( "public void " )
.append( EnhancerConstants.TRACKER_CLEAR_NAME )
.append( "() {" )
.append( "if (" )
.append( EnhancerConstants.TRACKER_FIELD_NAME )
.append( " != null) " )
.append( EnhancerConstants.TRACKER_FIELD_NAME )
.append( ".clear(); " )
.append( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME )
.append( "(); }" );
managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );
}
private void createClearDirtyCollectionMethod(CtClass managedCtClass) throws CannotCompileException {
StringBuilder builder = new StringBuilder();
builder.append( "private void " )
.append( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME )
.append( "() { if(" )
.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
.append( " == null)" )
.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
.append( " = new org.hibernate.bytecode.enhance.spi.CollectionTracker();" );
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
if ( !enhancementContext.isMappedCollection( ctField ) ) {
builder.append( "if(" )
.append( ctField.getName() )
.append( " != null) " )
.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
.append( ".add(\"" )
.append( ctField.getName() )
.append( "\", " )
.append( ctField.getName() )
.append( ".size());" );
}
}
builder.append( "}" );
managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );
}
private void createCollectionDirtyCheckMethod(CtClass managedCtClass) throws CannotCompileException {
StringBuilder builder = new StringBuilder( "private boolean " )
.append( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME )
.append( "() { if (" )
.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
.append( " == null) return false; " );
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
if ( !enhancementContext.isMappedCollection( ctField ) ) {
builder.append( "if(" )
.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
.append( ".getSize(\"" )
.append( ctField.getName() )
.append( "\") != " )
.append( ctField.getName() )
.append( ".size()) return true;" );
}
}
builder.append( "return false; }" );
managedCtClass.addMethod( CtNewMethod.make( builder.toString(), managedCtClass ) );
}
private void createCollectionDirtyCheckGetFieldsMethod(CtClass managedCtClass) throws CannotCompileException {
StringBuilder collectionFieldDirtyFieldMethod = new StringBuilder( "private void " )
.append( EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME )
.append( "(java.util.Set trackerSet) { if(" )
.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
.append( " == null) return; else {" );
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
if ( !ctField.getName().startsWith( "$$_hibernate" )
&& !enhancementContext.isMappedCollection( ctField ) ) {
collectionFieldDirtyFieldMethod
.append( "if(" )
.append( EnhancerConstants.TRACKER_COLLECTION_NAME )
.append( ".getSize(\"" )
.append( ctField.getName() )
.append( "\") != " )
.append( ctField.getName() )
.append( ".size()) trackerSet.add(\"" )
.append( ctField.getName() )
.append( "\");" );
}
}
collectionFieldDirtyFieldMethod.append( "}}" );
managedCtClass.addMethod( CtNewMethod.make( collectionFieldDirtyFieldMethod.toString(), managedCtClass ) );
}
private void addFieldWithGetterAndSetter(
CtClass targetClass,
CtClass fieldType,
String fieldName,
String getterName,
String setterName) {
final CtField theField = addField( targetClass, fieldType, fieldName, true );
addGetter( targetClass, theField, getterName );
addSetter( targetClass, theField, setterName );
}
private CtField addField(CtClass targetClass, CtClass fieldType, String fieldName, boolean makeTransient) {
final ConstPool constPool = targetClass.getClassFile().getConstPool();
final CtField theField;
try {
theField = new CtField( fieldType, fieldName, targetClass );
targetClass.addField( theField );
}
catch (CannotCompileException e) {
throw new EnhancementException(
String.format(
"Could not enhance class [%s] to add field [%s]",
targetClass.getName(),
fieldName
),
e
);
}
if ( makeTransient ) {
theField.setModifiers( theField.getModifiers() | Modifier.TRANSIENT );
}
theField.setModifiers( Modifier.setPrivate( theField.getModifiers() ) );
final AnnotationsAttribute annotationsAttribute = getVisibleAnnotations( theField.getFieldInfo() );
annotationsAttribute.addAnnotation( new Annotation( Transient.class.getName(), constPool ) );
return theField;
}
private void addGetter(CtClass targetClass, CtField theField, String getterName) {
try {
targetClass.addMethod( CtNewMethod.getter( getterName, theField ) );
}
catch (CannotCompileException e) {
throw new EnhancementException(
String.format(
"Could not enhance entity class [%s] to add getter method [%s]",
targetClass.getName(),
getterName
),
e
);
}
}
private void addSetter(CtClass targetClass, CtField theField, String setterName) {
try {
targetClass.addMethod( CtNewMethod.setter( setterName, theField ) );
}
catch (CannotCompileException e) {
throw new EnhancementException(
String.format(
"Could not enhance entity class [%s] to add setter method [%s]",
targetClass.getName(),
setterName
),
e
);
}
}
private CtMethod generateFieldReader(
CtClass managedCtClass,
CtField persistentField,
AttributeTypeDescriptor typeDescriptor)
throws BadBytecode, CannotCompileException {
final FieldInfo fieldInfo = persistentField.getFieldInfo();
final String fieldName = fieldInfo.getName();
final String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;
if ( !enhancementContext.isLazyLoadable( persistentField ) ) {
try {
final CtMethod reader = CtNewMethod.getter( readerName, persistentField );
managedCtClass.addMethod( reader );
return reader;
}
catch (CannotCompileException e) {
throw new EnhancementException(
String.format(
"Could not enhance entity class [%s] to add field reader method [%s]",
managedCtClass.getName(),
readerName
),
e
);
}
}
final String methodBody = typeDescriptor.buildReadInterceptionBodyFragment( fieldName )
+ " return this." + fieldName + ";";
try {
final CtMethod reader = CtNewMethod.make(
Modifier.PRIVATE,
persistentField.getType(),
readerName,
null,
null,
"{" + methodBody + "}",
managedCtClass
);
managedCtClass.addMethod( reader );
return reader;
}
catch (Exception e) {
throw new EnhancementException(
String.format(
"Could not enhance entity class [%s] to add field reader method [%s]",
managedCtClass.getName(),
readerName
),
e
);
}
}
private CtMethod generateFieldWriter(
CtClass managedCtClass,
CtField persistentField,
AttributeTypeDescriptor typeDescriptor) {
final FieldInfo fieldInfo = persistentField.getFieldInfo();
final String fieldName = fieldInfo.getName();
final String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;
final CtMethod writer;
try {
if ( !enhancementContext.isLazyLoadable( persistentField ) ) {
writer = CtNewMethod.setter( writerName, persistentField );
}
else {
final String methodBody = typeDescriptor.buildWriteInterceptionBodyFragment( fieldName );
writer = CtNewMethod.make(
Modifier.PRIVATE,
CtClass.voidType,
writerName,
new CtClass[] {persistentField.getType()},
null,
"{" + methodBody + "}",
managedCtClass
);
}
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) && !isComposite ) {
writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( persistentField ) );
}
if ( isComposite ) {
StringBuilder builder = new StringBuilder();
builder.append( " if( " )
.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
.append( " != null) " )
.append( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME )
.append( ".callOwner(\"." )
.append( persistentField.getName() )
.append( "\");" );
writer.insertBefore( builder.toString() );
}
if ( persistentField.getAnnotation( Embedded.class ) != null ) {
if ( !doClassInheritCompositeOwner( managedCtClass ) ) {
managedCtClass.addInterface( classPool.get( "org.hibernate.engine.spi.CompositeOwner" ) );
}
if ( isComposite ) {
createTrackChangeCompositeMethod( managedCtClass );
}
writer.insertBefore( cleanupPreviousOwner( persistentField ) );
writer.insertAfter( compositeMethodBody( persistentField ) );
}
managedCtClass.addMethod( writer );
return writer;
}
catch (Exception e) {
throw new EnhancementException(
String.format(
"Could not enhance entity class [%s] to add field writer method [%s]",
managedCtClass.getName(),
writerName
),
e
);
}
}
private boolean doClassInheritCompositeOwner(CtClass managedCtClass) {
try {
for ( CtClass ctClass : managedCtClass.getInterfaces() ) {
if ( ctClass.getName().equals( "org.hibernate.engine.spi.CompositeOwner" ) ) {
return true;
}
}
return false;
}
catch (NotFoundException e) {
return false;
}
}
private String cleanupPreviousOwner(CtField currentValue) {
StringBuilder builder = new StringBuilder();
builder.append( "if (" )
.append( currentValue.getName() )
.append( " != null) " )
.append( "((org.hibernate.engine.spi.CompositeTracker)" )
.append( currentValue.getName() )
.append( ")." )
.append( EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER )
.append( "(\"" )
.append( currentValue.getName() )
.append( "\");" );
return builder.toString();
}
private String compositeMethodBody(CtField currentValue) {
StringBuilder builder = new StringBuilder();
builder.append( "((org.hibernate.engine.spi.CompositeTracker) " )
.append( currentValue.getName() )
.append( ").$$_hibernate_setOwner(\"" )
.append( currentValue.getName() )
.append( "\",(org.hibernate.engine.spi.CompositeOwner) this);" )
.append( EnhancerConstants.TRACKER_CHANGER_NAME + "(\"" ).append( currentValue.getName() ).append(
"\");"
);
return builder.toString();
}
private void transformFieldAccessesIntoReadsAndWrites(
CtClass managedCtClass,
IdentityHashMap<String, PersistentAttributeDescriptor> attributeDescriptorMap) {
final ConstPool constPool = managedCtClass.getClassFile().getConstPool();
for ( Object oMethod : managedCtClass.getClassFile().getMethods() ) {
final MethodInfo methodInfo = (MethodInfo) oMethod;
final String methodName = methodInfo.getName();
if ( methodName.startsWith( EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX )
|| methodName.startsWith( EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX )
|| methodName.equals( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME )
|| methodName.equals( EnhancerConstants.ENTITY_ENTRY_GETTER_NAME )
|| methodName.equals( EnhancerConstants.ENTITY_ENTRY_SETTER_NAME )
|| methodName.equals( EnhancerConstants.PREVIOUS_GETTER_NAME )
|| methodName.equals( EnhancerConstants.PREVIOUS_SETTER_NAME )
|| methodName.equals( EnhancerConstants.NEXT_GETTER_NAME )
|| methodName.equals( EnhancerConstants.NEXT_SETTER_NAME ) ) {
continue;
}
final CodeAttribute codeAttr = methodInfo.getCodeAttribute();
if ( codeAttr == null ) {
continue;
}
try {
final CodeIterator itr = codeAttr.iterator();
while ( itr.hasNext() ) {
final int index = itr.next();
final int op = itr.byteAt( index );
if ( op != Opcode.PUTFIELD && op != Opcode.GETFIELD ) {
continue;
}
final int constIndex = itr.u16bitAt( index + 1 );
final String fieldName = constPool.getFieldrefName( constIndex );
final PersistentAttributeDescriptor attributeDescriptor = attributeDescriptorMap.get( fieldName );
if ( attributeDescriptor == null ) {
continue;
}
log.tracef(
"Transforming access to field [%s] from method [%s]",
fieldName,
methodName
);
if ( op == Opcode.GETFIELD ) {
final int readMethodIndex = constPool.addMethodrefInfo(
constPool.getThisClassInfo(),
attributeDescriptor.getReader().getName(),
attributeDescriptor.getReader().getSignature()
);
itr.writeByte( Opcode.INVOKESPECIAL, index );
itr.write16bit( readMethodIndex, index + 1 );
}
else {
final int writeMethodIndex = constPool.addMethodrefInfo(
constPool.getThisClassInfo(),
attributeDescriptor.getWriter().getName(),
attributeDescriptor.getWriter().getSignature()
);
itr.writeByte( Opcode.INVOKESPECIAL, index );
itr.write16bit( writeMethodIndex, index + 1 );
}
}
final StackMapTable smt = MapMaker.make( classPool, methodInfo );
methodInfo.getCodeAttribute().setAttribute( smt );
}
catch (BadBytecode e) {
throw new EnhancementException(
"Unable to perform field access transformation in method : " + methodName,
e
);
}
}
}
private static class PersistentAttributeDescriptor {
private final CtField field;
private final CtMethod reader;
private final CtMethod writer;
private final AttributeTypeDescriptor typeDescriptor;
private PersistentAttributeDescriptor(
CtField field,
CtMethod reader,
CtMethod writer,
AttributeTypeDescriptor typeDescriptor) {
this.field = field;
this.reader = reader;
this.writer = writer;
this.typeDescriptor = typeDescriptor;
}
public CtField getField() {
return field;
}
public CtMethod getReader() {
return reader;
}
public CtMethod getWriter() {
return writer;
}
@SuppressWarnings("UnusedDeclaration")
public AttributeTypeDescriptor getTypeDescriptor() {
return typeDescriptor;
}
}
private static interface AttributeTypeDescriptor {
public String buildReadInterceptionBodyFragment(String fieldName);
public String buildWriteInterceptionBodyFragment(String fieldName);
public String buildInLineDirtyCheckingBodyFragment(CtField currentField);
}
private AttributeTypeDescriptor resolveAttributeTypeDescriptor(CtField persistentField) throws NotFoundException {
if ( persistentField.getType() == CtClass.booleanType ) {
return BOOLEAN_DESCRIPTOR;
}
else if ( persistentField.getType() == CtClass.byteType ) {
return BYTE_DESCRIPTOR;
}
else if ( persistentField.getType() == CtClass.charType ) {
return CHAR_DESCRIPTOR;
}
else if ( persistentField.getType() == CtClass.shortType ) {
return SHORT_DESCRIPTOR;
}
else if ( persistentField.getType() == CtClass.intType ) {
return INT_DESCRIPTOR;
}
else if ( persistentField.getType() == CtClass.longType ) {
return LONG_DESCRIPTOR;
}
else if ( persistentField.getType() == CtClass.doubleType ) {
return DOUBLE_DESCRIPTOR;
}
else if ( persistentField.getType() == CtClass.floatType ) {
return FLOAT_DESCRIPTOR;
}
else {
return new ObjectAttributeTypeDescriptor( persistentField.getType() );
}
}
private abstract static class AbstractAttributeTypeDescriptor implements AttributeTypeDescriptor {
@Override
public String buildInLineDirtyCheckingBodyFragment(CtField currentValue) {
StringBuilder builder = new StringBuilder();
try {
for ( Object o : currentValue.getType().getAnnotations() ) {
if ( o instanceof Id ) {
return "";
}
}
builder.append( entityMethodBody( currentValue ) );
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
catch (NotFoundException e) {
e.printStackTrace();
}
return builder.toString();
}
private String entityMethodBody(CtField currentValue) {
StringBuilder inlineBuilder = new StringBuilder();
try {
if ( currentValue.getType().isPrimitive() || currentValue.getType().isEnum() ) {
inlineBuilder.append( "if(" ).append( currentValue.getName() ).append( " != $1)" );
}
else if ( currentValue.getType().getName().startsWith( "java.lang" )
|| currentValue.getType().getName().startsWith( "java.math.Big" )
|| currentValue.getType().getName().startsWith( "java.sql.Time" )
|| currentValue.getType().getName().startsWith( "java.sql.Date" )
|| currentValue.getType().getName().startsWith( "java.util.Date" )
|| currentValue.getType().getName().startsWith( "java.util.Calendar" ) ) {
inlineBuilder.append( "if(" )
.append( currentValue.getName() )
.append( " == null || !" )
.append( currentValue.getName() )
.append( ".equals( $1))" );
}
else {
for ( CtClass ctClass : currentValue.getType().getInterfaces() ) {
if ( ctClass.getName().equals( "java.util.Collection" ) ) {
if ( currentValue.getAnnotation( OneToMany.class ) != null ||
currentValue.getAnnotation( ManyToMany.class ) != null ||
currentValue.getAnnotation( ElementCollection.class ) != null ) {
return "";
}
}
}
inlineBuilder.append( "if(" )
.append( currentValue.getName() )
.append( " == null || !" )
.append( currentValue.getName() )
.append( ".equals( $1))" );
}
inlineBuilder.append( EnhancerConstants.TRACKER_CHANGER_NAME + "(\"" )
.append( currentValue.getName() )
.append( "\");" );
}
catch (NotFoundException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
return inlineBuilder.toString();
}
}
private static class ObjectAttributeTypeDescriptor extends AbstractAttributeTypeDescriptor {
private final CtClass concreteType;
private ObjectAttributeTypeDescriptor(CtClass concreteType) {
this.concreteType = concreteType;
}
@Override
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format(
"if ( $$_hibernate_getInterceptor() != null ) { " +
"this.%1$s = (%2$s) $$_hibernate_getInterceptor().readObject(this, \"%1$s\", this.%1$s); " +
"}",
fieldName,
concreteType.getName()
);
}
@Override
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format(
"%2$s localVar = $1;" +
"if ( $$_hibernate_getInterceptor() != null ) {" +
"localVar = (%2$s) $$_hibernate_getInterceptor().writeObject(this, \"%1$s\", this.%1$s, $1);" +
"}" +
"this.%1$s = localVar;",
fieldName,
concreteType.getName()
);
}
}
private static final AttributeTypeDescriptor BOOLEAN_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
@Override
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format(
"if ( $$_hibernate_getInterceptor() != null ) { " +
"this.%1$s = $$_hibernate_getInterceptor().readBoolean(this, \"%1$s\", this.%1$s); " +
"}",
fieldName
);
}
@Override
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format(
"boolean localVar = $1;" +
"if ( $$_hibernate_getInterceptor() != null ) {" +
"localVar = $$_hibernate_getInterceptor().writeBoolean(this, \"%1$s\", this.%1$s, $1);" +
"}" +
"this.%1$s = localVar;",
fieldName
);
}
};
private static final AttributeTypeDescriptor BYTE_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
@Override
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format(
"if ( $$_hibernate_getInterceptor() != null ) { " +
"this.%1$s = $$_hibernate_getInterceptor().readByte(this, \"%1$s\", this.%1$s); " +
"}",
fieldName
);
}
@Override
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format(
"byte localVar = $1;" +
"if ( $$_hibernate_getInterceptor() != null ) {" +
"localVar = $$_hibernate_getInterceptor().writeByte(this, \"%1$s\", this.%1$s, $1);" +
"}" +
"this.%1$s = localVar;",
fieldName
);
}
};
private static final AttributeTypeDescriptor CHAR_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
@Override
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format(
"if ( $$_hibernate_getInterceptor() != null ) { " +
"this.%1$s = $$_hibernate_getInterceptor().readChar(this, \"%1$s\", this.%1$s); " +
"}",
fieldName
);
}
@Override
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format(
"char localVar = $1;" +
"if ( $$_hibernate_getInterceptor() != null ) {" +
"localVar = $$_hibernate_getInterceptor().writeChar(this, \"%1$s\", this.%1$s, $1);" +
"}" +
"this.%1$s = localVar;",
fieldName
);
}
};
private static final AttributeTypeDescriptor SHORT_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
@Override
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format(
"if ( $$_hibernate_getInterceptor() != null ) { " +
"this.%1$s = $$_hibernate_getInterceptor().readShort(this, \"%1$s\", this.%1$s); " +
"}",
fieldName
);
}
@Override
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format(
"short localVar = $1;" +
"if ( $$_hibernate_getInterceptor() != null ) {" +
"localVar = $$_hibernate_getInterceptor().writeShort(this, \"%1$s\", this.%1$s, $1);" +
"}" +
"this.%1$s = localVar;",
fieldName
);
}
};
private static final AttributeTypeDescriptor INT_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
@Override
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format(
"if ( $$_hibernate_getInterceptor() != null ) { " +
"this.%1$s = $$_hibernate_getInterceptor().readInt(this, \"%1$s\", this.%1$s); " +
"}",
fieldName
);
}
@Override
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format(
"int localVar = $1;" +
"if ( $$_hibernate_getInterceptor() != null ) {" +
"localVar = $$_hibernate_getInterceptor().writeInt(this, \"%1$s\", this.%1$s, $1);" +
"}" +
"this.%1$s = localVar;",
fieldName
);
}
};
private static final AttributeTypeDescriptor LONG_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
@Override
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format(
"if ( $$_hibernate_getInterceptor() != null ) { " +
"this.%1$s = $$_hibernate_getInterceptor().readLong(this, \"%1$s\", this.%1$s); " +
"}",
fieldName
);
}
@Override
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format(
"long localVar = $1;" +
"if ( $$_hibernate_getInterceptor() != null ) {" +
"localVar = $$_hibernate_getInterceptor().writeLong(this, \"%1$s\", this.%1$s, $1);" +
"}" +
"this.%1$s = localVar;",
fieldName
);
}
};
private static final AttributeTypeDescriptor DOUBLE_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
@Override
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format(
"if ( $$_hibernate_getInterceptor() != null ) { " +
"this.%1$s = $$_hibernate_getInterceptor().readDouble(this, \"%1$s\", this.%1$s); " +
"}",
fieldName
);
}
@Override
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format(
"double localVar = $1;" +
"if ( $$_hibernate_getInterceptor() != null ) {" +
"localVar = $$_hibernate_getInterceptor().writeDouble(this, \"%1$s\", this.%1$s, $1);" +
"}" +
"this.%1$s = localVar;",
fieldName
);
}
};
private static final AttributeTypeDescriptor FLOAT_DESCRIPTOR = new AbstractAttributeTypeDescriptor() {
@Override
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format(
"if ( $$_hibernate_getInterceptor() != null ) { " +
"this.%1$s = $$_hibernate_getInterceptor().readFloat(this, \"%1$s\", this.%1$s); " +
"}",
fieldName
);
}
@Override
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format(
"float localVar = $1;" +
"if ( $$_hibernate_getInterceptor() != null ) {" +
"localVar = $$_hibernate_getInterceptor().writeFloat(this, \"%1$s\", this.%1$s, $1);" +
"}" +
"this.%1$s = localVar;",
fieldName
);
}
};
}