fix issue with not compiling tests
This commit is contained in:
parent
1a6b01a2a8
commit
fc914ea647
|
@ -32,6 +32,12 @@ sourceSets {
|
|||
}
|
||||
}
|
||||
|
||||
testJavassist {
|
||||
java {
|
||||
compileClasspath += main.output + test.output
|
||||
runtimeClasspath += main.output + test.output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
|
@ -39,10 +45,16 @@ configurations {
|
|||
description = 'Configuration for the produced test jar'
|
||||
}
|
||||
|
||||
//Configures the compile and runtime configurations for our javassist tests
|
||||
//and includes the dependencies of the test task.
|
||||
testJavassistCompile.extendsFrom testCompile
|
||||
testJavassistRuntime.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile( libraries.jpa )
|
||||
// This can now be made provided
|
||||
compile( libraries.javassist )
|
||||
compile( libraries.byteBuddy )
|
||||
compile( libraries.antlr )
|
||||
compile( libraries.jta )
|
||||
|
@ -101,6 +113,7 @@ dependencies {
|
|||
testRuntime( "org.jboss.spec.javax.ejb:jboss-ejb-api_3.2_spec:1.0.0.Final" )
|
||||
testRuntime( libraries.expression_language )
|
||||
testRuntime( 'jaxen:jaxen:1.1' )
|
||||
testRuntime( libraries.javassist )
|
||||
testRuntime( libraries.byteBuddy )
|
||||
testRuntime( libraries.weld )
|
||||
testRuntime( libraries.atomikos )
|
||||
|
@ -115,6 +128,9 @@ dependencies {
|
|||
testCompile libraries.jboss_ejb_spec_jar
|
||||
testCompile libraries.jboss_annotation_spec_jar
|
||||
|
||||
// Additional tests requiring Javassist
|
||||
// folder in src/javassist/java
|
||||
testJavassistCompile libraries.javassist
|
||||
}
|
||||
|
||||
jar {
|
||||
|
@ -390,3 +406,15 @@ class Antlr4GenerationTask extends DefaultTask {
|
|||
}
|
||||
|
||||
}
|
||||
//Create the task that runs the integration tests found from the
|
||||
//configured source directory and uses the correct classpath.
|
||||
task testJavassist(type: Test) {
|
||||
testClassesDirs = sourceSets.testJavassist.output.classesDirs
|
||||
classpath = sourceSets.testJavassist.runtimeClasspath
|
||||
//If you want to ensure that integration tests are run every time when you invoke
|
||||
//this task, uncomment the following line.
|
||||
//outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
check.dependsOn testJavassist
|
||||
testJavassist.mustRunAfter test
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
import javassist.NotFoundException;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
|
||||
/**
|
||||
* utility class to generate interceptor methods
|
||||
* @see org.hibernate.engine.spi.PersistentAttributeInterceptor
|
||||
*
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
*/
|
||||
public abstract class AttributeTypeDescriptor {
|
||||
|
||||
protected InheritanceMetadata inheritanceMetadata;
|
||||
|
||||
protected AttributeTypeDescriptor(InheritanceMetadata inheritanceMetadata) {
|
||||
this.inheritanceMetadata = inheritanceMetadata;
|
||||
}
|
||||
|
||||
public abstract String buildReadInterceptionBodyFragment(String fieldName);
|
||||
|
||||
public abstract String buildWriteInterceptionBodyFragment(String fieldName);
|
||||
|
||||
public String buildInLineDirtyCheckingBodyFragment(JavassistEnhancementContext context, CtField currentValue) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try {
|
||||
// should ignore primary keys
|
||||
if ( PersistentAttributesHelper.hasAnnotation( currentValue, Id.class )
|
||||
|| PersistentAttributesHelper.hasAnnotation( currentValue, EmbeddedId.class ) ) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String readFragment = inheritanceMetadata.isInherited() && !inheritanceMetadata.isVisible()
|
||||
? "super." + inheritanceMetadata.getReaderName() + "()"
|
||||
: "this." + currentValue.getName();
|
||||
|
||||
if ( currentValue.getType().isPrimitive() || currentValue.getType().isEnum() ) {
|
||||
// primitives || enums
|
||||
builder.append( String.format( " if ( %s != $1 )", readFragment ) );
|
||||
}
|
||||
else {
|
||||
// if the field is a collection we return since we handle that in a separate method
|
||||
for ( CtClass ctClass : currentValue.getType().getInterfaces() ) {
|
||||
if ( ctClass.getName().equals( Collection.class.getName() ) ) {
|
||||
// if the collection is not managed we should write it to the tracker
|
||||
if ( context.isMappedCollection( currentValue ) ) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.append(
|
||||
String.format(
|
||||
" if ( !%s.deepEquals( %s, $1 ) )",
|
||||
Objects.class.getName(),
|
||||
readFragment
|
||||
)
|
||||
);
|
||||
}
|
||||
builder.append( String.format( " { %s(\"%s\"); }", EnhancerConstants.TRACKER_CHANGER_NAME, currentValue.getName() ) );
|
||||
}
|
||||
catch (NotFoundException ignore) {
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
/**
|
||||
* factory method to get the AttributeTypeDescriptor for a particular field type
|
||||
*/
|
||||
public static AttributeTypeDescriptor resolve(CtClass managedCtClass, CtField persistentField) throws NotFoundException {
|
||||
boolean inherited = !managedCtClass.equals( persistentField.getDeclaringClass() );
|
||||
boolean visible = persistentField.visibleFrom( managedCtClass );
|
||||
String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName();
|
||||
String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + persistentField.getName();
|
||||
InheritanceMetadata inheritanceMetadata = new InheritanceMetadata( inherited, visible, readerName, writerName );
|
||||
|
||||
if ( CtClass.booleanType.equals( persistentField.getType() ) ) {
|
||||
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Boolean.TYPE );
|
||||
}
|
||||
else if ( CtClass.byteType.equals( persistentField.getType() )) {
|
||||
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Byte.TYPE );
|
||||
}
|
||||
else if ( CtClass.charType.equals( persistentField.getType() ) ) {
|
||||
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Character.TYPE );
|
||||
}
|
||||
else if ( CtClass.shortType.equals( persistentField.getType() ) ) {
|
||||
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Short.TYPE );
|
||||
}
|
||||
else if ( CtClass.intType.equals( persistentField.getType() ) ) {
|
||||
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Integer.TYPE );
|
||||
}
|
||||
else if ( CtClass.longType.equals( persistentField.getType() ) ) {
|
||||
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Long.TYPE );
|
||||
}
|
||||
else if ( CtClass.doubleType.equals( persistentField.getType() ) ) {
|
||||
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Double.TYPE );
|
||||
}
|
||||
else if ( CtClass.floatType.equals( persistentField.getType() ) ) {
|
||||
return new PrimitiveAttributeTypeDescriptor( inheritanceMetadata, Float.TYPE );
|
||||
}
|
||||
else {
|
||||
return new ObjectAttributeTypeDescriptor( inheritanceMetadata, persistentField.getType() );
|
||||
}
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
/**
|
||||
* AttributeTypeDescriptor for non primitive types
|
||||
*/
|
||||
private static class ObjectAttributeTypeDescriptor extends AttributeTypeDescriptor {
|
||||
|
||||
private final String type;
|
||||
|
||||
private ObjectAttributeTypeDescriptor(InheritanceMetadata inheritanceMetadata, CtClass concreteType) {
|
||||
super( inheritanceMetadata );
|
||||
this.type = concreteType.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildReadInterceptionBodyFragment(String fieldName) {
|
||||
if ( inheritanceMetadata.isInherited() && !inheritanceMetadata.isVisible() ) {
|
||||
return String.format(
|
||||
" if( %3$s() != null ) { super.%5$s( (%2$s) %3$s().readObject(this, \"%1$s\", super.%4$s())); }%n",
|
||||
fieldName,
|
||||
type,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
|
||||
inheritanceMetadata.getReaderName(),
|
||||
inheritanceMetadata.getWriterName() );
|
||||
}
|
||||
else {
|
||||
return String.format(
|
||||
" if ( %3$s() != null ) { this.%1$s = (%2$s) %3$s().readObject(this, \"%1$s\", this.%1$s); }%n",
|
||||
fieldName,
|
||||
type,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildWriteInterceptionBodyFragment(String fieldName) {
|
||||
if ( inheritanceMetadata.isInherited() && !inheritanceMetadata.isVisible() ) {
|
||||
return String.format(
|
||||
" %2$s localVar = $1;%n" +
|
||||
" if ( %3$s() != null ) { localVar = (%2$s) %3$s().writeObject(this, \"%1$s\", super.%4$s(), $1); }%n" +
|
||||
" super.%5$s(localVar);",
|
||||
fieldName,
|
||||
type,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
|
||||
inheritanceMetadata.getReaderName(),
|
||||
inheritanceMetadata.getWriterName() );
|
||||
}
|
||||
else {
|
||||
return String.format(
|
||||
" %2$s localVar = $1;%n" +
|
||||
" if ( %3$s() != null ) { localVar = (%2$s) %3$s().writeObject(this, \"%1$s\", this.%1$s, $1); }%n" +
|
||||
" this.%1$s = localVar;",
|
||||
fieldName,
|
||||
type,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AttributeTypeDescriptor for primitive types
|
||||
*/
|
||||
private static class PrimitiveAttributeTypeDescriptor extends AttributeTypeDescriptor {
|
||||
|
||||
private final String type;
|
||||
|
||||
private PrimitiveAttributeTypeDescriptor(InheritanceMetadata inheritanceMetadata, Class<?> primitiveType) {
|
||||
super( inheritanceMetadata );
|
||||
if ( !primitiveType.isPrimitive() ) {
|
||||
throw new IllegalArgumentException( "Primitive attribute type descriptor can only be used on primitive types" );
|
||||
}
|
||||
// capitalize first letter
|
||||
this.type = primitiveType.getSimpleName().substring( 0, 1 ).toUpperCase( Locale.ROOT ) + primitiveType.getSimpleName().substring( 1 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildReadInterceptionBodyFragment(String fieldName) {
|
||||
if ( inheritanceMetadata.isInherited() && !inheritanceMetadata.isVisible() ) {
|
||||
return String.format(
|
||||
" if (%3$s() != null ) { super.%5$s( %3$s().read%2$s(this, \"%1$s\", super.%4$s())); }",
|
||||
fieldName,
|
||||
type,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
|
||||
inheritanceMetadata.getReaderName(),
|
||||
inheritanceMetadata.getWriterName() );
|
||||
}
|
||||
else {
|
||||
return String.format(
|
||||
" if (%3$s() != null ) { this.%1$s = %3$s().read%2$s(this, \"%1$s\", this.%1$s); }",
|
||||
fieldName,
|
||||
type,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildWriteInterceptionBodyFragment(String fieldName) {
|
||||
if ( inheritanceMetadata.isInherited() && !inheritanceMetadata.isVisible() ) {
|
||||
return String.format(
|
||||
" %2$s localVar = $1;%n" +
|
||||
" if ( %4$s() != null ) { localVar = %4$s().write%3$s(this, \"%1$s\", super.%5$s(), $1); }%n" +
|
||||
" super.%6$s(localVar);",
|
||||
fieldName,
|
||||
type.toLowerCase( Locale.ROOT ),
|
||||
type,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
|
||||
inheritanceMetadata.getReaderName(),
|
||||
inheritanceMetadata.getWriterName() );
|
||||
}
|
||||
else {
|
||||
return String.format(
|
||||
" %2$s localVar = $1;%n" +
|
||||
" if ( %4$s() != null ) { localVar = %4$s().write%3$s(this, \"%1$s\", this.%1$s, $1); }%n" +
|
||||
" this.%1$s = localVar;",
|
||||
fieldName,
|
||||
type.toLowerCase( Locale.ROOT ),
|
||||
type,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
private static class InheritanceMetadata {
|
||||
|
||||
private boolean inherited;
|
||||
private boolean visible;
|
||||
private String readerName;
|
||||
private String writerName;
|
||||
|
||||
public InheritanceMetadata(boolean inherited, boolean visible, String readerName, String writerName) {
|
||||
this.inherited = inherited;
|
||||
this.visible = visible;
|
||||
this.readerName = readerName;
|
||||
this.writerName = writerName;
|
||||
}
|
||||
|
||||
public boolean isInherited() {
|
||||
return inherited;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
public String getReaderName() {
|
||||
return readerName;
|
||||
}
|
||||
|
||||
public String getWriterName() {
|
||||
return writerName;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import javassist.CannotCompileException;
|
||||
import javassist.CtClass;
|
||||
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.engine.spi.CompositeOwner;
|
||||
import org.hibernate.engine.spi.CompositeTracker;
|
||||
import org.hibernate.engine.spi.ManagedComposite;
|
||||
|
||||
/**
|
||||
* enhancer for composite (embeddable) entities
|
||||
*
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
*/
|
||||
public class CompositeEnhancer extends PersistentAttributesEnhancer {
|
||||
|
||||
public CompositeEnhancer(JavassistEnhancementContext context) {
|
||||
super( context );
|
||||
}
|
||||
|
||||
public void enhance(CtClass managedCtClass) {
|
||||
// add the ManagedComposite interface
|
||||
managedCtClass.addInterface( loadCtClassFromClass( ManagedComposite.class ) );
|
||||
|
||||
addInterceptorHandling( managedCtClass );
|
||||
|
||||
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
addInLineDirtyHandling( managedCtClass );
|
||||
}
|
||||
|
||||
super.enhance( managedCtClass );
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
private void addInLineDirtyHandling(CtClass managedCtClass) {
|
||||
managedCtClass.addInterface( loadCtClassFromClass( CompositeTracker.class ) );
|
||||
|
||||
final CtClass compositeCtType = loadCtClassFromClass( CompositeOwnerTracker.class );
|
||||
FieldWriter.addField( managedCtClass, compositeCtType, EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME );
|
||||
|
||||
createCompositeTrackerMethod( managedCtClass );
|
||||
}
|
||||
|
||||
private void createCompositeTrackerMethod(CtClass managedCtClass) {
|
||||
try {
|
||||
MethodWriter.write( managedCtClass, "" +
|
||||
"public void %1$s(String name, %3$s tracker) {%n" +
|
||||
" if (%2$s == null) { %2$s = new %4$s(); }%n" +
|
||||
" %2$s.add(name, tracker);%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
|
||||
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME,
|
||||
CompositeOwner.class.getName(),
|
||||
CompositeOwnerTracker.class.getName() );
|
||||
|
||||
MethodWriter.write( managedCtClass, "" +
|
||||
"public void %1$s(String name) {%n" +
|
||||
" if (%2$s != null) { %2$s.removeOwner(name); }%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER,
|
||||
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME );
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
throw new RuntimeException( "createCompositeTrackerMethod failed", cce );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.LoaderClassPath;
|
||||
|
||||
import javassist.NotFoundException;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.engine.spi.Managed;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
public class EnhancerImpl implements Enhancer {
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class );
|
||||
|
||||
protected final JavassistEnhancementContext enhancementContext;
|
||||
private final ClassPool classPool;
|
||||
|
||||
/**
|
||||
* Constructs the Enhancer, using the given context.
|
||||
*
|
||||
* @param enhancementContext Describes the context in which enhancement will occur so as to give access
|
||||
* to contextual/environmental information.
|
||||
*/
|
||||
public EnhancerImpl(EnhancementContext enhancementContext) {
|
||||
this.enhancementContext = new JavassistEnhancementContext( enhancementContext );
|
||||
this.classPool = buildClassPool( this.enhancementContext );
|
||||
}
|
||||
|
||||
EnhancerImpl(JavassistEnhancementContext enhancementContext) {
|
||||
this.enhancementContext = enhancementContext;
|
||||
this.classPool = buildClassPool( enhancementContext );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the enhancement.
|
||||
*
|
||||
* @param className The name of the class whose bytecode is being enhanced.
|
||||
* @param originalBytes The class's original (pre-enhancement) byte code
|
||||
*
|
||||
* @return The enhanced bytecode. Could be the same as the original bytecode if the original was
|
||||
* already enhanced or we could not enhance it for some reason.
|
||||
*
|
||||
* @throws EnhancementException Indicates a problem performing the enhancement
|
||||
*/
|
||||
@Override
|
||||
public synchronized byte[] enhance(String className, byte[] originalBytes) throws EnhancementException {
|
||||
CtClass managedCtClass = null;
|
||||
try {
|
||||
managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) );
|
||||
return enhance( managedCtClass ) ? getByteCode( managedCtClass ) : null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.unableToBuildEnhancementMetamodel( className );
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
if ( managedCtClass != null ) {
|
||||
managedCtClass.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ClassPool buildClassPool(JavassistEnhancementContext enhancementContext) {
|
||||
ClassPool classPool = new ClassPool( false ) {
|
||||
@Override
|
||||
public ClassLoader getClassLoader() {
|
||||
return enhancementContext.getLoadingClassLoader();
|
||||
}
|
||||
};
|
||||
|
||||
ClassLoader loadingClassLoader = enhancementContext.getLoadingClassLoader();
|
||||
if ( loadingClassLoader != null ) {
|
||||
classPool.appendClassPath( new LoaderClassPath( loadingClassLoader ) );
|
||||
}
|
||||
return classPool;
|
||||
}
|
||||
|
||||
protected CtClass loadCtClassFromClass(Class<?> aClass) {
|
||||
String resourceName = aClass.getName().replace( '.', '/' ) + ".class";
|
||||
InputStream resourceAsStream = aClass.getClassLoader().getResourceAsStream( resourceName );
|
||||
if ( resourceAsStream == null ) {
|
||||
throw new UncheckedIOException( new FileNotFoundException ( "Not found: " + resourceName ) );
|
||||
}
|
||||
try {
|
||||
return classPool.makeClass( resourceAsStream );
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new EnhancementException( "Could not prepare Javassist ClassPool", e );
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
resourceAsStream.close();
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
log.debugf( "An error occurs closing InputStream for class [%s]", aClass.getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean enhance(CtClass managedCtClass) {
|
||||
// can't effectively enhance interfaces
|
||||
if ( managedCtClass.isInterface() ) {
|
||||
log.debugf( "Skipping enhancement of [%s]: it's an interface!", managedCtClass.getName() );
|
||||
return false;
|
||||
}
|
||||
// skip already enhanced classes
|
||||
if ( alreadyEnhanced( managedCtClass ) ) {
|
||||
log.debugf( "Skipping enhancement of [%s]: already enhanced", managedCtClass.getName() );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( enhancementContext.isEntityClass( managedCtClass ) ) {
|
||||
log.debugf( "Enhancing [%s] as Entity", managedCtClass.getName() );
|
||||
new EntityEnhancer( enhancementContext ).enhance( managedCtClass );
|
||||
return true;
|
||||
}
|
||||
else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
log.debugf( "Enhancing [%s] as Composite", managedCtClass.getName() );
|
||||
new CompositeEnhancer( enhancementContext ).enhance( managedCtClass );
|
||||
return true;
|
||||
}
|
||||
else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
|
||||
log.debugf( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() );
|
||||
new MappedSuperclassEnhancer( enhancementContext ).enhance( managedCtClass );
|
||||
return true;
|
||||
}
|
||||
else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
|
||||
log.debugf( "Extended enhancement of [%s]", managedCtClass.getName() );
|
||||
new PersistentAttributesEnhancer( enhancementContext ).extendedEnhancement( managedCtClass );
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
log.debugf( "Skipping enhancement of [%s]: not entity or composite", managedCtClass.getName() );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// See HHH-10977 HHH-11284 HHH-11404 --- check for declaration of Managed interface on the class, not inherited
|
||||
private boolean alreadyEnhanced(CtClass managedCtClass) {
|
||||
try {
|
||||
for ( CtClass declaredInterface : managedCtClass.getInterfaces() ) {
|
||||
if ( PersistentAttributesHelper.isAssignable( declaredInterface, Managed.class.getName() ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch ( NotFoundException e ) {
|
||||
throw new HibernateException( "Unable to transform class: " + e.getMessage() , e );
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getByteCode(CtClass managedCtClass) {
|
||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream( byteStream );
|
||||
try {
|
||||
managedCtClass.toBytecode( out );
|
||||
return byteStream.toByteArray();
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.unableToTransformClass( e.getMessage() );
|
||||
throw new HibernateException( "Unable to transform class: " + e.getMessage() , e );
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
out.close();
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void addInterceptorHandling(CtClass managedCtClass) {
|
||||
// interceptor handling is only needed if class has lazy-loadable attributes
|
||||
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) {
|
||||
return;
|
||||
}
|
||||
log.debugf( "Weaving in PersistentAttributeInterceptable implementation on [%s]", managedCtClass.getName() );
|
||||
|
||||
managedCtClass.addInterface( loadCtClassFromClass( PersistentAttributeInterceptable.class ) );
|
||||
|
||||
FieldWriter.addFieldWithGetterAndSetter(
|
||||
managedCtClass,
|
||||
loadCtClassFromClass( PersistentAttributeInterceptor.class ),
|
||||
EnhancerConstants.INTERCEPTOR_FIELD_NAME,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
|
||||
EnhancerConstants.INTERCEPTOR_SETTER_NAME
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,448 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javassist.CannotCompileException;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
import javassist.Modifier;
|
||||
import javassist.NotFoundException;
|
||||
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.NoopCollectionTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.SimpleCollectionTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker;
|
||||
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||
|
||||
/**
|
||||
* enhancer for regular entities
|
||||
*
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
*/
|
||||
public class EntityEnhancer extends PersistentAttributesEnhancer {
|
||||
|
||||
public EntityEnhancer(JavassistEnhancementContext context) {
|
||||
super( context );
|
||||
}
|
||||
|
||||
// assuming the number of fields is not very high, SimpleFieldTracker implementation it's the fastest
|
||||
private static final String DIRTY_TRACKER_IMPL = SimpleFieldTracker.class.getName();
|
||||
private static final String COLLECTION_TRACKER_IMPL = SimpleCollectionTracker.class.getName();
|
||||
|
||||
public void enhance(CtClass managedCtClass) {
|
||||
// add the ManagedEntity interface
|
||||
managedCtClass.addInterface( loadCtClassFromClass( ManagedEntity.class ) );
|
||||
|
||||
addEntityInstanceHandling( managedCtClass );
|
||||
addEntityEntryHandling( managedCtClass );
|
||||
addLinkedPreviousHandling( managedCtClass );
|
||||
addLinkedNextHandling( managedCtClass );
|
||||
addInterceptorHandling( managedCtClass );
|
||||
|
||||
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
addInLineDirtyHandling( managedCtClass );
|
||||
}
|
||||
|
||||
super.enhance( managedCtClass );
|
||||
}
|
||||
|
||||
private void addEntityInstanceHandling(CtClass managedCtClass) {
|
||||
try {
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public Object %s() { return this; }",
|
||||
EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME
|
||||
);
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
throw new EnhancementException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Could not enhance entity class [%s] to add EntityEntry getter",
|
||||
managedCtClass.getName()
|
||||
),
|
||||
cce
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void addEntityEntryHandling(CtClass managedCtClass) {
|
||||
FieldWriter.addFieldWithGetterAndSetter(
|
||||
managedCtClass, loadCtClassFromClass( EntityEntry.class ),
|
||||
EnhancerConstants.ENTITY_ENTRY_FIELD_NAME,
|
||||
EnhancerConstants.ENTITY_ENTRY_GETTER_NAME,
|
||||
EnhancerConstants.ENTITY_ENTRY_SETTER_NAME
|
||||
);
|
||||
}
|
||||
|
||||
private void addLinkedPreviousHandling(CtClass managedCtClass) {
|
||||
FieldWriter.addFieldWithGetterAndSetter(
|
||||
managedCtClass, loadCtClassFromClass( ManagedEntity.class ),
|
||||
EnhancerConstants.PREVIOUS_FIELD_NAME,
|
||||
EnhancerConstants.PREVIOUS_GETTER_NAME,
|
||||
EnhancerConstants.PREVIOUS_SETTER_NAME
|
||||
);
|
||||
}
|
||||
|
||||
private void addLinkedNextHandling(CtClass managedCtClass) {
|
||||
FieldWriter.addFieldWithGetterAndSetter(
|
||||
managedCtClass, loadCtClassFromClass( ManagedEntity.class ),
|
||||
EnhancerConstants.NEXT_FIELD_NAME,
|
||||
EnhancerConstants.NEXT_GETTER_NAME,
|
||||
EnhancerConstants.NEXT_SETTER_NAME
|
||||
);
|
||||
}
|
||||
|
||||
private void addInLineDirtyHandling(CtClass managedCtClass) {
|
||||
managedCtClass.addInterface( loadCtClassFromClass( SelfDirtinessTracker.class ) );
|
||||
|
||||
FieldWriter.addField(
|
||||
managedCtClass,
|
||||
loadCtClassFromClass( DirtyTracker.class ),
|
||||
EnhancerConstants.TRACKER_FIELD_NAME
|
||||
);
|
||||
|
||||
if ( collectCollectionFields( managedCtClass ).isEmpty() ) {
|
||||
createDirtyTrackerMethodsWithoutCollections( managedCtClass );
|
||||
}
|
||||
else {
|
||||
FieldWriter.addField(
|
||||
managedCtClass,
|
||||
loadCtClassFromClass( CollectionTracker.class ),
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
);
|
||||
createDirtyTrackerMethodsWithCollections( managedCtClass );
|
||||
}
|
||||
}
|
||||
|
||||
private void createDirtyTrackerMethodsWithoutCollections(CtClass managedCtClass) {
|
||||
try {
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public void %1$s(String name) {%n" +
|
||||
" if (%2$s == null) { %2$s = new %3$s(); }%n" +
|
||||
" %2$s.add(name);%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_CHANGER_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME,
|
||||
DIRTY_TRACKER_IMPL
|
||||
);
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public String[] %1$s() {%n" +
|
||||
" return (%2$s == null) ? new String[0] : %2$s.get();%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_GET_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME
|
||||
);
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public boolean %1$s() {%n" +
|
||||
" return (%2$s != null && !%2$s.isEmpty());%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_HAS_CHANGED_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME
|
||||
);
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public void %1$s() {%n" +
|
||||
" if (%2$s != null) { %2$s.clear(); }%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_CLEAR_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME
|
||||
);
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public void %1$s(boolean f) {%n" +
|
||||
" if (%2$s == null) %2$s = new %3$s();%n %2$s.suspend(f);%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_SUSPEND_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME ,
|
||||
DIRTY_TRACKER_IMPL
|
||||
);
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public %s %s() { return %s.INSTANCE; }",
|
||||
CollectionTracker.class.getName(),
|
||||
EnhancerConstants.TRACKER_COLLECTION_GET_NAME,
|
||||
NoopCollectionTracker.class.getName()
|
||||
);
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
throw new RuntimeException( "createDirtyTrackerMethodsWithoutCollections failed", cce );
|
||||
}
|
||||
}
|
||||
|
||||
private void createDirtyTrackerMethodsWithCollections(CtClass managedCtClass) {
|
||||
try {
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public void %1$s(String name) {%n" +
|
||||
" if (%2$s == null) { %2$s = new %3$s(); }%n" +
|
||||
" %2$s.add(name);%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_CHANGER_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME,
|
||||
DIRTY_TRACKER_IMPL
|
||||
);
|
||||
|
||||
createCollectionDirtyCheckMethod( managedCtClass );
|
||||
createCollectionDirtyCheckGetFieldsMethod( managedCtClass );
|
||||
createClearDirtyCollectionMethod( managedCtClass );
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public String[] %1$s() {%n" +
|
||||
" if(%3$s == null) {%n" +
|
||||
" return (%2$s == null) ? new String[0] : %2$s.get();%n" +
|
||||
" } else {%n" +
|
||||
" if (%2$s == null) %2$s = new %5$s();%n" +
|
||||
" %4$s(%2$s);%n" +
|
||||
" return %2$s.get();%n" +
|
||||
" }%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_GET_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME,
|
||||
DIRTY_TRACKER_IMPL
|
||||
);
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public boolean %1$s() {%n" +
|
||||
" return (%2$s != null && !%2$s.isEmpty()) || %3$s();%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_HAS_CHANGED_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME
|
||||
);
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public void %1$s() {%n" +
|
||||
" if (%2$s != null) { %2$s.clear(); }%n" +
|
||||
" %3$s();%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_CLEAR_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME
|
||||
);
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public void %1$s(boolean f) {%n" +
|
||||
" if (%2$s == null) %2$s = new %3$s();%n %2$s.suspend(f);%n" +
|
||||
"}",
|
||||
EnhancerConstants.TRACKER_SUSPEND_NAME,
|
||||
EnhancerConstants.TRACKER_FIELD_NAME ,
|
||||
DIRTY_TRACKER_IMPL
|
||||
);
|
||||
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public %s %s() { return %s; }",
|
||||
CollectionTracker.class.getName(),
|
||||
EnhancerConstants.TRACKER_COLLECTION_GET_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
);
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
throw new RuntimeException( "createDirtyTrackerMethodsWithCollections failed", cce );
|
||||
}
|
||||
}
|
||||
|
||||
private List<CtField> collectCollectionFields(CtClass managedCtClass) {
|
||||
List<CtField> collectionList = new ArrayList<>();
|
||||
|
||||
for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
|
||||
// skip static fields and skip fields added by enhancement
|
||||
if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( enhancementContext.isPersistentField( ctField ) && !enhancementContext.isMappedCollection( ctField ) ) {
|
||||
if ( PersistentAttributesHelper.isAssignable( ctField, Collection.class.getName() ) ||
|
||||
PersistentAttributesHelper.isAssignable( ctField, Map.class.getName() ) ) {
|
||||
collectionList.add( ctField );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HHH-10646 Add fields inherited from @MappedSuperclass
|
||||
// HHH-10981 There is no need to do it for @MappedSuperclass
|
||||
if ( !enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
|
||||
collectionList.addAll( collectInheritCollectionFields( managedCtClass ) );
|
||||
}
|
||||
|
||||
return collectionList;
|
||||
}
|
||||
|
||||
private Collection<CtField> collectInheritCollectionFields(CtClass managedCtClass) {
|
||||
if ( managedCtClass == null || Object.class.getName().equals( managedCtClass.getName() ) ) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
CtClass managedCtSuperclass = managedCtClass.getSuperclass();
|
||||
|
||||
if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass ) ) {
|
||||
return collectInheritCollectionFields( managedCtSuperclass );
|
||||
}
|
||||
List<CtField> collectionList = new ArrayList<>();
|
||||
|
||||
for ( CtField ctField : managedCtSuperclass.getDeclaredFields() ) {
|
||||
if ( !Modifier.isStatic( ctField.getModifiers() ) ) {
|
||||
if ( enhancementContext.isPersistentField( ctField ) && !enhancementContext.isMappedCollection( ctField ) ) {
|
||||
if ( PersistentAttributesHelper.isAssignable( ctField, Collection.class.getName() ) ||
|
||||
PersistentAttributesHelper.isAssignable( ctField, Map.class.getName() ) ) {
|
||||
collectionList.add( ctField );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
collectionList.addAll( collectInheritCollectionFields( managedCtSuperclass ) );
|
||||
return collectionList;
|
||||
}
|
||||
catch ( NotFoundException nfe ) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private void createCollectionDirtyCheckMethod(CtClass managedCtClass) {
|
||||
try {
|
||||
final StringBuilder body = new StringBuilder();
|
||||
|
||||
body.append(
|
||||
String.format(
|
||||
"private boolean %1$s() {%n" +
|
||||
" if (%2$s == null) { return false; }%n%n",
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
)
|
||||
);
|
||||
|
||||
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
|
||||
body.append(
|
||||
String.format(
|
||||
" // collection field [%1$s]%n" +
|
||||
" if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { return true; }%n" +
|
||||
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n%n",
|
||||
ctField.getName(),
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
body.append( " return false;%n}" );
|
||||
|
||||
MethodWriter.write( managedCtClass, body.toString() );
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
throw new RuntimeException( "createCollectionDirtyCheckMethod failed", cce );
|
||||
}
|
||||
}
|
||||
|
||||
private void createCollectionDirtyCheckGetFieldsMethod(CtClass managedCtClass) {
|
||||
try {
|
||||
final StringBuilder body = new StringBuilder();
|
||||
|
||||
body.append(
|
||||
String.format(
|
||||
"private void %1$s(%3$s tracker) {%n" +
|
||||
" if (%2$s == null) { return; }%n%n",
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME,
|
||||
DirtyTracker.class.getName()
|
||||
)
|
||||
);
|
||||
|
||||
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
|
||||
body.append(
|
||||
String.format(
|
||||
" // Collection field [%1$s]%n" +
|
||||
" if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { tracker.add(\"%1$s\"); }%n" +
|
||||
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { tracker.add(\"%1$s\"); }%n%n",
|
||||
ctField.getName(),
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
body.append( "}" );
|
||||
|
||||
MethodWriter.write( managedCtClass, body.toString() );
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
throw new RuntimeException( "createCollectionDirtyCheckGetFieldsMethod failed", cce );
|
||||
}
|
||||
}
|
||||
|
||||
private void createClearDirtyCollectionMethod(CtClass managedCtClass) throws CannotCompileException {
|
||||
try {
|
||||
final StringBuilder body = new StringBuilder();
|
||||
|
||||
body.append(
|
||||
String.format(
|
||||
"private void %1$s() {%n" +
|
||||
" if (%2$s == null) { %2$s = new %3$s(); }%n" +
|
||||
" %4$s lazyInterceptor = null;%n",
|
||||
EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME,
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME,
|
||||
COLLECTION_TRACKER_IMPL,
|
||||
LazyAttributeLoadingInterceptor.class.getName()
|
||||
)
|
||||
);
|
||||
|
||||
if ( PersistentAttributesHelper.isAssignable( managedCtClass, PersistentAttributeInterceptable.class.getName() ) ) {
|
||||
body.append(
|
||||
String.format(
|
||||
" if(%1$s != null && %1$s instanceof %2$s) lazyInterceptor = (%2$s) %1$s;%n%n",
|
||||
EnhancerConstants.INTERCEPTOR_FIELD_NAME,
|
||||
LazyAttributeLoadingInterceptor.class.getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
|
||||
body.append(
|
||||
String.format(
|
||||
" // collection field [%1$s]%n" +
|
||||
" if (lazyInterceptor == null || lazyInterceptor.isAttributeLoaded(\"%1$s\")) {%n" +
|
||||
" if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%n" +
|
||||
" else { %2$s.add(\"%1$s\", %1$s.size()); }%n" +
|
||||
" }%n%n",
|
||||
ctField.getName(),
|
||||
EnhancerConstants.TRACKER_COLLECTION_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
body.append( "}" );
|
||||
|
||||
MethodWriter.write( managedCtClass, body.toString() );
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
throw cce;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import javassist.CannotCompileException;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
import javassist.Modifier;
|
||||
import javassist.bytecode.AnnotationsAttribute;
|
||||
import javassist.bytecode.FieldInfo;
|
||||
import javassist.bytecode.annotation.Annotation;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
*/
|
||||
public class FieldWriter {
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( FieldWriter.class );
|
||||
|
||||
private FieldWriter() { }
|
||||
|
||||
/* --- */
|
||||
|
||||
/**
|
||||
* Add enhancement field
|
||||
*/
|
||||
public static void addField(CtClass target, CtClass type, String field) {
|
||||
addPrivateTransient( target, type, field );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add enhancement field with getter and setter
|
||||
*/
|
||||
public static void addFieldWithGetterAndSetter(CtClass target, CtClass type, String field, String getter, String setter) {
|
||||
addPrivateTransient( target, type, field );
|
||||
MethodWriter.addGetter( target, field, getter );
|
||||
MethodWriter.addSetter( target, field, setter );
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
private static void addPrivateTransient(CtClass target, CtClass type, String name) {
|
||||
addWithModifiers( target, type, name, Modifier.PRIVATE | Modifier.TRANSIENT, Transient.class );
|
||||
log.debugf( "Wrote field into [%s]: @Transient private transient %s %s;", target.getName(), type.getName(), name );
|
||||
}
|
||||
|
||||
private static void addWithModifiers(CtClass target, CtClass type, String name, int modifiers, Class<?> ... annotations ) {
|
||||
try {
|
||||
final CtField f = new CtField( type, name, target );
|
||||
f.setModifiers( f.getModifiers() | modifiers );
|
||||
addAnnotations( f.getFieldInfo(), annotations );
|
||||
target.addField( f );
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
final String msg = String.format( "Could not enhance class [%s] to add field [%s]", target.getName(), name );
|
||||
throw new EnhancementException( msg, cce );
|
||||
}
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
private static void addAnnotations(FieldInfo fieldInfo, Class<?>[] annotations) {
|
||||
AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) fieldInfo.getAttribute( AnnotationsAttribute.visibleTag );
|
||||
if ( annotationsAttribute == null ) {
|
||||
annotationsAttribute = new AnnotationsAttribute( fieldInfo.getConstPool(), AnnotationsAttribute.visibleTag );
|
||||
fieldInfo.addAttribute( annotationsAttribute );
|
||||
}
|
||||
for (Class<?> annotation : annotations) {
|
||||
annotationsAttribute.addAnnotation( new Annotation( annotation.getName(), fieldInfo.getConstPool() ) );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedField;
|
||||
|
||||
public class JavassistEnhancementContext {
|
||||
|
||||
private final EnhancementContext enhancementContext;
|
||||
|
||||
public JavassistEnhancementContext(EnhancementContext enhancementContext) {
|
||||
this.enhancementContext = enhancementContext;
|
||||
}
|
||||
|
||||
public ClassLoader getLoadingClassLoader() {
|
||||
return enhancementContext.getLoadingClassLoader();
|
||||
}
|
||||
|
||||
public boolean isEntityClass(CtClass classDescriptor) {
|
||||
return enhancementContext.isEntityClass( new UnloadedCtClass( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean isCompositeClass(CtClass classDescriptor) {
|
||||
return enhancementContext.isCompositeClass( new UnloadedCtClass( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean isMappedSuperclassClass(CtClass classDescriptor) {
|
||||
return enhancementContext.isMappedSuperclassClass( new UnloadedCtClass( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean doBiDirectionalAssociationManagement(CtField field) {
|
||||
return enhancementContext.doBiDirectionalAssociationManagement( new UnloadedCtField( field ) );
|
||||
}
|
||||
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
|
||||
return enhancementContext.doDirtyCheckingInline( new UnloadedCtClass( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean doExtendedEnhancement(CtClass classDescriptor) {
|
||||
return enhancementContext.doExtendedEnhancement( new UnloadedCtClass( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
|
||||
return enhancementContext.hasLazyLoadableAttributes( new UnloadedCtClass( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean isPersistentField(CtField ctField) {
|
||||
return enhancementContext.isPersistentField( new UnloadedCtField( ctField ) );
|
||||
}
|
||||
|
||||
public CtField[] order(CtField[] persistentFields) {
|
||||
UnloadedField[] unloadedFields = new UnloadedField[persistentFields.length];
|
||||
for ( int i = 0; i < unloadedFields.length; i++ ) {
|
||||
unloadedFields[i] = new UnloadedCtField( persistentFields[i] );
|
||||
}
|
||||
UnloadedField[] ordered = enhancementContext.order( unloadedFields );
|
||||
CtField[] orderedFields = new CtField[persistentFields.length];
|
||||
for ( int i = 0; i < orderedFields.length; i++ ) {
|
||||
orderedFields[i] = ( (UnloadedCtField) ordered[i] ).ctField;
|
||||
}
|
||||
return orderedFields;
|
||||
}
|
||||
|
||||
public boolean isLazyLoadable(CtField field) {
|
||||
return enhancementContext.isLazyLoadable( new UnloadedCtField( field ) );
|
||||
}
|
||||
|
||||
public boolean isMappedCollection(CtField field) {
|
||||
return enhancementContext.isMappedCollection( new UnloadedCtField( field ) );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
import javassist.CtMethod;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.engine.spi.ManagedMappedSuperclass;
|
||||
|
||||
/**
|
||||
* enhancer for mapped superclass
|
||||
*
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
*/
|
||||
public class MappedSuperclassEnhancer extends PersistentAttributesEnhancer {
|
||||
|
||||
public MappedSuperclassEnhancer(JavassistEnhancementContext context) {
|
||||
super( context );
|
||||
}
|
||||
|
||||
public void enhance(CtClass managedCtClass) {
|
||||
// Add the Managed interface
|
||||
managedCtClass.addInterface( loadCtClassFromClass( ManagedMappedSuperclass.class ) );
|
||||
|
||||
super.enhance( managedCtClass );
|
||||
}
|
||||
|
||||
// Generate 'template' methods for each attribute. This will be overriden by the actual entities
|
||||
|
||||
@Override
|
||||
protected CtMethod generateFieldReader(
|
||||
CtClass managedCtClass,
|
||||
CtField persistentField,
|
||||
AttributeTypeDescriptor typeDescriptor) {
|
||||
|
||||
String fieldName = persistentField.getName();
|
||||
String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;
|
||||
|
||||
return MethodWriter.addGetter( managedCtClass, fieldName, readerName );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CtMethod generateFieldWriter(
|
||||
CtClass managedCtClass,
|
||||
CtField persistentField,
|
||||
AttributeTypeDescriptor typeDescriptor) {
|
||||
|
||||
String fieldName = persistentField.getName();
|
||||
String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;
|
||||
|
||||
return MethodWriter.addSetter( managedCtClass, fieldName, writerName );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import javassist.CannotCompileException;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
import javassist.CtMethod;
|
||||
import javassist.CtNewMethod;
|
||||
import javassist.NotFoundException;
|
||||
import javassist.bytecode.ConstPool;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
/**
|
||||
* utility class to compile methods and add the to class files
|
||||
*
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
*/
|
||||
public class MethodWriter {
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( MethodWriter.class );
|
||||
|
||||
private MethodWriter() { }
|
||||
|
||||
/* --- */
|
||||
|
||||
/**
|
||||
* convenience method that builds a method from a format string. {@see String.format} for more details
|
||||
*
|
||||
* @throws CannotCompileException
|
||||
*/
|
||||
public static CtMethod write(CtClass target, String format, Object ... args) throws CannotCompileException {
|
||||
String body = String.format( format, args );
|
||||
// System.out.printf( "writing method into [%s]:%n%s%n", target.getName(), body );
|
||||
log.debugf( "writing method into [%s]:%n%s", target.getName(), body );
|
||||
CtMethod method = CtNewMethod.make( body, target );
|
||||
target.addMethod( method );
|
||||
return method;
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
public static CtMethod addGetter(CtClass target, String field, String name) {
|
||||
CtField actualField = null;
|
||||
try {
|
||||
actualField = target.getField( field );
|
||||
log.debugf( "Writing getter method [%s] into [%s] for field [%s]", name, target.getName(), field );
|
||||
CtMethod method = CtNewMethod.getter( name, target.getField( field ) );
|
||||
target.addMethod( method );
|
||||
return method;
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
try {
|
||||
// Fall back to create a getter from delegation.
|
||||
CtMethod method = CtNewMethod.delegator( CtNewMethod.getter( name, actualField ), target );
|
||||
target.addMethod( method );
|
||||
return method;
|
||||
}
|
||||
catch (CannotCompileException ignored) {
|
||||
String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field );
|
||||
throw new EnhancementException( msg, cce );
|
||||
}
|
||||
}
|
||||
catch (NotFoundException nfe) {
|
||||
String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field );
|
||||
throw new EnhancementException( msg, nfe );
|
||||
}
|
||||
}
|
||||
|
||||
public static CtMethod addSetter(CtClass target, String field, String name) {
|
||||
CtField actualField = null;
|
||||
try {
|
||||
actualField = target.getField( field );
|
||||
log.debugf( "Writing setter method [%s] into [%s] for field [%s]", name, target.getName(), field );
|
||||
CtMethod method = CtNewMethod.setter( name, actualField );
|
||||
target.addMethod( method );
|
||||
return method;
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
try {
|
||||
// Fall back to create a getter from delegation.
|
||||
CtMethod method = CtNewMethod.delegator( CtNewMethod.setter( name, actualField ), target );
|
||||
target.addMethod( method );
|
||||
return method;
|
||||
}
|
||||
catch (CannotCompileException ignored) {
|
||||
String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field );
|
||||
throw new EnhancementException( msg, cce );
|
||||
}
|
||||
}
|
||||
catch (NotFoundException nfe) {
|
||||
String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field );
|
||||
throw new EnhancementException( msg, nfe );
|
||||
}
|
||||
}
|
||||
|
||||
/* --- */
|
||||
|
||||
public static int addMethod(ConstPool cPool, CtMethod method) {
|
||||
return cPool.addMethodrefInfo( cPool.getThisClassInfo(), method.getName(), method.getSignature() );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,751 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
import javassist.CannotCompileException;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
import javassist.CtMethod;
|
||||
import javassist.Modifier;
|
||||
import javassist.NotFoundException;
|
||||
import javassist.bytecode.BadBytecode;
|
||||
import javassist.bytecode.CodeIterator;
|
||||
import javassist.bytecode.ConstPool;
|
||||
import javassist.bytecode.MethodInfo;
|
||||
import javassist.bytecode.Opcode;
|
||||
import javassist.bytecode.stackmap.MapMaker;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.engine.spi.CompositeOwner;
|
||||
import org.hibernate.engine.spi.CompositeTracker;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
/**
|
||||
* enhancer for persistent attributes of any type of entity
|
||||
*
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
*/
|
||||
public class PersistentAttributesEnhancer extends EnhancerImpl {
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributesEnhancer.class );
|
||||
|
||||
public PersistentAttributesEnhancer(JavassistEnhancementContext context) {
|
||||
super( context );
|
||||
}
|
||||
|
||||
public void enhance(CtClass managedCtClass) {
|
||||
final IdentityHashMap<String, PersistentAttributeAccessMethods> attrDescriptorMap = new IdentityHashMap<>();
|
||||
|
||||
for ( CtField persistentField : collectPersistentFields( managedCtClass ) ) {
|
||||
attrDescriptorMap.put(
|
||||
persistentField.getName(), enhancePersistentAttribute(
|
||||
managedCtClass,
|
||||
persistentField
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// find all references to the transformed fields and replace with calls to the added reader/writer methods
|
||||
enhanceAttributesAccess( managedCtClass, attrDescriptorMap );
|
||||
|
||||
// same thing for direct access to fields of other entities
|
||||
if ( this.enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
|
||||
extendedEnhancement( managedCtClass );
|
||||
}
|
||||
}
|
||||
|
||||
private CtField[] collectPersistentFields(CtClass managedCtClass) {
|
||||
List<CtField> persistentFieldList = new ArrayList<>();
|
||||
for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
|
||||
// skip static fields and skip fields added by enhancement and outer reference in inner classes
|
||||
if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( !Modifier.isStatic( ctField.getModifiers() ) && enhancementContext.isPersistentField( ctField ) ) {
|
||||
persistentFieldList.add( ctField );
|
||||
}
|
||||
}
|
||||
// HHH-10646 Add fields inherited from @MappedSuperclass
|
||||
// HHH-10981 There is no need to do it for @MappedSuperclass
|
||||
if ( !enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
|
||||
persistentFieldList.addAll( collectInheritPersistentFields( managedCtClass ) );
|
||||
}
|
||||
|
||||
CtField[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new CtField[0] ) );
|
||||
if ( log.isDebugEnabled() ) {
|
||||
log.debugf(
|
||||
"Persistent fields for entity %s: %s",
|
||||
managedCtClass.getName(),
|
||||
Arrays.toString( orderedFields )
|
||||
);
|
||||
}
|
||||
return orderedFields;
|
||||
}
|
||||
|
||||
private Collection<CtField> collectInheritPersistentFields(CtClass managedCtClass) {
|
||||
if ( managedCtClass == null || Object.class.getName().equals( managedCtClass.getName() ) ) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
CtClass managedCtSuperclass = managedCtClass.getSuperclass();
|
||||
|
||||
if ( enhancementContext.isEntityClass( managedCtSuperclass ) ) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
else if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass ) ) {
|
||||
return collectInheritPersistentFields( managedCtSuperclass );
|
||||
}
|
||||
log.debugf( "Found @MappedSuperclass %s to collectPersistenceFields", managedCtSuperclass.getName() );
|
||||
List<CtField> persistentFieldList = new ArrayList<>();
|
||||
|
||||
for ( CtField ctField : managedCtSuperclass.getDeclaredFields() ) {
|
||||
if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( !Modifier.isStatic( ctField.getModifiers() ) && enhancementContext.isPersistentField( ctField ) ) {
|
||||
persistentFieldList.add( ctField );
|
||||
}
|
||||
}
|
||||
persistentFieldList.addAll( collectInheritPersistentFields( managedCtSuperclass ) );
|
||||
return persistentFieldList;
|
||||
}
|
||||
catch ( NotFoundException nfe ) {
|
||||
log.warnf( "Could not find the superclass of %s", managedCtClass );
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private PersistentAttributeAccessMethods enhancePersistentAttribute(CtClass managedCtClass, CtField persistentField) {
|
||||
try {
|
||||
AttributeTypeDescriptor typeDescriptor = AttributeTypeDescriptor.resolve( managedCtClass, persistentField );
|
||||
return new PersistentAttributeAccessMethods(
|
||||
generateFieldReader( managedCtClass, persistentField, typeDescriptor ),
|
||||
generateFieldWriter( managedCtClass, persistentField, typeDescriptor )
|
||||
);
|
||||
}
|
||||
catch (Exception e) {
|
||||
final String msg = String.format(
|
||||
"Unable to enhance persistent attribute [%s:%s]",
|
||||
managedCtClass.getName(),
|
||||
persistentField.getName()
|
||||
);
|
||||
throw new EnhancementException( msg, e );
|
||||
}
|
||||
}
|
||||
|
||||
protected CtMethod generateFieldReader(
|
||||
CtClass managedCtClass,
|
||||
CtField persistentField,
|
||||
AttributeTypeDescriptor typeDescriptor) {
|
||||
String fieldName = persistentField.getName();
|
||||
String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;
|
||||
String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;
|
||||
CtMethod tmpSuperReader = null;
|
||||
CtMethod tmpSuperWriter = null;
|
||||
CtMethod reader = null;
|
||||
|
||||
try {
|
||||
boolean declared = persistentField.getDeclaringClass().equals( managedCtClass );
|
||||
String declaredReadFragment = "this." + fieldName;
|
||||
String superReadFragment = "super." + readerName + "()";
|
||||
|
||||
if ( !declared ) {
|
||||
// create a temporary getter on the supper entity to be able to compile our code
|
||||
try {
|
||||
persistentField.getDeclaringClass().getDeclaredMethod( readerName );
|
||||
persistentField.getDeclaringClass().getDeclaredMethod( writerName );
|
||||
}
|
||||
catch ( NotFoundException nfe ) {
|
||||
tmpSuperReader = MethodWriter.addGetter( persistentField.getDeclaringClass(), persistentField.getName(), readerName );
|
||||
tmpSuperWriter = MethodWriter.addSetter( persistentField.getDeclaringClass(), persistentField.getName(), writerName );
|
||||
}
|
||||
}
|
||||
|
||||
// read attempts only have to deal lazy-loading support, not dirty checking;
|
||||
// so if the field is not enabled as lazy-loadable return a plain simple getter as the reader
|
||||
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( persistentField ) ) {
|
||||
reader = MethodWriter.write(
|
||||
managedCtClass, "public %s %s() { return %s;%n}",
|
||||
persistentField.getType().getName(),
|
||||
readerName,
|
||||
declared ? declaredReadFragment : superReadFragment
|
||||
);
|
||||
}
|
||||
else {
|
||||
reader = MethodWriter.write(
|
||||
managedCtClass, "public %s %s() {%n%s%n return %s;%n}",
|
||||
persistentField.getType().getName(),
|
||||
readerName,
|
||||
typeDescriptor.buildReadInterceptionBodyFragment( fieldName ),
|
||||
declared ? declaredReadFragment : superReadFragment
|
||||
);
|
||||
}
|
||||
if ( tmpSuperReader != null ) {
|
||||
persistentField.getDeclaringClass().removeMethod( tmpSuperReader );
|
||||
}
|
||||
if ( tmpSuperWriter != null ) {
|
||||
persistentField.getDeclaringClass().removeMethod( tmpSuperWriter );
|
||||
}
|
||||
return reader;
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
final String msg = String.format(
|
||||
"Could not enhance entity class [%s] to add field reader method [%s]",
|
||||
managedCtClass.getName(),
|
||||
readerName
|
||||
);
|
||||
throw new EnhancementException( msg, cce );
|
||||
}
|
||||
catch (NotFoundException nfe) {
|
||||
final String msg = String.format(
|
||||
"Could not enhance entity class [%s] to add field reader method [%s]",
|
||||
managedCtClass.getName(),
|
||||
readerName
|
||||
);
|
||||
throw new EnhancementException( msg, nfe );
|
||||
}
|
||||
}
|
||||
|
||||
protected CtMethod generateFieldWriter(
|
||||
CtClass managedCtClass,
|
||||
CtField persistentField,
|
||||
AttributeTypeDescriptor typeDescriptor) {
|
||||
String fieldName = persistentField.getName();
|
||||
String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;
|
||||
String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;
|
||||
CtMethod tmpSuperReader = null;
|
||||
CtMethod tmpSuperWriter = null;
|
||||
CtMethod writer;
|
||||
|
||||
try {
|
||||
boolean declared = persistentField.getDeclaringClass().equals( managedCtClass );
|
||||
String declaredWriteFragment = "this." + fieldName + "=" + fieldName + ";";
|
||||
String superWriteFragment = "super." + writerName + "(" + fieldName + ");";
|
||||
|
||||
if ( !declared ) {
|
||||
// create a temporary setter on the supper entity to be able to compile our code
|
||||
try {
|
||||
persistentField.getDeclaringClass().getDeclaredMethod( readerName );
|
||||
persistentField.getDeclaringClass().getDeclaredMethod( writerName );
|
||||
}
|
||||
catch ( NotFoundException nfe ) {
|
||||
tmpSuperReader = MethodWriter.addGetter( persistentField.getDeclaringClass(), persistentField.getName(), readerName );
|
||||
tmpSuperWriter = MethodWriter.addSetter( persistentField.getDeclaringClass(), persistentField.getName(), writerName );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( persistentField ) ) {
|
||||
writer = MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public void %s(%s %s) {%n %s%n}",
|
||||
writerName,
|
||||
persistentField.getType().getName(),
|
||||
fieldName,
|
||||
declared ? declaredWriteFragment : superWriteFragment
|
||||
);
|
||||
}
|
||||
else {
|
||||
writer = MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public void %s(%s %s) {%n%s%n}",
|
||||
writerName,
|
||||
persistentField.getType().getName(),
|
||||
fieldName,
|
||||
typeDescriptor.buildWriteInterceptionBodyFragment( fieldName )
|
||||
);
|
||||
}
|
||||
|
||||
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
writer.insertBefore(
|
||||
String.format(
|
||||
" if (%1$s != null) { %1$s.callOwner(\"\"); }%n",
|
||||
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( enhancementContext, persistentField ) );
|
||||
}
|
||||
|
||||
handleCompositeField( managedCtClass, persistentField, writer );
|
||||
}
|
||||
|
||||
if ( enhancementContext.doBiDirectionalAssociationManagement( persistentField ) ) {
|
||||
handleBiDirectionalAssociation( managedCtClass, persistentField, writer );
|
||||
}
|
||||
|
||||
if ( tmpSuperReader != null ) {
|
||||
persistentField.getDeclaringClass().removeMethod( tmpSuperReader );
|
||||
}
|
||||
if ( tmpSuperWriter != null ) {
|
||||
persistentField.getDeclaringClass().removeMethod( tmpSuperWriter );
|
||||
}
|
||||
return writer;
|
||||
}
|
||||
catch (CannotCompileException cce) {
|
||||
final String msg = String.format(
|
||||
"Could not enhance entity class [%s] to add field writer method [%s]",
|
||||
managedCtClass.getName(),
|
||||
writerName
|
||||
);
|
||||
throw new EnhancementException( msg, cce );
|
||||
}
|
||||
catch (NotFoundException nfe) {
|
||||
final String msg = String.format(
|
||||
"Could not enhance entity class [%s] to add field writer method [%s]",
|
||||
managedCtClass.getName(),
|
||||
writerName
|
||||
);
|
||||
throw new EnhancementException( msg, nfe );
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBiDirectionalAssociation(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
|
||||
throws NotFoundException, CannotCompileException {
|
||||
if ( !PersistentAttributesHelper.isPossibleBiDirectionalAssociation( persistentField ) ) {
|
||||
return;
|
||||
}
|
||||
final CtClass targetEntity = PersistentAttributesHelper.getTargetEntityClass( managedCtClass, persistentField );
|
||||
if ( targetEntity == null ) {
|
||||
log.infof(
|
||||
"Bi-directional association not managed for field [%s#%s]: Could not find target type",
|
||||
managedCtClass.getName(),
|
||||
persistentField.getName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
final String mappedBy = PersistentAttributesHelper.getMappedBy( persistentField, targetEntity, enhancementContext );
|
||||
if ( mappedBy == null || mappedBy.isEmpty() ) {
|
||||
log.infof(
|
||||
"Bi-directional association not managed for field [%s#%s]: Could not find target field in [%s]",
|
||||
managedCtClass.getName(),
|
||||
persistentField.getName(),
|
||||
targetEntity.getName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// create a temporary getter and setter on the target entity to be able to compile our code
|
||||
final String mappedByGetterName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + mappedBy;
|
||||
final String mappedBySetterName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy;
|
||||
CtMethod getter;
|
||||
CtMethod setter;
|
||||
boolean tmpTargetMethods = false;
|
||||
try {
|
||||
getter = targetEntity.getDeclaredMethod( mappedByGetterName );
|
||||
setter = targetEntity.getDeclaredMethod( mappedByGetterName );
|
||||
}
|
||||
catch ( NotFoundException nfe ) {
|
||||
getter = MethodWriter.addGetter( targetEntity, mappedBy, mappedByGetterName );
|
||||
setter = MethodWriter.addSetter( targetEntity, mappedBy, mappedBySetterName );
|
||||
tmpTargetMethods = true;
|
||||
}
|
||||
|
||||
// code fragments to check loaded state. We don't want to trigger lazy loading in association management code
|
||||
String currentAssociationLoaded = String.format(
|
||||
"%s.isPropertyInitialized(this.%s, \"%s\")",
|
||||
Hibernate.class.getName(),
|
||||
persistentField.getName(),
|
||||
mappedBy
|
||||
);
|
||||
String targetElementLoaded = String.format(
|
||||
"%s.isPropertyInitialized(target, \"%s\")",
|
||||
Hibernate.class.getName(),
|
||||
mappedBy
|
||||
);
|
||||
String newAssociationLoaded = String.format(
|
||||
"%s.isPropertyInitialized($1, \"%s\")",
|
||||
Hibernate.class.getName(),
|
||||
mappedBy
|
||||
);
|
||||
|
||||
if ( PersistentAttributesHelper.hasAnnotation( persistentField, OneToOne.class ) ) {
|
||||
// only unset when $1 != null to avoid recursion
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
" if (this.%1$s != null && %2$s && $1 != null) { this.%1$s.%3$s(null); }%n",
|
||||
persistentField.getName(),
|
||||
currentAssociationLoaded,
|
||||
mappedBySetterName
|
||||
)
|
||||
);
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
" if ($1 != null && %s && $1.%s() != this) { $1.%s(this); }%n",
|
||||
newAssociationLoaded,
|
||||
mappedByGetterName,
|
||||
mappedBySetterName
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( PersistentAttributesHelper.hasAnnotation( persistentField, OneToMany.class ) ) {
|
||||
boolean isMap = PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() );
|
||||
String toArrayMethod = isMap ? "values().toArray()" : "toArray()";
|
||||
|
||||
// only remove elements not in the new collection or else we would loose those elements
|
||||
// don't use iterator to avoid ConcurrentModException
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
" if (this.%3$s != null && %1$s) {%n" +
|
||||
" Object[] array = this.%3$s.%2$s;%n" +
|
||||
" for (int i = 0; i < array.length; i++) {%n" +
|
||||
" %4$s target = (%4$s) array[i];%n" +
|
||||
" if ($1 == null || !$1.contains(target)) { target.%5$s(null); }%n" +
|
||||
" }%n" +
|
||||
" }%n",
|
||||
currentAssociationLoaded,
|
||||
toArrayMethod,
|
||||
persistentField.getName(),
|
||||
targetEntity.getName(),
|
||||
mappedBySetterName
|
||||
)
|
||||
);
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
" if ($1 != null && %1$s) {%n" +
|
||||
" Object[] array = $1.%2$s;%n" +
|
||||
" for (int i = 0; i < array.length; i++) {%n" +
|
||||
" %4$s target = (%4$s) array[i];%n" +
|
||||
" if (%3$s && target.%5$s() != this) { target.%6$s(this); }%n" +
|
||||
" }%n" +
|
||||
" }%n",
|
||||
newAssociationLoaded,
|
||||
toArrayMethod,
|
||||
targetElementLoaded,
|
||||
targetEntity.getName(),
|
||||
mappedByGetterName,
|
||||
mappedBySetterName
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( PersistentAttributesHelper.hasAnnotation( persistentField, ManyToOne.class ) ) {
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
" if (this.%2$s != null && %1$s && this.%2$s.%3$s() != null) { this.%2$s.%3$s().remove(this); }%n",
|
||||
currentAssociationLoaded,
|
||||
persistentField.getName(),
|
||||
mappedByGetterName
|
||||
)
|
||||
);
|
||||
// check .contains(this) to avoid double inserts (but preventing duplicates)
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
" if ($1 != null && %s) {%n" +
|
||||
" java.util.Collection c = $1.%s();%n" +
|
||||
" if (c != null && !c.contains(this)) { c.add(this); }%n" +
|
||||
" }%n",
|
||||
newAssociationLoaded,
|
||||
mappedByGetterName
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( PersistentAttributesHelper.hasAnnotation( persistentField, ManyToMany.class ) ) {
|
||||
if ( PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() ) ||
|
||||
PersistentAttributesHelper.isAssignable( targetEntity.getField( mappedBy ).getType(), Map.class.getName() ) ) {
|
||||
log.infof(
|
||||
"Bi-directional association not managed for field [%s#%s]: @ManyToMany in java.util.Map attribute not supported ",
|
||||
managedCtClass.getName(),
|
||||
persistentField.getName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
" if (this.%2$s != null && %1$s) {%n" +
|
||||
" Object[] array = this.%2$s.toArray();%n" +
|
||||
" for (int i = 0; i < array.length; i++) {%n" +
|
||||
" %3$s target = (%3$s) array[i];%n" +
|
||||
" if ($1 == null || !$1.contains(target)) { target.%4$s().remove(this); }%n" +
|
||||
" }%n" +
|
||||
" }%n",
|
||||
currentAssociationLoaded,
|
||||
persistentField.getName(),
|
||||
targetEntity.getName(),
|
||||
mappedByGetterName
|
||||
)
|
||||
);
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
" if ($1 != null && %s) {%n" +
|
||||
" Object[] array = $1.toArray();%n" +
|
||||
" for (int i = 0; i < array.length; i++) {%n" +
|
||||
" %s target = (%s) array[i];%n" +
|
||||
" if (%s) {%n" +
|
||||
" java.util.Collection c = target.%s();%n" +
|
||||
" if (c != this && c != null) { c.add(this); }%n" +
|
||||
" }%n" +
|
||||
" }%n" +
|
||||
" }%n",
|
||||
newAssociationLoaded,
|
||||
targetEntity.getName(),
|
||||
targetEntity.getName(),
|
||||
targetElementLoaded,
|
||||
mappedByGetterName
|
||||
)
|
||||
);
|
||||
}
|
||||
// implementation note: association management @OneToMany and @ManyToMay works for add() operations but for remove() a snapshot of the collection is needed so we know what associations to break.
|
||||
// another approach that could force that behavior would be to return Collections.unmodifiableCollection() ...
|
||||
|
||||
if ( tmpTargetMethods ) {
|
||||
targetEntity.removeMethod( getter );
|
||||
targetEntity.removeMethod( setter );
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCompositeField(CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter)
|
||||
throws NotFoundException, CannotCompileException {
|
||||
if ( !enhancementContext.isCompositeClass( persistentField.getType() ) ||
|
||||
!PersistentAttributesHelper.hasAnnotation( persistentField, Embedded.class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure to add the CompositeOwner interface
|
||||
addCompositeOwnerInterface( managedCtClass );
|
||||
|
||||
String readFragment = persistentField.visibleFrom( managedCtClass ) ? persistentField.getName() : "super." + EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName() + "()";
|
||||
|
||||
// cleanup previous owner
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
"if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%4$s\"); }%n",
|
||||
readFragment,
|
||||
CompositeTracker.class.getName(),
|
||||
EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER,
|
||||
persistentField.getName()
|
||||
)
|
||||
);
|
||||
|
||||
// trigger track changes
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
"if (%1$s != null) { ((%2$s) %1$s).%4$s(\"%6$s\", (%3$s) this); }%n" +
|
||||
"%5$s(\"%6$s\");",
|
||||
readFragment,
|
||||
CompositeTracker.class.getName(),
|
||||
CompositeOwner.class.getName(),
|
||||
EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
|
||||
EnhancerConstants.TRACKER_CHANGER_NAME,
|
||||
persistentField.getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void addCompositeOwnerInterface(CtClass managedCtClass) throws NotFoundException, CannotCompileException {
|
||||
CtClass compositeOwnerCtClass = managedCtClass.getClassPool().get( CompositeOwner.class.getName() );
|
||||
|
||||
// HHH-10540 only add the interface once
|
||||
for ( CtClass i : managedCtClass.getInterfaces() ) {
|
||||
if ( i.subclassOf( compositeOwnerCtClass ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
managedCtClass.addInterface( compositeOwnerCtClass );
|
||||
|
||||
if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
// if a composite have a embedded field we need to implement the TRACKER_CHANGER_NAME method as well
|
||||
MethodWriter.write(
|
||||
managedCtClass,
|
||||
"public void %1$s(String name) {%n" +
|
||||
" if (%2$s != null) { %2$s.callOwner(\".\" + name); }%n}",
|
||||
EnhancerConstants.TRACKER_CHANGER_NAME,
|
||||
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected void enhanceAttributesAccess(
|
||||
CtClass managedCtClass,
|
||||
IdentityHashMap<String, PersistentAttributeAccessMethods> attributeDescriptorMap) {
|
||||
final ConstPool constPool = managedCtClass.getClassFile().getConstPool();
|
||||
final ClassPool classPool = managedCtClass.getClassPool();
|
||||
|
||||
for ( Object oMethod : managedCtClass.getClassFile().getMethods() ) {
|
||||
final MethodInfo methodInfo = (MethodInfo) oMethod;
|
||||
final String methodName = methodInfo.getName();
|
||||
|
||||
// skip methods added by enhancement and abstract methods (methods without any code)
|
||||
if ( methodName.startsWith( "$$_hibernate_" ) || methodInfo.getCodeAttribute() == null ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
final CodeIterator itr = methodInfo.getCodeAttribute().iterator();
|
||||
while ( itr.hasNext() ) {
|
||||
final int index = itr.next();
|
||||
final int op = itr.byteAt( index );
|
||||
if ( op != Opcode.PUTFIELD && op != Opcode.GETFIELD ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// only transform access to fields of the entity being enhanced
|
||||
if ( !managedCtClass.getName().equals( constPool.getFieldrefClassName( itr.u16bitAt( index + 1 ) ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String fieldName = constPool.getFieldrefName( itr.u16bitAt( index + 1 ) );
|
||||
final PersistentAttributeAccessMethods attributeMethods = attributeDescriptorMap.get( fieldName );
|
||||
|
||||
// its not a field we have enhanced for interception, so skip it
|
||||
if ( attributeMethods == null ) {
|
||||
continue;
|
||||
}
|
||||
//System.out.printf( "Transforming access to field [%s] from method [%s]%n", fieldName, methodName );
|
||||
log.debugf( "Transforming access to field [%s] from method [%s]", fieldName, methodName );
|
||||
|
||||
if ( op == Opcode.GETFIELD ) {
|
||||
final int methodIndex = MethodWriter.addMethod( constPool, attributeMethods.getReader() );
|
||||
itr.writeByte( Opcode.INVOKEVIRTUAL, index );
|
||||
itr.write16bit( methodIndex, index + 1 );
|
||||
}
|
||||
else {
|
||||
final int methodIndex = MethodWriter.addMethod( constPool, attributeMethods.getWriter() );
|
||||
itr.writeByte( Opcode.INVOKEVIRTUAL, index );
|
||||
itr.write16bit( methodIndex, index + 1 );
|
||||
}
|
||||
}
|
||||
methodInfo.getCodeAttribute().setAttribute( MapMaker.make( classPool, methodInfo ) );
|
||||
}
|
||||
catch (BadBytecode bb) {
|
||||
final String msg = String.format(
|
||||
"Unable to perform field access transformation in method [%s]",
|
||||
methodName
|
||||
);
|
||||
throw new EnhancementException( msg, bb );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PersistentAttributeAccessMethods {
|
||||
private final CtMethod reader;
|
||||
private final CtMethod writer;
|
||||
|
||||
private PersistentAttributeAccessMethods(CtMethod reader, CtMethod writer) {
|
||||
this.reader = reader;
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
private CtMethod getReader() {
|
||||
return reader;
|
||||
}
|
||||
|
||||
private CtMethod getWriter() {
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
|
||||
// --- //
|
||||
|
||||
/**
|
||||
* Replace access to fields of entities (for example, entity.field) with a call to the enhanced getter / setter
|
||||
* (in this example, entity.$$_hibernate_read_field()). It's assumed that the target entity is enhanced as well.
|
||||
*
|
||||
* @param aCtClass Class to enhance (not an entity class).
|
||||
*/
|
||||
public void extendedEnhancement(CtClass aCtClass) {
|
||||
final ConstPool constPool = aCtClass.getClassFile().getConstPool();
|
||||
final ClassPool classPool = aCtClass.getClassPool();
|
||||
|
||||
for ( Object oMethod : aCtClass.getClassFile().getMethods() ) {
|
||||
final MethodInfo methodInfo = (MethodInfo) oMethod;
|
||||
final String methodName = methodInfo.getName();
|
||||
|
||||
// skip methods added by enhancement and abstract methods (methods without any code)
|
||||
if ( methodName.startsWith( "$$_hibernate_" ) || methodInfo.getCodeAttribute() == null ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
final CodeIterator itr = methodInfo.getCodeAttribute().iterator();
|
||||
while ( itr.hasNext() ) {
|
||||
int index = itr.next();
|
||||
int op = itr.byteAt( index );
|
||||
if ( op != Opcode.PUTFIELD && op != Opcode.GETFIELD ) {
|
||||
continue;
|
||||
}
|
||||
String fieldName = constPool.getFieldrefName( itr.u16bitAt( index + 1 ) );
|
||||
String fieldClassName = constPool.getClassInfo( constPool.getFieldrefClass( itr.u16bitAt( index + 1 ) ) );
|
||||
CtClass targetCtClass = classPool.getCtClass( fieldClassName );
|
||||
|
||||
if ( !enhancementContext.isEntityClass( targetCtClass ) && !enhancementContext.isCompositeClass( targetCtClass ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( targetCtClass == aCtClass
|
||||
|| !enhancementContext.isPersistentField( targetCtClass.getField( fieldName ) )
|
||||
|| PersistentAttributesHelper.hasAnnotation( targetCtClass, fieldName, Id.class )
|
||||
|| "this$0".equals( fieldName ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
log.debugf(
|
||||
"Extended enhancement: Transforming access to field [%s.%s] from method [%s#%s]",
|
||||
fieldClassName,
|
||||
fieldName,
|
||||
aCtClass.getName(),
|
||||
methodName
|
||||
);
|
||||
|
||||
if ( op == Opcode.GETFIELD ) {
|
||||
int fieldReaderMethodIndex = constPool.addMethodrefInfo(
|
||||
constPool.addClassInfo( fieldClassName ),
|
||||
EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName,
|
||||
"()" + constPool.getFieldrefType( itr.u16bitAt( index + 1 ) )
|
||||
);
|
||||
itr.writeByte( Opcode.INVOKEVIRTUAL, index );
|
||||
itr.write16bit( fieldReaderMethodIndex, index + 1 );
|
||||
}
|
||||
else {
|
||||
int fieldWriterMethodIndex = constPool.addMethodrefInfo(
|
||||
constPool.addClassInfo( fieldClassName ),
|
||||
EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName,
|
||||
"(" + constPool.getFieldrefType( itr.u16bitAt( index + 1 ) ) + ")V"
|
||||
);
|
||||
itr.writeByte( Opcode.INVOKEVIRTUAL, index );
|
||||
itr.write16bit( fieldWriterMethodIndex, index + 1 );
|
||||
}
|
||||
|
||||
}
|
||||
methodInfo.getCodeAttribute().setAttribute( MapMaker.make( classPool, methodInfo ) );
|
||||
}
|
||||
catch (BadBytecode bb) {
|
||||
final String msg = String.format(
|
||||
"Unable to perform extended enhancement in method [%s]",
|
||||
methodName
|
||||
);
|
||||
throw new EnhancementException( msg, bb );
|
||||
}
|
||||
catch (NotFoundException nfe) {
|
||||
final String msg = String.format(
|
||||
"Unable to perform extended enhancement in method [%s]",
|
||||
methodName
|
||||
);
|
||||
throw new EnhancementException( msg, nfe );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import java.beans.Introspector;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
import javassist.CtMember;
|
||||
import javassist.CtMethod;
|
||||
import javassist.NotFoundException;
|
||||
import javassist.bytecode.BadBytecode;
|
||||
import javassist.bytecode.SignatureAttribute;
|
||||
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
/**
|
||||
* util methods to fetch attribute metadata. consistent for both field and property access types.
|
||||
*
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
* @see org.hibernate.internal.util.ReflectHelper
|
||||
*/
|
||||
public class PersistentAttributesHelper {
|
||||
|
||||
private PersistentAttributesHelper() {
|
||||
}
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributesHelper.class );
|
||||
|
||||
public static boolean hasAnnotation(CtField attribute, Class<? extends Annotation> annotation) {
|
||||
return getAnnotation( attribute, annotation ) != null;
|
||||
}
|
||||
|
||||
public static boolean hasAnnotation(CtClass ctClass, String attributeName, Class<? extends Annotation> annotation) {
|
||||
return getAnnotation( ctClass, attributeName, annotation ) != null;
|
||||
}
|
||||
|
||||
public static <T extends Annotation> T getAnnotation(CtField attribute, Class<T> annotation) {
|
||||
return getAnnotation( attribute.getDeclaringClass(), attribute.getName(), annotation );
|
||||
}
|
||||
|
||||
public static <T extends Annotation> T getAnnotation(CtClass ctClass, String attributeName, Class<T> annotation) {
|
||||
AccessType classAccessType = getAccessTypeOrNull( ctClass );
|
||||
CtField field = findFieldOrNull( ctClass, attributeName );
|
||||
CtMethod getter = findGetterOrNull( ctClass, attributeName );
|
||||
|
||||
if ( classAccessType == AccessType.FIELD || ( field != null && getAccessTypeOrNull( field ) == AccessType.FIELD ) ) {
|
||||
return field == null ? null : getAnnotationOrNull( field, annotation );
|
||||
}
|
||||
if ( classAccessType == AccessType.PROPERTY || ( getter != null && getAccessTypeOrNull( getter ) == AccessType.PROPERTY ) ) {
|
||||
return getter == null ? null : getAnnotationOrNull( getter, annotation );
|
||||
}
|
||||
|
||||
T found = ( getter == null ? null : getAnnotationOrNull( getter, annotation ) );
|
||||
if ( found == null && field != null ) {
|
||||
return getAnnotationOrNull( field, annotation );
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private static <T extends Annotation> T getAnnotationOrNull(CtMember ctMember, Class<T> annotation) {
|
||||
try {
|
||||
if ( ctMember.hasAnnotation( annotation ) ) {
|
||||
return annotation.cast( ctMember.getAnnotation( annotation ) );
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException cnfe) {
|
||||
// should never happen
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static AccessType getAccessTypeOrNull(CtMember ctMember) {
|
||||
Access access = getAnnotationOrNull( ctMember, Access.class );
|
||||
return access == null ? null : access.value();
|
||||
}
|
||||
|
||||
private static AccessType getAccessTypeOrNull(CtClass ctClass) {
|
||||
try {
|
||||
if ( ctClass.hasAnnotation( Access.class ) ) {
|
||||
return ( (Access) ctClass.getAnnotation( Access.class ) ).value();
|
||||
}
|
||||
else {
|
||||
CtClass extendsClass = ctClass.getSuperclass();
|
||||
return extendsClass == null ? null : getAccessTypeOrNull( extendsClass );
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/**
|
||||
* duplicated here to take CtClass instead of Class
|
||||
* @see org.hibernate.internal.util.ReflectHelper#locateField
|
||||
*/
|
||||
private static CtField findFieldOrNull(CtClass ctClass, String propertyName) {
|
||||
if ( ctClass == null ) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return ctClass.getField( propertyName );
|
||||
}
|
||||
catch ( NotFoundException nsfe ) {
|
||||
try {
|
||||
return findFieldOrNull( ctClass.getSuperclass(), propertyName );
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* duplicated here to take CtClass instead of Class
|
||||
* @see org.hibernate.internal.util.ReflectHelper#findGetterMethod
|
||||
*/
|
||||
private static CtMethod findGetterOrNull(CtClass ctClass, String propertyName) {
|
||||
if ( ctClass == null ) {
|
||||
return null;
|
||||
}
|
||||
CtMethod method = getterOrNull( ctClass, propertyName );
|
||||
if ( method != null ) {
|
||||
return method;
|
||||
}
|
||||
try {
|
||||
// check if extends
|
||||
method = findGetterOrNull( ctClass.getSuperclass(), propertyName );
|
||||
if ( method != null ) {
|
||||
return method;
|
||||
}
|
||||
// check if implements
|
||||
for ( CtClass interfaceCtClass : ctClass.getInterfaces() ) {
|
||||
method = getterOrNull( interfaceCtClass, propertyName );
|
||||
if ( method != null ) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NotFoundException nfe) {
|
||||
// give up
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static CtMethod getterOrNull(CtClass containerClass, String propertyName) {
|
||||
for ( CtMethod method : containerClass.getDeclaredMethods() ) {
|
||||
try {
|
||||
// if the method has parameters, skip it
|
||||
if ( method.isEmpty() || method.getParameterTypes().length != 0 ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String methodName = method.getName();
|
||||
|
||||
// try "get"
|
||||
if ( methodName.startsWith( "get" ) ) {
|
||||
String testStdMethod = Introspector.decapitalize( methodName.substring( 3 ) );
|
||||
String testOldMethod = methodName.substring( 3 );
|
||||
if ( testStdMethod.equals( propertyName ) || testOldMethod.equals( propertyName ) ) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
// if not "get", then try "is"
|
||||
if ( methodName.startsWith( "is" ) ) {
|
||||
String testStdMethod = Introspector.decapitalize( methodName.substring( 2 ) );
|
||||
String testOldMethod = methodName.substring( 2 );
|
||||
if ( testStdMethod.equals( propertyName ) || testOldMethod.equals( propertyName ) ) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public static boolean isPossibleBiDirectionalAssociation(CtField persistentField) {
|
||||
return PersistentAttributesHelper.hasAnnotation( persistentField, OneToOne.class ) ||
|
||||
PersistentAttributesHelper.hasAnnotation( persistentField, OneToMany.class ) ||
|
||||
PersistentAttributesHelper.hasAnnotation( persistentField, ManyToOne.class ) ||
|
||||
PersistentAttributesHelper.hasAnnotation( persistentField, ManyToMany.class );
|
||||
}
|
||||
|
||||
public static String getMappedBy(CtField persistentField, CtClass targetEntity, JavassistEnhancementContext context) throws NotFoundException {
|
||||
final String local = getMappedByFromAnnotation( persistentField );
|
||||
if ( local == null || local.isEmpty() ) {
|
||||
return getMappedByFromTargetEntity( persistentField, targetEntity, context );
|
||||
}
|
||||
else {
|
||||
// HHH-13446 - mappedBy from annotation may not be a valid bi-directional association, verify by calling isValidMappedBy()
|
||||
return isValidMappedBy( persistentField, targetEntity, local, context ) ? local : "";
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidMappedBy(CtField persistentField, CtClass targetEntity, String mappedBy, JavassistEnhancementContext context) {
|
||||
try {
|
||||
CtField f = targetEntity.getField( mappedBy );
|
||||
return context.isPersistentField( f ) && isAssignable( persistentField.getDeclaringClass(), inferFieldTypeName( f ) );
|
||||
}
|
||||
catch ( NotFoundException e ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getMappedByFromAnnotation(CtField persistentField) {
|
||||
|
||||
OneToOne oto = PersistentAttributesHelper.getAnnotation( persistentField, OneToOne.class );
|
||||
if ( oto != null ) {
|
||||
return oto.mappedBy();
|
||||
}
|
||||
|
||||
OneToMany otm = PersistentAttributesHelper.getAnnotation( persistentField, OneToMany.class );
|
||||
if ( otm != null ) {
|
||||
return otm.mappedBy();
|
||||
}
|
||||
|
||||
// For @ManyToOne associations, mappedBy must come from the @OneToMany side of the association
|
||||
|
||||
ManyToMany mtm = PersistentAttributesHelper.getAnnotation( persistentField, ManyToMany.class );
|
||||
return mtm == null ? "" : mtm.mappedBy();
|
||||
}
|
||||
|
||||
private static String getMappedByFromTargetEntity(
|
||||
CtField persistentField,
|
||||
CtClass targetEntity,
|
||||
JavassistEnhancementContext context) throws NotFoundException {
|
||||
// get mappedBy value by searching in the fields of the target entity class
|
||||
for ( CtField f : targetEntity.getDeclaredFields() ) {
|
||||
if ( context.isPersistentField( f )
|
||||
&& getMappedByFromAnnotation( f ).equals( persistentField.getName() )
|
||||
&& isAssignable( persistentField.getDeclaringClass(), inferFieldTypeName( f ) ) ) {
|
||||
log.debugf(
|
||||
"mappedBy association for field [%s#%s] is [%s#%s]",
|
||||
persistentField.getDeclaringClass().getName(),
|
||||
persistentField.getName(),
|
||||
targetEntity.getName(),
|
||||
f.getName()
|
||||
);
|
||||
return f.getName();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static CtClass getTargetEntityClass(CtClass managedCtClass, CtField persistentField) throws NotFoundException {
|
||||
// get targetEntity defined in the annotation
|
||||
try {
|
||||
OneToOne oto = PersistentAttributesHelper.getAnnotation( persistentField, OneToOne.class );
|
||||
OneToMany otm = PersistentAttributesHelper.getAnnotation( persistentField, OneToMany.class );
|
||||
ManyToOne mto = PersistentAttributesHelper.getAnnotation( persistentField, ManyToOne.class );
|
||||
ManyToMany mtm = PersistentAttributesHelper.getAnnotation( persistentField, ManyToMany.class );
|
||||
|
||||
Class<?> targetClass = null;
|
||||
if ( oto != null ) {
|
||||
targetClass = oto.targetEntity();
|
||||
}
|
||||
if ( otm != null ) {
|
||||
targetClass = otm.targetEntity();
|
||||
}
|
||||
if ( mto != null ) {
|
||||
targetClass = mto.targetEntity();
|
||||
}
|
||||
if ( mtm != null ) {
|
||||
targetClass = mtm.targetEntity();
|
||||
}
|
||||
|
||||
if ( targetClass != null && targetClass != void.class ) {
|
||||
return managedCtClass.getClassPool().get( targetClass.getName() );
|
||||
}
|
||||
}
|
||||
catch (NotFoundException ignore) {
|
||||
}
|
||||
|
||||
// infer targetEntity from generic type signature
|
||||
String inferredTypeName = inferTypeName( managedCtClass, persistentField.getName() );
|
||||
return inferredTypeName == null ? null : managedCtClass.getClassPool().get( inferredTypeName );
|
||||
}
|
||||
|
||||
/**
|
||||
* Consistent with hasAnnotation()
|
||||
*/
|
||||
private static String inferTypeName(CtClass ctClass, String attributeName ) {
|
||||
AccessType classAccessType = getAccessTypeOrNull( ctClass );
|
||||
CtField field = findFieldOrNull( ctClass, attributeName );
|
||||
CtMethod getter = findGetterOrNull( ctClass, attributeName );
|
||||
|
||||
if ( classAccessType == AccessType.FIELD || ( field != null && getAccessTypeOrNull( field ) == AccessType.FIELD ) ) {
|
||||
return field == null ? null : inferFieldTypeName( field );
|
||||
}
|
||||
if ( classAccessType == AccessType.PROPERTY || ( getter != null && getAccessTypeOrNull( getter ) == AccessType.PROPERTY ) ) {
|
||||
return getter == null ? null : inferMethodTypeName( getter );
|
||||
}
|
||||
|
||||
String found = ( getter == null ? null : inferMethodTypeName( getter ) );
|
||||
if ( found == null && field != null ) {
|
||||
return inferFieldTypeName( field );
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private static String inferFieldTypeName(CtField field) {
|
||||
try {
|
||||
if ( field.getFieldInfo2().getAttribute( SignatureAttribute.tag ) == null ) {
|
||||
return field.getType().getName();
|
||||
}
|
||||
return inferGenericTypeName(
|
||||
field.getType(),
|
||||
SignatureAttribute.toTypeSignature( field.getGenericSignature() )
|
||||
);
|
||||
}
|
||||
catch (BadBytecode ignore) {
|
||||
return null;
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String inferMethodTypeName(CtMethod method) {
|
||||
try {
|
||||
if ( method.getMethodInfo2().getAttribute( SignatureAttribute.tag ) == null ) {
|
||||
return method.getReturnType().getName();
|
||||
}
|
||||
return inferGenericTypeName(
|
||||
method.getReturnType(),
|
||||
SignatureAttribute.toMethodSignature( method.getGenericSignature() ).getReturnType()
|
||||
);
|
||||
}
|
||||
catch (BadBytecode ignore) {
|
||||
return null;
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String inferGenericTypeName(CtClass ctClass, SignatureAttribute.Type genericSignature) {
|
||||
// infer targetEntity from generic type signature
|
||||
if ( isAssignable( ctClass, Collection.class.getName() ) ) {
|
||||
return ( (SignatureAttribute.ClassType) genericSignature ).getTypeArguments()[0].getType().jvmTypeName();
|
||||
}
|
||||
if ( isAssignable( ctClass, Map.class.getName() ) ) {
|
||||
return ( (SignatureAttribute.ClassType) genericSignature ).getTypeArguments()[1].getType().jvmTypeName();
|
||||
}
|
||||
return ctClass.getName();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public static boolean isAssignable(CtClass thisCtClass, String targetClassName) {
|
||||
if ( thisCtClass == null ) {
|
||||
return false;
|
||||
}
|
||||
if ( thisCtClass.getName().equals( targetClassName ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// check if extends
|
||||
if ( isAssignable( thisCtClass.getSuperclass(), targetClassName ) ) {
|
||||
return true;
|
||||
}
|
||||
// check if implements
|
||||
for ( CtClass interfaceCtClass : thisCtClass.getInterfaces() ) {
|
||||
if ( isAssignable( interfaceCtClass, targetClassName ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
// keep going
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isAssignable(CtField thisCtField, String targetClassName) {
|
||||
try {
|
||||
return isAssignable( thisCtField.getType(), targetClassName );
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
// keep going
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import javassist.CtClass;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
|
||||
|
||||
public class UnloadedCtClass implements UnloadedClass {
|
||||
|
||||
private final CtClass ctClass;
|
||||
|
||||
public UnloadedCtClass(CtClass ctClass) {
|
||||
this.ctClass = ctClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
|
||||
return ctClass.hasAnnotation( annotationType );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return ctClass.getName();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import javassist.CtField;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedField;
|
||||
|
||||
public class UnloadedCtField implements UnloadedField {
|
||||
|
||||
final CtField ctField;
|
||||
|
||||
public UnloadedCtField(CtField ctField) {
|
||||
this.ctField = ctField;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
|
||||
return ctField.hasAnnotation( annotationType );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.ctField.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* package containing bytecode enhancement code (internals)
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.javassist;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.hibernate.PropertyAccessException;
|
||||
import org.hibernate.bytecode.spi.ReflectionOptimizer;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
|
||||
/**
|
||||
* The {@link org.hibernate.bytecode.spi.ReflectionOptimizer.AccessOptimizer} implementation for Javassist
|
||||
* which simply acts as an adapter to the {@link BulkAccessor} class.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class AccessOptimizerAdapter implements ReflectionOptimizer.AccessOptimizer, Serializable {
|
||||
|
||||
private static final String PROPERTY_GET_EXCEPTION = String.format(
|
||||
"exception getting property value with Javassist (set %s to false for more info)",
|
||||
AvailableSettings.USE_REFLECTION_OPTIMIZER
|
||||
);
|
||||
|
||||
private static final String PROPERTY_SET_EXCEPTION = String.format(
|
||||
"exception setting property value with Javassist (set %s to false for more info)",
|
||||
AvailableSettings.USE_REFLECTION_OPTIMIZER
|
||||
);
|
||||
|
||||
private final BulkAccessor bulkAccessor;
|
||||
private final Class mappedClass;
|
||||
|
||||
/**
|
||||
* Constructs an AccessOptimizerAdapter
|
||||
*
|
||||
* @param bulkAccessor The bulk accessor to use
|
||||
* @param mappedClass The mapped class
|
||||
*/
|
||||
public AccessOptimizerAdapter(BulkAccessor bulkAccessor, Class mappedClass) {
|
||||
this.bulkAccessor = bulkAccessor;
|
||||
this.mappedClass = mappedClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getPropertyNames() {
|
||||
return bulkAccessor.getGetters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getPropertyValues(Object object) {
|
||||
try {
|
||||
return bulkAccessor.getPropertyValues( object );
|
||||
}
|
||||
catch ( Throwable t ) {
|
||||
throw new PropertyAccessException(
|
||||
t,
|
||||
PROPERTY_GET_EXCEPTION,
|
||||
false,
|
||||
mappedClass,
|
||||
getterName( t, bulkAccessor )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPropertyValues(Object object, Object[] values) {
|
||||
try {
|
||||
bulkAccessor.setPropertyValues( object, values );
|
||||
}
|
||||
catch ( Throwable t ) {
|
||||
throw new PropertyAccessException(
|
||||
t,
|
||||
PROPERTY_SET_EXCEPTION,
|
||||
true,
|
||||
mappedClass,
|
||||
setterName( t, bulkAccessor )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static String setterName(Throwable t, BulkAccessor accessor) {
|
||||
if (t instanceof BulkAccessorException ) {
|
||||
return accessor.getSetters()[ ( (BulkAccessorException) t ).getIndex() ];
|
||||
}
|
||||
else {
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getterName(Throwable t, BulkAccessor accessor) {
|
||||
if (t instanceof BulkAccessorException ) {
|
||||
return accessor.getGetters()[ ( (BulkAccessorException) t ).getIndex() ];
|
||||
}
|
||||
else {
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.javassist;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* A JavaBean bulk accessor, which provides methods capable of getting/setting multiple properties
|
||||
* of a JavaBean at once.
|
||||
*
|
||||
* IMPORTANT NOTE!!! Apparently the order of the methods here is important as I think BulkAccessorFactory
|
||||
* makes use of that information in terms of accessing the constructor. Anyway, when I tried to re-arrange them
|
||||
* the BulkAccessor creation failed and tests started to fail.
|
||||
*
|
||||
* @author Muga Nishizawa
|
||||
* @author Shigeru Chiba
|
||||
*/
|
||||
public abstract class BulkAccessor implements Serializable {
|
||||
protected Class target;
|
||||
protected String[] getters, setters;
|
||||
protected Class[] types;
|
||||
|
||||
/**
|
||||
* Protected access constructor so the generated class has access to it.
|
||||
*/
|
||||
protected BulkAccessor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the values of properties of a given bean.
|
||||
*
|
||||
* @param bean JavaBean.
|
||||
* @param values the obtained values are stored in this array.
|
||||
*/
|
||||
public abstract void getPropertyValues(Object bean, Object[] values);
|
||||
|
||||
/**
|
||||
* Sets properties of a given bean to specified values.
|
||||
*
|
||||
* @param bean JavaBean.
|
||||
* @param values the values assinged to properties.
|
||||
*/
|
||||
public abstract void setPropertyValues(Object bean, Object[] values);
|
||||
|
||||
/**
|
||||
* Returns the values of properties of a given bean.
|
||||
*
|
||||
* @param bean JavaBean.
|
||||
*
|
||||
* @return The property values
|
||||
*/
|
||||
public Object[] getPropertyValues(Object bean) {
|
||||
final Object[] values = new Object[getters.length];
|
||||
getPropertyValues( bean, values );
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the types of properties.
|
||||
*
|
||||
* @return The property types
|
||||
*/
|
||||
public Class[] getPropertyTypes() {
|
||||
return types.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setter names of properties.
|
||||
*
|
||||
* @return The getter names
|
||||
*/
|
||||
public String[] getGetters() {
|
||||
return getters.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the getter names of the properties.
|
||||
*
|
||||
* @return The setter names
|
||||
*/
|
||||
public String[] getSetters() {
|
||||
return setters.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>BulkAccessor</code>.
|
||||
* The created instance provides methods for setting/getting
|
||||
* specified properties at once.
|
||||
*
|
||||
* @param beanClass the class of the JavaBeans accessed
|
||||
* through the created object.
|
||||
* @param getters the names of setter methods for specified properties.
|
||||
* @param setters the names of getter methods for specified properties.
|
||||
* @param types the types of specified properties.
|
||||
*
|
||||
* @return The created BulkAccessor
|
||||
*/
|
||||
public static BulkAccessor create(
|
||||
Class beanClass,
|
||||
String[] getters,
|
||||
String[] setters,
|
||||
Class[] types) {
|
||||
return new BulkAccessorFactory( beanClass, getters, setters, types ).create();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.javassist;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
|
||||
/**
|
||||
* An exception thrown while generating a bulk accessor.
|
||||
*
|
||||
* @author Muga Nishizawa
|
||||
* @author modified by Shigeru Chiba
|
||||
*/
|
||||
public class BulkAccessorException extends HibernateException {
|
||||
private final int index;
|
||||
|
||||
/**
|
||||
* Constructs an exception.
|
||||
*
|
||||
* @param message Message explaining the exception condition
|
||||
*/
|
||||
public BulkAccessorException(String message) {
|
||||
this( message, -1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an exception.
|
||||
*
|
||||
* @param message Message explaining the exception condition
|
||||
* @param index The index of the property that causes an exception.
|
||||
*/
|
||||
public BulkAccessorException(String message, int index) {
|
||||
this( message, index, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an exception.
|
||||
*
|
||||
* @param message Message explaining the exception condition
|
||||
* @param cause The underlying cause
|
||||
*/
|
||||
public BulkAccessorException(String message, Exception cause) {
|
||||
this( message, -1, cause );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an exception.
|
||||
*
|
||||
* @param message Message explaining the exception condition
|
||||
* @param index The index of the property that causes an exception.
|
||||
* @param cause The underlying cause
|
||||
*/
|
||||
public BulkAccessorException(String message, int index, Exception cause) {
|
||||
super( message + " : @" + index, cause );
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the property that causes this exception.
|
||||
*
|
||||
* @return -1 if the index is not specified.
|
||||
*/
|
||||
public int getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,423 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.javassist;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
import javassist.CannotCompileException;
|
||||
import javassist.bytecode.AccessFlag;
|
||||
import javassist.bytecode.Bytecode;
|
||||
import javassist.bytecode.ClassFile;
|
||||
import javassist.bytecode.CodeAttribute;
|
||||
import javassist.bytecode.ConstPool;
|
||||
import javassist.bytecode.MethodInfo;
|
||||
import javassist.bytecode.Opcode;
|
||||
import javassist.bytecode.StackMapTable;
|
||||
import javassist.util.proxy.FactoryHelper;
|
||||
import javassist.util.proxy.RuntimeSupport;
|
||||
|
||||
/**
|
||||
* A factory of bulk accessors.
|
||||
*
|
||||
* @author Muga Nishizawa
|
||||
* @author modified by Shigeru Chiba
|
||||
*/
|
||||
class BulkAccessorFactory {
|
||||
private static final String PACKAGE_NAME_PREFIX = "org.javassist.tmp.";
|
||||
private static final String BULKACESSOR_CLASS_NAME = BulkAccessor.class.getName();
|
||||
private static final String OBJECT_CLASS_NAME = Object.class.getName();
|
||||
private static final String GENERATED_GETTER_NAME = "getPropertyValues";
|
||||
private static final String GENERATED_SETTER_NAME = "setPropertyValues";
|
||||
private static final String GET_SETTER_DESC = "(Ljava/lang/Object;[Ljava/lang/Object;)V";
|
||||
private static final String THROWABLE_CLASS_NAME = Throwable.class.getName();
|
||||
private static final String BULKEXCEPTION_CLASS_NAME = BulkAccessorException.class.getName();
|
||||
|
||||
private static int counter;
|
||||
|
||||
private Class targetBean;
|
||||
private String[] getterNames;
|
||||
private String[] setterNames;
|
||||
private Class[] types;
|
||||
public String writeDirectory;
|
||||
|
||||
BulkAccessorFactory(
|
||||
Class target,
|
||||
String[] getterNames,
|
||||
String[] setterNames,
|
||||
Class[] types) {
|
||||
this.targetBean = target;
|
||||
this.getterNames = getterNames;
|
||||
this.setterNames = setterNames;
|
||||
this.types = types;
|
||||
this.writeDirectory = null;
|
||||
}
|
||||
|
||||
BulkAccessor create() {
|
||||
final Method[] getters = new Method[getterNames.length];
|
||||
final Method[] setters = new Method[setterNames.length];
|
||||
findAccessors( targetBean, getterNames, setterNames, types, getters, setters );
|
||||
|
||||
final Class beanClass;
|
||||
try {
|
||||
final ClassFile classfile = make( getters, setters );
|
||||
final ClassLoader loader = this.getClassLoader();
|
||||
if ( writeDirectory != null ) {
|
||||
FactoryHelper.writeFile( classfile, writeDirectory );
|
||||
}
|
||||
|
||||
beanClass = FactoryHelper.toClass( classfile, null, loader, getDomain() );
|
||||
return (BulkAccessor) this.newInstance( beanClass );
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
throw new BulkAccessorException( e.getMessage(), e );
|
||||
}
|
||||
}
|
||||
|
||||
private ProtectionDomain getDomain() {
|
||||
final Class cl;
|
||||
if ( this.targetBean != null ) {
|
||||
cl = this.targetBean;
|
||||
}
|
||||
else {
|
||||
cl = this.getClass();
|
||||
}
|
||||
return cl.getProtectionDomain();
|
||||
}
|
||||
|
||||
private ClassFile make(Method[] getters, Method[] setters) throws CannotCompileException {
|
||||
String className = targetBean.getName();
|
||||
// set the name of bulk accessor.
|
||||
className = className + "_$$_bulkaccess_" + counter++;
|
||||
if ( className.startsWith( "java." ) ) {
|
||||
className = PACKAGE_NAME_PREFIX + className;
|
||||
}
|
||||
|
||||
final ClassFile classfile = new ClassFile( false, className, BULKACESSOR_CLASS_NAME );
|
||||
classfile.setAccessFlags( AccessFlag.PUBLIC );
|
||||
addDefaultConstructor( classfile );
|
||||
addGetter( classfile, getters );
|
||||
addSetter( classfile, setters );
|
||||
return classfile;
|
||||
}
|
||||
|
||||
private ClassLoader getClassLoader() {
|
||||
if ( targetBean != null && targetBean.getName().equals( OBJECT_CLASS_NAME ) ) {
|
||||
return targetBean.getClassLoader();
|
||||
}
|
||||
else {
|
||||
return getClass().getClassLoader();
|
||||
}
|
||||
}
|
||||
|
||||
private Object newInstance(Class type) throws Exception {
|
||||
final BulkAccessor instance = (BulkAccessor) type.newInstance();
|
||||
instance.target = targetBean;
|
||||
final int len = getterNames.length;
|
||||
instance.getters = new String[len];
|
||||
instance.setters = new String[len];
|
||||
instance.types = new Class[len];
|
||||
for ( int i = 0; i < len; i++ ) {
|
||||
instance.getters[i] = getterNames[i];
|
||||
instance.setters[i] = setterNames[i];
|
||||
instance.types[i] = types[i];
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares a constructor that takes no parameter.
|
||||
*
|
||||
* @param classfile The class descriptor
|
||||
*
|
||||
* @throws CannotCompileException Indicates trouble with the underlying Javassist calls
|
||||
*/
|
||||
private void addDefaultConstructor(ClassFile classfile) throws CannotCompileException {
|
||||
final ConstPool constPool = classfile.getConstPool();
|
||||
final String constructorSignature = "()V";
|
||||
final MethodInfo constructorMethodInfo = new MethodInfo( constPool, MethodInfo.nameInit, constructorSignature );
|
||||
|
||||
final Bytecode code = new Bytecode( constPool, 0, 1 );
|
||||
// aload_0
|
||||
code.addAload( 0 );
|
||||
// invokespecial
|
||||
code.addInvokespecial( BulkAccessor.class.getName(), MethodInfo.nameInit, constructorSignature );
|
||||
// return
|
||||
code.addOpcode( Opcode.RETURN );
|
||||
|
||||
constructorMethodInfo.setCodeAttribute( code.toCodeAttribute() );
|
||||
constructorMethodInfo.setAccessFlags( AccessFlag.PUBLIC );
|
||||
classfile.addMethod( constructorMethodInfo );
|
||||
}
|
||||
|
||||
private void addGetter(ClassFile classfile, final Method[] getters) throws CannotCompileException {
|
||||
final ConstPool constPool = classfile.getConstPool();
|
||||
final int targetBeanConstPoolIndex = constPool.addClassInfo( this.targetBean.getName() );
|
||||
final String desc = GET_SETTER_DESC;
|
||||
final MethodInfo getterMethodInfo = new MethodInfo( constPool, GENERATED_GETTER_NAME, desc );
|
||||
|
||||
final Bytecode code = new Bytecode( constPool, 6, 4 );
|
||||
/* | this | bean | args | raw bean | */
|
||||
if ( getters.length >= 0 ) {
|
||||
// aload_1 // load bean
|
||||
code.addAload( 1 );
|
||||
// checkcast // cast bean
|
||||
code.addCheckcast( this.targetBean.getName() );
|
||||
// astore_3 // store bean
|
||||
code.addAstore( 3 );
|
||||
for ( int i = 0; i < getters.length; ++i ) {
|
||||
if ( getters[i] != null ) {
|
||||
final Method getter = getters[i];
|
||||
// aload_2 // args
|
||||
code.addAload( 2 );
|
||||
// iconst_i // continue to aastore
|
||||
// growing stack is 1
|
||||
code.addIconst( i );
|
||||
final Class returnType = getter.getReturnType();
|
||||
int typeIndex = -1;
|
||||
if ( returnType.isPrimitive() ) {
|
||||
typeIndex = FactoryHelper.typeIndex( returnType );
|
||||
// new
|
||||
code.addNew( FactoryHelper.wrapperTypes[typeIndex] );
|
||||
// dup
|
||||
code.addOpcode( Opcode.DUP );
|
||||
}
|
||||
|
||||
// aload_3 // load the raw bean
|
||||
code.addAload( 3 );
|
||||
final String getterSignature = RuntimeSupport.makeDescriptor( getter );
|
||||
final String getterName = getter.getName();
|
||||
if ( this.targetBean.isInterface() ) {
|
||||
// invokeinterface
|
||||
code.addInvokeinterface( targetBeanConstPoolIndex, getterName, getterSignature, 1 );
|
||||
}
|
||||
else {
|
||||
// invokevirtual
|
||||
code.addInvokevirtual( targetBeanConstPoolIndex, getterName, getterSignature );
|
||||
}
|
||||
|
||||
if ( typeIndex >= 0 ) {
|
||||
// is a primitive type
|
||||
// invokespecial
|
||||
code.addInvokespecial(
|
||||
FactoryHelper.wrapperTypes[typeIndex],
|
||||
MethodInfo.nameInit,
|
||||
FactoryHelper.wrapperDesc[typeIndex]
|
||||
);
|
||||
}
|
||||
|
||||
// aastore // args
|
||||
code.add( Opcode.AASTORE );
|
||||
code.growStack( -3 );
|
||||
}
|
||||
}
|
||||
}
|
||||
// return
|
||||
code.addOpcode( Opcode.RETURN );
|
||||
|
||||
getterMethodInfo.setCodeAttribute( code.toCodeAttribute() );
|
||||
getterMethodInfo.setAccessFlags( AccessFlag.PUBLIC );
|
||||
classfile.addMethod( getterMethodInfo );
|
||||
}
|
||||
|
||||
private void addSetter(ClassFile classfile, final Method[] setters) throws CannotCompileException {
|
||||
final ConstPool constPool = classfile.getConstPool();
|
||||
final int targetTypeConstPoolIndex = constPool.addClassInfo( this.targetBean.getName() );
|
||||
final String desc = GET_SETTER_DESC;
|
||||
final MethodInfo setterMethodInfo = new MethodInfo( constPool, GENERATED_SETTER_NAME, desc );
|
||||
|
||||
final Bytecode code = new Bytecode( constPool, 4, 6 );
|
||||
StackMapTable stackmap = null;
|
||||
/* | this | bean | args | i | raw bean | exception | */
|
||||
if ( setters.length > 0 ) {
|
||||
// required to exception table
|
||||
int start;
|
||||
int end;
|
||||
// iconst_0 // i
|
||||
code.addIconst( 0 );
|
||||
// istore_3 // store i
|
||||
code.addIstore( 3 );
|
||||
// aload_1 // load the bean
|
||||
code.addAload( 1 );
|
||||
// checkcast // cast the bean into a raw bean
|
||||
code.addCheckcast( this.targetBean.getName() );
|
||||
// astore 4 // store the raw bean
|
||||
code.addAstore( 4 );
|
||||
/* current stack len = 0 */
|
||||
// start region to handling exception (BulkAccessorException)
|
||||
start = code.currentPc();
|
||||
int lastIndex = 0;
|
||||
for ( int i = 0; i < setters.length; ++i ) {
|
||||
if ( setters[i] != null ) {
|
||||
final int diff = i - lastIndex;
|
||||
if ( diff > 0 ) {
|
||||
// iinc 3, 1
|
||||
code.addOpcode( Opcode.IINC );
|
||||
code.add( 3 );
|
||||
code.add( diff );
|
||||
lastIndex = i;
|
||||
}
|
||||
}
|
||||
/* current stack len = 0 */
|
||||
// aload 4 // load the raw bean
|
||||
code.addAload( 4 );
|
||||
// aload_2 // load the args
|
||||
code.addAload( 2 );
|
||||
// iconst_i
|
||||
code.addIconst( i );
|
||||
// aaload
|
||||
code.addOpcode( Opcode.AALOAD );
|
||||
// checkcast
|
||||
final Class[] setterParamTypes = setters[i].getParameterTypes();
|
||||
final Class setterParamType = setterParamTypes[0];
|
||||
if ( setterParamType.isPrimitive() ) {
|
||||
// checkcast (case of primitive type)
|
||||
// invokevirtual (case of primitive type)
|
||||
this.addUnwrapper( code, setterParamType );
|
||||
}
|
||||
else {
|
||||
// checkcast (case of reference type)
|
||||
code.addCheckcast( setterParamType.getName() );
|
||||
}
|
||||
/* current stack len = 2 */
|
||||
final String rawSetterMethodDesc = RuntimeSupport.makeDescriptor( setters[i] );
|
||||
if ( !this.targetBean.isInterface() ) {
|
||||
// invokevirtual
|
||||
code.addInvokevirtual( targetTypeConstPoolIndex, setters[i].getName(), rawSetterMethodDesc );
|
||||
}
|
||||
else {
|
||||
// invokeinterface
|
||||
final Class[] params = setters[i].getParameterTypes();
|
||||
int size;
|
||||
if ( params[0].equals( Double.TYPE ) || params[0].equals( Long.TYPE ) ) {
|
||||
size = 3;
|
||||
}
|
||||
else {
|
||||
size = 2;
|
||||
}
|
||||
|
||||
code.addInvokeinterface( targetTypeConstPoolIndex, setters[i].getName(), rawSetterMethodDesc, size );
|
||||
}
|
||||
}
|
||||
|
||||
// end region to handling exception (BulkAccessorException)
|
||||
end = code.currentPc();
|
||||
// return
|
||||
code.addOpcode( Opcode.RETURN );
|
||||
/* current stack len = 0 */
|
||||
// register in exception table
|
||||
final int throwableTypeIndex = constPool.addClassInfo( THROWABLE_CLASS_NAME );
|
||||
final int handlerPc = code.currentPc();
|
||||
code.addExceptionHandler( start, end, handlerPc, throwableTypeIndex );
|
||||
// astore 5 // store exception
|
||||
code.addAstore( 5 );
|
||||
// new // BulkAccessorException
|
||||
code.addNew( BULKEXCEPTION_CLASS_NAME );
|
||||
// dup
|
||||
code.addOpcode( Opcode.DUP );
|
||||
// aload 5 // load exception
|
||||
code.addAload( 5 );
|
||||
// iload_3 // i
|
||||
code.addIload( 3 );
|
||||
// invokespecial // BulkAccessorException.<init>
|
||||
final String consDesc = "(Ljava/lang/Throwable;I)V";
|
||||
code.addInvokespecial( BULKEXCEPTION_CLASS_NAME, MethodInfo.nameInit, consDesc );
|
||||
// athrow
|
||||
code.addOpcode( Opcode.ATHROW );
|
||||
final StackMapTable.Writer writer = new StackMapTable.Writer(32);
|
||||
final int[] localTags = {
|
||||
StackMapTable.OBJECT,
|
||||
StackMapTable.OBJECT,
|
||||
StackMapTable.OBJECT,
|
||||
StackMapTable.INTEGER
|
||||
};
|
||||
final int[] localData = {
|
||||
constPool.getThisClassInfo(),
|
||||
constPool.addClassInfo( "java/lang/Object" ),
|
||||
constPool.addClassInfo( "[Ljava/lang/Object;" ),
|
||||
0
|
||||
};
|
||||
final int[] stackTags = {
|
||||
StackMapTable.OBJECT
|
||||
};
|
||||
final int[] stackData = {
|
||||
throwableTypeIndex
|
||||
};
|
||||
writer.fullFrame( handlerPc, localTags, localData, stackTags, stackData );
|
||||
stackmap = writer.toStackMapTable( constPool );
|
||||
}
|
||||
else {
|
||||
// return
|
||||
code.addOpcode( Opcode.RETURN );
|
||||
}
|
||||
final CodeAttribute ca = code.toCodeAttribute();
|
||||
if ( stackmap != null ) {
|
||||
ca.setAttribute( stackmap );
|
||||
}
|
||||
setterMethodInfo.setCodeAttribute( ca );
|
||||
setterMethodInfo.setAccessFlags( AccessFlag.PUBLIC );
|
||||
classfile.addMethod( setterMethodInfo );
|
||||
}
|
||||
|
||||
private void addUnwrapper(Bytecode code, Class type) {
|
||||
final int index = FactoryHelper.typeIndex( type );
|
||||
final String wrapperType = FactoryHelper.wrapperTypes[index];
|
||||
// checkcast
|
||||
code.addCheckcast( wrapperType );
|
||||
// invokevirtual
|
||||
code.addInvokevirtual( wrapperType, FactoryHelper.unwarpMethods[index], FactoryHelper.unwrapDesc[index] );
|
||||
}
|
||||
|
||||
private static void findAccessors(
|
||||
Class clazz,
|
||||
String[] getterNames,
|
||||
String[] setterNames,
|
||||
Class[] types,
|
||||
Method[] getters,
|
||||
Method[] setters) {
|
||||
final int length = types.length;
|
||||
if ( setterNames.length != length || getterNames.length != length ) {
|
||||
throw new BulkAccessorException( "bad number of accessors" );
|
||||
}
|
||||
|
||||
final Class[] getParam = new Class[0];
|
||||
final Class[] setParam = new Class[1];
|
||||
for ( int i = 0; i < length; i++ ) {
|
||||
if ( getterNames[i] != null ) {
|
||||
final Method getter = findAccessor( clazz, getterNames[i], getParam, i );
|
||||
if ( getter.getReturnType() != types[i] ) {
|
||||
throw new BulkAccessorException( "wrong return type: " + getterNames[i], i );
|
||||
}
|
||||
|
||||
getters[i] = getter;
|
||||
}
|
||||
|
||||
if ( setterNames[i] != null ) {
|
||||
setParam[0] = types[i];
|
||||
setters[i] = findAccessor( clazz, setterNames[i], setParam, i );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Method findAccessor(Class clazz, String name, Class[] params, int index)
|
||||
throws BulkAccessorException {
|
||||
try {
|
||||
final Method method = clazz.getDeclaredMethod( name, params );
|
||||
if ( Modifier.isPrivate( method.getModifiers() ) ) {
|
||||
throw new BulkAccessorException( "private property", index );
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
catch ( NoSuchMethodException e ) {
|
||||
throw new BulkAccessorException( "cannot find an accessor", index );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.javassist;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.hibernate.bytecode.enhance.internal.javassist.EnhancerImpl;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.bytecode.spi.BytecodeProvider;
|
||||
import org.hibernate.bytecode.spi.ProxyFactoryFactory;
|
||||
import org.hibernate.bytecode.spi.ReflectionOptimizer;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Bytecode provider implementation for Javassist.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class BytecodeProviderImpl implements BytecodeProvider {
|
||||
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
|
||||
CoreMessageLogger.class,
|
||||
BytecodeProviderImpl.class.getName()
|
||||
);
|
||||
|
||||
@Override
|
||||
public ProxyFactoryFactory getProxyFactoryFactory() {
|
||||
return new ProxyFactoryFactoryImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReflectionOptimizer getReflectionOptimizer(
|
||||
Class clazz,
|
||||
String[] getterNames,
|
||||
String[] setterNames,
|
||||
Class[] types) {
|
||||
FastClass fastClass;
|
||||
BulkAccessor bulkAccessor;
|
||||
try {
|
||||
fastClass = FastClass.create( clazz );
|
||||
bulkAccessor = BulkAccessor.create( clazz, getterNames, setterNames, types );
|
||||
if ( !clazz.isInterface() && !Modifier.isAbstract( clazz.getModifiers() ) ) {
|
||||
if ( fastClass == null ) {
|
||||
bulkAccessor = null;
|
||||
}
|
||||
else {
|
||||
//test out the optimizer:
|
||||
final Object instance = fastClass.newInstance();
|
||||
bulkAccessor.setPropertyValues( instance, bulkAccessor.getPropertyValues( instance ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( Throwable t ) {
|
||||
fastClass = null;
|
||||
bulkAccessor = null;
|
||||
if ( LOG.isDebugEnabled() ) {
|
||||
int index = 0;
|
||||
if (t instanceof BulkAccessorException) {
|
||||
index = ( (BulkAccessorException) t ).getIndex();
|
||||
}
|
||||
if ( index >= 0 ) {
|
||||
LOG.debugf(
|
||||
"Reflection optimizer disabled for %s [%s: %s (property %s)]",
|
||||
clazz.getName(),
|
||||
StringHelper.unqualify( t.getClass().getName() ),
|
||||
t.getMessage(),
|
||||
setterNames[index]
|
||||
);
|
||||
}
|
||||
else {
|
||||
LOG.debugf(
|
||||
"Reflection optimizer disabled for %s [%s: %s]",
|
||||
clazz.getName(),
|
||||
StringHelper.unqualify( t.getClass().getName() ),
|
||||
t.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( fastClass != null && bulkAccessor != null ) {
|
||||
return new ReflectionOptimizerImpl(
|
||||
new InstantiationOptimizerAdapter( fastClass ),
|
||||
new AccessOptimizerAdapter( bulkAccessor, clazz )
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enhancer getEnhancer(EnhancementContext enhancementContext) {
|
||||
return new EnhancerImpl( enhancementContext );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.javassist;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Fast access to class information
|
||||
*
|
||||
* @author Muga Nishizawa
|
||||
*/
|
||||
public class FastClass implements Serializable {
|
||||
private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
|
||||
|
||||
private final Class type;
|
||||
|
||||
/**
|
||||
* Constructs a FastClass
|
||||
*
|
||||
* @param type The class to optimize
|
||||
*
|
||||
* @return The fast class access to the given class
|
||||
*/
|
||||
public static FastClass create(Class type) {
|
||||
return new FastClass( type );
|
||||
}
|
||||
|
||||
private FastClass(Class type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access to invoke a method on the class that this fast class handles
|
||||
*
|
||||
* @param name The name of the method to invoke,
|
||||
* @param parameterTypes The method parameter types
|
||||
* @param obj The instance on which to invoke the method
|
||||
* @param args The parameter arguments
|
||||
*
|
||||
* @return The method result
|
||||
*
|
||||
* @throws InvocationTargetException Indicates a problem performing invocation
|
||||
*/
|
||||
public Object invoke(
|
||||
String name,
|
||||
Class[] parameterTypes,
|
||||
Object obj,
|
||||
Object[] args) throws InvocationTargetException {
|
||||
return this.invoke( this.getIndex( name, parameterTypes ), obj, args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Access to invoke a method on the class that this fast class handles by its index
|
||||
*
|
||||
* @param index The method index
|
||||
* @param obj The instance on which to invoke the method
|
||||
* @param args The parameter arguments
|
||||
*
|
||||
* @return The method result
|
||||
*
|
||||
* @throws InvocationTargetException Indicates a problem performing invocation
|
||||
*/
|
||||
public Object invoke(
|
||||
int index,
|
||||
Object obj,
|
||||
Object[] args) throws InvocationTargetException {
|
||||
final Method[] methods = this.type.getMethods();
|
||||
try {
|
||||
return methods[index].invoke( obj, args );
|
||||
}
|
||||
catch ( ArrayIndexOutOfBoundsException e ) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot find matching method/constructor"
|
||||
);
|
||||
}
|
||||
catch ( IllegalAccessException e ) {
|
||||
throw new InvocationTargetException( e );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the default constructor
|
||||
*
|
||||
* @return The constructed instance
|
||||
*
|
||||
* @throws InvocationTargetException Indicates a problem performing invocation
|
||||
*/
|
||||
public Object newInstance() throws InvocationTargetException {
|
||||
return this.newInstance( this.getIndex( EMPTY_CLASS_ARRAY ), null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a parameterized constructor
|
||||
*
|
||||
* @param parameterTypes The parameter types
|
||||
* @param args The parameter arguments to pass along
|
||||
*
|
||||
* @return The constructed instance
|
||||
*
|
||||
* @throws InvocationTargetException Indicates a problem performing invocation
|
||||
*/
|
||||
public Object newInstance(
|
||||
Class[] parameterTypes,
|
||||
Object[] args) throws InvocationTargetException {
|
||||
return this.newInstance( this.getIndex( parameterTypes ), args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a constructor by its index
|
||||
*
|
||||
* @param index The constructor index
|
||||
* @param args The parameter arguments to pass along
|
||||
*
|
||||
* @return The constructed instance
|
||||
*
|
||||
* @throws InvocationTargetException Indicates a problem performing invocation
|
||||
*/
|
||||
public Object newInstance(
|
||||
int index,
|
||||
Object[] args) throws InvocationTargetException {
|
||||
final Constructor[] constructors = this.type.getConstructors();
|
||||
try {
|
||||
return constructors[index].newInstance( args );
|
||||
}
|
||||
catch ( ArrayIndexOutOfBoundsException e ) {
|
||||
throw new IllegalArgumentException( "Cannot find matching method/constructor" );
|
||||
}
|
||||
catch ( InstantiationException e ) {
|
||||
throw new InvocationTargetException( e );
|
||||
}
|
||||
catch ( IllegalAccessException e ) {
|
||||
throw new InvocationTargetException( e );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the index of a method
|
||||
*
|
||||
* @param name The method name
|
||||
* @param parameterTypes The method parameter types
|
||||
*
|
||||
* @return The index
|
||||
*/
|
||||
public int getIndex(String name, Class[] parameterTypes) {
|
||||
final Method[] methods = this.type.getMethods();
|
||||
boolean eq;
|
||||
|
||||
for ( int i = 0; i < methods.length; ++i ) {
|
||||
if ( !Modifier.isPublic( methods[i].getModifiers() ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( !methods[i].getName().equals( name ) ) {
|
||||
continue;
|
||||
}
|
||||
final Class[] params = methods[i].getParameterTypes();
|
||||
if ( params.length != parameterTypes.length ) {
|
||||
continue;
|
||||
}
|
||||
eq = true;
|
||||
for ( int j = 0; j < params.length; ++j ) {
|
||||
if ( !params[j].equals( parameterTypes[j] ) ) {
|
||||
eq = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( eq ) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the index of a constructor
|
||||
*
|
||||
* @param parameterTypes The constructor parameter types
|
||||
*
|
||||
* @return The index
|
||||
*/
|
||||
public int getIndex(Class[] parameterTypes) {
|
||||
final Constructor[] constructors = this.type.getConstructors();
|
||||
boolean eq;
|
||||
|
||||
for ( int i = 0; i < constructors.length; ++i ) {
|
||||
if ( !Modifier.isPublic( constructors[i].getModifiers() ) ) {
|
||||
continue;
|
||||
}
|
||||
final Class[] params = constructors[i].getParameterTypes();
|
||||
if ( params.length != parameterTypes.length ) {
|
||||
continue;
|
||||
}
|
||||
eq = true;
|
||||
for ( int j = 0; j < params.length; ++j ) {
|
||||
if ( !params[j].equals( parameterTypes[j] ) ) {
|
||||
eq = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( eq ) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wrapped class name
|
||||
*
|
||||
* @return The class name
|
||||
*/
|
||||
public String getName() {
|
||||
return this.type.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wrapped java class reference
|
||||
*
|
||||
* @return The class reference
|
||||
*/
|
||||
public Class getJavaClass() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.type.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.type.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof FastClass
|
||||
&& this.type.equals( ((FastClass) o).type );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.javassist;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.hibernate.InstantiationException;
|
||||
import org.hibernate.bytecode.spi.ReflectionOptimizer;
|
||||
|
||||
/**
|
||||
* The {@link org.hibernate.bytecode.spi.ReflectionOptimizer.InstantiationOptimizer} implementation for Javassist
|
||||
* which simply acts as an adapter to the {@link FastClass} class.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class InstantiationOptimizerAdapter implements ReflectionOptimizer.InstantiationOptimizer, Serializable {
|
||||
private final FastClass fastClass;
|
||||
|
||||
/**
|
||||
* Constructs the InstantiationOptimizerAdapter
|
||||
*
|
||||
* @param fastClass The fast class for the class to be instantiated here.
|
||||
*/
|
||||
public InstantiationOptimizerAdapter(FastClass fastClass) {
|
||||
this.fastClass = fastClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object newInstance() {
|
||||
try {
|
||||
return fastClass.newInstance();
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
throw new InstantiationException(
|
||||
"Could not instantiate entity with Javassist optimizer: ",
|
||||
fastClass.getJavaClass(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.javassist;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javassist.util.proxy.MethodFilter;
|
||||
import javassist.util.proxy.MethodHandler;
|
||||
import javassist.util.proxy.Proxy;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.bytecode.spi.BasicProxyFactory;
|
||||
import org.hibernate.bytecode.spi.ProxyFactoryFactory;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.proxy.ProxyFactory;
|
||||
import org.hibernate.proxy.pojo.javassist.JavassistProxyFactory;
|
||||
|
||||
/**
|
||||
* A factory for Javassist-based {@link ProxyFactory} instances.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ProxyFactoryFactoryImpl implements ProxyFactoryFactory {
|
||||
|
||||
/**
|
||||
* Builds a Javassist-based proxy factory.
|
||||
*
|
||||
* @return a new Javassist-based proxy factory.
|
||||
*/
|
||||
@Override
|
||||
public ProxyFactory buildProxyFactory(SessionFactoryImplementor sessionFactory) {
|
||||
return new JavassistProxyFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a BasicProxyFactoryImpl
|
||||
*
|
||||
* @param superClass The abstract super class (or null if none).
|
||||
* @param interfaces Interfaces to be proxied (or null if none).
|
||||
*
|
||||
* @return The constructed BasicProxyFactoryImpl
|
||||
*/
|
||||
@Override
|
||||
public BasicProxyFactory buildBasicProxyFactory(Class superClass, Class[] interfaces) {
|
||||
return new BasicProxyFactoryImpl( superClass, interfaces );
|
||||
}
|
||||
|
||||
private static class BasicProxyFactoryImpl implements BasicProxyFactory {
|
||||
private final Class proxyClass;
|
||||
|
||||
public BasicProxyFactoryImpl(Class superClass, Class[] interfaces) {
|
||||
if ( superClass == null && ( interfaces == null || interfaces.length < 1 ) ) {
|
||||
throw new AssertionFailure( "attempting to build proxy without any superclass or interfaces" );
|
||||
}
|
||||
|
||||
final javassist.util.proxy.ProxyFactory factory = new javassist.util.proxy.ProxyFactory();
|
||||
factory.setFilter( FINALIZE_FILTER );
|
||||
if ( superClass != null ) {
|
||||
factory.setSuperclass( superClass );
|
||||
}
|
||||
if ( interfaces != null && interfaces.length > 0 ) {
|
||||
factory.setInterfaces( interfaces );
|
||||
}
|
||||
proxyClass = factory.createClass();
|
||||
}
|
||||
|
||||
public Object getProxy() {
|
||||
try {
|
||||
final Proxy proxy = (Proxy) proxyClass.newInstance();
|
||||
proxy.setHandler( new PassThroughHandler( proxy, proxyClass.getName() ) );
|
||||
return proxy;
|
||||
}
|
||||
catch ( Throwable t ) {
|
||||
throw new HibernateException( "Unable to instantiated proxy instance" );
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInstance(Object object) {
|
||||
return proxyClass.isInstance( object );
|
||||
}
|
||||
}
|
||||
|
||||
private static final MethodFilter FINALIZE_FILTER = new MethodFilter() {
|
||||
public boolean isHandled(Method m) {
|
||||
// skip finalize methods
|
||||
return !( m.getParameterCount() == 0 && m.getName().equals( "finalize" ) );
|
||||
}
|
||||
};
|
||||
|
||||
private static class PassThroughHandler implements MethodHandler {
|
||||
private HashMap data = new HashMap();
|
||||
private final Object proxiedObject;
|
||||
private final String proxiedClassName;
|
||||
|
||||
public PassThroughHandler(Object proxiedObject, String proxiedClassName) {
|
||||
this.proxiedObject = proxiedObject;
|
||||
this.proxiedClassName = proxiedClassName;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object invoke(
|
||||
Object object,
|
||||
Method method,
|
||||
Method method1,
|
||||
Object[] args) throws Exception {
|
||||
final String name = method.getName();
|
||||
if ( "toString".equals( name ) ) {
|
||||
return proxiedClassName + "@" + System.identityHashCode( object );
|
||||
}
|
||||
else if ( "equals".equals( name ) ) {
|
||||
return proxiedObject == object;
|
||||
}
|
||||
else if ( "hashCode".equals( name ) ) {
|
||||
return System.identityHashCode( object );
|
||||
}
|
||||
|
||||
final boolean hasGetterSignature = method.getParameterCount() == 0
|
||||
&& method.getReturnType() != null;
|
||||
final boolean hasSetterSignature = method.getParameterCount() == 1
|
||||
&& ( method.getReturnType() == null || method.getReturnType() == void.class );
|
||||
|
||||
if ( name.startsWith( "get" ) && hasGetterSignature ) {
|
||||
final String propName = name.substring( 3 );
|
||||
return data.get( propName );
|
||||
}
|
||||
else if ( name.startsWith( "is" ) && hasGetterSignature ) {
|
||||
final String propName = name.substring( 2 );
|
||||
return data.get( propName );
|
||||
}
|
||||
else if ( name.startsWith( "set" ) && hasSetterSignature) {
|
||||
final String propName = name.substring( 3 );
|
||||
data.put( propName, args[0] );
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
// todo : what else to do here?
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.javassist;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.hibernate.bytecode.spi.ReflectionOptimizer;
|
||||
|
||||
/**
|
||||
* ReflectionOptimizer implementation for Javassist.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ReflectionOptimizerImpl implements ReflectionOptimizer, Serializable {
|
||||
private final InstantiationOptimizer instantiationOptimizer;
|
||||
private final AccessOptimizer accessOptimizer;
|
||||
|
||||
/**
|
||||
* Constructs a ReflectionOptimizerImpl
|
||||
*
|
||||
* @param instantiationOptimizer The instantiation optimizer to use
|
||||
* @param accessOptimizer The property access optimizer to use.
|
||||
*/
|
||||
public ReflectionOptimizerImpl(
|
||||
InstantiationOptimizer instantiationOptimizer,
|
||||
AccessOptimizer accessOptimizer) {
|
||||
this.instantiationOptimizer = instantiationOptimizer;
|
||||
this.accessOptimizer = accessOptimizer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstantiationOptimizer getInstantiationOptimizer() {
|
||||
return instantiationOptimizer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessOptimizer getAccessOptimizer() {
|
||||
return accessOptimizer;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Javassist support internals
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.javassist;
|
|
@ -14,7 +14,7 @@ import org.hibernate.service.Service;
|
|||
* An interface for factories of {@link ProxyFactory proxy factory} instances.
|
||||
* <p/>
|
||||
* Currently used to abstract from the tuplizer whether we are using Byte Buddy or
|
||||
* not for lazy proxy generation.
|
||||
* Javassist for lazy proxy generation.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
|
|
@ -1255,7 +1255,7 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
|
|||
|
||||
|
||||
/**
|
||||
* Pick which bytecode enhancing library to use. Currently only supports bytebuddy.
|
||||
* Pick which bytecode enhancing library to use. Currently supports javassist and bytebuddy, bytebuddy being the default since version 5.3.
|
||||
*/
|
||||
String BYTECODE_PROVIDER = "hibernate.bytecode.provider";
|
||||
|
||||
|
|
|
@ -327,6 +327,7 @@ public final class Environment implements AvailableSettings {
|
|||
}
|
||||
|
||||
|
||||
public static final String BYTECODE_PROVIDER_NAME_JAVASSIST = "javassist";
|
||||
public static final String BYTECODE_PROVIDER_NAME_BYTEBUDDY = "bytebuddy";
|
||||
public static final String BYTECODE_PROVIDER_NAME_NONE = "none";
|
||||
public static final String BYTECODE_PROVIDER_NAME_DEFAULT = BYTECODE_PROVIDER_NAME_BYTEBUDDY;
|
||||
|
@ -344,6 +345,10 @@ public final class Environment implements AvailableSettings {
|
|||
return new org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl();
|
||||
}
|
||||
|
||||
if ( BYTECODE_PROVIDER_NAME_JAVASSIST.equals( providerName ) ) {
|
||||
return new org.hibernate.bytecode.internal.javassist.BytecodeProviderImpl();
|
||||
}
|
||||
|
||||
LOG.bytecodeProvider( providerName );
|
||||
|
||||
// there is no need to support plugging in a custom BytecodeProvider via FQCN:
|
||||
|
|
|
@ -397,6 +397,7 @@ class PropertyContainer {
|
|||
private static boolean mustBeSkipped(XProperty property) {
|
||||
//TODO make those hardcoded tests more portable (through the bytecode provider?)
|
||||
return property.isAnnotationPresent( Transient.class )
|
||||
|| "net.sf.cglib.transform.impl.InterceptFieldCallback".equals( property.getType().getName() );
|
||||
|| "net.sf.cglib.transform.impl.InterceptFieldCallback".equals( property.getType().getName() )
|
||||
|| "org.hibernate.bytecode.internal.javassist.FieldHandler".equals( property.getType().getName() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,8 +159,13 @@ public class StandardPojoEntityRepresentationStrategy implements EntityRepresent
|
|||
PersistentClass bootDescriptor,
|
||||
BytecodeProvider bytecodeProvider,
|
||||
RuntimeModelCreationContext creationContext) {
|
||||
|
||||
final Set<Class> proxyInterfaces = new java.util.HashSet<>();
|
||||
/*
|
||||
* We need to preserve the order of the interfaces they were put into the set, since javassist will choose the
|
||||
* first one's class-loader to construct the proxy class with. This is also the reason why HibernateProxy.class
|
||||
* should be the last one in the order (on JBossAS7 its class-loader will be org.hibernate module's class-
|
||||
* loader, which will not see the classes inside deployed apps. See HHH-3078
|
||||
*/
|
||||
final Set<Class> proxyInterfaces = new java.util.LinkedHashSet<>();
|
||||
|
||||
final Class mappedClass = mappedJtd.getJavaType();
|
||||
Class proxyInterface;
|
||||
|
|
|
@ -38,7 +38,13 @@ public final class ProxyFactoryHelper {
|
|||
}
|
||||
|
||||
public static Set<Class> extractProxyInterfaces(final PersistentClass persistentClass, final String entityName) {
|
||||
final Set<Class> proxyInterfaces = new java.util.HashSet<>();
|
||||
/*
|
||||
* We need to preserve the order of the interfaces they were put into the set, since javassist will choose the
|
||||
* first one's class-loader to construct the proxy class with. This is also the reason why HibernateProxy.class
|
||||
* should be the last one in the order (on JBossAS7 its class-loader will be org.hibernate module's class-
|
||||
* loader, which will not see the classes inside deployed apps. See HHH-3078
|
||||
*/
|
||||
final Set<Class> proxyInterfaces = new java.util.LinkedHashSet<Class>();
|
||||
final Class mappedClass = persistentClass.getMappedClass();
|
||||
final Class proxyInterface = persistentClass.getProxyInterface();
|
||||
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.proxy.pojo.javassist;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javassist.util.proxy.MethodHandler;
|
||||
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.internal.util.ReflectHelper;
|
||||
import org.hibernate.proxy.pojo.BasicLazyInitializer;
|
||||
import org.hibernate.type.CompositeType;
|
||||
|
||||
import static org.hibernate.internal.CoreLogging.messageLogger;
|
||||
|
||||
/**
|
||||
* A Javassist-based lazy initializer proxy.
|
||||
*
|
||||
* @author Muga Nishizawa
|
||||
*/
|
||||
public class JavassistLazyInitializer extends BasicLazyInitializer implements MethodHandler {
|
||||
private static final CoreMessageLogger LOG = messageLogger( JavassistLazyInitializer.class );
|
||||
|
||||
private final Class[] interfaces;
|
||||
|
||||
private boolean constructed;
|
||||
|
||||
public JavassistLazyInitializer(
|
||||
String entityName,
|
||||
Class persistentClass,
|
||||
Class[] interfaces,
|
||||
Object id,
|
||||
Method getIdentifierMethod,
|
||||
Method setIdentifierMethod,
|
||||
CompositeType componentIdType,
|
||||
SharedSessionContractImplementor session,
|
||||
boolean overridesEquals) {
|
||||
super( entityName, persistentClass, id, getIdentifierMethod, setIdentifierMethod, componentIdType, session, overridesEquals );
|
||||
this.interfaces = interfaces;
|
||||
}
|
||||
|
||||
protected void constructed() {
|
||||
constructed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(
|
||||
final Object proxy,
|
||||
final Method thisMethod,
|
||||
final Method proceed,
|
||||
final Object[] args) throws Throwable {
|
||||
if ( this.constructed ) {
|
||||
// HHH-10922 - Internal calls to bytecode enhanced methods cause proxy to be initialized
|
||||
if ( thisMethod.getName().startsWith( "$$_hibernate_" ) ) {
|
||||
return proceed.invoke( proxy, args );
|
||||
}
|
||||
|
||||
Object result;
|
||||
try {
|
||||
result = this.invoke( thisMethod, args, proxy );
|
||||
}
|
||||
catch ( Throwable t ) {
|
||||
throw t instanceof RuntimeException ? t : new Exception( t.getCause() );
|
||||
}
|
||||
if ( result == INVOKE_IMPLEMENTATION ) {
|
||||
Object target = getImplementation();
|
||||
final Object returnValue;
|
||||
try {
|
||||
if ( ReflectHelper.isPublic( persistentClass, thisMethod ) ) {
|
||||
if ( !thisMethod.getDeclaringClass().isInstance( target ) ) {
|
||||
throw new ClassCastException(
|
||||
target.getClass().getName()
|
||||
+ " incompatible with "
|
||||
+ thisMethod.getDeclaringClass().getName()
|
||||
);
|
||||
}
|
||||
returnValue = thisMethod.invoke( target, args );
|
||||
}
|
||||
else {
|
||||
thisMethod.setAccessible( true );
|
||||
returnValue = thisMethod.invoke( target, args );
|
||||
}
|
||||
|
||||
if ( returnValue == target ) {
|
||||
if ( returnValue.getClass().isInstance( proxy ) ) {
|
||||
return proxy;
|
||||
}
|
||||
else {
|
||||
LOG.narrowingProxy( returnValue.getClass() );
|
||||
}
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
catch ( InvocationTargetException ite ) {
|
||||
throw ite.getTargetException();
|
||||
}
|
||||
}
|
||||
else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// while constructor is running
|
||||
if ( thisMethod.getName().equals( "getHibernateLazyInitializer" ) ) {
|
||||
return this;
|
||||
}
|
||||
else {
|
||||
return proceed.invoke( proxy, args );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object serializableProxy() {
|
||||
return new SerializableProxy(
|
||||
getEntityName(),
|
||||
persistentClass,
|
||||
interfaces,
|
||||
getIdentifier(),
|
||||
( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ),
|
||||
getSessionFactoryUuid(),
|
||||
isAllowLoadOutsideTransaction(),
|
||||
getIdentifierMethod,
|
||||
setIdentifierMethod,
|
||||
componentIdType
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.proxy.pojo.javassist;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import javassist.util.proxy.MethodFilter;
|
||||
import javassist.util.proxy.Proxy;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.internal.util.ReflectHelper;
|
||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.ProxyFactory;
|
||||
import org.hibernate.type.CompositeType;
|
||||
|
||||
import static org.hibernate.internal.CoreLogging.messageLogger;
|
||||
|
||||
/**
|
||||
* A {@link ProxyFactory} implementation for producing Javassist-based proxies.
|
||||
*
|
||||
* @author Muga Nishizawa
|
||||
*/
|
||||
public class JavassistProxyFactory implements ProxyFactory, Serializable {
|
||||
private static final CoreMessageLogger LOG = messageLogger( JavassistProxyFactory.class );
|
||||
|
||||
private static final MethodFilter EXCLUDE_FILTER = m -> {
|
||||
// skip finalize methods and Groovy getMetaClass
|
||||
return !(
|
||||
m.getParameterCount() == 0 && m.getName().equals( "finalize" ) || (
|
||||
m.getName().equals( "getMetaClass" ) &&
|
||||
m.getReturnType().getName().equals( "groovy.lang.MetaClass" )
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
private Class persistentClass;
|
||||
private String entityName;
|
||||
private Class[] interfaces;
|
||||
private Method getIdentifierMethod;
|
||||
private Method setIdentifierMethod;
|
||||
private CompositeType componentIdType;
|
||||
private boolean overridesEquals;
|
||||
|
||||
private Class proxyClass;
|
||||
|
||||
public JavassistProxyFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInstantiate(
|
||||
final String entityName,
|
||||
final Class persistentClass,
|
||||
final Set<Class> interfaces,
|
||||
final Method getIdentifierMethod,
|
||||
final Method setIdentifierMethod,
|
||||
CompositeType componentIdType) throws HibernateException {
|
||||
this.entityName = entityName;
|
||||
this.persistentClass = persistentClass;
|
||||
this.interfaces = toArray( interfaces );
|
||||
this.getIdentifierMethod = getIdentifierMethod;
|
||||
this.setIdentifierMethod = setIdentifierMethod;
|
||||
this.componentIdType = componentIdType;
|
||||
this.overridesEquals = ReflectHelper.overridesEquals( persistentClass );
|
||||
|
||||
this.proxyClass = buildJavassistProxyFactory().createClass();
|
||||
}
|
||||
|
||||
private Class[] toArray(Set<Class> interfaces) {
|
||||
if ( interfaces == null ) {
|
||||
return ArrayHelper.EMPTY_CLASS_ARRAY;
|
||||
}
|
||||
|
||||
return interfaces.toArray( new Class[interfaces.size()] );
|
||||
}
|
||||
|
||||
private javassist.util.proxy.ProxyFactory buildJavassistProxyFactory() {
|
||||
return buildJavassistProxyFactory(
|
||||
persistentClass,
|
||||
interfaces
|
||||
);
|
||||
}
|
||||
|
||||
public static javassist.util.proxy.ProxyFactory buildJavassistProxyFactory(
|
||||
final Class persistentClass,
|
||||
final Class[] interfaces) {
|
||||
javassist.util.proxy.ProxyFactory factory = new javassist.util.proxy.ProxyFactory() {
|
||||
@Override
|
||||
protected ClassLoader getClassLoader() {
|
||||
return persistentClass.getClassLoader();
|
||||
}
|
||||
};
|
||||
factory.setSuperclass( interfaces.length == 1 ? persistentClass : null );
|
||||
factory.setInterfaces( interfaces );
|
||||
factory.setFilter( EXCLUDE_FILTER );
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HibernateProxy getProxy(
|
||||
Object id,
|
||||
SharedSessionContractImplementor session) throws HibernateException {
|
||||
final JavassistLazyInitializer initializer = new JavassistLazyInitializer(
|
||||
entityName,
|
||||
persistentClass,
|
||||
interfaces,
|
||||
id,
|
||||
getIdentifierMethod,
|
||||
setIdentifierMethod,
|
||||
componentIdType,
|
||||
session,
|
||||
overridesEquals
|
||||
);
|
||||
|
||||
try {
|
||||
final HibernateProxy proxy = (HibernateProxy) proxyClass.getConstructor().newInstance();
|
||||
( (Proxy) proxy ).setHandler( initializer );
|
||||
initializer.constructed();
|
||||
|
||||
return proxy;
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
String logMessage = LOG.bytecodeEnhancementFailedBecauseOfDefaultConstructor( entityName );
|
||||
LOG.error( logMessage, e );
|
||||
throw new HibernateException( logMessage, e );
|
||||
}
|
||||
catch (Throwable t) {
|
||||
String logMessage = LOG.bytecodeEnhancementFailed( entityName );
|
||||
LOG.error( logMessage, t );
|
||||
throw new HibernateException( logMessage, t );
|
||||
}
|
||||
}
|
||||
|
||||
public static HibernateProxy deserializeProxy(SerializableProxy serializableProxy) {
|
||||
final JavassistLazyInitializer initializer = new JavassistLazyInitializer(
|
||||
serializableProxy.getEntityName(),
|
||||
serializableProxy.getPersistentClass(),
|
||||
serializableProxy.getInterfaces(),
|
||||
serializableProxy.getId(),
|
||||
resolveIdGetterMethod( serializableProxy ),
|
||||
resolveIdSetterMethod( serializableProxy ),
|
||||
serializableProxy.getComponentIdType(),
|
||||
null,
|
||||
ReflectHelper.overridesEquals( serializableProxy.getPersistentClass() )
|
||||
);
|
||||
|
||||
final javassist.util.proxy.ProxyFactory factory = buildJavassistProxyFactory(
|
||||
serializableProxy.getPersistentClass(),
|
||||
serializableProxy.getInterfaces()
|
||||
);
|
||||
|
||||
// note: interface is assumed to already contain HibernateProxy.class
|
||||
try {
|
||||
final Class proxyClass = factory.createClass();
|
||||
final HibernateProxy proxy = ( HibernateProxy ) proxyClass.newInstance();
|
||||
( (Proxy) proxy ).setHandler( initializer );
|
||||
initializer.constructed();
|
||||
return proxy;
|
||||
}
|
||||
catch ( Throwable t ) {
|
||||
final String message = LOG.bytecodeEnhancementFailed( serializableProxy.getEntityName() );
|
||||
LOG.error( message, t );
|
||||
throw new HibernateException( message, t );
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Method resolveIdGetterMethod(SerializableProxy serializableProxy) {
|
||||
if ( serializableProxy.getIdentifierGetterMethodName() == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return serializableProxy.getIdentifierGetterMethodClass().getDeclaredMethod( serializableProxy.getIdentifierGetterMethodName() );
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
throw new HibernateException(
|
||||
String.format(
|
||||
Locale.ENGLISH,
|
||||
"Unable to deserialize proxy [%s, %s]; could not locate id getter method [%s] on entity class [%s]",
|
||||
serializableProxy.getEntityName(),
|
||||
serializableProxy.getId(),
|
||||
serializableProxy.getIdentifierGetterMethodName(),
|
||||
serializableProxy.getIdentifierGetterMethodClass()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Method resolveIdSetterMethod(SerializableProxy serializableProxy) {
|
||||
if ( serializableProxy.getIdentifierSetterMethodName() == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return serializableProxy.getIdentifierSetterMethodClass().getDeclaredMethod(
|
||||
serializableProxy.getIdentifierSetterMethodName(),
|
||||
serializableProxy.getIdentifierSetterMethodParams()
|
||||
);
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
throw new HibernateException(
|
||||
String.format(
|
||||
Locale.ENGLISH,
|
||||
"Unable to deserialize proxy [%s, %s]; could not locate id setter method [%s] on entity class [%s]",
|
||||
serializableProxy.getEntityName(),
|
||||
serializableProxy.getId(),
|
||||
serializableProxy.getIdentifierSetterMethodName(),
|
||||
serializableProxy.getIdentifierSetterMethodClass()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.proxy.pojo.javassist;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.hibernate.proxy.AbstractSerializableProxy;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.type.CompositeType;
|
||||
|
||||
/**
|
||||
* Serializable placeholder for Javassist proxies
|
||||
*/
|
||||
public final class SerializableProxy extends AbstractSerializableProxy {
|
||||
private final Class persistentClass;
|
||||
private final Class[] interfaces;
|
||||
|
||||
private final String identifierGetterMethodName;
|
||||
private final Class identifierGetterMethodClass;
|
||||
|
||||
private final String identifierSetterMethodName;
|
||||
private final Class identifierSetterMethodClass;
|
||||
private final Class[] identifierSetterMethodParams;
|
||||
|
||||
private final CompositeType componentIdType;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #SerializableProxy(String, Class, Class[], Object, Boolean, String, boolean, Method, Method, CompositeType)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public SerializableProxy(
|
||||
String entityName,
|
||||
Class persistentClass,
|
||||
Class[] interfaces,
|
||||
Serializable id,
|
||||
Boolean readOnly,
|
||||
Method getIdentifierMethod,
|
||||
Method setIdentifierMethod,
|
||||
CompositeType componentIdType) {
|
||||
this(
|
||||
entityName, persistentClass, interfaces, id, readOnly, null, false,
|
||||
getIdentifierMethod, setIdentifierMethod, componentIdType
|
||||
);
|
||||
}
|
||||
|
||||
public SerializableProxy(
|
||||
String entityName,
|
||||
Class persistentClass,
|
||||
Class[] interfaces,
|
||||
Object id,
|
||||
Boolean readOnly,
|
||||
String sessionFactoryUuid,
|
||||
boolean allowLoadOutsideTransaction,
|
||||
Method getIdentifierMethod,
|
||||
Method setIdentifierMethod,
|
||||
CompositeType componentIdType) {
|
||||
super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction );
|
||||
this.persistentClass = persistentClass;
|
||||
this.interfaces = interfaces;
|
||||
if ( getIdentifierMethod != null ) {
|
||||
identifierGetterMethodName = getIdentifierMethod.getName();
|
||||
identifierGetterMethodClass = getIdentifierMethod.getDeclaringClass();
|
||||
}
|
||||
else {
|
||||
identifierGetterMethodName = null;
|
||||
identifierGetterMethodClass = null;
|
||||
}
|
||||
|
||||
if ( setIdentifierMethod != null ) {
|
||||
identifierSetterMethodName = setIdentifierMethod.getName();
|
||||
identifierSetterMethodClass = setIdentifierMethod.getDeclaringClass();
|
||||
identifierSetterMethodParams = setIdentifierMethod.getParameterTypes();
|
||||
}
|
||||
else {
|
||||
identifierSetterMethodName = null;
|
||||
identifierSetterMethodClass = null;
|
||||
identifierSetterMethodParams = null;
|
||||
}
|
||||
|
||||
this.componentIdType = componentIdType;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getEntityName() {
|
||||
return super.getEntityName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getId() {
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
protected Class getPersistentClass() {
|
||||
return persistentClass;
|
||||
}
|
||||
|
||||
protected Class[] getInterfaces() {
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
protected String getIdentifierGetterMethodName() {
|
||||
return identifierGetterMethodName;
|
||||
}
|
||||
|
||||
protected Class getIdentifierGetterMethodClass() {
|
||||
return identifierGetterMethodClass;
|
||||
}
|
||||
|
||||
protected String getIdentifierSetterMethodName() {
|
||||
return identifierSetterMethodName;
|
||||
}
|
||||
|
||||
protected Class getIdentifierSetterMethodClass() {
|
||||
return identifierSetterMethodClass;
|
||||
}
|
||||
|
||||
protected Class[] getIdentifierSetterMethodParams() {
|
||||
return identifierSetterMethodParams;
|
||||
}
|
||||
|
||||
protected CompositeType getComponentIdType() {
|
||||
return componentIdType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialization hook. This method is called by JDK deserialization. We use this hook
|
||||
* to replace the serial form with a live form.
|
||||
*
|
||||
* @return The live form.
|
||||
*/
|
||||
private Object readResolve() {
|
||||
HibernateProxy proxy = JavassistProxyFactory.deserializeProxy( this );
|
||||
afterDeserialization( ( JavassistLazyInitializer ) proxy.getHibernateLazyInitializer() );
|
||||
return proxy;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.tool.instrument.javassist;
|
||||
|
||||
import org.hibernate.internal.log.DeprecationLogger;
|
||||
import org.hibernate.tool.enhance.EnhancementTask;
|
||||
|
||||
/**
|
||||
* This is the legacy Ant-task Hibernate provided historically to
|
||||
* perform its old-school bytecode instrumentation. That has been replaced wholesale
|
||||
* with a new approach to bytecode manipulation offering 3 build-time variations for Ant,
|
||||
* Maven and Gradle.
|
||||
*
|
||||
* @author Muga Nishizawa
|
||||
* @author Steve Ebersole
|
||||
*
|
||||
* @deprecated This is the legacy Ant-task Hibernate provided historically to
|
||||
* perform its old-school bytecode instrumentation. That has been replaced wholesale
|
||||
* with a new approach to bytecode manipulation offering 3 build-time variations for Ant,
|
||||
* Maven and Gradle.
|
||||
*
|
||||
* @see EnhancementTask
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("unused")
|
||||
public class InstrumentTask extends EnhancementTask {
|
||||
public InstrumentTask() {
|
||||
DeprecationLogger.DEPRECATION_LOGGER.logDeprecatedInstrumentTask( InstrumentTask.class, EnhancementTask.class );
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ import org.hibernate.internal.CoreMessageLogger;
|
|||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||
import org.hibernate.proxy.ProxyFactory;
|
||||
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory;
|
||||
import org.hibernate.proxy.pojo.javassist.JavassistProxyFactory;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.logger.LoggerInspectionRule;
|
||||
|
@ -103,6 +104,9 @@ public class PrivateConstructorTest extends BaseEntityManagerFunctionalTestCase
|
|||
if ( byteCodeProvider == null || Environment.BYTECODE_PROVIDER_NAME_BYTEBUDDY.equals( byteCodeProvider ) ) {
|
||||
return ByteBuddyProxyFactory.class;
|
||||
}
|
||||
else if ( Environment.BYTECODE_PROVIDER_NAME_JAVASSIST.equals( byteCodeProvider ) ) {
|
||||
return JavassistProxyFactory.class;
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException( "Unknown bytecode provider:" + byteCodeProvider );
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.test.bytecode;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.hibernate.bytecode.internal.javassist.BulkAccessor;
|
||||
import org.hibernate.bytecode.spi.BytecodeProvider;
|
||||
import org.hibernate.bytecode.spi.ReflectionOptimizer;
|
||||
import org.hibernate.cfg.Environment;
|
||||
|
@ -20,6 +21,15 @@ import org.junit.Test;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ReflectionOptimizerTest extends BaseUnitTestCase {
|
||||
@Test
|
||||
public void testBulkAccessorDirectly() {
|
||||
BulkAccessor bulkAccessor = BulkAccessor.create(
|
||||
Bean.class,
|
||||
BeanReflectionHelper.getGetterNames(),
|
||||
BeanReflectionHelper.getSetterNames(),
|
||||
BeanReflectionHelper.getTypes()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReflectionOptimization() {
|
||||
|
|
|
@ -56,7 +56,7 @@ public class BidirectionalLazyGroupsInEmbeddableTest extends BaseCoreFunctionalT
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Test is failing with ByteBuddy if the mappings are moved to the fields.")
|
||||
@Ignore("Test is failing with Javassist and also fails with ByteBuddy if the mappings are moved to the fields.")
|
||||
public void test() {
|
||||
|
||||
doInHibernate(
|
||||
|
|
|
@ -19,7 +19,7 @@ import org.junit.Assert;
|
|||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests if bytebuddy instrumentation is done with the proper classloader for entities with proxy class. The classloader
|
||||
* Tests if javassist instrumentation is done with the proper classloader for entities with proxy class. The classloader
|
||||
* of {@link HibernateProxy} will not see {@link IPerson}, since it is only accessible from this package. But: the
|
||||
* classloader of {@link IPerson} will see {@link HibernateProxy}, so instrumentation will only work if this classloader
|
||||
* is chosen for creating the instrumented proxy class. We need to check the class of a loaded object though, since
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.test.bytecode.enhancement.javassist;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import javassist.CtClass;
|
||||
|
||||
import org.hibernate.bytecode.enhance.internal.javassist.EnhancerImpl;
|
||||
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author Vlad Mihalcea
|
||||
*/
|
||||
public class EnhancerFileNotFoundTest {
|
||||
|
||||
@Test
|
||||
@TestForIssue( jiraKey = "HHH-11307" )
|
||||
public void test() throws Exception {
|
||||
Enhancer enhancer = new Enhancer( new DefaultEnhancementContext() );
|
||||
try {
|
||||
String resourceName = Hidden.class.getName().replace( '.', '/' ) + ".class";
|
||||
URL url = getClass().getClassLoader().getResource( resourceName );
|
||||
if ( url != null ) {
|
||||
Files.delete( Paths.get( url.toURI() ) );
|
||||
enhancer.loadCtClassFromClass( Hidden.class );
|
||||
}
|
||||
fail( "Should throw FileNotFoundException!" );
|
||||
} catch ( Exception expected ) {
|
||||
assertSame( FileNotFoundException.class, expected.getCause().getClass() );
|
||||
}
|
||||
}
|
||||
|
||||
// --- //
|
||||
|
||||
private static class Enhancer extends EnhancerImpl {
|
||||
|
||||
public Enhancer(EnhancementContext enhancementContext) {
|
||||
super( enhancementContext );
|
||||
}
|
||||
|
||||
@Override
|
||||
// change visibility protected -> public
|
||||
public CtClass loadCtClassFromClass(Class<?> aClass) {
|
||||
return super.loadCtClassFromClass( aClass );
|
||||
}
|
||||
}
|
||||
|
||||
// --- //
|
||||
|
||||
private static class Hidden {
|
||||
}
|
||||
}
|
|
@ -77,7 +77,7 @@ public abstract class EntityTools {
|
|||
return null;
|
||||
}
|
||||
else if ( HibernateProxy.class.isAssignableFrom( clazz ) ) {
|
||||
// Get the source class of Bytebuddy proxy instance.
|
||||
// Get the source class of Javassist proxy instance.
|
||||
return (Class<T>) clazz.getSuperclass();
|
||||
}
|
||||
return clazz;
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
module org.hibernate.orm.integrationtest.java.module {
|
||||
exports org.hibernate.orm.integrationtest.java.module.service;
|
||||
opens org.hibernate.orm.integrationtest.java.module.entity to
|
||||
org.hibernate.orm.core;
|
||||
org.hibernate.orm.core,
|
||||
javassist; // Necessary for javassist, but not for bytebuddy (the default)
|
||||
|
||||
requires java.persistence;
|
||||
/*
|
||||
|
|
|
@ -168,7 +168,8 @@ public class OsgiIntegrationTest {
|
|||
// // and use defined imports instead
|
||||
// probe.setHeader(
|
||||
// Constants.IMPORT_PACKAGE,
|
||||
// "javax.persistence"
|
||||
// "javassist.util.proxy"
|
||||
// + ",javax.persistence"
|
||||
// + ",javax.persistence.spi"
|
||||
// + ",org.h2"
|
||||
// + ",org.osgi.framework"
|
||||
|
|
|
@ -169,6 +169,7 @@ public class DialectFilterExtension implements ExecutionCondition {
|
|||
}
|
||||
return dialectAccess.get().getDialect();
|
||||
}
|
||||
|
||||
return emScope.get().getDialect();
|
||||
}
|
||||
|
||||
|
|
|
@ -43,5 +43,5 @@ public @interface RequiresDialect {
|
|||
/**
|
||||
* the Dialect version
|
||||
*/
|
||||
int version();
|
||||
int version() default -1;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue