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:
Steve Ebersole 2009-03-04 02:20:50 +00:00
parent bb0e94756a
commit ccc2571e2b
12 changed files with 1049 additions and 429 deletions

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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 );
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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 );
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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 );
}
}
}

View File

@ -57,7 +57,8 @@
<module>testing</module>
<module>testsuite</module>
<module>tutorials</module>
<!--
<module>hibernate-maven-plugin</module>
<!--
Need to scope bytecode providers first...
<module>bytecode-cglib</module>
<module>bytecode-javassist</module>