[HHH-9690] reorganization of bytecode enhancer code

Enhancer class was split into 3 logical units: EntityEnhancer for regular entities; CompositeEnhancer for emmbeded entities; PersistentAttributesEnhancer to enhance the persistent fields of both types of entities
Added a few helper classes and re-worked the tests a bit as well
This commit is contained in:
barreiro 2015-03-19 16:51:58 +00:00 committed by Steve Ebersole
parent e9230758b4
commit 4def797408
36 changed files with 2168 additions and 2127 deletions

View File

@ -0,0 +1,207 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance.internal;
import javassist.CtClass;
import javassist.CtField;
import javassist.NotFoundException;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
import javax.persistence.Id;
import java.util.Collection;
/**
* utility class to generate interceptor methods
* @see org.hibernate.engine.spi.PersistentAttributeInterceptor
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public abstract class AttributeTypeDescriptor {
public abstract String buildReadInterceptionBodyFragment(String fieldName);
public abstract String buildWriteInterceptionBodyFragment(String fieldName);
public String buildInLineDirtyCheckingBodyFragment(EnhancementContext context, CtField currentValue) {
final StringBuilder builder = new StringBuilder();
try {
// should ignore primary keys
for ( Object o : currentValue.getType().getAnnotations() ) {
if ( o instanceof Id) {
return "";
}
}
builder.append( String.format( "if (%s() != null", EnhancerConstants.INTERCEPTOR_GETTER_NAME ) );
// primitives || enums
if ( currentValue.getType().isPrimitive() || currentValue.getType().isEnum() ) {
builder.append( String.format( " && %s != $1)", currentValue.getName()) );
}
// simple data types
else if ( currentValue.getType().getName().startsWith( "java.lang" )
|| currentValue.getType().getName().startsWith( "java.math.Big" )
|| currentValue.getType().getName().startsWith( "java.sql.Time" )
|| currentValue.getType().getName().startsWith( "java.sql.Date" )
|| currentValue.getType().getName().startsWith( "java.util.Date" )
|| currentValue.getType().getName().startsWith( "java.util.Calendar" ) ) {
builder.append( String.format( "&& ((%s == null) || (!%<s.equals($1))))", currentValue.getName() ) );
}
// all other objects
else {
// if the field is a collection we return since we handle that in a separate method
for ( CtClass ctClass : currentValue.getType().getInterfaces() ) {
if ( ctClass.getName().equals( Collection.class.getName() ) ) {
// if the collection is not managed we should write it to the tracker
if ( context.isMappedCollection( currentValue ) ) {
return "";
}
}
}
// TODO: for now just call equals, should probably do something else here
builder.append( String.format( "&& ((%s == null) || (!%<s.equals($1))))", currentValue.getName() ) );
}
builder.append( String.format( " { %s(\"%s\"); }", EnhancerConstants.TRACKER_CHANGER_NAME, currentValue.getName() ) );
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
catch (NotFoundException e) {
e.printStackTrace();
}
return builder.toString();
}
/* --- */
/**
* factory method to get the AttributeTypeDescriptor for a particular field type
*/
public static AttributeTypeDescriptor resolve(CtField persistentField) throws NotFoundException {
if ( persistentField.getType() == CtClass.booleanType ) {
return new PrimitiveAttributeTypeDescriptor( Boolean.TYPE );
}
else if ( persistentField.getType() == CtClass.byteType ) {
return new PrimitiveAttributeTypeDescriptor( Byte.TYPE );
}
else if ( persistentField.getType() == CtClass.charType ) {
return new PrimitiveAttributeTypeDescriptor( Character.TYPE );
}
else if ( persistentField.getType() == CtClass.shortType ) {
return new PrimitiveAttributeTypeDescriptor( Short.TYPE );
}
else if ( persistentField.getType() == CtClass.intType ) {
return new PrimitiveAttributeTypeDescriptor( Integer.TYPE );
}
else if ( persistentField.getType() == CtClass.longType ) {
return new PrimitiveAttributeTypeDescriptor( Long.TYPE );
}
else if ( persistentField.getType() == CtClass.doubleType ) {
return new PrimitiveAttributeTypeDescriptor( Double.TYPE );
}
else if ( persistentField.getType() == CtClass.floatType ) {
return new PrimitiveAttributeTypeDescriptor( Float.TYPE );
}
else {
return new ObjectAttributeTypeDescriptor( persistentField.getType() );
}
}
/* --- */
/**
* AttributeTypeDescriptor for non primitive types
*/
private static class ObjectAttributeTypeDescriptor extends AttributeTypeDescriptor {
private final String type;
private ObjectAttributeTypeDescriptor(CtClass concreteType) {
this.type = concreteType.getName();
}
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format( "" +
"if ( %3$s() != null ) {%n" +
" this.%1$s = (%2$s) %3$s().readObject(this, \"%1$s\", this.%1$s);%n" +
"}",
fieldName,
type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME);
}
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format( "" +
"%2$s localVar = $1;%n" +
"if ( %3$s() != null ) {%n" +
" localVar = (%2$s) %3$s().writeObject(this, \"%1$s\", this.%1$s, $1);%n" +
"}%n" +
"this.%1$s = localVar;",
fieldName,
type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME);
}
}
/**
* AttributeTypeDescriptor for primitive types
*/
private static class PrimitiveAttributeTypeDescriptor extends AttributeTypeDescriptor {
private final String type;
private PrimitiveAttributeTypeDescriptor(Class<?> primitiveType) {
if ( !primitiveType.isPrimitive() ) {
throw new IllegalArgumentException( "Primitive attribute type descriptor can only be used on primitive types" );
}
// capitalize first letter
this.type = primitiveType.getSimpleName().substring( 0, 1 ).toUpperCase() + primitiveType.getSimpleName().substring( 1 );
}
public String buildReadInterceptionBodyFragment(String fieldName) {
return String.format( "" +
"if (%3$s() != null ) {%n" +
" this.%1$s = %3$s().read%2$s(this, \"%1$s\", this.%1$s);%n" +
"}",
fieldName,
type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME );
}
public String buildWriteInterceptionBodyFragment(String fieldName) {
return String.format( "" +
"%2$s localVar = $1;%n" +
"if ( %4$s() != null ) {%n" +
" localVar = %4$s().write%3$s(this, \"%1$s\", this.%1$s, $1);%n" +
"}%n" +
"this.%1$s = localVar;",
fieldName,
type.toLowerCase(),
type,
EnhancerConstants.INTERCEPTOR_GETTER_NAME
);
}
}
}

View File

@ -0,0 +1,97 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance.internal;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.NotFoundException;
import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
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;
/**
* enhancer for composite (embeddable) entities
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public class CompositeEnhancer extends Enhancer {
public CompositeEnhancer(EnhancementContext context) {
super( context );
}
public void enhance(CtClass managedCtClass) {
addInterceptorHandling( managedCtClass );
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
addInLineDirtyHandling( managedCtClass );
}
new PersistentAttributesEnhancer( enhancementContext ).enhance( managedCtClass );
}
/* --- */
private void addInLineDirtyHandling(CtClass managedCtClass) {
try {
managedCtClass.addInterface( classPool.get( CompositeTracker.class.getName() ) );
final CtClass compositeCtType = classPool.get( CompositeOwnerTracker.class.getName() );
FieldWriter.addField( managedCtClass, compositeCtType, EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME );
createCompositeTrackerMethod( managedCtClass );
}
catch (NotFoundException nfe) {
nfe.printStackTrace();
}
}
private void createCompositeTrackerMethod(CtClass managedCtClass) {
try {
MethodWriter.write( managedCtClass, "" +
"public void %1$s(String name, %3$s tracker) {%n" +
" if (%2$s == null) { %2$s = new %4$s(); }%n" +
" %2$s.add(name, tracker);%n" +
"}",
EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME,
CompositeOwner.class.getName(),
CompositeOwnerTracker.class.getName() );
MethodWriter.write( managedCtClass, "" +
"public void %1$s(String name) {%n" +
" if (%2$s != null) { %2$s.removeOwner(name); }%n" +
"}",
EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER,
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME );
}
catch (CannotCompileException cce) {
cce.printStackTrace();
}
}
}

View File

@ -0,0 +1,292 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance.internal;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtField;
import javassist.Modifier;
import javassist.NotFoundException;
import org.hibernate.bytecode.enhance.internal.tracker.CollectionTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleDirtyTracker;
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.SelfDirtinessTracker;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* enhancer for regular entities
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public class EntityEnhancer extends Enhancer {
public EntityEnhancer(EnhancementContext context) {
super( context );
}
// for very small sizes SimpleDirtyTracker implementation ends up being faster
private static final String TRACKER_IMPL = SimpleDirtyTracker.class.getName();
public void enhance(CtClass managedCtClass) {
// add the ManagedEntity interface
managedCtClass.addInterface( managedEntityCtClass );
addEntityInstanceHandling( managedCtClass );
addEntityEntryHandling( managedCtClass );
addLinkedPreviousHandling( managedCtClass );
addLinkedNextHandling( managedCtClass );
addInterceptorHandling( managedCtClass );
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
addInLineDirtyHandling( managedCtClass );
}
new PersistentAttributesEnhancer( enhancementContext ).enhance( managedCtClass );
}
/* -- */
private void addEntityInstanceHandling(CtClass managedCtClass) {
try {
MethodWriter.write( managedCtClass, "public Object %s() { return this; }", EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME );
}
catch (CannotCompileException cce) {
final String msg = String.format( "Could not enhance entity class [%s] to add EntityEntry getter", managedCtClass.getName() );
throw new EnhancementException(msg, cce);
}
}
/* -- */
private void addEntityEntryHandling(CtClass managedCtClass) {
FieldWriter.addFieldWithGetterAndSetter( managedCtClass, entityEntryCtClass,
EnhancerConstants.ENTITY_ENTRY_FIELD_NAME,
EnhancerConstants.ENTITY_ENTRY_GETTER_NAME,
EnhancerConstants.ENTITY_ENTRY_SETTER_NAME );
}
private void addLinkedPreviousHandling(CtClass managedCtClass) {
FieldWriter.addFieldWithGetterAndSetter( managedCtClass, managedEntityCtClass,
EnhancerConstants.PREVIOUS_FIELD_NAME,
EnhancerConstants.PREVIOUS_GETTER_NAME,
EnhancerConstants.PREVIOUS_SETTER_NAME );
}
private void addLinkedNextHandling(CtClass managedCtClass) {
FieldWriter.addFieldWithGetterAndSetter( managedCtClass, managedEntityCtClass,
EnhancerConstants.NEXT_FIELD_NAME,
EnhancerConstants.NEXT_GETTER_NAME,
EnhancerConstants.NEXT_SETTER_NAME );
}
/* --- */
private void addInLineDirtyHandling(CtClass managedCtClass) {
try {
managedCtClass.addInterface( classPool.get( SelfDirtinessTracker.class.getName() ) );
FieldWriter.addField( managedCtClass, classPool.get( TRACKER_IMPL ), EnhancerConstants.TRACKER_FIELD_NAME );
FieldWriter.addField( managedCtClass, classPool.get( CollectionTracker.class.getName() ), EnhancerConstants.TRACKER_COLLECTION_NAME );
createDirtyTrackerMethods( managedCtClass );
}
catch (NotFoundException nfe) {
nfe.printStackTrace();
}
}
private void createDirtyTrackerMethods(CtClass managedCtClass) {
try {
MethodWriter.write( managedCtClass, "" +
"public void %1$s(String name) {%n" +
" if (%2$s == null) { %2$s = new %3$s(); }%n" +
" %2$s.add(name);%n" +
"}",
EnhancerConstants.TRACKER_CHANGER_NAME,
EnhancerConstants.TRACKER_FIELD_NAME,
TRACKER_IMPL );
/* --- */
createCollectionDirtyCheckMethod( managedCtClass );
createCollectionDirtyCheckGetFieldsMethod( managedCtClass );
createClearDirtyCollectionMethod( managedCtClass );
/* --- */
MethodWriter.write( managedCtClass, "" +
"public java.util.Set %1$s() {%n" +
" if (%2$s == null) { %2$s = new %4$s(); }%n" +
" %3$s(%2$s);%n" +
" return %2$s.asSet();%n" +
"}",
EnhancerConstants.TRACKER_GET_NAME,
EnhancerConstants.TRACKER_FIELD_NAME,
EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME,
TRACKER_IMPL );
MethodWriter.write( managedCtClass, "" +
"public boolean %1$s() {%n" +
" return (%2$s != null && !%2$s.isEmpty()) || %3$s();%n" +
"}",
EnhancerConstants.TRACKER_HAS_CHANGED_NAME,
EnhancerConstants.TRACKER_FIELD_NAME,
EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME );
MethodWriter.write( managedCtClass, "" +
"public void %1$s() {%n" +
" if (%2$s != null) { %2$s.clear(); }%n" +
" %3$s();%n" +
"}",
EnhancerConstants.TRACKER_CLEAR_NAME,
EnhancerConstants.TRACKER_FIELD_NAME,
EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME );
}
catch (CannotCompileException cce) {
cce.printStackTrace();
}
}
/* -- */
private List<CtField> collectCollectionFields(CtClass managedCtClass) {
final List<CtField> collectionList = new LinkedList<CtField>();
try {
for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
// skip static fields and skip fields added by enhancement
if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) {
continue;
}
if ( enhancementContext.isPersistentField( ctField ) ) {
for ( CtClass ctClass : ctField.getType().getInterfaces() ) {
if ( ctClass.getName().equals( Collection.class.getName() ) ) {
collectionList.add( ctField );
break;
}
}
}
}
}
catch (NotFoundException ignored) {
}
return collectionList;
}
private void createCollectionDirtyCheckMethod(CtClass managedCtClass) {
try {
final StringBuilder body = new StringBuilder();
body.append( String.format( "" +
"private boolean %1$s() {%n" +
" if (%2$s() == null || %3$s == null) { return false; }%n",
EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME,
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
EnhancerConstants.TRACKER_COLLECTION_NAME ) );
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
if ( !enhancementContext.isMappedCollection( ctField )) {
body.append( String.format( "" +
" // collection field [%1$s]%n" +
" if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { return true; }%n"+
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { return true; }%n",
ctField.getName(),
EnhancerConstants.TRACKER_COLLECTION_NAME ) );
}
}
body.append( " return false;%n}" );
MethodWriter.write( managedCtClass, body.toString() );
}
catch (CannotCompileException cce) {
cce.printStackTrace();
}
}
private void createCollectionDirtyCheckGetFieldsMethod(CtClass managedCtClass) {
try {
final StringBuilder body = new StringBuilder();
body.append( String.format( "" +
"private void %1$s(%3$s tracker) {%n" +
" if (%2$s == null) { return; }%n",
EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME,
EnhancerConstants.TRACKER_COLLECTION_NAME,
TRACKER_IMPL ) );
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
if ( !enhancementContext.isMappedCollection( ctField )) {
body.append( String.format( "" +
" // Collection field [%1$s]%n" +
" if (%1$s == null && %2$s.getSize(\"%1$s\") != -1) { tracker.add(\"%1$s\"); }%n"+
" if (%1$s != null && %2$s.getSize(\"%1$s\") != %1$s.size()) { tracker.add(\"%1$s\"); }%n",
ctField.getName(),
EnhancerConstants.TRACKER_COLLECTION_NAME ) );
}
}
body.append( "}" );
MethodWriter.write( managedCtClass, body.toString() );
}
catch (CannotCompileException cce) {
cce.printStackTrace();
}
}
private void createClearDirtyCollectionMethod(CtClass managedCtClass) throws CannotCompileException {
try {
final StringBuilder body = new StringBuilder();
body.append( String.format( "" +
"private void %1$s() {%n" +
" if (%2$s == null) { %2$s = new %3$s(); }%n",
EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME,
EnhancerConstants.TRACKER_COLLECTION_NAME,
CollectionTracker.class.getName()) );
for ( CtField ctField : collectCollectionFields( managedCtClass ) ) {
if ( !enhancementContext.isMappedCollection( ctField ) ) {
body.append( String.format( "" +
" // Collection field [%1$s]%n" +
" if (%1$s == null) { %2$s.add(\"%1$s\", -1); }%n"+
" else { %2$s.add(\"%1$s\", %1$s.size()); }%n",
ctField.getName(),
EnhancerConstants.TRACKER_COLLECTION_NAME) );
}
}
body.append( "}" );
MethodWriter.write( managedCtClass, body.toString() );
}
catch (CannotCompileException cce) {
cce.printStackTrace();
}
}
}

View File

@ -0,0 +1,99 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance.internal;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtField;
import javassist.Modifier;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.annotation.Annotation;
import org.hibernate.bytecode.enhance.spi.EnhancementException;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import javax.persistence.Transient;
/**
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public class FieldWriter {
private static final CoreMessageLogger log = CoreLogging.messageLogger( FieldWriter.class );
private FieldWriter() { }
/* --- */
/**
* Add enhancement field
*/
public static void addField(CtClass target, CtClass type, String field) {
addPrivateTransient( target, type, field );
}
/**
* Add enhancement field with getter and setter
*/
public static void addFieldWithGetterAndSetter(CtClass target, CtClass type, String field, String getter, String setter) {
addPrivateTransient( target, type, field );
MethodWriter.addGetter( target, field, getter );
MethodWriter.addSetter( target, field, setter );
}
/* --- */
private static void addPrivateTransient(CtClass target, CtClass type, String name) {
addWithModifiers( target, type, name, Modifier.PRIVATE | Modifier.TRANSIENT, Transient.class );
log.debugf( "Wrote field into [%s]: @Transient private transient %s %s() %s;%n", target.getName(), type.getName(), name );
}
private static void addWithModifiers(CtClass target, CtClass type, String name, int modifiers, Class<?> ... annotations ) {
try {
final CtField f = new CtField( type, name, target );
f.setModifiers( f.getModifiers() | modifiers );
addAnnotations( f.getFieldInfo(), annotations );
target.addField( f );
}
catch (CannotCompileException cce) {
final String msg = String.format( "Could not enhance class [%s] to add field [%s]", target.getName(), name );
throw new EnhancementException( msg, cce );
}
}
/* --- */
private static void addAnnotations(FieldInfo fieldInfo, Class<?>[] annotations) {
AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) fieldInfo.getAttribute( AnnotationsAttribute.visibleTag );
if ( annotationsAttribute == null ) {
annotationsAttribute = new AnnotationsAttribute( fieldInfo.getConstPool(), AnnotationsAttribute.visibleTag );
fieldInfo.addAttribute( annotationsAttribute );
}
for (Class<?> annotation : annotations) {
annotationsAttribute.addAnnotation( new Annotation( annotation.getName(), fieldInfo.getConstPool() ) );
}
}
}

View File

@ -0,0 +1,105 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance.internal;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
import javassist.bytecode.ConstPool;
import org.hibernate.bytecode.enhance.spi.EnhancementException;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
/**
* utility class to compile methods and add the to class files
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public class MethodWriter {
private static final CoreMessageLogger log = CoreLogging.messageLogger( MethodWriter.class );
private MethodWriter() { }
/* --- */
/**
* convenience method that builds a method from a format string. {@see String.format} for more details
*
* @throws CannotCompileException
*/
public static CtMethod write(CtClass target, String format, Object ... args) throws CannotCompileException {
final String body = String.format( format, args );
// System.out.printf( "writing method into [%s]:%n%s%n", target.getName(), body );
log.debugf( "writing method into [%s]:%n%s%n", target.getName(), body );
final CtMethod method = CtNewMethod.make( body, target );
target.addMethod( method );
return method;
}
/* --- */
public static CtMethod addGetter(CtClass target, String field, String name) {
try {
log.debugf( "Writing getter method [%s] into [%s] for field [%s]%n", name, target.getName(), field );
final CtMethod method = CtNewMethod.getter( name, target.getField( field ) );
target.addMethod( method );
return method;
}
catch (CannotCompileException cce) {
final String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field );
throw new EnhancementException( msg, cce );
}
catch (NotFoundException nfe) {
final String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field );
throw new EnhancementException( msg, nfe );
}
}
public static CtMethod addSetter(CtClass target, String field, String name) {
try {
log.debugf( "Writing setter method [%s] into [%s] for field [%s]%n", name, target.getName(), field );
final CtMethod method = CtNewMethod.setter( name, target.getField( field ) );
target.addMethod( method );
return method;
}
catch (CannotCompileException cce) {
final String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field );
throw new EnhancementException( msg, cce );
}
catch (NotFoundException nfe) {
final String msg = String.format( "Could not enhance class [%s] to add method [%s] for field [%s]", target.getName(), name, field );
throw new EnhancementException( msg, nfe );
}
}
/* --- */
public static int addMethod(ConstPool cPool, CtMethod method) {
return cPool.addMethodrefInfo( cPool.getThisClassInfo(), method.getName(), method.getSignature() );
}
}

View File

