HHH-3279 : maven plugin (initial work)
git-svn-id: https://svn.jboss.org/repos/hibernate/core/branches/Branch_3_3@16068 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
parent
bb0e94756a
commit
ccc2571e2b
|
@ -0,0 +1,443 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC.
|
||||
*
|
||||
* 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.buildtime;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import java.util.zip.CRC32;
|
||||
import java.io.File;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
import org.hibernate.bytecode.util.ByteCodeHelper;
|
||||
import org.hibernate.bytecode.util.ClassDescriptor;
|
||||
import org.hibernate.bytecode.util.FieldFilter;
|
||||
import org.hibernate.bytecode.ClassTransformer;
|
||||
|
||||
/**
|
||||
* Provides the basic templating of how instrumentation should occur.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public abstract class AbstractInstrumenter implements Instrumenter {
|
||||
private static final int ZIP_MAGIC = 0x504B0304;
|
||||
private static final int CLASS_MAGIC = 0xCAFEBABE;
|
||||
|
||||
protected final Logger logger;
|
||||
protected final Options options;
|
||||
|
||||
/**
|
||||
* Creates the basic instrumentation strategy.
|
||||
*
|
||||
* @param logger The bridge to the environment's logging system.
|
||||
* @param options User-supplied options.
|
||||
*/
|
||||
public AbstractInstrumenter(Logger logger, Options options) {
|
||||
this.logger = logger;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the bytecode of a java class, retrieve the descriptor for that class.
|
||||
*
|
||||
* @param byecode The class bytecode.
|
||||
*
|
||||
* @return The class's descriptor
|
||||
*
|
||||
* @throws Exception Indicates problems access the bytecode.
|
||||
*/
|
||||
protected abstract ClassDescriptor getClassDescriptor(byte[] byecode) throws Exception;
|
||||
|
||||
/**
|
||||
* Create class transformer for the class.
|
||||
*
|
||||
* @param descriptor The descriptor of the class to be instrumented.
|
||||
* @param classNames The names of all classes to be instrumented; the "pipeline" if you will.
|
||||
*
|
||||
* @return The transformer for the given class; may return null to indicate that transformation should
|
||||
* be skipped (ala already instrumented).
|
||||
*/
|
||||
protected abstract ClassTransformer getClassTransformer(ClassDescriptor descriptor, Set classNames);
|
||||
|
||||
/**
|
||||
* The main instrumentation entry point. Given a set of files, perform instrumentation on each discovered class
|
||||
* file.
|
||||
*
|
||||
* @param files The files.
|
||||
*/
|
||||
public void execute(Set files) {
|
||||
Set classNames = new HashSet();
|
||||
|
||||
if ( options.performExtendedInstrumentation() ) {
|
||||
logger.debug( "collecting class names for extended instrumentation determination" );
|
||||
try {
|
||||
Iterator itr = files.iterator();
|
||||
while ( itr.hasNext() ) {
|
||||
final File file = ( File ) itr.next();
|
||||
collectClassNames( file, classNames );
|
||||
}
|
||||
}
|
||||
catch ( ExecutionException ee ) {
|
||||
throw ee;
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
throw new ExecutionException( e );
|
||||
}
|
||||
}
|
||||
|
||||
logger.info( "starting instrumentation" );
|
||||
try {
|
||||
Iterator itr = files.iterator();
|
||||
while ( itr.hasNext() ) {
|
||||
final File file = ( File ) itr.next();
|
||||
processFile( file, classNames );
|
||||
}
|
||||
}
|
||||
catch ( ExecutionException ee ) {
|
||||
throw ee;
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
throw new ExecutionException( e );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the names of classes from file, addding them to the classNames collection.
|
||||
* <p/>
|
||||
* IMPL NOTE : file here may be either a class file or a jar. If a jar, all entries in the jar file are
|
||||
* processed.
|
||||
*
|
||||
* @param file The file from which to extract class metadata (descriptor).
|
||||
* @param classNames The collected class name collection.
|
||||
*
|
||||
* @throws Exception indicates problems accessing the file or its contents.
|
||||
*/
|
||||
private void collectClassNames(File file, final Set classNames) throws Exception {
|
||||
if ( isClassFile( file ) ) {
|
||||
byte[] bytes = ByteCodeHelper.readByteCode( file );
|
||||
ClassDescriptor descriptor = getClassDescriptor( bytes );
|
||||
classNames.add( descriptor.getName() );
|
||||
}
|
||||
else if ( isJarFile( file ) ) {
|
||||
ZipEntryHandler collector = new ZipEntryHandler() {
|
||||
public void handleEntry(ZipEntry entry, byte[] byteCode) throws Exception {
|
||||
if ( !entry.isDirectory() ) {
|
||||
// see if the entry represents a class file
|
||||
DataInputStream din = new DataInputStream( new ByteArrayInputStream( byteCode ) );
|
||||
if ( din.readInt() == CLASS_MAGIC ) {
|
||||
classNames.add( getClassDescriptor( byteCode ).getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
ZipFileProcessor processor = new ZipFileProcessor( collector );
|
||||
processor.process( file );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this file represent a compiled class?
|
||||
*
|
||||
* @param file The file to check.
|
||||
*
|
||||
* @return True if the file is a class; false otherwise.
|
||||
*
|
||||
* @throws IOException Indicates problem access the file.
|
||||
*/
|
||||
protected final boolean isClassFile(File file) throws IOException {
|
||||
return checkMagic( file, CLASS_MAGIC );
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this file represent a zip file of some format?
|
||||
*
|
||||
* @param file The file to check.
|
||||
*
|
||||
* @return True if the file is n archive; false otherwise.
|
||||
*
|
||||
* @throws IOException Indicates problem access the file.
|
||||
*/
|
||||
protected final boolean isJarFile(File file) throws IOException {
|
||||
return checkMagic(file, ZIP_MAGIC);
|
||||
}
|
||||
|
||||
protected final boolean checkMagic(File file, long magic) throws IOException {
|
||||
DataInputStream in = new DataInputStream( new FileInputStream( file ) );
|
||||
try {
|
||||
int m = in.readInt();
|
||||
return magic == m;
|
||||
}
|
||||
finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually process the file by applying instrumentation transformations to any classes it contains.
|
||||
* <p/>
|
||||
* Again, just like with {@link #collectClassNames} this method can handle both class and archive files.
|
||||
*
|
||||
* @param file The file to process.
|
||||
* @param classNames The 'pipeline' of classes to be processed. Only actually populated when the user
|
||||
* specifies to perform {@link Options#performExtendedInstrumentation() extended} instrumentation.
|
||||
*
|
||||
* @throws Exception Indicates an issue either access files or applying the transformations.
|
||||
*/
|
||||
protected void processFile(File file, Set classNames) throws Exception {
|
||||
if ( isClassFile( file ) ) {
|
||||
logger.debug( "processing class file : " + file.getAbsolutePath() );
|
||||
processClassFile( file, classNames );
|
||||
}
|
||||
else if ( isJarFile( file ) ) {
|
||||
logger.debug( "processing jar file : " + file.getAbsolutePath() );
|
||||
processJarFile( file, classNames );
|
||||
}
|
||||
else {
|
||||
logger.debug( "ignoring file : " + file.getAbsolutePath() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a class file. Delegated to from {@link #processFile} in the case of a class file.
|
||||
*
|
||||
* @param file The class file to process.
|
||||
* @param classNames The 'pipeline' of classes to be processed. Only actually populated when the user
|
||||
* specifies to perform {@link Options#performExtendedInstrumentation() extended} instrumentation.
|
||||
*
|
||||
* @throws Exception Indicates an issue either access files or applying the transformations.
|
||||
*/
|
||||
protected void processClassFile(File file, Set classNames) throws Exception {
|
||||
byte[] bytes = ByteCodeHelper.readByteCode( file );
|
||||
ClassDescriptor descriptor = getClassDescriptor( bytes );
|
||||
ClassTransformer transformer = getClassTransformer( descriptor, classNames );
|
||||
if ( transformer == null ) {
|
||||
logger.debug( "no trasformer for class file : " + file.getAbsolutePath() );
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info( "processing class : " + descriptor.getName() + "; file = " + file.getAbsolutePath() );
|
||||
byte[] transformedBytes = transformer.transform(
|
||||
getClass().getClassLoader(),
|
||||
descriptor.getName(),
|
||||
null,
|
||||
null,
|
||||
descriptor.getBytes()
|
||||
);
|
||||
|
||||
OutputStream out = new FileOutputStream( file );
|
||||
try {
|
||||
out.write( transformedBytes );
|
||||
out.flush();
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
out.close();
|
||||
}
|
||||
catch ( IOException ignore) {
|
||||
// intentionally empty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an archive file. Delegated to from {@link #processFile} in the case of an archive file.
|
||||
*
|
||||
* @param file The archive file to process.
|
||||
* @param classNames The 'pipeline' of classes to be processed. Only actually populated when the user
|
||||
* specifies to perform {@link Options#performExtendedInstrumentation() extended} instrumentation.
|
||||
*
|
||||
* @throws Exception Indicates an issue either access files or applying the transformations.
|
||||
*/
|
||||
protected void processJarFile(final File file, final Set classNames) throws Exception {
|
||||
File tempFile = File.createTempFile(
|
||||
file.getName(),
|
||||
null,
|
||||
new File( file.getAbsoluteFile().getParent() )
|
||||
);
|
||||
|
||||
try {
|
||||
FileOutputStream fout = new FileOutputStream( tempFile, false );
|
||||
try {
|
||||
final ZipOutputStream out = new ZipOutputStream( fout );
|
||||
ZipEntryHandler transformer = new ZipEntryHandler() {
|
||||
public void handleEntry(ZipEntry entry, byte[] byteCode) throws Exception {
|
||||
logger.debug( "starting zip entry : " + entry.toString() );
|
||||
if ( !entry.isDirectory() ) {
|
||||
// see if the entry represents a class file
|
||||
DataInputStream din = new DataInputStream( new ByteArrayInputStream( byteCode ) );
|
||||
if ( din.readInt() == CLASS_MAGIC ) {
|
||||
ClassDescriptor descriptor = getClassDescriptor( byteCode );
|
||||
ClassTransformer transformer = getClassTransformer( descriptor, classNames );
|
||||
if ( transformer == null ) {
|
||||
logger.debug( "no transformer for zip entry : " + entry.toString() );
|
||||
}
|
||||
else {
|
||||
logger.info( "processing class : " + descriptor.getName() + "; entry = " + file.getAbsolutePath() );
|
||||
byteCode = transformer.transform(
|
||||
getClass().getClassLoader(),
|
||||
descriptor.getName(),
|
||||
null,
|
||||
null,
|
||||
descriptor.getBytes()
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.debug( "ignoring zip entry : " + entry.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
ZipEntry outEntry = new ZipEntry( entry.getName() );
|
||||
outEntry.setMethod( entry.getMethod() );
|
||||
outEntry.setComment( entry.getComment() );
|
||||
outEntry.setSize( byteCode.length );
|
||||
|
||||
if ( outEntry.getMethod() == ZipEntry.STORED ){
|
||||
CRC32 crc = new CRC32();
|
||||
crc.update( byteCode );
|
||||
outEntry.setCrc( crc.getValue() );
|
||||
outEntry.setCompressedSize( byteCode.length );
|
||||
}
|
||||
out.putNextEntry( outEntry );
|
||||
out.write( byteCode );
|
||||
out.closeEntry();
|
||||
}
|
||||
};
|
||||
ZipFileProcessor processor = new ZipFileProcessor( transformer );
|
||||
processor.process( file );
|
||||
out.close();
|
||||
}
|
||||
finally{
|
||||
fout.close();
|
||||
}
|
||||
|
||||
if ( file.delete() ) {
|
||||
File newFile = new File( tempFile.getAbsolutePath() );
|
||||
if( !newFile.renameTo( file ) ) {
|
||||
throw new IOException( "can not rename " + tempFile + " to " + file );
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IOException( "can not delete " + file );
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ( ! tempFile.delete() ) {
|
||||
logger.info( "Unable to cleanup temporary jar file : " + tempFile.getAbsolutePath() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows control over what exacctly to transform.
|
||||
*/
|
||||
protected class CustomFieldFilter implements FieldFilter {
|
||||
private final ClassDescriptor descriptor;
|
||||
private final Set classNames;
|
||||
|
||||
public CustomFieldFilter(ClassDescriptor descriptor, Set classNames) {
|
||||
this.descriptor = descriptor;
|
||||
this.classNames = classNames;
|
||||
}
|
||||
|
||||
public boolean shouldInstrumentField(String className, String fieldName) {
|
||||
if ( descriptor.getName().equals( className ) ) {
|
||||
logger.trace( "accepting transformation of field [" + className + "." + fieldName + "]" );
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
logger.trace( "rejecting transformation of field [" + className + "." + fieldName + "]" );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldTransformFieldAccess(
|
||||
String transformingClassName,
|
||||
String fieldOwnerClassName,
|
||||
String fieldName) {
|
||||
if ( descriptor.getName().equals( fieldOwnerClassName ) ) {
|
||||
logger.trace( "accepting transformation of field access [" + fieldOwnerClassName + "." + fieldName + "]" );
|
||||
return true;
|
||||
}
|
||||
else if ( options.performExtendedInstrumentation() && classNames.contains( fieldOwnerClassName ) ) {
|
||||
logger.trace( "accepting extended transformation of field access [" + fieldOwnerClassName + "." + fieldName + "]" );
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
logger.trace( "rejecting transformation of field access [" + fieldOwnerClassName + "." + fieldName + "]; caller = " + transformingClassName );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* General strategy contract for handling entries in an archive file.
|
||||
*/
|
||||
private static interface ZipEntryHandler {
|
||||
/**
|
||||
* Apply strategy to the given archive entry.
|
||||
*
|
||||
* @param entry The archive file entry.
|
||||
* @param byteCode
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void handleEntry(ZipEntry entry, byte[] byteCode) throws Exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies {@link ZipEntryHandler} strategies to the entries of an archive file.
|
||||
*/
|
||||
private static class ZipFileProcessor {
|
||||
private final ZipEntryHandler entryHandler;
|
||||
|
||||
public ZipFileProcessor(ZipEntryHandler entryHandler) {
|
||||
this.entryHandler = entryHandler;
|
||||
}
|
||||
|
||||
public void process(File file) throws Exception {
|
||||
ZipInputStream zip = new ZipInputStream( new FileInputStream( file ) );
|
||||
|
||||
try {
|
||||
ZipEntry entry;
|
||||
while ( (entry = zip.getNextEntry()) != null ) {
|
||||
byte bytes[] = ByteCodeHelper.readByteCode( zip );
|
||||
entryHandler.handleEntry( entry, bytes );
|
||||
zip.closeEntry();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
zip.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC.
|
||||
*
|
||||
* 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.buildtime;
|
||||
|
||||
import java.util.Set;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import org.hibernate.bytecode.util.ClassDescriptor;
|
||||
import org.hibernate.bytecode.util.BasicClassFilter;
|
||||
import org.hibernate.bytecode.ClassTransformer;
|
||||
import org.hibernate.bytecode.cglib.BytecodeProviderImpl;
|
||||
import org.hibernate.repackage.cglib.asm.ClassReader;
|
||||
import org.hibernate.repackage.cglib.core.ClassNameReader;
|
||||
import org.hibernate.repackage.cglib.transform.impl.InterceptFieldEnabled;
|
||||
|
||||
/**
|
||||
* Strategy for performing build-time instrumentation of persistent classes in order to enable
|
||||
* field-level interception using CGLIB.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
* @author Gavin King
|
||||
*/
|
||||
public class CGLIBInstrumenter extends AbstractInstrumenter {
|
||||
private static final BasicClassFilter CLASS_FILTER = new BasicClassFilter();
|
||||
|
||||
private final BytecodeProviderImpl provider = new BytecodeProviderImpl();
|
||||
|
||||
public CGLIBInstrumenter(Logger logger, Options options) {
|
||||
super( logger, options );
|
||||
}
|
||||
|
||||
protected ClassDescriptor getClassDescriptor(byte[] byecode) throws Exception {
|
||||
return new CustomClassDescriptor( byecode );
|
||||
}
|
||||
|
||||
protected ClassTransformer getClassTransformer(ClassDescriptor descriptor, Set classNames) {
|
||||
if ( descriptor.isInstrumented() ) {
|
||||
logger.debug( "class [" + descriptor.getName() + "] already instrumented" );
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return provider.getTransformer( CLASS_FILTER, new CustomFieldFilter( descriptor, classNames ) );
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomClassDescriptor implements ClassDescriptor {
|
||||
private final byte[] bytecode;
|
||||
private final String name;
|
||||
private final boolean isInstrumented;
|
||||
|
||||
public CustomClassDescriptor(byte[] bytecode) throws Exception {
|
||||
this.bytecode = bytecode;
|
||||
ClassReader reader = new ClassReader( new ByteArrayInputStream( bytecode ) );
|
||||
String[] names = ClassNameReader.getClassInfo( reader );
|
||||
this.name = names[0];
|
||||
boolean instrumented = false;
|
||||
for ( int i = 1; i < names.length; i++ ) {
|
||||
if ( InterceptFieldEnabled.class.getName().equals( names[i] ) ) {
|
||||
instrumented = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.isInstrumented = instrumented;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isInstrumented() {
|
||||
return isInstrumented;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytecode;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC.
|
||||
*
|
||||
* 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.buildtime;
|
||||
|
||||
/**
|
||||
* Indicates problem performing the instrumentation execution.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ExecutionException extends RuntimeException {
|
||||
public ExecutionException(String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public ExecutionException(Throwable cause) {
|
||||
super( cause );
|
||||
}
|
||||
|
||||
public ExecutionException(String message, Throwable cause) {
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC.
|
||||
*
|
||||
* 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.buildtime;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TODO : javadoc
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface Instrumenter {
|
||||
public void execute(Set files);
|
||||
|
||||
public static interface Options {
|
||||
public boolean performExtendedInstrumentation();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC.
|
||||
*
|
||||
* 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.buildtime;
|
||||
|
||||
import java.util.Set;
|
||||
import java.io.IOException;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import javassist.bytecode.ClassFile;
|
||||
|
||||
import org.hibernate.bytecode.util.ClassDescriptor;
|
||||
import org.hibernate.bytecode.util.BasicClassFilter;
|
||||
import org.hibernate.bytecode.ClassTransformer;
|
||||
import org.hibernate.bytecode.javassist.BytecodeProviderImpl;
|
||||
import org.hibernate.bytecode.javassist.FieldHandled;
|
||||
|
||||
/**
|
||||
* Strategy for performing build-time instrumentation of persistent classes in order to enable
|
||||
* field-level interception using Javassist.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
* @author Muga Nishizawa
|
||||
*/
|
||||
public class JavassistInstrumenter extends AbstractInstrumenter {
|
||||
|
||||
private static final BasicClassFilter CLASS_FILTER = new BasicClassFilter();
|
||||
|
||||
private final BytecodeProviderImpl provider = new BytecodeProviderImpl();
|
||||
|
||||
public JavassistInstrumenter(Logger logger, Options options) {
|
||||
super( logger, options );
|
||||
}
|
||||
|
||||
protected ClassDescriptor getClassDescriptor(byte[] bytecode) throws IOException {
|
||||
return new CustomClassDescriptor( bytecode );
|
||||
}
|
||||
|
||||
protected ClassTransformer getClassTransformer(ClassDescriptor descriptor, Set classNames) {
|
||||
if ( descriptor.isInstrumented() ) {
|
||||
logger.debug( "class [" + descriptor.getName() + "] already instrumented" );
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return provider.getTransformer( CLASS_FILTER, new CustomFieldFilter( descriptor, classNames ) );
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomClassDescriptor implements ClassDescriptor {
|
||||
private final byte[] bytes;
|
||||
private final ClassFile classFile;
|
||||
|
||||
public CustomClassDescriptor(byte[] bytes) throws IOException {
|
||||
this.bytes = bytes;
|
||||
this.classFile = new ClassFile( new DataInputStream( new ByteArrayInputStream( bytes ) ) );
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return classFile.getName();
|
||||
}
|
||||
|
||||
public boolean isInstrumented() {
|
||||
String[] intfs = classFile.getInterfaces();
|
||||
for ( int i = 0; i < intfs.length; i++ ) {
|
||||
if ( FieldHandled.class.getName().equals( intfs[i] ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC.
|
||||
*
|
||||
* 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.buildtime;
|
||||
|
||||
/**
|
||||
* Provides an abstraction for how instrumentation does logging because it is usually run in environments (Ant/Maven)
|
||||
* with their own logging infrastructure. This abstraction allows proper bridging.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface Logger {
|
||||
public void trace(String message);
|
||||
|
||||
public void debug(String message);
|
||||
|
||||
public void info(String message);
|
||||
|
||||
public void warn(String message);
|
||||
|
||||
public void error(String message);
|
||||
}
|
|
@ -20,7 +20,6 @@
|
|||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
package org.hibernate.tool.instrument;
|
||||
|
||||
|
@ -29,43 +28,31 @@ import org.apache.tools.ant.BuildException;
|
|||
import org.apache.tools.ant.Project;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
import org.hibernate.bytecode.util.ClassDescriptor;
|
||||
import org.hibernate.bytecode.util.ByteCodeHelper;
|
||||
import org.hibernate.bytecode.util.FieldFilter;
|
||||
import org.hibernate.bytecode.ClassTransformer;
|
||||
|
||||
import org.hibernate.bytecode.buildtime.Instrumenter;
|
||||
import org.hibernate.bytecode.buildtime.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.CRC32;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
/**
|
||||
* Super class for all Hibernate instrumentation tasks. Provides the basic
|
||||
* templating of how instrumentation should occur.
|
||||
* Super class for all Hibernate instrumentation tasks. Provides the basic templating of how instrumentation
|
||||
* should occur; subclasses simply plug in to that process appropriately for the given bytecode provider.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public abstract class BasicInstrumentationTask extends Task {
|
||||
public abstract class BasicInstrumentationTask extends Task implements Instrumenter.Options {
|
||||
|
||||
private static final int ZIP_MAGIC = 0x504B0304;
|
||||
private static final int CLASS_MAGIC = 0xCAFEBABE;
|
||||
private final LoggerBridge logger = new LoggerBridge();
|
||||
|
||||
protected final Logger logger = new Logger();
|
||||
private List filesets = new ArrayList();
|
||||
private Set classNames = new HashSet();
|
||||
private boolean extended;
|
||||
|
||||
// deprecated option...
|
||||
private boolean verbose;
|
||||
|
||||
public void addFileset(FileSet set) {
|
||||
|
@ -92,273 +79,40 @@ public abstract class BasicInstrumentationTask extends Task {
|
|||
this.verbose = verbose;
|
||||
}
|
||||
|
||||
public final boolean performExtendedInstrumentation() {
|
||||
return isExtended();
|
||||
}
|
||||
|
||||
protected abstract Instrumenter buildInstrumenter(Logger logger, Instrumenter.Options options);
|
||||
|
||||
public void execute() throws BuildException {
|
||||
if ( isExtended() ) {
|
||||
collectClassNames();
|
||||
}
|
||||
logger.info( "starting instrumentation" );
|
||||
Project project = getProject();
|
||||
Iterator filesets = filesets();
|
||||
while ( filesets.hasNext() ) {
|
||||
FileSet fs = ( FileSet ) filesets.next();
|
||||
DirectoryScanner ds = fs.getDirectoryScanner( project );
|
||||
String[] includedFiles = ds.getIncludedFiles();
|
||||
File d = fs.getDir( project );
|
||||
for ( int i = 0; i < includedFiles.length; ++i ) {
|
||||
File file = new File( d, includedFiles[i] );
|
||||
try {
|
||||
processFile( file );
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
throw new BuildException( e );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectClassNames() {
|
||||
logger.info( "collecting class names for extended instrumentation determination" );
|
||||
Project project = getProject();
|
||||
Iterator filesets = filesets();
|
||||
while ( filesets.hasNext() ) {
|
||||
FileSet fs = ( FileSet ) filesets.next();
|
||||
DirectoryScanner ds = fs.getDirectoryScanner( project );
|
||||
String[] includedFiles = ds.getIncludedFiles();
|
||||
File d = fs.getDir( project );
|
||||
for ( int i = 0; i < includedFiles.length; ++i ) {
|
||||
File file = new File( d, includedFiles[i] );
|
||||
try {
|
||||
collectClassNames( file );
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
throw new BuildException( e );
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info( classNames.size() + " class(es) being checked" );
|
||||
}
|
||||
|
||||
private void collectClassNames(File file) throws Exception {
|
||||
if ( isClassFile( file ) ) {
|
||||
byte[] bytes = ByteCodeHelper.readByteCode( file );
|
||||
ClassDescriptor descriptor = getClassDescriptor( bytes );
|
||||
classNames.add( descriptor.getName() );
|
||||
}
|
||||
else if ( isJarFile( file ) ) {
|
||||
ZipEntryHandler collector = new ZipEntryHandler() {
|
||||
public void handleEntry(ZipEntry entry, byte[] byteCode) throws Exception {
|
||||
if ( !entry.isDirectory() ) {
|
||||
// see if the entry represents a class file
|
||||
DataInputStream din = new DataInputStream( new ByteArrayInputStream( byteCode ) );
|
||||
if ( din.readInt() == CLASS_MAGIC ) {
|
||||
classNames.add( getClassDescriptor( byteCode ).getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
ZipFileProcessor processor = new ZipFileProcessor( collector );
|
||||
processor.process( file );
|
||||
}
|
||||
}
|
||||
|
||||
protected void processFile(File file) throws Exception {
|
||||
logger.verbose( "processing file : " + file.toURL() );
|
||||
if ( isClassFile( file ) ) {
|
||||
processClassFile(file);
|
||||
}
|
||||
else if ( isJarFile( file ) ) {
|
||||
processJarFile(file);
|
||||
}
|
||||
else {
|
||||
logger.verbose( "ignoring " + file.toURL() );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected final boolean isClassFile(File file) throws IOException {
|
||||
return checkMagic( file, CLASS_MAGIC );
|
||||
}
|
||||
|
||||
protected final boolean isJarFile(File file) throws IOException {
|
||||
return checkMagic(file, ZIP_MAGIC);
|
||||
}
|
||||
|
||||
protected final boolean checkMagic(File file, long magic) throws IOException {
|
||||
DataInputStream in = new DataInputStream( new FileInputStream( file ) );
|
||||
try {
|
||||
int m = in.readInt();
|
||||
return magic == m;
|
||||
}
|
||||
finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected void processClassFile(File file) throws Exception {
|
||||
logger.verbose( "Starting class file : " + file.toURL() );
|
||||
byte[] bytes = ByteCodeHelper.readByteCode( file );
|
||||
ClassDescriptor descriptor = getClassDescriptor( bytes );
|
||||
ClassTransformer transformer = getClassTransformer( descriptor );
|
||||
if ( transformer == null ) {
|
||||
logger.verbose( "skipping file : " + file.toURL() );
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info( "processing class [" + descriptor.getName() + "]; file = " + file.toURL() );
|
||||
byte[] transformedBytes = transformer.transform(
|
||||
getClass().getClassLoader(),
|
||||
descriptor.getName(),
|
||||
null,
|
||||
null,
|
||||
descriptor.getBytes()
|
||||
);
|
||||
|
||||
OutputStream out = new FileOutputStream( file );
|
||||
try {
|
||||
out.write( transformedBytes );
|
||||
out.flush();
|
||||
buildInstrumenter( logger, this )
|
||||
.execute( collectSpecifiedFiles() );
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
out.close();
|
||||
}
|
||||
catch ( IOException ignore) {
|
||||
// intentionally empty
|
||||
}
|
||||
catch ( Throwable t ) {
|
||||
throw new BuildException( t );
|
||||
}
|
||||
}
|
||||
|
||||
protected void processJarFile(final File file) throws Exception {
|
||||
logger.verbose( "starting jar file : " + file.toURL() );
|
||||
|
||||
File tempFile = File.createTempFile(
|
||||
file.getName(),
|
||||
null,
|
||||
new File( file.getAbsoluteFile().getParent() )
|
||||
);
|
||||
|
||||
try {
|
||||
FileOutputStream fout = new FileOutputStream( tempFile, false );
|
||||
try {
|
||||
final ZipOutputStream out = new ZipOutputStream( fout );
|
||||
ZipEntryHandler transformer = new ZipEntryHandler() {
|
||||
public void handleEntry(ZipEntry entry, byte[] byteCode) throws Exception {
|
||||
logger.verbose( "starting entry : " + entry.toString() );
|
||||
if ( !entry.isDirectory() ) {
|
||||
// see if the entry represents a class file
|
||||
DataInputStream din = new DataInputStream( new ByteArrayInputStream( byteCode ) );
|
||||
if ( din.readInt() == CLASS_MAGIC ) {
|
||||
ClassDescriptor descriptor = getClassDescriptor( byteCode );
|
||||
ClassTransformer transformer = getClassTransformer( descriptor );
|
||||
if ( transformer == null ) {
|
||||
logger.verbose( "skipping entry : " + entry.toString() );
|
||||
}
|
||||
else {
|
||||
logger.info( "processing class [" + descriptor.getName() + "]; entry = " + file.toURL() );
|
||||
byteCode = transformer.transform(
|
||||
getClass().getClassLoader(),
|
||||
descriptor.getName(),
|
||||
null,
|
||||
null,
|
||||
descriptor.getBytes()
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.verbose( "ignoring zip entry : " + entry.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
ZipEntry outEntry = new ZipEntry( entry.getName() );
|
||||
outEntry.setMethod( entry.getMethod() );
|
||||
outEntry.setComment( entry.getComment() );
|
||||
outEntry.setSize( byteCode.length );
|
||||
|
||||
if ( outEntry.getMethod() == ZipEntry.STORED ){
|
||||
CRC32 crc = new CRC32();
|
||||
crc.update( byteCode );
|
||||
outEntry.setCrc( crc.getValue() );
|
||||
outEntry.setCompressedSize( byteCode.length );
|
||||
}
|
||||
out.putNextEntry( outEntry );
|
||||
out.write( byteCode );
|
||||
out.closeEntry();
|
||||
}
|
||||
};
|
||||
ZipFileProcessor processor = new ZipFileProcessor( transformer );
|
||||
processor.process( file );
|
||||
out.close();
|
||||
}
|
||||
finally{
|
||||
fout.close();
|
||||
}
|
||||
|
||||
if ( file.delete() ) {
|
||||
File newFile = new File( tempFile.getAbsolutePath() );
|
||||
if( !newFile.renameTo( file ) ) {
|
||||
throw new IOException( "can not rename " + tempFile + " to " + file );
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IOException("can not delete " + file);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
tempFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isBeingIntrumented(String className) {
|
||||
logger.verbose( "checking to see if class [" + className + "] is set to be instrumented" );
|
||||
return classNames.contains( className );
|
||||
}
|
||||
|
||||
protected abstract ClassDescriptor getClassDescriptor(byte[] byecode) throws Exception;
|
||||
|
||||
protected abstract ClassTransformer getClassTransformer(ClassDescriptor descriptor);
|
||||
|
||||
protected class CustomFieldFilter implements FieldFilter {
|
||||
private final ClassDescriptor descriptor;
|
||||
|
||||
public CustomFieldFilter(ClassDescriptor descriptor) {
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
public boolean shouldInstrumentField(String className, String fieldName) {
|
||||
if ( descriptor.getName().equals( className ) ) {
|
||||
logger.verbose( "accepting transformation of field [" + className + "." + fieldName + "]" );
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
logger.verbose( "rejecting transformation of field [" + className + "." + fieldName + "]" );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldTransformFieldAccess(
|
||||
String transformingClassName,
|
||||
String fieldOwnerClassName,
|
||||
String fieldName) {
|
||||
if ( descriptor.getName().equals( fieldOwnerClassName ) ) {
|
||||
logger.verbose( "accepting transformation of field access [" + fieldOwnerClassName + "." + fieldName + "]" );
|
||||
return true;
|
||||
}
|
||||
else if ( isExtended() && isBeingIntrumented( fieldOwnerClassName ) ) {
|
||||
logger.verbose( "accepting extended transformation of field access [" + fieldOwnerClassName + "." + fieldName + "]" );
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
logger.verbose( "rejecting transformation of field access [" + fieldOwnerClassName + "." + fieldName + "]; caller = " + transformingClassName );
|
||||
return false;
|
||||
private Set collectSpecifiedFiles() {
|
||||
HashSet files = new HashSet();
|
||||
Project project = getProject();
|
||||
Iterator filesets = filesets();
|
||||
while ( filesets.hasNext() ) {
|
||||
FileSet fs = ( FileSet ) filesets.next();
|
||||
DirectoryScanner ds = fs.getDirectoryScanner( project );
|
||||
String[] includedFiles = ds.getIncludedFiles();
|
||||
File d = fs.getDir( project );
|
||||
for ( int i = 0; i < includedFiles.length; ++i ) {
|
||||
files.add( new File( d, includedFiles[i] ) );
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
protected class Logger {
|
||||
public void verbose(String message) {
|
||||
if ( verbose ) {
|
||||
System.out.println( message );
|
||||
}
|
||||
protected class LoggerBridge implements Logger {
|
||||
public void trace(String message) {
|
||||
log( message, Project.MSG_VERBOSE );
|
||||
}
|
||||
|
||||
|
@ -373,34 +127,10 @@ public abstract class BasicInstrumentationTask extends Task {
|
|||
public void warn(String message) {
|
||||
log( message, Project.MSG_WARN );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static interface ZipEntryHandler {
|
||||
public void handleEntry(ZipEntry entry, byte[] byteCode) throws Exception;
|
||||
}
|
||||
|
||||
private static class ZipFileProcessor {
|
||||
private final ZipEntryHandler entryHandler;
|
||||
|
||||
public ZipFileProcessor(ZipEntryHandler entryHandler) {
|
||||
this.entryHandler = entryHandler;
|
||||
}
|
||||
|
||||
public void process(File file) throws Exception {
|
||||
ZipInputStream zip = new ZipInputStream( new FileInputStream( file ) );
|
||||
|
||||
try {
|
||||
ZipEntry entry;
|
||||
while ( (entry = zip.getNextEntry()) != null ) {
|
||||
byte bytes[] = ByteCodeHelper.readByteCode( zip );
|
||||
entryHandler.handleEntry( entry, bytes );
|
||||
zip.closeEntry();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
zip.close();
|
||||
}
|
||||
public void error(String message) {
|
||||
log( message, Project.MSG_ERR );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,17 +24,10 @@
|
|||
*/
|
||||
package org.hibernate.tool.instrument.cglib;
|
||||
|
||||
import org.hibernate.bytecode.util.BasicClassFilter;
|
||||
import org.hibernate.bytecode.util.ClassDescriptor;
|
||||
import org.hibernate.bytecode.cglib.BytecodeProviderImpl;
|
||||
import org.hibernate.bytecode.ClassTransformer;
|
||||
import org.hibernate.bytecode.buildtime.CGLIBInstrumenter;
|
||||
import org.hibernate.bytecode.buildtime.Instrumenter;
|
||||
import org.hibernate.bytecode.buildtime.Logger;
|
||||
import org.hibernate.tool.instrument.BasicInstrumentationTask;
|
||||
import org.hibernate.repackage.cglib.asm.ClassReader;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import org.hibernate.repackage.cglib.core.ClassNameReader;
|
||||
import org.hibernate.repackage.cglib.transform.impl.InterceptFieldEnabled;
|
||||
|
||||
/**
|
||||
* An Ant task for instrumenting persistent classes in order to enable
|
||||
|
@ -50,7 +43,7 @@ import org.hibernate.repackage.cglib.transform.impl.InterceptFieldEnabled;
|
|||
* required Hibernate and CGLIB libraries.
|
||||
* <p/>
|
||||
* And then use it like:<pre>
|
||||
* <instrument verbose="true">
|
||||
* <instrument>
|
||||
* <fileset dir="${testclasses.dir}/org/hibernate/test">
|
||||
* <include name="yadda/yadda/**"/>
|
||||
* ...
|
||||
|
@ -62,7 +55,7 @@ import org.hibernate.repackage.cglib.transform.impl.InterceptFieldEnabled;
|
|||
* <p/>
|
||||
* Optionally you can chose to enable "Extended Instrumentation" if desired
|
||||
* by specifying the extended attriubute on the task:<pre>
|
||||
* <instrument verbose="true" extended="true">
|
||||
* <instrument extended="true">
|
||||
* ...
|
||||
* </instrument>
|
||||
* </pre>
|
||||
|
@ -72,58 +65,7 @@ import org.hibernate.repackage.cglib.transform.impl.InterceptFieldEnabled;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public class InstrumentTask extends BasicInstrumentationTask {
|
||||
|
||||
private static final BasicClassFilter CLASS_FILTER = new BasicClassFilter();
|
||||
|
||||
private final BytecodeProviderImpl provider = new BytecodeProviderImpl();
|
||||
|
||||
|
||||
protected ClassDescriptor getClassDescriptor(byte[] byecode) throws Exception {
|
||||
return new CustomClassDescriptor( byecode );
|
||||
protected Instrumenter buildInstrumenter(Logger logger, Instrumenter.Options options) {
|
||||
return new CGLIBInstrumenter( logger, options );
|
||||
}
|
||||
|
||||
protected ClassTransformer getClassTransformer(ClassDescriptor descriptor) {
|
||||
if ( descriptor.isInstrumented() ) {
|
||||
logger.verbose( "class [" + descriptor.getName() + "] already instrumented" );
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return provider.getTransformer( CLASS_FILTER, new CustomFieldFilter( descriptor ) );
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomClassDescriptor implements ClassDescriptor {
|
||||
private final byte[] bytecode;
|
||||
private final String name;
|
||||
private final boolean isInstrumented;
|
||||
|
||||
public CustomClassDescriptor(byte[] bytecode) throws Exception {
|
||||
this.bytecode = bytecode;
|
||||
ClassReader reader = new ClassReader( new ByteArrayInputStream( bytecode ) );
|
||||
String[] names = ClassNameReader.getClassInfo( reader );
|
||||
this.name = names[0];
|
||||
boolean instrumented = false;
|
||||
for ( int i = 1; i < names.length; i++ ) {
|
||||
if ( InterceptFieldEnabled.class.getName().equals( names[i] ) ) {
|
||||
instrumented = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.isInstrumented = instrumented;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isInstrumented() {
|
||||
return isInstrumented;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytecode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -24,18 +24,10 @@
|
|||
*/
|
||||
package org.hibernate.tool.instrument.javassist;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import javassist.bytecode.ClassFile;
|
||||
|
||||
import org.hibernate.bytecode.buildtime.Instrumenter;
|
||||
import org.hibernate.bytecode.buildtime.JavassistInstrumenter;
|
||||
import org.hibernate.bytecode.buildtime.Logger;
|
||||
import org.hibernate.tool.instrument.BasicInstrumentationTask;
|
||||
import org.hibernate.bytecode.util.ClassDescriptor;
|
||||
import org.hibernate.bytecode.util.BasicClassFilter;
|
||||
import org.hibernate.bytecode.ClassTransformer;
|
||||
import org.hibernate.bytecode.javassist.BytecodeProviderImpl;
|
||||
import org.hibernate.bytecode.javassist.FieldHandled;
|
||||
|
||||
/**
|
||||
* An Ant task for instrumenting persistent classes in order to enable
|
||||
|
@ -73,51 +65,7 @@ import org.hibernate.bytecode.javassist.FieldHandled;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public class InstrumentTask extends BasicInstrumentationTask {
|
||||
|
||||
private static final BasicClassFilter CLASS_FILTER = new BasicClassFilter();
|
||||
|
||||
private final BytecodeProviderImpl provider = new BytecodeProviderImpl();
|
||||
|
||||
protected ClassDescriptor getClassDescriptor(byte[] bytecode) throws IOException {
|
||||
return new CustomClassDescriptor( bytecode );
|
||||
protected Instrumenter buildInstrumenter(Logger logger, Instrumenter.Options options) {
|
||||
return new JavassistInstrumenter( logger, options );
|
||||
}
|
||||
|
||||
protected ClassTransformer getClassTransformer(ClassDescriptor descriptor) {
|
||||
if ( descriptor.isInstrumented() ) {
|
||||
logger.verbose( "class [" + descriptor.getName() + "] already instrumented" );
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return provider.getTransformer( CLASS_FILTER, new CustomFieldFilter( descriptor ) );
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomClassDescriptor implements ClassDescriptor {
|
||||
private final byte[] bytes;
|
||||
private final ClassFile classFile;
|
||||
|
||||
public CustomClassDescriptor(byte[] bytes) throws IOException {
|
||||
this.bytes = bytes;
|
||||
this.classFile = new ClassFile( new DataInputStream( new ByteArrayInputStream( bytes ) ) );
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return classFile.getName();
|
||||
}
|
||||
|
||||
public boolean isInstrumented() {
|
||||
String[] intfs = classFile.getInterfaces();
|
||||
for ( int i = 0; i < intfs.length; i++ ) {
|
||||
if ( FieldHandled.class.getName().equals( intfs[i] ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Hibernate, Relational Persistence for Idiomatic Java
|
||||
~
|
||||
~ Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC.
|
||||
~
|
||||
~ 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
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>hibernate-parent</artifactId>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<version>3.3.2-SNAPSHOT</version>
|
||||
<relativePath>../parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-maven-plugin</artifactId>
|
||||
<packaging>mojo</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${groupId}</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<version>${version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-plugin-api</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-project</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2009, Red Hat Middleware LLC 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 Middleware LLC.
|
||||
*
|
||||
* 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.maven;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Arrays;
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.maven.plugin.AbstractMojo;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoFailureException;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.codehaus.plexus.util.DirectoryScanner;
|
||||
|
||||
import org.hibernate.bytecode.buildtime.Instrumenter;
|
||||
import org.hibernate.bytecode.buildtime.Logger;
|
||||
import org.hibernate.bytecode.buildtime.JavassistInstrumenter;
|
||||
import org.hibernate.bytecode.buildtime.CGLIBInstrumenter;
|
||||
|
||||
/**
|
||||
* @goal instrument
|
||||
* @phase process-classes
|
||||
* @requiresDependencyResolution
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class InstrumentationMojo extends AbstractMojo implements Instrumenter.Options {
|
||||
/**
|
||||
* INTERNAL : The Maven Project to which we are attached
|
||||
*
|
||||
* @parameter expression="${project}"
|
||||
* @required
|
||||
*/
|
||||
private MavenProject project;
|
||||
|
||||
/**
|
||||
* Specifies the directory containing the classes to be instrumented. By default we use the
|
||||
* project's output directory, which in turn defaults to <samp>${basedir}/target/classes</samp>.
|
||||
*
|
||||
* @parameter expression="${project.build.outputDirectory}"
|
||||
* @required
|
||||
*/
|
||||
private File instrumentationDirectory;
|
||||
|
||||
/**
|
||||
* @parameter
|
||||
*/
|
||||
private boolean extended;
|
||||
|
||||
/**
|
||||
* @parameter
|
||||
*/
|
||||
private String provider;
|
||||
|
||||
public boolean performExtendedInstrumentation() {
|
||||
return extended;
|
||||
}
|
||||
|
||||
public void execute() throws MojoExecutionException, MojoFailureException {
|
||||
// first, lets determine whether to apply cglib or javassist based instrumentation...
|
||||
if ( provider == null ) {
|
||||
provider = determineProvider();
|
||||
if ( provider == null ) {
|
||||
throw new MojoExecutionException( "Unable to determine provider to use" );
|
||||
}
|
||||
}
|
||||
|
||||
Instrumenter instrumenter = resolveInstrumenter( provider, new LoggingBridge() );
|
||||
try {
|
||||
instrumenter.execute( collectFilesToProcess() );
|
||||
}
|
||||
catch ( Throwable t ) {
|
||||
throw new MojoExecutionException( "Error executing instrumentation", t );
|
||||
}
|
||||
}
|
||||
|
||||
private Set collectFilesToProcess() {
|
||||
DirectoryScanner scanner = new DirectoryScanner();
|
||||
scanner.setBasedir( instrumentationDirectory );
|
||||
scanner.setIncludes( new String[] { "**/*.class" } );
|
||||
scanner.addDefaultExcludes();
|
||||
scanner.scan();
|
||||
String[] includedFiles = scanner.getIncludedFiles();
|
||||
HashSet fileSet = new HashSet( includedFiles.length + (int)(.75*includedFiles.length) + 1 );
|
||||
fileSet.addAll( Arrays.asList( includedFiles ) );
|
||||
return fileSet;
|
||||
}
|
||||
|
||||
private Instrumenter resolveInstrumenter(String provider, Logger logger) throws MojoExecutionException {
|
||||
if ( "javassist".equals( provider ) ) {
|
||||
return new JavassistInstrumenter( logger, this );
|
||||
}
|
||||
else if ( "cglib".equals( provider ) ) {
|
||||
return new CGLIBInstrumenter( logger, this );
|
||||
}
|
||||
else {
|
||||
throw new MojoExecutionException( "Unable to resolve provider [" + provider + "] to appropriate instrumenter" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the provider to use. Called in the cases where the user did not explicitly specify; so we look
|
||||
* through the dependencies for the project and decide which provider should be applied.
|
||||
* <p/>
|
||||
* NOTE: this impl prefers javassist.
|
||||
*
|
||||
* @return The provider determined from project's dependencies.
|
||||
*/
|
||||
private String determineProvider() {
|
||||
if ( project.getCompileArtifacts() != null ) {
|
||||
boolean foundCglib = false;
|
||||
Iterator itr = project.getCompileArtifacts().iterator();
|
||||
while ( itr.hasNext() ) {
|
||||
final Artifact artifact = ( Artifact ) itr.next();
|
||||
if ( "javassist".equals( artifact.getGroupId() ) && "javassist".equals( artifact.getArtifactId() ) ) {
|
||||
return "javassist";
|
||||
}
|
||||
else if ( "org.hibernate".equals( artifact.getGroupId() )
|
||||
&& "hibernate-cglib-repack".equals( artifact.getArtifactId() ) ) {
|
||||
foundCglib = true;
|
||||
}
|
||||
}
|
||||
if ( foundCglib ) {
|
||||
return "cglib";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private class LoggingBridge implements Logger {
|
||||
public void trace(String message) {
|
||||
getLog().debug( message );
|
||||
}
|
||||
|
||||
public void debug(String message) {
|
||||
getLog().debug( message );
|
||||
}
|
||||
|
||||
public void info(String message) {
|
||||
getLog().info( message );
|
||||
}
|
||||
|
||||
public void warn(String message) {
|
||||
getLog().warn( message );
|
||||
}
|
||||
|
||||
public void error(String message) {
|
||||
getLog().error( message );
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue