/* * Copyright (c) 2013 Hudson. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Hudson - initial API and implementation and/or initial documentation */ package org.jvnet.hudson.test; import hudson.AbortException; import hudson.maven.MavenEmbedder; import hudson.maven.MavenEmbedderException; import hudson.maven.MavenRequest; import hudson.model.TaskListener; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.util.Enumeration; import java.util.Properties; import java.util.logging.Logger; import org.apache.commons.io.IOUtils; import java.io.PrintStream; import java.util.StringTokenizer; import org.apache.maven.model.building.ModelBuildingRequest; import org.apache.maven.cli.MavenLoggerManager; import org.codehaus.plexus.logging.console.ConsoleLogger; /** * Just enough of MavenUtil from legacy-maven to fix test harness bug * https://bugs.eclipse.org/bugs/show_bug.cgi?id=403703 * * TODO Move class back to Hudson? * * @author Bob Foster */ public class MavenUtil { /** * Create MavenRequest given only a TaskListener. Used by HudsonTestCase. * * @param listener * @return MavenRequest * @throws MavenEmbedderException * @throws IOException */ public static MavenRequest createMavenRequest(TaskListener listener) throws MavenEmbedderException, IOException { Properties systemProperties = new Properties(); MavenRequest mavenRequest = new MavenRequest(); // make sure ~/.m2 exists to avoid http://www.nabble.com/BUG-Report-tf3401736.html File m2Home = new File(MavenEmbedder.userHome, ".m2"); m2Home.mkdirs(); if(!m2Home.exists()) throw new AbortException("Failed to create "+m2Home); mavenRequest.setUserSettingsFile( new File( m2Home, "settings.xml" ).getAbsolutePath() ); mavenRequest.setGlobalSettingsFile( new File( "conf/settings.xml" ).getAbsolutePath() ); mavenRequest.setUpdateSnapshots(false); // TODO olamy check this sould be userProperties mavenRequest.setSystemProperties(systemProperties); EmbedderLoggerImpl logger = new EmbedderLoggerImpl( listener, debugMavenEmbedder ? org.codehaus.plexus.logging.Logger.LEVEL_DEBUG : org.codehaus.plexus.logging.Logger.LEVEL_INFO ); mavenRequest.setMavenLoggerManager( logger ); ClassLoader mavenEmbedderClassLoader = new MaskingClassLoader( MavenUtil.class.getClassLoader() ); {// are we loading the right components.xml? (and not from Maven that's running Jetty, if we are running in "mvn hudson-dev:run" or "mvn hpi:run"? Enumeration<URL> e = mavenEmbedderClassLoader.getResources("META-INF/plexus/components.xml"); while (e.hasMoreElements()) { URL url = e.nextElement(); LOGGER.fine("components.xml from "+url); } } mavenRequest.setProcessPlugins( false ); mavenRequest.setResolveDependencies( false ); mavenRequest.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 ); return mavenRequest; } /** * {@link MavenEmbedderLogger} implementation that * sends output to {@link TaskListener}. * * @author Kohsuke Kawaguchi */ private static final class EmbedderLoggerImpl extends MavenLoggerManager { private final PrintStream logger; public EmbedderLoggerImpl(TaskListener listener, int threshold) { super(new ConsoleLogger( threshold, "hudson-logger" )); logger = listener.getLogger(); } private void print(String message, Throwable throwable, int threshold, String prefix) { if (getThreshold() <= threshold) { StringTokenizer tokens = new StringTokenizer(message,"\n"); while(tokens.hasMoreTokens()) { logger.print(prefix); logger.println(tokens.nextToken()); } if (throwable!=null) throwable.printStackTrace(logger); } } public void debug(String message, Throwable throwable) { print(message, throwable, org.codehaus.plexus.logging.Logger.LEVEL_DEBUG, "[DEBUG] "); } public void info(String message, Throwable throwable) { print(message, throwable, org.codehaus.plexus.logging.Logger.LEVEL_INFO, "[INFO ] "); } public void warn(String message, Throwable throwable) { print(message, throwable, org.codehaus.plexus.logging.Logger.LEVEL_WARN, "[WARN ] "); } public void error(String message, Throwable throwable) { print(message, throwable, org.codehaus.plexus.logging.Logger.LEVEL_ERROR, "[ERROR] "); } public void fatalError(String message, Throwable throwable) { print(message, throwable, org.codehaus.plexus.logging.Logger.LEVEL_FATAL, "[FATAL] "); } } /** * When we run in Jetty during development, embedded Maven will end up * seeing some of the Maven class visible through Jetty, and this confuses it. * * <p> * Specifically, embedded Maven will find all the component descriptors * visible through Jetty, yet when it comes to loading classes, classworlds * still load classes from local realms created inside embedder. * * <p> * This classloader prevents this issue by hiding the component descriptor * visible through Jetty. */ private static final class MaskingClassLoader extends ClassLoader { public MaskingClassLoader(ClassLoader parent) { super(parent); } public Enumeration<URL> getResources(String name) throws IOException { final Enumeration<URL> e = super.getResources(name); return new Enumeration<URL>() { URL next; public boolean hasMoreElements() { fetch(); return next!=null; } public URL nextElement() { fetch(); URL r = next; next = null; return r; } private void fetch() { while(next==null && e.hasMoreElements()) { next = e.nextElement(); if(shouldBeIgnored(next)) next = null; } } private boolean shouldBeIgnored(URL url) { String s = url.toExternalForm(); if(s.contains("maven-plugin-tools-api")) return true; // because RemoteClassLoader mangles the path, we can't check for plexus/components.xml, // which would have otherwise made the test cheaper. if(s.endsWith("components.xml")) { BufferedReader r=null; try { // is this designated for interception purpose? If so, don't load them in the MavenEmbedder // earlier I tried to use a marker file in the same directory, but that won't work r = new BufferedReader(new InputStreamReader(url.openStream())); for (int i=0; i<2; i++) { String l = r.readLine(); if(l!=null && l.contains("MAVEN-INTERCEPTION-TO-BE-MASKED")) return true; } } catch (IOException _) { // let whoever requesting this resource re-discover an error and report it } finally { IOUtils.closeQuietly(r); } } return false; } }; } } /** * If set to true, maximize the logging level of Maven embedder. */ public static boolean debugMavenEmbedder = Boolean.getBoolean( "debugMavenEmbedder" ); private static final Logger LOGGER = Logger.getLogger(MavenUtil.class.getName()); }