HHH-11152: Added BytecodeProvider based on Byte Buddy
This commit is contained in:
parent
31a60b84c6
commit
ba3359fe62
|
@ -154,6 +154,7 @@ subprojects { subProject ->
|
|||
|
||||
testRuntime( libraries.log4j )
|
||||
testRuntime( libraries.javassist )
|
||||
testRuntime( libraries.byteBuddy )
|
||||
testRuntime( libraries.woodstox )
|
||||
|
||||
//Databases
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
org.hibernate.cfg,
|
||||
org.hibernate.service,
|
||||
javax.persistence;version="[1.0.0,2.1.0]",
|
||||
<!-- Needed for proxying's Javassist enhancement during runtime -->
|
||||
<!-- Needed for proxy enhancement during runtime -->
|
||||
org.hibernate.proxy,
|
||||
javassist.util.proxy,
|
||||
*
|
||||
|
|
|
@ -23,6 +23,7 @@ configurations {
|
|||
dependencies {
|
||||
compile( libraries.jpa )
|
||||
compile( libraries.javassist )
|
||||
compile( libraries.byteBuddy )
|
||||
compile( libraries.antlr )
|
||||
compile( libraries.jta )
|
||||
compile( libraries.jandex )
|
||||
|
@ -95,6 +96,7 @@ dependencies {
|
|||
testRuntime( libraries.expression_language_impl )
|
||||
testRuntime( 'jaxen:jaxen:1.1' )
|
||||
testRuntime( libraries.javassist )
|
||||
testRuntime( libraries.byteBuddy )
|
||||
|
||||
testCompile( project( ':hibernate-jpamodelgen' ) )
|
||||
|
||||
|
|
|
@ -0,0 +1,330 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
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 org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.annotation.AnnotationDescription;
|
||||
import net.bytebuddy.description.annotation.AnnotationValue;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.scaffold.FieldLocator;
|
||||
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.jar.asm.Type;
|
||||
|
||||
class BiDirectionalAssociationHandler implements Implementation {
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( BiDirectionalAssociationHandler.class );
|
||||
|
||||
static Implementation wrap(
|
||||
TypeDescription managedCtClass,
|
||||
ByteBuddyEnhancementContext enhancementContext,
|
||||
FieldDescription persistentField,
|
||||
Implementation implementation) {
|
||||
if ( !enhancementContext.doBiDirectionalAssociationManagement( persistentField ) ) {
|
||||
return implementation;
|
||||
}
|
||||
|
||||
TypeDescription targetEntity = getTargetEntityClass( managedCtClass, persistentField );
|
||||
if ( targetEntity == null ) {
|
||||
return implementation;
|
||||
}
|
||||
String mappedBy = getMappedBy( persistentField, targetEntity, enhancementContext );
|
||||
if ( mappedBy == null || mappedBy.isEmpty() ) {
|
||||
log.infof(
|
||||
"Could not find bi-directional association for field [%s#%s]",
|
||||
managedCtClass.getName(),
|
||||
persistentField.getName()
|
||||
);
|
||||
return implementation;
|
||||
}
|
||||
|
||||
TypeDescription targetType = FieldLocator.ForClassHierarchy.Factory.INSTANCE.make( targetEntity )
|
||||
.locate( mappedBy )
|
||||
.getField()
|
||||
.getType()
|
||||
.asErasure();
|
||||
|
||||
if ( EnhancerImpl.isAnnotationPresent( persistentField, OneToOne.class ) ) {
|
||||
implementation = Advice.withCustomMapping()
|
||||
.bind( CodeTemplates.FieldValue.class, persistentField )
|
||||
.bind( CodeTemplates.MappedBy.class, mappedBy )
|
||||
.to( CodeTemplates.OneToOneHandler.class )
|
||||
.wrap( implementation );
|
||||
}
|
||||
|
||||
if ( EnhancerImpl.isAnnotationPresent( persistentField, OneToMany.class ) ) {
|
||||
implementation = Advice.withCustomMapping()
|
||||
.bind( CodeTemplates.FieldValue.class, persistentField )
|
||||
.bind( CodeTemplates.MappedBy.class, mappedBy )
|
||||
.to( persistentField.getType().asErasure().isAssignableTo( Map.class )
|
||||
? CodeTemplates.OneToManyOnMapHandler.class
|
||||
: CodeTemplates.OneToManyOnCollectionHandler.class )
|
||||
.wrap( implementation );
|
||||
}
|
||||
|
||||
if ( EnhancerImpl.isAnnotationPresent( persistentField, ManyToOne.class ) ) {
|
||||
implementation = Advice.withCustomMapping()
|
||||
.bind( CodeTemplates.FieldValue.class, persistentField )
|
||||
.bind( CodeTemplates.MappedBy.class, mappedBy )
|
||||
.to( CodeTemplates.ManyToOneHandler.class )
|
||||
.wrap( implementation );
|
||||
}
|
||||
|
||||
if ( EnhancerImpl.isAnnotationPresent( persistentField, ManyToMany.class ) ) {
|
||||
|
||||
if ( persistentField.getType().asErasure().isAssignableTo( Map.class ) || targetType.isAssignableTo( Map.class ) ) {
|
||||
log.infof(
|
||||
"Bi-directional association for field [%s#%s] not managed: @ManyToMany in java.util.Map attribute not supported ",
|
||||
managedCtClass.getName(),
|
||||
persistentField.getName()
|
||||
);
|
||||
return implementation;
|
||||
}
|
||||
|
||||
implementation = Advice.withCustomMapping()
|
||||
.bind( CodeTemplates.FieldValue.class, persistentField )
|
||||
.bind( CodeTemplates.MappedBy.class, mappedBy )
|
||||
.to( CodeTemplates.ManyToManyHandler.class )
|
||||
.wrap( implementation );
|
||||
}
|
||||
|
||||
return new BiDirectionalAssociationHandler( implementation, targetEntity, targetType, mappedBy );
|
||||
}
|
||||
|
||||
public static TypeDescription getTargetEntityClass(TypeDescription managedCtClass, FieldDescription persistentField) {
|
||||
try {
|
||||
AnnotationDescription.Loadable<OneToOne> oto = EnhancerImpl.getAnnotation( persistentField, OneToOne.class );
|
||||
AnnotationDescription.Loadable<OneToMany> otm = EnhancerImpl.getAnnotation( persistentField, OneToMany.class );
|
||||
AnnotationDescription.Loadable<ManyToOne> mto = EnhancerImpl.getAnnotation( persistentField, ManyToOne.class );
|
||||
AnnotationDescription.Loadable<ManyToMany> mtm = EnhancerImpl.getAnnotation( persistentField, ManyToMany.class );
|
||||
|
||||
if ( oto == null && otm == null && mto == null && mtm == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AnnotationValue<?, ?> targetClass = null;
|
||||
if ( oto != null ) {
|
||||
targetClass = oto.getValue( new MethodDescription.ForLoadedMethod( OneToOne.class.getDeclaredMethod( "targetEntity" ) ) );
|
||||
}
|
||||
if ( otm != null ) {
|
||||
targetClass = otm.getValue( new MethodDescription.ForLoadedMethod( OneToMany.class.getDeclaredMethod( "targetEntity" ) ) );
|
||||
}
|
||||
if ( mto != null ) {
|
||||
targetClass = mto.getValue( new MethodDescription.ForLoadedMethod( ManyToOne.class.getDeclaredMethod( "targetEntity" ) ) );
|
||||
}
|
||||
if ( mtm != null ) {
|
||||
targetClass = mtm.getValue( new MethodDescription.ForLoadedMethod( ManyToMany.class.getDeclaredMethod( "targetEntity" ) ) );
|
||||
}
|
||||
|
||||
if ( targetClass == null ) {
|
||||
log.infof(
|
||||
"Could not find type of bi-directional association for field [%s#%s]",
|
||||
managedCtClass.getName(),
|
||||
persistentField.getName()
|
||||
);
|
||||
return null;
|
||||
}
|
||||
else if ( !targetClass.resolve( TypeDescription.class ).represents( void.class ) ) {
|
||||
return targetClass.resolve( TypeDescription.class );
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
|
||||
return entityType( target( persistentField ) );
|
||||
}
|
||||
|
||||
private static TypeDescription.Generic target(FieldDescription persistentField) {
|
||||
AnnotationDescription.Loadable<Access> access = persistentField.getDeclaringType().asErasure().getDeclaredAnnotations().ofType( Access.class );
|
||||
if ( access != null && access.loadSilent().value() == AccessType.FIELD ) {
|
||||
return persistentField.getType();
|
||||
}
|
||||
else {
|
||||
MethodDescription getter = EnhancerImpl.getterOf( persistentField );
|
||||
if ( getter == null ) {
|
||||
return persistentField.getType();
|
||||
}
|
||||
else {
|
||||
return getter.getReturnType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getMappedBy(FieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) {
|
||||
String mappedBy = getMappedByNotManyToMany( target );
|
||||
if ( mappedBy == null || mappedBy.isEmpty() ) {
|
||||
return getMappedByManyToMany( target, targetEntity, context );
|
||||
}
|
||||
else {
|
||||
return mappedBy;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getMappedByNotManyToMany(FieldDescription target) {
|
||||
try {
|
||||
AnnotationDescription.Loadable<OneToOne> oto = EnhancerImpl.getAnnotation( target, OneToOne.class );
|
||||
if ( oto != null ) {
|
||||
return oto.getValue( new MethodDescription.ForLoadedMethod( OneToOne.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class );
|
||||
}
|
||||
|
||||
AnnotationDescription.Loadable<OneToMany> otm = EnhancerImpl.getAnnotation( target, OneToMany.class );
|
||||
if ( otm != null ) {
|
||||
return otm.getValue( new MethodDescription.ForLoadedMethod( OneToMany.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class );
|
||||
}
|
||||
|
||||
AnnotationDescription.Loadable<ManyToMany> mtm = EnhancerImpl.getAnnotation( target, ManyToMany.class );
|
||||
if ( mtm != null ) {
|
||||
return mtm.getValue( new MethodDescription.ForLoadedMethod( ManyToMany.class.getDeclaredMethod( "mappedBy" ) ) ).resolve( String.class );
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getMappedByManyToMany(FieldDescription target, TypeDescription targetEntity, ByteBuddyEnhancementContext context) {
|
||||
for ( FieldDescription f : targetEntity.getDeclaredFields() ) {
|
||||
if ( context.isPersistentField( f )
|
||||
&& target.getName().equals( getMappedByNotManyToMany( f ) )
|
||||
&& target.getDeclaringType().asErasure().isAssignableTo( entityType( f.getType() ) ) ) {
|
||||
log.debugf(
|
||||
"mappedBy association for field [%s#%s] is [%s#%s]",
|
||||
target.getDeclaringType().asErasure().getName(),
|
||||
target.getName(),
|
||||
targetEntity.getName(),
|
||||
f.getName()
|
||||
);
|
||||
return f.getName();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static TypeDescription entityType(TypeDescription.Generic type) {
|
||||
if ( type.getSort().isParameterized() ) {
|
||||
if ( type.asErasure().isAssignableTo( Collection.class ) ) {
|
||||
return type.getTypeArguments().get( 0 ).asErasure();
|
||||
}
|
||||
if ( type.asErasure().isAssignableTo( Map.class ) ) {
|
||||
return type.getTypeArguments().get( 1 ).asErasure();
|
||||
}
|
||||
}
|
||||
|
||||
return type.asErasure();
|
||||
}
|
||||
|
||||
private final Implementation delegate;
|
||||
|
||||
private final TypeDescription targetEntity;
|
||||
|
||||
private final TypeDescription targetType;
|
||||
|
||||
private final String mappedBy;
|
||||
|
||||
private BiDirectionalAssociationHandler(
|
||||
Implementation delegate,
|
||||
TypeDescription targetEntity,
|
||||
TypeDescription targetType,
|
||||
String mappedBy) {
|
||||
this.delegate = delegate;
|
||||
this.targetEntity = targetEntity;
|
||||
this.targetType = targetType;
|
||||
this.mappedBy = mappedBy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteCodeAppender appender(Target implementationTarget) {
|
||||
return new WrappingAppender( delegate.appender( implementationTarget ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstrumentedType prepare(InstrumentedType instrumentedType) {
|
||||
return delegate.prepare( instrumentedType );
|
||||
}
|
||||
|
||||
private class WrappingAppender implements ByteCodeAppender {
|
||||
|
||||
private final ByteCodeAppender delegate;
|
||||
|
||||
private WrappingAppender(ByteCodeAppender delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Size apply(
|
||||
MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
|
||||
return delegate.apply( new MethodVisitor( Opcodes.ASM5, methodVisitor ) {
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
|
||||
if ( owner.startsWith( Type.getInternalName( CodeTemplates.class ) ) ) {
|
||||
if ( name.equals( "getter" ) ) {
|
||||
super.visitTypeInsn( Opcodes.CHECKCAST, targetEntity.getInternalName() );
|
||||
super.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
targetEntity.getInternalName(),
|
||||
EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + mappedBy,
|
||||
Type.getMethodDescriptor( Type.getType( targetType.getDescriptor() ) ),
|
||||
false
|
||||
);
|
||||
}
|
||||
else if ( name.equals( "setterSelf" ) ) {
|
||||
super.visitInsn( Opcodes.POP );
|
||||
super.visitTypeInsn( Opcodes.CHECKCAST, targetEntity.getInternalName() );
|
||||
super.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
super.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
targetEntity.getInternalName(),
|
||||
EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy,
|
||||
Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( targetType.getDescriptor() ) ),
|
||||
false
|
||||
);
|
||||
}
|
||||
else if ( name.equals( "setterNull" ) ) {
|
||||
super.visitInsn( Opcodes.POP );
|
||||
super.visitTypeInsn( Opcodes.CHECKCAST, targetEntity.getInternalName() );
|
||||
super.visitInsn( Opcodes.ACONST_NULL );
|
||||
super.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
targetEntity.getInternalName(),
|
||||
EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy,
|
||||
Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( targetType.getDescriptor() ) ),
|
||||
false
|
||||
);
|
||||
}
|
||||
else {
|
||||
throw new EnhancementException( "Unknown template method: " + name );
|
||||
}
|
||||
}
|
||||
else {
|
||||
super.visitMethodInsn( opcode, owner, name, desc, itf );
|
||||
}
|
||||
}
|
||||
}, implementationContext, instrumentedMethod );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.bytebuddy;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedField;
|
||||
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
|
||||
class ByteBuddyEnhancementContext {
|
||||
|
||||
private final EnhancementContext enhancementContext;
|
||||
|
||||
ByteBuddyEnhancementContext(EnhancementContext enhancementContext) {
|
||||
this.enhancementContext = enhancementContext;
|
||||
}
|
||||
|
||||
public ClassLoader getLoadingClassLoader() {
|
||||
return enhancementContext.getLoadingClassLoader();
|
||||
}
|
||||
|
||||
public boolean isEntityClass(TypeDescription classDescriptor) {
|
||||
return enhancementContext.isEntityClass( new UnloadedTypeDescription( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean isCompositeClass(TypeDescription classDescriptor) {
|
||||
return enhancementContext.isCompositeClass( new UnloadedTypeDescription( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean isMappedSuperclassClass(TypeDescription classDescriptor) {
|
||||
return enhancementContext.isMappedSuperclassClass( new UnloadedTypeDescription( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean doBiDirectionalAssociationManagement(FieldDescription field) {
|
||||
return enhancementContext.doBiDirectionalAssociationManagement( new UnloadedFieldDescription( field ) );
|
||||
}
|
||||
|
||||
public boolean doDirtyCheckingInline(TypeDescription classDescriptor) {
|
||||
return enhancementContext.doDirtyCheckingInline( new UnloadedTypeDescription( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean doExtendedEnhancement(TypeDescription classDescriptor) {
|
||||
return enhancementContext.doExtendedEnhancement( new UnloadedTypeDescription( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean hasLazyLoadableAttributes(TypeDescription classDescriptor) {
|
||||
return enhancementContext.hasLazyLoadableAttributes( new UnloadedTypeDescription( classDescriptor ) );
|
||||
}
|
||||
|
||||
public boolean isPersistentField(FieldDescription ctField) {
|
||||
return enhancementContext.isPersistentField( new UnloadedFieldDescription( ctField ) );
|
||||
}
|
||||
|
||||
public FieldDescription[] order(FieldDescription[] persistentFields) {
|
||||
UnloadedField[] unloadedFields = new UnloadedField[persistentFields.length];
|
||||
for ( int i = 0; i < unloadedFields.length; i++ ) {
|
||||
unloadedFields[i] = new UnloadedFieldDescription( persistentFields[i] );
|
||||
}
|
||||
UnloadedField[] ordered = enhancementContext.order( unloadedFields );
|
||||
FieldDescription[] orderedFields = new FieldDescription[persistentFields.length];
|
||||
for ( int i = 0; i < orderedFields.length; i++ ) {
|
||||
orderedFields[i] = ( (UnloadedFieldDescription) ordered[i] ).fieldDescription;
|
||||
}
|
||||
return orderedFields;
|
||||
}
|
||||
|
||||
public boolean isLazyLoadable(FieldDescription field) {
|
||||
return enhancementContext.isLazyLoadable( new UnloadedFieldDescription( field ) );
|
||||
}
|
||||
|
||||
public boolean isMappedCollection(FieldDescription field) {
|
||||
return enhancementContext.isMappedCollection( new UnloadedFieldDescription( field ) );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker;
|
||||
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.EnhancerConstants;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
||||
import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker;
|
||||
import org.hibernate.engine.spi.CompositeOwner;
|
||||
import org.hibernate.engine.spi.CompositeTracker;
|
||||
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
||||
class CodeTemplates {
|
||||
|
||||
static class SetOwner {
|
||||
@Advice.OnMethodEnter
|
||||
static void $$_hibernate_setOwner(
|
||||
@Advice.Argument(0) String name,
|
||||
@Advice.Argument(1) CompositeOwner tracker,
|
||||
@Advice.FieldValue(value = EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, readOnly = false) CompositeOwnerTracker $$_hibernate_compositeOwners) {
|
||||
if ( $$_hibernate_compositeOwners == null ) {
|
||||
$$_hibernate_compositeOwners = new CompositeOwnerTracker();
|
||||
}
|
||||
$$_hibernate_compositeOwners.add( name, tracker );
|
||||
}
|
||||
}
|
||||
|
||||
static class ClearOwner {
|
||||
@Advice.OnMethodEnter
|
||||
static void $$_hibernate_setOwner(
|
||||
@Advice.Argument(0) String name,
|
||||
@Advice.FieldValue(value = EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, readOnly = false) CompositeOwnerTracker $$_hibernate_compositeOwners) {
|
||||
if ( $$_hibernate_compositeOwners != null ) {
|
||||
$$_hibernate_compositeOwners.removeOwner( name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class TrackChange {
|
||||
@Advice.OnMethodEnter
|
||||
static void $$_hibernate_trackChange(
|
||||
@Advice.Argument(0) String name,
|
||||
@Advice.FieldValue(value = EnhancerConstants.TRACKER_FIELD_NAME, readOnly = false) DirtyTracker $$_hibernate_tracker) {
|
||||
if ( $$_hibernate_tracker == null ) {
|
||||
$$_hibernate_tracker = new SimpleFieldTracker();
|
||||
}
|
||||
$$_hibernate_tracker.add( name );
|
||||
}
|
||||
}
|
||||
|
||||
static class GetDirtyAttributes {
|
||||
@Advice.OnMethodExit
|
||||
static void $$_hibernate_getDirtyAttributes(
|
||||
@Advice.This ExtendedSelfDirtinessTracker self,
|
||||
@Advice.Return(readOnly = false) String[] returned,
|
||||
@Advice.FieldValue(value = EnhancerConstants.TRACKER_FIELD_NAME, readOnly = false) DirtyTracker $$_hibernate_tracker,
|
||||
@Advice.FieldValue(value = EnhancerConstants.TRACKER_COLLECTION_NAME, readOnly = false) CollectionTracker $$_hibernate_collectionTracker) {
|
||||
if ( $$_hibernate_collectionTracker == null ) {
|
||||
returned = ( $$_hibernate_tracker == null ) ? new String[0] : $$_hibernate_tracker.get();
|
||||
}
|
||||
else {
|
||||
if ( $$_hibernate_tracker == null ) {
|
||||
$$_hibernate_tracker = new SimpleFieldTracker();
|
||||
}
|
||||
self.$$_hibernate_getCollectionFieldDirtyNames( $$_hibernate_tracker );
|
||||
returned = $$_hibernate_tracker.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class AreCollectionFieldsDirty {
|
||||
@Advice.OnMethodExit
|
||||
static void $$_hibernate_hasDirtyAttributes(
|
||||
@Advice.This ExtendedSelfDirtinessTracker self,
|
||||
@Advice.Return(readOnly = false) boolean returned,
|
||||
@Advice.FieldValue(value = EnhancerConstants.TRACKER_FIELD_NAME, readOnly = false) DirtyTracker $$_hibernate_tracker) {
|
||||
returned = ( $$_hibernate_tracker != null && !$$_hibernate_tracker.isEmpty() ) || self.$$_hibernate_areCollectionFieldsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
static class ClearDirtyAttributes {
|
||||
@Advice.OnMethodEnter
|
||||
static void $$_hibernate_clearDirtyAttributes(
|
||||
@Advice.This ExtendedSelfDirtinessTracker self,
|
||||
@Advice.FieldValue(value = EnhancerConstants.TRACKER_FIELD_NAME, readOnly = false) DirtyTracker $$_hibernate_tracker) {
|
||||
if ( $$_hibernate_tracker != null ) {
|
||||
$$_hibernate_tracker.clear();
|
||||
}
|
||||
self.$$_hibernate_clearDirtyCollectionNames();
|
||||
}
|
||||
}
|
||||
|
||||
static class SuspendDirtyTracking {
|
||||
@Advice.OnMethodEnter
|
||||
static void $$_hibernate_suspendDirtyTracking(
|
||||
@Advice.Argument(0) boolean suspend,
|
||||
@Advice.FieldValue(value = EnhancerConstants.TRACKER_FIELD_NAME, readOnly = false) DirtyTracker $$_hibernate_tracker) {
|
||||
if ( $$_hibernate_tracker == null ) {
|
||||
$$_hibernate_tracker = new SimpleFieldTracker();
|
||||
}
|
||||
$$_hibernate_tracker.suspend( suspend );
|
||||
}
|
||||
}
|
||||
|
||||
static class CollectionAreCollectionFieldsDirty {
|
||||
@Advice.OnMethodExit
|
||||
static void $$_hibernate_areCollectionFieldsDirty(
|
||||
@Advice.Return(readOnly = false) boolean returned,
|
||||
@FieldName String fieldName,
|
||||
@FieldValue Collection<?> collection,
|
||||
@Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) {
|
||||
if ( !returned && $$_hibernate_collectionTracker != null ) {
|
||||
if ( collection == null && $$_hibernate_collectionTracker.getSize( fieldName ) != -1 ) {
|
||||
returned = true;
|
||||
}
|
||||
else if ( collection != null && $$_hibernate_collectionTracker.getSize( fieldName ) != collection.size() ) {
|
||||
returned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class MapAreCollectionFieldsDirty {
|
||||
@Advice.OnMethodExit
|
||||
static void $$_hibernate_areCollectionFieldsDirty(
|
||||
@Advice.Return(readOnly = false) boolean returned,
|
||||
@FieldName String fieldName,
|
||||
@FieldValue Map<?, ?> map,
|
||||
@Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) {
|
||||
if ( !returned && $$_hibernate_collectionTracker != null ) {
|
||||
if ( map == null && $$_hibernate_collectionTracker.getSize( fieldName ) != -1 ) {
|
||||
returned = true;
|
||||
}
|
||||
else if ( map != null && $$_hibernate_collectionTracker.getSize( fieldName ) != map.size() ) {
|
||||
returned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class CollectionGetCollectionFieldDirtyNames {
|
||||
@Advice.OnMethodExit
|
||||
static void $$_hibernate_areCollectionFieldsDirty(
|
||||
@FieldName String fieldName,
|
||||
@FieldValue Collection<?> collection,
|
||||
@Advice.Argument(0) DirtyTracker tracker,
|
||||
@Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) {
|
||||
if ( $$_hibernate_collectionTracker != null ) {
|
||||
if ( collection == null && $$_hibernate_collectionTracker.getSize( fieldName ) != -1 ) {
|
||||
tracker.add( fieldName );
|
||||
}
|
||||
else if ( collection != null && $$_hibernate_collectionTracker.getSize( fieldName ) != collection.size() ) {
|
||||
tracker.add( fieldName );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class MapGetCollectionFieldDirtyNames {
|
||||
@Advice.OnMethodExit
|
||||
static void $$_hibernate_areCollectionFieldsDirty(
|
||||
@FieldName String fieldName,
|
||||
@FieldValue Map<?, ?> map,
|
||||
@Advice.Argument(0) DirtyTracker tracker,
|
||||
@Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) {
|
||||
if ( $$_hibernate_collectionTracker != null ) {
|
||||
if ( map == null && $$_hibernate_collectionTracker.getSize( fieldName ) != -1 ) {
|
||||
tracker.add( fieldName );
|
||||
}
|
||||
else if ( map != null && $$_hibernate_collectionTracker.getSize( fieldName ) != map.size() ) {
|
||||
tracker.add( fieldName );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class CollectionGetCollectionClearDirtyNames {
|
||||
@Advice.OnMethodExit
|
||||
static void $$_hibernate_clearDirtyCollectionNames(
|
||||
@FieldName String fieldName,
|
||||
@FieldValue Collection<?> collection,
|
||||
@Advice.Argument(0) LazyAttributeLoadingInterceptor lazyInterceptor,
|
||||
@Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) {
|
||||
if ( lazyInterceptor == null || lazyInterceptor.isAttributeLoaded( fieldName ) ) {
|
||||
if ( collection == null ) {
|
||||
$$_hibernate_collectionTracker.add( fieldName, -1 );
|
||||
}
|
||||
else {
|
||||
$$_hibernate_collectionTracker.add( fieldName, collection.size() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class MapGetCollectionClearDirtyNames {
|
||||
@Advice.OnMethodExit
|
||||
static void $$_hibernate_clearDirtyCollectionNames(
|
||||
@FieldName String fieldName,
|
||||
@FieldValue Map<?, ?> map,
|
||||
@Advice.Argument(0) LazyAttributeLoadingInterceptor lazyInterceptor,
|
||||
@Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) CollectionTracker $$_hibernate_collectionTracker) {
|
||||
if ( lazyInterceptor == null || lazyInterceptor.isAttributeLoaded( fieldName ) ) {
|
||||
if ( map == null ) {
|
||||
$$_hibernate_collectionTracker.add( fieldName, -1 );
|
||||
}
|
||||
else {
|
||||
$$_hibernate_collectionTracker.add( fieldName, map.size() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class ClearDirtyCollectionNames {
|
||||
@Advice.OnMethodEnter
|
||||
static void $$_hibernate_clearDirtyCollectionNames(
|
||||
@Advice.This ExtendedSelfDirtinessTracker self,
|
||||
@Advice.FieldValue(value = EnhancerConstants.TRACKER_COLLECTION_NAME, readOnly = false) CollectionTracker $$_hibernate_collectionTracker) {
|
||||
if ( $$_hibernate_collectionTracker == null ) {
|
||||
$$_hibernate_collectionTracker = new SimpleCollectionTracker();
|
||||
}
|
||||
self.$$_hibernate_removeDirtyFields( null );
|
||||
}
|
||||
}
|
||||
|
||||
static class InitializeLazyAttributeLoadingInterceptor {
|
||||
@Advice.OnMethodEnter
|
||||
static void $$_hibernate_removeDirtyFields(
|
||||
@Advice.Argument(value = 0, readOnly = false) LazyAttributeLoadingInterceptor lazyInterceptor,
|
||||
@Advice.FieldValue(EnhancerConstants.TRACKER_COLLECTION_NAME) Object $$_hibernate_attributeInterceptor) {
|
||||
if ( $$_hibernate_attributeInterceptor instanceof LazyAttributeLoadingInterceptor ) {
|
||||
lazyInterceptor = (LazyAttributeLoadingInterceptor) $$_hibernate_attributeInterceptor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class CompositeFieldDirtyCheckingHandler {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@FieldName String fieldName, @FieldValue Object field) {
|
||||
if ( field != null ) {
|
||||
( (CompositeTracker) field ).$$_hibernate_clearOwner( fieldName );
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.This CompositeOwner self, @FieldName String fieldName, @FieldValue Object field) {
|
||||
if ( field != null ) {
|
||||
( (CompositeTracker) field ).$$_hibernate_setOwner( fieldName, self );
|
||||
}
|
||||
self.$$_hibernate_trackChange( fieldName );
|
||||
}
|
||||
}
|
||||
|
||||
static class CompositeDirtyCheckingHandler {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.FieldValue(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME) CompositeOwnerTracker $$_hibernate_compositeOwners) {
|
||||
if ( $$_hibernate_compositeOwners != null ) {
|
||||
$$_hibernate_compositeOwners.callOwner( "" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class CompositeOwnerDirtyCheckingHandler {
|
||||
@Advice.OnMethodEnter
|
||||
static void $$_hibernate_trackChange(
|
||||
@Advice.Argument(0) String name,
|
||||
@Advice.FieldValue(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME) CompositeOwnerTracker $$_hibernate_compositeOwners) {
|
||||
if ( $$_hibernate_compositeOwners != null ) {
|
||||
$$_hibernate_compositeOwners.callOwner( "." + name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class OneToOneHandler {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@FieldValue Object field, @Advice.Argument(0) Object argument, @MappedBy String mappedBy) {
|
||||
if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) && argument != null ) {
|
||||
setterNull( field, null );
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.This Object self, @Advice.Argument(0) Object argument, @MappedBy String mappedBy) {
|
||||
if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) && getter( argument ) != self ) {
|
||||
setterSelf( argument, self );
|
||||
}
|
||||
}
|
||||
|
||||
static Object getter(Object target) {
|
||||
// is replaced by the actual method call
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
static void setterNull(Object target, Object argument) {
|
||||
// is replaced by the actual method call
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
static void setterSelf(Object target, Object argument) {
|
||||
// is replaced by the actual method call
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
static class OneToManyOnCollectionHandler {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@FieldValue Collection<?> field, @Advice.Argument(0) Collection<?> argument, @MappedBy String mappedBy) {
|
||||
if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) ) {
|
||||
Object[] array = field.toArray();
|
||||
for ( int i = 0; i < array.length; i++ ) {
|
||||
if ( argument == null || !argument.contains( array[i] ) ) {
|
||||
setterNull( array[i], null );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.This Object self, @Advice.Argument(0) Collection<?> argument, @MappedBy String mappedBy) {
|
||||
if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) ) {
|
||||
Object[] array = argument.toArray();
|
||||
for ( int i = 0; i < array.length; i++ ) {
|
||||
if ( Hibernate.isPropertyInitialized( array[i], mappedBy ) && getter( array[i] ) != self ) {
|
||||
setterSelf( array[i], self );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Object getter(Object target) {
|
||||
// is replaced by the actual method call
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
static void setterNull(Object target, Object argument) {
|
||||
// is replaced by the actual method call
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
static void setterSelf(Object target, Object argument) {
|
||||
// is replaced by the actual method call
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
static class OneToManyOnMapHandler {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@FieldValue Map<?, ?> field, @Advice.Argument(0) Map<?, ?> argument, @MappedBy String mappedBy) {
|
||||
if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) ) {
|
||||
Object[] array = field.values().toArray();
|
||||
for ( int i = 0; i < array.length; i++ ) {
|
||||
if ( argument == null || !argument.values().contains( array[i] ) ) {
|
||||
setterNull( array[i], null );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.This Object self, @Advice.Argument(0) Map<?, ?> argument, @MappedBy String mappedBy) {
|
||||
if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) ) {
|
||||
Object[] array = argument.values().toArray();
|
||||
for ( int i = 0; i < array.length; i++ ) {
|
||||
if ( Hibernate.isPropertyInitialized( array[i], mappedBy ) && getter( array[i] ) != self ) {
|
||||
setterSelf( array[i], self );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Object getter(Object target) {
|
||||
// is replaced with the actual getter call during instrumentation.
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
static void setterNull(Object target, Object argument) {
|
||||
// is replaced with the actual setter call during instrumentation.
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
static void setterSelf(Object target, Object argument) {
|
||||
// is replaced with the actual setter call during instrumentation.
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
static class ManyToOneHandler {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.This Object self, @FieldValue Object field, @MappedBy String mappedBy) {
|
||||
if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) ) {
|
||||
Collection<?> c = getter( field );
|
||||
if ( c != null ) {
|
||||
c.remove( self );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.This Object self, @Advice.Argument(0) Object argument, @MappedBy String mappedBy) {
|
||||
if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) ) {
|
||||
Collection<Object> c = getter( argument );
|
||||
if ( c != null && !c.contains( self ) ) {
|
||||
c.add( self );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Collection<Object> getter(Object target) {
|
||||
// is replaced by the actual method call
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
static class ManyToManyHandler {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.This Object self, @FieldValue Collection<?> field, @Advice.Argument(0) Collection<?> argument, @MappedBy String mappedBy) {
|
||||
if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) ) {
|
||||
Object[] array = field.toArray();
|
||||
for ( int i = 0; i < array.length; i++ ) {
|
||||
if ( argument == null || !argument.contains( array[i] ) ) {
|
||||
getter( array[i] ).remove( self );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit
|
||||
static void exit(@Advice.This Object self, @Advice.Argument(0) Collection<?> argument, @MappedBy String mappedBy) {
|
||||
if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) ) {
|
||||
Object[] array = argument.toArray();
|
||||
for ( int i = 0; i < array.length; i++ ) {
|
||||
if ( Hibernate.isPropertyInitialized( array[i], mappedBy ) ) {
|
||||
Collection<Object> c = getter( array[i] );
|
||||
if ( c != self && c != null ) {
|
||||
c.add( self );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Collection<Object> getter(Object self) {
|
||||
// is replaced by the actual method call
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface FieldName {
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface FieldValue {
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MappedBy {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,443 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker;
|
||||
import org.hibernate.bytecode.enhance.spi.CollectionTracker;
|
||||
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.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
||||
import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker;
|
||||
import org.hibernate.engine.spi.CompositeOwner;
|
||||
import org.hibernate.engine.spi.CompositeTracker;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.Managed;
|
||||
import org.hibernate.engine.spi.ManagedComposite;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
import org.hibernate.engine.spi.ManagedMappedSuperclass;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.annotation.AnnotationDescription;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.method.MethodList;
|
||||
import net.bytebuddy.description.modifier.FieldManifestation;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.description.type.TypeDefinition;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.ClassFileLocator;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.dynamic.scaffold.MethodGraph;
|
||||
import net.bytebuddy.dynamic.scaffold.TypeValidation;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.FixedValue;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.implementation.StubMethod;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
import net.bytebuddy.utility.StreamDrainer;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isGetter;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
public class EnhancerImpl implements Enhancer {
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class );
|
||||
|
||||
protected final ByteBuddyEnhancementContext enhancementContext;
|
||||
|
||||
private final TypePool 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 ByteBuddyEnhancementContext( enhancementContext );
|
||||
classPool = buildClassPool( this.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 {
|
||||
try {
|
||||
final TypeDescription managedCtClass = classPool.describe( className ).resolve();
|
||||
DynamicType.Builder<?> builder = doEnhance(
|
||||
new ByteBuddy().with( TypeValidation.DISABLED ).redefine( managedCtClass, ClassFileLocator.Simple.of( className, originalBytes ) ),
|
||||
managedCtClass
|
||||
);
|
||||
if ( builder == null ) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return builder.make().getBytes();
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
log.unableToBuildEnhancementMetamodel( className );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] enhance(File javaClassFile) throws EnhancementException, IOException {
|
||||
String name = javaClassFile.getName().substring( 0, javaClassFile.getName().length() - ".class".length() );
|
||||
InputStream inputStream = new FileInputStream( javaClassFile );
|
||||
try {
|
||||
return enhance( name, StreamDrainer.DEFAULT.drain( inputStream ) );
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private TypePool buildClassPool(final ByteBuddyEnhancementContext enhancementContext) {
|
||||
return TypePool.Default.WithLazyResolution.of( enhancementContext.getLoadingClassLoader() );
|
||||
}
|
||||
|
||||
private DynamicType.Builder<?> doEnhance(DynamicType.Builder<?> builder, TypeDescription managedCtClass) {
|
||||
// can't effectively enhance interfaces
|
||||
if ( managedCtClass.isInterface() ) {
|
||||
log.debugf( "Skipping enhancement of [%s]: it's an interface!", managedCtClass.getName() );
|
||||
return null;
|
||||
}
|
||||
// skip already enhanced classes
|
||||
if ( alreadyEnhanced( managedCtClass ) ) {
|
||||
log.debugf( "Skipping enhancement of [%s]: already enhanced", managedCtClass.getName() );
|
||||
return null;
|
||||
}
|
||||
|
||||
PersistentAttributeTransformer transformer = PersistentAttributeTransformer.collectPersistentFields( managedCtClass, enhancementContext, classPool );
|
||||
|
||||
if ( enhancementContext.isEntityClass( managedCtClass ) ) {
|
||||
log.infof( "Enhancing [%s] as Entity", managedCtClass.getName() );
|
||||
builder = builder.implement( ManagedEntity.class )
|
||||
.defineMethod( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME, Object.class, Visibility.PUBLIC )
|
||||
.intercept( FixedValue.self() );
|
||||
|
||||
builder = addFieldWithGetterAndSetter(
|
||||
builder,
|
||||
EntityEntry.class,
|
||||
EnhancerConstants.ENTITY_ENTRY_FIELD_NAME,
|
||||
EnhancerConstants.ENTITY_ENTRY_GETTER_NAME,
|
||||
EnhancerConstants.ENTITY_ENTRY_SETTER_NAME
|
||||
);
|
||||
builder = addFieldWithGetterAndSetter(
|
||||
builder,
|
||||
ManagedEntity.class,
|
||||
EnhancerConstants.PREVIOUS_FIELD_NAME,
|
||||
EnhancerConstants.PREVIOUS_GETTER_NAME,
|
||||
EnhancerConstants.PREVIOUS_SETTER_NAME
|
||||
);
|
||||
builder = addFieldWithGetterAndSetter(
|
||||
builder,
|
||||
ManagedEntity.class,
|
||||
EnhancerConstants.NEXT_FIELD_NAME,
|
||||
EnhancerConstants.NEXT_GETTER_NAME,
|
||||
EnhancerConstants.NEXT_SETTER_NAME
|
||||
);
|
||||
|
||||
builder = addInterceptorHandling( builder, managedCtClass );
|
||||
|
||||
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
builder = builder.implement( ExtendedSelfDirtinessTracker.class )
|
||||
.defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldManifestation.TRANSIENT, Visibility.PRIVATE )
|
||||
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
||||
.defineField( EnhancerConstants.TRACKER_COLLECTION_NAME, CollectionTracker.class, FieldManifestation.TRANSIENT, Visibility.PRIVATE )
|
||||
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
||||
.defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC )
|
||||
.withParameters( String.class )
|
||||
.intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) )
|
||||
.defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC )
|
||||
.intercept( Advice.to( CodeTemplates.GetDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) )
|
||||
.defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC )
|
||||
.intercept( Advice.to( CodeTemplates.AreCollectionFieldsDirty.class ).wrap( StubMethod.INSTANCE ) )
|
||||
.defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC )
|
||||
.intercept( Advice.to( CodeTemplates.ClearDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) )
|
||||
.defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC )
|
||||
.withParameters( boolean.class )
|
||||
.intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) )
|
||||
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC )
|
||||
.intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) );
|
||||
|
||||
Implementation isDirty = StubMethod.INSTANCE, getDirtyNames = StubMethod.INSTANCE, clearDirtyNames = StubMethod.INSTANCE;
|
||||
for ( FieldDescription collectionField : collectCollectionFields( managedCtClass ) ) {
|
||||
if ( !enhancementContext.isMappedCollection( collectionField ) ) {
|
||||
if ( collectionField.getType().asErasure().isAssignableTo( Map.class ) ) {
|
||||
isDirty = Advice.withCustomMapping()
|
||||
.bind( CodeTemplates.FieldName.class, collectionField.getName() )
|
||||
.bind( CodeTemplates.FieldValue.class, collectionField )
|
||||
.to( CodeTemplates.MapAreCollectionFieldsDirty.class )
|
||||
.wrap( isDirty );
|
||||
getDirtyNames = Advice.withCustomMapping()
|
||||
.bind( CodeTemplates.FieldName.class, collectionField.getName() )
|
||||
.bind( CodeTemplates.FieldValue.class, collectionField )
|
||||
.to( CodeTemplates.MapGetCollectionFieldDirtyNames.class )
|
||||
.wrap( getDirtyNames );
|
||||
clearDirtyNames = Advice.withCustomMapping()
|
||||
.bind( CodeTemplates.FieldName.class, collectionField.getName() )
|
||||
.bind( CodeTemplates.FieldValue.class, collectionField )
|
||||
.to( CodeTemplates.MapGetCollectionClearDirtyNames.class )
|
||||
.wrap( clearDirtyNames );
|
||||
}
|
||||
else {
|
||||
isDirty = Advice.withCustomMapping()
|
||||
.bind( CodeTemplates.FieldName.class, collectionField.getName() )
|
||||
.bind( CodeTemplates.FieldValue.class, collectionField )
|
||||
.to( CodeTemplates.CollectionAreCollectionFieldsDirty.class )
|
||||
.wrap( isDirty );
|
||||
getDirtyNames = Advice.withCustomMapping()
|
||||
.bind( CodeTemplates.FieldName.class, collectionField.getName() )
|
||||
.bind( CodeTemplates.FieldValue.class, collectionField )
|
||||
.to( CodeTemplates.CollectionGetCollectionFieldDirtyNames.class )
|
||||
.wrap( getDirtyNames );
|
||||
clearDirtyNames = Advice.withCustomMapping()
|
||||
.bind( CodeTemplates.FieldName.class, collectionField.getName() )
|
||||
.bind( CodeTemplates.FieldValue.class, collectionField )
|
||||
.to( CodeTemplates.CollectionGetCollectionClearDirtyNames.class )
|
||||
.wrap( clearDirtyNames );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) {
|
||||
clearDirtyNames = Advice.to( CodeTemplates.InitializeLazyAttributeLoadingInterceptor.class ).wrap( clearDirtyNames );
|
||||
}
|
||||
|
||||
builder = builder.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, boolean.class, Visibility.PUBLIC )
|
||||
.intercept( isDirty )
|
||||
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, void.class, Visibility.PUBLIC )
|
||||
.withParameters( DirtyTracker.class )
|
||||
.intercept( getDirtyNames )
|
||||
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, void.class, Visibility.PUBLIC )
|
||||
.intercept( Advice.withCustomMapping().to( CodeTemplates.ClearDirtyCollectionNames.class ).wrap( StubMethod.INSTANCE ) )
|
||||
.defineMethod( ExtendedSelfDirtinessTracker.REMOVE_DIRTY_FIELDS_NAME, void.class, Visibility.PUBLIC )
|
||||
.withParameters( LazyAttributeLoadingInterceptor.class )
|
||||
.intercept( clearDirtyNames );
|
||||
}
|
||||
|
||||
return transformer.applyTo( builder, false );
|
||||
}
|
||||
else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
log.infof( "Enhancing [%s] as Composite", managedCtClass.getName() );
|
||||
|
||||
builder = builder.implement( ManagedComposite.class );
|
||||
builder = addInterceptorHandling( builder, managedCtClass );
|
||||
|
||||
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
builder = builder.implement( CompositeTracker.class )
|
||||
.defineField(
|
||||
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME,
|
||||
CompositeOwnerTracker.class,
|
||||
FieldManifestation.TRANSIENT,
|
||||
Visibility.PRIVATE
|
||||
)
|
||||
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
||||
.defineMethod(
|
||||
EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
|
||||
void.class,
|
||||
Visibility.PUBLIC
|
||||
)
|
||||
.withParameters( String.class, CompositeOwner.class )
|
||||
.intercept( Advice.to( CodeTemplates.SetOwner.class ).wrap( StubMethod.INSTANCE ) )
|
||||
.defineMethod(
|
||||
EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER,
|
||||
void.class,
|
||||
Visibility.PUBLIC
|
||||
)
|
||||
.withParameters( String.class )
|
||||
.intercept( Advice.to( CodeTemplates.ClearOwner.class ).wrap( StubMethod.INSTANCE ) );
|
||||
}
|
||||
|
||||
return transformer.applyTo( builder, false );
|
||||
}
|
||||
else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
|
||||
log.infof( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() );
|
||||
return transformer.applyTo( builder, true );
|
||||
}
|
||||
else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
|
||||
log.infof( "Extended enhancement of [%s]", managedCtClass.getName() );
|
||||
return transformer.applyExtended( builder );
|
||||
}
|
||||
else {
|
||||
log.debugf( "Skipping enhancement of [%s]: not entity or composite", managedCtClass.getName() );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean alreadyEnhanced(TypeDescription managedCtClass) {
|
||||
if ( !managedCtClass.isAssignableTo( Managed.class ) ) {
|
||||
return false;
|
||||
}
|
||||
// HHH-10977 - When a mapped superclass gets enhanced before a subclassing entity, the entity does not get enhanced, but it implements the Managed interface
|
||||
return enhancementContext.isEntityClass( managedCtClass ) && managedCtClass.isAssignableTo( ManagedEntity.class )
|
||||
|| enhancementContext.isCompositeClass( managedCtClass ) && managedCtClass.isAssignableTo( ManagedComposite.class )
|
||||
|| enhancementContext.isMappedSuperclassClass( managedCtClass ) && managedCtClass.isAssignableTo( ManagedMappedSuperclass.class );
|
||||
}
|
||||
|
||||
private DynamicType.Builder<?> addInterceptorHandling(DynamicType.Builder<?> builder, TypeDescription managedCtClass) {
|
||||
// interceptor handling is only needed if class has lazy-loadable attributes
|
||||
if ( enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) {
|
||||
log.debugf( "Weaving in PersistentAttributeInterceptable implementation on [%s]", managedCtClass.getName() );
|
||||
|
||||
builder = builder.implement( PersistentAttributeInterceptable.class );
|
||||
|
||||
builder = addFieldWithGetterAndSetter(
|
||||
builder,
|
||||
PersistentAttributeInterceptor.class,
|
||||
EnhancerConstants.INTERCEPTOR_FIELD_NAME,
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
|
||||
EnhancerConstants.INTERCEPTOR_SETTER_NAME
|
||||
);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static DynamicType.Builder<?> addFieldWithGetterAndSetter(
|
||||
DynamicType.Builder<?> builder,
|
||||
Class<?> type,
|
||||
String fieldName,
|
||||
String getterName,
|
||||
String setterName) {
|
||||
return builder.defineField( fieldName, type, Visibility.PRIVATE, FieldManifestation.TRANSIENT )
|
||||
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
||||
.defineMethod( getterName, type, Visibility.PUBLIC )
|
||||
.intercept( FieldAccessor.ofField( fieldName ) )
|
||||
.defineMethod( setterName, void.class, Visibility.PUBLIC )
|
||||
.withParameters( type )
|
||||
.intercept( FieldAccessor.ofField( fieldName ) );
|
||||
}
|
||||
|
||||
private List<FieldDescription> collectCollectionFields(TypeDescription managedCtClass) {
|
||||
List<FieldDescription> collectionList = new ArrayList<>();
|
||||
|
||||
for ( FieldDescription 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 ) ) {
|
||||
if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) {
|
||||
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<FieldDescription> collectInheritCollectionFields(TypeDefinition managedCtClass) {
|
||||
TypeDefinition managedCtSuperclass = managedCtClass.getSuperClass();
|
||||
if ( managedCtSuperclass == null || managedCtSuperclass.represents( Object.class ) ) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) {
|
||||
return collectInheritCollectionFields( managedCtSuperclass.asErasure() );
|
||||
}
|
||||
List<FieldDescription> collectionList = new ArrayList<FieldDescription>();
|
||||
|
||||
for ( FieldDescription ctField : managedCtSuperclass.getDeclaredFields() ) {
|
||||
if ( !Modifier.isStatic( ctField.getModifiers() ) && enhancementContext.isPersistentField( ctField ) ) {
|
||||
if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) {
|
||||
collectionList.add( ctField );
|
||||
}
|
||||
}
|
||||
}
|
||||
collectionList.addAll( collectInheritCollectionFields( managedCtSuperclass ) );
|
||||
return collectionList;
|
||||
}
|
||||
|
||||
static String capitalize(String value) {
|
||||
return Character.toUpperCase( value.charAt( 0 ) ) + value.substring( 1 );
|
||||
}
|
||||
|
||||
static boolean isAnnotationPresent(FieldDescription fieldDescription, Class<? extends Annotation> type) {
|
||||
return getAnnotation( fieldDescription, type ) != null;
|
||||
}
|
||||
|
||||
static <T extends Annotation> AnnotationDescription.Loadable<T> getAnnotation(FieldDescription fieldDescription, Class<T> type) {
|
||||
AnnotationDescription.Loadable<Access> access = fieldDescription.getDeclaringType().asErasure().getDeclaredAnnotations().ofType( Access.class );
|
||||
if ( access != null && access.loadSilent().value() == AccessType.PROPERTY ) {
|
||||
MethodDescription getter = getterOf( fieldDescription );
|
||||
if ( getter == null ) {
|
||||
return fieldDescription.getDeclaredAnnotations().ofType( type );
|
||||
}
|
||||
else {
|
||||
return getter.getDeclaredAnnotations().ofType( type );
|
||||
}
|
||||
}
|
||||
else if ( access != null && access.loadSilent().value() == AccessType.FIELD ) {
|
||||
return fieldDescription.getDeclaredAnnotations().ofType( type );
|
||||
}
|
||||
else {
|
||||
MethodDescription getter = getterOf( fieldDescription );
|
||||
if ( getter != null ) {
|
||||
AnnotationDescription.Loadable<T> annotationDescription = getter.getDeclaredAnnotations().ofType( type );
|
||||
if ( annotationDescription != null ) {
|
||||
return annotationDescription;
|
||||
}
|
||||
}
|
||||
return fieldDescription.getDeclaredAnnotations().ofType( type );
|
||||
}
|
||||
}
|
||||
|
||||
static MethodDescription getterOf(FieldDescription persistentField) {
|
||||
MethodList<?> methodList = MethodGraph.Compiler.DEFAULT.compile( persistentField.getDeclaringType().asErasure() )
|
||||
.listNodes()
|
||||
.asMethodList()
|
||||
.filter( isGetter(persistentField.getName() ) );
|
||||
if ( methodList.size() == 1 ) {
|
||||
return methodList.getOnly();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import javax.persistence.Id;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
import net.bytebuddy.ClassFileVersion;
|
||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.field.FieldList;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.jar.asm.Type;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.hasDescriptor;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
class FieldAccessEnhancer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( FieldAccessEnhancer.class );
|
||||
|
||||
private final TypeDescription managedCtClass;
|
||||
|
||||
private final ByteBuddyEnhancementContext enhancementContext;
|
||||
|
||||
private final TypePool classPool;
|
||||
|
||||
FieldAccessEnhancer(TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, TypePool classPool) {
|
||||
this.managedCtClass = managedCtClass;
|
||||
this.enhancementContext = enhancementContext;
|
||||
this.classPool = classPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor wrap(
|
||||
TypeDescription instrumentedType,
|
||||
MethodDescription.InDefinedShape instrumentedMethod,
|
||||
MethodVisitor methodVisitor,
|
||||
Implementation.Context implementationContext,
|
||||
TypePool typePool,
|
||||
int writerFlags,
|
||||
int readerFlags) {
|
||||
return new MethodVisitor( Opcodes.ASM5, methodVisitor ) {
|
||||
@Override
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
|
||||
if ( opcode != Opcodes.GETFIELD && opcode != Opcodes.PUTFIELD ) {
|
||||
super.visitFieldInsn( opcode, owner, name, desc );
|
||||
return;
|
||||
}
|
||||
|
||||
FieldDescription field = findField( owner, name, desc );
|
||||
|
||||
if ( ( enhancementContext.isEntityClass( field.getDeclaringType().asErasure() )
|
||||
|| enhancementContext.isCompositeClass( field.getDeclaringType().asErasure() ) )
|
||||
&& !field.getType().asErasure().equals( managedCtClass )
|
||||
&& enhancementContext.isPersistentField( field )
|
||||
&& !EnhancerImpl.isAnnotationPresent( field, Id.class )
|
||||
&& !field.getName().equals( "this$0" ) ) {
|
||||
|
||||
log.debugf(
|
||||
"Extended enhancement: Transforming access to field [%s.%s] from method [%s#%s]",
|
||||
field.getType().asErasure(),
|
||||
field.getName(),
|
||||
field.getName(),
|
||||
name
|
||||
);
|
||||
|
||||
switch ( opcode ) {
|
||||
case Opcodes.GETFIELD:
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
owner,
|
||||
EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + name,
|
||||
Type.getMethodDescriptor( Type.getType( desc ) ),
|
||||
false
|
||||
);
|
||||
return;
|
||||
case Opcodes.PUTFIELD:
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
owner,
|
||||
EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + name,
|
||||
Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( desc ) ),
|
||||
false
|
||||
);
|
||||
return;
|
||||
default:
|
||||
throw new EnhancementException( "Unexpected opcode: " + opcode );
|
||||
}
|
||||
}
|
||||
else {
|
||||
super.visitFieldInsn( opcode, owner, name, desc );
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private FieldDescription findField(String owner, String name, String desc) {
|
||||
TypePool.Resolution resolution = classPool.describe( owner.replace( '/', '.' ) );
|
||||
if ( !resolution.isResolved() ) {
|
||||
final String msg = String.format(
|
||||
"Unable to perform extended enhancement - Unable to locate [%s]",
|
||||
owner.replace( '/', '.' )
|
||||
);
|
||||
throw new EnhancementException( msg );
|
||||
}
|
||||
FieldList<?> fields = resolution.resolve().getDeclaredFields().filter( named( name ).and( hasDescriptor( desc ) ) );
|
||||
if ( fields.size() != 1 ) {
|
||||
final String msg = String.format(
|
||||
"Unable to perform extended enhancement - No unique field [%s] defined by [%s]",
|
||||
name,
|
||||
owner.replace( '/', '.' )
|
||||
);
|
||||
throw new EnhancementException( msg );
|
||||
}
|
||||
return fields.getOnly();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||
|
||||
import net.bytebuddy.ClassFileVersion;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
|
||||
import net.bytebuddy.jar.asm.Label;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.jar.asm.Type;
|
||||
|
||||
abstract class FieldReaderAppender implements ByteCodeAppender {
|
||||
|
||||
protected final TypeDescription managedCtClass;
|
||||
|
||||
protected final FieldDescription persistentField;
|
||||
|
||||
private FieldReaderAppender(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentField) {
|
||||
this.managedCtClass = managedCtClass;
|
||||
this.persistentField = persistentField;
|
||||
}
|
||||
|
||||
static ByteCodeAppender of(TypeDescription managedCtClass, FieldDescription persistentField) {
|
||||
if ( !persistentField.isVisibleTo( managedCtClass ) ) {
|
||||
return new MethodDispatching( managedCtClass, persistentField.asDefined() );
|
||||
}
|
||||
else {
|
||||
return new FieldWriting( managedCtClass, persistentField.asDefined() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Size apply(
|
||||
MethodVisitor methodVisitor,
|
||||
Implementation.Context implementationContext,
|
||||
MethodDescription instrumentedMethod) {
|
||||
TypeDescription dispatcherType = persistentField.getType().isPrimitive()
|
||||
? persistentField.getType().asErasure()
|
||||
: TypeDescription.OBJECT;
|
||||
// if ( this.$$_hibernate_getInterceptor() != null )
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
managedCtClass.getInternalName(),
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
|
||||
Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ),
|
||||
false
|
||||
);
|
||||
Label skip = new Label();
|
||||
methodVisitor.visitJumpInsn( Opcodes.IFNULL, skip );
|
||||
// this (for field write)
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
// this.$$_hibernate_getInterceptor();
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
managedCtClass.getInternalName(),
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
|
||||
Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ),
|
||||
false
|
||||
);
|
||||
// .readXXX( self, fieldName, field );
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
methodVisitor.visitLdcInsn( persistentField.getName() );
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
fieldRead( methodVisitor );
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEINTERFACE,
|
||||
Type.getInternalName( PersistentAttributeInterceptor.class ),
|
||||
"read" + EnhancerImpl.capitalize( dispatcherType.getSimpleName() ),
|
||||
Type.getMethodDescriptor(
|
||||
Type.getType( dispatcherType.getDescriptor() ),
|
||||
Type.getType( Object.class ),
|
||||
Type.getType( String.class ),
|
||||
Type.getType( dispatcherType.getDescriptor() )
|
||||
),
|
||||
true
|
||||
);
|
||||
// field = (cast) XXX
|
||||
if ( !dispatcherType.isPrimitive() ) {
|
||||
methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentField.getType().asErasure().getInternalName() );
|
||||
}
|
||||
fieldWrite( methodVisitor );
|
||||
// end if
|
||||
methodVisitor.visitLabel( skip );
|
||||
if ( implementationContext.getClassFileVersion().isAtLeast( ClassFileVersion.JAVA_V6 ) ) {
|
||||
methodVisitor.visitFrame( Opcodes.F_SAME, 0, null, 0, null );
|
||||
}
|
||||
// return field
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
fieldRead( methodVisitor );
|
||||
methodVisitor.visitInsn( Type.getType( persistentField.getType().asErasure().getDescriptor() ).getOpcode( Opcodes.IRETURN ) );
|
||||
return new Size( 4 + persistentField.getType().getStackSize().getSize(), instrumentedMethod.getStackSize() );
|
||||
}
|
||||
|
||||
protected abstract void fieldRead(MethodVisitor methodVisitor);
|
||||
|
||||
protected abstract void fieldWrite(MethodVisitor methodVisitor);
|
||||
|
||||
private static class FieldWriting extends FieldReaderAppender {
|
||||
|
||||
private FieldWriting(TypeDescription managedCtClass, FieldDescription.InDefinedShape fieldDescription) {
|
||||
super( managedCtClass, fieldDescription );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fieldRead(MethodVisitor methodVisitor) {
|
||||
methodVisitor.visitFieldInsn(
|
||||
Opcodes.GETFIELD,
|
||||
persistentField.getDeclaringType().asErasure().getInternalName(),
|
||||
persistentField.getInternalName(),
|
||||
persistentField.getDescriptor()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fieldWrite(MethodVisitor methodVisitor) {
|
||||
methodVisitor.visitFieldInsn(
|
||||
Opcodes.PUTFIELD,
|
||||
persistentField.getDeclaringType().asErasure().getInternalName(),
|
||||
persistentField.getInternalName(),
|
||||
persistentField.getDescriptor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static class MethodDispatching extends FieldReaderAppender {
|
||||
|
||||
private MethodDispatching(TypeDescription managedCtClass, FieldDescription.InDefinedShape fieldDescription) {
|
||||
super( managedCtClass, fieldDescription );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fieldRead(MethodVisitor methodVisitor) {
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKESPECIAL,
|
||||
managedCtClass.getSuperClass().asErasure().getInternalName(),
|
||||
EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName(),
|
||||
Type.getMethodDescriptor( Type.getType( persistentField.getType().asErasure().getDescriptor() ) ),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fieldWrite(MethodVisitor methodVisitor) {
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKESPECIAL,
|
||||
managedCtClass.getSuperClass().asErasure().getInternalName(),
|
||||
EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + persistentField.getName(),
|
||||
Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( persistentField.getType().asErasure().getDescriptor() ) ),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||
|
||||
import net.bytebuddy.ClassFileVersion;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
|
||||
import net.bytebuddy.jar.asm.Label;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.jar.asm.Type;
|
||||
|
||||
abstract class FieldWriterAppender implements ByteCodeAppender {
|
||||
|
||||
protected final TypeDescription managedCtClass;
|
||||
|
||||
protected final FieldDescription.InDefinedShape persistentField;
|
||||
|
||||
private FieldWriterAppender(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentField) {
|
||||
this.managedCtClass = managedCtClass;
|
||||
this.persistentField = persistentField;
|
||||
}
|
||||
|
||||
static ByteCodeAppender of(TypeDescription managedCtClass, FieldDescription persistentField) {
|
||||
if ( !persistentField.isVisibleTo( managedCtClass ) ) {
|
||||
return new MethodDispatching( managedCtClass, persistentField.asDefined() );
|
||||
}
|
||||
else {
|
||||
return new FieldWriting( managedCtClass, persistentField.asDefined() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Size apply(
|
||||
MethodVisitor methodVisitor,
|
||||
Implementation.Context implementationContext,
|
||||
MethodDescription instrumentedMethod) {
|
||||
TypeDescription dispatcherType = persistentField.getType().isPrimitive()
|
||||
? persistentField.getType().asErasure()
|
||||
: TypeDescription.OBJECT;
|
||||
// if ( this.$$_hibernate_getInterceptor() != null )
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
managedCtClass.getInternalName(),
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
|
||||
Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ),
|
||||
false
|
||||
);
|
||||
Label noInterceptor = new Label();
|
||||
methodVisitor.visitJumpInsn( Opcodes.IFNULL, noInterceptor );
|
||||
// this (for field write)
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
// this.$$_hibernate_getInterceptor();
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
managedCtClass.getInternalName(),
|
||||
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
|
||||
Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ),
|
||||
false
|
||||
);
|
||||
// .writeXXX( self, fieldName, field, arg1 );
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
methodVisitor.visitLdcInsn( persistentField.getName() );
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
fieldRead( methodVisitor );
|
||||
methodVisitor.visitVarInsn( Type.getType( dispatcherType.getDescriptor() ).getOpcode( Opcodes.ILOAD ), 1 );
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEINTERFACE,
|
||||
Type.getInternalName( PersistentAttributeInterceptor.class ),
|
||||
"write" + EnhancerImpl.capitalize( dispatcherType.getSimpleName() ),
|
||||
Type.getMethodDescriptor(
|
||||
Type.getType( dispatcherType.getDescriptor() ),
|
||||
Type.getType( Object.class ),
|
||||
Type.getType( String.class ),
|
||||
Type.getType( dispatcherType.getDescriptor() ),
|
||||
Type.getType( dispatcherType.getDescriptor() )
|
||||
),
|
||||
true
|
||||
);
|
||||
// arg1 = (cast) XXX
|
||||
if ( !dispatcherType.isPrimitive() ) {
|
||||
methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentField.getType().asErasure().getInternalName() );
|
||||
}
|
||||
fieldWrite( methodVisitor );
|
||||
// return
|
||||
methodVisitor.visitInsn( Opcodes.RETURN );
|
||||
// else
|
||||
methodVisitor.visitLabel( noInterceptor );
|
||||
if ( implementationContext.getClassFileVersion().isAtLeast( ClassFileVersion.JAVA_V6 ) ) {
|
||||
methodVisitor.visitFrame( Opcodes.F_SAME, 0, null, 0, null );
|
||||
}
|
||||
// this (for field write)
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
// arg1 = (cast) XXX
|
||||
methodVisitor.visitVarInsn( Type.getType( dispatcherType.getDescriptor() ).getOpcode( Opcodes.ILOAD ), 1 );
|
||||
if ( !dispatcherType.isPrimitive() ) {
|
||||
methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentField.getType().asErasure().getInternalName() );
|
||||
}
|
||||
fieldWrite( methodVisitor );
|
||||
// return
|
||||
methodVisitor.visitInsn( Opcodes.RETURN );
|
||||
return new Size( 4 + 2 * persistentField.getType().getStackSize().getSize(), instrumentedMethod.getStackSize() );
|
||||
}
|
||||
|
||||
protected abstract void fieldRead(MethodVisitor methodVisitor);
|
||||
|
||||
protected abstract void fieldWrite(MethodVisitor methodVisitor);
|
||||
|
||||
private static class FieldWriting extends FieldWriterAppender {
|
||||
|
||||
private FieldWriting(TypeDescription managedCtClass, FieldDescription.InDefinedShape fieldDescription) {
|
||||
super( managedCtClass, fieldDescription );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fieldRead(MethodVisitor methodVisitor) {
|
||||
methodVisitor.visitFieldInsn(
|
||||
Opcodes.GETFIELD,
|
||||
persistentField.getDeclaringType().asErasure().getInternalName(),
|
||||
persistentField.getInternalName(),
|
||||
persistentField.getDescriptor()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fieldWrite(MethodVisitor methodVisitor) {
|
||||
methodVisitor.visitFieldInsn(
|
||||
Opcodes.PUTFIELD,
|
||||
persistentField.getDeclaringType().asErasure().getInternalName(),
|
||||
persistentField.getInternalName(),
|
||||
persistentField.getDescriptor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static class MethodDispatching extends FieldWriterAppender {
|
||||
|
||||
private MethodDispatching(TypeDescription managedCtClass, FieldDescription.InDefinedShape fieldDescription) {
|
||||
super( managedCtClass, fieldDescription );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fieldRead(MethodVisitor methodVisitor) {
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKESPECIAL,
|
||||
managedCtClass.getSuperClass().asErasure().getInternalName(),
|
||||
EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName(),
|
||||
Type.getMethodDescriptor( Type.getType( persistentField.getType().asErasure().getDescriptor() ) ),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fieldWrite(MethodVisitor methodVisitor) {
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKESPECIAL,
|
||||
managedCtClass.getSuperClass().asErasure().getInternalName(),
|
||||
EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + persistentField.getName(),
|
||||
Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( persistentField.getType().asErasure().getDescriptor() ) ),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import java.util.Collection;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.EmbeddedId;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.internal.util.compare.EqualsHelper;
|
||||
|
||||
import net.bytebuddy.ClassFileVersion;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
|
||||
import net.bytebuddy.jar.asm.Label;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.jar.asm.Type;
|
||||
|
||||
class InlineDirtyCheckingHandler implements Implementation, ByteCodeAppender {
|
||||
|
||||
private final Implementation delegate;
|
||||
|
||||
private final TypeDescription managedCtClass;
|
||||
|
||||
private final FieldDescription.InDefinedShape persistentField;
|
||||
|
||||
private InlineDirtyCheckingHandler(Implementation delegate, TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentField) {
|
||||
this.delegate = delegate;
|
||||
this.managedCtClass = managedCtClass;
|
||||
this.persistentField = persistentField;
|
||||
}
|
||||
|
||||
static Implementation wrap(
|
||||
TypeDescription managedCtClass,
|
||||
ByteBuddyEnhancementContext enhancementContext,
|
||||
FieldDescription persistentField,
|
||||
Implementation implementation) {
|
||||
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
|
||||
if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
implementation = Advice.to( CodeTemplates.CompositeDirtyCheckingHandler.class ).wrap( implementation );
|
||||
}
|
||||
else if ( !EnhancerImpl.isAnnotationPresent( persistentField, Id.class )
|
||||
&& !EnhancerImpl.isAnnotationPresent( persistentField, EmbeddedId.class )
|
||||
&& !( persistentField.getType().asErasure().isAssignableTo( Collection.class )
|
||||
&& enhancementContext.isMappedCollection( persistentField ) ) ) {
|
||||
implementation = new InlineDirtyCheckingHandler( implementation, managedCtClass, persistentField.asDefined() );
|
||||
}
|
||||
|
||||
if ( enhancementContext.isCompositeClass( persistentField.getType().asErasure() )
|
||||
&& EnhancerImpl.isAnnotationPresent( persistentField, Embedded.class ) ) {
|
||||
|
||||
implementation = Advice.withCustomMapping()
|
||||
.bind( CodeTemplates.FieldValue.class, persistentField )
|
||||
.bind( CodeTemplates.FieldName.class, persistentField.getName() )
|
||||
.to( CodeTemplates.CompositeFieldDirtyCheckingHandler.class )
|
||||
.wrap( implementation );
|
||||
}
|
||||
}
|
||||
return implementation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteCodeAppender appender(Target implementationTarget) {
|
||||
return new ByteCodeAppender.Compound( this, delegate.appender( implementationTarget ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstrumentedType prepare(InstrumentedType instrumentedType) {
|
||||
return delegate.prepare( instrumentedType );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Size apply(
|
||||
MethodVisitor methodVisitor,
|
||||
Context implementationContext,
|
||||
MethodDescription instrumentedMethod) {
|
||||
// if (arg != field) {
|
||||
methodVisitor.visitVarInsn( Type.getType( persistentField.getType().asErasure().getDescriptor() ).getOpcode( Opcodes.ILOAD ), 1 );
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
if ( persistentField.getDeclaringType().asErasure().equals( managedCtClass ) ) {
|
||||
methodVisitor.visitFieldInsn(
|
||||
Opcodes.GETFIELD,
|
||||
persistentField.getDeclaringType().asErasure().getInternalName(),
|
||||
persistentField.getName(),
|
||||
persistentField.getDescriptor()
|
||||
);
|
||||
}
|
||||
else {
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
persistentField.getDeclaringType().asErasure().getInternalName(),
|
||||
EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName(),
|
||||
Type.getMethodDescriptor( Type.getType( persistentField.getDescriptor() ) ),
|
||||
false
|
||||
);
|
||||
}
|
||||
int branchCode;
|
||||
if ( persistentField.getType().isPrimitive() ) {
|
||||
if ( persistentField.getType().represents( long.class ) ) {
|
||||
methodVisitor.visitInsn( Opcodes.LCMP );
|
||||
}
|
||||
else if ( persistentField.getType().represents( float.class ) ) {
|
||||
methodVisitor.visitInsn( Opcodes.FCMPL );
|
||||
}
|
||||
else if ( persistentField.getType().represents( double.class ) ) {
|
||||
methodVisitor.visitInsn( Opcodes.DCMPL );
|
||||
}
|
||||
else {
|
||||
methodVisitor.visitInsn( Opcodes.ISUB );
|
||||
}
|
||||
branchCode = Opcodes.IFEQ;
|
||||
}
|
||||
else {
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
Type.getInternalName( EqualsHelper.class ),
|
||||
"areEqual",
|
||||
Type.getMethodDescriptor( Type.getType( boolean.class ), Type.getType( Object.class ), Type.getType( Object.class ) ),
|
||||
false
|
||||
);
|
||||
branchCode = Opcodes.IFNE;
|
||||
}
|
||||
Label skip = new Label();
|
||||
methodVisitor.visitJumpInsn( branchCode, skip );
|
||||
// this.$$_hibernate_trackChange(fieldName)
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
methodVisitor.visitLdcInsn( persistentField.getName() );
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
managedCtClass.getInternalName(),
|
||||
EnhancerConstants.TRACKER_CHANGER_NAME,
|
||||
Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( String.class ) ),
|
||||
false
|
||||
);
|
||||
// }
|
||||
methodVisitor.visitLabel( skip );
|
||||
if ( implementationContext.getClassFileVersion().isAtLeast( ClassFileVersion.JAVA_V6 ) ) {
|
||||
methodVisitor.visitFrame( Opcodes.F_SAME, 0, null, 0, null );
|
||||
}
|
||||
return new Size( 1 + 2 * persistentField.getType().asErasure().getStackSize().getSize(), instrumentedMethod.getStackSize() );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.persistence.Embedded;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.engine.spi.CompositeOwner;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
import net.bytebuddy.ClassFileVersion;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.asm.AsmVisitorWrapper;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.description.type.TypeDefinition;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.implementation.StubMethod;
|
||||
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.jar.asm.Type;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
|
||||
class PersistentAttributeTransformer implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper {
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributeTransformer.class );
|
||||
|
||||
private final TypeDescription managedCtClass;
|
||||
|
||||
private final ByteBuddyEnhancementContext enhancementContext;
|
||||
|
||||
private final TypePool classPool;
|
||||
|
||||
private final FieldDescription[] enhancedFields;
|
||||
|
||||
private PersistentAttributeTransformer(
|
||||
TypeDescription managedCtClass,
|
||||
ByteBuddyEnhancementContext enhancementContext,
|
||||
TypePool classPool,
|
||||
FieldDescription[] enhancedFields) {
|
||||
this.managedCtClass = managedCtClass;
|
||||
this.enhancementContext = enhancementContext;
|
||||
this.classPool = classPool;
|
||||
this.enhancedFields = enhancedFields;
|
||||
}
|
||||
|
||||
public static PersistentAttributeTransformer collectPersistentFields(
|
||||
TypeDescription managedCtClass,
|
||||
ByteBuddyEnhancementContext enhancementContext,
|
||||
TypePool classPool) {
|
||||
List<FieldDescription> persistentFieldList = new ArrayList<FieldDescription>();
|
||||
for ( FieldDescription 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 ( !ctField.isStatic() && 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, enhancementContext ) );
|
||||
}
|
||||
|
||||
FieldDescription[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new FieldDescription[0] ) );
|
||||
log.debugf( "Persistent fields for entity %s: %s", managedCtClass.getName(), Arrays.toString( orderedFields ) );
|
||||
return new PersistentAttributeTransformer( managedCtClass, enhancementContext, classPool, orderedFields );
|
||||
}
|
||||
|
||||
private static Collection<FieldDescription> collectInheritPersistentFields(
|
||||
TypeDefinition managedCtClass,
|
||||
ByteBuddyEnhancementContext enhancementContext) {
|
||||
if ( managedCtClass == null || managedCtClass.represents( Object.class ) ) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
TypeDefinition managedCtSuperclass = managedCtClass.getSuperClass();
|
||||
|
||||
if ( !enhancementContext.isMappedSuperclassClass( managedCtSuperclass.asErasure() ) ) {
|
||||
return collectInheritPersistentFields( managedCtSuperclass, enhancementContext );
|
||||
}
|
||||
log.debugf( "Found @MappedSuperclass %s to collectPersistenceFields", managedCtSuperclass );
|
||||
List<FieldDescription> persistentFieldList = new ArrayList<FieldDescription>();
|
||||
|
||||
for ( FieldDescription ctField : managedCtSuperclass.getDeclaredFields() ) {
|
||||
if ( ctField.getName().startsWith( "$$_hibernate_" ) || "this$0".equals( ctField.getName() ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( !ctField.isStatic() && enhancementContext.isPersistentField( ctField ) ) {
|
||||
persistentFieldList.add( ctField );
|
||||
}
|
||||
}
|
||||
persistentFieldList.addAll( collectInheritPersistentFields( managedCtSuperclass, enhancementContext ) );
|
||||
return persistentFieldList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor wrap(
|
||||
TypeDescription instrumentedType,
|
||||
MethodDescription.InDefinedShape instrumentedMethod,
|
||||
MethodVisitor methodVisitor,
|
||||
Implementation.Context implementationContext,
|
||||
TypePool typePool,
|
||||
int writerFlags,
|
||||
int readerFlags) {
|
||||
return new MethodVisitor( Opcodes.ASM5, methodVisitor ) {
|
||||
@Override
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
|
||||
if ( isEnhanced( owner, name, desc ) ) {
|
||||
switch ( opcode ) {
|
||||
case Opcodes.GETFIELD:
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
owner,
|
||||
EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + name,
|
||||
"()" + desc,
|
||||
false
|
||||
);
|
||||
return;
|
||||
case Opcodes.PUTFIELD:
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
owner,
|
||||
EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + name,
|
||||
"(" + desc + ")V",
|
||||
false
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.visitFieldInsn( opcode, owner, name, desc );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isEnhanced(String owner, String name, String desc) {
|
||||
for ( FieldDescription enhancedField : enhancedFields ) {
|
||||
if ( enhancedField.getName().equals( name )
|
||||
&& enhancedField.getDescriptor().equals( desc )
|
||||
&& enhancedField.getDeclaringType().asErasure().getInternalName().equals( owner ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DynamicType.Builder<?> applyTo(DynamicType.Builder<?> builder, boolean accessor) {
|
||||
boolean compositeOwner = false;
|
||||
|
||||
builder = builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().method( not( nameStartsWith( "$$_hibernate_" ) ), this ) );
|
||||
for ( FieldDescription enhancedField : enhancedFields ) {
|
||||
builder = builder
|
||||
.defineMethod(
|
||||
EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + enhancedField.getName(),
|
||||
enhancedField.getType().asErasure(),
|
||||
Visibility.PUBLIC
|
||||
)
|
||||
.intercept(
|
||||
accessor
|
||||
? FieldAccessor.ofField( enhancedField.getName() ).in( enhancedField.getDeclaringType().asErasure() )
|
||||
: fieldReader( enhancedField )
|
||||
)
|
||||
.defineMethod(
|
||||
EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + enhancedField.getName(),
|
||||
TypeDescription.VOID,
|
||||
Visibility.PUBLIC
|
||||
)
|
||||
.withParameters( enhancedField.getType().asErasure() )
|
||||
.intercept( accessor
|
||||
? FieldAccessor.ofField( enhancedField.getName() ).in( enhancedField.getDeclaringType().asErasure() )
|
||||
: fieldWriter( enhancedField ) );
|
||||
|
||||
if ( !compositeOwner
|
||||
&& !accessor
|
||||
&& EnhancerImpl.isAnnotationPresent( enhancedField, Embedded.class )
|
||||
&& enhancementContext.isCompositeClass( enhancedField.getType().asErasure() )
|
||||
&& enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
compositeOwner = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( compositeOwner ) {
|
||||
builder = builder.implement( CompositeOwner.class );
|
||||
|
||||
if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
builder = builder.defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC )
|
||||
.withParameters( String.class )
|
||||
.intercept( Advice.to( CodeTemplates.CompositeOwnerDirtyCheckingHandler.class ).wrap( StubMethod.INSTANCE ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
|
||||
builder = applyExtended( builder );
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private Implementation fieldReader(FieldDescription enhancedField) {
|
||||
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( enhancedField ) ) {
|
||||
if ( enhancedField.getDeclaringType().asErasure().equals( managedCtClass ) ) {
|
||||
return FieldAccessor.ofField( enhancedField.getName() ).in( enhancedField.getDeclaringType().asErasure() );
|
||||
}
|
||||
else {
|
||||
return new Implementation.Simple( new FieldMethodReader( managedCtClass, enhancedField ) );
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new Implementation.Simple( FieldReaderAppender.of( managedCtClass, enhancedField ) );
|
||||
}
|
||||
}
|
||||
|
||||
private Implementation fieldWriter(FieldDescription enhancedField) {
|
||||
Implementation implementation;
|
||||
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( enhancedField ) ) {
|
||||
if ( enhancedField.getDeclaringType().asErasure().equals( managedCtClass ) ) {
|
||||
implementation = FieldAccessor.ofField( enhancedField.getName() ).in( enhancedField.getDeclaringType().asErasure() );
|
||||
}
|
||||
else {
|
||||
implementation = new Implementation.Simple( new FieldMethodWriter( managedCtClass, enhancedField ) );
|
||||
}
|
||||
}
|
||||
else {
|
||||
implementation = new Implementation.Simple( FieldWriterAppender.of( managedCtClass, enhancedField ) );
|
||||
}
|
||||
implementation = InlineDirtyCheckingHandler.wrap( managedCtClass, enhancementContext, enhancedField, implementation );
|
||||
return BiDirectionalAssociationHandler.wrap( managedCtClass, enhancementContext, enhancedField, implementation );
|
||||
}
|
||||
|
||||
DynamicType.Builder<?> applyExtended(DynamicType.Builder<?> builder) {
|
||||
AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper enhancer = new FieldAccessEnhancer( managedCtClass, enhancementContext, classPool );
|
||||
return builder.visit( new AsmVisitorWrapper.ForDeclaredMethods().method( not( nameStartsWith( "$$_hibernate_" ) ), enhancer ) );
|
||||
}
|
||||
|
||||
private static class FieldMethodReader implements ByteCodeAppender {
|
||||
|
||||
private final TypeDescription managedCtClass;
|
||||
|
||||
private final FieldDescription persistentField;
|
||||
|
||||
private FieldMethodReader(TypeDescription managedCtClass, FieldDescription persistentField) {
|
||||
this.managedCtClass = managedCtClass;
|
||||
this.persistentField = persistentField;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Size apply(
|
||||
MethodVisitor methodVisitor,
|
||||
Implementation.Context implementationContext,
|
||||
MethodDescription instrumentedMethod
|
||||
) {
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKESPECIAL,
|
||||
managedCtClass.getSuperClass().asErasure().getInternalName(),
|
||||
EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName(),
|
||||
Type.getMethodDescriptor( Type.getType( persistentField.getType().asErasure().getDescriptor() ) ),
|
||||
false
|
||||
);
|
||||
methodVisitor.visitInsn( Type.getType( persistentField.getType().asErasure().getDescriptor() ).getOpcode( Opcodes.IRETURN ) );
|
||||
return new Size( persistentField.getType().getStackSize().getSize(), instrumentedMethod.getStackSize() );
|
||||
}
|
||||
}
|
||||
|
||||
private static class FieldMethodWriter implements ByteCodeAppender {
|
||||
|
||||
private final TypeDescription managedCtClass;
|
||||
|
||||
private final FieldDescription persistentField;
|
||||
|
||||
private FieldMethodWriter(TypeDescription managedCtClass, FieldDescription persistentField) {
|
||||
this.managedCtClass = managedCtClass;
|
||||
this.persistentField = persistentField;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Size apply(
|
||||
MethodVisitor methodVisitor,
|
||||
Implementation.Context implementationContext,
|
||||
MethodDescription instrumentedMethod
|
||||
) {
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
|
||||
methodVisitor.visitVarInsn( Type.getType( persistentField.getType().asErasure().getDescriptor() ).getOpcode( Opcodes.ILOAD ), 1 );
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKESPECIAL,
|
||||
managedCtClass.getSuperClass().asErasure().getInternalName(),
|
||||
EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + persistentField.getName(),
|
||||
Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( persistentField.getType().asErasure().getDescriptor() ) ),
|
||||
false
|
||||
);
|
||||
methodVisitor.visitInsn( Opcodes.RETURN );
|
||||
return new Size( 1 + persistentField.getType().getStackSize().getSize(), instrumentedMethod.getStackSize() );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedField;
|
||||
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
|
||||
class UnloadedFieldDescription implements UnloadedField {
|
||||
|
||||
final FieldDescription fieldDescription;
|
||||
|
||||
UnloadedFieldDescription(FieldDescription fieldDescription) {
|
||||
this.fieldDescription = fieldDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
|
||||
return fieldDescription.getDeclaredAnnotations().isAnnotationPresent( annotationType );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedField;
|
||||
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
|
||||
class UnloadedTypeDescription implements UnloadedClass {
|
||||
|
||||
private final TypeDescription typeDescription;
|
||||
|
||||
UnloadedTypeDescription(TypeDescription typeDescription) {
|
||||
this.typeDescription = typeDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
|
||||
return typeDescription.getDeclaredAnnotations().isAnnotationPresent( annotationType );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return typeDescription.getName();
|
||||
}
|
||||
}
|
|
@ -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.bytebuddy;
|
|
@ -4,7 +4,7 @@
|
|||
* 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;
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
@ -37,7 +37,7 @@ public abstract class AttributeTypeDescriptor {
|
|||
|
||||
public abstract String buildWriteInterceptionBodyFragment(String fieldName);
|
||||
|
||||
public String buildInLineDirtyCheckingBodyFragment(EnhancementContext context, CtField currentValue) {
|
||||
public String buildInLineDirtyCheckingBodyFragment(JavassistEnhancementContext context, CtField currentValue) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try {
|
||||
// should ignore primary keys
|
|
@ -4,10 +4,11 @@
|
|||
* 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;
|
||||
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.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
|
@ -22,7 +23,7 @@ import org.hibernate.engine.spi.ManagedComposite;
|
|||
*/
|
||||
public class CompositeEnhancer extends PersistentAttributesEnhancer {
|
||||
|
||||
public CompositeEnhancer(EnhancementContext context) {
|
||||
public CompositeEnhancer(JavassistEnhancementContext context) {
|
||||
super( context );
|
||||
}
|
||||
|
||||
|
@ -57,10 +58,10 @@ public class CompositeEnhancer extends PersistentAttributesEnhancer {
|
|||
" 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() );
|
||||
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" +
|
|
@ -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.bytecode.enhance.internal.javassist;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javassist.CannotCompileException;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.LoaderClassPath;
|
||||
|
||||
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.ManagedComposite;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
import org.hibernate.engine.spi.ManagedMappedSuperclass;
|
||||
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 {
|
||||
try {
|
||||
final CtClass managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) );
|
||||
if ( enhance( managedCtClass ) ) {
|
||||
return getByteCode( managedCtClass );
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.unableToBuildEnhancementMetamodel( className );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] enhance(File javaClassFile) throws EnhancementException, IOException {
|
||||
final CtClass ctClass = classPool.makeClass( new FileInputStream( javaClassFile ) );
|
||||
try {
|
||||
return enhance( ctClass.getName(), ctClass.toBytecode() );
|
||||
}
|
||||
catch (CannotCompileException e) {
|
||||
log.warn( "Unable to enhance class file [" + javaClassFile.getAbsolutePath() + "]", e );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ClassPool buildClassPool(final JavassistEnhancementContext enhancementContext) {
|
||||
final ClassPool classPool = new ClassPool( false ) {
|
||||
@Override
|
||||
public ClassLoader getClassLoader() {
|
||||
return enhancementContext.getLoadingClassLoader();
|
||||
}
|
||||
};
|
||||
|
||||
final 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 );
|
||||
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.infof( "Enhancing [%s] as Entity", managedCtClass.getName() );
|
||||
new EntityEnhancer( enhancementContext ).enhance( managedCtClass );
|
||||
return true;
|
||||
}
|
||||
else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
log.infof( "Enhancing [%s] as Composite", managedCtClass.getName() );
|
||||
new CompositeEnhancer( enhancementContext ).enhance( managedCtClass );
|
||||
return true;
|
||||
}
|
||||
else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
|
||||
log.infof( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() );
|
||||
new MappedSuperclassEnhancer( enhancementContext ).enhance( managedCtClass );
|
||||
return true;
|
||||
}
|
||||
else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
|
||||
log.infof( "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;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean alreadyEnhanced(CtClass managedCtClass) {
|
||||
if ( !PersistentAttributesHelper.isAssignable( managedCtClass, Managed.class.getName() ) ) {
|
||||
return false;
|
||||
}
|
||||
// HHH-10977 - When a mapped superclass gets enhanced before a subclassing entity, the entity does not get enhanced, but it implements the Managed interface
|
||||
return enhancementContext.isEntityClass( managedCtClass ) && PersistentAttributesHelper.isAssignable( managedCtClass, ManagedEntity.class.getName() )
|
||||
|| enhancementContext.isCompositeClass( managedCtClass ) && PersistentAttributesHelper.isAssignable(
|
||||
managedCtClass,
|
||||
ManagedComposite.class.getName()
|
||||
)
|
||||
|| enhancementContext.isMappedSuperclassClass( managedCtClass ) && PersistentAttributesHelper.isAssignable(
|
||||
managedCtClass,
|
||||
ManagedMappedSuperclass.class.getName()
|
||||
);
|
||||
}
|
||||
|
||||
private byte[] getByteCode(CtClass managedCtClass) {
|
||||
final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
final 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() );
|
||||
}
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,12 +4,11 @@
|
|||
* 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;
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -20,6 +19,7 @@ import javassist.CtField;
|
|||
import javassist.Modifier;
|
||||
|
||||
import javassist.NotFoundException;
|
||||
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.SimpleCollectionTracker;
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker;
|
||||
|
@ -40,7 +40,7 @@ import org.hibernate.engine.spi.SelfDirtinessTracker;
|
|||
*/
|
||||
public class EntityEnhancer extends PersistentAttributesEnhancer {
|
||||
|
||||
public EntityEnhancer(EnhancementContext context) {
|
||||
public EntityEnhancer(JavassistEnhancementContext context) {
|
||||
super( context );
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* 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;
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import javax.persistence.Transient;
|
||||
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
|
@ -4,11 +4,12 @@
|
|||
* 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;
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
import javassist.CtMethod;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.engine.spi.ManagedMappedSuperclass;
|
||||
|
@ -20,7 +21,7 @@ import org.hibernate.engine.spi.ManagedMappedSuperclass;
|
|||
*/
|
||||
public class MappedSuperclassEnhancer extends PersistentAttributesEnhancer {
|
||||
|
||||
public MappedSuperclassEnhancer(EnhancementContext context) {
|
||||
public MappedSuperclassEnhancer(JavassistEnhancementContext context) {
|
||||
super( context );
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
* 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;
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import javassist.CannotCompileException;
|
||||
import javassist.CtClass;
|
|
@ -4,14 +4,13 @@
|
|||
* 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;
|
||||
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.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Embedded;
|
||||
|
@ -36,9 +35,7 @@ import javassist.bytecode.Opcode;
|
|||
import javassist.bytecode.stackmap.MapMaker;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
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.CompositeOwner;
|
||||
import org.hibernate.engine.spi.CompositeTracker;
|
||||
|
@ -50,11 +47,11 @@ import org.hibernate.internal.CoreMessageLogger;
|
|||
*
|
||||
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
|
||||
*/
|
||||
public class PersistentAttributesEnhancer extends Enhancer {
|
||||
public class PersistentAttributesEnhancer extends EnhancerImpl {
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributesEnhancer.class );
|
||||
|
||||
public PersistentAttributesEnhancer(EnhancementContext context) {
|
||||
public PersistentAttributesEnhancer(JavassistEnhancementContext context) {
|
||||
super( context );
|
||||
}
|
||||
|
||||
|
@ -97,7 +94,7 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
}
|
||||
|
||||
CtField[] orderedFields = enhancementContext.order( persistentFieldList.toArray( new CtField[0] ) );
|
||||
log.debugf( "Persistent fields for entity %s: %s", managedCtClass.getName(), Arrays.toString( orderedFields ));
|
||||
log.debugf( "Persistent fields for entity %s: %s", managedCtClass.getName(), Arrays.toString( orderedFields ) );
|
||||
return orderedFields;
|
||||
}
|
||||
|
||||
|
@ -131,7 +128,7 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
}
|
||||
}
|
||||
|
||||
private PersistentAttributeAccessMethods enhancePersistentAttribute( CtClass managedCtClass, CtField persistentField) {
|
||||
private PersistentAttributeAccessMethods enhancePersistentAttribute(CtClass managedCtClass, CtField persistentField) {
|
||||
try {
|
||||
AttributeTypeDescriptor typeDescriptor = AttributeTypeDescriptor.resolve( managedCtClass, persistentField );
|
||||
return new PersistentAttributeAccessMethods(
|
||||
|
@ -165,13 +162,13 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
String declaredReadFragment = "this." + fieldName + "";
|
||||
String superReadFragment = "super." + readerName + "()";
|
||||
|
||||
if (!declared) {
|
||||
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){
|
||||
catch ( NotFoundException nfe ) {
|
||||
tmpSuperReader = MethodWriter.addGetter( persistentField.getDeclaringClass(), persistentField.getName(), readerName );
|
||||
tmpSuperWriter = MethodWriter.addSetter( persistentField.getDeclaringClass(), persistentField.getName(), writerName );
|
||||
}
|
||||
|
@ -179,8 +176,7 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
|
||||
// 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 ) ) {
|
||||
if ( !enhancementContext.hasLazyLoadableAttributes( managedCtClass ) || !enhancementContext.isLazyLoadable( persistentField ) ) {
|
||||
reader = MethodWriter.write(
|
||||
managedCtClass, "public %s %s() { return %s;%n}",
|
||||
persistentField.getType().getName(),
|
||||
|
@ -237,15 +233,15 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
try {
|
||||
boolean declared = persistentField.getDeclaringClass().equals( managedCtClass );
|
||||
String declaredWriteFragment = "this." + fieldName + "=" + fieldName + ";";
|
||||
String superWriteFragment = "super." + writerName + "(" + fieldName + ");";
|
||||
String superWriteFragment = "super." + writerName + "(" + fieldName + ");";
|
||||
|
||||
if (!declared) {
|
||||
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){
|
||||
catch ( NotFoundException nfe ) {
|
||||
tmpSuperReader = MethodWriter.addGetter( persistentField.getDeclaringClass(), persistentField.getName(), readerName );
|
||||
tmpSuperWriter = MethodWriter.addSetter( persistentField.getDeclaringClass(), persistentField.getName(), writerName );
|
||||
}
|
||||
|
@ -352,7 +348,7 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
getter = targetEntity.getDeclaredMethod( mappedByGetterName );
|
||||
setter = targetEntity.getDeclaredMethod( mappedByGetterName );
|
||||
}
|
||||
catch (NotFoundException nfe){
|
||||
catch ( NotFoundException nfe ) {
|
||||
getter = MethodWriter.addGetter( targetEntity, mappedBy, mappedByGetterName );
|
||||
setter = MethodWriter.addSetter( targetEntity, mappedBy, mappedBySetterName );
|
||||
tmpTargetMethods = true;
|
||||
|
@ -397,19 +393,19 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
}
|
||||
if ( PersistentAttributesHelper.hasAnnotation( persistentField, OneToMany.class ) ) {
|
||||
boolean isMap = PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() );
|
||||
String toArrayMethod = isMap ? "values().toArray()" : "toArray()" ;
|
||||
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",
|
||||
" 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(),
|
||||
|
@ -420,12 +416,12 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
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",
|
||||
" 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,
|
||||
|
@ -448,9 +444,9 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
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",
|
||||
" java.util.Collection c = $1.%s();%n" +
|
||||
" if (c != null && !c.contains(this)) { c.add(this); }%n" +
|
||||
" }%n",
|
||||
newAssociationLoaded,
|
||||
mappedByGetterName
|
||||
)
|
||||
|
@ -458,7 +454,7 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
}
|
||||
if ( PersistentAttributesHelper.hasAnnotation( persistentField, ManyToMany.class ) ) {
|
||||
if ( PersistentAttributesHelper.isAssignable( persistentField.getType(), Map.class.getName() ) ||
|
||||
PersistentAttributesHelper.isAssignable( targetEntity.getField( mappedBy ).getType() , Map.class.getName() ) ) {
|
||||
PersistentAttributesHelper.isAssignable( targetEntity.getField( mappedBy ).getType(), Map.class.getName() ) ) {
|
||||
log.infof(
|
||||
"Bi-directional association for field [%s#%s] not managed: @ManyToMany in java.util.Map attribute not supported ",
|
||||
managedCtClass.getName(),
|
||||
|
@ -469,12 +465,12 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
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",
|
||||
" 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(),
|
||||
|
@ -484,15 +480,15 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
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",
|
||||
" 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(),
|
||||
|
@ -523,7 +519,7 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
// cleanup previous owner
|
||||
fieldWriter.insertBefore(
|
||||
String.format(
|
||||
"if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n",
|
||||
"if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n",
|
||||
persistentField.getName(),
|
||||
CompositeTracker.class.getName(),
|
||||
EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER
|
||||
|
@ -533,7 +529,7 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
// trigger track changes
|
||||
fieldWriter.insertAfter(
|
||||
String.format(
|
||||
"if (%1$s != null) { ((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this); }%n" +
|
||||
"if (%1$s != null) { ((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this); }%n" +
|
||||
"%5$s(\"%1$s\");",
|
||||
persistentField.getName(),
|
||||
CompositeTracker.class.getName(),
|
||||
|
@ -691,11 +687,13 @@ public class PersistentAttributesEnhancer extends Enhancer {
|
|||
continue;
|
||||
}
|
||||
|
||||
log.debugf( "Extended enhancement: Transforming access to field [%s.%s] from method [%s#%s]",
|
||||
fieldClassName,
|
||||
fieldName,
|
||||
aCtClass.getName(),
|
||||
methodName );
|
||||
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(
|
|
@ -4,7 +4,7 @@
|
|||
* 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;
|
||||
package org.hibernate.bytecode.enhance.internal.javassist;
|
||||
|
||||
import java.beans.Introspector;
|
||||
import java.lang.annotation.Annotation;
|
||||
|
@ -207,7 +207,7 @@ public class PersistentAttributesHelper {
|
|||
PersistentAttributesHelper.hasAnnotation( persistentField, ManyToMany.class );
|
||||
}
|
||||
|
||||
public static String getMappedBy(CtField persistentField, CtClass targetEntity, EnhancementContext context) throws NotFoundException {
|
||||
public static String getMappedBy(CtField persistentField, CtClass targetEntity, JavassistEnhancementContext context) throws NotFoundException {
|
||||
final String local = getMappedByFromAnnotation( persistentField );
|
||||
return local.isEmpty() ? getMappedByFromTargetEntity( persistentField, targetEntity, context ) : local;
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ public class PersistentAttributesHelper {
|
|||
private static String getMappedByFromTargetEntity(
|
||||
CtField persistentField,
|
||||
CtClass targetEntity,
|
||||
EnhancementContext context) throws NotFoundException {
|
||||
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 )
|
|
@ -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,27 @@
|
|||
/*
|
||||
* 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 );
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -6,6 +6,6 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* package containing bytecode enhancement code (internals)
|
||||
* package containing bytecode enhancement (internals)
|
||||
*/
|
||||
package org.hibernate.bytecode.enhance.internal;
|
||||
|
|
|
@ -14,9 +14,6 @@ import javax.persistence.MappedSuperclass;
|
|||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
|
||||
/**
|
||||
* default implementation of EnhancementContext. May be sub-classed as needed.
|
||||
*
|
||||
|
@ -27,6 +24,7 @@ public class DefaultEnhancementContext implements EnhancementContext {
|
|||
/**
|
||||
* @return the classloader for this class
|
||||
*/
|
||||
@Override
|
||||
public ClassLoader getLoadingClassLoader() {
|
||||
return getClass().getClassLoader();
|
||||
}
|
||||
|
@ -34,77 +32,88 @@ public class DefaultEnhancementContext implements EnhancementContext {
|
|||
/**
|
||||
* look for @Entity annotation
|
||||
*/
|
||||
public boolean isEntityClass(CtClass classDescriptor) {
|
||||
@Override
|
||||
public boolean isEntityClass(UnloadedClass classDescriptor) {
|
||||
return classDescriptor.hasAnnotation( Entity.class );
|
||||
}
|
||||
|
||||
/**
|
||||
* look for @Embeddable annotation
|
||||
*/
|
||||
public boolean isCompositeClass(CtClass classDescriptor) {
|
||||
@Override
|
||||
public boolean isCompositeClass(UnloadedClass classDescriptor) {
|
||||
return classDescriptor.hasAnnotation( Embeddable.class );
|
||||
}
|
||||
|
||||
/**
|
||||
* look for @MappedSuperclass annotation
|
||||
*/
|
||||
public boolean isMappedSuperclassClass(CtClass classDescriptor) {
|
||||
@Override
|
||||
public boolean isMappedSuperclassClass(UnloadedClass classDescriptor) {
|
||||
return classDescriptor.hasAnnotation( MappedSuperclass.class );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true
|
||||
*/
|
||||
public boolean doBiDirectionalAssociationManagement(CtField field) {
|
||||
@Override
|
||||
public boolean doBiDirectionalAssociationManagement(UnloadedField field) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true
|
||||
*/
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
|
||||
@Override
|
||||
public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false
|
||||
*/
|
||||
public boolean doExtendedEnhancement(CtClass classDescriptor) {
|
||||
@Override
|
||||
public boolean doExtendedEnhancement(UnloadedClass classDescriptor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true
|
||||
*/
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
|
||||
@Override
|
||||
public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true
|
||||
*/
|
||||
public boolean isLazyLoadable(CtField field) {
|
||||
@Override
|
||||
public boolean isLazyLoadable(UnloadedField field) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* look for @Transient annotation
|
||||
*/
|
||||
public boolean isPersistentField(CtField ctField) {
|
||||
@Override
|
||||
public boolean isPersistentField(UnloadedField ctField) {
|
||||
return ! ctField.hasAnnotation( Transient.class );
|
||||
}
|
||||
|
||||
/**
|
||||
* look for @OneToMany, @ManyToMany and @ElementCollection annotations
|
||||
*/
|
||||
public boolean isMappedCollection(CtField field) {
|
||||
@Override
|
||||
public boolean isMappedCollection(UnloadedField field) {
|
||||
return field.hasAnnotation( OneToMany.class ) || field.hasAnnotation( ManyToMany.class ) || field.hasAnnotation( ElementCollection.class );
|
||||
}
|
||||
|
||||
/**
|
||||
* keep the same order.
|
||||
*/
|
||||
public CtField[] order(CtField[] persistentFields) {
|
||||
@Override
|
||||
public UnloadedField[] order(UnloadedField[] persistentFields) {
|
||||
return persistentFields;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.bytecode.enhance.spi;
|
||||
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
|
||||
/**
|
||||
* The context for performing an enhancement. Enhancement can happen in any number of ways:<ul>
|
||||
* <li>Build time, via Ant</li>
|
||||
|
@ -22,7 +19,6 @@ import javassist.CtField;
|
|||
* the enhancement is being performed.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
* @todo Not sure its a great idea to expose Javassist classes this way. maybe wrap them in our own contracts?
|
||||
*/
|
||||
public interface EnhancementContext {
|
||||
/**
|
||||
|
@ -41,7 +37,7 @@ public interface EnhancementContext {
|
|||
*
|
||||
* @return {@code true} if the class is an entity; {@code false} otherwise.
|
||||
*/
|
||||
public boolean isEntityClass(CtClass classDescriptor);
|
||||
public boolean isEntityClass(UnloadedClass classDescriptor);
|
||||
|
||||
/**
|
||||
* Does the given class name represent an embeddable/component class?
|
||||
|
@ -50,7 +46,7 @@ public interface EnhancementContext {
|
|||
*
|
||||
* @return {@code true} if the class is an embeddable/component; {@code false} otherwise.
|
||||
*/
|
||||
public boolean isCompositeClass(CtClass classDescriptor);
|
||||
public boolean isCompositeClass(UnloadedClass classDescriptor);
|
||||
|
||||
/**
|
||||
* Does the given class name represent an MappedSuperclass class?
|
||||
|
@ -59,7 +55,7 @@ public interface EnhancementContext {
|
|||
*
|
||||
* @return {@code true} if the class is an mapped super class; {@code false} otherwise.
|
||||
*/
|
||||
public boolean isMappedSuperclassClass(CtClass classDescriptor);
|
||||
public boolean isMappedSuperclassClass(UnloadedClass classDescriptor);
|
||||
|
||||
/**
|
||||
* Should we manage association of bi-directional persistent attributes for this field?
|
||||
|
@ -70,7 +66,7 @@ public interface EnhancementContext {
|
|||
* the association is managed, i.e. the associations are automatically set; {@code false} indicates that
|
||||
* the management is handled by the user.
|
||||
*/
|
||||
public boolean doBiDirectionalAssociationManagement(CtField field);
|
||||
public boolean doBiDirectionalAssociationManagement(UnloadedField field);
|
||||
|
||||
/**
|
||||
* Should we in-line dirty checking for persistent attributes for this class?
|
||||
|
@ -80,7 +76,7 @@ public interface EnhancementContext {
|
|||
* @return {@code true} indicates that dirty checking should be in-lined within the entity; {@code false}
|
||||
* indicates it should not. In-lined is more easily serializable and probably more performant.
|
||||
*/
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor);
|
||||
public boolean doDirtyCheckingInline(UnloadedClass classDescriptor);
|
||||
|
||||
/**
|
||||
* Should we enhance field access to entities from this class?
|
||||
|
@ -90,7 +86,7 @@ public interface EnhancementContext {
|
|||
* @return {@code true} indicates that any direct access to fields of entities should be routed to the enhanced
|
||||
* getter / setter method.
|
||||
*/
|
||||
public boolean doExtendedEnhancement(CtClass classDescriptor);
|
||||
public boolean doExtendedEnhancement(UnloadedClass classDescriptor);
|
||||
|
||||
/**
|
||||
* Does the given class define any lazy loadable attributes?
|
||||
|
@ -99,7 +95,7 @@ public interface EnhancementContext {
|
|||
*
|
||||
* @return true/false
|
||||
*/
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor);
|
||||
public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor);
|
||||
|
||||
// todo : may be better to invert these 2 such that the context is asked for an ordered list of persistent fields for an entity/composite
|
||||
|
||||
|
@ -113,7 +109,7 @@ public interface EnhancementContext {
|
|||
*
|
||||
* @return {@code true} if the field is ; {@code false} otherwise.
|
||||
*/
|
||||
public boolean isPersistentField(CtField ctField);
|
||||
public boolean isPersistentField(UnloadedField ctField);
|
||||
|
||||
/**
|
||||
* For fields which are persistent (according to {@link #isPersistentField}), determine the corresponding ordering
|
||||
|
@ -123,7 +119,7 @@ public interface EnhancementContext {
|
|||
*
|
||||
* @return The ordered references.
|
||||
*/
|
||||
public CtField[] order(CtField[] persistentFields);
|
||||
public UnloadedField[] order(UnloadedField[] persistentFields);
|
||||
|
||||
/**
|
||||
* Determine if a field is lazy loadable.
|
||||
|
@ -132,12 +128,12 @@ public interface EnhancementContext {
|
|||
*
|
||||
* @return {@code true} if the field is lazy loadable; {@code false} otherwise.
|
||||
*/
|
||||
public boolean isLazyLoadable(CtField field);
|
||||
public boolean isLazyLoadable(UnloadedField field);
|
||||
|
||||
/**
|
||||
* @param field the field to check
|
||||
*
|
||||
* @return {@code true} if the field is mapped
|
||||
*/
|
||||
public boolean isMappedCollection(CtField field);
|
||||
public boolean isMappedCollection(UnloadedField field);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.spi;
|
||||
|
||||
public class EnhancementContextWrapper implements EnhancementContext {
|
||||
|
||||
private final ClassLoader loadingClassloader;
|
||||
private final EnhancementContext wrappedContext;
|
||||
|
||||
public EnhancementContextWrapper(EnhancementContext wrappedContext, ClassLoader loadingClassloader) {
|
||||
this.wrappedContext = wrappedContext;
|
||||
this.loadingClassloader = loadingClassloader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getLoadingClassLoader() {
|
||||
return loadingClassloader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntityClass(UnloadedClass classDescriptor) {
|
||||
return wrappedContext.isEntityClass( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompositeClass(UnloadedClass classDescriptor) {
|
||||
return wrappedContext.isCompositeClass( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMappedSuperclassClass(UnloadedClass classDescriptor) {
|
||||
return wrappedContext.isMappedSuperclassClass( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doBiDirectionalAssociationManagement(UnloadedField field) {
|
||||
return wrappedContext.doBiDirectionalAssociationManagement( field );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) {
|
||||
return wrappedContext.doDirtyCheckingInline( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doExtendedEnhancement(UnloadedClass classDescriptor) {
|
||||
return wrappedContext.doExtendedEnhancement( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) {
|
||||
return wrappedContext.hasLazyLoadableAttributes( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersistentField(UnloadedField ctField) {
|
||||
return wrappedContext.isPersistentField( ctField );
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnloadedField[] order(UnloadedField[] persistentFields) {
|
||||
return wrappedContext.order( persistentFields );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLazyLoadable(UnloadedField field) {
|
||||
return wrappedContext.isLazyLoadable( field );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMappedCollection(UnloadedField field) {
|
||||
return wrappedContext.isMappedCollection( field );
|
||||
}
|
||||
}
|
|
@ -14,6 +14,11 @@ import org.hibernate.HibernateException;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public class EnhancementException extends HibernateException {
|
||||
|
||||
public EnhancementException(String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an EnhancementException
|
||||
*
|
||||
|
|
|
@ -6,31 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.bytecode.enhance.spi;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.LoaderClassPath;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.bytecode.enhance.internal.CompositeEnhancer;
|
||||
import org.hibernate.bytecode.enhance.internal.EntityEnhancer;
|
||||
import org.hibernate.bytecode.enhance.internal.FieldWriter;
|
||||
import org.hibernate.bytecode.enhance.internal.MappedSuperclassEnhancer;
|
||||
import org.hibernate.bytecode.enhance.internal.PersistentAttributesEnhancer;
|
||||
import org.hibernate.bytecode.enhance.internal.PersistentAttributesHelper;
|
||||
import org.hibernate.engine.spi.Managed;
|
||||
import org.hibernate.engine.spi.ManagedComposite;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
import org.hibernate.engine.spi.ManagedMappedSuperclass;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
/**
|
||||
* Class responsible for performing enhancement.
|
||||
|
@ -39,22 +16,7 @@ import org.hibernate.internal.CoreMessageLogger;
|
|||
* @author Jason Greene
|
||||
* @author Luis Barreiro
|
||||
*/
|
||||
public class Enhancer {
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class );
|
||||
|
||||
protected final EnhancementContext 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 Enhancer(EnhancementContext enhancementContext) {
|
||||
this.enhancementContext = enhancementContext;
|
||||
this.classPool = buildClassPool( enhancementContext );
|
||||
}
|
||||
public interface Enhancer {
|
||||
|
||||
/**
|
||||
* Performs the enhancement.
|
||||
|
@ -62,140 +24,11 @@ public class Enhancer {
|
|||
* @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.
|
||||
* @return The enhanced bytecode. If the original bytes are not enhanced, null is returned.
|
||||
*
|
||||
* @throws EnhancementException Indicates a problem performing the enhancement
|
||||
*/
|
||||
public synchronized byte[] enhance(String className, byte[] originalBytes) throws EnhancementException {
|
||||
try {
|
||||
final CtClass managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) );
|
||||
enhance( managedCtClass );
|
||||
return getByteCode( managedCtClass );
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.unableToBuildEnhancementMetamodel( className );
|
||||
return originalBytes;
|
||||
}
|
||||
}
|
||||
byte[] enhance(String className, byte[] originalBytes) throws EnhancementException;
|
||||
|
||||
private ClassPool buildClassPool(final EnhancementContext enhancementContext) {
|
||||
final ClassPool classPool = new ClassPool( false ) {
|
||||
@Override
|
||||
public ClassLoader getClassLoader() {
|
||||
return enhancementContext.getLoadingClassLoader();
|
||||
}
|
||||
};
|
||||
|
||||
final 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 );
|
||||
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 void enhance(CtClass managedCtClass) {
|
||||
// can't effectively enhance interfaces
|
||||
if ( managedCtClass.isInterface() ) {
|
||||
log.debugf( "Skipping enhancement of [%s]: it's an interface!", managedCtClass.getName() );
|
||||
return;
|
||||
}
|
||||
// skip already enhanced classes
|
||||
if ( alreadyEnhanced( managedCtClass ) ) {
|
||||
log.debugf( "Skipping enhancement of [%s]: already enhanced", managedCtClass.getName() );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( enhancementContext.isEntityClass( managedCtClass ) ) {
|
||||
log.infof( "Enhancing [%s] as Entity", managedCtClass.getName() );
|
||||
new EntityEnhancer( enhancementContext ).enhance( managedCtClass );
|
||||
}
|
||||
else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
log.infof( "Enhancing [%s] as Composite", managedCtClass.getName() );
|
||||
new CompositeEnhancer( enhancementContext ).enhance( managedCtClass );
|
||||
}
|
||||
else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
|
||||
log.infof( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() );
|
||||
new MappedSuperclassEnhancer( enhancementContext ).enhance( managedCtClass );
|
||||
}
|
||||
else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
|
||||
log.infof( "Extended enhancement of [%s]", managedCtClass.getName() );
|
||||
new PersistentAttributesEnhancer( enhancementContext ).extendedEnhancement( managedCtClass );
|
||||
}
|
||||
else {
|
||||
log.debugf( "Skipping enhancement of [%s]: not entity or composite", managedCtClass.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
private boolean alreadyEnhanced(CtClass managedCtClass) {
|
||||
if ( !PersistentAttributesHelper.isAssignable( managedCtClass, Managed.class.getName() ) ) {
|
||||
return false;
|
||||
}
|
||||
// HHH-10977 - When a mapped superclass gets enhanced before a subclassing entity, the entity does not get enhanced, but it implements the Managed interface
|
||||
return enhancementContext.isEntityClass( managedCtClass ) && PersistentAttributesHelper.isAssignable( managedCtClass, ManagedEntity.class.getName() )
|
||||
|| enhancementContext.isCompositeClass( managedCtClass ) && PersistentAttributesHelper.isAssignable( managedCtClass, ManagedComposite.class.getName() )
|
||||
|| enhancementContext.isMappedSuperclassClass( managedCtClass ) && PersistentAttributesHelper.isAssignable( managedCtClass, ManagedMappedSuperclass.class.getName() );
|
||||
}
|
||||
|
||||
private byte[] getByteCode(CtClass managedCtClass) {
|
||||
final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
final 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() );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Should use enhance(String, byte[]) and a proper EnhancementContext
|
||||
*/
|
||||
@Deprecated
|
||||
public byte[] enhanceComposite(String className, byte[] originalBytes) throws EnhancementException {
|
||||
return enhance( className, originalBytes );
|
||||
}
|
||||
byte[] enhance(File javaClassFile) throws EnhancementException, IOException;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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.spi;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
public interface UnloadedClass {
|
||||
|
||||
boolean hasAnnotation(Class<? extends Annotation> annotationType);
|
||||
|
||||
String getName();
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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.spi;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
public interface UnloadedField {
|
||||
|
||||
boolean hasAnnotation(Class<? extends Annotation> annotationType);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.bytecode.spi.BasicProxyFactory;
|
||||
import org.hibernate.proxy.ProxyConfiguration;
|
||||
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.dynamic.scaffold.TypeValidation;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.MethodDelegation;
|
||||
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
|
||||
import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
|
||||
public class BasicProxyFactoryImpl implements BasicProxyFactory {
|
||||
|
||||
private static final ConcurrentMap<Set<Class>, Class> CACHE = new ConcurrentHashMap<Set<Class>, Class>();
|
||||
|
||||
private static final Class[] NO_INTERFACES = new Class[0];
|
||||
|
||||
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" );
|
||||
}
|
||||
|
||||
Set<Class> key = new HashSet<Class>();
|
||||
if ( superClass != null ) {
|
||||
key.add( superClass );
|
||||
}
|
||||
if ( interfaces != null && interfaces.length > 0 ) {
|
||||
key.addAll( Arrays.asList( interfaces ) );
|
||||
}
|
||||
|
||||
Class proxyClass = CACHE.get( key );
|
||||
|
||||
if ( proxyClass == null ) {
|
||||
proxyClass = new ByteBuddy()
|
||||
.with( TypeValidation.DISABLED )
|
||||
.with( new AuxiliaryType.NamingStrategy.SuffixingRandom( "HibernateBasicProxy" ) )
|
||||
.subclass( superClass == null ? Object.class : superClass )
|
||||
.implement( interfaces == null ? NO_INTERFACES : interfaces )
|
||||
.defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE )
|
||||
.method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) )
|
||||
.intercept( MethodDelegation.toField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) )
|
||||
.implement( ProxyConfiguration.class )
|
||||
.intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) )
|
||||
.make()
|
||||
.load( BasicProxyFactory.class.getClassLoader() )
|
||||
.getLoaded();
|
||||
Class previousProxy = CACHE.putIfAbsent( key, proxyClass );
|
||||
if ( previousProxy != null ) {
|
||||
proxyClass = previousProxy;
|
||||
}
|
||||
}
|
||||
|
||||
this.proxyClass = proxyClass;
|
||||
}
|
||||
|
||||
public Object getProxy() {
|
||||
try {
|
||||
final ProxyConfiguration proxy = (ProxyConfiguration) proxyClass.newInstance();
|
||||
proxy.$$_hibernate_set_interceptor( new PassThroughInterceptor( proxy, proxyClass.getName() ) );
|
||||
return proxy;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw new HibernateException( "Unable to instantiate proxy instance" );
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInstance(Object object) {
|
||||
return proxyClass.isInstance( object );
|
||||
}
|
||||
}
|
|
@ -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.bytecode.internal.bytebuddy;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
|
||||
public class BulkAccessorException extends HibernateException {
|
||||
private final int index;
|
||||
|
||||
public BulkAccessorException(String message) {
|
||||
this( message, -1 );
|
||||
}
|
||||
|
||||
public BulkAccessorException(String message, int index) {
|
||||
this( message, index, null );
|
||||
}
|
||||
|
||||
public BulkAccessorException(String message, Exception cause) {
|
||||
this( message, -1, cause );
|
||||
}
|
||||
|
||||
public BulkAccessorException(String message, int index, Exception cause) {
|
||||
super( message + " : @" + index, cause );
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.bytecode.enhance.internal.bytebuddy.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 net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.NamingStrategy;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.scaffold.TypeValidation;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.implementation.MethodCall;
|
||||
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
|
||||
import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
||||
import net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveBoxingDelegate;
|
||||
import net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveUnboxingDelegate;
|
||||
import net.bytebuddy.implementation.bytecode.assign.reference.ReferenceTypeAwareAssigner;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.jar.asm.Type;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
|
||||
public class BytecodeProviderImpl implements BytecodeProvider {
|
||||
|
||||
private final ConcurrentMap<Class, Class> FAST_CLASSES = new ConcurrentHashMap<Class, Class>();
|
||||
private final ConcurrentMap<Class, Class> BULK_ACCESSORS = new ConcurrentHashMap<Class, Class>();
|
||||
|
||||
@Override
|
||||
public ProxyFactoryFactory getProxyFactoryFactory() {
|
||||
return new ProxyFactoryFactoryImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReflectionOptimizer getReflectionOptimizer(
|
||||
final Class clazz,
|
||||
final String[] getterNames,
|
||||
final String[] setterNames,
|
||||
final Class[] types) {
|
||||
final Method[] getters = new Method[getterNames.length];
|
||||
final Method[] setters = new Method[setterNames.length];
|
||||
findAccessors( clazz, getterNames, setterNames, types, getters, setters );
|
||||
final Constructor<?> constructor = findConstructor( clazz );
|
||||
|
||||
Class fastClass = FAST_CLASSES.get( clazz );
|
||||
|
||||
if ( fastClass == null ) {
|
||||
fastClass = new ByteBuddy()
|
||||
.with( TypeValidation.DISABLED )
|
||||
.with( new NamingStrategy.SuffixingRandom( "HibernateInstantiator" ) )
|
||||
.subclass( ReflectionOptimizer.InstantiationOptimizer.class )
|
||||
.method( ElementMatchers.named( "newInstance" ) )
|
||||
.intercept( MethodCall.construct( constructor ) )
|
||||
.make()
|
||||
.load( clazz.getClassLoader() )
|
||||
.getLoaded();
|
||||
}
|
||||
|
||||
Class previousFastClass = FAST_CLASSES.putIfAbsent( clazz, fastClass );
|
||||
if ( previousFastClass != null ) {
|
||||
fastClass = previousFastClass;
|
||||
}
|
||||
|
||||
Class bulkAccessor = BULK_ACCESSORS.get( clazz );
|
||||
|
||||
if ( bulkAccessor == null ) {
|
||||
bulkAccessor = new ByteBuddy()
|
||||
.with( TypeValidation.DISABLED )
|
||||
.with( new NamingStrategy.SuffixingRandom( "HibernateAccessOptimizer" ) )
|
||||
.subclass( ReflectionOptimizer.AccessOptimizer.class )
|
||||
.method( ElementMatchers.named( "getPropertyValues" ) )
|
||||
.intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) )
|
||||
.method( ElementMatchers.named( "setPropertyValues" ) )
|
||||
.intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) )
|
||||
.method( ElementMatchers.named( "getPropertyNames" ) )
|
||||
.intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) )
|
||||
.make()
|
||||
.load( clazz.getClassLoader() )
|
||||
.getLoaded();
|
||||
}
|
||||
|
||||
|
||||
Class previousBulkAccessor = BULK_ACCESSORS.putIfAbsent( clazz, fastClass );
|
||||
if ( previousBulkAccessor != null ) {
|
||||
bulkAccessor = previousBulkAccessor;
|
||||
}
|
||||
|
||||
try {
|
||||
return new ReflectionOptimizerImpl(
|
||||
(ReflectionOptimizer.InstantiationOptimizer) fastClass.newInstance(),
|
||||
(ReflectionOptimizer.AccessOptimizer) bulkAccessor.newInstance()
|
||||
);
|
||||
}
|
||||
catch (Exception exception) {
|
||||
throw new HibernateException( exception );
|
||||
}
|
||||
}
|
||||
|
||||
private static class GetPropertyValues implements ByteCodeAppender {
|
||||
|
||||
private final Class clazz;
|
||||
|
||||
private final Method[] getters;
|
||||
|
||||
public GetPropertyValues(Class clazz, Method[] getters) {
|
||||
this.clazz = clazz;
|
||||
this.getters = getters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Size apply(
|
||||
MethodVisitor methodVisitor,
|
||||
Implementation.Context implementationContext,
|
||||
MethodDescription instrumentedMethod) {
|
||||
methodVisitor.visitLdcInsn( getters.length );
|
||||
methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName( Object.class ) );
|
||||
int index = 0;
|
||||
for ( Method getter : getters ) {
|
||||
methodVisitor.visitInsn( Opcodes.DUP );
|
||||
methodVisitor.visitLdcInsn( index++ );
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 );
|
||||
methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, Type.getInternalName( clazz ) );
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
Type.getInternalName( clazz ),
|
||||
getter.getName(),
|
||||
Type.getMethodDescriptor( getter ),
|
||||
false
|
||||
);
|
||||
if ( getter.getReturnType().isPrimitive() ) {
|
||||
PrimitiveBoxingDelegate.forPrimitive( new TypeDescription.ForLoadedType( getter.getReturnType() ) )
|
||||
.assignBoxedTo(
|
||||
TypeDescription.Generic.OBJECT,
|
||||
ReferenceTypeAwareAssigner.INSTANCE,
|
||||
Assigner.Typing.STATIC
|
||||
)
|
||||
.apply( methodVisitor, implementationContext );
|
||||
}
|
||||
methodVisitor.visitInsn( Opcodes.AASTORE );
|
||||
}
|
||||
methodVisitor.visitInsn( Opcodes.ARETURN );
|
||||
return new Size( 6, instrumentedMethod.getStackSize() );
|
||||
}
|
||||
}
|
||||
|
||||
private static class SetPropertyValues implements ByteCodeAppender {
|
||||
|
||||
private final Class clazz;
|
||||
|
||||
private final Method[] setters;
|
||||
|
||||
public SetPropertyValues(Class clazz, Method[] setters) {
|
||||
this.clazz = clazz;
|
||||
this.setters = setters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Size apply(
|
||||
MethodVisitor methodVisitor,
|
||||
Implementation.Context implementationContext,
|
||||
MethodDescription instrumentedMethod) {
|
||||
int index = 0;
|
||||
for ( Method setter : setters ) {
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 );
|
||||
methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, Type.getInternalName( clazz ) );
|
||||
methodVisitor.visitVarInsn( Opcodes.ALOAD, 2 );
|
||||
methodVisitor.visitLdcInsn( index++ );
|
||||
methodVisitor.visitInsn( Opcodes.AALOAD );
|
||||
if ( setter.getParameterTypes()[0].isPrimitive() ) {
|
||||
PrimitiveUnboxingDelegate.forReferenceType( TypeDescription.Generic.OBJECT )
|
||||
.assignUnboxedTo(
|
||||
new TypeDescription.Generic.OfNonGenericType.ForLoadedType( setter.getParameterTypes()[0] ),
|
||||
ReferenceTypeAwareAssigner.INSTANCE,
|
||||
Assigner.Typing.DYNAMIC
|
||||
)
|
||||
.apply( methodVisitor, implementationContext );
|
||||
}
|
||||
else {
|
||||
methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, Type.getInternalName( setter.getParameterTypes()[0] ) );
|
||||
}
|
||||
methodVisitor.visitMethodInsn(
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
Type.getInternalName( clazz ),
|
||||
setter.getName(),
|
||||
Type.getMethodDescriptor( setter ),
|
||||
false
|
||||
);
|
||||
}
|
||||
methodVisitor.visitInsn( Opcodes.RETURN );
|
||||
return new Size( 4, instrumentedMethod.getStackSize() );
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
private static Constructor<?> findConstructor(Class clazz) {
|
||||
try {
|
||||
return clazz.getDeclaredConstructor();
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
throw new HibernateException( e );
|
||||
}
|
||||
}
|
||||
|
||||
public static class CloningPropertyCall implements Callable<String[]> {
|
||||
|
||||
private final String[] propertyNames;
|
||||
|
||||
private CloningPropertyCall(String[] propertyNames) {
|
||||
this.propertyNames = propertyNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] call() {
|
||||
return propertyNames.clone();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enhancer getEnhancer(EnhancementContext enhancementContext) {
|
||||
return new EnhancerImpl( enhancementContext );
|
||||
}
|
||||
}
|
|
@ -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.bytecode.internal.bytebuddy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.hibernate.proxy.ProxyConfiguration;
|
||||
|
||||
import net.bytebuddy.implementation.bind.annotation.AllArguments;
|
||||
import net.bytebuddy.implementation.bind.annotation.Origin;
|
||||
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
||||
import net.bytebuddy.implementation.bind.annotation.This;
|
||||
|
||||
public class PassThroughInterceptor implements ProxyConfiguration.Interceptor {
|
||||
|
||||
private HashMap data = new HashMap();
|
||||
private final Object proxiedObject;
|
||||
private final String proxiedClassName;
|
||||
|
||||
public PassThroughInterceptor(Object proxiedObject, String proxiedClassName) {
|
||||
this.proxiedObject = proxiedObject;
|
||||
this.proxiedClassName = proxiedClassName;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object intercept(Object instance, Method method, Object[] arguments) throws Exception {
|
||||
final String name = method.getName();
|
||||
if ( "toString".equals( name ) ) {
|
||||
return proxiedClassName + "@" + System.identityHashCode( instance );
|
||||
}
|
||||
else if ( "equals".equals( name ) ) {
|
||||
return proxiedObject == instance;
|
||||
}
|
||||
else if ( "hashCode".equals( name ) ) {
|
||||
return System.identityHashCode( instance );
|
||||
}
|
||||
|
||||
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, arguments[0] );
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
// todo : what else to do here?
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
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.bytebuddy.ByteBuddyProxyFactory;
|
||||
|
||||
public class ProxyFactoryFactoryImpl implements ProxyFactoryFactory {
|
||||
|
||||
@Override
|
||||
public ProxyFactory buildProxyFactory(SessionFactoryImplementor sessionFactory) {
|
||||
return new ByteBuddyProxyFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicProxyFactory buildBasicProxyFactory(Class superClass, Class[] interfaces) {
|
||||
return new BasicProxyFactoryImpl( superClass, interfaces );
|
||||
}
|
||||
}
|
|
@ -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.bytecode.internal.bytebuddy;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.hibernate.bytecode.spi.ReflectionOptimizer;
|
||||
|
||||
public class ReflectionOptimizerImpl implements ReflectionOptimizer, Serializable {
|
||||
private final InstantiationOptimizer instantiationOptimizer;
|
||||
private final AccessOptimizer accessOptimizer;
|
||||
|
||||
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>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Byte Buddy support internals
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.bytebuddy;
|
|
@ -8,6 +8,9 @@ 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;
|
||||
|
@ -92,4 +95,8 @@ public class BytecodeProviderImpl implements BytecodeProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enhancer getEnhancer(EnhancementContext enhancementContext) {
|
||||
return new EnhancerImpl( enhancementContext );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
*/
|
||||
package org.hibernate.bytecode.spi;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
|
||||
/**
|
||||
* Contract for providers of bytecode services to Hibernate.
|
||||
* <p/>
|
||||
|
@ -36,4 +39,13 @@ public interface BytecodeProvider {
|
|||
* @return The reflection optimization delegate.
|
||||
*/
|
||||
ReflectionOptimizer getReflectionOptimizer(Class clazz, String[] getterNames, String[] setterNames, Class[] types);
|
||||
|
||||
/**
|
||||
* Returns a byte code enhancer that implements the enhancements described in the supplied enhancement context.
|
||||
*
|
||||
* @param enhancementContext The enhancement context that describes the enhancements to apply.
|
||||
*
|
||||
* @return An enhancer to perform byte code manipulations.
|
||||
*/
|
||||
Enhancer getEnhancer(EnhancementContext enhancementContext);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ public class InstrumentedClassLoader extends ClassLoader {
|
|||
try {
|
||||
final byte[] originalBytecode = ByteCodeHelper.readByteCode( is );
|
||||
final byte[] transformedBytecode = classTransformer.transform( getParent(), name, null, null, originalBytecode );
|
||||
if ( originalBytecode == transformedBytecode ) {
|
||||
if ( transformedBytecode == null ) {
|
||||
// no transformations took place, so handle it as we would a
|
||||
// non-instrumented class
|
||||
return getParent().loadClass( name );
|
||||
|
@ -57,7 +57,7 @@ public class InstrumentedClassLoader extends ClassLoader {
|
|||
return defineClass( name, transformedBytecode, 0, transformedBytecode.length );
|
||||
}
|
||||
}
|
||||
catch( Throwable t ) {
|
||||
catch ( Throwable t ) {
|
||||
throw new ClassNotFoundException( name + " not found", t );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -314,17 +314,20 @@ public final class Environment implements AvailableSettings {
|
|||
}
|
||||
|
||||
public static BytecodeProvider buildBytecodeProvider(Properties properties) {
|
||||
String provider = ConfigurationHelper.getString( BYTECODE_PROVIDER, properties, "javassist" );
|
||||
String provider = ConfigurationHelper.getString( BYTECODE_PROVIDER, properties, "bytebuddy" );
|
||||
LOG.bytecodeProvider( provider );
|
||||
return buildBytecodeProvider( provider );
|
||||
}
|
||||
|
||||
private static BytecodeProvider buildBytecodeProvider(String providerName) {
|
||||
if ( "bytebuddy".equals( providerName ) ) {
|
||||
return new org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl();
|
||||
}
|
||||
if ( "javassist".equals( providerName ) ) {
|
||||
return new org.hibernate.bytecode.internal.javassist.BytecodeProviderImpl();
|
||||
}
|
||||
|
||||
LOG.unknownBytecodeProvider( providerName );
|
||||
return new org.hibernate.bytecode.internal.javassist.BytecodeProviderImpl();
|
||||
return new org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.engine.spi;
|
||||
|
||||
import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||
|
||||
/**
|
||||
* A self dirtiness tracker that declares additional methods that are intended for internal communication. This
|
||||
* interface can be implemented optionally instead of the plain {@link SelfDirtinessTracker}.
|
||||
*/
|
||||
public interface ExtendedSelfDirtinessTracker extends SelfDirtinessTracker {
|
||||
|
||||
String REMOVE_DIRTY_FIELDS_NAME = "$$_hibernate_removeDirtyFields";
|
||||
|
||||
void $$_hibernate_getCollectionFieldDirtyNames(DirtyTracker dirtyTracker);
|
||||
|
||||
boolean $$_hibernate_areCollectionFieldsDirty();
|
||||
|
||||
void $$_hibernate_clearDirtyCollectionNames();
|
||||
|
||||
void $$_hibernate_removeDirtyFields(LazyAttributeLoadingInterceptor lazyInterceptor);
|
||||
}
|
|
@ -467,8 +467,8 @@ public interface CoreMessageLogger extends BasicLogger {
|
|||
String old,
|
||||
String name);
|
||||
|
||||
@Message(value = "Javassist Enhancement failed: %s", id = 142)
|
||||
String javassistEnhancementFailed(String entityName);
|
||||
@Message(value = "Bytecode enhancement failed: %s", id = 142)
|
||||
String bytecodeEnhancementFailed(String entityName);
|
||||
|
||||
@LogMessage(level = WARN)
|
||||
@Message(value = "%s = false breaks the EJB3 specification", id = 144)
|
||||
|
@ -1327,7 +1327,7 @@ public interface CoreMessageLogger extends BasicLogger {
|
|||
void unexpectedRowCounts();
|
||||
|
||||
@LogMessage(level = WARN)
|
||||
@Message(value = "unrecognized bytecode provider [%s], using javassist by default", id = 382)
|
||||
@Message(value = "unrecognized bytecode provider [%s], using 'bytebuddy' by default", id = 382)
|
||||
void unknownBytecodeProvider(String providerName);
|
||||
|
||||
@LogMessage(level = WARN)
|
||||
|
|
|
@ -51,6 +51,8 @@ import org.hibernate.boot.spi.MetadataImplementor;
|
|||
import org.hibernate.boot.spi.SessionFactoryBuilderImplementor;
|
||||
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedField;
|
||||
import org.hibernate.cfg.AttributeConverterDefinition;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.cfg.beanvalidation.BeanValidationIntegrator;
|
||||
|
@ -276,39 +278,39 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil
|
|||
return new DefaultEnhancementContext() {
|
||||
|
||||
@Override
|
||||
public boolean isEntityClass(CtClass classDescriptor) {
|
||||
public boolean isEntityClass(UnloadedClass classDescriptor) {
|
||||
return managedResources.getAnnotatedClassNames().contains( classDescriptor.getName() )
|
||||
&& super.isEntityClass( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompositeClass(CtClass classDescriptor) {
|
||||
public boolean isCompositeClass(UnloadedClass classDescriptor) {
|
||||
return managedResources.getAnnotatedClassNames().contains( classDescriptor.getName() )
|
||||
&& super.isCompositeClass( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doBiDirectionalAssociationManagement(CtField field) {
|
||||
public boolean doBiDirectionalAssociationManagement(UnloadedField field) {
|
||||
return associationManagementEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
|
||||
public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) {
|
||||
return dirtyTrackingEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
|
||||
public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) {
|
||||
return lazyInitializationEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLazyLoadable(CtField field) {
|
||||
public boolean isLazyLoadable(UnloadedField field) {
|
||||
return lazyInitializationEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doExtendedEnhancement(CtClass classDescriptor) {
|
||||
public boolean doExtendedEnhancement(UnloadedClass classDescriptor) {
|
||||
// doesn't make any sense to have extended enhancement enabled at runtime. we only enhance entities anyway.
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -9,12 +9,11 @@ package org.hibernate.jpa.internal.enhance;
|
|||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContextWrapper;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.bytecode.spi.ClassTransformer;
|
||||
import org.hibernate.cfg.Environment;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -41,7 +40,7 @@ public class EnhancingClassTransformerImpl implements ClassTransformer {
|
|||
// It also assumed that all calls come from the same class loader, which is fair, but this makes it more robust.
|
||||
|
||||
try {
|
||||
Enhancer enhancer = new Enhancer( new EnhancementContextWrapper( enhancementContext, loader ) );
|
||||
Enhancer enhancer = Environment.getBytecodeProvider().getEnhancer( new EnhancementContextWrapper( enhancementContext, loader ) );
|
||||
return enhancer.enhance( className, classfileBuffer );
|
||||
}
|
||||
catch (final Exception e) {
|
||||
|
@ -54,76 +53,4 @@ public class EnhancingClassTransformerImpl implements ClassTransformer {
|
|||
}
|
||||
}
|
||||
|
||||
// Wrapper for a EnhancementContext that allows to set the right classloader.
|
||||
private class EnhancementContextWrapper implements EnhancementContext {
|
||||
|
||||
private final ClassLoader loadingClassloader;
|
||||
private final EnhancementContext wrappedContext;
|
||||
|
||||
private EnhancementContextWrapper(EnhancementContext wrappedContext, ClassLoader loadingClassloader) {
|
||||
this.wrappedContext = wrappedContext;
|
||||
this.loadingClassloader = loadingClassloader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getLoadingClassLoader() {
|
||||
return loadingClassloader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntityClass(CtClass classDescriptor) {
|
||||
return wrappedContext.isEntityClass( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompositeClass(CtClass classDescriptor) {
|
||||
return wrappedContext.isCompositeClass( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMappedSuperclassClass(CtClass classDescriptor) {
|
||||
return wrappedContext.isMappedSuperclassClass( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doBiDirectionalAssociationManagement(CtField field) {
|
||||
return wrappedContext.doBiDirectionalAssociationManagement( field );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
|
||||
return wrappedContext.doDirtyCheckingInline( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doExtendedEnhancement(CtClass classDescriptor) {
|
||||
return wrappedContext.doExtendedEnhancement( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
|
||||
return wrappedContext.hasLazyLoadableAttributes( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersistentField(CtField ctField) {
|
||||
return wrappedContext.isPersistentField( ctField );
|
||||
}
|
||||
|
||||
@Override
|
||||
public CtField[] order(CtField[] persistentFields) {
|
||||
return wrappedContext.order( persistentFields );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLazyLoadable(CtField field) {
|
||||
return wrappedContext.isLazyLoadable( field );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMappedCollection(CtField field) {
|
||||
return wrappedContext.isMappedCollection( field );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import net.bytebuddy.implementation.bind.annotation.AllArguments;
|
||||
import net.bytebuddy.implementation.bind.annotation.FieldValue;
|
||||
import net.bytebuddy.implementation.bind.annotation.Origin;
|
||||
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
||||
import net.bytebuddy.implementation.bind.annotation.StubValue;
|
||||
import net.bytebuddy.implementation.bind.annotation.This;
|
||||
|
||||
/**
|
||||
* A proxy configuration allows the definition of an interceptor object that decides on the behavior of a proxy.
|
||||
* This interface is meant for internal use but is in a public package in order to provide code generation.
|
||||
* <p>
|
||||
* While this interface depends on Byte Buddy types, this is only true for annotation types which are silently
|
||||
* suppressed by the runtime if they are not available on a class loader. This allows using this interceptor
|
||||
* and configuration with for example OSGi without any export of Byte Buddy when using Hibernate.
|
||||
*/
|
||||
public interface ProxyConfiguration {
|
||||
|
||||
/**
|
||||
* The canonical field name for an interceptor object stored in a proxied object.
|
||||
*/
|
||||
String INTERCEPTOR_FIELD_NAME = "$$_hibernate_interceptor";
|
||||
|
||||
/**
|
||||
* Defines an interceptor object that specifies the behavior of the proxy object.
|
||||
*
|
||||
* @param interceptor The interceptor object.
|
||||
*/
|
||||
void $$_hibernate_set_interceptor(Interceptor interceptor);
|
||||
|
||||
/**
|
||||
* An interceptor object that is responsible for invoking a proxy's method.
|
||||
*/
|
||||
interface Interceptor {
|
||||
|
||||
/**
|
||||
* Intercepts a method call to a proxy.
|
||||
*
|
||||
* @param instance The proxied instance.
|
||||
* @param method The invoked method.
|
||||
* @param arguments The intercepted method arguments.
|
||||
*
|
||||
* @return The method's return value.
|
||||
*
|
||||
* @throws Throwable If the intercepted method raises an exception.
|
||||
*/
|
||||
@RuntimeType
|
||||
Object intercept(@This Object instance, @Origin Method method, @AllArguments Object[] arguments) throws Throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* An static interceptor that guards against method calls before the interceptor is set.
|
||||
*/
|
||||
class InterceptorDispatcher {
|
||||
|
||||
/**
|
||||
* Intercepts a method call to a proxy.
|
||||
*
|
||||
* @param instance The proxied instance.
|
||||
* @param method The invoked method.
|
||||
* @param arguments The method arguments.
|
||||
* @param stubValue The intercepted method's default value.
|
||||
* @param interceptor The proxy object's interceptor instance.
|
||||
*
|
||||
* @return The intercepted method's return value.
|
||||
*
|
||||
* @throws Throwable If the intercepted method raises an exception.
|
||||
*/
|
||||
@RuntimeType
|
||||
public static Object intercept(
|
||||
@This final Object instance,
|
||||
@Origin final Method method,
|
||||
@AllArguments final Object[] arguments,
|
||||
@StubValue final Object stubValue,
|
||||
@FieldValue(INTERCEPTOR_FIELD_NAME) Interceptor interceptor
|
||||
) throws Throwable {
|
||||
if ( interceptor == null ) {
|
||||
if ( method.getName().equals( "getHibernateLazyInitializer" ) ) {
|
||||
return instance;
|
||||
}
|
||||
else {
|
||||
return stubValue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return interceptor.intercept( instance, method, arguments );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.internal.util.ReflectHelper;
|
||||
import org.hibernate.proxy.ProxyConfiguration;
|
||||
import org.hibernate.proxy.pojo.BasicLazyInitializer;
|
||||
import org.hibernate.type.CompositeType;
|
||||
|
||||
import net.bytebuddy.implementation.bind.annotation.AllArguments;
|
||||
import net.bytebuddy.implementation.bind.annotation.FieldValue;
|
||||
import net.bytebuddy.implementation.bind.annotation.Origin;
|
||||
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
||||
import net.bytebuddy.implementation.bind.annotation.StubValue;
|
||||
import net.bytebuddy.implementation.bind.annotation.This;
|
||||
|
||||
import static org.hibernate.internal.CoreLogging.messageLogger;
|
||||
|
||||
public class ByteBuddyInterceptor extends BasicLazyInitializer implements ProxyConfiguration.Interceptor {
|
||||
private static final CoreMessageLogger LOG = messageLogger( ByteBuddyInterceptor.class );
|
||||
|
||||
private final Class[] interfaces;
|
||||
|
||||
public ByteBuddyInterceptor(
|
||||
String entityName,
|
||||
Class persistentClass,
|
||||
Class[] interfaces,
|
||||
Serializable id,
|
||||
Method getIdentifierMethod,
|
||||
Method setIdentifierMethod,
|
||||
CompositeType componentIdType,
|
||||
SharedSessionContractImplementor session,
|
||||
boolean overridesEquals) {
|
||||
super( entityName, persistentClass, id, getIdentifierMethod, setIdentifierMethod, componentIdType, session, overridesEquals );
|
||||
this.interfaces = interfaces;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object proxy, Method thisMethod, Object[] args) throws Throwable {
|
||||
Object result;
|
||||
try {
|
||||
result = this.invoke( thisMethod, args, proxy );
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw 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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object serializableProxy() {
|
||||
return new SerializableProxy(
|
||||
getEntityName(),
|
||||
persistentClass,
|
||||
interfaces,
|
||||
getIdentifier(),
|
||||
( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ),
|
||||
getIdentifierMethod,
|
||||
setIdentifierMethod,
|
||||
componentIdType
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
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.proxy.ProxyConfiguration;
|
||||
import org.hibernate.type.CompositeType;
|
||||
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.NamingStrategy;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.dynamic.scaffold.TypeValidation;
|
||||
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.FixedValue;
|
||||
import net.bytebuddy.implementation.MethodDelegation;
|
||||
import net.bytebuddy.implementation.SuperMethodCall;
|
||||
import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static org.hibernate.internal.CoreLogging.messageLogger;
|
||||
|
||||
public class ByteBuddyProxyFactory implements ProxyFactory, Serializable {
|
||||
private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class );
|
||||
|
||||
private static final ConcurrentMap<Set<Class>, Class> CACHE = new ConcurrentHashMap<Set<Class>, Class>();
|
||||
|
||||
private Class persistentClass;
|
||||
private String entityName;
|
||||
private Class[] interfaces;
|
||||
private Method getIdentifierMethod;
|
||||
private Method setIdentifierMethod;
|
||||
private CompositeType componentIdType;
|
||||
private boolean overridesEquals;
|
||||
|
||||
private Class proxyClass;
|
||||
|
||||
@Override
|
||||
public void postInstantiate(
|
||||
String entityName,
|
||||
Class persistentClass,
|
||||
Set<Class> interfaces,
|
||||
Method getIdentifierMethod,
|
||||
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 = buildProxy( persistentClass, this.interfaces );
|
||||
}
|
||||
|
||||
private Class[] toArray(Set<Class> interfaces) {
|
||||
if ( interfaces == null ) {
|
||||
return ArrayHelper.EMPTY_CLASS_ARRAY;
|
||||
}
|
||||
|
||||
return interfaces.toArray( new Class[interfaces.size()] );
|
||||
}
|
||||
|
||||
public static Class buildProxy(
|
||||
final Class persistentClass,
|
||||
final Class[] interfaces) {
|
||||
Set<Class> key = new HashSet<Class>();
|
||||
if ( interfaces.length == 1 ) {
|
||||
key.add( persistentClass );
|
||||
}
|
||||
key.addAll( Arrays.asList( interfaces ) );
|
||||
|
||||
Class<?> proxy = CACHE.get( key );
|
||||
if ( proxy != null ) {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
proxy = new ByteBuddy()
|
||||
.with( TypeValidation.DISABLED )
|
||||
.with( new NamingStrategy.SuffixingRandom( "HibernateProxy" ) )
|
||||
.subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING )
|
||||
.implement( (Type[]) interfaces )
|
||||
.method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) )
|
||||
.intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) )
|
||||
.method( ElementMatchers.nameStartsWith( "$$_hibernate_" ).and( ElementMatchers.isVirtual() ) )
|
||||
.intercept( SuperMethodCall.INSTANCE )
|
||||
.defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE )
|
||||
.implement( ProxyConfiguration.class )
|
||||
.intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) )
|
||||
.make()
|
||||
.load( persistentClass.getClassLoader() )
|
||||
.getLoaded();
|
||||
|
||||
Class previousProxy = CACHE.putIfAbsent( key, proxy );
|
||||
if ( previousProxy != null ) {
|
||||
proxy = previousProxy;
|
||||
}
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HibernateProxy getProxy(
|
||||
Serializable id,
|
||||
SharedSessionContractImplementor session) throws HibernateException {
|
||||
final ByteBuddyInterceptor interceptor = new ByteBuddyInterceptor(
|
||||
entityName,
|
||||
persistentClass,
|
||||
interfaces,
|
||||
id,
|
||||
getIdentifierMethod,
|
||||
setIdentifierMethod,
|
||||
componentIdType,
|
||||
session,
|
||||
overridesEquals
|
||||
);
|
||||
|
||||
try {
|
||||
final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance();
|
||||
( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor );
|
||||
|
||||
return proxy;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
LOG.error( LOG.bytecodeEnhancementFailed( entityName ), t );
|
||||
throw new HibernateException( LOG.bytecodeEnhancementFailed( entityName ), t );
|
||||
}
|
||||
}
|
||||
|
||||
public static HibernateProxy deserializeProxy(SerializableProxy serializableProxy) {
|
||||
final ByteBuddyInterceptor interceptor = new ByteBuddyInterceptor(
|
||||
serializableProxy.getEntityName(),
|
||||
serializableProxy.getPersistentClass(),
|
||||
serializableProxy.getInterfaces(),
|
||||
serializableProxy.getId(),
|
||||
resolveIdGetterMethod( serializableProxy ),
|
||||
resolveIdSetterMethod( serializableProxy ),
|
||||
serializableProxy.getComponentIdType(),
|
||||
null,
|
||||
ReflectHelper.overridesEquals( serializableProxy.getPersistentClass() )
|
||||
);
|
||||
|
||||
// note: interface is assumed to already contain HibernateProxy.class
|
||||
try {
|
||||
final Class proxyClass = buildProxy(
|
||||
serializableProxy.getPersistentClass(),
|
||||
serializableProxy.getInterfaces()
|
||||
);
|
||||
final HibernateProxy proxy = (HibernateProxy) proxyClass.newInstance();
|
||||
( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor );
|
||||
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,111 @@
|
|||
/*
|
||||
* 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.bytebuddy;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.hibernate.proxy.AbstractSerializableProxy;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.type.CompositeType;
|
||||
|
||||
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;
|
||||
|
||||
public SerializableProxy(
|
||||
String entityName,
|
||||
Class persistentClass,
|
||||
Class[] interfaces,
|
||||
Serializable id,
|
||||
Boolean readOnly,
|
||||
Method getIdentifierMethod,
|
||||
Method setIdentifierMethod,
|
||||
CompositeType componentIdType) {
|
||||
super( entityName, id, readOnly );
|
||||
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 Serializable 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;
|
||||
}
|
||||
|
||||
private Object readResolve() {
|
||||
HibernateProxy proxy = ByteBuddyProxyFactory.deserializeProxy( this );
|
||||
setReadOnlyBeforeAttachedToSession( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() );
|
||||
return proxy;
|
||||
}
|
||||
}
|
|
@ -126,8 +126,8 @@ public class JavassistProxyFactory implements ProxyFactory, Serializable {
|
|||
return proxy;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
LOG.error( LOG.javassistEnhancementFailed( entityName ), t );
|
||||
throw new HibernateException( LOG.javassistEnhancementFailed( entityName ), t );
|
||||
LOG.error( LOG.bytecodeEnhancementFailed( entityName ), t );
|
||||
throw new HibernateException( LOG.bytecodeEnhancementFailed( entityName ), t );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,7 +158,7 @@ public class JavassistProxyFactory implements ProxyFactory, Serializable {
|
|||
return proxy;
|
||||
}
|
||||
catch ( Throwable t ) {
|
||||
final String message = LOG.javassistEnhancementFailed( serializableProxy.getEntityName() );
|
||||
final String message = LOG.bytecodeEnhancementFailed( serializableProxy.getEntityName() );
|
||||
LOG.error( message, t );
|
||||
throw new HibernateException( message, t );
|
||||
}
|
||||
|
|
|
@ -14,11 +14,9 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.cfg.Environment;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
|
@ -39,9 +37,7 @@ import org.apache.tools.ant.types.FileSet;
|
|||
public class EnhancementTask extends Task {
|
||||
private List<FileSet> filesets = new ArrayList<FileSet>();
|
||||
|
||||
// Enhancer also builds CtClass instances. Might make sense to share these (ClassPool).
|
||||
private final ClassPool classPool = new ClassPool( false );
|
||||
private final Enhancer enhancer = new Enhancer( new DefaultEnhancementContext() );
|
||||
private final Enhancer enhancer = Environment.getBytecodeProvider().getEnhancer( new DefaultEnhancementContext() );
|
||||
|
||||
public void addFileset(FileSet set) {
|
||||
this.filesets.add( set );
|
||||
|
@ -72,8 +68,7 @@ public class EnhancementTask extends Task {
|
|||
|
||||
private void processClassFile(File javaClassFile) {
|
||||
try {
|
||||
final CtClass ctClass = classPool.makeClass( new FileInputStream( javaClassFile ) );
|
||||
byte[] result = enhancer.enhance( ctClass.getName(), ctClass.toBytecode() );
|
||||
byte[] result = enhancer.enhance( javaClassFile );
|
||||
if ( result != null ) {
|
||||
writeEnhancedClass( javaClassFile, result );
|
||||
}
|
||||
|
|
|
@ -11,10 +11,9 @@ import java.io.InputStream;
|
|||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.util.List;
|
||||
|
||||
import javassist.CtClass;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
|
||||
import org.hibernate.jpa.internal.enhance.EnhancingClassTransformerImpl;
|
||||
|
||||
/**
|
||||
|
@ -31,29 +30,36 @@ public class InstrumentedClassLoader extends ClassLoader {
|
|||
@Override
|
||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||
// Do not instrument the following packages
|
||||
if (name != null
|
||||
&& (name.startsWith("java.lang.") ||
|
||||
name.startsWith("java.util.")))
|
||||
return getParent().loadClass(name);
|
||||
if ( name != null
|
||||
&& ( name.startsWith( "java.lang." ) ||
|
||||
name.startsWith( "java.util." ) ) ) {
|
||||
return getParent().loadClass( name );
|
||||
}
|
||||
Class c = findLoadedClass( name );
|
||||
if ( c != null ) return c;
|
||||
if ( c != null ) {
|
||||
return c;
|
||||
}
|
||||
|
||||
byte[] transformed = loadClassBytes( name );
|
||||
|
||||
byte[] transformed = loadClassBytes(name);
|
||||
|
||||
return defineClass( name, transformed, 0, transformed.length );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specialized {@link ClassLoader#loadClass(String)} that returns the class
|
||||
* as a byte array.
|
||||
*
|
||||
*
|
||||
* @param name
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public byte[] loadClassBytes(String name) throws ClassNotFoundException {
|
||||
InputStream is = this.getResourceAsStream( name.replace( ".", "/" ) + ".class" );
|
||||
if ( is == null ) throw new ClassNotFoundException( name );
|
||||
if ( is == null ) {
|
||||
throw new ClassNotFoundException( name );
|
||||
}
|
||||
byte[] buffer = new byte[409600];
|
||||
byte[] originalClass = new byte[0];
|
||||
int r = 0;
|
||||
|
@ -64,13 +70,13 @@ public class InstrumentedClassLoader extends ClassLoader {
|
|||
throw new ClassNotFoundException( name + " not found", e );
|
||||
}
|
||||
while ( r >= buffer.length ) {
|
||||
byte[] temp = new byte[ originalClass.length + buffer.length ];
|
||||
byte[] temp = new byte[originalClass.length + buffer.length];
|
||||
System.arraycopy( originalClass, 0, temp, 0, originalClass.length );
|
||||
System.arraycopy( buffer, 0, temp, originalClass.length, buffer.length );
|
||||
originalClass = temp;
|
||||
}
|
||||
if ( r != -1 ) {
|
||||
byte[] temp = new byte[ originalClass.length + r ];
|
||||
byte[] temp = new byte[originalClass.length + r];
|
||||
System.arraycopy( originalClass, 0, temp, 0, originalClass.length );
|
||||
System.arraycopy( buffer, 0, temp, originalClass.length, r );
|
||||
originalClass = temp;
|
||||
|
@ -84,13 +90,20 @@ public class InstrumentedClassLoader extends ClassLoader {
|
|||
|
||||
EnhancingClassTransformerImpl t = new EnhancingClassTransformerImpl( getEnhancementContext( getParent(), entities ) );
|
||||
try {
|
||||
return t.transform(
|
||||
byte[] transformed = t.transform(
|
||||
getParent(),
|
||||
name,
|
||||
null,
|
||||
null,
|
||||
originalClass
|
||||
);
|
||||
|
||||
if ( transformed == null ) {
|
||||
return originalClass;
|
||||
}
|
||||
else {
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
catch (IllegalClassFormatException e) {
|
||||
throw new ClassNotFoundException( name + " not found", e );
|
||||
|
@ -110,12 +123,12 @@ public class InstrumentedClassLoader extends ClassLoader {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntityClass(CtClass classDescriptor) {
|
||||
public boolean isEntityClass(UnloadedClass classDescriptor) {
|
||||
return entities.contains( classDescriptor.getName() ) && super.isEntityClass( classDescriptor );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompositeClass(CtClass classDescriptor) {
|
||||
public boolean isCompositeClass(UnloadedClass classDescriptor) {
|
||||
return entities.contains( classDescriptor.getName() ) && super.isCompositeClass( classDescriptor );
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,24 +6,14 @@
|
|||
*/
|
||||
package org.hibernate.jpa.test.enhancement;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtMethod;
|
||||
import javassist.bytecode.AttributeInfo;
|
||||
import javassist.bytecode.StackMapTable;
|
||||
|
||||
import org.hibernate.engine.spi.Managed;
|
||||
import org.hibernate.engine.spi.ManagedComposite;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
import org.hibernate.jpa.test.enhancement.cases.domain.Simple;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -36,10 +26,10 @@ import static org.junit.Assert.assertTrue;
|
|||
* @author Dustin Schultz
|
||||
*/
|
||||
public class InterceptFieldClassFileTransformerTest {
|
||||
|
||||
|
||||
private List<String> entities = new ArrayList<String>();
|
||||
private InstrumentedClassLoader loader = null;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
entities.add( Simple.class.getName() );
|
||||
|
@ -48,13 +38,13 @@ public class InterceptFieldClassFileTransformerTest {
|
|||
cl.setEntities( entities );
|
||||
this.loader = cl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that class file enhancement works.
|
||||
*
|
||||
*
|
||||
* @throws Exception in case the test fails.
|
||||
*/
|
||||
@Test
|
||||
@Test
|
||||
public void testEnhancement() throws Exception {
|
||||
// sanity check that the class is unmodified and does not contain getFieldHandler()
|
||||
assertFalse( implementsManaged( Simple.class ) );
|
||||
|
@ -75,35 +65,4 @@ public class InterceptFieldClassFileTransformerTest {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that methods that were enhanced by javassist have
|
||||
* StackMapTables for java verification. Without these,
|
||||
* java.lang.VerifyError's occur in JDK7.
|
||||
*
|
||||
* @throws ClassNotFoundException
|
||||
* @throws InstantiationException
|
||||
* @throws IllegalAccessException
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-7747")
|
||||
public void testStackMapTableEnhancment() throws ClassNotFoundException,
|
||||
InstantiationException, IllegalAccessException, IOException {
|
||||
byte[] classBytes = loader.loadClassBytes(entities.get(0));
|
||||
ClassPool classPool = new ClassPool();
|
||||
CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(
|
||||
classBytes));
|
||||
for (CtMethod ctMethod : ctClass.getMethods()) {
|
||||
//Only check methods that were added by javassist
|
||||
if (ctMethod.getName().startsWith("$javassist_")) {
|
||||
AttributeInfo attributeInfo = ctMethod
|
||||
.getMethodInfo().getCodeAttribute()
|
||||
.getAttribute(StackMapTable.tag);
|
||||
Assert.assertNotNull(attributeInfo);
|
||||
StackMapTable smt = (StackMapTable)attributeInfo;
|
||||
Assert.assertNotNull(smt.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,18 +75,18 @@ public class DuplicatedDiscriminatorValueTest extends BaseUnitTestCase {
|
|||
|
||||
@Entity
|
||||
@DiscriminatorValue(DISCRIMINATOR_VALUE) // Duplicated discriminator value in single hierarchy.
|
||||
private static class Building1 extends Building {
|
||||
public static class Building1 extends Building {
|
||||
}
|
||||
|
||||
@Entity
|
||||
@DiscriminatorValue(DISCRIMINATOR_VALUE) // Duplicated discriminator value in single hierarchy.
|
||||
private static class Building2 extends Building {
|
||||
public static class Building2 extends Building {
|
||||
}
|
||||
|
||||
@Entity
|
||||
@DiscriminatorColumn(name = "entity_type")
|
||||
@DiscriminatorValue("F")
|
||||
private static class Furniture {
|
||||
public static class Furniture {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Integer id;
|
||||
|
@ -94,6 +94,6 @@ public class DuplicatedDiscriminatorValueTest extends BaseUnitTestCase {
|
|||
|
||||
@Entity
|
||||
@DiscriminatorValue(DISCRIMINATOR_VALUE) // Duplicated discriminator value in different hierarchy.
|
||||
private static class Chair extends Furniture {
|
||||
public static class Chair extends Furniture {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,40 +4,20 @@
|
|||
* 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.javassist;
|
||||
package org.hibernate.test.bytecode;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.bytecode.internal.javassist.BytecodeProviderImpl;
|
||||
import org.hibernate.cfg.Environment;
|
||||
|
||||
import org.hibernate.testing.Skip;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.hibernate.test.bytecode.Bean;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Test that the Javassist-based lazy initializer properly handles InvocationTargetExceptions
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Skip(
|
||||
condition = InvocationTargetExceptionTest.LocalSkipMatcher.class,
|
||||
message = "environment not configured for javassist bytecode provider"
|
||||
)
|
||||
public class InvocationTargetExceptionTest extends BaseCoreFunctionalTestCase {
|
||||
public static class LocalSkipMatcher implements Skip.Matcher {
|
||||
@Override
|
||||
public boolean isMatch() {
|
||||
return ! BytecodeProviderImpl.class.isInstance( Environment.getBytecodeProvider() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getMappings() {
|
||||
return new String[] { "bytecode/Bean.hbm.xml" };
|
|
@ -4,15 +4,15 @@
|
|||
* 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.javassist;
|
||||
package org.hibernate.test.bytecode;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.bytecode.internal.javassist.BulkAccessor;
|
||||
import org.hibernate.bytecode.internal.javassist.BytecodeProviderImpl;
|
||||
import org.hibernate.bytecode.spi.BytecodeProvider;
|
||||
import org.hibernate.bytecode.spi.ReflectionOptimizer;
|
||||
import org.hibernate.test.bytecode.Bean;
|
||||
import org.hibernate.test.bytecode.BeanReflectionHelper;
|
||||
import org.hibernate.cfg.Environment;
|
||||
|
||||
import org.hibernate.testing.junit4.BaseUnitTestCase;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -34,7 +34,7 @@ public class ReflectionOptimizerTest extends BaseUnitTestCase {
|
|||
|
||||
@Test
|
||||
public void testReflectionOptimization() {
|
||||
BytecodeProviderImpl provider = new BytecodeProviderImpl();
|
||||
BytecodeProvider provider = Environment.getBytecodeProvider();
|
||||
ReflectionOptimizer optimizer = provider.getReflectionOptimizer(
|
||||
Bean.class,
|
||||
BeanReflectionHelper.getGetterNames(),
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.test.bytecode.enhancement;
|
||||
|
||||
import javassist.CtClass;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
|
||||
|
||||
import org.hibernate.test.bytecode.enhancement.association.InheritedAttributeAssociationTestTask;
|
||||
import org.hibernate.test.bytecode.enhancement.lazy.group.LazyGroupUpdateTestTask;
|
||||
|
@ -50,6 +50,7 @@ import org.hibernate.test.bytecode.enhancement.merge.CompositeMergeTestTask;
|
|||
import org.hibernate.test.bytecode.enhancement.ondemandload.LazyCollectionWithClearedSessionTestTask;
|
||||
import org.hibernate.test.bytecode.enhancement.ondemandload.LazyCollectionWithClosedSessionTestTask;
|
||||
import org.hibernate.test.bytecode.enhancement.ondemandload.LazyEntityLoadingWithClosedSessionTestTask;
|
||||
import org.hibernate.test.bytecode.enhancement.otherentityentrycontext.OtherEntityEntryContextTestTask;
|
||||
import org.hibernate.test.bytecode.enhancement.pk.EmbeddedPKTestTask;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -112,7 +113,7 @@ public class EnhancerTest extends BaseUnitTestCase {
|
|||
public void testLazyProxyOnEnhancedEntity() {
|
||||
EnhancerTestUtils.runEnhancerTestTask( LazyProxyOnEnhancedEntityTestTask.class, new EnhancerTestContext() {
|
||||
@Override
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
|
||||
public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) {
|
||||
return false;
|
||||
}
|
||||
} );
|
||||
|
@ -176,7 +177,7 @@ public class EnhancerTest extends BaseUnitTestCase {
|
|||
EnhancerTestUtils.runEnhancerTestTask( MappedSuperclassTestTask.class );
|
||||
EnhancerTestUtils.runEnhancerTestTask( MappedSuperclassTestTask.class, new EnhancerTestContext() {
|
||||
@Override
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
|
||||
public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) {
|
||||
// HHH-10981 - Without lazy loading, the generation of getters and setters has a different code path
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class ProxyInterfaceClassLoaderTest extends BaseCoreFunctionalTestCase {
|
|||
s.close();
|
||||
}
|
||||
|
||||
interface IPerson {
|
||||
public interface IPerson {
|
||||
|
||||
int getId();
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ dependencies {
|
|||
compile( libraries.commons_annotations )
|
||||
compile( libraries.jpa )
|
||||
compile( libraries.javassist )
|
||||
compile( libraries.byteBuddy )
|
||||
compile( libraries.jta )
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ dependencies {
|
|||
testCompile( project( ':hibernate-testing' ) )
|
||||
testCompile( project( path: ':hibernate-core', configuration: 'tests' ) )
|
||||
testRuntime( libraries.javassist )
|
||||
testRuntime( libraries.byteBuddy )
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Java 9 ftw!
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.envers.internal.tools;
|
||||
|
||||
import javassist.util.proxy.ProxyFactory;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
|
@ -76,7 +75,7 @@ public abstract class EntityTools {
|
|||
if ( clazz == null ) {
|
||||
return null;
|
||||
}
|
||||
else if ( ProxyFactory.isProxyClass( clazz ) ) {
|
||||
else if ( HibernateProxy.class.isAssignableFrom( clazz ) ) {
|
||||
// Get the source class of Javassist proxy instance.
|
||||
return (Class<T>) clazz.getSuperclass();
|
||||
}
|
||||
|
|
|
@ -37,7 +37,8 @@ ext {
|
|||
minorSlot: minorSlot,
|
||||
version: rootProject.hibernateTargetVersion,
|
||||
wildflyVersion: wildflyVersion,
|
||||
javassistVersion: javassistVersion
|
||||
javassistVersion: javassistVersion,
|
||||
byteBuddyVersion: byteBuddyVersion
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -54,12 +55,14 @@ configurations {
|
|||
wildflyDist
|
||||
|
||||
javassist
|
||||
byteBuddy
|
||||
}
|
||||
|
||||
dependencies {
|
||||
jipijapa "org.wildfly:jipijapa-hibernate5:${wildflyVersion}"
|
||||
wildflyDist "org.wildfly:wildfly-dist:${wildflyVersion}@zip"
|
||||
javassist libraries.javassist
|
||||
byteBuddy libraries.byteBuddy
|
||||
|
||||
testCompile project( ":hibernate-core" )
|
||||
testCompile project( ":hibernate-envers" )
|
||||
|
@ -121,6 +124,8 @@ task createModulesZip(type: Zip, dependsOn: [copyAndExpandModuleXml]) {
|
|||
|
||||
// also need Javassist's jar
|
||||
from configurations.javassist
|
||||
// also need Byte Buddy's jar
|
||||
from configurations.byteBuddy
|
||||
}
|
||||
|
||||
into( 'org/hibernate/infinispan/' + slot ) {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
<resource-root path="hibernate-core-${version}.jar"/>
|
||||
<resource-root path="hibernate-envers-${version}.jar"/>
|
||||
<resource-root path="javassist-${javassistVersion}.jar"/>
|
||||
<resource-root path="byte-buddy-${byteBuddyVersion}.jar"/>
|
||||
</resources>
|
||||
|
||||
<dependencies>
|
||||
|
|
|
@ -47,6 +47,7 @@ dependencies {
|
|||
|
||||
testRuntime('jaxen:jaxen:1.1')
|
||||
testRuntime(libraries.javassist)
|
||||
testRuntime(libraries.byteBuddy)
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -10,6 +10,8 @@ import javassist.CtClass;
|
|||
import javassist.CtField;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedField;
|
||||
|
||||
/**
|
||||
* Enhancement context used in tests
|
||||
|
@ -19,27 +21,27 @@ import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
|||
public class EnhancerTestContext extends DefaultEnhancementContext {
|
||||
|
||||
@Override
|
||||
public boolean doBiDirectionalAssociationManagement(CtField field) {
|
||||
public boolean doBiDirectionalAssociationManagement(UnloadedField field) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
|
||||
public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doExtendedEnhancement(CtClass classDescriptor) {
|
||||
public boolean doExtendedEnhancement(UnloadedClass classDescriptor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
|
||||
public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLazyLoadable(CtField field) {
|
||||
public boolean isLazyLoadable(UnloadedField field) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.hibernate.HibernateException;
|
|||
import org.hibernate.LockMode;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.engine.internal.MutableEntityEntryFactory;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||
|
@ -57,8 +58,8 @@ public abstract class EnhancerTestUtils extends BaseUnitTestCase {
|
|||
CtClass entityCtClass = generateCtClassForAnEntity( classToEnhance );
|
||||
|
||||
byte[] original = entityCtClass.toBytecode();
|
||||
byte[] enhanced = new Enhancer( new EnhancerTestContext() ).enhance( entityCtClass.getName(), original );
|
||||
assertFalse( "entity was not enhanced", Arrays.equals( original, enhanced ) );
|
||||
byte[] enhanced = Environment.getBytecodeProvider().getEnhancer( new EnhancerTestContext() ).enhance( entityCtClass.getName(), original );
|
||||
assertFalse( "entity was not enhanced", enhanced == null );
|
||||
log.infof( "enhanced entity [%s]", entityCtClass.getName() );
|
||||
|
||||
ClassPool cp = new ClassPool( false );
|
||||
|
@ -127,7 +128,7 @@ public abstract class EnhancerTestUtils extends BaseUnitTestCase {
|
|||
private static ClassLoader getEnhancerClassLoader(EnhancementContext context, String packageName) {
|
||||
return new ClassLoader() {
|
||||
|
||||
private Enhancer enhancer = new Enhancer( context );
|
||||
private Enhancer enhancer = Environment.getBytecodeProvider().getEnhancer( context );
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@Override
|
||||
|
@ -151,12 +152,17 @@ public abstract class EnhancerTestUtils extends BaseUnitTestCase {
|
|||
|
||||
byte[] enhanced = enhancer.enhance( name, original );
|
||||
|
||||
File f = new File( workingDir + File.separator + name.replace( ".", File.separator ) + ".class" );
|
||||
f.getParentFile().mkdirs();
|
||||
f.createNewFile();
|
||||
FileOutputStream out = new FileOutputStream( f );
|
||||
out.write( enhanced );
|
||||
out.close();
|
||||
if ( enhanced != null ) {
|
||||
File f = new File( workingDir + File.separator + name.replace( ".", File.separator ) + ".class" );
|
||||
f.getParentFile().mkdirs();
|
||||
f.createNewFile();
|
||||
FileOutputStream out = new FileOutputStream( f );
|
||||
out.write( enhanced );
|
||||
out.close();
|
||||
}
|
||||
else {
|
||||
enhanced = original;
|
||||
}
|
||||
|
||||
return defineClass( name, enhanced, 0, enhanced.length );
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ ext {
|
|||
cdiVersion = '1.1'
|
||||
|
||||
javassistVersion = '3.20.0-GA'
|
||||
byteBuddyVersion = '1.5.4'
|
||||
|
||||
// Wildfly version targeted by module ZIP; Arquillian/Shrinkwrap versions used for CDI testing and testing the module ZIP
|
||||
wildflyVersion = '10.0.0.Final'
|
||||
|
@ -48,6 +49,9 @@ ext {
|
|||
// Javassist
|
||||
javassist: "org.javassist:javassist:${javassistVersion}",
|
||||
|
||||
// Byte Buddy
|
||||
byteBuddy: "net.bytebuddy:byte-buddy:${byteBuddyVersion}",
|
||||
|
||||
// javax
|
||||
jpa: 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final',
|
||||
// There is a bug in the OSGi information in the JBoss one. See https://issues.jboss.org/browse/JBEE-160
|
||||
|
|
|
@ -29,6 +29,7 @@ dependencies {
|
|||
compile( project(':hibernate-core') ) { transitive = false }
|
||||
compile( libraries.jpa ) { transitive = false }
|
||||
compile( libraries.javassist ) { transitive = false }
|
||||
compile( libraries.byteBuddy ) { transitive = false }
|
||||
compile 'org.codehaus.plexus:plexus-utils:3.0.1'
|
||||
runtime( libraries.maven_core )
|
||||
runtime( libraries.maven_artifact )
|
||||
|
@ -37,6 +38,7 @@ dependencies {
|
|||
runtime( project(':hibernate-core') )
|
||||
runtime( libraries.jpa )
|
||||
runtime( libraries.javassist )
|
||||
runtime( libraries.byteBuddy )
|
||||
runtime 'org.codehaus.plexus:plexus-utils:3.0.1'
|
||||
}
|
||||
|
||||
|
@ -56,6 +58,7 @@ task processPluginXml(type: Copy) {
|
|||
+ generateMavenDependency(libraries.jta)\
|
||||
+ generateMavenDependency(libraries.commons_annotations)\
|
||||
+ generateMavenDependency(libraries.javassist)\
|
||||
+ generateMavenDependency(libraries.byteBuddy)\
|
||||
+ generateMavenDependency(libraries.logging)\
|
||||
+ generateMavenDependency("org.hibernate:hibernate-core:" + project.version)])
|
||||
}
|
||||
|
|
|
@ -8,11 +8,9 @@ package org.hibernate.orm.tooling.maven;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
@ -21,10 +19,6 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.plugin.AbstractMojo;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
|
@ -39,6 +33,9 @@ import org.apache.maven.project.MavenProject;
|
|||
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedField;
|
||||
import org.hibernate.cfg.Environment;
|
||||
|
||||
/**
|
||||
* This plugin will enhance Entity objects.
|
||||
|
@ -105,27 +102,27 @@ public class MavenEnhancePlugin extends AbstractMojo {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean doBiDirectionalAssociationManagement(CtField field) {
|
||||
public boolean doBiDirectionalAssociationManagement(UnloadedField field) {
|
||||
return enableAssociationManagement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
|
||||
public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) {
|
||||
return enableDirtyTracking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
|
||||
public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) {
|
||||
return enableLazyInitialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLazyLoadable(CtField field) {
|
||||
public boolean isLazyLoadable(UnloadedField field) {
|
||||
return enableLazyInitialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doExtendedEnhancement(CtClass classDescriptor) {
|
||||
public boolean doExtendedEnhancement(UnloadedClass classDescriptor) {
|
||||
return enableExtendedEnhancement;
|
||||
}
|
||||
};
|
||||
|
@ -134,28 +131,19 @@ public class MavenEnhancePlugin extends AbstractMojo {
|
|||
getLog().warn( "Extended enhancement is enabled. Classes other than entities may be modified. You should consider access the entities using getter/setter methods and disable this property. Use at your own risk." );
|
||||
}
|
||||
|
||||
final Enhancer enhancer = new Enhancer( enhancementContext );
|
||||
final ClassPool classPool = new ClassPool( false );
|
||||
final Enhancer enhancer = Environment.getBytecodeProvider().getEnhancer( enhancementContext );
|
||||
|
||||
for ( File file : sourceSet ) {
|
||||
final CtClass ctClass = toCtClass( file, classPool );
|
||||
if ( ctClass == null ) {
|
||||
|
||||
final byte[] enhancedBytecode = doEnhancement( file, enhancer );
|
||||
|
||||
if ( enhancedBytecode == null ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( !enableLazyInitialization ) {
|
||||
if ( !enhancementContext.isEntityClass( ctClass )
|
||||
&& !enhancementContext.isCompositeClass( ctClass )
|
||||
&& !enhancementContext.isMappedSuperclassClass( ctClass ) ) {
|
||||
getLog().info( "Skipping class file [" + file.getAbsolutePath() + "], not an entity nor embeddable" );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
writeOutEnhancedClass( enhancedBytecode, file );
|
||||
|
||||
final byte[] enhancedBytecode = doEnhancement( ctClass, enhancer );
|
||||
writeOutEnhancedClass( enhancedBytecode, ctClass, file );
|
||||
|
||||
getLog().info( "Successfully enhanced class [" + ctClass.getName() + "]" );
|
||||
getLog().info( "Successfully enhanced class [" + file + "]" );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,47 +192,12 @@ public class MavenEnhancePlugin extends AbstractMojo {
|
|||
return new URLClassLoader( urls.toArray( new URL[urls.size()] ), Enhancer.class.getClassLoader() );
|
||||
}
|
||||
|
||||
private CtClass toCtClass(File file, ClassPool classPool) throws MojoExecutionException {
|
||||
private byte[] doEnhancement(File javaClassFile, Enhancer enhancer) throws MojoExecutionException {
|
||||
try {
|
||||
final InputStream is = new FileInputStream( file.getAbsolutePath() );
|
||||
|
||||
try {
|
||||
return classPool.makeClass( is );
|
||||
}
|
||||
catch (IOException e) {
|
||||
String msg = "Javassist unable to load class in preparation for enhancing: " + file.getAbsolutePath();
|
||||
if ( failOnError ) {
|
||||
throw new MojoExecutionException( msg, e );
|
||||
}
|
||||
getLog().warn( msg );
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
is.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
getLog().info( "Was unable to close InputStream : " + file.getAbsolutePath(), e );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
// should never happen, but...
|
||||
String msg = "Unable to locate class file for InputStream: " + file.getAbsolutePath();
|
||||
if ( failOnError ) {
|
||||
throw new MojoExecutionException( msg, e );
|
||||
}
|
||||
getLog().warn( msg );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] doEnhancement(CtClass ctClass, Enhancer enhancer) throws MojoExecutionException {
|
||||
try {
|
||||
return enhancer.enhance( ctClass.getName(), ctClass.toBytecode() );
|
||||
return enhancer.enhance(javaClassFile);
|
||||
}
|
||||
catch (Exception e) {
|
||||
String msg = "Unable to enhance class: " + ctClass.getName();
|
||||
String msg = "Unable to enhance class: " + javaClassFile.getName();
|
||||
if ( failOnError ) {
|
||||
throw new MojoExecutionException( msg, e );
|
||||
}
|
||||
|
@ -283,22 +236,19 @@ public class MavenEnhancePlugin extends AbstractMojo {
|
|||
Collections.addAll( this.sourceSet, files );
|
||||
}
|
||||
|
||||
private void writeOutEnhancedClass(byte[] enhancedBytecode, CtClass ctClass, File file) throws MojoExecutionException{
|
||||
if ( enhancedBytecode == null ) {
|
||||
return;
|
||||
}
|
||||
private void writeOutEnhancedClass(byte[] enhancedBytecode, File file) throws MojoExecutionException {
|
||||
try {
|
||||
if ( file.delete() ) {
|
||||
if ( !file.createNewFile() ) {
|
||||
getLog().error( "Unable to recreate class file [" + ctClass.getName() + "]" );
|
||||
getLog().error( "Unable to recreate class file [" + file.getName() + "]" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
getLog().error( "Unable to delete class file [" + ctClass.getName() + "]" );
|
||||
getLog().error( "Unable to delete class file [" + file.getName() + "]" );
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
getLog().warn( "Problem preparing class file for writing out enhancements [" + ctClass.getName() + "]" );
|
||||
getLog().warn( "Problem preparing class file for writing out enhancements [" + file.getName() + "]" );
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -308,7 +258,7 @@ public class MavenEnhancePlugin extends AbstractMojo {
|
|||
outputStream.flush();
|
||||
}
|
||||
catch (IOException e) {
|
||||
String msg = String.format( "Error writing to enhanced class [%s] to file [%s]", ctClass.getName(), file.getAbsolutePath() );
|
||||
String msg = String.format( "Error writing to enhanced class [%s] to file [%s]", file.getName(), file.getAbsolutePath() );
|
||||
if ( failOnError ) {
|
||||
throw new MojoExecutionException( msg, e );
|
||||
}
|
||||
|
@ -317,7 +267,6 @@ public class MavenEnhancePlugin extends AbstractMojo {
|
|||
finally {
|
||||
try {
|
||||
outputStream.close();
|
||||
ctClass.detach();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ dependencies {
|
|||
compile( project( ':hibernate-core' ) )
|
||||
compile( libraries.jpa )
|
||||
compile( libraries.javassist )
|
||||
compile( libraries.byteBuddy )
|
||||
compile gradleApi()
|
||||
compile localGroovy()
|
||||
}
|
||||
|
|
|
@ -7,21 +7,15 @@
|
|||
package org.hibernate.orm.tooling.gradle;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
|
@ -36,6 +30,9 @@ import org.gradle.api.tasks.SourceSet;
|
|||
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
|
||||
import org.hibernate.bytecode.enhance.spi.UnloadedField;
|
||||
import org.hibernate.cfg.Environment;
|
||||
|
||||
/**
|
||||
* The Hibernate Gradle plugin. Adds Hibernate build-time capabilities into your Gradle-based build.
|
||||
|
@ -92,37 +89,36 @@ public class HibernatePlugin implements Plugin<Project> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean doBiDirectionalAssociationManagement(CtField field) {
|
||||
public boolean doBiDirectionalAssociationManagement(UnloadedField field) {
|
||||
return hibernateExtension.enhance.getEnableAssociationManagement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
|
||||
public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) {
|
||||
return hibernateExtension.enhance.getEnableDirtyTracking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
|
||||
public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) {
|
||||
return hibernateExtension.enhance.getEnableLazyInitialization();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLazyLoadable(CtField field) {
|
||||
public boolean isLazyLoadable(UnloadedField field) {
|
||||
return hibernateExtension.enhance.getEnableLazyInitialization();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doExtendedEnhancement(CtClass classDescriptor) {
|
||||
public boolean doExtendedEnhancement(UnloadedClass classDescriptor) {
|
||||
return hibernateExtension.enhance.getEnableExtendedEnhancement();
|
||||
}
|
||||
};
|
||||
|
||||
if ( hibernateExtension.enhance.getEnableExtendedEnhancement() ) {
|
||||
logger.warn( "Extended enhancement is enabled. Classes other than entities may be modified. You should consider access the entities using getter/setter methods and disable this property. Use at your own risk." );
|
||||
logger.warn("Extended enhancement is enabled. Classes other than entities may be modified. You should consider access the entities using getter/setter methods and disable this property. Use at your own risk." );
|
||||
}
|
||||
|
||||
final Enhancer enhancer = new Enhancer( enhancementContext );
|
||||
final ClassPool classPool = new ClassPool( false );
|
||||
final Enhancer enhancer = Environment.getBytecodeProvider().getEnhancer( enhancementContext );
|
||||
|
||||
final FileTree fileTree = project.fileTree( sourceSet.getOutput().getClassesDir() );
|
||||
for ( File file : fileTree ) {
|
||||
|
@ -130,19 +126,14 @@ public class HibernatePlugin implements Plugin<Project> {
|
|||
continue;
|
||||
}
|
||||
|
||||
final CtClass ctClass = toCtClass( file, classPool );
|
||||
|
||||
if ( !enhancementContext.isEntityClass( ctClass )
|
||||
&& !enhancementContext.isCompositeClass( ctClass )
|
||||
&& !enhancementContext.isMappedSuperclassClass( ctClass ) ) {
|
||||
logger.info( "Skipping class [" + file.getAbsolutePath() + "], not an entity nor embeddable" );
|
||||
continue;
|
||||
final byte[] enhancedBytecode = doEnhancement( file, enhancer );
|
||||
if ( enhancedBytecode != null ) {
|
||||
writeOutEnhancedClass( enhancedBytecode, file );
|
||||
logger.info( "Successfully enhanced class [" + file + "]" );
|
||||
}
|
||||
else {
|
||||
logger.info( "Skipping class [" + file.getAbsolutePath() + "], not an entity nor embeddable" );
|
||||
}
|
||||
|
||||
final byte[] enhancedBytecode = doEnhancement( ctClass, enhancer );
|
||||
writeOutEnhancedClass( enhancedBytecode, ctClass, file );
|
||||
|
||||
logger.info( "Successfully enhanced class [" + ctClass.getName() + "]" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,53 +155,28 @@ public class HibernatePlugin implements Plugin<Project> {
|
|||
return new URLClassLoader( urls.toArray( new URL[urls.size()] ), Enhancer.class.getClassLoader() );
|
||||
}
|
||||
|
||||
private CtClass toCtClass(File file, ClassPool classPool) {
|
||||
private byte[] doEnhancement(File javaClassFile, Enhancer enhancer) {
|
||||
try {
|
||||
final InputStream is = new FileInputStream( file.getAbsolutePath() );
|
||||
|
||||
try {
|
||||
return classPool.makeClass( is );
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new GradleException( "Javassist unable to load class in preparation for enhancing : " + file.getAbsolutePath(), e );
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
is.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.info( "Was unable to close InputStream : " + file.getAbsolutePath(), e );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
// should never happen, but...
|
||||
throw new GradleException( "Unable to locate class file for InputStream: " + file.getAbsolutePath(), e );
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] doEnhancement(CtClass ctClass, Enhancer enhancer) {
|
||||
try {
|
||||
return enhancer.enhance( ctClass.getName(), ctClass.toBytecode() );
|
||||
return enhancer.enhance( javaClassFile );
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new GradleException( "Unable to enhance class : " + ctClass.getName(), e );
|
||||
throw new GradleException( "Unable to enhance class : " + javaClassFile, e );
|
||||
}
|
||||
}
|
||||
|
||||
private void writeOutEnhancedClass(byte[] enhancedBytecode, CtClass ctClass, File file) {
|
||||
private void writeOutEnhancedClass(byte[] enhancedBytecode, File file) {
|
||||
try {
|
||||
if ( file.delete() ) {
|
||||
if ( !file.createNewFile() ) {
|
||||
logger.error( "Unable to recreate class file [" + ctClass.getName() + "]" );
|
||||
logger.error( "Unable to recreate class file [" + file.getName() + "]" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.error( "Unable to delete class file [" + ctClass.getName() + "]" );
|
||||
logger.error( "Unable to delete class file [" + file.getName() + "]" );
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.warn( "Problem preparing class file for writing out enhancements [" + ctClass.getName() + "]" );
|
||||
logger.warn( "Problem preparing class file for writing out enhancements [" + file.getName() + "]" );
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -220,12 +186,11 @@ public class HibernatePlugin implements Plugin<Project> {
|
|||
outputStream.flush();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new GradleException( "Error writing to enhanced class [" + ctClass.getName() + "] to file [" + file.getAbsolutePath() + "]", e );
|
||||
throw new GradleException( "Error writing to enhanced class [" + file.getName() + "] to file [" + file.getAbsolutePath() + "]", e );
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
outputStream.close();
|
||||
ctClass.detach();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue