All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.bytecode.buildtime.spi.AbstractInstrumenter Maven / Gradle / Ivy

There is a newer version: 7.0.0.Alpha1
Show newest version
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2009-2011, Red Hat Inc. or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Inc.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.hibernate.bytecode.buildtime.spi;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.hibernate.bytecode.spi.ByteCodeHelper;
import org.hibernate.bytecode.spi.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) {
		final Set classNames = new HashSet();

		if ( options.performExtendedInstrumentation() ) {
			logger.debug( "collecting class names for extended instrumentation determination" );
			try {
				for ( Object file1 : files ) {
					final File file = (File) file1;
					collectClassNames( file, classNames );
				}
			}
			catch ( ExecutionException ee ) {
				throw ee;
			}
			catch ( Exception e ) {
				throw new ExecutionException( e );
			}
		}

		logger.info( "starting instrumentation" );
		try {
			for ( File file : files ) {
				processFile( file, classNames );
			}
		}
		catch ( ExecutionException ee ) {
			throw ee;
		}
		catch ( Exception e ) {
			throw new ExecutionException( e );
		}
	}

	/**
	 * Extract the names of classes from file, adding 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 ) ) { final byte[] bytes = ByteCodeHelper.readByteCode( file ); final ClassDescriptor descriptor = getClassDescriptor( bytes ); classNames.add( descriptor.getName() ); } else if ( isJarFile( file ) ) { final ZipEntryHandler collector = new ZipEntryHandler() { public void handleEntry(ZipEntry entry, byte[] byteCode) throws Exception { if ( !entry.isDirectory() ) { // see if the entry represents a class file final DataInputStream din = new DataInputStream( new ByteArrayInputStream( byteCode ) ); if ( din.readInt() == CLASS_MAGIC ) { classNames.add( getClassDescriptor( byteCode ).getName() ); } } } }; final 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 { final DataInputStream in = new DataInputStream( new FileInputStream( file ) ); try { final 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 org.hibernate.bytecode.buildtime.spi.Instrumenter.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 org.hibernate.bytecode.buildtime.spi.Instrumenter.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 { final byte[] bytes = ByteCodeHelper.readByteCode( file ); final ClassDescriptor descriptor = getClassDescriptor( bytes ); final 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() ); final byte[] transformedBytes = transformer.transform( getClass().getClassLoader(), descriptor.getName(), null, null, descriptor.getBytes() ); final 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 org.hibernate.bytecode.buildtime.spi.Instrumenter.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 { final File tempFile = File.createTempFile( file.getName(), null, new File( file.getAbsoluteFile().getParent() ) ); try { final FileOutputStream fout = new FileOutputStream( tempFile, false ); try { final ZipOutputStream out = new ZipOutputStream( fout ); final 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 final DataInputStream din = new DataInputStream( new ByteArrayInputStream( byteCode ) ); if ( din.readInt() == CLASS_MAGIC ) { final ClassDescriptor descriptor = getClassDescriptor( byteCode ); final 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() ); } } final ZipEntry outEntry = new ZipEntry( entry.getName() ); outEntry.setMethod( entry.getMethod() ); outEntry.setComment( entry.getComment() ); outEntry.setSize( byteCode.length ); if ( outEntry.getMethod() == ZipEntry.STORED ){ final CRC32 crc = new CRC32(); crc.update( byteCode ); outEntry.setCrc( crc.getValue() ); outEntry.setCompressedSize( byteCode.length ); } out.putNextEntry( outEntry ); out.write( byteCode ); out.closeEntry(); } }; final ZipFileProcessor processor = new ZipFileProcessor( transformer ); processor.process( file ); out.close(); } finally{ fout.close(); } if ( file.delete() ) { final 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 exactly 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 The bytes making up the entry * * @throws Exception Problem handling entry */ 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 { final ZipInputStream zip = new ZipInputStream( new FileInputStream( file ) ); try { ZipEntry entry; while ( (entry = zip.getNextEntry()) != null ) { final byte[] bytes = ByteCodeHelper.readByteCode( zip ); entryHandler.handleEntry( entry, bytes ); zip.closeEntry(); } } finally { zip.close(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy