javassist-maven-plugin icon indicating copy to clipboard operation
javassist-maven-plugin copied to clipboard

Exception in transformation should stop build

Open OpenHelios opened this issue 10 years ago • 0 comments

It would be great to stop the build, if the transformation of a class file fails. This is not possible in the current implementation. In a further step, javassist-maven-plugin could be configured for stopping on exceptions, or not.

For a work around to stop build on exceptions, I have created my own abstract class, which inherits from ClassTransformer. It can be used by extending AbstractClassTransformer instead of ClassTransformer.

package de.icongmbh.oss.maven.plugin.javassist;

import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Iterator;

import org.apache.maven.MavenExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
import javassist.bytecode.ClassFile;

public abstract class AbstractClassTransformer extends ClassTransformer {

private static final Logger LOG = LoggerFactory.getLogger(AbstractClassTransformer.class);

@Override
public void transform(final String inputDir, final String outputDir) {
    if (null == inputDir || inputDir.trim().isEmpty()) {
        return;
    }
    final String outDirectory = outputDir != null
            && !outputDir.trim().isEmpty() ? outputDir : inputDir;
    try {
        transformWithBuildErrors(inputDir, outDirectory,
                iterateClassnames(inputDir));
    } catch (final Exception e) {
        throw new RuntimeException(e);
    }
}

/**
 * Use the passed className iterator, load each one as {@link CtClass},
 * filter the valid candidates (using {@link #filter(CtClass)}) and apply
 * transformation to each one ({@link #applyTransformations(CtClass)}).
 * <ol>
 * <li><strong>Limitation:</strong> do not search inside .jar files yet.</li>
 * <li>This implementation is the same as the original method
 * {@link #transform(String, String, Iterator)} except, that exceptions can
 * be thrown by the called abstract method
 * {@link #applyTransformations(CtClass)} to stop the Maven build, if the
 * transformation fails</li>
 * </ol>
 * 
 * @param inputDir
 *            root directory - required - if <code>null</code> or empty
 *            nothing will be transformed
 * @param outputDir
 *            must be not <code>null</code>
 * @throws Exception
 * @see #applyTransformations(CtClass)
 */
public void transformWithBuildErrors(final String inputDir,
        final String outputDir, final Iterator<String> classNames) throws Exception {
    if (null == classNames || !classNames.hasNext()) {
        return;
    }
    // create new class pool for transform; don't blow up the default
    final ClassPool classPool = new ClassPool(ClassPool.getDefault());
    classPool.childFirstLookup = true;
    classPool.appendClassPath(inputDir);
    classPool.appendClassPath(new LoaderClassPath(Thread
            .currentThread().getContextClassLoader()));
    classPool.appendSystemPath();
    debugClassLoader(classPool);
    int i = 0;
    while (classNames.hasNext()) {
        final String className = classNames.next();
        if (null == className) {
            continue;
        }
        try {
            LOG.debug("Got class name {}", className);
            classPool.importPackage(className);
            final CtClass candidateClass = classPool.get(className);
            initializeClass(candidateClass);
            if (!hasStamp(candidateClass)
                    && shouldTransform(candidateClass)) {
                applyTransformations(candidateClass);
                applyStamp(candidateClass);
                candidateClass.writeFile(outputDir);
                LOG.debug("Class {} instrumented by {}", className,
                        getClass().getName());
                ++i;
            }
        } catch (final NotFoundException e) {
            throw new MavenExecutionException(String.format("Class %s has not been found on class path.", className), e);
        } catch (final Exception ex) {
            throw new MavenExecutionException(String.format("Instrumenting class %s failed.", className), ex);
        }
    }
    LOG.info("#{} classes instrumented by {}", i, getClass()
            .getName());
}

private void initializeClass(final CtClass candidateClass)
        throws NotFoundException {
    debugClassFile(candidateClass.getClassFile2());
    // TODO hack to initialize class to avoid further NotFoundException
    // (what's the right way of doing this?)
    candidateClass.subtypeOf(ClassPool.getDefault().get(
            Object.class.getName()));
}

private void debugClassLoader(final ClassPool classPool) {
    if (!LOG.isDebugEnabled()) {
        return;
    }
    LOG.debug(" - classPool: {}", classPool.toString());
    ClassLoader classLoader = classPool.getClassLoader();
    while (classLoader != null) {
        LOG.debug(" -- {}: {}", classLoader.getClass().getName(),
                classLoader.toString());
        if (classLoader instanceof URLClassLoader) {
            LOG.debug(" --- urls: {}", Arrays
                    .deepToString(((URLClassLoader) classLoader).getURLs()));
        }
        classLoader = classLoader.getParent();
    }
}

private void debugClassFile(final ClassFile classFile) {
    if (!LOG.isDebugEnabled()) {
        return;
    }
    LOG.debug(" - class: {}", classFile.getName());
    LOG.debug(" -- Java version: {}.{}", classFile.getMajorVersion(),
            classFile.getMinorVersion());
    LOG.debug(" -- interface: {} abstract: {} final: {}",
            classFile.isInterface(), classFile.isAbstract(),
            classFile.isFinal());
    LOG.debug(" -- extends class: {}", classFile.getSuperclass());
    LOG.debug(" -- implements interfaces: {}",
            Arrays.deepToString(classFile.getInterfaces()));
}

}

OpenHelios avatar Oct 23 '14 22:10 OpenHelios