Skip to content

Commit ae1a762

Browse files
Merge pull request #16 from Sanne/HibernateBytecodeEnhancement
Automatic Hibernate bytecode enhancement
2 parents 8e5a2d6 + 42bc561 commit ae1a762

File tree

6 files changed

+212
-1
lines changed

6 files changed

+212
-1
lines changed

jpa/deployment/pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@
2424
<groupId>org.hibernate</groupId>
2525
<artifactId>hibernate-core</artifactId>
2626
</dependency>
27+
<dependency>
28+
<groupId>junit</groupId>
29+
<artifactId>junit</artifactId>
30+
<scope>test</scope>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.jboss.protean.gizmo</groupId>
34+
<artifactId>gizmo</artifactId>
35+
<type>test-jar</type>
36+
<scope>test</scope>
37+
</dependency>
2738
</dependencies>
2839

2940

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package org.jboss.shamrock.jpa;
2+
3+
import java.util.Set;
4+
import java.util.function.Function;
5+
6+
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
7+
import org.hibernate.bytecode.enhance.spi.Enhancer;
8+
import org.hibernate.bytecode.spi.BytecodeProvider;
9+
10+
import org.objectweb.asm.ClassReader;
11+
import org.objectweb.asm.ClassVisitor;
12+
import org.objectweb.asm.ClassWriter;
13+
import org.objectweb.asm.Opcodes;
14+
15+
/**
16+
* Used to transform bytecode by registering to org.jboss.shamrock.deployment.ProcessorContext#addByteCodeTransformer(java.util.function.Function)
17+
* this function adapts the Shamrock bytecode transformer API - which uses ASM - to use the Entity Enhancement API of
18+
* Hibernate ORM, which exposes a simple byte array.
19+
*
20+
* @author Sanne Grinovero <sanne@hibernate.org>
21+
*/
22+
public final class HibernateEntityEnhancer implements Function<String, Function<ClassVisitor, ClassVisitor>> {
23+
24+
private final Enhancer enhancer;
25+
private final Set<String> classnameWhitelist;
26+
27+
@Deprecated //shouldn't use this version, but will do for a while
28+
public HibernateEntityEnhancer() {
29+
this(null);
30+
}
31+
32+
public HibernateEntityEnhancer(Set<String> classnameWhitelist) {
33+
this.classnameWhitelist = classnameWhitelist;
34+
BytecodeProvider provider = new org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl();
35+
this.enhancer = provider.getEnhancer(new DefaultEnhancementContext());
36+
}
37+
38+
@Override
39+
public Function<ClassVisitor, ClassVisitor> apply(String classname) {
40+
if (classnameWhitelist == null || classnameWhitelist.contains(classname))
41+
return new HibernateTransformingVisitorFunction(classname);
42+
else
43+
return null;
44+
}
45+
46+
/**
47+
* Having to convert a ClassVisitor into another, this allows visitor chaining: the returned ClassVisitor needs to
48+
* refer to the previous ClassVisitor in the chain to forward input events (optionally transformed).
49+
*/
50+
private class HibernateTransformingVisitorFunction implements Function<ClassVisitor, ClassVisitor> {
51+
52+
private final String className;
53+
54+
public HibernateTransformingVisitorFunction(String className) {
55+
this.className = className;
56+
}
57+
58+
@Override
59+
public ClassVisitor apply(ClassVisitor outputClassVisitor) {
60+
return new HibernateEnhancingClassVisitor(className, outputClassVisitor);
61+
}
62+
}
63+
64+
private class HibernateEnhancingClassVisitor extends ClassVisitor {
65+
66+
private final String className;
67+
private final ClassVisitor outputClassVisitor;
68+
69+
public HibernateEnhancingClassVisitor(String className, ClassVisitor outputClassVisitor) {
70+
super(Opcodes.ASM6, new ClassWriter(0));
71+
this.className = className;
72+
this.outputClassVisitor = outputClassVisitor;
73+
}
74+
75+
public void visitEnd() {
76+
super.visitEnd();
77+
final ClassWriter writer = (ClassWriter) this.cv; //safe cast: cv is the the ClassWriter instance we passed to the super constructor
78+
//We need to convert the nice Visitor chain into a plain byte array to adapt to the Hibernate ORM
79+
//enhancement API:
80+
final byte[] inputBytes = writer.toByteArray();
81+
final byte[] transformedBytes = hibernateEnhancement(className, inputBytes);
82+
//Then re-convert the transformed bytecode to not interrupt the visitor chain:
83+
ClassReader cr = new ClassReader(transformedBytes);
84+
cr.accept(outputClassVisitor, 0);
85+
}
86+
87+
}
88+
89+
private byte[] hibernateEnhancement(final String className, final byte[] originalBytes) {
90+
final byte[] enhanced = enhancer.enhance(className, originalBytes);
91+
return enhanced == null ? originalBytes : enhanced;
92+
}
93+
94+
}