@ -0,0 +1,279 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance.internal;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.ConstPool;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.bytecode.stackmap.MapMaker;
import org.hibernate.bytecode.enhance.spi.EnhancementException;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
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;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import javax.persistence.Embedded;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
/**
* enhancer for persistent attributes of any type of entity
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public class PersistentAttributesEnhancer extends Enhancer {
private static final CoreMessageLogger log = CoreLogging.messageLogger( PersistentAttributesEnhancer.class );
public PersistentAttributesEnhancer(EnhancementContext context) {
super( context );
}
public void enhance(CtClass managedCtClass) {
final IdentityHashMap<String, PersistentAttributeAccessMethods> attrDescriptorMap = new IdentityHashMap<String, PersistentAttributeAccessMethods>();
for ( CtField persistentField : collectPersistentFields( managedCtClass ) ) {
attrDescriptorMap.put( persistentField.getName(), enhancePersistentAttribute( managedCtClass, persistentField ) );
}
// lastly, find all references to the transformed fields and replace with calls to the added reader/writer methods
enhanceAttributesAccess( managedCtClass, attrDescriptorMap );
}
/* --- */
// TODO: drive this from the Hibernate metamodel instance...
private CtField[] collectPersistentFields(CtClass managedCtClass) {
final List<CtField> persistentFieldList = new LinkedList<CtField>();
for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
// skip static fields and skip fields added by enhancement
if ( Modifier.isStatic( ctField.getModifiers() ) || ctField.getName().startsWith( "$$_hibernate_" ) ) {
continue;
}
if ( enhancementContext.isPersistentField( ctField ) ) {
persistentFieldList.add( ctField );
}
}
return enhancementContext.order( persistentFieldList.toArray( new CtField[ persistentFieldList.size() ] ) );
}
/* --- */
private PersistentAttributeAccessMethods enhancePersistentAttribute(CtClass managedCtClass, CtField persistentField) {
try {
final AttributeTypeDescriptor typeDescriptor = AttributeTypeDescriptor.resolve( persistentField );
return new PersistentAttributeAccessMethods(
generateFieldReader( managedCtClass, persistentField, typeDescriptor ),
generateFieldWriter( managedCtClass, persistentField, typeDescriptor ) );
}
catch (Exception e) {
final String msg = String.format( "Unable to enhance persistent attribute [%s:%s]", managedCtClass.getName(), persistentField.getName() );
throw new EnhancementException( msg, e );
}
}
private CtMethod generateFieldReader(CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) {
final String fieldName = persistentField.getName();
final String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName;
// 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.isLazyLoadable( persistentField ) ) {
return MethodWriter.addGetter( managedCtClass, fieldName, readerName );
}
// TODO: temporary solution...
try {
return MethodWriter.write( managedCtClass, "private %s %s() {%n %s%n return this.%s;%n}",
persistentField.getType().getName(),
readerName,
typeDescriptor.buildReadInterceptionBodyFragment( fieldName ),
fieldName);
}
catch (CannotCompileException cce) {
final String msg = String.format( "Could not enhance entity class [%s] to add field reader method [%s]", managedCtClass.getName(), readerName );
throw new EnhancementException( msg, cce );
}
catch (NotFoundException nfe) {
final String msg = String.format( "Could not enhance entity class [%s] to add field reader method [%s]", managedCtClass.getName(), readerName );
throw new EnhancementException( msg, nfe );
}
}
private CtMethod generateFieldWriter(CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) {
final String fieldName = persistentField.getName();
final String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName;
try {
final CtMethod writer;
if ( !enhancementContext.isLazyLoadable( persistentField ) ) {
writer = MethodWriter.addSetter( managedCtClass, fieldName, writerName );
}
else {
writer = MethodWriter.write( managedCtClass, "private void %s(%s %s) {%n %s%n}",
writerName,
persistentField.getType().getName(),
fieldName,
typeDescriptor.buildWriteInterceptionBodyFragment( fieldName ) );
}
if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
writer.insertBefore( String.format( "if (%s != null) { %<s.callOwner(\".%s\"); }%n",
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME,
fieldName ) );
}
else if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( enhancementContext, persistentField ) );
}
// composite fields
if ( persistentField.hasAnnotation( Embedded.class ) ) {
// make sure to add the CompositeOwner interface
managedCtClass.addInterface( classPool.get( CompositeOwner.class.getName() ) );
if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
// if a composite have a embedded field we need to implement the TRACKER_CHANGER_NAME method as well
MethodWriter.write( managedCtClass, "" +
"public void %1$s(String name) {%n" +
" if (%2$s != null) { %2$s.callOwner(\".\" + name) ; }%n}",
EnhancerConstants.TRACKER_CHANGER_NAME,
EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME );
}
// cleanup previous owner
writer.insertBefore( String.format( "" +
"if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n",
fieldName,
CompositeTracker.class.getName(),
EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER ) );
// trigger track changes
writer.insertAfter( String.format( "" +
"((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this);%n" +
"%5$s(\"%1$s\");",
fieldName,
CompositeTracker.class.getName(),
CompositeOwner.class.getName(),
EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
EnhancerConstants.TRACKER_CHANGER_NAME ) );
}
return writer;
}
catch (CannotCompileException cce) {
final String msg = String.format( "Could not enhance entity class [%s] to add field writer method [%s]", managedCtClass.getName(), writerName );
throw new EnhancementException( msg, cce );
}
catch (NotFoundException nfe) {
final String msg = String.format( "Could not enhance entity class [%s] to add field writer method [%s]", managedCtClass.getName(), writerName );
throw new EnhancementException( msg, nfe );
}
}
/* --- */
protected void enhanceAttributesAccess(CtClass managedCtClass, IdentityHashMap<String, PersistentAttributeAccessMethods> attributeDescriptorMap) {
final ConstPool constPool = managedCtClass.getClassFile().getConstPool();
for ( Object oMethod : managedCtClass.getClassFile().getMethods() ) {
final MethodInfo methodInfo = (MethodInfo) oMethod;
final String methodName = methodInfo.getName();
// skip methods added by enhancement and abstract methods (methods without any code)
if ( methodName.startsWith( "$$_hibernate_" ) || methodInfo.getCodeAttribute() == null ) {
continue;
}
try {
final CodeIterator itr = methodInfo.getCodeAttribute().iterator();
while ( itr.hasNext() ) {
final int index = itr.next();
final int op = itr.byteAt( index );
if ( op != Opcode.PUTFIELD && op != Opcode.GETFIELD ) {
continue;
}
final String fieldName = constPool.getFieldrefName( itr.u16bitAt( index + 1 ) );
final PersistentAttributeAccessMethods attributeMethods = attributeDescriptorMap.get( fieldName );
// its not a field we have enhanced for interception, so skip it
if ( attributeMethods == null ) {
continue;
}
//System.out.printf( "Transforming access to field [%s] from method [%s]%n", fieldName, methodName );
log.debugf( "Transforming access to field [%s] from method [%s]", fieldName, methodName );
if ( op == Opcode.GETFIELD ) {
final int methodIndex = MethodWriter.addMethod( constPool, attributeMethods.getReader() );
itr.writeByte( Opcode.INVOKESPECIAL, index );
itr.write16bit( methodIndex, index + 1 );
}
else {
final int methodIndex = MethodWriter.addMethod( constPool, attributeMethods.getWriter() );
itr.writeByte( Opcode.INVOKESPECIAL, index );
itr.write16bit( methodIndex, index + 1 );
}
}
methodInfo.getCodeAttribute().setAttribute( MapMaker.make( classPool, methodInfo ) );
}
catch (BadBytecode bb) {
final String msg = String.format( "Unable to perform field access transformation in method [%s]", methodName );
throw new EnhancementException( msg, bb );
}
}
}
/* --- */
private static class PersistentAttributeAccessMethods {
private final CtMethod reader;
private final CtMethod writer;
private PersistentAttributeAccessMethods(CtMethod reader, CtMethod writer) {
this.reader = reader;
this.writer = writer;
}
private CtMethod getReader() {
return reader;
}
private CtMethod getWriter() {
return writer;
}
}
}

View File

@ -0,0 +1,4 @@
/**
* package containing bytecode enhancement code (internals)
*/
package org.hibernate.bytecode.enhance.internal;

View File

