HHH-8354 - New dirty-checking options based on bytecode enhancement
This commit is contained in:
parent
d476eb7e16
commit
cf903b78f0
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
public class CollectionTracker {
|
||||
|
||||
private Map<String, Integer> tracker;
|
||||
|
||||
public CollectionTracker() {
|
||||
tracker = new HashMap<String, Integer>();
|
||||
}
|
||||
|
||||
public void add(String name, int size) {
|
||||
tracker.put(name, size);
|
||||
}
|
||||
|
||||
public int getSize(String name) {
|
||||
Integer size = tracker.get(name);
|
||||
if(size == null)
|
||||
return -1;
|
||||
else
|
||||
return size;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 org.hibernate.engine.spi.CompositeOwner;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
private String[] names;
|
||||
private CompositeOwner[] owners;
|
||||
private int size = 0;
|
||||
|
||||
public CompositeOwnerTracker() {
|
||||
names = new String[1];
|
||||
owners = new CompositeOwner[1];
|
||||
}
|
||||
|
||||
public void add(String name, CompositeOwner owner) {
|
||||
for(int i=0; i<size;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++;
|
||||
}
|
||||
|
||||
public void callOwner(String fieldName) {
|
||||
for(int i=0; i < size;i++) {
|
||||
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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -121,4 +121,11 @@ public interface EnhancementContext {
|
|||
* @return {@code true} if the field is lazy loadable; {@code false} otherwise.
|
||||
*/
|
||||
public boolean isLazyLoadable(CtField field);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param field the field to check
|
||||
* @return {@code true} if the field is mapped
|
||||
*/
|
||||
public boolean isMappedCollection(CtField field);
|
||||
}
|
||||
|
|
|
@ -23,14 +23,21 @@
|
|||
*/
|
||||
package org.hibernate.bytecode.enhance.spi;
|
||||
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Transient;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javassist.CannotCompileException;
|
||||
import javassist.ClassPool;
|
||||
|
@ -49,10 +56,12 @@ import javassist.bytecode.ConstPool;
|
|||
import javassist.bytecode.FieldInfo;
|
||||
import javassist.bytecode.MethodInfo;
|
||||
import javassist.bytecode.Opcode;
|
||||
import javassist.bytecode.SignatureAttribute;
|
||||
import javassist.bytecode.StackMapTable;
|
||||
import javassist.bytecode.annotation.Annotation;
|
||||
import javassist.bytecode.stackmap.MapMaker;
|
||||
|
||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
|
@ -82,8 +91,9 @@ public class Enhancer {
|
|||
private final CtClass attributeInterceptableCtClass;
|
||||
private final CtClass entityEntryCtClass;
|
||||
private final CtClass objectCtClass;
|
||||
private boolean isComposite;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Constructs the Enhancer, using the given context.
|
||||
*
|
||||
* @param enhancementContext Describes the context in which enhancement will occur so as to give access
|
||||
|
@ -163,12 +173,31 @@ public class Enhancer {
|
|||
managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) );
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.unableToBuildEnhancementMetamodel( className );
|
||||
log.unableToBuildEnhancementMetamodel(className);
|
||||
return originalBytes;
|
||||
}
|
||||
|
||||
enhance( managedCtClass );
|
||||
enhance(managedCtClass, false);
|
||||
|
||||
return getByteCode(managedCtClass);
|
||||
}
|
||||
|
||||
public byte[] enhanceComposite(String className, byte[] originalBytes) throws EnhancementException {
|
||||
final CtClass managedCtClass;
|
||||
try {
|
||||
managedCtClass = classPool.makeClassIfNew( new ByteArrayInputStream( originalBytes ) );
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.unableToBuildEnhancementMetamodel(className);
|
||||
return originalBytes;
|
||||
}
|
||||
|
||||
enhance(managedCtClass, true);
|
||||
|
||||
return getByteCode(managedCtClass);
|
||||
}
|
||||
|
||||
private byte[] getByteCode(CtClass managedCtClass) {
|
||||
final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
final DataOutputStream out;
|
||||
try {
|
||||
|
@ -190,9 +219,10 @@ public class Enhancer {
|
|||
log.unableToTransformClass( e.getMessage() );
|
||||
throw new HibernateException( "Unable to transform class: " + e.getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void enhance(CtClass managedCtClass) {
|
||||
private void enhance(CtClass managedCtClass, boolean isComposite) {
|
||||
this.isComposite = isComposite;
|
||||
final String className = managedCtClass.getName();
|
||||
log.debugf( "Enhancing %s", className );
|
||||
|
||||
|
@ -212,10 +242,10 @@ public class Enhancer {
|
|||
}
|
||||
}
|
||||
|
||||
if ( enhancementContext.isEntityClass( managedCtClass ) ) {
|
||||
if (!isComposite && enhancementContext.isEntityClass( managedCtClass ) ) {
|
||||
enhanceAsEntity( managedCtClass );
|
||||
}
|
||||
else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
else if (isComposite || enhancementContext.isCompositeClass( managedCtClass ) ) {
|
||||
enhanceAsComposite( managedCtClass );
|
||||
}
|
||||
else {
|
||||
|
@ -303,7 +333,7 @@ public class Enhancer {
|
|||
return annotationsAttribute;
|
||||
}
|
||||
|
||||
private void enhancePersistentAttributes(CtClass managedCtClass) {
|
||||
private void enhancePersistentAttributes(CtClass managedCtClass ) {
|
||||
addInterceptorHandling( managedCtClass );
|
||||
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
addInLineDirtyHandling( managedCtClass );
|
||||
|
@ -366,6 +396,34 @@ public class Enhancer {
|
|||
return enhancementContext.order( persistentFieldList.toArray( new CtField[persistentFieldList.size()]) );
|
||||
}
|
||||
|
||||
private List<CtField> collectCollectionFields(CtClass managedCtClass) {
|
||||
|
||||
final List<CtField> collectionList = new ArrayList<CtField>();
|
||||
try {
|
||||
for ( CtField ctField : managedCtClass.getDeclaredFields() ) {
|
||||
// skip static fields
|
||||
if ( Modifier.isStatic( ctField.getModifiers() ) ) {
|
||||
continue;
|
||||
}
|
||||
// skip fields added by enhancement
|
||||
if ( ctField.getName().startsWith( "$" ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( enhancementContext.isPersistentField( ctField ) ) {
|
||||
for(CtClass ctClass : ctField.getType().getInterfaces()) {
|
||||
if(ctClass.getName().equals("java.util.Collection")) {
|
||||
collectionList.add(ctField);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NotFoundException ignored) { }
|
||||
|
||||
return collectionList;
|
||||
}
|
||||
|
||||
private void addInterceptorHandling(CtClass managedCtClass) {
|
||||
// interceptor handling is only needed if either:
|
||||
// a) in-line dirty checking has *not* been requested
|
||||
|
@ -390,9 +448,266 @@ public class Enhancer {
|
|||
);
|
||||
}
|
||||
|
||||
private void addInLineDirtyHandling(CtClass managedCtClass) {
|
||||
// todo : implement
|
||||
}
|
||||
private boolean isClassAlreadyTrackingDirtyStatus(CtClass managedCtClass) {
|
||||
try {
|
||||
for(CtClass ctInterface : managedCtClass.getInterfaces()) {
|
||||
if(ctInterface.getName().equals(SelfDirtinessTracker.class.getName()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addInLineDirtyHandling(CtClass managedCtClass ) {
|
||||
try {
|
||||
|
||||
//create composite methods
|
||||
if(isComposite) {
|
||||
managedCtClass.addInterface(classPool.get("org.hibernate.engine.spi.CompositeTracker"));
|
||||
CtClass compositeCtType = classPool.get("org.hibernate.bytecode.enhance.spi.CompositeOwnerTracker");
|
||||
addField(managedCtClass, compositeCtType, EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, true);
|
||||
createCompositeTrackerMethod(managedCtClass);
|
||||
}
|
||||
// "normal" entity
|
||||
else {
|
||||
managedCtClass.addInterface(classPool.get("org.hibernate.engine.spi.SelfDirtinessTracker"));
|
||||
CtClass trackerCtType = classPool.get("java.util.Set");
|
||||
addField(managedCtClass, trackerCtType, EnhancerConstants.TRACKER_FIELD_NAME, true);
|
||||
|
||||
CtClass collectionTrackerCtType = classPool.get("org.hibernate.bytecode.enhance.spi.CollectionTracker");
|
||||
addField(managedCtClass, collectionTrackerCtType, EnhancerConstants.TRACKER_COLLECTION_NAME, true);
|
||||
|
||||
createDirtyTrackerMethods(managedCtClass);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all dirty tracker methods
|
||||
*/
|
||||
private void createDirtyTrackerMethods(CtClass managedCtClass) {
|
||||
try {
|
||||
String trackerChangeMethod =
|
||||
"public void "+EnhancerConstants.TRACKER_CHANGER_NAME+"(String name) {" +
|
||||
" if(" +EnhancerConstants.TRACKER_FIELD_NAME+ " == null) {" +
|
||||
" "+EnhancerConstants.TRACKER_FIELD_NAME+ " = new java.util.HashSet();" +
|
||||
" }" +
|
||||
" if(!" +EnhancerConstants.TRACKER_FIELD_NAME+ ".contains(name)) {" +
|
||||
" "+EnhancerConstants.TRACKER_FIELD_NAME+".add(name);" +
|
||||
" }"+
|
||||
"}";
|
||||
managedCtClass.addMethod(CtNewMethod.make(trackerChangeMethod, managedCtClass));
|
||||
|
||||
createCollectionDirtyCheckMethod(managedCtClass);
|
||||
createCollectionDirtyCheckGetFieldsMethod(managedCtClass);
|
||||
//createCompositeFieldsDirtyCheckMethod(managedCtClass);
|
||||
//createGetCompositeDirtyFieldsMethod(managedCtClass);
|
||||
|
||||
createHasDirtyAttributesMethod(managedCtClass);
|
||||
|
||||
createClearDirtyCollectionMethod(managedCtClass);
|
||||
createClearDirtyMethod(managedCtClass);
|
||||
|
||||
String trackerGetMethod =
|
||||
"public java.util.List "+EnhancerConstants.TRACKER_GET_NAME+"() { "+
|
||||
"if("+ EnhancerConstants.TRACKER_FIELD_NAME+" == null) "+
|
||||
EnhancerConstants.TRACKER_FIELD_NAME+" = new java.util.HashSet();"+
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME+"("+
|
||||
EnhancerConstants.TRACKER_FIELD_NAME+");"+
|
||||
"return "+EnhancerConstants.TRACKER_FIELD_NAME+"; }";
|
||||
CtMethod getMethod = CtNewMethod.make(trackerGetMethod, managedCtClass);
|
||||
|
||||
MethodInfo methodInfo = getMethod.getMethodInfo();
|
||||
SignatureAttribute signatureAttribute =
|
||||
new SignatureAttribute(methodInfo.getConstPool(), "()Ljava/util/Set<Ljava/lang/String;>;");
|
||||
methodInfo.addAttribute(signatureAttribute);
|
||||
managedCtClass.addMethod(getMethod);
|
||||
|
||||
}
|
||||
catch (CannotCompileException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void createTrackChangeCompositeMethod(CtClass managedCtClass) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("public void ")
|
||||
.append(EnhancerConstants.TRACKER_CHANGER_NAME)
|
||||
.append("(String name) {")
|
||||
.append("if (")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME)
|
||||
.append(" != null) ")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME)
|
||||
.append(".callOwner(\".\"+name); }");
|
||||
|
||||
System.out.println("COMPOSITE METHOD: "+builder.toString());
|
||||
|
||||
try {
|
||||
managedCtClass.addMethod(CtNewMethod.make(builder.toString(), managedCtClass));
|
||||
}
|
||||
catch (CannotCompileException e) {
|
||||
}
|
||||
}
|
||||
|
||||
private void createCompositeTrackerMethod(CtClass managedCtClass) {
|
||||
try {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("public void ")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER)
|
||||
.append("(String name, org.hibernate.engine.spi.CompositeOwner tracker) {")
|
||||
.append("if(")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME)
|
||||
.append(" == null) ")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME)
|
||||
.append(" = new org.hibernate.bytecode.enhance.spi.CompositeOwnerTracker();")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME)
|
||||
.append(".add(name, tracker); }");
|
||||
|
||||
managedCtClass.addMethod(CtNewMethod.make(builder.toString(), managedCtClass));
|
||||
|
||||
builder = new StringBuilder();
|
||||
builder.append("public void ")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER)
|
||||
.append("(String name) {")
|
||||
.append(" if(")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME)
|
||||
.append(" != null)")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME)
|
||||
.append(".removeOwner(name);}");
|
||||
|
||||
managedCtClass.addMethod(CtNewMethod.make(builder.toString(), managedCtClass));
|
||||
}
|
||||
catch (CannotCompileException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void createHasDirtyAttributesMethod(CtClass managedCtClass) throws CannotCompileException {
|
||||
String trackerHasChangedMethod =
|
||||
"public boolean "+EnhancerConstants.TRACKER_HAS_CHANGED_NAME+"() { return ("+
|
||||
EnhancerConstants.TRACKER_FIELD_NAME+" != null && !" +
|
||||
EnhancerConstants.TRACKER_FIELD_NAME+".isEmpty()) || "+
|
||||
EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME+"(); } ";
|
||||
|
||||
managedCtClass.addMethod(CtNewMethod.make(trackerHasChangedMethod, managedCtClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates _clearDirtyAttributes
|
||||
*/
|
||||
private void createClearDirtyMethod(CtClass managedCtClass) throws CannotCompileException, ClassNotFoundException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("public void ")
|
||||
.append( EnhancerConstants.TRACKER_CLEAR_NAME)
|
||||
.append("() {")
|
||||
.append("if (")
|
||||
.append(EnhancerConstants.TRACKER_FIELD_NAME)
|
||||
.append(" != null) ")
|
||||
.append(EnhancerConstants.TRACKER_FIELD_NAME)
|
||||
.append(".clear(); ")
|
||||
.append(EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME)
|
||||
.append("(); }");
|
||||
|
||||
managedCtClass.addMethod(CtNewMethod.make(builder.toString(), managedCtClass));
|
||||
}
|
||||
|
||||
private void createClearDirtyCollectionMethod(CtClass managedCtClass) throws CannotCompileException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("private void ")
|
||||
.append(EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME)
|
||||
.append("() { if(")
|
||||
.append(EnhancerConstants.TRACKER_COLLECTION_NAME)
|
||||
.append(" == null)")
|
||||
.append(EnhancerConstants.TRACKER_COLLECTION_NAME)
|
||||
.append(" = new org.hibernate.bytecode.enhance.spi.CollectionTracker();");
|
||||
|
||||
for(CtField ctField : collectCollectionFields(managedCtClass)) {
|
||||
if(!enhancementContext.isMappedCollection(ctField)) {
|
||||
builder.append("if(")
|
||||
.append(ctField.getName())
|
||||
.append(" != null) ")
|
||||
.append(EnhancerConstants.TRACKER_COLLECTION_NAME)
|
||||
.append(".add(\"")
|
||||
.append(ctField.getName())
|
||||
.append("\", ")
|
||||
.append(ctField.getName())
|
||||
.append(".size());");
|
||||
}
|
||||
}
|
||||
|
||||
builder.append("}");
|
||||
|
||||
managedCtClass.addMethod(CtNewMethod.make(builder.toString(), managedCtClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* create _areCollectionFieldsDirty
|
||||
*/
|
||||
private void createCollectionDirtyCheckMethod(CtClass managedCtClass) throws CannotCompileException {
|
||||
StringBuilder builder = new StringBuilder("private boolean ")
|
||||
.append(EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME)
|
||||
.append("() { if ($$_hibernate_getInterceptor() == null || ")
|
||||
.append(EnhancerConstants.TRACKER_COLLECTION_NAME)
|
||||
.append(" == null) return false; ");
|
||||
|
||||
for(CtField ctField : collectCollectionFields(managedCtClass)) {
|
||||
if(!enhancementContext.isMappedCollection(ctField)) {
|
||||
builder.append("if(")
|
||||
.append(EnhancerConstants.TRACKER_COLLECTION_NAME)
|
||||
.append(".getSize(\"")
|
||||
.append(ctField.getName())
|
||||
.append("\") != ")
|
||||
.append(ctField.getName())
|
||||
.append(".size()) return true;");
|
||||
}
|
||||
}
|
||||
|
||||
builder.append("return false; }");
|
||||
|
||||
managedCtClass.addMethod(CtNewMethod.make(builder.toString(), managedCtClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* create _getCollectionFieldDirtyNames
|
||||
*/
|
||||
private void createCollectionDirtyCheckGetFieldsMethod(CtClass managedCtClass) throws CannotCompileException {
|
||||
StringBuilder collectionFieldDirtyFieldMethod = new StringBuilder("private void ")
|
||||
.append(EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME)
|
||||
.append("(java.util.Set trackerSet) { if(")
|
||||
.append(EnhancerConstants.TRACKER_COLLECTION_NAME)
|
||||
.append(" == null) return; else {");
|
||||
|
||||
for(CtField ctField : collectCollectionFields(managedCtClass)) {
|
||||
if(!ctField.getName().startsWith("$$_hibernate") &&
|
||||
!enhancementContext.isMappedCollection(ctField)) {
|
||||
collectionFieldDirtyFieldMethod
|
||||
.append("if(")
|
||||
.append(EnhancerConstants.TRACKER_COLLECTION_NAME)
|
||||
.append(".getSize(\"")
|
||||
.append(ctField.getName())
|
||||
.append("\") != ")
|
||||
.append(ctField.getName())
|
||||
.append(".size()) trackerSet.add(\"")
|
||||
.append(ctField.getName())
|
||||
.append("\");");
|
||||
}
|
||||
}
|
||||
|
||||
collectionFieldDirtyFieldMethod.append("}}");
|
||||
|
||||
managedCtClass.addMethod(CtNewMethod.make(collectionFieldDirtyFieldMethod.toString(), managedCtClass));
|
||||
}
|
||||
|
||||
private void addFieldWithGetterAndSetter(
|
||||
CtClass targetClass,
|
||||
|
@ -557,10 +872,39 @@ public class Enhancer {
|
|||
);
|
||||
}
|
||||
|
||||
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) {
|
||||
writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( fieldName ) );
|
||||
if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) && !isComposite ) {
|
||||
writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( persistentField ));
|
||||
}
|
||||
|
||||
if( isComposite) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(" if( ")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME)
|
||||
.append(" != null) ")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME)
|
||||
.append(".callOwner(\".")
|
||||
.append(persistentField.getName())
|
||||
.append("\");");
|
||||
|
||||
writer.insertBefore( builder.toString() );
|
||||
}
|
||||
|
||||
//composite types
|
||||
if(persistentField.getAnnotation(Embedded.class) != null) {
|
||||
//make sure to add the CompositeOwner interface
|
||||
if(!doClassInheritCompositeOwner(managedCtClass)) {
|
||||
managedCtClass.addInterface(classPool.get("org.hibernate.engine.spi.CompositeOwner"));
|
||||
}
|
||||
//if a composite have a embedded field we need to implement the method as well
|
||||
if(isComposite)
|
||||
createTrackChangeCompositeMethod(managedCtClass);
|
||||
|
||||
|
||||
writer.insertBefore( cleanupPreviousOwner(persistentField));
|
||||
|
||||
writer.insertAfter( compositeMethodBody(persistentField));
|
||||
}
|
||||
|
||||
managedCtClass.addMethod( writer );
|
||||
return writer;
|
||||
}
|
||||
|
@ -576,6 +920,47 @@ public class Enhancer {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean doClassInheritCompositeOwner(CtClass managedCtClass) {
|
||||
try {
|
||||
for(CtClass ctClass : managedCtClass.getInterfaces())
|
||||
if(ctClass.getName().equals("org.hibernate.engine.spi.CompositeOwner"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String cleanupPreviousOwner(CtField currentValue) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("if (")
|
||||
.append(currentValue.getName())
|
||||
.append(" != null) ")
|
||||
.append("((org.hibernate.engine.spi.CompositeTracker)")
|
||||
.append(currentValue.getName())
|
||||
.append(").")
|
||||
.append(EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER)
|
||||
.append("(\"")
|
||||
.append(currentValue.getName())
|
||||
.append("\");");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String compositeMethodBody(CtField currentValue) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("((org.hibernate.engine.spi.CompositeTracker) ")
|
||||
.append(currentValue.getName())
|
||||
.append(").$$_hibernate_setOwner(\"")
|
||||
.append(currentValue.getName())
|
||||
.append("\",(org.hibernate.engine.spi.CompositeOwner) this);")
|
||||
.append(EnhancerConstants.TRACKER_CHANGER_NAME + "(\"" + currentValue.getName() + "\");");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void transformFieldAccessesIntoReadsAndWrites(
|
||||
CtClass managedCtClass,
|
||||
IdentityHashMap<String, PersistentAttributeDescriptor> attributeDescriptorMap) {
|
||||
|
@ -699,7 +1084,7 @@ public class Enhancer {
|
|||
private static interface AttributeTypeDescriptor {
|
||||
public String buildReadInterceptionBodyFragment(String fieldName);
|
||||
public String buildWriteInterceptionBodyFragment(String fieldName);
|
||||
public String buildInLineDirtyCheckingBodyFragment(String fieldName);
|
||||
public String buildInLineDirtyCheckingBodyFragment(CtField currentField);
|
||||
}
|
||||
|
||||
private AttributeTypeDescriptor resolveAttributeTypeDescriptor(CtField persistentField) throws NotFoundException {
|
||||
|
@ -735,14 +1120,74 @@ public class Enhancer {
|
|||
|
||||
private abstract static class AbstractAttributeTypeDescriptor implements AttributeTypeDescriptor {
|
||||
@Override
|
||||
public String buildInLineDirtyCheckingBodyFragment(String fieldName) {
|
||||
// for now...
|
||||
// todo : hook-in in-lined dirty checking
|
||||
return String.format(
|
||||
"System.out.println( \"DIRTY CHECK (%1$s) : \" + this.%1$s + \" -> \" + $1 + \" (dirty=\" + (this.%1$s != $1) +\")\" );",
|
||||
fieldName
|
||||
);
|
||||
public String buildInLineDirtyCheckingBodyFragment(CtField currentValue) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try {
|
||||
//should ignore primary keys
|
||||
for(Object o : currentValue.getType().getAnnotations()) {
|
||||
if(o instanceof Id)
|
||||
return "";
|
||||
}
|
||||
|
||||
builder.append(entityMethodBody(currentValue));
|
||||
|
||||
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String entityMethodBody(CtField currentValue) {
|
||||
StringBuilder inlineBuilder = new StringBuilder();
|
||||
try {
|
||||
inlineBuilder.append("if ( $$_hibernate_getInterceptor() != null ");
|
||||
//primitives || enums
|
||||
if(currentValue.getType().isPrimitive() || currentValue.getType().isEnum()) {
|
||||
inlineBuilder.append("&& "+currentValue.getName()+" != $1)");
|
||||
}
|
||||
//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")
|
||||
) {
|
||||
inlineBuilder.append("&& (("+currentValue.getName()+" == null) || (!" +currentValue.getName()+".equals( $1))))");
|
||||
}
|
||||
//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("java.util.Collection")) {
|
||||
|
||||
//if the collection is not managed we should write it to the tracker
|
||||
//todo: should use EnhancementContext.isMappedCollection here instead
|
||||
if (currentValue.getAnnotation(OneToMany.class) != null ||
|
||||
currentValue.getAnnotation(ManyToMany.class) != null ||
|
||||
currentValue.getAnnotation(ElementCollection.class) != null) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//todo: for now just call equals, should probably do something else here
|
||||
inlineBuilder.append("&& (("+currentValue.getName()+" == null) || (!" +currentValue.getName()+".equals( $1))))");
|
||||
}
|
||||
|
||||
inlineBuilder.append( EnhancerConstants.TRACKER_CHANGER_NAME+"(\""+currentValue.getName()+"\");");
|
||||
} catch (NotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return inlineBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ObjectAttributeTypeDescriptor extends AbstractAttributeTypeDescriptor {
|
||||
|
|
|
@ -128,6 +128,54 @@ public class EnhancerConstants {
|
|||
*/
|
||||
public static final String INTERCEPTOR_SETTER_NAME = "$$_hibernate_setInterceptor";
|
||||
|
||||
/**
|
||||
* Name of tracker field
|
||||
*/
|
||||
public static final String TRACKER_FIELD_NAME = "$$_hibernate_tracker";
|
||||
|
||||
/**
|
||||
* Name of method that add changed fields
|
||||
*/
|
||||
public static final String TRACKER_CHANGER_NAME = "$$_hibernate_trackChange";
|
||||
|
||||
/**
|
||||
* Name of method to see if any fields has changed
|
||||
*/
|
||||
public static final String TRACKER_HAS_CHANGED_NAME = "$$_hibernate_hasDirtyAttributes";
|
||||
|
||||
/**
|
||||
* Name of method to fetch dirty attributes
|
||||
*/
|
||||
public static final String TRACKER_GET_NAME = "$$_hibernate_getDirtyAttributes";
|
||||
|
||||
/**
|
||||
* Name of method to clear stored dirty attributes
|
||||
*/
|
||||
public static final String TRACKER_CLEAR_NAME = "$$_hibernate_clearDirtyAttributes";
|
||||
|
||||
/**
|
||||
* Name of method to check if collection fields are dirty
|
||||
*/
|
||||
public static final String TRACKER_COLLECTION_CHANGED_NAME = "$$_hibernate_areCollectionFieldsDirty";
|
||||
|
||||
public static final String TRACKER_COLLECTION_NAME = "$$_hibernate_collectionTracker";
|
||||
/**
|
||||
* Name of method to get dirty collection field names
|
||||
*/
|
||||
public static final String TRACKER_COLLECTION_CHANGED_FIELD_NAME = "$$_hibernate_getCollectionFieldDirtyNames";
|
||||
|
||||
public static final String TRACKER_COLLECTION_CLEAR_NAME = "$$_hibernate_clearDirtyCollectionNames";
|
||||
|
||||
public static final String TRACKER_COMPOSITE_DIRTY_CHECK = "$$_hibernate_areCompositeFieldsDirty";
|
||||
|
||||
public static final String TRACKER_COMPOSITE_DIRTY_FIELDS_GETTER = "$$_hibernate_getCompositeDirtyFields";
|
||||
|
||||
public static final String TRACKER_COMPOSITE_FIELD_NAME = "$$_hibernate_compositeOwners";
|
||||
|
||||
public static final String TRACKER_COMPOSITE_SET_OWNER = "$$_hibernate_setOwner";
|
||||
|
||||
public static final String TRACKER_COMPOSITE_CLEAR_OWNER = "$$_hibernate_clearOwner";
|
||||
|
||||
private EnhancerConstants() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.engine.spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
public interface CompositeOwner {
|
||||
|
||||
/**
|
||||
* @param attributeName to be added to the dirty list
|
||||
*/
|
||||
void $$_hibernate_trackChange(String attributeName);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.engine.spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
public interface CompositeTracker {
|
||||
|
||||
void $$_hibernate_setOwner(String name, CompositeOwner tracker);
|
||||
|
||||
void $$_hibernate_clearOwner(String name);
|
||||
}
|
|
@ -236,6 +236,9 @@ public final class EntityEntry implements Serializable {
|
|||
interceptor.clearDirty();
|
||||
}
|
||||
}
|
||||
if( entity instanceof SelfDirtinessTracker)
|
||||
((SelfDirtinessTracker) entity).$$_hibernate_clearDirtyAttributes();
|
||||
|
||||
persistenceContext.getSession()
|
||||
.getFactory()
|
||||
.getCustomEntityDirtinessStrategy()
|
||||
|
@ -299,6 +302,10 @@ public final class EntityEntry implements Serializable {
|
|||
|
||||
@SuppressWarnings( {"SimplifiableIfStatement"})
|
||||
private boolean isUnequivocallyNonDirty(Object entity) {
|
||||
|
||||
if(entity instanceof SelfDirtinessTracker)
|
||||
return ((SelfDirtinessTracker) entity).$$_hibernate_hasDirtyAttributes();
|
||||
|
||||
final CustomEntityDirtinessStrategy customEntityDirtinessStrategy =
|
||||
persistenceContext.getSession().getFactory().getCustomEntityDirtinessStrategy();
|
||||
if ( customEntityDirtinessStrategy.canDirtyCheck( entity, getPersister(), (Session) persistenceContext.getSession() ) ) {
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.engine.spi;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Specify if an entity class is instrumented to track field changes
|
||||
*
|
||||
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
public interface SelfDirtinessTracker {
|
||||
|
||||
/**
|
||||
* Return true if any fields has been changed
|
||||
*/
|
||||
boolean $$_hibernate_hasDirtyAttributes();
|
||||
|
||||
/**
|
||||
* Get the field names of all the fields thats been changed
|
||||
*/
|
||||
Set<String> $$_hibernate_getDirtyAttributes();
|
||||
|
||||
/**
|
||||
* Clear the stored dirty attributes
|
||||
*/
|
||||
void $$_hibernate_clearDirtyAttributes();
|
||||
}
|
|
@ -26,6 +26,7 @@ package org.hibernate.event.internal;
|
|||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
|
@ -491,25 +492,32 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
|
|||
);
|
||||
|
||||
if ( dirtyProperties == null ) {
|
||||
// see if the custom dirtiness strategy can tell us...
|
||||
class DirtyCheckContextImpl implements CustomEntityDirtinessStrategy.DirtyCheckContext {
|
||||
int[] found = null;
|
||||
@Override
|
||||
public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attributeChecker) {
|
||||
found = new DirtyCheckAttributeInfoImpl( event ).visitAttributes( attributeChecker );
|
||||
if ( found != null && found.length == 0 ) {
|
||||
found = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
DirtyCheckContextImpl context = new DirtyCheckContextImpl();
|
||||
session.getFactory().getCustomEntityDirtinessStrategy().findDirty(
|
||||
entity,
|
||||
persister,
|
||||
(Session) session,
|
||||
context
|
||||
);
|
||||
dirtyProperties = context.found;
|
||||
if(entity instanceof SelfDirtinessTracker) {
|
||||
if(((SelfDirtinessTracker) entity).$$_hibernate_hasDirtyAttributes()) {
|
||||
dirtyProperties = persister.resolveAttributeIndexes(((SelfDirtinessTracker) entity).$$_hibernate_getDirtyAttributes());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// see if the custom dirtiness strategy can tell us...
|
||||
class DirtyCheckContextImpl implements CustomEntityDirtinessStrategy.DirtyCheckContext {
|
||||
int[] found = null;
|
||||
@Override
|
||||
public void doDirtyChecking(CustomEntityDirtinessStrategy.AttributeChecker attributeChecker) {
|
||||
found = new DirtyCheckAttributeInfoImpl( event ).visitAttributes( attributeChecker );
|
||||
if ( found != null && found.length == 0 ) {
|
||||
found = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
DirtyCheckContextImpl context = new DirtyCheckContextImpl();
|
||||
session.getFactory().getCustomEntityDirtinessStrategy().findDirty(
|
||||
entity,
|
||||
persister,
|
||||
(Session) session,
|
||||
context
|
||||
);
|
||||
dirtyProperties = context.found;
|
||||
}
|
||||
}
|
||||
|
||||
event.setDatabaseSnapshot(null);
|
||||
|
|
|
@ -2148,6 +2148,20 @@ public abstract class AbstractEntityPersister
|
|||
return subclassPropertyNameClosure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] resolveAttributeIndexes(Set<String> properties) {
|
||||
Iterator<String> iter = properties.iterator();
|
||||
int[] fields = new int[properties.size()];
|
||||
int counter = 0;
|
||||
while(iter.hasNext()) {
|
||||
Integer index = entityMetamodel.getPropertyIndexOrNull(iter.next());
|
||||
if(index != null)
|
||||
fields[counter++] = index;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
protected String[] getSubclassPropertySubclassNameClosure() {
|
||||
return subclassPropertySubclassNameClosure;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ package org.hibernate.persister.entity;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.EntityMode;
|
||||
import org.hibernate.HibernateException;
|
||||
|
@ -765,4 +766,7 @@ public interface EntityPersister extends OptimisticCacheSource, EntityDefinition
|
|||
public EntityInstrumentationMetadata getInstrumentationMetadata();
|
||||
|
||||
public FilterAliasGenerator getFilterAliasGenerator(final String rootAlias);
|
||||
|
||||
public int[] resolveAttributeIndexes(Set<String> properties);
|
||||
|
||||
}
|
||||
|
|
|
@ -23,7 +23,11 @@
|
|||
*/
|
||||
package org.hibernate.tool.enhance;
|
||||
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Transient;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -84,39 +88,69 @@ public class EnhancementTask extends Task implements EnhancementContext {
|
|||
continue;
|
||||
}
|
||||
|
||||
processClassFile( javaClassFile );
|
||||
processClassFile( javaClassFile);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void processClassFile(File javaClassFile) {
|
||||
/**
|
||||
* Atm only process files annotated with either @Entity or @Embeddable
|
||||
* @param javaClassFile
|
||||
*/
|
||||
private void processClassFile(File javaClassFile) {
|
||||
try {
|
||||
final CtClass ctClass = classPool.makeClass( new FileInputStream( javaClassFile ) );
|
||||
if ( ! shouldInclude( ctClass ) ) {
|
||||
return;
|
||||
}
|
||||
if(this.isEntityClass(ctClass))
|
||||
processEntityClassFile(javaClassFile, ctClass);
|
||||
else if(this.isCompositeClass(ctClass))
|
||||
processCompositeClassFile(javaClassFile, ctClass);
|
||||
|
||||
final byte[] enhancedBytecode;
|
||||
try {
|
||||
enhancedBytecode = enhancer.enhance( ctClass.getName(), ctClass.toBytecode() );
|
||||
}
|
||||
catch (Exception e) {
|
||||
log( "Unable to enhance class [" + ctClass.getName() + "]", e, Project.MSG_WARN );
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new BuildException(
|
||||
String.format( "Error processing included file [%s]", javaClassFile.getAbsolutePath() ), e );
|
||||
}
|
||||
}
|
||||
|
||||
private void processEntityClassFile(File javaClassFile, CtClass ctClass ) {
|
||||
try {
|
||||
byte[] result = enhancer.enhance( ctClass.getName(), ctClass.toBytecode() );
|
||||
if(result != null)
|
||||
writeEnhancedClass(javaClassFile, result);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log( "Unable to enhance class [" + ctClass.getName() + "]", e, Project.MSG_WARN );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void processCompositeClassFile(File javaClassFile, CtClass ctClass) {
|
||||
try {
|
||||
byte[] result = enhancer.enhanceComposite(ctClass.getName(), ctClass.toBytecode());
|
||||
if(result != null)
|
||||
writeEnhancedClass(javaClassFile, result);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log( "Unable to enhance class [" + ctClass.getName() + "]", e, Project.MSG_WARN );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeEnhancedClass(File javaClassFile, byte[] result) {
|
||||
try {
|
||||
if ( javaClassFile.delete() ) {
|
||||
if ( ! javaClassFile.createNewFile() ) {
|
||||
log( "Unable to recreate class file [" + ctClass.getName() + "]", Project.MSG_INFO );
|
||||
}
|
||||
}
|
||||
if ( ! javaClassFile.createNewFile() ) {
|
||||
log( "Unable to recreate class file [" + javaClassFile.getName() + "]", Project.MSG_INFO );
|
||||
}
|
||||
}
|
||||
else {
|
||||
log( "Unable to delete class file [" + ctClass.getName() + "]", Project.MSG_INFO );
|
||||
log( "Unable to delete class file [" + javaClassFile.getName() + "]", Project.MSG_INFO );
|
||||
}
|
||||
|
||||
FileOutputStream outputStream = new FileOutputStream( javaClassFile, false );
|
||||
try {
|
||||
outputStream.write( enhancedBytecode );
|
||||
outputStream.write( result);
|
||||
outputStream.flush();
|
||||
}
|
||||
finally {
|
||||
|
@ -126,26 +160,17 @@ public class EnhancementTask extends Task implements EnhancementContext {
|
|||
catch ( IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException ignore) {
|
||||
// should not ever happen because of explicit checks
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new BuildException(
|
||||
String.format( "Error processing included file [%s]", javaClassFile.getAbsolutePath() ),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldInclude(CtClass ctClass) {
|
||||
// we currently only handle entity enhancement
|
||||
return ctClass.hasAnnotation( Entity.class );
|
||||
}
|
||||
|
||||
}
|
||||
catch (FileNotFoundException ignore) {
|
||||
// should not ever happen because of explicit checks
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new BuildException(
|
||||
String.format( "Error processing included file [%s]", javaClassFile.getAbsolutePath() ), e );
|
||||
}
|
||||
}
|
||||
|
||||
// EnhancementContext impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@Override
|
||||
public ClassLoader getLoadingClassLoader() {
|
||||
return getClass().getClassLoader();
|
||||
|
@ -153,18 +178,17 @@ public class EnhancementTask extends Task implements EnhancementContext {
|
|||
|
||||
@Override
|
||||
public boolean isEntityClass(CtClass classDescriptor) {
|
||||
// currently we only call enhance on the classes with @Entity, so here we always return true
|
||||
return true;
|
||||
}
|
||||
return classDescriptor.hasAnnotation(Entity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompositeClass(CtClass classDescriptor) {
|
||||
return false;
|
||||
return classDescriptor.hasAnnotation(Embeddable.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -183,6 +207,18 @@ public class EnhancementTask extends Task implements EnhancementContext {
|
|||
return ! ctField.hasAnnotation( Transient.class );
|
||||
}
|
||||
|
||||
@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 CtField[] order(CtField[] persistentFields) {
|
||||
// for now...
|
||||
|
|
|
@ -510,6 +510,11 @@ public class PojoEntityTuplizer extends AbstractEntityTuplizer {
|
|||
//TODO: if we support multiple fetch groups, we would need
|
||||
// to clone the set of lazy properties!
|
||||
FieldInterceptionHelper.injectFieldInterceptor( entity, getEntityName(), lazyProps, session );
|
||||
|
||||
//also clear the fields that are marked as dirty in the dirtyness tracker
|
||||
if(entity instanceof org.hibernate.engine.spi.SelfDirtinessTracker) {
|
||||
((org.hibernate.engine.spi.SelfDirtinessTracker) entity).$$_hibernate_clearDirtyAttributes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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;
|
||||
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Embedded;
|
||||
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;
|
||||
private String city;
|
||||
private String state;
|
||||
|
||||
@Embedded
|
||||
private Country country;
|
||||
private String zip;
|
||||
private String phone;
|
||||
|
||||
public Address() {
|
||||
}
|
||||
|
||||
public String getStreet1() {
|
||||
return street1;
|
||||
}
|
||||
|
||||
public void setStreet1(String street1) {
|
||||
this.street1 = street1;
|
||||
}
|
||||
|
||||
public String getStreet2() {
|
||||
return street2;
|
||||
}
|
||||
|
||||
public void setStreet2(String street2) {
|
||||
this.street2 = street2;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public void setCity(String city) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public Country getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
public void setCountry(Country country) {
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public String getZip() {
|
||||
return zip;
|
||||
}
|
||||
|
||||
public void setZip(String zip) {
|
||||
this.zip = zip;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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.CompositeOwnerTracker;
|
||||
import org.hibernate.engine.spi.CompositeOwner;
|
||||
import org.junit.Test;
|
||||
|
||||
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;
|
||||
|
||||
@Test
|
||||
public void testCompositeOwnerTracker() {
|
||||
|
||||
CompositeOwnerTracker tracker = new CompositeOwnerTracker();
|
||||
tracker.add("foo", new TestCompositeOwner());
|
||||
|
||||
tracker.callOwner(".street1");
|
||||
assertEquals(1, counter);
|
||||
tracker.add("bar", new TestCompositeOwner());
|
||||
tracker.callOwner(".city");
|
||||
assertEquals(3, counter);
|
||||
|
||||
tracker.removeOwner("foo");
|
||||
|
||||
tracker.callOwner(".country");
|
||||
assertEquals(4, counter);
|
||||
tracker.removeOwner("bar");
|
||||
|
||||
tracker.callOwner(".country");
|
||||
|
||||
tracker.add("moo", new TestCompositeOwner());
|
||||
tracker.callOwner(".country");
|
||||
assertEquals(5, counter);
|
||||
}
|
||||
|
||||
class TestCompositeOwner implements CompositeOwner {
|
||||
|
||||
@Override
|
||||
public void $$_hibernate_trackChange(String attributeName) {
|
||||
if(counter == 0)
|
||||
assertEquals("foo.street1", attributeName);
|
||||
if(counter == 1)
|
||||
assertEquals("foo.city", attributeName);
|
||||
if(counter == 2)
|
||||
assertEquals("bar.city", attributeName);
|
||||
if(counter == 3)
|
||||
assertEquals("bar.country", attributeName);
|
||||
if(counter == 4)
|
||||
assertEquals("moo.country", attributeName);
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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;
|
||||
|
||||
import javax.persistence.Embeddable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
@Embeddable
|
||||
public class Country {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
|
@ -25,7 +25,12 @@ 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;
|
||||
|
@ -46,11 +51,17 @@ import org.junit.Test;
|
|||
|
||||
import org.hibernate.testing.junit4.BaseUnitTestCase;
|
||||
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.OneToMany;
|
||||
|
||||
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
|
||||
|
@ -87,7 +98,19 @@ public class EnhancerTest extends BaseUnitTestCase {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@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;
|
||||
}
|
||||
|
@ -108,13 +131,32 @@ public class EnhancerTest extends BaseUnitTestCase {
|
|||
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 );
|
||||
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 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() );
|
||||
|
||||
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() );
|
||||
|
||||
}
|
||||
Class entityClass = enhancedCtClass.toClass( cl, this.getClass().getProtectionDomain() );
|
||||
Object entityInstance = entityClass.newInstance();
|
||||
|
||||
|
@ -151,20 +193,96 @@ public class EnhancerTest extends BaseUnitTestCase {
|
|||
entityClass.getMethod( "getId" ).invoke( entityInstance );
|
||||
|
||||
interceptorSetter.invoke( entityInstance, new LocalPersistentAttributeInterceptor() );
|
||||
assertNotNull( interceptorGetter.invoke( entityInstance ) );
|
||||
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( "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 );
|
||||
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")) {
|
||||
|
||||
List<String> strings = new ArrayList<String>();
|
||||
strings.add("FooBar");
|
||||
|
||||
entityClass.getMethod( "setSomeStrings", java.util.List.class ).invoke(entityInstance, strings);
|
||||
|
||||
assertTrue((Boolean) entityClass.getMethod("$$_hibernate_hasDirtyAttributes").invoke(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() );
|
||||
|
||||
entityClass.getMethod("setAddress", addressClass).invoke(entityInstance, address);
|
||||
|
||||
addressClass.getMethod("setCity", String.class).invoke(address, "Arendal");
|
||||
|
||||
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());
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
private CtClass generateCtClassForAnEntity(Class entityClassToEnhance) throws Exception {
|
||||
|
|
|
@ -23,8 +23,12 @@
|
|||
*/
|
||||
package org.hibernate.test.bytecode.enhancement;
|
||||
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -35,6 +39,13 @@ public class SimpleEntity {
|
|||
private String name;
|
||||
private boolean active;
|
||||
private long someNumber;
|
||||
private List<String> someStrings;
|
||||
|
||||
@OneToMany
|
||||
private Set<Integer> someInts;
|
||||
|
||||
@Embedded
|
||||
private Address address;
|
||||
|
||||
@Id
|
||||
public Long getId() {
|
||||
|
@ -68,4 +79,28 @@ public class SimpleEntity {
|
|||
public void setSomeNumber(long someNumber) {
|
||||
this.someNumber = someNumber;
|
||||
}
|
||||
|
||||
public List<String> getSomeStrings() {
|
||||
return someStrings;
|
||||
}
|
||||
|
||||
public void setSomeStrings(List<String> someStrings) {
|
||||
this.someStrings = someStrings;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Set<Integer> getSomeInts() {
|
||||
return someInts;
|
||||
}
|
||||
|
||||
public void setSomeInts(Set<Integer> someInts) {
|
||||
this.someInts = someInts;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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 java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
public class Address implements Serializable {
|
||||
private String street1;
|
||||
private String street2;
|
||||
private String city;
|
||||
private String state;
|
||||
private String country;
|
||||
private String zip;
|
||||
private String phone;
|
||||
|
||||
public Address() {
|
||||
}
|
||||
public Address(String street1, String street2, String city, String state,
|
||||
String country, String zip, String phone) {
|
||||
this.street1 = street1;
|
||||
this.street2 = street2;
|
||||
this.city = city;
|
||||
this.state = state;
|
||||
this.country = country;
|
||||
setZip(zip);
|
||||
setPhone(phone);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return street1 + "\n" + street2 + "\n" + city + "," + state + " " + zip + "\n" + phone;
|
||||
}
|
||||
|
||||
public String getStreet1() {
|
||||
return street1;
|
||||
}
|
||||
|
||||
public void setStreet1(String street1) {
|
||||
this.street1 = street1;
|
||||
}
|
||||
|
||||
public String getStreet2() {
|
||||
return street2;
|
||||
}
|
||||
|
||||
public void setStreet2(String street2) {
|
||||
this.street2 = street2;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public void setCity(String city) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
public void setCountry(String country) {
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public String getZip() {
|
||||
return zip;
|
||||
}
|
||||
|
||||
public void setZip(String zip) {
|
||||
assertNumeric(zip, "Non-numeric zip ");
|
||||
this.zip = zip;
|
||||
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
assertNumeric(zip, "Non-numeric phone ");
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
void assertNumeric(String s, String error) {
|
||||
for (int i=0; i<s.length(); i++) {
|
||||
if (!Character.isDigit(s.charAt(i))) {
|
||||
throw new IllegalArgumentException(error + s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* 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 javax.persistence.AttributeOverride;
|
||||
import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
import javax.persistence.Version;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
@Entity
|
||||
@Table(name="O_CUSTOMER")
|
||||
public class Customer {
|
||||
public static final String QUERY_ALL = "Customer.selectAll";
|
||||
public static final String QUERY_COUNT = "Customer.count";
|
||||
public static final String QUERY_BY_CREDIT = "Customer.selectByCreditLimit";
|
||||
|
||||
public static final String BAD_CREDIT = "BC";
|
||||
|
||||
@Id
|
||||
@Column(name="C_ID")
|
||||
private int id;
|
||||
|
||||
@Column(name="C_FIRST")
|
||||
private String firstName;
|
||||
|
||||
@Column(name="C_LAST")
|
||||
private String lastName;
|
||||
|
||||
@Column(name="C_CONTACT")
|
||||
private String contact;
|
||||
|
||||
@Column(name="C_CREDIT")
|
||||
private String credit;
|
||||
|
||||
@Column(name="C_CREDIT_LIMIT")
|
||||
private BigDecimal creditLimit;
|
||||
|
||||
@Column(name="C_SINCE")
|
||||
@Temporal(TemporalType.DATE)
|
||||
private Calendar since;
|
||||
|
||||
@Column(name="C_BALANCE")
|
||||
private BigDecimal balance;
|
||||
|
||||
@Column(name="C_YTD_PAYMENT")
|
||||
private BigDecimal ytdPayment;
|
||||
|
||||
@OneToMany(mappedBy="customer", cascade= CascadeType.ALL, fetch= FetchType.EAGER)
|
||||
private List<CustomerInventory> customerInventories;
|
||||
|
||||
@Embedded
|
||||
@AttributeOverrides(
|
||||
{@AttributeOverride(name="street1",column=@Column(name="C_STREET1")),
|
||||
@AttributeOverride(name="street2",column=@Column(name="C_STREET2")),
|
||||
@AttributeOverride(name="city", column=@Column(name="C_CITY")),
|
||||
@AttributeOverride(name="state", column=@Column(name="C_STATE")),
|
||||
@AttributeOverride(name="country",column=@Column(name="C_COUNTRY")),
|
||||
@AttributeOverride(name="zip", column=@Column(name="C_ZIP")),
|
||||
@AttributeOverride(name="phone", column=@Column(name="C_PHONE"))})
|
||||
private Address address;
|
||||
|
||||
@Version
|
||||
@Column(name = "C_VERSION")
|
||||
private int version;
|
||||
|
||||
public Customer() {
|
||||
}
|
||||
|
||||
public Customer(String first, String last, Address address,
|
||||
String contact, String credit, BigDecimal creditLimit,
|
||||
BigDecimal balance, BigDecimal YtdPayment) {
|
||||
|
||||
this.firstName = first;
|
||||
this.lastName = last;
|
||||
this.address = address;
|
||||
this.contact = contact;
|
||||
this.since = Calendar.getInstance();
|
||||
this.credit = credit;
|
||||
this.creditLimit = creditLimit;
|
||||
this.balance = balance;
|
||||
this.ytdPayment = YtdPayment;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer customerId) {
|
||||
this.id = customerId;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getContact() {
|
||||
return contact;
|
||||
}
|
||||
|
||||
public void setContact(String contact) {
|
||||
this.contact = contact;
|
||||
}
|
||||
|
||||
public String getCredit() {
|
||||
return credit;
|
||||
}
|
||||
|
||||
public void setCredit(String credit) {
|
||||
this.credit = credit;
|
||||
}
|
||||
|
||||
public BigDecimal getCreditLimit() {
|
||||
return creditLimit;
|
||||
}
|
||||
|
||||
public void setCreditLimit(BigDecimal creditLimit) {
|
||||
this.creditLimit = creditLimit;
|
||||
}
|
||||
|
||||
public Calendar getSince() {
|
||||
return since;
|
||||
}
|
||||
|
||||
public void setSince(Calendar since) {
|
||||
this.since = since;
|
||||
}
|
||||
|
||||
public BigDecimal getBalance() {
|
||||
return balance;
|
||||
}
|
||||
|
||||
public void setBalance(BigDecimal balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
public void changeBalance(BigDecimal change) {
|
||||
setBalance(balance.add(change).setScale(2, BigDecimal.ROUND_DOWN));
|
||||
}
|
||||
|
||||
public BigDecimal getYtdPayment() {
|
||||
return ytdPayment;
|
||||
}
|
||||
|
||||
public void setYtdPayment(BigDecimal ytdPayment) {
|
||||
this.ytdPayment = ytdPayment;
|
||||
}
|
||||
|
||||
public List<CustomerInventory> getInventories() {
|
||||
if (customerInventories == null){
|
||||
customerInventories = new ArrayList<CustomerInventory>();
|
||||
}
|
||||
return customerInventories;
|
||||
}
|
||||
|
||||
public CustomerInventory addInventory(String item, int quantity,
|
||||
BigDecimal totalValue) {
|
||||
|
||||
CustomerInventory inventory = new CustomerInventory(this, item,
|
||||
quantity, totalValue);
|
||||
getInventories().add(inventory);
|
||||
return inventory;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public boolean hasSufficientCredit(BigDecimal amount) {
|
||||
return !BAD_CREDIT.equals(getCredit())
|
||||
&& creditLimit != null
|
||||
&& creditLimit.compareTo(amount) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
return id == ((Customer) o).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new Integer(id).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getFirstName() + " " + this.getLastName();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,384 @@
|
|||
/*
|
||||
* 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,
|
||||
EntityMode.POJO,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.TableGenerator;
|
||||
import javax.persistence.Version;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Comparator;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
@Entity
|
||||
@Table(name="O_CUSTINVENTORY")
|
||||
@IdClass(CustomerInventoryPK.class)
|
||||
public class CustomerInventory implements Serializable, Comparator<CustomerInventory> {
|
||||
|
||||
public static final String QUERY_COUNT = "CustomerInventory.count";
|
||||
|
||||
@Id
|
||||
@TableGenerator(name="inventory",
|
||||
table="U_SEQUENCES",
|
||||
pkColumnName="S_ID",
|
||||
valueColumnName="S_NEXTNUM",
|
||||
pkColumnValue="inventory",
|
||||
allocationSize=1000)
|
||||
@GeneratedValue(strategy= GenerationType.TABLE,generator="inventory")
|
||||
@Column(name="CI_ID")
|
||||
private Long id;
|
||||
|
||||
@Id
|
||||
@Column(name = "CI_CUSTOMERID", insertable = false, updatable = false)
|
||||
private int custId;
|
||||
|
||||
@ManyToOne(cascade= CascadeType.MERGE)
|
||||
@JoinColumn(name="CI_CUSTOMERID")
|
||||
private Customer customer;
|
||||
|
||||
@ManyToOne(cascade=CascadeType.MERGE)
|
||||
@JoinColumn(name = "CI_ITEMID")
|
||||
private String vehicle;
|
||||
|
||||
@Column(name="CI_VALUE")
|
||||
private BigDecimal totalCost;
|
||||
|
||||
@Column(name="CI_QUANTITY")
|
||||
private int quantity;
|
||||
|
||||
@Version
|
||||
@Column(name = "CI_VERSION")
|
||||
private int version;
|
||||
|
||||
public CustomerInventory() {
|
||||
}
|
||||
|
||||
CustomerInventory(Customer customer, String vehicle, int quantity,
|
||||
BigDecimal totalValue) {
|
||||
this.customer = customer;
|
||||
this.vehicle = vehicle;
|
||||
this.quantity = quantity;
|
||||
this.totalCost = totalValue;
|
||||
}
|
||||
|
||||
public String getVehicle() {
|
||||
return vehicle;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalCost() {
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
public int getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Customer getCustomer() {
|
||||
return customer;
|
||||
}
|
||||
|
||||
public int getCustId() {
|
||||
return custId;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public int compare(CustomerInventory cdb1, CustomerInventory cdb2) {
|
||||
return cdb1.id.compareTo(cdb2.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this)
|
||||
return true;
|
||||
if (obj == null || !(obj instanceof CustomerInventory))
|
||||
return false;
|
||||
if (this.id == ((CustomerInventory)obj).id)
|
||||
return true;
|
||||
if (this.id != null && ((CustomerInventory)obj).id == null)
|
||||
return false;
|
||||
if (this.id == null && ((CustomerInventory)obj).id != null)
|
||||
return false;
|
||||
|
||||
return this.id.equals(((CustomerInventory)obj).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = id.hashCode();
|
||||
result = 31 * result + custId;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
public class CustomerInventoryPK implements Serializable {
|
||||
|
||||
private Long id;
|
||||
private int custId;
|
||||
|
||||
public CustomerInventoryPK() {
|
||||
}
|
||||
|
||||
public CustomerInventoryPK(Long id, int custId) {
|
||||
this.id = id;
|
||||
this.custId = custId;
|
||||
}
|
||||
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) {
|
||||
return true;
|
||||
}
|
||||
if (other == null || getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CustomerInventoryPK cip = (CustomerInventoryPK) other;
|
||||
return (custId == cip.custId && (id == cip.id ||
|
||||
( id != null && id.equals(cip.id))));
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return (id == null ? 0 : id.hashCode()) ^ custId;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public int getCustId() {
|
||||
return custId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 javax.persistence.Embeddable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
|
||||
*/
|
||||
@Embeddable
|
||||
public class SupplierComponentPK {
|
||||
|
||||
String componentID;
|
||||
int supplierID;
|
||||
|
||||
public SupplierComponentPK() {
|
||||
}
|
||||
|
||||
public SupplierComponentPK(String suppCompID, int suppCompSuppID) {
|
||||
this.componentID = suppCompID;
|
||||
this.supplierID = suppCompSuppID;
|
||||
}
|
||||
|
||||
public String getComponentID() {
|
||||
return componentID;
|
||||
}
|
||||
|
||||
public int getSupplierID() {
|
||||
return supplierID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int PRIME = 31;
|
||||
int result = 1;
|
||||
result = PRIME * result + componentID.hashCode();
|
||||
result = PRIME * result + supplierID;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
final SupplierComponentPK other = (SupplierComponentPK) obj;
|
||||
return componentID.equals(other.componentID);
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import java.sql.ResultSet;
|
|||
import java.sql.SQLException;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.EntityMode;
|
||||
import org.hibernate.HibernateException;
|
||||
|
@ -603,6 +604,11 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
|
|||
public Iterable<AttributeDefinition> getAttributes() {
|
||||
throw new NotYetImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] resolveAttributeIndexes(Set<String> attributes) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NoopCollectionPersister implements CollectionPersister {
|
||||
|
|
|
@ -4,6 +4,7 @@ import java.io.Serializable;
|
|||
import java.util.Comparator;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.EntityMode;
|
||||
import org.hibernate.HibernateException;
|
||||
|
@ -693,4 +694,9 @@ public class CustomPersister implements EntityPersister {
|
|||
public Iterable<AttributeDefinition> getAttributes() {
|
||||
throw new NotYetImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] resolveAttributeIndexes(Set<String> attributes) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,10 @@ import org.gradle.api.tasks.TaskAction
|
|||
import org.hibernate.bytecode.enhance.spi.EnhancementContext
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer
|
||||
|
||||
import javax.persistence.ElementCollection
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.ManyToMany
|
||||
import javax.persistence.OneToMany
|
||||
import javax.persistence.Transient
|
||||
|
||||
/**
|
||||
|
@ -142,7 +145,7 @@ public class EnhancerTask extends DefaultTask implements EnhancementContext {
|
|||
}
|
||||
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public CtField[] order(CtField[] fields) {
|
||||
|
@ -150,6 +153,17 @@ public class EnhancerTask extends DefaultTask implements EnhancementContext {
|
|||
return fields;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPersistentField(CtField ctField) {
|
||||
return !ctField.hasAnnotation( Transient.class );
|
||||
}
|
||||
|
|
|
@ -37,7 +37,11 @@ import javassist.ClassPool;
|
|||
import javassist.CtClass;
|
||||
import javassist.CtField;
|
||||
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
|
||||
|
@ -55,13 +59,14 @@ import org.apache.maven.plugins.annotations.Parameter;
|
|||
* @author Jeremy Whiting
|
||||
*/
|
||||
@Mojo(name = "enhance")
|
||||
public class HibernateEnhancementMojo extends AbstractMojo {
|
||||
public class HibernateEnhancementMojo extends AbstractMojo implements EnhancementContext {
|
||||
|
||||
/**
|
||||
* The contexts to use during enhancement.
|
||||
*/
|
||||
private List<File> classes = new ArrayList<File>();
|
||||
private ClassPool pool = new ClassPool( false );
|
||||
private final Enhancer enhancer = new Enhancer( this);
|
||||
|
||||
private static final String CLASS_EXTENSION = ".class";
|
||||
|
||||
|
@ -74,57 +79,9 @@ public class HibernateEnhancementMojo extends AbstractMojo {
|
|||
File root = new File( this.dir );
|
||||
walkDir( root );
|
||||
|
||||
Enhancer enhancer = new Enhancer( new EnhancementContext() {
|
||||
|
||||
private ClassLoader overridden;
|
||||
|
||||
public ClassLoader getLoadingClassLoader() {
|
||||
if ( null == this.overridden ) {
|
||||
return getClass().getClassLoader();
|
||||
}
|
||||
else {
|
||||
return this.overridden;
|
||||
}
|
||||
}
|
||||
|
||||
public void setClassLoader(ClassLoader loader) {
|
||||
this.overridden = loader;
|
||||
}
|
||||
|
||||
public boolean isEntityClass(CtClass classDescriptor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean hasLazyLoadableAttributes(CtClass classDescriptor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isLazyLoadable(CtField field) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isCompositeClass(CtClass classDescriptor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean doDirtyCheckingInline(CtClass classDescriptor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public CtField[] order(CtField[] fields) {
|
||||
// TODO: load ordering from configuration.
|
||||
return fields;
|
||||
}
|
||||
|
||||
public boolean isPersistentField(CtField ctField) {
|
||||
return !ctField.hasAnnotation( Transient.class );
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
if ( 0 < classes.size() ) {
|
||||
for ( File file : classes ) {
|
||||
enhanceClass( enhancer, file );
|
||||
processClassFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,6 +109,7 @@ public class HibernateEnhancementMojo extends AbstractMojo {
|
|||
}
|
||||
|
||||
private void walkDir(File dir, FileFilter classesFilter, FileFilter dirFilter) {
|
||||
|
||||
File[] dirs = dir.listFiles( dirFilter );
|
||||
for ( int i = 0; i < dirs.length; i++ ) {
|
||||
walkDir( dirs[i], classesFilter, dirFilter );
|
||||
|
@ -163,72 +121,146 @@ public class HibernateEnhancementMojo extends AbstractMojo {
|
|||
}
|
||||
}
|
||||
|
||||
private void enhanceClass(Enhancer enhancer, File file) {
|
||||
byte[] enhancedBytecode = null;
|
||||
InputStream is = null;
|
||||
CtClass clas = null;
|
||||
|
||||
/**
|
||||
* Atm only process files annotated with either @Entity or @Embeddable
|
||||
* @param javaClassFile
|
||||
*/
|
||||
private void processClassFile(File javaClassFile)
|
||||
throws MojoExecutionException {
|
||||
try {
|
||||
is = new FileInputStream( file.toString() );
|
||||
clas = getClassPool().makeClass( is );
|
||||
if ( !clas.hasAnnotation( Entity.class ) ) {
|
||||
getLog().debug( "Class $file not an annotated Entity class. skipping..." );
|
||||
}
|
||||
final CtClass ctClass = getClassPool().makeClass( new FileInputStream( javaClassFile ) );
|
||||
if(this.isEntityClass(ctClass))
|
||||
processEntityClassFile(javaClassFile, ctClass);
|
||||
else if(this.isCompositeClass(ctClass))
|
||||
processCompositeClassFile(javaClassFile, ctClass);
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new MojoExecutionException(
|
||||
String.format( "Error processing included file [%s]", javaClassFile.getAbsolutePath() ), e );
|
||||
}
|
||||
}
|
||||
|
||||
private void processEntityClassFile(File javaClassFile, CtClass ctClass ) {
|
||||
try {
|
||||
byte[] result = enhancer.enhance( ctClass.getName(), ctClass.toBytecode() );
|
||||
if(result != null)
|
||||
writeEnhancedClass(javaClassFile, result);
|
||||
}
|
||||
catch (Exception e) {
|
||||
getLog().error( "Unable to enhance class [" + ctClass.getName() + "]", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void processCompositeClassFile(File javaClassFile, CtClass ctClass) {
|
||||
try {
|
||||
byte[] result = enhancer.enhanceComposite(ctClass.getName(), ctClass.toBytecode());
|
||||
if(result != null)
|
||||
writeEnhancedClass(javaClassFile, result);
|
||||
}
|
||||
catch (Exception e) {
|
||||
getLog().error( "Unable to enhance class [" + ctClass.getName() + "]", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeEnhancedClass(File javaClassFile, byte[] result)
|
||||
throws MojoExecutionException {
|
||||
try {
|
||||
if ( javaClassFile.delete() ) {
|
||||
if ( ! javaClassFile.createNewFile() ) {
|
||||
getLog().error( "Unable to recreate class file [" + javaClassFile.getName() + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
enhancedBytecode = enhancer.enhance( clas.getName(), clas.toBytecode() );
|
||||
getLog().error( "Unable to delete class file [" + javaClassFile.getName() + "]");
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
getLog().error( "Unable to enhance class [${file.toString()}]", e );
|
||||
return;
|
||||
}
|
||||
finally {
|
||||
|
||||
FileOutputStream outputStream = new FileOutputStream( javaClassFile, false );
|
||||
try {
|
||||
if ( null != is )
|
||||
is.close();
|
||||
}
|
||||
catch (IOException ioe) {}
|
||||
}
|
||||
if ( null != enhancedBytecode ) {
|
||||
if ( file.delete() ) {
|
||||
try {
|
||||
if ( !file.createNewFile() ) {
|
||||
getLog().error( "Unable to recreate class file [" + clas.getName() + "]" );
|
||||
}
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
getLog().error( "Unable to delete class file [" + clas.getName() + "]" );
|
||||
}
|
||||
FileOutputStream outputStream = null;
|
||||
try {
|
||||
outputStream = new FileOutputStream( file, false );
|
||||
outputStream.write( enhancedBytecode );
|
||||
outputStream.write( result);
|
||||
outputStream.flush();
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
if ( outputStream != null )
|
||||
outputStream.close();
|
||||
clas.detach();// release memory
|
||||
outputStream.close();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
catch ( IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setDir(String dir) {
|
||||
if ( null != dir && !"".equals( dir.trim() ) ) {
|
||||
this.dir = dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException ignore) {
|
||||
// should not ever happen because of explicit checks
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new MojoExecutionException(
|
||||
String.format( "Error processing included file [%s]", javaClassFile.getAbsolutePath() ), e );
|
||||
}
|
||||
}
|
||||
|
||||
private ClassPool getClassPool() {
|
||||
return this.pool;
|
||||
}
|
||||
|
||||
private boolean shouldInclude(CtClass ctClass) {
|
||||
// we currently only handle entity enhancement
|
||||
return ctClass.hasAnnotation( Entity.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getLoadingClassLoader() {
|
||||
return getClass().getClassLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntityClass(CtClass classDescriptor) {
|
||||
return classDescriptor.hasAnnotation(Entity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompositeClass(CtClass classDescriptor) {
|
||||
return classDescriptor.hasAnnotation(Embeddable.class);
|
||||
}
|
||||
|
||||
@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 isPersistentField(CtField ctField) {
|
||||
// current check is to look for @Transient
|
||||
return ! ctField.hasAnnotation( Transient.class );
|
||||
}
|
||||
|
||||
@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 CtField[] order(CtField[] persistentFields) {
|
||||
// for now...
|
||||
return persistentFields;
|
||||
// eventually needs to consult the Hibernate metamodel for proper ordering
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue