org.jenkinsci.maven.plugins.hpi.RunMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.jenkinsci.maven.plugins.hpi.RunMojo.java

Source

//========================================================================
//$Id: RunMojo.java 36037 2010-10-18 09:48:58Z kohsuke $
//Copyright 2000-2004 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//http://www.apache.org/licenses/LICENSE-2.0
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//========================================================================
package org.jenkinsci.maven.plugins.hpi;

import org.apache.commons.io.FileUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.eclipse.jetty.maven.plugin.JettyWebAppContext;
import org.eclipse.jetty.maven.plugin.MavenServerConnector;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.webapp.WebAppClassLoader;
import org.eclipse.jetty.webapp.WebAppContext;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.jar.JarFile;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Runs Jenkins with the current plugin project.
 *
 * <p>
 * This only needs the source files to be compiled, so run in the compile phase.
 * </p>
 *
 * <p>
 * To specify the HTTP port, use <tt>-Djetty.port=<i>PORT</i></tt>
 * </p>
 * 
 * @author Kohsuke Kawaguchi
 */
@Mojo(name = "run")
@Execute(phase = LifecyclePhase.COMPILE)
public class RunMojo extends AbstractJettyMojo {

    /**
     * The location of the war file.
     *
     * <p>
     * Normally this should be left empty, in which case the plugin loads it from the repository.
     * But this parameter allows that to be overwritten.
     * </p>
     */
    @Parameter
    private File webAppFile;

    /**
     * Path to {@code $JENKINS_HOME}. The launched Jenkins will use this directory as the workspace.
     */
    @Parameter(property = "hudsonHome", defaultValue = "${HUDSON_HOME}")
    private File hudsonHome;

    /**
     * Path to {@code $JENKINS_HOME}. The launched Jenkins will use this directory as the workspace.
     */
    @Parameter(property = "jenkinsHome", defaultValue = "${JENKINS_HOME}")
    private File jenkinsHome;

    /**
     * Decides the level of dependency resolution.
     *
     * This controls what plugins are made available to the
     * running Jenkins.
     */
    @Parameter(defaultValue = "test")
    protected String dependencyResolution;

    /**
     * Single directory for extra files to include in the WAR.
     */
    @Parameter(defaultValue = "${basedir}/src/main/webapp")
    protected File warSourceDirectory;

    @Component
    protected ArtifactResolver artifactResolver;

    @Component
    protected ArtifactFactory artifactFactory;

    @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true)
    protected List<ArtifactRepository> remoteRepos;

    @Parameter(defaultValue = "${localRepository}")
    protected ArtifactRepository localRepository;

    @Component
    protected ArtifactMetadataSource artifactMetadataSource;

    /**
     * Specifies the HTTP port number.
     *
     * If connectors are configured in the Mojo, that'll take precedence.
     */
    @Parameter(property = "port")
    protected int defaultPort;

    /**
     * If true, the context will be restarted after a line feed on
     * the input console. Disabled by default.
     */
    @Parameter(property = "jetty.consoleForceReload", defaultValue = "true")
    protected boolean consoleForceReload;

    @Component
    protected MavenProjectBuilder projectBuilder;

    /**
     * Optional string that represents "groupId:artifactId" of Jenkins core jar.
     * If left unspecified, the default groupId/artifactId pair for Jenkins is looked for.
     *
     * @since 1.65
     */
    @Parameter
    protected String jenkinsCoreId;

    /**
     * Optional string that represents "groupId:artifactId" of Jenkins war.
     * If left unspecified, the default groupId/artifactId pair for Jenkins is looked for.
     *
     * @since 1.68
     */
    @Parameter
    protected String jenkinsWarId;

    /**
     * [ws|tab|CR|LF]+ separated list of package prefixes that your plugin doesn't want to see
     * from the core.
     *
     * <p>
     * Tokens in this list is prefix-matched against the fully-qualified class name, so add
     * "." to the end of each package name, like "com.foo. com.bar."
     */
    @Parameter
    protected String maskClasses;

    /**
     * @since 1.94
     */
    @Parameter
    protected boolean pluginFirstClassLoader = false;

    /**
     * List of additional System properties to set
     *
     * @since 1.85
     */
    @Parameter
    private Map<String, String> systemProperties;

    /**
     * List of loggers to define.
     * Keys are logger names (usually package or class names);
     * values are level names (such as {@code FINE}).
     * @since 1.98
     */
    @Parameter
    private Map<String, String> loggers;

    private Collection<Logger> loggerReferences; // just to prevent GC

    /**
     * The context path for the webapp. Defaults to the
     * name of the webapp's artifact.
     *
     * @deprecated Use &lt;webApp&gt;&lt;contextPath&gt; instead.
     */
    @Parameter(readonly = true, required = true, defaultValue = "/${project.artifactId}")
    protected String contextPath;

    @Component
    protected PluginWorkspaceMap pluginWorkspaceMap;

    public void execute() throws MojoExecutionException, MojoFailureException {
        getProject().setArtifacts(resolveDependencies(dependencyResolution));

        File basedir = getProject().getBasedir();

        if (webApp == null || webApp.getContextPath() == null) {
            if (contextPath != null) {
                getLog().warn(
                        "Please use `webApp/contextPath` configuration parameter in place of the deprecated `contextPath` parameter");
                if (webApp == null) {
                    try {
                        webApp = new JettyWebAppContext();
                    } catch (Exception e) {
                        throw new MojoExecutionException("Failed to initialize webApp configuration", e);
                    }
                }
                webApp.setContextPath(contextPath);
            }
        }

        // compute jenkinsHome
        if (jenkinsHome == null) {
            if (hudsonHome != null) {
                getLog().warn(
                        "Please use the `jenkinsHome` configuration parameter in place of the deprecated `hudsonHome` parameter");
                jenkinsHome = hudsonHome;
            }
            String h = System.getenv("JENKINS_HOME");
            if (h == null) {
                h = System.getenv("HUDSON_HOME");
            }
            if (h != null)
                jenkinsHome = new File(h);
            else
                jenkinsHome = new File(basedir, "work");
        }

        // auto-enable stapler trace, unless otherwise configured already.
        setSystemPropertyIfEmpty("stapler.trace", "true");
        // allow Jetty to accept a bigger form so that it can handle update center JSON post
        setSystemPropertyIfEmpty("org.eclipse.jetty.Request.maxFormContentSize", "-1");
        // general-purpose system property so that we can tell from Jenkins if we are running in the hpi:run mode.
        setSystemPropertyIfEmpty("hudson.hpi.run", "true");
        // this adds 3 secs to the shutdown time. Skip it.
        setSystemPropertyIfEmpty("hudson.DNSMultiCast.disabled", "true");
        // expose the current top-directory of the plugin
        setSystemPropertyIfEmpty("jenkins.moduleRoot", basedir.getAbsolutePath());

        if (systemProperties != null && !systemProperties.isEmpty()) {
            for (Map.Entry<String, String> entry : systemProperties.entrySet()) {
                if (entry.getKey() != null && entry.getValue() != null) {
                    System.setProperty(entry.getKey(), entry.getValue());
                }
            }
        }

        // look for jenkins.war
        Artifacts jenkinsArtifacts = Artifacts.of(getProject())
                .groupIdIs("org.jenkins-ci.main", "org.jvnet.hudson.main").artifactIdIsNot("remoting"); // remoting moved to its own release cycle

        webAppFile = getJenkinsWarArtifact().getFile();

        // make sure all the relevant Jenkins artifacts have the same version
        for (Artifact a : jenkinsArtifacts) {
            Artifact ba = jenkinsArtifacts.get(0);
            if (!a.getVersion().equals(ba.getVersion()))
                throw new MojoExecutionException("Version of " + a.getId() + " is inconsistent with " + ba.getId());
        }

        // set JENKINS_HOME
        setSystemPropertyIfEmpty("JENKINS_HOME", jenkinsHome.getAbsolutePath());
        File pluginsDir = new File(jenkinsHome, "plugins");
        pluginsDir.mkdirs();

        // enable view auto refreshing via stapler
        setSystemPropertyIfEmpty("stapler.jelly.noCache", "true");

        List<Resource> res = getProject().getBuild().getResources();
        if (!res.isEmpty()) {
            // pick up the first one and use it
            Resource r = res.get(0);
            setSystemPropertyIfEmpty("stapler.resourcePath", r.getDirectory());
        }

        generateHpl();

        // copy other dependency Jenkins plugins
        try {
            for (MavenArtifact a : getProjectArtifacts()) {
                if (!a.isPlugin())
                    continue;

                // find corresponding .hpi file
                Artifact hpi = artifactFactory.createArtifact(a.getGroupId(), a.getArtifactId(), a.getVersion(),
                        null, "hpi");
                artifactResolver.resolve(hpi, getProject().getRemoteArtifactRepositories(), localRepository);

                // check recursive dependency. this is a rare case that happens when we split out some things from the core
                // into a plugin
                if (hasSameGavAsProject(hpi))
                    continue;

                if (hpi.getFile().isDirectory())
                    throw new UnsupportedOperationException(
                            hpi.getFile() + " is a directory and not packaged yet. this isn't supported");

                File upstreamHpl = pluginWorkspaceMap.read(hpi.getId());
                if (upstreamHpl != null) {
                    copyHpl(upstreamHpl, pluginsDir, a.getActualArtifactId());
                } else {
                    copyPlugin(hpi.getFile(), pluginsDir, a.getActualArtifactId());
                }
            }
        } catch (IOException e) {
            throw new MojoExecutionException("Unable to copy dependency plugin", e);
        } catch (ArtifactNotFoundException e) {
            throw new MojoExecutionException("Unable to copy dependency plugin", e);
        } catch (ArtifactResolutionException e) {
            throw new MojoExecutionException("Unable to copy dependency plugin", e);
        }

        if (loggers != null) {
            for (Handler h : LogManager.getLogManager().getLogger("").getHandlers()) {
                if (h instanceof ConsoleHandler) {
                    h.setLevel(Level.ALL);
                }
            }
            loggerReferences = new LinkedList<Logger>();
            for (Map.Entry<String, String> logger : loggers.entrySet()) {
                Logger l = Logger.getLogger(logger.getKey());
                loggerReferences.add(l);
                l.setLevel(Level.parse(logger.getValue()));
            }
        }

        super.execute();
    }

    private boolean hasSameGavAsProject(Artifact a) {
        return getProject().getGroupId().equals(a.getGroupId())
                && getProject().getArtifactId().equals(a.getArtifactId())
                && getProject().getVersion().equals(a.getVersion());
    }

    private void setSystemPropertyIfEmpty(String name, String value) {
        if (System.getProperty(name) == null)
            System.setProperty(name, value);
    }

    private void copyPlugin(File src, File pluginsDir, String shortName) throws IOException {
        File dst = new File(pluginsDir, shortName + ".jpi");
        File hpi = new File(pluginsDir, shortName + ".hpi");
        if (hpi.isFile()) {
            getLog().warn("Moving historical " + hpi + " to *.jpi");
            hpi.renameTo(dst);
        }
        if (versionOfPlugin(src).compareTo(versionOfPlugin(dst)) < 0) {
            getLog().warn("will not overwrite " + dst + " with " + src + " because it is newer");
            return;
        }
        getLog().info("Copying dependency Jenkins plugin " + src);
        FileUtils.copyFile(src, dst);
        // pin the dependency plugin, so that even if a different version of the same plugin is bundled to Jenkins,
        // we still use the plugin as specified by the POM of the plugin.
        FileUtils.writeStringToFile(new File(dst + ".pinned"), "pinned");
    }

    private VersionNumber versionOfPlugin(File p) throws IOException {
        if (!p.isFile()) {
            return new VersionNumber("0.0");
        }
        JarFile j = new JarFile(p);
        try {
            String v = j.getManifest().getMainAttributes().getValue("Plugin-Version");
            if (v == null) {
                throw new IOException("no Plugin-Version in " + p);
            }
            try {
                return new VersionNumber(v);
            } catch (IllegalArgumentException x) {
                throw new IOException("malformed Plugin-Version in " + p + ": " + x, x);
            }
        } finally {
            j.close();
        }
    }

    private void copyHpl(File src, File pluginsDir, String shortName) throws IOException {
        File dst = new File(pluginsDir, shortName + ".jpl");
        getLog().info("Copying snapshot dependency Jenkins plugin " + src);
        FileUtils.copyFile(src, dst);
        FileUtils.writeStringToFile(new File(pluginsDir, shortName + ".jpi.pinned"), "pinned");
    }

    /**
     * Create a dot-hpl file.
     *
     * <p>
     * All I want to do here is to invoke the hpl target.
     * there must be a better way to do this!
     * (jglick: perhaps https://github.com/TimMoore/mojo-executor would work?)
     *
     * <p>
     * Besides, if the user wants to change the plugin name, etc,
     * this forces them to do it in two places.
     */
    private void generateHpl() throws MojoExecutionException, MojoFailureException {
        HplMojo hpl = new HplMojo();
        hpl.project = getProject();
        hpl.setJenkinsHome(jenkinsHome);
        hpl.setLog(getLog());
        hpl.pluginName = getProject().getName();
        hpl.warSourceDirectory = warSourceDirectory;
        hpl.scopeFilter = new ScopeArtifactFilter("runtime");
        hpl.projectBuilder = this.projectBuilder;
        hpl.localRepository = this.localRepository;
        hpl.jenkinsCoreId = this.jenkinsCoreId;
        hpl.pluginFirstClassLoader = this.pluginFirstClassLoader;
        hpl.maskClasses = this.maskClasses;
        hpl.remoteRepos = this.remoteRepos;
        /* As needed:
        hpl.artifactFactory = this.artifactFactory;
        hpl.artifactResolver = this.artifactResolver;
        hpl.artifactMetadataSource = this.artifactMetadataSource;
        */
        hpl.execute();
    }

    public void configureWebApplication() throws Exception {
        // Jetty tries to do this in WebAppContext.resolveWebApp but it failed to delete the directory.
        File t = webApp.getTempDirectory();
        if (t == null)
            t = new File(getProject().getBuild().getDirectory(), "tmp");
        File extractedWebAppDir = new File(t, "webapp");
        if (isExtractedWebAppDirStale(extractedWebAppDir, webAppFile)) {
            FileUtils.deleteDirectory(extractedWebAppDir);
        }

        super.configureWebApplication();
        getWebAppConfig().setWar(webAppFile.getCanonicalPath());
        for (Artifact a : (Set<Artifact>) project.getArtifacts()) {
            if (a.getGroupId().equals("org.jenkins-ci.main") && a.getArtifactId().equals("jenkins-core")) {
                File coreBasedir = pluginWorkspaceMap.read(a.getId());
                if (coreBasedir != null) {
                    String extraCP = new File(coreBasedir, "src/main/resources").toURI() + ","
                            + new File(coreBasedir, "target/classes").toURI();
                    getLog().info("Will load directly from " + extraCP);
                    getWebAppConfig().setExtraClasspath(extraCP);
                }
            }
        }
        // cf. https://wiki.jenkins-ci.org/display/JENKINS/Jetty
        HashLoginService hashLoginService = (new HashLoginService("Jenkins Realm"));
        hashLoginService.setConfig(System.getProperty("jetty.home", "work") + "/etc/realm.properties");
        getWebAppConfig().getSecurityHandler().setLoginService(hashLoginService);
    }

    private static final String VERSION_PATH = "META-INF/maven/org.jenkins-ci.main/jenkins-war/pom.properties";
    private static final String VERSION_PROP = "version";

    private boolean isExtractedWebAppDirStale(File extractedWebAppDir, File webApp) throws IOException {
        if (!extractedWebAppDir.isDirectory()) {
            getLog().info(extractedWebAppDir + " does not yet exist, will receive " + webApp);
            return false;
        }
        if (extractedWebAppDir.lastModified() < webApp.lastModified()) {
            getLog().info(extractedWebAppDir + " is older than " + webApp + ", will recreate");
            return true;
        }
        File extractedPath = new File(extractedWebAppDir, VERSION_PATH);
        if (!extractedPath.isFile()) {
            getLog().warn("no such file " + extractedPath);
            return false;
        }
        InputStream is = new FileInputStream(extractedPath);
        String extractedVersion;
        try {
            extractedVersion = loadVersion(is);
        } finally {
            is.close();
        }
        if (extractedVersion == null) {
            getLog().warn("no " + VERSION_PROP + " in " + extractedPath);
            return false;
        }
        ZipFile zip = new ZipFile(webApp);
        String originalVersion;
        try {
            ZipEntry entry = zip.getEntry(VERSION_PATH);
            if (entry == null) {
                getLog().warn("no " + VERSION_PATH + " in " + webApp);
                return false;
            }
            is = zip.getInputStream(entry);
            try {
                originalVersion = loadVersion(is);
            } finally {
                is.close();
            }
        } finally {
            zip.close();
        }
        if (originalVersion == null) {
            getLog().warn("no " + VERSION_PROP + " in jar:" + webApp.toURI() + "!/" + VERSION_PATH);
            return false;
        }
        if (!extractedVersion.equals(originalVersion)) {
            getLog().info("Version " + extractedVersion + " in " + extractedWebAppDir + " does not match "
                    + originalVersion + " in " + webApp + ", will recreate");
            return true;
        }
        getLog().info(extractedWebAppDir + " already up to date with respect to " + webApp);
        return false;
    }

    private String loadVersion(InputStream is) throws IOException {
        Properties props = new Properties();
        props.load(is);
        return props.getProperty(VERSION_PROP);
    }

    public void configureScanner() throws MojoExecutionException {
        // use a bigger buffer as Stapler traces can get pretty large on deeply nested URL
        // this can only be done after server.start() is called, which happens in AbstractJettyMojo.startJetty()
        // and this configureScanner method is one of the few places that are run afterward.
        for (Connector con : server.getConnectors()) {
            for (ConnectionFactory cf : con.getConnectionFactories()) {
                if (cf instanceof HttpConnectionFactory) {
                    HttpConnectionFactory hcf = (HttpConnectionFactory) cf;
                    hcf.getHttpConfiguration().setResponseHeaderSize(12 * 1024);
                }
            }
        }

        setUpScanList();

        scannerListeners = new ArrayList<Scanner.BulkListener>();
        scannerListeners.add(new Scanner.BulkListener() {
            public void filesChanged(List<String> changes) {
                try {
                    restartWebApp(changes.contains(getProject().getFile().getCanonicalPath()));
                } catch (Exception e) {
                    getLog().error("Error reconfiguring/restarting webapp after change in watched files", e);
                }
            }
        });
    }

    @Override
    public void restartWebApp(boolean reconfigureScanner) throws Exception {
        getLog().info("restarting " + webApp);
        getLog().debug("Stopping webapp ...");
        webApp.stop();
        getLog().debug("Reconfiguring webapp ...");

        checkPomConfiguration();
        configureWebApplication();

        // check if we need to reconfigure the scanner,
        // which is if the pom changes
        if (reconfigureScanner) {
            getLog().info("Reconfiguring scanner after change to pom.xml ...");
            generateHpl(); // regenerate hpl if POM changes.

            scanList.clear();
            if (webApp.getDescriptor() != null)
                scanList.add(new File(webApp.getDescriptor()));
            if (webApp.getJettyEnvXml() != null)
                scanList.add(new File(webApp.getJettyEnvXml()));
            scanList.add(project.getFile());
            if (webApp.getTestClasses() != null)
                scanList.add(webApp.getTestClasses());
            if (webApp.getClasses() != null)
                scanList.add(webApp.getClasses());
            scanList.addAll(webApp.getWebInfLib());
            scanner.setScanDirs(scanList);
        }

        getLog().debug("Restarting webapp ...");
        webApp.start();
        getLog().info("Restart completed at " + new Date().toString());
    }

    private void setUpScanList() {
        scanList = new ArrayList<File>();
        scanList.add(getProject().getFile());
        scanList.add(webAppFile);
        scanList.add(new File(getProject().getBuild().getOutputDirectory()));
    }

    @Override
    protected void startConsoleScanner() throws Exception {
        if (consoleForceReload) {
            getLog().info("Console reloading is ENABLED. Hit ENTER on the console to restart the context.");
            consoleScanner = new ConsoleScanner(this);
            consoleScanner.start();
        }
    }

    public void checkPomConfiguration() throws MojoExecutionException {
    }

    public void finishConfigurationBeforeStart() throws Exception {
        super.finishConfigurationBeforeStart();
        WebAppContext wac = getWebAppConfig();
        wac.setAttribute("org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern", ".*/classes/.*");
        // to allow the development environment to run multiple "mvn hpi:run" with different port,
        // use different session cookie names. Otherwise they can mix up. See
        // http://stackoverflow.com/questions/1612177/are-http-cookies-port-specific
        wac.getSessionHandler().getSessionManager().getSessionCookieConfig()
                .setName("JSESSIONID." + UUID.randomUUID().toString().replace("-", "").substring(0, 8));

        try {
            // for Jenkins modules, swap the component from jenkins.war by target/classes
            // via classloader magic
            WebAppClassLoader wacl = new WebAppClassLoader(
                    new JettyAndServletApiOnlyClassLoader(null, getClass().getClassLoader()), wac) {
                private final Pattern exclusionPattern;
                {
                    if (getProject().getPackaging().equals("jenkins-module")) {
                        // classes compiled from jenkins module should behave as if it's a part of the core
                        // load resources from source folders directly
                        for (Resource r : (List<Resource>) getProject().getResources())
                            super.addURL(new File(r.getDirectory()).toURL());
                        super.addURL(new File(getProject().getBuild().getOutputDirectory()).toURL());

                        // add all the jar dependencies of the module
                        // "provided" includes all core and others, so drop them
                        // similarly, "test" would pull in all the harness
                        // pom dependency is sometimes used so that one can depend on its transitive dependencies
                        for (Artifact a : Artifacts.of(getProject()).scopeIsNot("provided", "test")
                                .typeIsNot("pom")) {
                            super.addURL(a.getFile().toURI().toURL());
                        }

                        exclusionPattern = Pattern.compile(
                                "[/\\\\]\\Q" + getProject().getArtifactId() + "\\E-[0-9]([^/\\\\]+)\\.jar$");
                    } else {
                        exclusionPattern = Pattern.compile("this should never match");
                    }
                }

                @Override
                public void addClassPath(String classPath) throws IOException {
                    if (exclusionPattern != null && exclusionPattern.matcher(classPath).find()) {
                        return;
                    }
                    super.addClassPath(classPath);
                }

                @Override
                public void addJars(org.eclipse.jetty.util.resource.Resource lib) {
                    super.addJars(lib);
                }
            };
            wac.setClassLoader(wacl);
        } catch (IOException e) {
            throw new Error(e);
        }
    }

    @Override
    public void startJetty() throws MojoExecutionException {
        if (httpConnector == null && defaultPort != 0) {
            httpConnector = new MavenServerConnector();
            httpConnector.setPort(defaultPort);
        }
        super.startJetty();
    }

    /**
     * Performs the equivalent of "@requireDependencyResolution" mojo attribute,
     * so that we can choose the scope at runtime.
     * @param scope
     */
    protected Set<Artifact> resolveDependencies(String scope) throws MojoExecutionException {
        try {
            ArtifactResolutionResult result = artifactResolver.resolveTransitively(
                    getProject().getDependencyArtifacts(), getProject().getArtifact(),
                    getProject().getManagedVersionMap(), localRepository,
                    getProject().getRemoteArtifactRepositories(), artifactMetadataSource,
                    new ScopeArtifactFilter(scope));
            return result.getArtifacts();
        } catch (ArtifactNotFoundException e) {
            throw new MojoExecutionException("Unable to copy dependency plugin", e);
        } catch (ArtifactResolutionException e) {
            throw new MojoExecutionException("Unable to copy dependency plugin", e);
        }
    }

    public Set<MavenArtifact> getProjectArtifacts() {
        Set<MavenArtifact> r = new HashSet<MavenArtifact>();
        for (Artifact a : (Collection<Artifact>) getProject().getArtifacts()) {
            r.add(wrap(a));
        }
        return r;
    }

    protected MavenArtifact wrap(Artifact a) {
        return new MavenArtifact(a, artifactResolver, artifactFactory, projectBuilder,
                getProject().getRemoteArtifactRepositories(), localRepository);
    }

    protected Artifact getJenkinsWarArtifact() throws MojoExecutionException {
        for (Artifact a : resolveDependencies("test")) {
            boolean match;
            if (jenkinsWarId != null)
                match = (a.getGroupId() + ':' + a.getArtifactId()).equals(jenkinsWarId);
            else
                match = (a.getArtifactId().equals("jenkins-war") || a.getArtifactId().equals("hudson-war"))
                        && a.getType().equals("war");
            if (match)
                return a;
        }

        if (jenkinsWarId != null) {
            getLog().error("Unable to locate jenkins.war in '" + jenkinsWarId + "'");
        } else {
            getLog().error("Unable to locate jenkins.war. Add the following dependency in your POM:\n" + "\n"
                    + "<dependency>\n" + "  <groupId>org.jenkins-ci.main</groupId>\n"
                    + "  <artifactId>jenkins-war</artifactId>\n" + "  <type>war</type>\n"
                    + "  <version>1.396<!-- replace this with the version you want--></version>\n"
                    + "  <scope>test</scope>\n" + "</dependency>");
        }
        throw new MojoExecutionException("Unable to find jenkins.war");
    }

    protected MavenProject getProject() {
        return project;
    }

    public WebAppContext getWebAppConfig() {
        return webApp;
    }
}