@ -21,32 +21,45 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance.spi;
package org.hibernate.bytecode.enhance.internal.tracker;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
/**
* small low memory class to keep track of the number of elements in a collection
*
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
*/
public class CollectionTracker {
private Map<String, Integer> tracker;
public final class CollectionTracker {
private String[] names;
private int[] sizes;
public CollectionTracker() {
tracker = new HashMap<String, Integer>();
names = new String[0];
sizes = new int[0];
}
public void add(String name, int size) {
tracker.put( name, size );
for ( int i = 0; i < names.length; i++ ) {
if ( names[i].equals( name ) ) {
sizes[i] = size;
return;
}
}
names = Arrays.copyOf( names, names.length + 1 );
names[names.length - 1] = name;
sizes = Arrays.copyOf( sizes, sizes.length + 1 );
sizes[sizes.length - 1] = size;
}
public int getSize(String name) {
Integer size = tracker.get( name );
if ( size == null ) {
return -1;
}
else {
return size;
for ( int i = 0; i < names.length; i++ ) {
if ( name.equals( names[i] ) ) {
return sizes[i];
}
}
return -1;
}
}

View File

@ -21,64 +21,63 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance.spi;
package org.hibernate.bytecode.enhance.internal.tracker;
import org.hibernate.engine.spi.CompositeOwner;
import java.util.Arrays;
/**
* small low memory class to keep references to composite owners
*
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
*/
public class CompositeOwnerTracker {
public final class CompositeOwnerTracker {
private String[] names;
private CompositeOwner[] owners;
private int size;
public CompositeOwnerTracker() {
names = new String[1];
owners = new CompositeOwner[1];
names = new String[0];
owners = new CompositeOwner[0];
}
public void add(String name, CompositeOwner owner) {
for ( int i = 0; i < size; i++ ) {
for ( int i = 0; i < names.length; i++ ) {
if ( names[i].equals( name ) ) {
owners[i] = owner;
return;
}
}
if ( size >= names.length ) {
String[] tmpNames = new String[size + 1];
System.arraycopy( names, 0, tmpNames, 0, size );
names = tmpNames;
CompositeOwner[] tmpOwners = new CompositeOwner[size + 1];
System.arraycopy( owners, 0, tmpOwners, 0, size );
owners = tmpOwners;
}
names[size] = name;
owners[size] = owner;
size++;
names = Arrays.copyOf( names, names.length + 1 );
names[names.length - 1] = name;
owners = Arrays.copyOf( owners, owners.length + 1 );
owners[owners.length - 1] = owner;
}
public void callOwner(String fieldName) {
for ( int i = 0; i < size; i++ ) {
owners[i].$$_hibernate_trackChange( names[i] + fieldName );
for ( int i = 0; i < owners.length ; i++ ) {
if ( owners[i] != null ) {
owners[i].$$_hibernate_trackChange( names[i] + fieldName );
}
}
}
public void removeOwner(String name) {
for ( int i = 0; i < size; i++ ) {
if ( names[i].equals( name ) ) {
if ( i < size ) {
for ( int j = i; j < size - 1; j++ ) {
names[j] = names[j + 1];
owners[j] = owners[j + 1];
}
names[size - 1] = null;
owners[size - 1] = null;
size--;
}
for ( int i = 0; i < names.length; i++ ) {
if ( name.equals( names[i] ) ) {
final String[] newNames = Arrays.copyOf( names, names.length - 1 );
System.arraycopy( names, i + 1, newNames, i, newNames.length - i);
names = newNames;
final CompositeOwner[] newOwners = Arrays.copyOf( owners, owners.length - 1 );
System.arraycopy( owners, i + 1, newOwners, i, newOwners.length - i);
owners = newOwners;
return;
}
}
}
}

View File

@ -0,0 +1,68 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance.internal.tracker;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* small low memory class to keep track of changed fields
*
* uses an array as a set (under the assumption that the number of elements will be low) to avoid having to instantiate an HashSet.
* if the assumption does not, hold the array can be kept ordered to reduce the cost of verifying duplicates
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public final class SimpleDirtyTracker {
private String[] names;
public SimpleDirtyTracker() {
names = new String[0];
}
public void add(String name) {
for (String existing : names) {
if ( existing.equals( name ) ) {
return;
}
}
names = Arrays.copyOf( names, names.length + 1 );
names[names.length - 1] = name;
}
public void clear() {
names = new String[0];
}
public boolean isEmpty() {
return names.length == 0;
}
public Set<String> asSet() {
return new CopyOnWriteArraySet<String>( Arrays.asList( names ) );
}
}

View File

@ -0,0 +1,82 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance.internal.tracker;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* small low memory class to keep track of changed fields
*
* similar to BasicTracker but where the array is kept ordered to reduce the cost of verifying duplicates
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public final class SortedDirtyTracker {
private String[] names;
public SortedDirtyTracker() {
names = new String[0];
}
public void add(String name) {
// we do a binary search: even if we don't find the name at least we get the position to insert into the array
int insert = 0;
for ( int low = 0, high = names.length - 1; low <= high; ) {
final int middle = low + ( ( high - low ) / 2 );
if ( names[middle].compareTo( name ) > 0 ) {
// bottom half: higher bound in (middle - 1) and insert position in middle
high = middle - 1;
insert = middle;
}
else if( names[middle].compareTo( name ) < 0 ) {
// top half: lower bound in (middle + 1) and insert position after middle
insert = low = middle + 1;
}
else {
return;
}
}
final String[] newNames = new String[names.length + 1];
System.arraycopy( names, 0, newNames, 0, insert);
System.arraycopy( names, insert, newNames, insert + 1, names.length - insert);
newNames[insert] = name;
names = newNames;
}
public void clear() {
names = new String[0];
}
public boolean isEmpty() {
return names.length == 0;
}
public Set<String> asSet() {
return new CopyOnWriteArraySet<String>( Arrays.asList( names ) );
}
}

View File

@ -0,0 +1,4 @@
/**
* specialized classes to keep track of changes
*/
package org.hibernate.bytecode.enhance.internal.tracker;

View File

@ -1,4 +0,0 @@
/**
* Package defining bytecode code enhancement (instrumentation) support.
*/
package org.hibernate.bytecode.enhance;

View File

@ -0,0 +1,105 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance.spi;
import javassist.CtClass;
import javassist.CtField;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
/**
* default implementation of EnhancementContext. May be sub-classed as needed.
*
* @author <a href="mailto:lbarreiro@redhat.com">Luis Barreiro</a>
*/
public class DefaultEnhancementContext implements EnhancementContext {
/**
* @return the classloader for this class
*/
public ClassLoader getLoadingClassLoader() {
return getClass().getClassLoader();
}
/**
* look for @Entity annotation
*/
public boolean isEntityClass(CtClass classDescriptor) {
return classDescriptor.hasAnnotation( Entity.class );
}
/**
* look for @Embeddable annotation
*/
public boolean isCompositeClass(CtClass classDescriptor) {
return classDescriptor.hasAnnotation( Embeddable.class );
}
/**
* @return true
*/
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
return true;
}
/**
* @return true
*/
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
return true;
}
/**
* @return true
*/
public boolean isLazyLoadable(CtField field) {
return true;
}
/**
* look for @Transient annotation
*/
public boolean isPersistentField(CtField ctField) {
return ! ctField.hasAnnotation( Transient.class );
}
/**
* look for @OneToMany, @ManyToMany and @ElementCollection annotations
*/
public boolean isMappedCollection(CtField field) {
return field.hasAnnotation( OneToMany.class ) || field.hasAnnotation( ManyToMany.class ) || field.hasAnnotation( ElementCollection.class );
}
/**
* keep the same order.
*/
public CtField[] order(CtField[] persistentFields) {
return persistentFields;
}
}

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.bytecode.enhance;
package org.hibernate.bytecode.enhance.spi;
import org.hibernate.HibernateException;

View File

@ -1,4 +1,4 @@
/**
* Package defining bytecode code enhancement (instrumentation) support.
* package defining bytecode code enhancement (instrumentation) support.
*/
package org.hibernate.bytecode.enhance.spi;

View File

@ -0,0 +1,123 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement;
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.test.bytecode.enhancement.entity.customer.Address;
import org.hibernate.test.bytecode.enhancement.entity.customer.Customer;
import org.hibernate.test.bytecode.enhancement.entity.customer.CustomerInventory;
import org.hibernate.test.bytecode.enhancement.entity.customer.SupplierComponentPK;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;
import java.lang.reflect.Method;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
/**
* @author Steve Ebersole
*/
public class CustomerEnhancerTest extends BaseUnitTestCase {
@Test
public void testEnhancement() throws Exception {
testFor(Customer.class);
}
private void testFor(Class entityClassToEnhance) throws Exception {
ClassLoader cl = new ClassLoader() {};
// just for debugging
Class<?> addressClass = EnhancerTestUtils.enhanceAndDecompile(Address.class, cl);
Class<?> customerInventoryClass = EnhancerTestUtils.enhanceAndDecompile(CustomerInventory.class, cl);
Class<?> supplierComponentPKCtClass = EnhancerTestUtils.enhanceAndDecompile(SupplierComponentPK.class, cl);
Class<?> entityClass = EnhancerTestUtils.enhanceAndDecompile(entityClassToEnhance, cl);
Object entityInstance = entityClass.newInstance();
assertTyping(ManagedEntity.class, entityInstance);
// call the new methods
Method setter = entityClass.getMethod(EnhancerConstants.ENTITY_ENTRY_SETTER_NAME, EntityEntry.class);
Method getter = entityClass.getMethod(EnhancerConstants.ENTITY_ENTRY_GETTER_NAME);
assertNull(getter.invoke(entityInstance));
setter.invoke(entityInstance, EnhancerTestUtils.makeEntityEntry());
assertNotNull(getter.invoke(entityInstance));
setter.invoke(entityInstance, new Object[] {null});
assertNull(getter.invoke(entityInstance));
Method entityInstanceGetter = entityClass.getMethod(EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME);
assertSame(entityInstance, entityInstanceGetter.invoke(entityInstance));
Method previousGetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_GETTER_NAME);
Method previousSetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class);
previousSetter.invoke(entityInstance, entityInstance);
assertSame(entityInstance, previousGetter.invoke(entityInstance));
Method nextGetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_GETTER_NAME);
Method nextSetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class);
nextSetter.invoke(entityInstance, entityInstance);
assertSame( entityInstance, nextGetter.invoke(entityInstance));
// add an attribute interceptor...
assertNull(entityClass.getMethod(EnhancerConstants.INTERCEPTOR_GETTER_NAME).invoke(entityInstance));
entityClass.getMethod("getId").invoke(entityInstance);
Method interceptorSetter = entityClass.getMethod(EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class);
interceptorSetter.invoke(entityInstance, new EnhancerTestUtils.LocalPersistentAttributeInterceptor());
assertNotNull(entityClass.getMethod(EnhancerConstants.INTERCEPTOR_GETTER_NAME).invoke(entityInstance));
// dirty checking is unfortunately just printlns for now... just verify the test output
entityClass.getMethod("getId").invoke(entityInstance);
entityClass.getMethod("setId", Integer.class).invoke(entityInstance, entityClass.getMethod("getId").invoke(entityInstance));
entityClass.getMethod("setId", Integer.class).invoke(entityInstance, 1);
EnhancerTestUtils.checkDirtyTracking(entityInstance, "id");
entityClass.getMethod("setFirstName", String.class).invoke(entityInstance, "Erik");
entityClass.getMethod("setLastName", String.class).invoke(entityInstance, "Mykland");
EnhancerTestUtils.checkDirtyTracking(entityInstance, "id", "firstName", "lastName");
EnhancerTestUtils.clearDirtyTracking(entityInstance);
// testing composite object
Object address = addressClass.newInstance();
entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address);
addressClass.getMethod("setCity", String.class).invoke(address, "Arendal");
EnhancerTestUtils.checkDirtyTracking(entityInstance, "address", "address.city");
EnhancerTestUtils.clearDirtyTracking(entityInstance);
//make sure that new composite instances are cleared
Object address2 = addressClass.newInstance();
entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address2);
addressClass.getMethod("setStreet1", String.class).invoke(address, "Heggedalveien");
EnhancerTestUtils.checkDirtyTracking(entityInstance, "address");
}
}

View File

@ -23,401 +23,148 @@
*/
package org.hibernate.test.bytecode.enhancement;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.LoaderClassPath;
import org.hibernate.EntityMode;
import org.hibernate.LockMode;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.Enhancer;
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.Status;
import org.hibernate.test.bytecode.enhancement.entity.Address;
import org.hibernate.test.bytecode.enhancement.entity.Country;
import org.hibernate.test.bytecode.enhancement.entity.SimpleEntity;
import org.hibernate.test.bytecode.enhancement.entity.SubEntity;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import javax.persistence.ElementCollection;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
/**
* @author Steve Ebersole
*/
public class EnhancerTest extends BaseUnitTestCase {
private static EnhancementContext enhancementContext = new EnhancementContext() {
@Override
public ClassLoader getLoadingClassLoader() {
return getClass().getClassLoader();
}
@Override
public boolean isEntityClass(CtClass classDescriptor) {
return true;
}
@Test
public void testEnhancement() throws Exception {
testFor(SimpleEntity.class);
testFor(SubEntity.class);
}
@Override
public boolean isCompositeClass(CtClass classDescriptor) {
return false;
}
private void testFor(Class<?> entityClassToEnhance) throws Exception {
ClassLoader cl = new ClassLoader() {};
Class<?> entityClass = EnhancerTestUtils.enhanceAndDecompile(entityClassToEnhance, cl);
Object entityInstance = entityClass.newInstance();
@Override
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
return true;
}
assertTyping(ManagedEntity.class, entityInstance);
@Override
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
return true;
}
// call the new methods
Method setter = entityClass.getMethod(EnhancerConstants.ENTITY_ENTRY_SETTER_NAME, EntityEntry.class);
Method getter = entityClass.getMethod(EnhancerConstants.ENTITY_ENTRY_GETTER_NAME);
@Override
public boolean isLazyLoadable(CtField field) {
return true;
}
assertNull(getter.invoke(entityInstance));
setter.invoke(entityInstance, EnhancerTestUtils.makeEntityEntry());
assertNotNull(getter.invoke(entityInstance));
setter.invoke(entityInstance, new Object[] { null } );
assertNull(getter.invoke(entityInstance));
@Override
public boolean isMappedCollection(CtField field) {
try {
return (field.getAnnotation(OneToMany.class) != null ||
field.getAnnotation(ManyToMany.class) != null ||
field.getAnnotation(ElementCollection.class) != null);
}
catch (ClassNotFoundException e) {
return false;
}
}
Method entityInstanceGetter = entityClass.getMethod(EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME);
assertSame(entityInstance, entityInstanceGetter.invoke(entityInstance));
@Override
public boolean isPersistentField(CtField ctField) {
return true;
}
Method previousGetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_GETTER_NAME);
Method previousSetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class);
previousSetter.invoke(entityInstance, entityInstance);
assertSame(entityInstance, previousGetter.invoke(entityInstance));
@Override
public CtField[] order(CtField[] persistentFields) {
return persistentFields;
}
};
Method nextGetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_GETTER_NAME);
Method nextSetter = entityClass.getMethod(EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class);
nextSetter.invoke(entityInstance, entityInstance);
assertSame(entityInstance, nextGetter.invoke(entityInstance));
@Test
public void testEnhancement() throws Exception {
testFor( SimpleEntity.class );
testFor( SubEntity.class );
}
// add an attribute interceptor...
Method interceptorGetter = entityClass.getMethod(EnhancerConstants.INTERCEPTOR_GETTER_NAME);
assertNull(interceptorGetter.invoke(entityInstance));
entityClass.getMethod("getId").invoke(entityInstance);
private void testFor(Class entityClassToEnhance) throws Exception {
Enhancer enhancer = new Enhancer( enhancementContext );
CtClass entityCtClass = generateCtClassForAnEntity( entityClassToEnhance );
byte[] original = entityCtClass.toBytecode();
//byte[] enhanced = enhancer.enhance( entityCtClass.getName(), original );
byte[] enhanced = enhancer.enhance( entityCtClass.getName(), original );
assertFalse( "entity was not enhanced", Arrays.equals( original, enhanced ) );
Method interceptorSetter = entityClass.getMethod(EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class);
interceptorSetter.invoke(entityInstance, new EnhancerTestUtils.LocalPersistentAttributeInterceptor());
assertNotNull(interceptorGetter.invoke(entityInstance));
ClassLoader cl = new ClassLoader() { };
ClassPool cp = new ClassPool( false );
cp.appendClassPath( new LoaderClassPath( cl ) );
CtClass enhancedCtClass = cp.makeClass( new ByteArrayInputStream( enhanced ) );
enhancedCtClass.debugWriteFile("/tmp");
//just for debugging
Class addressClass = null;
Class countryClass = null;
if(entityClassToEnhance.getName().endsWith("SimpleEntity")) {
CtClass addressCtClass = generateCtClassForAnEntity( Address.class );
byte[] enhancedAddress = enhancer.enhanceComposite(Address.class.getName(), addressCtClass.toBytecode());
CtClass enhancedCtClassAddress = cp.makeClass( new ByteArrayInputStream( enhancedAddress ) );
enhancedCtClassAddress.debugWriteFile("/tmp");
addressClass = enhancedCtClassAddress.toClass( cl, this.getClass().getProtectionDomain() );
// dirty checking is unfortunately just printlns for now... just verify the test output
entityClass.getMethod("getId").invoke(entityInstance);
entityClass.getMethod("setId", Long.class).invoke(entityInstance, entityClass.getMethod("getId").invoke(entityInstance));
entityClass.getMethod("setId", Long.class).invoke(entityInstance, 1L);
EnhancerTestUtils.checkDirtyTracking(entityInstance, "id");
CtClass countryCtClass = generateCtClassForAnEntity( Country.class );
byte[] enhancedCountry = enhancer.enhanceComposite(Country.class.getName(), countryCtClass.toBytecode());
CtClass enhancedCtClassCountry = cp.makeClass( new ByteArrayInputStream( enhancedCountry ) );
enhancedCtClassCountry.debugWriteFile("/tmp");
countryClass = enhancedCtClassCountry.toClass( cl, this.getClass().getProtectionDomain() );
entityClass.getMethod("isActive").invoke(entityInstance);
entityClass.getMethod("setActive", boolean.class).invoke(entityInstance, entityClass.getMethod("isActive").invoke(entityInstance));
entityClass.getMethod("setActive", boolean.class).invoke(entityInstance, true);
}
Class entityClass = enhancedCtClass.toClass( cl, this.getClass().getProtectionDomain() );
Object entityInstance = entityClass.newInstance();
entityClass.getMethod("getSomeNumber").invoke(entityInstance);
entityClass.getMethod("setSomeNumber", long.class).invoke(entityInstance, entityClass.getMethod("getSomeNumber").invoke(entityInstance));
entityClass.getMethod("setSomeNumber", long.class).invoke(entityInstance, 1L);
assertTyping( ManagedEntity.class, entityInstance );
EnhancerTestUtils.checkDirtyTracking(entityInstance, "id", "active", "someNumber");
EnhancerTestUtils.clearDirtyTracking(entityInstance);
// call the new methods
//
Method setter = entityClass.getMethod( EnhancerConstants.ENTITY_ENTRY_SETTER_NAME, EntityEntry.class );
Method getter = entityClass.getMethod( EnhancerConstants.ENTITY_ENTRY_GETTER_NAME );
assertNull( getter.invoke( entityInstance ) );
setter.invoke( entityInstance, makeEntityEntry() );
assertNotNull( getter.invoke( entityInstance ) );
setter.invoke( entityInstance, new Object[] {null} );
assertNull( getter.invoke( entityInstance ) );
// setting the same value should not make it dirty
entityClass.getMethod("setSomeNumber", long.class).invoke(entityInstance, 1L);
EnhancerTestUtils.checkDirtyTracking(entityInstance);
Method entityInstanceGetter = entityClass.getMethod( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME );
assertSame( entityInstance, entityInstanceGetter.invoke( entityInstance ) );
if(entityClassToEnhance.getName().endsWith(SimpleEntity.class.getSimpleName())) {
cl = new ClassLoader() {};
Method previousGetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_GETTER_NAME );
Method previousSetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class );
previousSetter.invoke( entityInstance, entityInstance );
assertSame( entityInstance, previousGetter.invoke( entityInstance ) );
Class<?> addressClass = EnhancerTestUtils.enhanceAndDecompile(Address.class, cl);
Class<?> countryClass = EnhancerTestUtils.enhanceAndDecompile(Country.class, cl);
Method nextGetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_GETTER_NAME );
Method nextSetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class );
nextSetter.invoke( entityInstance, entityInstance );
assertSame( entityInstance, nextGetter.invoke( entityInstance ) );
entityClass = EnhancerTestUtils.enhanceAndDecompile(entityClassToEnhance, cl);
entityInstance = entityClass.newInstance();
// add an attribute interceptor...
Method interceptorGetter = entityClass.getMethod( EnhancerConstants.INTERCEPTOR_GETTER_NAME );
Method interceptorSetter = entityClass.getMethod( EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class );
assertNull( interceptorGetter.invoke( entityInstance ) );
entityClass.getMethod( "getId" ).invoke( entityInstance );
interceptorSetter.invoke( entityInstance, new LocalPersistentAttributeInterceptor() );
assertNotNull(interceptorGetter.invoke(entityInstance));
// dirty checking is unfortunately just printlns for now... just verify the test output
entityClass.getMethod( "getId" ).invoke( entityInstance );
entityClass.getMethod( "setId", Long.class ).invoke( entityInstance, entityClass.getMethod( "getId" ).invoke( entityInstance ) );
entityClass.getMethod( "setId", Long.class ).invoke( entityInstance, 1L );
assertTrue((Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance));
entityClass.getMethod( "isActive" ).invoke( entityInstance );
entityClass.getMethod( "setActive", boolean.class ).invoke( entityInstance, entityClass.getMethod( "isActive" ).invoke( entityInstance ) );
entityClass.getMethod( "setActive", boolean.class ).invoke(entityInstance, true);
entityClass.getMethod( "getSomeNumber" ).invoke( entityInstance );
entityClass.getMethod( "setSomeNumber", long.class ).invoke( entityInstance, entityClass.getMethod( "getSomeNumber" ).invoke( entityInstance ) );
entityClass.getMethod( "setSomeNumber", long.class ).invoke(entityInstance, 1L);
assertEquals(3, ((Set) entityClass.getMethod("$$_hibernate_getDirtyAttributes").invoke(entityInstance)).size());
entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance);
//setting the same value should not make it dirty
entityClass.getMethod( "setSomeNumber", long.class ).invoke(entityInstance, 1L);
//assertEquals(0, ((Set) entityClass.getMethod("$$_hibernate_getDirtyAttributes").invoke(entityInstance)).size());
//assertFalse((Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance));
if(entityClass.getName().endsWith("SimpleEntity")) {
interceptorSetter = entityClass.getMethod(EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class);
interceptorSetter.invoke(entityInstance, new EnhancerTestUtils.LocalPersistentAttributeInterceptor());
List<String> strings = new ArrayList<String>();
strings.add("FooBar");
entityClass.getMethod("setSomeStrings", List.class).invoke(entityInstance, strings);
EnhancerTestUtils.checkDirtyTracking(entityInstance, "someStrings");
EnhancerTestUtils.clearDirtyTracking(entityInstance);
entityClass.getMethod( "setSomeStrings", java.util.List.class ).invoke(entityInstance, strings);
strings.add("JADA!");
EnhancerTestUtils.checkDirtyTracking(entityInstance, "someStrings");
EnhancerTestUtils.clearDirtyTracking(entityInstance);
assertTrue((Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance));
// this should not set the entity to dirty
Set<Integer> intSet = new HashSet<Integer>();
intSet.add(42);
entityClass.getMethod("setSomeInts", Set.class).invoke(entityInstance, intSet);
EnhancerTestUtils.checkDirtyTracking(entityInstance);
Set tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance);
assertEquals(1, tracked.size());
assertEquals("someStrings", tracked.iterator().next());
entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance);
((List) entityClass.getMethod( "getSomeStrings").invoke(entityInstance)).add("JADA!");
Boolean isDirty = (Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance);
assertTrue(isDirty);
assertEquals("someStrings",
((Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance)).iterator().next());
entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance);
//this should not set the entity to dirty
Set<Integer> ints = new HashSet<Integer>();
ints.add(42);
entityClass.getMethod( "setSomeInts", java.util.Set.class ).invoke(entityInstance, ints);
isDirty = (Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance);
assertFalse(isDirty);
//testing composite object
assert addressClass != null;
Object address = addressClass.newInstance();
assert countryClass != null;
Object country = countryClass.newInstance();
//Method adrInterceptorGetter = addressClass.getMethod( EnhancerConstants.INTERCEPTOR_GETTER_NAME );
//Method adrInterceptorSetter = addressClass.getMethod( EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class );
//adrInterceptorSetter.invoke( address, new LocalPersistentAttributeInterceptor() );
// testing composite object
Object address = addressClass.newInstance();
Object country = countryClass.newInstance();
entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address);
addressClass.getMethod("setCity", String.class).invoke(address, "Arendal");
EnhancerTestUtils.checkDirtyTracking(entityInstance, "address", "address.city");
tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance);
assertEquals(2, tracked.size());
Iterator iter = tracked.iterator();
assertEquals("address", iter.next());
assertEquals("address.city", iter.next());
entityClass.getMethod(EnhancerConstants.TRACKER_CLEAR_NAME).invoke(entityInstance);
entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance);
//make sure that new composite instances are cleared
Object address2 = addressClass.newInstance();
// make sure that new composite instances are cleared
Object address2 = addressClass.newInstance();
entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address2);
addressClass.getMethod("setStreet1", String.class).invoke(address, "Heggedalveien");
tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance);
assertEquals(1, tracked.size());
EnhancerTestUtils.checkDirtyTracking(entityInstance, "address");
addressClass.getMethod("setCountry", countryClass).invoke(address2, country);
countryClass.getMethod("setName", String.class).invoke(country, "Norway");
tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance);
assertEquals(3, tracked.size());
EnhancerTestUtils.checkDirtyTracking(entityInstance, "address", "address.country", "address.country.name");
}
}
}
private CtClass generateCtClassForAnEntity(Class entityClassToEnhance) throws Exception {
ClassPool cp = new ClassPool( false );
return cp.makeClass(
getClass().getClassLoader().getResourceAsStream(
entityClassToEnhance.getName().replace( '.', '/' ) + ".class"
)
);
}
private EntityEntry makeEntityEntry() {
return new EntityEntry(
Status.MANAGED,
null,
null,
new Long(1),
null,
LockMode.NONE,
false,
null,
false,
false,
null
);
}
private class LocalPersistentAttributeInterceptor implements PersistentAttributeInterceptor {
@Override
public boolean readBoolean(Object obj, String name, boolean oldValue) {
System.out.println( "Reading boolean [" + name + "]" );
return oldValue;
}
@Override
public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) {
System.out.println( "Writing boolean [" + name + "]" );
return newValue;
}
@Override
public byte readByte(Object obj, String name, byte oldValue) {
System.out.println( "Reading byte [" + name + "]" );
return oldValue;
}
@Override
public byte writeByte(Object obj, String name, byte oldValue, byte newValue) {
System.out.println( "Writing byte [" + name + "]" );
return newValue;
}
@Override
public char readChar(Object obj, String name, char oldValue) {
System.out.println( "Reading char [" + name + "]" );
return oldValue;
}
@Override
public char writeChar(Object obj, String name, char oldValue, char newValue) {
System.out.println( "Writing char [" + name + "]" );
return newValue;
}
@Override
public short readShort(Object obj, String name, short oldValue) {
System.out.println( "Reading short [" + name + "]" );
return oldValue;
}
@Override
public short writeShort(Object obj, String name, short oldValue, short newValue) {
System.out.println( "Writing short [" + name + "]" );
return newValue;
}
@Override
public int readInt(Object obj, String name, int oldValue) {
System.out.println( "Reading int [" + name + "]" );
return oldValue;
}
@Override
public int writeInt(Object obj, String name, int oldValue, int newValue) {
System.out.println( "Writing int [" + name + "]" );
return newValue;
}
@Override
public float readFloat(Object obj, String name, float oldValue) {
System.out.println( "Reading float [" + name + "]" );
return oldValue;
}
@Override
public float writeFloat(Object obj, String name, float oldValue, float newValue) {
System.out.println( "Writing float [" + name + "]" );
return newValue;
}
@Override
public double readDouble(Object obj, String name, double oldValue) {
System.out.println( "Reading double [" + name + "]" );
return oldValue;
}
@Override
public double writeDouble(Object obj, String name, double oldValue, double newValue) {
System.out.println( "Writing double [" + name + "]" );
return newValue;
}
@Override
public long readLong(Object obj, String name, long oldValue) {
System.out.println( "Reading long [" + name + "]" );
return oldValue;
}
@Override
public long writeLong(Object obj, String name, long oldValue, long newValue) {
System.out.println( "Writing long [" + name + "]" );
return newValue;
}
@Override
public Object readObject(Object obj, String name, Object oldValue) {
System.out.println( "Reading Object [" + name + "]" );
return oldValue;
}
@Override
public Object writeObject(Object obj, String name, Object oldValue, Object newValue) {
System.out.println( "Writing Object [" + name + "]" );
return newValue;
}
}
}

