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 {
+}