HHH-3279: create series of maven plugins offering functionality of the ant tools
git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@16064 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
parent
b0f07a6e10
commit
8232087289
|
@ -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.
|
* Free Software Foundation, Inc.
|
||||||
* 51 Franklin Street, Fifth Floor
|
* 51 Franklin Street, Fifth Floor
|
||||||
* Boston, MA 02110-1301 USA
|
* Boston, MA 02110-1301 USA
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
package org.hibernate.tool.instrument;
|
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.Project;
|
||||||
import org.apache.tools.ant.DirectoryScanner;
|
import org.apache.tools.ant.DirectoryScanner;
|
||||||
import org.apache.tools.ant.types.FileSet;
|
import org.apache.tools.ant.types.FileSet;
|
||||||
import org.hibernate.bytecode.util.ClassDescriptor;
|
|
||||||
import org.hibernate.bytecode.util.ByteCodeHelper;
|
import org.hibernate.bytecode.buildtime.Instrumenter;
|
||||||
import org.hibernate.bytecode.util.FieldFilter;
|
import org.hibernate.bytecode.buildtime.Logger;
|
||||||
import org.hibernate.bytecode.ClassTransformer;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.HashSet;
|
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.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
|
* Super class for all Hibernate instrumentation tasks. Provides the basic templating of how instrumentation
|
||||||
* templating of how instrumentation should occur.
|
* should occur; subclasses simply plug in to that process appropriately for the given bytecode provider.
|
||||||
*
|
*
|
||||||
* @author Steve Ebersole
|
* @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 final LoggerBridge logger = new LoggerBridge();
|
||||||
private static final int CLASS_MAGIC = 0xCAFEBABE;
|
|
||||||
|
|
||||||
protected final Logger logger = new Logger();
|
|
||||||
private List filesets = new ArrayList();
|
private List filesets = new ArrayList();
|
||||||
private Set classNames = new HashSet();
|
|
||||||
private boolean extended;
|
private boolean extended;
|
||||||
|
|
||||||
|
// deprecated option...
|
||||||
private boolean verbose;
|
private boolean verbose;
|
||||||
|
|
||||||
public void addFileset(FileSet set) {
|
public void addFileset(FileSet set) {
|
||||||
|
@ -92,273 +79,40 @@ public abstract class BasicInstrumentationTask extends Task {
|
||||||
this.verbose = verbose;
|
this.verbose = verbose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final boolean performExtendedInstrumentation() {
|
||||||
|
return isExtended();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Instrumenter buildInstrumenter(Logger logger, Instrumenter.Options options);
|
||||||
|
|
||||||
public void execute() throws BuildException {
|
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 {
|
try {
|
||||||
out.write( transformedBytes );
|
buildInstrumenter( logger, this )
|
||||||
out.flush();
|
.execute( collectSpecifiedFiles() );
|
||||||
}
|
}
|
||||||
finally {
|
catch ( Throwable t ) {
|
||||||
try {
|
throw new BuildException( t );
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
catch ( IOException ignore) {
|
|
||||||
// intentionally empty
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void processJarFile(final File file) throws Exception {
|
private Set collectSpecifiedFiles() {
|
||||||
logger.verbose( "starting jar file : " + file.toURL() );
|
HashSet files = new HashSet();
|
||||||
|
Project project = getProject();
|
||||||
File tempFile = File.createTempFile(
|
Iterator filesets = filesets();
|
||||||
file.getName(),
|
while ( filesets.hasNext() ) {
|
||||||
null,
|
FileSet fs = ( FileSet ) filesets.next();
|
||||||
new File( file.getAbsoluteFile().getParent() )
|
DirectoryScanner ds = fs.getDirectoryScanner( project );
|
||||||
);
|
String[] includedFiles = ds.getIncludedFiles();
|
||||||
|
File d = fs.getDir( project );
|
||||||
try {
|
for ( int i = 0; i < includedFiles.length; ++i ) {
|
||||||
FileOutputStream fout = new FileOutputStream( tempFile, false );
|
files.add( new File( d, includedFiles[i] ) );
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class Logger {
|
protected class LoggerBridge implements Logger {
|
||||||
public void verbose(String message) {
|
public void trace(String message) {
|
||||||
if ( verbose ) {
|
|
||||||
System.out.println( message );
|
|
||||||
}
|
|
||||||
log( message, Project.MSG_VERBOSE );
|
log( message, Project.MSG_VERBOSE );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,34 +127,10 @@ public abstract class BasicInstrumentationTask extends Task {
|
||||||
public void warn(String message) {
|
public void warn(String message) {
|
||||||
log( message, Project.MSG_WARN );
|
log( message, Project.MSG_WARN );
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
public void error(String message) {
|
||||||
private static interface ZipEntryHandler {
|
log( message, Project.MSG_ERR );
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,17 +24,10 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.tool.instrument.cglib;
|
package org.hibernate.tool.instrument.cglib;
|
||||||
|
|
||||||
import org.hibernate.bytecode.util.BasicClassFilter;
|
import org.hibernate.bytecode.buildtime.CGLIBInstrumenter;
|
||||||
import org.hibernate.bytecode.util.ClassDescriptor;
|
import org.hibernate.bytecode.buildtime.Instrumenter;
|
||||||
import org.hibernate.bytecode.cglib.BytecodeProviderImpl;
|
import org.hibernate.bytecode.buildtime.Logger;
|
||||||
import org.hibernate.bytecode.ClassTransformer;
|
|
||||||
import org.hibernate.tool.instrument.BasicInstrumentationTask;
|
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
|
* 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.
|
* required Hibernate and CGLIB libraries.
|
||||||
* <p/>
|
* <p/>
|
||||||
* And then use it like:<pre>
|
* And then use it like:<pre>
|
||||||
* <instrument verbose="true">
|
* <instrument>
|
||||||
* <fileset dir="${testclasses.dir}/org/hibernate/test">
|
* <fileset dir="${testclasses.dir}/org/hibernate/test">
|
||||||
* <include name="yadda/yadda/**"/>
|
* <include name="yadda/yadda/**"/>
|
||||||
* ...
|
* ...
|
||||||
|
@ -62,7 +55,7 @@ import org.hibernate.repackage.cglib.transform.impl.InterceptFieldEnabled;
|
||||||
* <p/>
|
* <p/>
|
||||||
* Optionally you can chose to enable "Extended Instrumentation" if desired
|
* Optionally you can chose to enable "Extended Instrumentation" if desired
|
||||||
* by specifying the extended attriubute on the task:<pre>
|
* by specifying the extended attriubute on the task:<pre>
|
||||||
* <instrument verbose="true" extended="true">
|
* <instrument extended="true">
|
||||||
* ...
|
* ...
|
||||||
* </instrument>
|
* </instrument>
|
||||||
* </pre>
|
* </pre>
|
||||||
|
@ -72,58 +65,7 @@ import org.hibernate.repackage.cglib.transform.impl.InterceptFieldEnabled;
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public class InstrumentTask extends BasicInstrumentationTask {
|
public class InstrumentTask extends BasicInstrumentationTask {
|
||||||
|
protected Instrumenter buildInstrumenter(Logger logger, Instrumenter.Options options) {
|
||||||
private static final BasicClassFilter CLASS_FILTER = new BasicClassFilter();
|
return new CGLIBInstrumenter( logger, options );
|
||||||
|
|
||||||
private final BytecodeProviderImpl provider = new BytecodeProviderImpl();
|
|
||||||
|
|
||||||
|
|
||||||
protected ClassDescriptor getClassDescriptor(byte[] byecode) throws Exception {
|
|
||||||
return new CustomClassDescriptor( byecode );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
package org.hibernate.tool.instrument.javassist;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
import org.hibernate.bytecode.buildtime.Instrumenter;
|
||||||
import java.io.IOException;
|
import org.hibernate.bytecode.buildtime.JavassistInstrumenter;
|
||||||
import java.io.ByteArrayInputStream;
|
import org.hibernate.bytecode.buildtime.Logger;
|
||||||
|
|
||||||
import javassist.bytecode.ClassFile;
|
|
||||||
|
|
||||||
import org.hibernate.tool.instrument.BasicInstrumentationTask;
|
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
|
* An Ant task for instrumenting persistent classes in order to enable
|
||||||
|
@ -73,51 +65,7 @@ import org.hibernate.bytecode.javassist.FieldHandled;
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public class InstrumentTask extends BasicInstrumentationTask {
|
public class InstrumentTask extends BasicInstrumentationTask {
|
||||||
|
protected Instrumenter buildInstrumenter(Logger logger, Instrumenter.Options options) {
|
||||||
private static final BasicClassFilter CLASS_FILTER = new BasicClassFilter();
|
return new JavassistInstrumenter( logger, options );
|
||||||
|
|
||||||
private final BytecodeProviderImpl provider = new BytecodeProviderImpl();
|
|
||||||
|
|
||||||
protected ClassDescriptor getClassDescriptor(byte[] bytecode) throws IOException {
|
|
||||||
return new CustomClassDescriptor( bytecode );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,13 +93,16 @@ public final class StringHelper {
|
||||||
public static String[] replace(String templates[], String placeholder, String replacement) {
|
public static String[] replace(String templates[], String placeholder, String replacement) {
|
||||||
String[] result = new String[templates.length];
|
String[] result = new String[templates.length];
|
||||||
for ( int i =0; i<templates.length; i++ ) {
|
for ( int i =0; i<templates.length; i++ ) {
|
||||||
result[i] = replace( templates[i], placeholder, replacement );;
|
result[i] = replace( templates[i], placeholder, replacement );
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String replace(String template, String placeholder, String replacement, boolean wholeWords) {
|
public static String replace(String template, String placeholder, String replacement, boolean wholeWords) {
|
||||||
int loc = template == null ? -1 : template.indexOf( placeholder );
|
if ( template == null ) {
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
int loc = template.indexOf( placeholder );
|
||||||
if ( loc < 0 ) {
|
if ( loc < 0 ) {
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
@ -119,6 +122,9 @@ public final class StringHelper {
|
||||||
|
|
||||||
|
|
||||||
public static String replaceOnce(String template, String placeholder, String replacement) {
|
public static String replaceOnce(String template, String placeholder, String replacement) {
|
||||||
|
if ( template == null ) {
|
||||||
|
return template; // returnign null!
|
||||||
|
}
|
||||||
int loc = template == null ? -1 : template.indexOf( placeholder );
|
int loc = template == null ? -1 : template.indexOf( placeholder );
|
||||||
if ( loc < 0 ) {
|
if ( loc < 0 ) {
|
||||||
return template;
|
return template;
|
||||||
|
@ -435,12 +441,12 @@ public final class StringHelper {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return new StringBuilder( name.length() + 2 ).append('`').append( name ).append( '`' ).toString();
|
return new StringBuffer( name.length() + 2 ).append('`').append( name ).append( '`' ).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the unquoted version of name (stripping the start and end '`' chanracters if present).
|
* Return the unquoted version of name (stripping the start and end '`' characters if present).
|
||||||
*
|
*
|
||||||
* @param name The name to be unquoted.
|
* @param name The name to be unquoted.
|
||||||
* @return The unquoted version.
|
* @return The unquoted version.
|
||||||
|
|
|
@ -122,18 +122,18 @@
|
||||||
<goals>
|
<goals>
|
||||||
<goal>enforce</goal>
|
<goal>enforce</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
|
||||||
<rules>
|
|
||||||
<requireJavaVersion>
|
|
||||||
<version>[1.5,)</version>
|
|
||||||
</requireJavaVersion>
|
|
||||||
<requireMavenVersion>
|
|
||||||
<version>(2.0.7,)</version>
|
|
||||||
</requireMavenVersion>
|
|
||||||
</rules>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<rules>
|
||||||
|
<requireJavaVersion>
|
||||||
|
<version>[1.5,)</version>
|
||||||
|
</requireJavaVersion>
|
||||||
|
<requireMavenVersion>
|
||||||
|
<version>(2.0.7,)</version>
|
||||||
|
</requireMavenVersion>
|
||||||
|
</rules>
|
||||||
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<!-- by default, compile to JDK 1.4 compatibility (individual modules and/or user can override) -->
|
<!-- by default, compile to JDK 1.4 compatibility (individual modules and/or user can override) -->
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|
3
pom.xml
3
pom.xml
|
@ -62,7 +62,8 @@
|
||||||
<module>testing</module>
|
<module>testing</module>
|
||||||
<module>testsuite</module>
|
<module>testsuite</module>
|
||||||
<module>tutorials</module>
|
<module>tutorials</module>
|
||||||
<!--
|
<module>hibernate-maven-plugin</module>
|
||||||
|
<!--
|
||||||
Need to scope bytecode providers first...
|
Need to scope bytecode providers first...
|
||||||
<module>bytecode-cglib</module>
|
<module>bytecode-cglib</module>
|
||||||
<module>bytecode-javassist</module>
|
<module>bytecode-javassist</module>
|
||||||
|
|
Loading…
Reference in New Issue