View File

@ -0,0 +1,324 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement;
import com.sun.tools.classfile.ConstantPoolException;
import com.sun.tools.javap.JavapTask;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import org.hibernate.LockMode;
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.EnhancerConstants;
import org.hibernate.engine.spi.CompositeOwner;
import org.hibernate.engine.spi.CompositeTracker;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.SelfDirtinessTracker;
import org.hibernate.engine.spi.Status;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* utility class to use in bytecode enhancement tests
*
* @author Steve Ebersole
*/
public abstract class EnhancerTestUtils extends BaseUnitTestCase {
private static EnhancementContext enhancementContext = new DefaultEnhancementContext();
private static String workingDir = System.getProperty("java.io.tmpdir");
private static final CoreMessageLogger log = CoreLogging.messageLogger(EnhancerTestUtils.class);
/**
* method that performs the enhancement of a class
* also checks the signature of enhanced entities methods using 'javap' decompiler
*/
static Class<?> enhanceAndDecompile(Class<?> classToEnhance, ClassLoader cl) throws Exception {
CtClass entityCtClass = generateCtClassForAnEntity(classToEnhance);
byte[] original = entityCtClass.toBytecode();
byte[] enhanced = new Enhancer(enhancementContext).enhance(entityCtClass.getName(), original);
assertFalse("entity was not enhanced", Arrays.equals(original, enhanced));
log.infof("enhanced entity [%s]", entityCtClass.getName());
ClassPool cp = new ClassPool(false);
cp.appendClassPath(new LoaderClassPath(cl));
CtClass enhancedCtClass = cp.makeClass(new ByteArrayInputStream(enhanced));
enhancedCtClass.debugWriteFile(workingDir);
decompileDumpedClass(classToEnhance.getName());
Class<?> enhancedClass = enhancedCtClass.toClass(cl, EnhancerTestUtils.class.getProtectionDomain());
assertNotNull(enhancedClass);
return enhancedClass;
}
private static void decompileDumpedClass(String className) {
try {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singletonList(new File(workingDir)));
JavapTask javapTask = new JavapTask();
for (JavaFileObject jfo : fileManager.getJavaFileObjects(workingDir + File.separator + getFilenameForClassName(className))) {
try {
Set<String> interfaceNames = new HashSet<String>();
Set<String> fieldNames = new HashSet<String>();
Set<String> methodNames = new HashSet<String>();
JavapTask.ClassFileInfo info = javapTask.read(jfo);
log.infof("decompiled class [%s]", info.cf.getName());
for (int i : info.cf.interfaces) {
interfaceNames.add(info.cf.constant_pool.getClassInfo(i).getName());
log.debugf("declared iFace = ", info.cf.constant_pool.getClassInfo(i).getName());
}
for (com.sun.tools.classfile.Field f : info.cf.fields) {
fieldNames.add(f.getName(info.cf.constant_pool));
log.debugf("declared field = ", f.getName(info.cf.constant_pool));
}
for (com.sun.tools.classfile.Method m : info.cf.methods) {
methodNames.add(m.getName(info.cf.constant_pool));
log.debugf("declared method = ", m.getName(info.cf.constant_pool));
}
// checks signature against known interfaces
if (interfaceNames.contains(PersistentAttributeInterceptor.class.getName())) {
assertTrue(fieldNames.contains(EnhancerConstants.INTERCEPTOR_FIELD_NAME));
assertTrue(methodNames.contains(EnhancerConstants.INTERCEPTOR_GETTER_NAME));
assertTrue(methodNames.contains(EnhancerConstants.INTERCEPTOR_SETTER_NAME));
}
if (interfaceNames.contains(ManagedEntity.class.getName())) {
assertTrue(methodNames.contains(EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME));
assertTrue(fieldNames.contains(EnhancerConstants.ENTITY_ENTRY_FIELD_NAME));
assertTrue(methodNames.contains(EnhancerConstants.ENTITY_ENTRY_GETTER_NAME));
assertTrue(methodNames.contains(EnhancerConstants.ENTITY_ENTRY_SETTER_NAME));
assertTrue(fieldNames.contains(EnhancerConstants.PREVIOUS_FIELD_NAME));
assertTrue(methodNames.contains(EnhancerConstants.PREVIOUS_GETTER_NAME));
assertTrue(methodNames.contains(EnhancerConstants.PREVIOUS_SETTER_NAME));
assertTrue(fieldNames.contains(EnhancerConstants.NEXT_FIELD_NAME));
assertTrue(methodNames.contains(EnhancerConstants.NEXT_GETTER_NAME));
assertTrue(methodNames.contains(EnhancerConstants.NEXT_SETTER_NAME));
}
if (interfaceNames.contains(SelfDirtinessTracker.class.getName())) {
assertTrue(fieldNames.contains(EnhancerConstants.TRACKER_FIELD_NAME));
assertTrue(methodNames.contains(EnhancerConstants.TRACKER_GET_NAME));
assertTrue(methodNames.contains(EnhancerConstants.TRACKER_CLEAR_NAME));
assertTrue(methodNames.contains(EnhancerConstants.TRACKER_HAS_CHANGED_NAME));
}
if (interfaceNames.contains(CompositeTracker.class.getName())) {
assertTrue(fieldNames.contains(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME));
assertTrue(methodNames.contains(EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER));
assertTrue(methodNames.contains(EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER));
}
if (interfaceNames.contains(CompositeOwner.class.getName())) {
assertTrue(fieldNames.contains(EnhancerConstants.TRACKER_CHANGER_NAME));
assertTrue(methodNames.contains(EnhancerConstants.TRACKER_CHANGER_NAME));
}
} catch (ConstantPoolException e) {
e.printStackTrace();
}
}
} catch (IOException ioe) {
assertNull("Failed to open class file", ioe);
} catch (RuntimeException re) {
log.warnf(re, "WARNING: UNABLE DECOMPILE DUE TO %s", re.getMessage());
}
}
private static CtClass generateCtClassForAnEntity(Class<?> entityClassToEnhance) throws Exception {
ClassPool cp = new ClassPool(false);
return cp.makeClass(EnhancerTestUtils.class.getClassLoader().getResourceAsStream(getFilenameForClassName(entityClassToEnhance.getName())));
}
private static String getFilenameForClassName(String className) {
return className.replace('.', File.separatorChar) + JavaFileObject.Kind.CLASS.extension;
}
/**
* clears the dirty set for an entity
*/
public static void clearDirtyTracking (Object entityInstance) {
try {
entityInstance.getClass().getMethod(EnhancerConstants.TRACKER_CLEAR_NAME).invoke(entityInstance);
checkDirtyTracking(entityInstance);
} catch (InvocationTargetException e) {
assertNull("Exception in clear dirty tracking", e);
} catch (NoSuchMethodException e) {
assertNull("Exception in clear dirty tracking", e);
} catch (IllegalAccessException e) {
assertNull("Exception in clear dirty tracking", e);
}
}
/**
* compares the dirty fields of an entity with a set of expected values
*/
public static void checkDirtyTracking (Object entityInstance, String ... dirtyFields) {
try {
assertTrue((dirtyFields.length == 0) != (Boolean) entityInstance.getClass().getMethod(EnhancerConstants.TRACKER_HAS_CHANGED_NAME).invoke(entityInstance));
Set<?> tracked = (Set<?>) entityInstance.getClass().getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance);
assertEquals(dirtyFields.length, tracked.size());
assertTrue(tracked.containsAll(Arrays.asList(dirtyFields)));
} catch (InvocationTargetException e) {
assertNull("Exception while checking dirty tracking", e);
} catch (NoSuchMethodException e) {
assertNull("Exception while checking dirty tracking", e);
} catch (IllegalAccessException e) {
assertNull("Exception while checking dirty tracking", e);
}
}
static EntityEntry makeEntityEntry() {
return new EntityEntry(
Status.MANAGED,
null,
null,
1,
null,
LockMode.NONE,
false,
null,
false,
false,
null
);
}
public static class LocalPersistentAttributeInterceptor implements PersistentAttributeInterceptor {
@Override public boolean readBoolean(Object obj, String name, boolean oldValue) {
log.infof( "Reading boolean [%s]" , name );
return oldValue;
}
@Override public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) {
log.infof( "Writing boolean []", name );
return newValue;
}
@Override public byte readByte(Object obj, String name, byte oldValue) {
log.infof( "Reading byte [%s]", name );
return oldValue;
}
@Override public byte writeByte(Object obj, String name, byte oldValue, byte newValue) {
log.infof( "Writing byte [%s]", name );
return newValue;
}
@Override public char readChar(Object obj, String name, char oldValue) {
log.infof( "Reading char [%s]", name );
return oldValue;
}
@Override public char writeChar(Object obj, String name, char oldValue, char newValue) {
log.infof( "Writing char [%s]", name );
return newValue;
}
@Override public short readShort(Object obj, String name, short oldValue) {
log.infof( "Reading short [%s]", name );
return oldValue;
}
@Override public short writeShort(Object obj, String name, short oldValue, short newValue) {
log.infof( "Writing short [%s]", name );
return newValue;
}
@Override public int readInt(Object obj, String name, int oldValue) {
log.infof( "Reading int [%s]", name );
return oldValue;
}
@Override public int writeInt(Object obj, String name, int oldValue, int newValue) {
log.infof( "Writing int [%s]", name );
return newValue;
}
@Override public float readFloat(Object obj, String name, float oldValue) {
log.infof( "Reading float [%s]", name );
return oldValue;
}
@Override public float writeFloat(Object obj, String name, float oldValue, float newValue) {
log.infof( "Writing float [%s]", name );
return newValue;
}
@Override public double readDouble(Object obj, String name, double oldValue) {
log.infof( "Reading double [%s]", name );
return oldValue;
}
@Override public double writeDouble(Object obj, String name, double oldValue, double newValue) {
log.infof( "Writing double [%s]", name );
return newValue;
}
@Override public long readLong(Object obj, String name, long oldValue) {
log.infof( "Reading long [%s]", name );
return oldValue;
}
@Override public long writeLong(Object obj, String name, long oldValue, long newValue) {
log.infof( "Writing long [%s]", name );
return newValue;
}
@Override public Object readObject(Object obj, String name, Object oldValue) {
log.infof( "Reading Object [%s]", name );
return oldValue;
}
@Override public Object writeObject(Object obj, String name, Object oldValue, Object newValue) {
log.infof( "Writing Object [%s]", name );
return newValue;
}
}
}

