diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java index 426972a83d..1c28b6ac4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java @@ -23,12 +23,40 @@ */ package org.hibernate.bytecode.enhance.spi; -import java.security.ProtectionDomain; +import javassist.CtField; /** * @author Steve Ebersole */ public interface EnhancementContext { + /** + * Does the given class name represent a entity class? + * + * @param className The name of the class to check. + * + * @return {@code true} if the class is an entity; {@code false} otherwise. + */ public boolean isEntityClass(String className); + + /** + * Does the given class name represent an embeddable/component class? + * + * @param className The name of the class to check. + * + * @return {@code true} if the class is an embeddable/component; {@code false} otherwise. + */ public boolean isCompositeClass(String className); + + /** + * Does the field represent persistent state? Persistent fields will be "enhanced". + *

+ * 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... + + * @param ctField The field reference. + * + * @return {@code true} if the field is ; {@code false} otherwise. + */ + public boolean isPersistentField(CtField ctField); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java index 2d2617ffc6..51c405615f 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java @@ -28,11 +28,13 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.util.IdentityHashMap; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; +import javassist.CtMethod; import javassist.CtNewMethod; import javassist.Modifier; import javassist.bytecode.AnnotationsAttribute; @@ -44,7 +46,7 @@ import org.jboss.logging.Logger; import org.hibernate.HibernateException; import org.hibernate.bytecode.enhance.EnhancementException; -import org.hibernate.bytecode.internal.javassist.FieldHandled; +import org.hibernate.engine.spi.Managed; import org.hibernate.engine.spi.ManagedComposite; import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.EntityEntry; @@ -57,6 +59,9 @@ import org.hibernate.internal.CoreMessageLogger; public class Enhancer { private static final CoreMessageLogger log = Logger.getMessageLogger( CoreMessageLogger.class, Enhancer.class.getName() ); + private static final String PERSISTENT_FIELD_READER_PREFIX = "$hibernate_read_"; + private static final String PERSISTENT_FIELD_WRITER_PREFIX = "$hibernate_write_"; + public static final String ENTITY_INSTANCE_GETTER_NAME = "hibernate_getEntityInstance"; public static final String ENTITY_ENTRY_FIELD_NAME = "$hibernate_entityEntryHolder"; @@ -168,7 +173,7 @@ public class Enhancer { final String[] interfaceNames = managedCtClass.getClassFile2().getInterfaces(); for ( String interfaceName : interfaceNames ) { - if ( FieldHandled.class.getName().equals( interfaceName ) ) { + if ( Managed.class.getName().equals( interfaceName ) ) { log.debug( "skipping enhancement : already enhanced" ); return; } @@ -191,13 +196,18 @@ public class Enhancer { // add the ManagedEntity interface managedCtClass.addInterface( managedEntityCtClass ); - addEntityInstanceHandling( managedCtClass, constPool ); - addEntityEntryHandling( managedCtClass, constPool ); - addLinkedPreviousHandling( managedCtClass, constPool ); - addLinkedNextHandling( managedCtClass, constPool ); +// enhancePersistentAttributes( managedCtClass ); + + addEntityInstanceHandling( managedCtClass ); + addEntityEntryHandling( managedCtClass ); + addLinkedPreviousHandling( managedCtClass ); + addLinkedNextHandling( managedCtClass ); } - private void addEntityInstanceHandling(CtClass managedCtClass, ConstPool constPool) { + private void enhanceAsComposite(CtClass classFile) { + } + + private void addEntityInstanceHandling(CtClass managedCtClass) { // add the ManagedEntity#hibernate_getEntityInstance method try { managedCtClass.addMethod( @@ -224,7 +234,9 @@ public class Enhancer { // essentially add `return this;` } - private void addEntityEntryHandling(CtClass managedCtClass, ConstPool constPool) { + private void addEntityEntryHandling(CtClass managedCtClass) { + final ConstPool constPool = managedCtClass.getClassFile().getConstPool(); + // add field to hold EntityEntry final CtField entityEntryField; try { @@ -243,6 +255,7 @@ public class Enhancer { // make that new field transient and @Transient entityEntryField.setModifiers( entityEntryField.getModifiers() | Modifier.TRANSIENT ); + entityEntryField.setModifiers( Modifier.setPrivate( entityEntryField.getModifiers() ) ); AnnotationsAttribute annotationsAttribute = getVisibleAnnotations( entityEntryField.getFieldInfo() ); annotationsAttribute.addAnnotation( new Annotation( Transient.class.getName(), constPool ) ); @@ -279,11 +292,13 @@ public class Enhancer { } } - private void addLinkedPreviousHandling(CtClass managedCtClass, ConstPool constPool) { + private void addLinkedPreviousHandling(CtClass managedCtClass) { + final ConstPool constPool = managedCtClass.getClassFile().getConstPool(); + // add field to hold "previous" ManagedEntity final CtField previousField; try { - previousField = new CtField( managedCtClass, PREVIOUS_FIELD_NAME, managedCtClass ); + previousField = new CtField( managedEntityCtClass, PREVIOUS_FIELD_NAME, managedCtClass ); managedCtClass.addField( previousField ); } catch (CannotCompileException e) { @@ -298,6 +313,7 @@ public class Enhancer { // make that new field transient and @Transient previousField.setModifiers( previousField.getModifiers() | Modifier.TRANSIENT ); + previousField.setModifiers( Modifier.setPrivate( previousField.getModifiers() ) ); AnnotationsAttribute annotationsAttribute = getVisibleAnnotations( previousField.getFieldInfo() ); annotationsAttribute.addAnnotation( new Annotation( Transient.class.getName(), constPool ) ); @@ -330,11 +346,13 @@ public class Enhancer { } } - private void addLinkedNextHandling(CtClass managedCtClass, ConstPool constPool) { + private void addLinkedNextHandling(CtClass managedCtClass) { + final ConstPool constPool = managedCtClass.getClassFile().getConstPool(); + // add field to hold "next" ManagedEntity final CtField nextField; try { - nextField = new CtField( managedCtClass, NEXT_FIELD_NAME, managedCtClass ); + nextField = new CtField( managedEntityCtClass, NEXT_FIELD_NAME, managedCtClass ); managedCtClass.addField( nextField ); } catch (CannotCompileException e) { @@ -347,8 +365,9 @@ public class Enhancer { ); } - // make that new field transient and @Transient + // make that new field (1) private, (2) transient and (3) @Transient nextField.setModifiers( nextField.getModifiers() | Modifier.TRANSIENT ); + nextField.setModifiers( Modifier.setPrivate( nextField.getModifiers() ) ); AnnotationsAttribute annotationsAttribute = getVisibleAnnotations( nextField.getFieldInfo() ); annotationsAttribute.addAnnotation( new Annotation( Transient.class.getName(), constPool ) ); @@ -390,8 +409,84 @@ public class Enhancer { return annotationsAttribute; } - private void enhanceAsComposite(CtClass classFile) { + private void enhancePersistentAttributes(CtClass managedCtClass) { + final IdentityHashMap fieldToMethodsXref = new IdentityHashMap(); + + for ( CtField ctField : managedCtClass.getFields() ) { + if ( ! enhancementContext.isPersistentField( ctField ) ) { + continue; + } + + final FieldVirtualReadWritePair methodPair = addReadAndWriteMethod( managedCtClass, ctField ); + fieldToMethodsXref.put( ctField, methodPair ); + } + + transformFieldAccessesIntoReadsAndWrites( managedCtClass, fieldToMethodsXref ); } + private FieldVirtualReadWritePair addReadAndWriteMethod(CtClass managedCtClass, CtField persistentField) { + // add the "reader" + final CtMethod reader = generateFieldReader( managedCtClass, persistentField ); + + // add the "writer" + final CtMethod writer = generateFieldWriter( managedCtClass, persistentField ); + + return new FieldVirtualReadWritePair( reader, writer ); + } + + private CtMethod generateFieldReader(CtClass managedCtClass, CtField persistentField) { + // todo : temporary; still need to add hooks into lazy-loading + try { + final String name = PERSISTENT_FIELD_READER_PREFIX + persistentField.getName(); + CtMethod reader = CtNewMethod.getter( name, persistentField ); + managedCtClass.addMethod( reader ); + return reader; + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add virtual reader method for field [%s]", + managedCtClass.getName(), + persistentField.getName() + ), + e + ); + } + } + + private CtMethod generateFieldWriter(CtClass managedCtClass, CtField persistentField) { + // todo : temporary; still need to add hooks into lazy-loading and dirtying + try { + final String name = PERSISTENT_FIELD_WRITER_PREFIX + persistentField.getName(); + CtMethod writer = CtNewMethod.setter( name, persistentField ); + managedCtClass.addMethod( writer ); + return writer; + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add virtual writer method for field [%s]", + managedCtClass.getName(), + persistentField.getName() + ), + e + ); + } + } + + private void transformFieldAccessesIntoReadsAndWrites( + CtClass managedCtClass, + IdentityHashMap fieldToMethodsXref) { + } + + private static class FieldVirtualReadWritePair { + private final CtMethod readMethod; + private final CtMethod writeMethod; + + private FieldVirtualReadWritePair(CtMethod readMethod, CtMethod writeMethod) { + this.readMethod = readMethod; + this.writeMethod = writeMethod; + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java b/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java index e51901bd8d..dc7b743b17 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java @@ -24,6 +24,7 @@ package org.hibernate.tool.enhance; import javax.persistence.Entity; +import javax.persistence.Transient; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -33,7 +34,10 @@ 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; @@ -74,6 +78,12 @@ public class EnhancementTask extends Task { 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 ); + } }; // we use the CtClass stuff here just as a simple vehicle for obtaining low level information about diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java index add986ae51..592a8ae658 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java @@ -36,6 +36,7 @@ import java.util.Arrays; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; +import javassist.CtField; import javassist.LoaderClassPath; import javassist.NotFoundException; @@ -44,68 +45,92 @@ import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.Enhancer; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.Status; 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; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; -import static org.junit.Assert.fail; /** * @author Steve Ebersole */ public class EnhancerTest extends BaseUnitTestCase { - @Test - public void testEnhancement() - throws IOException, CannotCompileException, NotFoundException, - NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { - EnhancementContext enhancementContext = new EnhancementContext() { - @Override - public boolean isEntityClass(String className) { - return true; - } + private static EnhancementContext enhancementContext = new EnhancementContext() { + @Override + public boolean isEntityClass(String className) { + return true; + } - @Override - public boolean isCompositeClass(String className) { - return false; - } - }; + @Override + public boolean isCompositeClass(String className) { + return false; + } + + @Override + public boolean isPersistentField(CtField ctField) { + return true; + } + }; + + @Test + public void testEnhancement() throws Exception { + testFor( SimpleEntity.class ); + testFor( SubEntity.class ); + } + + private void testFor(Class entityClassToEnhance) throws Exception { Enhancer enhancer = new Enhancer( enhancementContext ); - CtClass anEntityCtClass = generateCtClassForAnEntity(); - byte[] original = anEntityCtClass.toBytecode(); - byte[] enhanced = enhancer.enhance( anEntityCtClass.getName(), original ); + CtClass entityCtClass = generateCtClassForAnEntity( entityClassToEnhance ); + byte[] original = entityCtClass.toBytecode(); + byte[] enhanced = enhancer.enhance( entityCtClass.getName(), original ); 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 ) ); - Class simpleEntityClass = enhancedCtClass.toClass( cl, this.getClass().getProtectionDomain() ); - Object simpleEntityInstance = simpleEntityClass.newInstance(); + Class entityClass = enhancedCtClass.toClass( cl, this.getClass().getProtectionDomain() ); + Object entityInstance = entityClass.newInstance(); + + assertTyping( ManagedEntity.class, entityInstance ); // call the new methods - Method setter = simpleEntityClass.getMethod( Enhancer.ENTITY_ENTRY_SETTER_NAME, EntityEntry.class ); - Method getter = simpleEntityClass.getMethod( Enhancer.ENTITY_ENTRY_GETTER_NAME ); - assertNull( getter.invoke( simpleEntityInstance ) ); - setter.invoke( simpleEntityInstance, makeEntityEntry() ); - assertNotNull( getter.invoke( simpleEntityInstance ) ); - setter.invoke( simpleEntityInstance, new Object[] { null } ); - assertNull( getter.invoke( simpleEntityInstance ) ); + // + Method setter = entityClass.getMethod( Enhancer.ENTITY_ENTRY_SETTER_NAME, EntityEntry.class ); + Method getter = entityClass.getMethod( Enhancer.ENTITY_ENTRY_GETTER_NAME ); + assertNull( getter.invoke( entityInstance ) ); + setter.invoke( entityInstance, makeEntityEntry() ); + assertNotNull( getter.invoke( entityInstance ) ); + setter.invoke( entityInstance, new Object[] {null} ); + assertNull( getter.invoke( entityInstance ) ); - Method entityInstanceGetter = simpleEntityClass.getMethod( Enhancer.ENTITY_INSTANCE_GETTER_NAME ); - assertSame( simpleEntityInstance, entityInstanceGetter.invoke( simpleEntityInstance ) ); + Method entityInstanceGetter = entityClass.getMethod( Enhancer.ENTITY_INSTANCE_GETTER_NAME ); + assertSame( entityInstance, entityInstanceGetter.invoke( entityInstance ) ); + + Method previousGetter = entityClass.getMethod( Enhancer.PREVIOUS_GETTER_NAME ); + Method previousSetter = entityClass.getMethod( Enhancer.PREVIOUS_SETTER_NAME, ManagedEntity.class ); + previousSetter.invoke( entityInstance, entityInstance ); + assertSame( entityInstance, previousGetter.invoke( entityInstance ) ); + + Method nextGetter = entityClass.getMethod( Enhancer.PREVIOUS_GETTER_NAME ); + Method nextSetter = entityClass.getMethod( Enhancer.PREVIOUS_SETTER_NAME, ManagedEntity.class ); + nextSetter.invoke( entityInstance, entityInstance ); + assertSame( entityInstance, nextGetter.invoke( entityInstance ) ); } - private CtClass generateCtClassForAnEntity() throws IOException, NotFoundException { + private CtClass generateCtClassForAnEntity(Class entityClassToEnhance) throws Exception { ClassPool cp = new ClassPool( false ); return cp.makeClass( getClass().getClassLoader().getResourceAsStream( - SimpleEntity.class.getName().replace( '.', '/' ) + ".class" + entityClassToEnhance.getName().replace( '.', '/' ) + ".class" ) ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SubEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SubEntity.java new file mode 100644 index 0000000000..21d3d8587a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SubEntity.java @@ -0,0 +1,33 @@ +/* + * 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.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +public class SubEntity extends SuperEntity { +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SuperEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SuperEntity.java new file mode 100644 index 0000000000..8306fa97ce --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/SuperEntity.java @@ -0,0 +1,33 @@ +/* + * 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.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +public class SuperEntity { +}