jpa/deployment/src/main/java/org/jboss/shamrock/jpa/JPAAnnotationProcessor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public class JPAAnnotationProcessor implements ResourceProcessor {
2727

2828
@Override
2929
public void process(ArchiveContext archiveContext, ProcessorContext processorContext) throws Exception {
30+
31+
enhanceEntities(archiveContext, processorContext);
32+
3033
// Hibernate specific reflective classes
3134
processorContext.addReflectiveClass(false, false, org.hibernate.jpa.HibernatePersistenceProvider.class.getName());
3235
processorContext.addReflectiveClass(false, false, org.hibernate.persister.entity.SingleTableEntityPersister.class.getName());
@@ -47,6 +50,11 @@ public void process(ArchiveContext archiveContext, ProcessorContext processorCon
4750
}
4851
}
4952

53+
private void enhanceEntities(ArchiveContext archiveContext, ProcessorContext processorContext) {
54+
//TODO make sure we pass the list of known entities as a white-list filter so to avoid processing all classes
55+
processorContext.addByteCodeTransformer(new HibernateEntityEnhancer());
56+
}
57+
5058
private void enlistReturnType(ProcessorContext processorContext, IndexView index) {
5159
Collection<AnnotationInstance> annotations = index.getAnnotations(EMBEDDED);
5260
if (annotations != null && annotations.size() > 0) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.jboss.shamrock.jpa.enhancer;
2+
3+
import javax.persistence.Entity;
4+
import javax.persistence.GeneratedValue;
5+
import javax.persistence.GenerationType;
6+
import javax.persistence.Id;
7+
8+
@Entity
9+
public class Address {
10+
11+
private long id;
12+
private String street;
13+
14+
public Address() {
15+
}
16+
17+
public Address(String street) {
18+
this.street = street;
19+
}
20+
21+
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="addressSeq")
22+
public long getId() {
23+
return id;
24+
}
25+
26+
public void setId(long id) {
27+
this.id = id;
28+
}
29+
30+
public String getStreet() {
31+
return street;
32+
}
33+
34+
public void setStreet(String name) {
35+
this.street = name;
36+
}
37+
38+
public void describeFully(StringBuilder sb) {
39+
sb.append( "Address with id=" ).append( id ).append( ", street='" ).append( street ).append( "'" );
40+
}
41+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.jboss.shamrock.jpa.enhancer;
2+
3+
import java.io.IOException;
4+
import java.util.Arrays;
5+
import java.util.HashSet;
6+
import java.util.Set;
7+
8+
import org.hibernate.engine.spi.ManagedEntity;
9+
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
10+
import org.hibernate.engine.spi.SelfDirtinessTracker;
11+
import org.jboss.shamrock.jpa.HibernateEntityEnhancer;
12+
13+
import org.junit.Assert;
14+
import org.junit.Test;
15+
import org.objectweb.asm.ClassReader;
16+
import org.objectweb.asm.ClassVisitor;
17+
import org.objectweb.asm.ClassWriter;
18+
19+
import org.jboss.protean.gizmo.TestClassLoader;
20+
21+
/**
22+
* Verifies the HibernateEntityEnhancer actually does enhance the entity class
23+
*
24+
* @author Sanne Grinovero <sanne@hibernate.org>
25+
*/
26+
public class HibernateEntityEnhancerTest {
27+
28+
@Test
29+
public void testBytecodeEnhancement() throws IOException, ClassNotFoundException {
30+
31+
Assert.assertFalse(isEnhanced(Address.class));
32+
33+
final String className = Address.class.getName();
34+
35+
ClassReader classReader = new ClassReader(className);
36+
ClassWriter writer = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
37+
ClassVisitor visitor = writer;
38+
HibernateEntityEnhancer hibernateEntityEnhancer = new HibernateEntityEnhancer();
39+
visitor = hibernateEntityEnhancer.apply(className).apply(visitor);
40+
classReader.accept(visitor, 0);
41+
final byte[] modifiedBytecode = writer.toByteArray();
42+
43+
TestClassLoader cl = new TestClassLoader(getClass().getClassLoader());
44+
cl.write(className, modifiedBytecode);
45+
final Class<?> modifiedClass = cl.loadClass(className);
46+
Assert.assertTrue(isEnhanced(modifiedClass));
47+
}
48+
49+
private boolean isEnhanced(final Class<?> modifiedClass) {
50+
Set<Class> interfaces = new HashSet<Class>(Arrays.asList(modifiedClass.getInterfaces()));
51+
//Assert it now implements these three interfaces:
52+
return interfaces.contains(ManagedEntity.class) &&
53+
interfaces.contains(PersistentAttributeInterceptable.class) &&
54+
interfaces.contains(SelfDirtinessTracker.class);
55+
}
56+
57+
}

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
<jboss-invocation.version>1.5.1.Final</jboss-invocation.version>
3131
<javax.json.version>1.1.2</javax.json.version>
3232
<jboss-jaxrs-api_2.1_spec.version>1.0.0.Final</jboss-jaxrs-api_2.1_spec.version>
33-
<asm.version>6.0</asm.version>
33+
<asm.version>6.2.1</asm.version>
3434
<logback.version>1.1.7</logback.version>
3535
<cdi-api.version>2.0</cdi-api.version>
3636
<fakereplace.version>1.0.0.Alpha7</fakereplace.version>

0 commit comments

Comments
 (0)