View File

@ -25,6 +25,7 @@ package org.hibernate.test.bytecode.enhancement;
import org.hibernate.Session;
import org.hibernate.test.bytecode.enhancement.entity.MyEntity;
import org.junit.Test;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
@ -53,14 +54,14 @@ public class MostBasicEnhancementTest extends BaseCoreFunctionalTestCase {
s = openSession();
s.beginTransaction();
MyEntity myEntity = (MyEntity) s.get( MyEntity.class, 1L );
MyEntity myEntity1 = (MyEntity) s.get( MyEntity.class, 1L );
MyEntity myEntity2 = (MyEntity) s.get( MyEntity.class, 2L );
assertNotNull( myEntity.$$_hibernate_getEntityInstance() );
assertSame( myEntity, myEntity.$$_hibernate_getEntityInstance() );
assertNotNull( myEntity.$$_hibernate_getEntityEntry() );
assertNull( myEntity.$$_hibernate_getPreviousManagedEntity() );
assertNotNull( myEntity.$$_hibernate_getNextManagedEntity() );
assertNotNull( myEntity1.$$_hibernate_getEntityInstance() );
assertSame( myEntity1, myEntity1.$$_hibernate_getEntityInstance() );
assertNotNull( myEntity1.$$_hibernate_getEntityEntry() );
assertNull( myEntity1.$$_hibernate_getPreviousManagedEntity() );
assertNotNull( myEntity1.$$_hibernate_getNextManagedEntity() );
assertNotNull( myEntity2.$$_hibernate_getEntityInstance() );
assertSame( myEntity2, myEntity2.$$_hibernate_getEntityInstance() );
@ -72,7 +73,7 @@ public class MostBasicEnhancementTest extends BaseCoreFunctionalTestCase {
s.getTransaction().commit();
s.close();
assertNull( myEntity.$$_hibernate_getEntityEntry() );
assertNull( myEntity1.$$_hibernate_getEntityEntry() );
}

View File

@ -1,382 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement.customer;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.LoaderClassPath;
import org.hibernate.EntityMode;
import org.hibernate.LockMode;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.bytecode.enhance.spi.Enhancer;
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.spi.Status;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;
import javax.persistence.ElementCollection;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
/**
* @author Steve Ebersole
*/
public class CustomerEnhancerTest extends BaseUnitTestCase {
private static EnhancementContext enhancementContext = new EnhancementContext() {
@Override
public ClassLoader getLoadingClassLoader() {
return getClass().getClassLoader();
}
@Override
public boolean isEntityClass(CtClass classDescriptor) {
return true;
}
@Override
public boolean isCompositeClass(CtClass classDescriptor) {
return false;
}
@Override
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
return true;
}
@Override
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
return true;
}
@Override
public boolean isLazyLoadable(CtField field) {
return true;
}
@Override
public boolean isMappedCollection(CtField field) {
try {
return (field.getAnnotation(OneToMany.class) != null ||
field.getAnnotation(ManyToMany.class) != null ||
field.getAnnotation(ElementCollection.class) != null);
}
catch (ClassNotFoundException e) {
return false;
}
}
@Override
public boolean isPersistentField(CtField ctField) {
return true;
}
@Override
public CtField[] order(CtField[] persistentFields) {
return persistentFields;
}
};
@Test
public void testEnhancement() throws Exception {
testFor( Customer.class );
}
private void testFor(Class entityClassToEnhance) throws Exception {
Enhancer enhancer = new Enhancer( enhancementContext );
CtClass entityCtClass = generateCtClassForAnEntity( entityClassToEnhance );
byte[] original = entityCtClass.toBytecode();
//byte[] enhanced = enhancer.enhance( entityCtClass.getName(), original );
byte[] enhanced = enhancer.enhance( entityCtClass.getName(), original );
assertFalse( "entity was not enhanced", Arrays.equals( original, enhanced ) );
ClassLoader cl = new ClassLoader() { };
ClassPool cp = new ClassPool( false );
cp.appendClassPath( new LoaderClassPath( cl ) );
CtClass enhancedCtClass = cp.makeClass( new ByteArrayInputStream( enhanced ) );
enhancedCtClass.debugWriteFile("/tmp");
//just for debugging
Class addressClass = null;
Class customerInventoryClass = null;
Class supplierComponentPKClass = null;
CtClass addressCtClass = generateCtClassForAnEntity( org.hibernate.test.bytecode.enhancement.customer.Address.class );
byte[] enhancedAddress = enhancer.enhanceComposite(Address.class.getName(), addressCtClass.toBytecode());
CtClass enhancedCtClassAddress = cp.makeClass( new ByteArrayInputStream( enhancedAddress ) );
enhancedCtClassAddress.debugWriteFile("/tmp");
addressClass = enhancedCtClassAddress.toClass( cl, this.getClass().getProtectionDomain() );
CtClass customerInventoryCtClass = generateCtClassForAnEntity( CustomerInventory.class );
byte[] enhancedCustomerInventory = enhancer.enhance(CustomerInventory.class.getName(), customerInventoryCtClass.toBytecode());
CtClass enhancedCtClassCustomerInventory = cp.makeClass( new ByteArrayInputStream( enhancedCustomerInventory ) );
enhancedCtClassCustomerInventory.debugWriteFile("/tmp");
customerInventoryClass = enhancedCtClassCustomerInventory.toClass( cl, this.getClass().getProtectionDomain() );
CtClass supplierComponentPKCtClass = generateCtClassForAnEntity( SupplierComponentPK.class );
byte[] enhancedSupplierComponentPK = enhancer.enhanceComposite(SupplierComponentPK.class.getName(), supplierComponentPKCtClass.toBytecode());
CtClass enhancedCtClassSupplierComponentPK = cp.makeClass( new ByteArrayInputStream( enhancedSupplierComponentPK ) );
enhancedCtClassSupplierComponentPK.debugWriteFile("/tmp");
supplierComponentPKClass = enhancedCtClassSupplierComponentPK.toClass( cl, this.getClass().getProtectionDomain() );
Class entityClass = enhancedCtClass.toClass( cl, this.getClass().getProtectionDomain() );
Object entityInstance = entityClass.newInstance();
assertTyping( ManagedEntity.class, entityInstance );
// call the new methods
//
Method setter = entityClass.getMethod( EnhancerConstants.ENTITY_ENTRY_SETTER_NAME, EntityEntry.class );
Method getter = entityClass.getMethod( EnhancerConstants.ENTITY_ENTRY_GETTER_NAME );
assertNull( getter.invoke( entityInstance ) );
setter.invoke( entityInstance, makeEntityEntry() );
assertNotNull( getter.invoke( entityInstance ) );
setter.invoke( entityInstance, new Object[] {null} );
assertNull( getter.invoke( entityInstance ) );
Method entityInstanceGetter = entityClass.getMethod( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME );
assertSame( entityInstance, entityInstanceGetter.invoke( entityInstance ) );
Method previousGetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_GETTER_NAME );
Method previousSetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class );
previousSetter.invoke( entityInstance, entityInstance );
assertSame( entityInstance, previousGetter.invoke( entityInstance ) );
Method nextGetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_GETTER_NAME );
Method nextSetter = entityClass.getMethod( EnhancerConstants.PREVIOUS_SETTER_NAME, ManagedEntity.class );
nextSetter.invoke( entityInstance, entityInstance );
assertSame( entityInstance, nextGetter.invoke( entityInstance ) );
// add an attribute interceptor...
Method interceptorGetter = entityClass.getMethod( EnhancerConstants.INTERCEPTOR_GETTER_NAME );
Method interceptorSetter = entityClass.getMethod( EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class );
assertNull( interceptorGetter.invoke( entityInstance ) );
entityClass.getMethod( "getId" ).invoke( entityInstance );
interceptorSetter.invoke( entityInstance, new LocalPersistentAttributeInterceptor() );
assertNotNull(interceptorGetter.invoke(entityInstance));
// dirty checking is unfortunately just printlns for now... just verify the test output
entityClass.getMethod( "getId" ).invoke( entityInstance );
entityClass.getMethod( "setId", Integer.class ).invoke( entityInstance, entityClass.getMethod( "getId" ).invoke( entityInstance ) );
entityClass.getMethod( "setId", Integer.class ).invoke( entityInstance, 1 );
assertTrue((Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(entityInstance));
entityClass.getMethod( "setFirstName", String.class ).invoke(entityInstance, "Erik");
entityClass.getMethod( "setLastName", String.class ).invoke(entityInstance, "Mykland");
assertEquals(3, ((Set) entityClass.getMethod("$$_hibernate_getDirtyAttributes").invoke(entityInstance)).size());
entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance);
//testing composite object
assert addressClass != null;
Object address = addressClass.newInstance();
assert customerInventoryClass != null;
Object customerInventory = customerInventoryClass.newInstance();
//Method adrInterceptorGetter = addressClass.getMethod( EnhancerConstants.INTERCEPTOR_GETTER_NAME );
//Method adrInterceptorSetter = addressClass.getMethod( EnhancerConstants.INTERCEPTOR_SETTER_NAME, PersistentAttributeInterceptor.class );
//adrInterceptorSetter.invoke( address, new LocalPersistentAttributeInterceptor() );
entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address);
addressClass.getMethod("setCity", String.class).invoke(address, "Arendal");
Set tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance);
assertEquals(2, tracked.size());
Iterator iter = tracked.iterator();
assertEquals("address", iter.next());
assertEquals("address.city", iter.next());
entityClass.getMethod("$$_hibernate_clearDirtyAttributes").invoke(entityInstance);
//make sure that new composite instances are cleared
Object address2 = addressClass.newInstance();
entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address2);
addressClass.getMethod("setStreet1", String.class).invoke(address, "Heggedalveien");
tracked = (Set) entityClass.getMethod(EnhancerConstants.TRACKER_GET_NAME).invoke(entityInstance);
assertEquals(1, tracked.size());
}
private CtClass generateCtClassForAnEntity(Class entityClassToEnhance) throws Exception {
ClassPool cp = new ClassPool( false );
return cp.makeClass(
getClass().getClassLoader().getResourceAsStream(
entityClassToEnhance.getName().replace( '.', '/' ) + ".class"
)
);
}
private EntityEntry makeEntityEntry() {
return new EntityEntry(
Status.MANAGED,
null,
null,
new Long(1),
null,
LockMode.NONE,
false,
null,
false,
false,
null
);
}
private class LocalPersistentAttributeInterceptor implements PersistentAttributeInterceptor {
@Override
public boolean readBoolean(Object obj, String name, boolean oldValue) {
System.out.println( "Reading boolean [" + name + "]" );
return oldValue;
}
@Override
public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) {
System.out.println( "Writing boolean [" + name + "]" );
return newValue;
}
@Override
public byte readByte(Object obj, String name, byte oldValue) {
System.out.println( "Reading byte [" + name + "]" );
return oldValue;
}
@Override
public byte writeByte(Object obj, String name, byte oldValue, byte newValue) {
System.out.println( "Writing byte [" + name + "]" );
return newValue;
}
@Override
public char readChar(Object obj, String name, char oldValue) {
System.out.println( "Reading char [" + name + "]" );
return oldValue;
}
@Override
public char writeChar(Object obj, String name, char oldValue, char newValue) {
System.out.println( "Writing char [" + name + "]" );
return newValue;
}
@Override
public short readShort(Object obj, String name, short oldValue) {
System.out.println( "Reading short [" + name + "]" );
return oldValue;
}
@Override
public short writeShort(Object obj, String name, short oldValue, short newValue) {
System.out.println( "Writing short [" + name + "]" );
return newValue;
}
@Override
public int readInt(Object obj, String name, int oldValue) {
System.out.println( "Reading int [" + name + "]" );
return oldValue;
}
@Override
public int writeInt(Object obj, String name, int oldValue, int newValue) {
System.out.println( "Writing int [" + name + "]" );
return newValue;
}
@Override
public float readFloat(Object obj, String name, float oldValue) {
System.out.println( "Reading float [" + name + "]" );
return oldValue;
}
@Override
public float writeFloat(Object obj, String name, float oldValue, float newValue) {
System.out.println( "Writing float [" + name + "]" );
return newValue;
}
@Override
public double readDouble(Object obj, String name, double oldValue) {
System.out.println( "Reading double [" + name + "]" );
return oldValue;
}
@Override
public double writeDouble(Object obj, String name, double oldValue, double newValue) {
System.out.println( "Writing double [" + name + "]" );
return newValue;
}
@Override
public long readLong(Object obj, String name, long oldValue) {
System.out.println( "Reading long [" + name + "]" );
return oldValue;
}
@Override
public long writeLong(Object obj, String name, long oldValue, long newValue) {
System.out.println( "Writing long [" + name + "]" );
return newValue;
}
@Override
public Object readObject(Object obj, String name, Object oldValue) {
System.out.println( "Reading Object [" + name + "]" );
return oldValue;
}
@Override
public Object writeObject(Object obj, String name, Object oldValue, Object newValue) {
System.out.println( "Writing Object [" + name + "]" );
return newValue;
}
}
}

