javassist-maven-plugin
javassist-maven-plugin copied to clipboard
Exception in transformation should stop build
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()));
}
}