HHH-7667 - Investigate expanding bytecode enhancement support

(cherry picked from commit 30b3bd1564)
This commit is contained in:
Steve Ebersole 2012-10-25 09:26:54 -05:00
parent 60836cda1b
commit 8c96a4a2c6
13 changed files with 1423 additions and 389 deletions

View File

@ -23,34 +23,58 @@
*/
package org.hibernate.bytecode.enhance.spi;
import javassist.CtClass;
import javassist.CtField;
/**
* todo : not sure its a great idea to expose Javassist classes this way.
* maybe wrap them in our own contracts?
*
* @author Steve Ebersole
*/
public interface EnhancementContext {
/**
* Does the given class name represent a entity class?
* Obtain access to the ClassLoader that can be used to load Class references. In JPA SPI terms, this
* should be a "temporary class loader" as defined by
* {@link javax.persistence.spi.PersistenceUnitInfo#getNewTempClassLoader()}
*/
public ClassLoader getLoadingClassLoader();
/**
* Does the given class descriptor represent a entity class?
*
* @param className The name of the class to check.
* @param classDescriptor The descriptor of the class to check.
*
* @return {@code true} if the class is an entity; {@code false} otherwise.
*/
public boolean isEntityClass(String className);
public boolean isEntityClass(CtClass classDescriptor);
/**
* Does the given class name represent an embeddable/component class?
*
* @param className The name of the class to check.
* @param classDescriptor The descriptor of the class to check.
*
* @return {@code true} if the class is an embeddable/component; {@code false} otherwise.
*/
public boolean isCompositeClass(String className);
public boolean isCompositeClass(CtClass classDescriptor);
/**
* Should we in-line dirty checking for persistent attributes for this class?
*
* @param classDescriptor The descriptor of the class to check.
*
* @return {@code true} indicates that dirty checking should be in-lined within the entity; {@code false}
* indicates it should not. In-lined is more easily serializable and probably more performant.
*/
public boolean doDirtyCheckingInline(CtClass classDescriptor);
public boolean hasLazyLoadableAttributes(CtClass classDescriptor);
// todo : may be better to invert these 2 such that the context is asked for an ordered list of persistent fields for an entity/composite
/**
* Does the field represent persistent state? Persistent fields will be "enhanced".
* <p/>
* todo : not sure its a great idea to expose Javassist classes this way.
// may be better to perform basic checks in the caller (non-static, etc) and call out with just the
// Class name and field name...
@ -59,4 +83,16 @@ public interface EnhancementContext {
* @return {@code true} if the field is ; {@code false} otherwise.
*/
public boolean isPersistentField(CtField ctField);
/**
* For fields which are persistent (according to {@link #isPersistentField}), determine the corresponding ordering
* maintained within the Hibernate metamodel.
* @param persistentFields The persistent field references.
*
* @return The ordered references.
*/
public CtField[] order(CtField[] persistentFields);
public boolean isLazyLoadable(CtField field);
}

View File