View File

@ -4,7 +4,7 @@
* Licensed under the Eclipse Public License version 1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.hibernate.test.bytecode.enhancement;
package org.hibernate.test.bytecode.enhancement.entity;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;

View File

@ -4,7 +4,7 @@
* Licensed under the Eclipse Public License version 1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.hibernate.test.bytecode.enhancement;
package org.hibernate.test.bytecode.enhancement.entity;
import javax.persistence.Embeddable;

View File

@ -21,15 +21,15 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement;
package org.hibernate.test.bytecode.enhancement.entity;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Transient;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.EntityEntry;
/**
* @author Steve Ebersole
*/

View File

@ -21,16 +21,16 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement;
import javax.persistence.Id;
import javax.persistence.Transient;
package org.hibernate.test.bytecode.enhancement.entity;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import javax.persistence.Id;
import javax.persistence.Transient;
/**
* @author Steve Ebersole
*/

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement;
package org.hibernate.test.bytecode.enhancement.entity;
import javax.persistence.Embedded;
import javax.persistence.Entity;

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement;
package org.hibernate.test.bytecode.enhancement.entity;
import javax.persistence.Entity;
import javax.persistence.Id;

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement;
package org.hibernate.test.bytecode.enhancement.entity;
import javax.persistence.Entity;

View File

@ -20,13 +20,15 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/package org.hibernate.test.bytecode.enhancement.customer;
*/package org.hibernate.test.bytecode.enhancement.entity.customer;
import javax.persistence.Embeddable;
import java.io.Serializable;
/**
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
*/
@Embeddable
public class Address implements Serializable {
private String street1;
private String street2;

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement.customer;
package org.hibernate.test.bytecode.enhancement.entity.customer;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;

View File

@ -20,7 +20,8 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/package org.hibernate.test.bytecode.enhancement.customer;
*/
package org.hibernate.test.bytecode.enhancement.entity.customer;
/**
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>

View File

@ -4,7 +4,7 @@
* Licensed under the Eclipse Public License version 1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.hibernate.test.bytecode.enhancement.customer;
package org.hibernate.test.bytecode.enhancement.entity.customer;
import java.io.Serializable;

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement.customer;
package org.hibernate.test.bytecode.enhancement.entity.customer;
import javax.persistence.Embeddable;

View File

@ -21,9 +21,9 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement;
package org.hibernate.test.bytecode.enhancement.tracker;
import org.hibernate.bytecode.enhance.spi.CompositeOwnerTracker;
import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker;
import org.hibernate.engine.spi.CompositeOwner;
import org.junit.Test;
@ -32,7 +32,6 @@ import static org.junit.Assert.assertEquals;
/**
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
*/
public class CompositeOwnerTrackerTest {
private int counter = 0;

View File

@ -0,0 +1,104 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.bytecode.enhancement.tracker;
import org.hibernate.bytecode.enhance.internal.tracker.SortedDirtyTracker;
import org.hibernate.bytecode.enhance.internal.tracker.SimpleDirtyTracker;
import org.junit.Test;
import java.util.Set;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
*/
public class DirtyTrackerTest {
@Test
public void testSimpleTracker() {
SimpleDirtyTracker tracker = new SimpleDirtyTracker();
assertTrue(tracker.isEmpty());
assertTrue(tracker.asSet().isEmpty());
tracker.add("foo");
assertFalse(tracker.isEmpty());
assertArrayEquals(tracker.asSet().toArray(), new String[]{"foo"});
tracker.clear();
assertTrue(tracker.isEmpty());
assertTrue(tracker.asSet().isEmpty());
tracker.add("foo");
tracker.add("bar");
tracker.add("another.bar");
tracker.add("foo");
tracker.add("another.foo");
tracker.add("another.bar");
assertTrue(tracker.asSet().size() == 4);
}
@Test
public void testSortedTracker() {
SortedDirtyTracker tracker = new SortedDirtyTracker();
assertTrue(tracker.isEmpty());
assertTrue(tracker.asSet().isEmpty());
tracker.add("foo");
assertFalse(tracker.isEmpty());
assertArrayEquals(tracker.asSet().toArray(), new String[]{"foo"});
tracker.clear();
assertTrue(tracker.isEmpty());
assertTrue(tracker.asSet().isEmpty());
tracker.add("foo");
tracker.add("bar");
tracker.add("another.bar");
tracker.add("foo");
tracker.add("another.foo");
tracker.add("another.bar");
assertTrue(tracker.asSet().size() == 4);
// we the algorithm for this implementation relies on the fact that the array is kept sorted, so let's check it really is
assertTrue(isSorted(tracker.asSet()));
}
private boolean isSorted(Set<String> set) {
String[] arr = new String[set.size()];
arr = set.toArray(arr);
for (int i = 1; i < arr.length; i++) {
if (arr[i - 1].compareTo(arr[i]) > 0) {
return false;
}
}
return true;
}
}