From 82320872895c0fb2d65e3ae17c1f05f3795a006b Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 3 Mar 2009 22:57:50 +0000 Subject: [PATCH] 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 --- .../buildtime/AbstractInstrumenter.java | 443 ++++++++++++++++++ .../bytecode/buildtime/CGLIBInstrumenter.java | 100 ++++ .../buildtime/ExecutionException.java | 43 ++ .../bytecode/buildtime/Instrumenter.java | 39 ++ .../buildtime/JavassistInstrumenter.java | 98 ++++ .../hibernate/bytecode/buildtime/Logger.java | 42 ++ .../instrument/BasicInstrumentationTask.java | 342 ++------------ .../tool/instrument/cglib/InstrumentTask.java | 72 +-- .../instrument/javassist/InstrumentTask.java | 62 +-- .../java/org/hibernate/util/StringHelper.java | 14 +- parent/pom.xml | 20 +- pom.xml | 3 +- 12 files changed, 835 insertions(+), 443 deletions(-) create mode 100644 core/src/main/java/org/hibernate/bytecode/buildtime/AbstractInstrumenter.java create mode 100644 core/src/main/java/org/hibernate/bytecode/buildtime/CGLIBInstrumenter.java create mode 100644 core/src/main/java/org/hibernate/bytecode/buildtime/ExecutionException.java create mode 100644 core/src/main/java/org/hibernate/bytecode/buildtime/Instrumenter.java create mode 100644 core/src/main/java/org/hibernate/bytecode/buildtime/JavassistInstrumenter.java create mode 100644 core/src/main/java/org/hibernate/bytecode/buildtime/Logger.java diff --git a/core/src/main/java/org/hibernate/bytecode/buildtime/AbstractInstrumenter.java b/core/src/main/java/org/hibernate/bytecode/buildtime/AbstractInstrumenter.java new file mode 100644 index 0000000000..0e7e4ecc95 --- /dev/null +++ b/core/src/main/java/org/hibernate/bytecode/buildtime/AbstractInstrumenter.java @@ -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. + *

+ * 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. + *

+ * 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(); + } + } + } +} diff --git a/core/src/main/java/org/hibernate/bytecode/buildtime/CGLIBInstrumenter.java b/core/src/main/java/org/hibernate/bytecode/buildtime/CGLIBInstrumenter.java new file mode 100644 index 0000000000..0abea984b0 --- /dev/null +++ b/core/src/main/java/org/hibernate/bytecode/buildtime/CGLIBInstrumenter.java @@ -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; + } + } + +} diff --git a/core/src/main/java/org/hibernate/bytecode/buildtime/ExecutionException.java b/core/src/main/java/org/hibernate/bytecode/buildtime/ExecutionException.java new file mode 100644 index 0000000000..014c3133f3 --- /dev/null +++ b/core/src/main/java/org/hibernate/bytecode/buildtime/ExecutionException.java @@ -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 ); + } +} diff --git a/core/src/main/java/org/hibernate/bytecode/buildtime/Instrumenter.java b/core/src/main/java/org/hibernate/bytecode/buildtime/Instrumenter.java new file mode 100644 index 0000000000..a0768d056a --- /dev/null +++ b/core/src/main/java/org/hibernate/bytecode/buildtime/Instrumenter.java @@ -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(); + } +} diff --git a/core/src/main/java/org/hibernate/bytecode/buildtime/JavassistInstrumenter.java b/core/src/main/java/org/hibernate/bytecode/buildtime/JavassistInstrumenter.java new file mode 100644 index 0000000000..1eea437450 --- /dev/null +++ b/core/src/main/java/org/hibernate/bytecode/buildtime/JavassistInstrumenter.java @@ -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; + } + } + +} diff --git a/core/src/main/java/org/hibernate/bytecode/buildtime/Logger.java b/core/src/main/java/org/hibernate/bytecode/buildtime/Logger.java new file mode 100644 index 0000000000..f2581394af --- /dev/null +++ b/core/src/main/java/org/hibernate/bytecode/buildtime/Logger.java @@ -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); +} diff --git a/core/src/main/java/org/hibernate/tool/instrument/BasicInstrumentationTask.java b/core/src/main/java/org/hibernate/tool/instrument/BasicInstrumentationTask.java index 1f2f4101ef..aae0d7d514 100644 --- a/core/src/main/java/org/hibernate/tool/instrument/BasicInstrumentationTask.java +++ b/core/src/main/java/org/hibernate/tool/instrument/BasicInstrumentationTask.java @@ -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 ); } } + } diff --git a/core/src/main/java/org/hibernate/tool/instrument/cglib/InstrumentTask.java b/core/src/main/java/org/hibernate/tool/instrument/cglib/InstrumentTask.java index 93c5dd24e6..cd850e9bca 100644 --- a/core/src/main/java/org/hibernate/tool/instrument/cglib/InstrumentTask.java +++ b/core/src/main/java/org/hibernate/tool/instrument/cglib/InstrumentTask.java @@ -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. *

* And then use it like:

- * 
+ * 
  *     
  *         
  *         ...
@@ -62,7 +55,7 @@ import org.hibernate.repackage.cglib.transform.impl.InterceptFieldEnabled;
  * 

* Optionally you can chose to enable "Extended Instrumentation" if desired * by specifying the extended attriubute on the task:

- * 
+ * 
  *     ...
  * 
  * 
@@ -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; - } - } - - } diff --git a/core/src/main/java/org/hibernate/tool/instrument/javassist/InstrumentTask.java b/core/src/main/java/org/hibernate/tool/instrument/javassist/InstrumentTask.java index e5d72c8964..b0c8bb70ef 100644 --- a/core/src/main/java/org/hibernate/tool/instrument/javassist/InstrumentTask.java +++ b/core/src/main/java/org/hibernate/tool/instrument/javassist/InstrumentTask.java @@ -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; - } - } - } diff --git a/core/src/main/java/org/hibernate/util/StringHelper.java b/core/src/main/java/org/hibernate/util/StringHelper.java index a158ac2cf1..93de983151 100644 --- a/core/src/main/java/org/hibernate/util/StringHelper.java +++ b/core/src/main/java/org/hibernate/util/StringHelper.java @@ -93,13 +93,16 @@ public final class StringHelper { public static String[] replace(String templates[], String placeholder, String replacement) { String[] result = new String[templates.length]; for ( int i =0; i enforce - - - - [1.5,) - - - (2.0.7,) - - - + + + + [1.5,) + + + (2.0.7,) + + + diff --git a/pom.xml b/pom.xml index 533339426f..a5c9d32c7a 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,8 @@ testing testsuite tutorials -