@ -81,7 +81,7 @@ public class EntityEntryContext {
final boolean alreadyAssociated;
if ( ManagedEntity.class.isInstance( entity ) ) {
managedEntity = (ManagedEntity) entity;
alreadyAssociated = managedEntity.hibernate_getEntityEntry() != null;
alreadyAssociated = managedEntity.$$_hibernate_getEntityEntry() != null;
}
else {
ManagedEntity wrapper = null;
@ -105,7 +105,7 @@ public class EntityEntryContext {
}
// associate the EntityEntry with the entity
managedEntity.hibernate_setEntityEntry( entityEntry );
managedEntity.$$_hibernate_setEntityEntry( entityEntry );
if ( alreadyAssociated ) {
// if the entity was already associated with the context, skip the linking step.
@ -120,8 +120,8 @@ public class EntityEntryContext {
count = 1;
}
else {
tail.hibernate_setNextManagedEntity( managedEntity );
managedEntity.hibernate_setPreviousManagedEntity( tail );
tail.$$_hibernate_setNextManagedEntity( managedEntity );
managedEntity.$$_hibernate_setPreviousManagedEntity( tail );
tail = managedEntity;
count++;
}
@ -145,7 +145,7 @@ public class EntityEntryContext {
return managedEntity == null
? null
: managedEntity.hibernate_getEntityEntry();
: managedEntity.$$_hibernate_getEntityEntry();
}
public EntityEntry removeEntityEntry(Object entity) {
@ -167,10 +167,10 @@ public class EntityEntryContext {
}
// prepare for re-linking...
ManagedEntity previous = managedEntity.hibernate_getPreviousManagedEntity();
ManagedEntity next = managedEntity.hibernate_getNextManagedEntity();
managedEntity.hibernate_setPreviousManagedEntity( null );
managedEntity.hibernate_setNextManagedEntity( null );
ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity();
ManagedEntity next = managedEntity.$$_hibernate_getNextManagedEntity();
managedEntity.$$_hibernate_setPreviousManagedEntity( null );
managedEntity.$$_hibernate_setNextManagedEntity( null );
count--;
@ -190,7 +190,7 @@ public class EntityEntryContext {
head = next;
}
else {
previous.hibernate_setNextManagedEntity( next );
previous.$$_hibernate_setNextManagedEntity( next );
}
if ( next == null ) {
@ -199,12 +199,12 @@ public class EntityEntryContext {
tail = previous;
}
else {
next.hibernate_setPreviousManagedEntity( previous );
next.$$_hibernate_setPreviousManagedEntity( previous );
}
}
EntityEntry theEntityEntry = managedEntity.hibernate_getEntityEntry();
managedEntity.hibernate_setEntityEntry( null );
EntityEntry theEntityEntry = managedEntity.$$_hibernate_getEntityEntry();
managedEntity.$$_hibernate_setEntityEntry( null );
return theEntityEntry;
}
@ -215,10 +215,10 @@ public class EntityEntryContext {
ManagedEntity managedEntity = head;
while ( managedEntity != null ) {
reentrantSafeEntries[i++] = new EntityEntryCrossRefImpl(
managedEntity.hibernate_getEntityInstance(),
managedEntity.hibernate_getEntityEntry()
managedEntity.$$_hibernate_getEntityInstance(),
managedEntity.$$_hibernate_getEntityEntry()
);
managedEntity = managedEntity.hibernate_getNextManagedEntity();
managedEntity = managedEntity.$$_hibernate_getNextManagedEntity();
}
dirty = false;
}
@ -230,11 +230,11 @@ public class EntityEntryContext {
ManagedEntity node = head;
while ( node != null ) {
final ManagedEntity nextNode = node.hibernate_getNextManagedEntity();
final ManagedEntity nextNode = node.$$_hibernate_getNextManagedEntity();
node.hibernate_setEntityEntry( null );
node.hibernate_setPreviousManagedEntity( null );
node.hibernate_setNextManagedEntity( null );
node.$$_hibernate_setEntityEntry( null );
node.$$_hibernate_setPreviousManagedEntity( null );
node.$$_hibernate_setNextManagedEntity( null );
node = nextNode;
}
@ -257,9 +257,9 @@ public class EntityEntryContext {
ManagedEntity node = head;
while ( node != null ) {
node.hibernate_getEntityEntry().setLockMode( LockMode.NONE );
node.$$_hibernate_getEntityEntry().setLockMode( LockMode.NONE );
node = node.hibernate_getNextManagedEntity();
node = node.$$_hibernate_getNextManagedEntity();
}
}
@ -273,11 +273,11 @@ public class EntityEntryContext {
ManagedEntity managedEntity = head;
while ( managedEntity != null ) {
// so we know whether or not to build a ManagedEntityImpl on deserialize
oos.writeBoolean( managedEntity == managedEntity.hibernate_getEntityInstance() );
oos.writeObject( managedEntity.hibernate_getEntityInstance() );
managedEntity.hibernate_getEntityEntry().serialize( oos );
oos.writeBoolean( managedEntity == managedEntity.$$_hibernate_getEntityInstance() );
oos.writeObject( managedEntity.$$_hibernate_getEntityInstance() );
managedEntity.$$_hibernate_getEntityEntry().serialize( oos );
managedEntity = managedEntity.hibernate_getNextManagedEntity();
managedEntity = managedEntity.$$_hibernate_getNextManagedEntity();
}
}
@ -310,14 +310,14 @@ public class EntityEntryContext {
}
context.nonEnhancedEntityXref.put( entity, managedEntity );
}
managedEntity.hibernate_setEntityEntry( entry );
managedEntity.$$_hibernate_setEntityEntry( entry );
if ( previous == null ) {
context.head = managedEntity;
}
else {
previous.hibernate_setNextManagedEntity( managedEntity );
managedEntity.hibernate_setPreviousManagedEntity( previous );
previous.$$_hibernate_setNextManagedEntity( managedEntity );
managedEntity.$$_hibernate_setPreviousManagedEntity( previous );
}
previous = managedEntity;
@ -343,37 +343,37 @@ public class EntityEntryContext {
}
@Override
public Object hibernate_getEntityInstance() {
public Object $$_hibernate_getEntityInstance() {
return entityInstance;
}
@Override
public EntityEntry hibernate_getEntityEntry() {
public EntityEntry $$_hibernate_getEntityEntry() {
return entityEntry;
}
@Override
public void hibernate_setEntityEntry(EntityEntry entityEntry) {
public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) {
this.entityEntry = entityEntry;
}
@Override
public ManagedEntity hibernate_getNextManagedEntity() {
public ManagedEntity $$_hibernate_getNextManagedEntity() {
return next;
}
@Override
public void hibernate_setNextManagedEntity(ManagedEntity next) {
public void $$_hibernate_setNextManagedEntity(ManagedEntity next) {
this.next = next;
}
@Override
public ManagedEntity hibernate_getPreviousManagedEntity() {
public ManagedEntity $$_hibernate_getPreviousManagedEntity() {
return previous;
}
@Override
public void hibernate_setPreviousManagedEntity(ManagedEntity previous) {
public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) {
this.previous = previous;
}
}

View File

@ -27,14 +27,14 @@ package org.hibernate.engine.spi;
* Specialized {@link Managed} contract for entity classes. Essentially provides access to information
* about an instance's association to a Session/EntityManager. Specific information includes:<ul>
* <li>
* the association's {@link EntityEntry} (by way of {@link #hibernate_getEntityEntry()} and
* {@link #hibernate_setEntityEntry}). EntityEntry describes states, snapshots, etc.
* the association's {@link EntityEntry} (by way of {@link #$$_hibernate_getEntityEntry} and
* {@link #$$_hibernate_setEntityEntry}). EntityEntry describes states, snapshots, etc.
* </li>
* <li>
* link information. ManagedEntity instances are part of a "linked list", thus link information
* describes the next and previous entries/nodes in that ordering. See
* {@link #hibernate_getNextManagedEntity}, {@link #hibernate_setNextManagedEntity},
* {@link #hibernate_getPreviousManagedEntity()}, {@link #hibernate_setPreviousManagedEntity}
* {@link #$$_hibernate_getNextManagedEntity}, {@link #$$_hibernate_setNextManagedEntity},
* {@link #$$_hibernate_getPreviousManagedEntity}, {@link #$$_hibernate_setPreviousManagedEntity}
* </li>
* </ul>
*
@ -46,16 +46,16 @@ public interface ManagedEntity extends Managed {
*
* @return The entity instance.
*/
public Object hibernate_getEntityInstance();
public Object $$_hibernate_getEntityInstance();
/**
* Provides access to the associated EntityEntry.
*
* @return The EntityEntry associated with this entity instance.
*
* @see #hibernate_setEntityEntry
* @see #$$_hibernate_setEntityEntry
*/
public EntityEntry hibernate_getEntityEntry();
public EntityEntry $$_hibernate_getEntityEntry();
/**
* Injects the EntityEntry associated with this entity instance. The EntityEntry represents state associated
@ -63,13 +63,37 @@ public interface ManagedEntity extends Managed {
*
* @param entityEntry The EntityEntry associated with this entity instance.
*/
public void hibernate_setEntityEntry(EntityEntry entityEntry);
public void $$_hibernate_setEntityEntry(EntityEntry entityEntry);
public ManagedEntity hibernate_getPreviousManagedEntity();
/**
* Part of entry linking; obtain reference to the previous entry. Can be {@code null}, which should indicate
* this is the head node.
*
* @return The previous entry
*/
public ManagedEntity $$_hibernate_getPreviousManagedEntity();
public void hibernate_setPreviousManagedEntity(ManagedEntity previous);
/**
* Part of entry linking; sets the previous entry. Again, can be {@code null}, which should indicate
* this is (now) the head node.
*
* @param previous The previous entry
*/
public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous);
public ManagedEntity hibernate_getNextManagedEntity();
/**
* Part of entry linking; obtain reference to the next entry. Can be {@code null}, which should indicate
* this is the tail node.
*
* @return The next entry
*/
public ManagedEntity $$_hibernate_getNextManagedEntity();
public void hibernate_setNextManagedEntity(ManagedEntity next);
/**
* Part of entry linking; sets the next entry. Again, can be {@code null}, which should indicate
* this is (now) the tail node.
*
* @param next The next entry
*/
public void $$_hibernate_setNextManagedEntity(ManagedEntity next);
}

View File

@ -0,0 +1,32 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.engine.spi;
/**
* @author Steve Ebersole
*/
public interface PersistentAttributeInterceptable {
public PersistentAttributeInterceptor $$_hibernate_getInterceptor();
public void $$_hibernate_setInterceptor(PersistentAttributeInterceptor interceptor);
}

View File

@ -0,0 +1,67 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.engine.spi;
/**
* @author Steve Ebersole
*/
public interface PersistentAttributeInterceptor {
public boolean readBoolean(Object obj, String name, boolean oldValue);
public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue);
public byte readByte(Object obj, String name, byte oldValue);
public byte writeByte(Object obj, String name, byte oldValue, byte newValue);
public char readChar(Object obj, String name, char oldValue);
public char writeChar(Object obj, String name, char oldValue, char newValue);
public short readShort(Object obj, String name, short oldValue);
public short writeShort(Object obj, String name, short oldValue, short newValue);
public int readInt(Object obj, String name, int oldValue);
public int writeInt(Object obj, String name, int oldValue, int newValue);
public float readFloat(Object obj, String name, float oldValue);
public float writeFloat(Object obj, String name, float oldValue, float newValue);
public double readDouble(Object obj, String name, double oldValue);
public double writeDouble(Object obj, String name, double oldValue, double newValue);
public long readLong(Object obj, String name, long oldValue);
public long writeLong(Object obj, String name, long oldValue, long newValue);
public Object readObject(Object obj, String name, Object oldValue);
public Object writeObject(Object obj, String name, Object oldValue, Object newValue);
}

View File

@ -28,6 +28,7 @@ import javax.persistence.Transient;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -35,9 +36,6 @@ import java.util.List;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
@ -58,61 +56,25 @@ import org.hibernate.bytecode.enhance.spi.Enhancer;
*
* @see org.hibernate.engine.spi.Managed
*/
public class EnhancementTask extends Task {
public class EnhancementTask extends Task implements EnhancementContext {
private List<FileSet> filesets = new ArrayList<FileSet>();
// Enhancer also builds CtClass instances. Might make sense to share these (ClassPool).
private final ClassPool classPool = new ClassPool( false );
private final Enhancer enhancer = new Enhancer( this );
public void addFileset(FileSet set) {
this.filesets.add( set );
}
@Override
public void execute() throws BuildException {
EnhancementContext enhancementContext = new EnhancementContext() {
@Override
public boolean isEntityClass(String className) {
// currently we only call enhance on the classes with @Entity, so here we always return true
return true;
}
@Override
public boolean isCompositeClass(String className) {
return false;
}
@Override
public boolean isPersistentField(CtField ctField) {
// current check is to look for @Transient
return ! ctField.hasAnnotation( Transient.class );
}
};
log( "Starting Hibernate EnhancementTask execution", Project.MSG_INFO );
// we use the CtClass stuff here just as a simple vehicle for obtaining low level information about
// the class(es) contained in a file while still maintaining easy access to the underlying byte[]
//
// Enhancer also builds CtClass instances. Might make sense to share these (ClassPool).
final ClassPool classPool = new ClassPool( false );
final List<CtClass> ctClassList = collectCtClasses( classPool );
final Enhancer enhancer = new Enhancer( enhancementContext );
for ( CtClass ctClass : ctClassList ) {
try {
enhancer.enhance( ctClass.getName(), ctClass.toBytecode() );
}
catch (Exception e) {
log(
"Unable to enhance class : " + ctClass.getName(),
e,
Project.MSG_WARN
);
}
}
}
private List<CtClass> collectCtClasses(ClassPool classPool) {
final List<CtClass> ctClassList = new ArrayList<CtClass>();
final Project project = getProject();
for ( FileSet fileSet : filesets ) {
final File fileSetBaseDir = fileSet.getDir( project );
final DirectoryScanner directoryScanner = fileSet.getDirectoryScanner( project );
@ -121,40 +83,110 @@ public class EnhancementTask extends Task {
if ( ! javaClassFile.exists() ) {
continue;
}
try {
final CtClass ctClass = classPool.makeClass( new FileInputStream( javaClassFile ) );
collectCtClasses( ctClassList, ctClass );
}
catch (FileNotFoundException ignore) {
// should not ever happen because of explicit check above
}
catch (IOException e) {
throw new BuildException(
String.format(
"Error processing included file [%s : %s]",
fileSetBaseDir.getAbsolutePath(),
relativeIncludedFileName
),
e
);
}
processClassFile( javaClassFile );
}
}
return ctClassList;
}
private void collectCtClasses(List<CtClass> ctClassList, CtClass ctClass) {
if ( ctClass.hasAnnotation( Entity.class ) ) {
ctClassList.add( ctClass );
}
private void processClassFile(File javaClassFile) {
try {
for ( CtClass nestedCtClass : ctClass.getNestedClasses() ) {
collectCtClasses( ctClassList, nestedCtClass );
final CtClass ctClass = classPool.makeClass( new FileInputStream( javaClassFile ) );
if ( ! shouldInclude( ctClass ) ) {
return;
}
final byte[] enhancedBytecode;
try {
enhancedBytecode = enhancer.enhance( ctClass.getName(), ctClass.toBytecode() );
}
catch (Exception e) {
log( "Unable to enhance class [" + ctClass.getName() + "]", e, Project.MSG_WARN );
return;
}
if ( javaClassFile.delete() ) {
if ( ! javaClassFile.createNewFile() ) {
log( "Unable to recreate class file [" + ctClass.getName() + "]", Project.MSG_INFO );
}
}
else {
log( "Unable to delete class file [" + ctClass.getName() + "]", Project.MSG_INFO );
}
FileOutputStream outputStream = new FileOutputStream( javaClassFile, false );
try {
outputStream.write( enhancedBytecode );
outputStream.flush();
}
finally {
try {
outputStream.close();
}
catch ( IOException ignore) {
}
}
}
catch (NotFoundException ignore) {
catch (FileNotFoundException ignore) {
// should not ever happen because of explicit checks
}
catch (IOException e) {
throw new BuildException(
String.format( "Error processing included file [%s]", javaClassFile.getAbsolutePath() ),
e
);
}
}
private boolean shouldInclude(CtClass ctClass) {
// we currently only handle entity enhancement
return ! ctClass.hasAnnotation( Entity.class );
}
// EnhancementContext impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public ClassLoader getLoadingClassLoader() {
return getClass().getClassLoader();
}
@Override
public boolean isEntityClass(CtClass classDescriptor) {
// currently we only call enhance on the classes with @Entity, so here we always return true
return true;
}
@Override
public boolean isCompositeClass(CtClass classDescriptor) {
return false;
}
@Override
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
return false;
}
@Override
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
return true;
}
@Override
public boolean isLazyLoadable(CtField field) {
return true;
}
@Override
public boolean isPersistentField(CtField ctField) {
// current check is to look for @Transient
return ! ctField.hasAnnotation( Transient.class );
}
@Override
public CtField[] order(CtField[] persistentFields) {
// for now...
return persistentFields;
// eventually needs to consult the Hibernate metamodel for proper ordering
}
}

View File

@ -23,35 +23,29 @@
*/
package org.hibernate.test.bytecode.enhancement;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Arrays;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
import org.hibernate.EntityMode;
import org.hibernate.LockMode;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.Enhancer;
import org.hibernate.cfg.Configuration;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.Status;
import org.hibernate.mapping.PersistentClass;
import org.junit.Test;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.hibernate.testing.junit4.ExtraAssertions;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertFalse;
@ -65,19 +59,44 @@ import static org.junit.Assert.assertSame;
public class EnhancerTest extends BaseUnitTestCase {
private static EnhancementContext enhancementContext = new EnhancementContext() {
@Override
public boolean isEntityClass(String className) {
public ClassLoader getLoadingClassLoader() {
return getClass().getClassLoader();
}
@Override
public boolean isEntityClass(CtClass classDescriptor) {
return true;
}
@Override
public boolean isCompositeClass(String className) {
public boolean isCompositeClass(CtClass classDescriptor) {
return false;
}
@Override
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
return true;
}
@Override
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
return true;
}
@Override
public boolean isLazyLoadable(CtField field) {
return true;
}
@Override
public boolean isPersistentField(CtField ctField) {
return true;
}
@Override
public CtField[] order(CtField[] persistentFields) {
return persistentFields;
}
};
@Test
@ -94,9 +113,9 @@ public class EnhancerTest extends BaseUnitTestCase {
assertFalse( "entity was not enhanced", Arrays.equals( original, enhanced ) );
ClassLoader cl = new ClassLoader() { };
ClassPool cp2 = new ClassPool( false );
cp2.appendClassPath( new LoaderClassPath( cl ) );
CtClass enhancedCtClass = cp2.makeClass( new ByteArrayInputStream( enhanced ) );
ClassPool cp = new ClassPool( false );
cp.appendClassPath( new LoaderClassPath( cl ) );
CtClass enhancedCtClass = cp.makeClass( new ByteArrayInputStream( enhanced ) );
Class entityClass = enhancedCtClass.toClass( cl, this.getClass().getProtectionDomain() );
Object entityInstance = entityClass.newInstance();
@ -124,6 +143,29 @@ public class EnhancerTest extends BaseUnitTestCase {
Method nextSetter = entityClass.getMethod( Enhancer.PREVIOUS_SETTER_NAME, ManagedEntity.class );
nextSetter.invoke( entityInstance, entityInstance );
assertSame( entityInstance, nextGetter.invoke( entityInstance ) );
// add an attribute interceptor...
Method interceptorGetter = entityClass.getMethod( Enhancer.INTERCEPTOR_GETTER_NAME );
Method interceptorSetter = entityClass.getMethod( Enhancer.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class );
assertNull( interceptorGetter.invoke( entityInstance ) );
entityClass.getMethod( "getId" ).invoke( entityInstance );
interceptorSetter.invoke( entityInstance, new LocalPersistentAttributeInterceptor() );
assertNotNull( interceptorGetter.invoke( entityInstance ) );
// dirty checking is unfortunately just printlns for now... just verify the test output
entityClass.getMethod( "getId" ).invoke( entityInstance );
entityClass.getMethod( "setId", Long.class ).invoke( entityInstance, entityClass.getMethod( "getId" ).invoke( entityInstance ) );
entityClass.getMethod( "setId", Long.class ).invoke( entityInstance, 1L );
entityClass.getMethod( "isActive" ).invoke( entityInstance );
entityClass.getMethod( "setActive", boolean.class ).invoke( entityInstance, entityClass.getMethod( "isActive" ).invoke( entityInstance ) );
entityClass.getMethod( "setActive", boolean.class ).invoke( entityInstance, true );
entityClass.getMethod( "getSomeNumber" ).invoke( entityInstance );
entityClass.getMethod( "setSomeNumber", long.class ).invoke( entityInstance, entityClass.getMethod( "getSomeNumber" ).invoke( entityInstance ) );
entityClass.getMethod( "setSomeNumber", long.class ).invoke( entityInstance, 1L );
}
private CtClass generateCtClassForAnEntity(Class entityClassToEnhance) throws Exception {
@ -153,4 +195,114 @@ public class EnhancerTest extends BaseUnitTestCase {
);
}
private class LocalPersistentAttributeInterceptor implements PersistentAttributeInterceptor {
@Override
public boolean readBoolean(Object obj, String name, boolean oldValue) {
System.out.println( "Reading boolean [" + name + "]" );
return oldValue;
}
@Override
public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) {
System.out.println( "Writing boolean [" + name + "]" );
return newValue;
}
@Override
public byte readByte(Object obj, String name, byte oldValue) {
System.out.println( "Reading byte [" + name + "]" );
return oldValue;
}
@Override
public byte writeByte(Object obj, String name, byte oldValue, byte newValue) {
System.out.println( "Writing byte [" + name + "]" );
return newValue;
}
@Override
public char readChar(Object obj, String name, char oldValue) {
System.out.println( "Reading char [" + name + "]" );
return oldValue;
}
@Override
public char writeChar(Object obj, String name, char oldValue, char newValue) {
System.out.println( "Writing char [" + name + "]" );
return newValue;
}
@Override
public short readShort(Object obj, String name, short oldValue) {
System.out.println( "Reading short [" + name + "]" );
return oldValue;
}
@Override
public short writeShort(Object obj, String name, short oldValue, short newValue) {
System.out.println( "Writing short [" + name + "]" );
return newValue;
}
@Override
public int readInt(Object obj, String name, int oldValue) {
System.out.println( "Reading int [" + name + "]" );
return oldValue;
}
@Override
public int writeInt(Object obj, String name, int oldValue, int newValue) {
System.out.println( "Writing int [" + name + "]" );
return newValue;
}
@Override
public float readFloat(Object obj, String name, float oldValue) {
System.out.println( "Reading float [" + name + "]" );
return oldValue;
}
@Override
public float writeFloat(Object obj, String name, float oldValue, float newValue) {
System.out.println( "Writing float [" + name + "]" );
return newValue;
}
@Override
public double readDouble(Object obj, String name, double oldValue) {
System.out.println( "Reading double [" + name + "]" );
return oldValue;
}
@Override
public double writeDouble(Object obj, String name, double oldValue, double newValue) {
System.out.println( "Writing double [" + name + "]" );
return newValue;
}
@Override
public long readLong(Object obj, String name, long oldValue) {
System.out.println( "Reading long [" + name + "]" );
return oldValue;
}
@Override
public long writeLong(Object obj, String name, long oldValue, long newValue) {
System.out.println( "Writing long [" + name + "]" );
return newValue;
}
@Override
public Object readObject(Object obj, String name, Object oldValue) {
System.out.println( "Reading Object [" + name + "]" );
return oldValue;
}
@Override
public Object writeObject(Object obj, String name, Object oldValue, Object newValue) {
System.out.println( "Writing Object [" + name + "]" );
return newValue;
}
}
}

View File

@ -56,23 +56,23 @@ public class MostBasicEnhancementTest extends BaseCoreFunctionalTestCase {
MyEntity myEntity = (MyEntity) s.get( MyEntity.class, 1L );
MyEntity myEntity2 = (MyEntity) s.get( MyEntity.class, 2L );
assertNotNull( myEntity.hibernate_getEntityInstance() );
assertSame( myEntity, myEntity.hibernate_getEntityInstance() );
assertNotNull( myEntity.hibernate_getEntityEntry() );
assertNull( myEntity.hibernate_getPreviousManagedEntity() );
assertNotNull( myEntity.hibernate_getNextManagedEntity() );
assertNotNull( myEntity.$$_hibernate_getEntityInstance() );
assertSame( myEntity, myEntity.$$_hibernate_getEntityInstance() );
assertNotNull( myEntity.$$_hibernate_getEntityEntry() );
assertNull( myEntity.$$_hibernate_getPreviousManagedEntity() );
assertNotNull( myEntity.$$_hibernate_getNextManagedEntity() );
assertNotNull( myEntity2.hibernate_getEntityInstance() );
assertSame( myEntity2, myEntity2.hibernate_getEntityInstance() );
assertNotNull( myEntity2.hibernate_getEntityEntry() );
assertNotNull( myEntity2.hibernate_getPreviousManagedEntity() );
assertNull( myEntity2.hibernate_getNextManagedEntity() );
assertNotNull( myEntity2.$$_hibernate_getEntityInstance() );
assertSame( myEntity2, myEntity2.$$_hibernate_getEntityInstance() );
assertNotNull( myEntity2.$$_hibernate_getEntityEntry() );
assertNotNull( myEntity2.$$_hibernate_getPreviousManagedEntity() );
assertNull( myEntity2.$$_hibernate_getNextManagedEntity() );
s.createQuery( "delete MyEntity" ).executeUpdate();
s.getTransaction().commit();
s.close();
assertNull( myEntity.hibernate_getEntityEntry() );
assertNull( myEntity.$$_hibernate_getEntityEntry() );
}

View File

@ -70,37 +70,37 @@ public class MyEntity implements ManagedEntity {
}
@Override
public Object hibernate_getEntityInstance() {
public Object $$_hibernate_getEntityInstance() {
return this;
}
@Override
public EntityEntry hibernate_getEntityEntry() {
public EntityEntry $$_hibernate_getEntityEntry() {
return entityEntry;
}
@Override
public void hibernate_setEntityEntry(EntityEntry entityEntry) {
public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) {
this.entityEntry = entityEntry;
}
@Override
public ManagedEntity hibernate_getNextManagedEntity() {
public ManagedEntity $$_hibernate_getNextManagedEntity() {
return next;
}
@Override
public void hibernate_setNextManagedEntity(ManagedEntity next) {
public void $$_hibernate_setNextManagedEntity(ManagedEntity next) {
this.next = next;
}
@Override
public ManagedEntity hibernate_getPreviousManagedEntity() {
public ManagedEntity $$_hibernate_getPreviousManagedEntity() {
return previous;
}
@Override
public void hibernate_setPreviousManagedEntity(ManagedEntity previous) {
public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) {
this.previous = previous;
}
}

View File

@ -0,0 +1,141 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement;
import javax.persistence.Id;
import javax.persistence.Transient;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
/**
* @author Steve Ebersole
*/
public class SampleEntity implements ManagedEntity, PersistentAttributeInterceptable {
@Transient
private transient EntityEntry entityEntry;
@Transient
private transient ManagedEntity previous;
@Transient
private transient ManagedEntity next;
@Transient
private transient PersistentAttributeInterceptor interceptor;
private Long id;
private String name;
@Id
public Long getId() {
return hibernate_read_id();
}
public void setId(Long id) {
hibernate_write_id( id );
}
public String getName() {
return hibernate_read_name();
}
public void setName(String name) {
hibernate_write_name( name );
}
private Long hibernate_read_id() {
if ( $$_hibernate_getInterceptor() != null ) {
this.id = (Long) $$_hibernate_getInterceptor().readObject( this, "id", this.id );
}
return id;
}
private void hibernate_write_id(Long id) {
Long localVar = id;
if ( $$_hibernate_getInterceptor() != null ) {
localVar = (Long) $$_hibernate_getInterceptor().writeObject( this, "id", this.id, id );
}
this.id = localVar;
}
private String hibernate_read_name() {
if ( $$_hibernate_getInterceptor() != null ) {
this.name = (String) $$_hibernate_getInterceptor().readObject( this, "name", this.name );
}
return name;
}
private void hibernate_write_name(String name) {
String localName = name;
if ( $$_hibernate_getInterceptor() != null ) {
localName = (String) $$_hibernate_getInterceptor().writeObject( this, "name", this.name, name );
}
this.name = localName;
}
@Override
public Object $$_hibernate_getEntityInstance() {
return this;
}
@Override
public EntityEntry $$_hibernate_getEntityEntry() {
return entityEntry;
}
@Override
public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) {
this.entityEntry = entityEntry;
}
@Override
public ManagedEntity $$_hibernate_getNextManagedEntity() {
return next;
}
@Override
public void $$_hibernate_setNextManagedEntity(ManagedEntity next) {
this.next = next;
}
@Override
public ManagedEntity $$_hibernate_getPreviousManagedEntity() {
return previous;
}
@Override
public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) {
this.previous = previous;
}
@Override
public PersistentAttributeInterceptor $$_hibernate_getInterceptor() {
return interceptor;
}
@Override
public void $$_hibernate_setInterceptor(PersistentAttributeInterceptor interceptor) {
this.interceptor = interceptor;
}
}

View File

@ -33,6 +33,8 @@ import javax.persistence.Id;
public class SimpleEntity {
private Long id;
private String name;
private boolean active;
private long someNumber;
@Id
public Long getId() {
@ -50,4 +52,20 @@ public class SimpleEntity {
public void setName(String name) {
this.name = name;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public long getSomeNumber() {
return someNumber;
}
public void setSomeNumber(long someNumber) {
this.someNumber = someNumber;
}
}

View File

@ -24,10 +24,48 @@
package org.hibernate.test.bytecode.enhancement;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author Steve Ebersole
*/
@Entity
public class SubEntity extends SuperEntity {
private Long id;
private String name;
private boolean active;
private long someNumber;
@Id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public long getSomeNumber() {
return someNumber;
}
public void setSomeNumber(long someNumber) {
this.someNumber = someNumber;
}
}