org.sonatype.plugin.nexus.testenvironment.AbstractEnvironmentMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.sonatype.plugin.nexus.testenvironment.AbstractEnvironmentMojo.java

Source

/*
 * Sonatype Nexus (TM) Open Source Version
 * Copyright (c) 2007-2012 Sonatype, Inc.
 * All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
 *
 * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
 * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
 *
 * Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
 * of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
 * Eclipse Foundation. All other trademarks are the property of their respective owners.
 */
package org.sonatype.plugin.nexus.testenvironment;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
import org.apache.maven.shared.artifact.filter.collection.ArtifactIdFilter;
import org.apache.maven.shared.artifact.filter.collection.ClassifierFilter;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.apache.maven.shared.artifact.filter.collection.GroupIdFilter;
import org.apache.maven.shared.artifact.filter.collection.TypeFilter;
import org.apache.maven.shared.filtering.MavenFileFilter;
import org.apache.maven.shared.filtering.MavenFilteringException;
import org.codehaus.plexus.PlexusConstants;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.context.Context;
import org.codehaus.plexus.context.ContextException;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.io.RawInputStreamFacade;
import org.sonatype.plugin.nexus.testenvironment.filter.TestScopeFilter;
import org.sonatype.plugins.portallocator.Port;
import org.sonatype.plugins.portallocator.PortAllocatorMojo;

import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.MapConstraints;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class AbstractEnvironmentMojo extends AbstractMojo implements Contextualizable {

    public final static List<String> DEFAULT_PORT_NAMES = Collections.unmodifiableList(Arrays.asList(new String[] {
            "proxy-repo-port", "proxy-repo-control-port", "nexus-application-port", "nexus-proxy-port",
            "nexus-control-port", "email-server-port", "webproxy-server-port", "jira-server-port" }));

    /**
     * Max times to try and allocate unique port values
     */
    public static final int MAX_PORT_ALLOCATION_RETRY = 3;

    protected static final String PROP_NEXUS_BASE_DIR = "nexus-base-dir";

    /** @component */
    protected org.apache.maven.artifact.factory.ArtifactFactory artifactFactory;

    /** @component */
    private org.apache.maven.artifact.resolver.ArtifactResolver resolver;

    /** @parameter expression="${localRepository}" */
    protected org.apache.maven.artifact.repository.ArtifactRepository localRepository;

    /** @parameter expression="${project.remoteArtifactRepositories}" */
    protected java.util.List<ArtifactRepository> remoteRepositories;

    /** @component */
    private MavenFileFilter mavenFileFilter;

    /** @component */
    protected MavenProjectBuilder mavenProjectBuilder;

    /** @component */
    private ArtifactMetadataSource artifactMetadataSource;

    /**
     * @parameter expression="${session}"
     * @readonly
     * @required
     */
    protected MavenSession session;

    private PlexusContainer plexus;

    /**
     * The maven project.
     * 
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    protected MavenProject project;

    /**
     * Where nexus instance should be extracted
     * 
     * @parameter default-value="${project.build.directory}/nexus"
     * @required
     */
    private File destination;

    /**
     * Artifact file containing nexus bundle
     * 
     * @parameter
     */
    protected MavenArtifact nexusBundleArtifact;

    /**
     * Name of teh directory created out of nexus artifact bundle. Default is
     * ${nexusBundleArtifactId}-${nexusBundleArtifactVersion}.
     * 
     * @parameter
     */
    protected String nexusBundleName;

    /**
     * Nexus plugin artifacts to be installed into the Nexus instance under test.
     * 
     * @parameter
     */
    private MavenArtifact[] nexusPluginsArtifacts;

    /**
     * Resources to be unpacked and then contents copied into Nexus default-configs
     * 
     * @parameter
     */
    private MavenArtifact[] extraResourcesArtifacts;

    /**
     * When true setup a maven instance
     * 
     * @parameter default-value="true"
     */
    private boolean setupMaven;

    /**
     * Maven used on ITs
     * 
     * @parameter
     * @see EnvironmentMojo#setupMaven
     */
    private MavenArtifact mavenArtifact;

    /**
     * Where Maven instance should be created
     * 
     * @parameter default-value="${project.build.directory}/maven"
     * @see EnvironmentMojo#setupMaven
     */
    private File mavenLocation;

    /**
     * Resources in the test project can be added beneath this directory so that
     * 
     * @parameter default-value="${basedir}/resources"
     */
    protected File resourcesSourceLocation;

    /**
     * This directory is where the default-configs included inside the this plugin will be extracted to BEFORE they are
     * copied into the nexus work dir. A project property 'test-resources-folder' contains the absolute path of this
     * directory.
     * 
     * @parameter default-value="${project.build.directory}/resources"
     */
    private File resourcesDestinationLocation;

    /**
     * @parameter expression="${maven.test.skip}"
     */
    protected boolean testSkip;

    /**
     * @parameter expression="${test-env.skip}"
     */
    protected boolean pluginSkip;

    /**
     * @parameter default-value="false"
     */
    private boolean extractNexusPluginsJavascript;

    /**
     * @parameter default-value="${project.build.testOutputDirectory}"
     */
    protected File testOutputDirectory;

    /**
     * @parameter default-value="${basedir}/src/test/resources"
     */
    protected File testResourcesDirectory;

    /**
     * @parameter default-value="false"
     */
    private boolean promoteOptionalPlugin;

    /**
     * @parameter
     */
    private String mavenBaseDir;

    /**
     * Known ports can be manually set as part of the configuration
     * 
     * @parameter
     */
    @SuppressWarnings("rawtypes")
    private Map staticPorts;

    /**
     * If true plugin won't include nexus-plugin dependencies with scope test
     * 
     * @parameter
     */
    private boolean excludeTestDependencies;

    /**
     * Comma separated list of patterns to delete from an unpacked Nexus bundle. Patterns are deleted before any
     * optional plugins or other external resources are installed
     * 
     * @parameter
     */
    private String nexusBundleExcludes;

    /**
     * If there is one or more *-webapp.zip files in runtime/apps/nexus/plugin-repository, then unpack that zip to nexus
     * base dir and delete the original file
     * 
     * @parameter
     */
    private boolean unpackPluginWebapps;

    /**
     * Prevents a given artifact to be copy into nexus plugin repository
     * 
     * @parameter
     */
    private String[] exclusions;

    /**
     * @parameter expression="${useBundlePluginsIfPresent}"
     */
    protected boolean useBundlePluginsIfPresent;

    public void execute() throws MojoExecutionException, MojoFailureException {
        if (testSkip || pluginSkip) {
            return;
        }

        init();

        validateStaticPorts();
        allocatePorts();

        project.getProperties().put("jetty-application-host", "0.0.0.0");
        project.getProperties().put("nexus-base-url",
                "http://localhost:" + project.getProperties().getProperty("nexus-application-port") + "/nexus/");
        project.getProperties().put("proxy-repo-base-url",
                "http://localhost:" + project.getProperties().getProperty("proxy-repo-port") + "/remote/");
        project.getProperties().put("proxy-repo-base-dir", getPath(new File(destination, "proxy-repo")));
        project.getProperties().put("proxy-repo-target-dir", getPath(new File(destination, "proxy-repo")));

        Artifact bundle = getNexusBundle();
        // extract the artifact specified in the plugin config
        if (!this.markerExist("bundle")) {
            unpack(bundle.getFile(), destination, bundle.getType());
            this.createMarkerFile("bundle");
        }

        // since specifying excludes/includes is not implemented for all archive types (tar.gz for example)
        // remove files and dirs based on specified pattern after all of the files were unpacked, rather than during the
        // unpack
        // the use case for this is that a plugin we are installing later may include a plugin that is already included
        // with bundle
        if (nexusBundleExcludes != null) {
            deleteFromDirectory(destination, nexusBundleExcludes);
        }

        File nexusBaseDir = new File(destination, bundle.getArtifactId() + "-" + bundle.getBaseVersion());
        if (nexusBundleName != null) {
            nexusBaseDir = new File(destination, nexusBundleName);
        }
        getLog().info("Nexus bundle directory: " + nexusBaseDir);

        File nexusWorkDir = new File(destination, "nexus-work-dir");
        getLog().info("Nexus work directory: " + nexusWorkDir);

        project.getProperties().put(PROP_NEXUS_BASE_DIR, getPath(nexusBaseDir));
        project.getProperties().put("nexus-work-dir", getPath(nexusWorkDir));

        // conf dir
        project.getProperties().put("application-conf", getPath(new File(destination, "nexus-work-dir/conf")));

        // final File plexusProps = new File( nexusBaseDir, "conf/plexus.properties" );
        final File plexusProps = new File(nexusBaseDir, "conf/nexus.properties");
        copyUrl("/default-config/nexus.properties", plexusProps);

        File extraPlexusProps = new File(testResourcesDirectory, "plexus.properties");
        if (extraPlexusProps.exists()) {
            merge(plexusProps, extraPlexusProps, "properties");
        }
        project.getProperties().put("nexus-plexus-config-file", getPath(new File(nexusBaseDir, "conf/plexus.xml")));

        File libFolder = new File(nexusBaseDir, "nexus/WEB-INF/lib");

        // if any plugin artifacts were specified, install them into runtime
        File pluginFolder = new File(nexusBaseDir, "nexus/WEB-INF/plugin-repository");
        project.getProperties().put("plugin-repository", getPath(pluginFolder));

        Collection<MavenArtifact> npas = getNexusPluginsArtifacts();
        if (npas != null) {
            setupPlugins(npas, libFolder, pluginFolder);
        }

        // promote any plugins included in the bundle to the runtime install
        if (promoteOptionalPlugin) {
            File optionalPluginFolder = new File(nexusBaseDir, "nexus/WEB-INF//optional-plugins");
            try {
                if (optionalPluginFolder.exists()) {
                    FileUtils.copyDirectoryStructure(optionalPluginFolder, pluginFolder);
                    FileUtils.deleteDirectory(optionalPluginFolder);
                }
            } catch (IOException e) {
                throw new MojoExecutionException("Failed to promote optinal plugins", e);
            }
        }

        if (unpackPluginWebapps) {
            try {
                // now if we have *-webapp.zip in pluginfolder, unpack that to nexus-work-dir and delete zip file
                @SuppressWarnings("unchecked")
                List<File> webapps = FileUtils.getFiles(pluginFolder, "*-webapp.zip", null);
                for (File webapp : webapps) {
                    unpack(webapp, new File((String) project.getProperties().get(PROP_NEXUS_BASE_DIR)), "zip");
                    webapp.delete();
                }
            } catch (IOException e) {
                throw new MojoExecutionException("Failed to unpack webapp plugins:", e);
            }

        }

        // setup Maven if requested for this test
        if (setupMaven) {
            mavenLocation.mkdirs();
            String mavenVersion = setupMaven().getBaseVersion();
            project.getProperties().put("maven-version", mavenVersion);
            if (this.mavenBaseDir == null) {
                this.mavenBaseDir = "apache-maven-" + mavenVersion;
            }
            project.getProperties().put("maven-basedir", getPath(new File(mavenLocation, mavenBaseDir)));

            File fakeRepoDest = new File(mavenLocation, "fake-repo");
            project.getProperties().put("maven-repository", getPath(fakeRepoDest));
        }

        if (!resourcesDestinationLocation.isDirectory()) {
            resourcesDestinationLocation.mkdirs();
        }
        project.getProperties().put("test-resources-folder", getPath(resourcesDestinationLocation));
        // ./resources dir at root of project by default, suitable for tests I guess?
        if (resourcesSourceLocation.isDirectory()) {
            project.getProperties().put("test-resources-source-folder", getPath(resourcesSourceLocation));
        }

        // start default configs
        File defaultConfig = new File(resourcesDestinationLocation, "default-configs");
        project.getProperties().put("default-configs", getPath(defaultConfig));

        copyUrl("/default-config/nexus.xml", new File(defaultConfig, "nexus.xml"));
        copyUrl("/default-config/security.xml", new File(defaultConfig, "security.xml"));
        copyUrl("/default-config/security-configuration.xml",
                new File(defaultConfig, "security-configuration.xml"));
        copyUrl("/default-config/settings.xml", new File(defaultConfig, "settings.xml"));
        copyUrl("/default-config/logback-nexus.xml", new File(defaultConfig, "logback-nexus.xml"));

        File sourceDefaultConfig = new File(resourcesSourceLocation, "default-config");
        if (sourceDefaultConfig.isDirectory()) {
            copyAndInterpolate(sourceDefaultConfig, defaultConfig);
        }
        // end default configs

        // start baseTest.properties
        File baseTestProperties = new File(testOutputDirectory, "baseTest.properties");
        copyUrl("/default-config/baseTest.properties", baseTestProperties);

        File testSuiteProperties = new File(resourcesSourceLocation, "baseTest.properties");
        if (testSuiteProperties.isFile()) {
            merge(baseTestProperties, testSuiteProperties, "properties");
        }

        addProjectProperties(baseTestProperties);
        // end baseTest.properties

        File logbackConfig = new File(testOutputDirectory, "logback.xml");
        if (!logbackConfig.exists()) {
            copyUrl("/test-config/logback.xml", logbackConfig);
        }

        copyExtraResources();

        File destinationComponents = new File(testOutputDirectory, "META-INF/plexus/components.xml");
        copyUrl("/default-config/components.xml", destinationComponents);

        File componentsXml = new File(testResourcesDirectory, "components.xml");
        if (componentsXml.exists()) {
            copyAndInterpolate(componentsXml.getParentFile(), destinationComponents.getParentFile());
        }

        if (extractNexusPluginsJavascript) {
            extractPluginJs();
        }
    }

    /**
     * Delete file patterns from a base directory
     */
    @SuppressWarnings("unchecked")
    protected void deleteFromDirectory(final File baseDirectory, final String patternsToDelete)
            throws MojoExecutionException {
        getLog().info("Deleting from: " + baseDirectory + "; pattern: " + patternsToDelete);
        try {
            final List<String> filesToDelete = FileUtils.getFileAndDirectoryNames(baseDirectory, patternsToDelete,
                    null, true, true, true, true);
            for (String fileToDelete : filesToDelete) {
                FileUtils.forceDelete(fileToDelete);
            }
        } catch (IOException e1) {
            throw new MojoExecutionException(e1.getMessage(), e1);
        }
    }

    protected Collection<MavenArtifact> getNexusPluginsArtifacts() throws MojoExecutionException {
        if (this.nexusPluginsArtifacts == null) {
            return Collections.emptySet();
        }

        return Arrays.asList(this.nexusPluginsArtifacts);
    }

    protected Artifact getNexusBundle() throws MojoExecutionException, MojoFailureException {
        return getMavenArtifact(nexusBundleArtifact);
    }

    private void extractPluginJs() throws MojoExecutionException {
        Collection<Artifact> plugins = getNexusPlugins();

        File outputDir = new File(project.getProperties().getProperty("nexus.webapp"), "static");
        outputDir.mkdirs();

        for (Artifact plugin : plugins) {
            ZipFile file;
            try {
                file = new ZipFile(plugin.getFile());
            } catch (IOException e) {
                throw new MojoExecutionException(e.getMessage(), e);
            }

            getLog().debug("Processing " + plugin);

            Enumeration<? extends ZipEntry> entries = file.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();

                String name = entry.getName();
                if (!(name.startsWith("static/js/") && name.endsWith(".js"))) {
                    continue;
                }

                File outFile = new File(outputDir, name.substring(10));
                getLog().debug("Extracting " + name + " to " + outFile);

                InputStream in = null;
                FileOutputStream out = null;

                try {
                    in = file.getInputStream(entry);
                    out = new FileOutputStream(outFile);

                    IOUtil.copy(in, out);
                } catch (IOException e) {
                    throw new MojoExecutionException(e.getMessage(), e);
                } finally {
                    IOUtil.close(out);
                    IOUtil.close(in);
                }
            }
        }
    }

    private void addProjectProperties(File baseTestProperties) throws MojoExecutionException {
        InputStream input = null;
        OutputStream output = null;
        try {
            input = new FileInputStream(baseTestProperties);

            Properties original = new Properties();
            original.load(input);
            IOUtil.close(input);

            original.putAll(this.project.getProperties());
            original.putAll(this.session.getExecutionProperties());

            output = new FileOutputStream(baseTestProperties);

            original.store(output, "Updated by EnvironmentMojo");
        } catch (Exception e) {
            throw new MojoExecutionException(
                    "Error adding properties '" + baseTestProperties.getAbsolutePath() + "'.", e);
        } finally {
            IOUtil.close(input);
            IOUtil.close(output);
        }
    }

    // todo add this as a constraint
    static final Pattern PORT_PATTERN = Pattern
            .compile("^(6553[0-5]|655[0-2]\\d|65[0-4]\\d\\d|6[0-4]\\d{3}|[1-5]\\d{4}|[1-9]\\d{0,3}|0)$");

    @SuppressWarnings("unchecked")
    private void validateStaticPorts() throws MojoExecutionException, MojoFailureException {
        if (this.staticPorts != null) {
            try {
                @SuppressWarnings("rawtypes")
                BiMap staticPortMap = HashBiMap.create(this.staticPorts.size());
                staticPortMap = MapConstraints.constrainedBiMap(staticPortMap, MapConstraints.notNull());
                staticPortMap.putAll(this.staticPorts);
                this.staticPorts = staticPortMap;
            } catch (NullPointerException npe) {
                throw new MojoExecutionException("Port names and values must not be null.", npe);
            } catch (IllegalArgumentException iae) {
                throw new MojoExecutionException("Port names and values must not be duplicated.", iae);
            }
        }
    }

    /**
     * Call this to allocate the port values and store as project properties so that they can be filtered into the Nexus
     * config files
     */
    private void allocatePorts() throws MojoExecutionException, MojoFailureException {
        allocatePorts(0);
    }

    /**
     * @throws MojoExecutionException
     * @throws MojoFailureException if methodEntryCount exceeds {@link #MAX_PORT_ALLOCATION_RETRY}
     * @param entryNum a value less than {@link #MAX_PORT_ALLOCATION_RETRY}
     */
    private void allocatePorts(int methodEntryCount) throws MojoExecutionException, MojoFailureException {
        if (methodEntryCount >= MAX_PORT_ALLOCATION_RETRY) {
            throw new MojoFailureException(
                    "Exceeded the maximum number of port allocation retries (" + MAX_PORT_ALLOCATION_RETRY + ")");
        }
        methodEntryCount++;

        // calc dynamic and static ports
        List<Port> portsList = new ArrayList<Port>();
        for (String portName : DEFAULT_PORT_NAMES) {
            Port port = new Port(portName);
            if (this.staticPorts != null && this.staticPorts.containsKey(portName)) {
                // this prevents assigning random port and instead
                // tests the static port for availability
                String portNum = String.valueOf(this.staticPorts.get(portName));
                getLog().debug("Statically defining port '" + portName + "' with value '" + portNum + "'.");
                port.setPortNumber(Integer.valueOf(portNum));
                port.setFailIfOccupied(true);
            }
            portsList.add(port);
        }

        // allocate dynamic ports and verify requested ports
        PortAllocatorMojo portAllocator = new PortAllocatorMojo();
        portAllocator.setProject(project);
        portAllocator.setLog(getLog());
        portAllocator.setPorts(portsList.toArray(new Port[0]));
        portAllocator.execute();

        // detect port collisions from dynamic port assignment
        List<String> portNums = new ArrayList<String>();
        for (String portName : DEFAULT_PORT_NAMES) {
            String portNum = String.valueOf(project.getProperties().get(portName));
            assert !"null".equals(portNum);
            if (portNums.contains(portNum)) {
                // duplicate ports generated by port allocator, try again
                getLog().debug("Duplicate port value of " + portNum
                        + " is defined. Trying to re-allocate non-duplicate port values.");
                allocatePorts(methodEntryCount);
            }
            portNums.add(portNum);
        }

    }

    private void copyExtraResources() throws MojoExecutionException, MojoFailureException {
        for (MavenArtifact extraResource : getExtraResourcesArtifacts()) {
            Artifact artifact = getMavenArtifact(extraResource);

            File dest;
            if (extraResource.getOutputDirectory() != null) {
                dest = extraResource.getOutputDirectory();
            } else if (extraResource.getOutputProperty() != null) {
                dest = new File(project.getProperties().getProperty(extraResource.getOutputProperty()));
            } else {
                dest = resourcesDestinationLocation;
            }
            unpack(artifact.getFile(), dest, artifact.getType());
        }
    }

    protected Collection<MavenArtifact> getExtraResourcesArtifacts() {
        if (extraResourcesArtifacts == null) {
            return Collections.emptySet();
        }
        return Arrays.asList(extraResourcesArtifacts);
    }

    private void merge(File originalFile, File extraContentFile, String type)
            throws MojoFailureException, MojoExecutionException {
        InputStream originalReader = null;
        InputStream extraContentReader = null;
        OutputStream originalWriter = null;
        try {
            String name = FileUtils.removeExtension(extraContentFile.getName());
            String extension = FileUtils.getExtension(extraContentFile.getName());

            if ("properties".equals(type)) {
                File tempFile = File.createTempFile(name, extension);
                mavenFileFilter.copyFile(extraContentFile, tempFile, true, project, null, true, "UTF-8", session);

                originalReader = new FileInputStream(originalFile);
                extraContentReader = new FileInputStream(tempFile);

                Properties original = new Properties();
                original.load(originalReader);
                IOUtil.close(originalReader);

                originalWriter = new FileOutputStream(originalFile);

                Properties extra = new Properties();
                extra.load(extraContentReader);
                IOUtil.close(extraContentReader);

                for (Object key : extra.keySet()) {
                    original.put(key, extra.get(key));
                }

                original.store(originalWriter, "Updated by EnvironmentMojo");
            } else {
                throw new MojoFailureException("Invalid file type: " + type);
            }
        } catch (Exception e) {
            throw new MojoExecutionException("Error merging files: Original '" + originalFile.getAbsolutePath()
                    + "', extraContent '" + extraContentFile.getAbsolutePath() + "'.", e);
        } finally {
            IOUtil.close(originalReader);
            IOUtil.close(extraContentReader);
            IOUtil.close(originalWriter);
        }
    }

    private void copyUrl(String sourceUrl, File destinationFile) throws MojoExecutionException {
        getLog().debug("Copying url '" + sourceUrl + "'");

        String name = FileUtils.removeExtension(FileUtils.removePath(sourceUrl, '/'));
        String extension = FileUtils.getExtension(sourceUrl);

        try {
            destinationFile.getParentFile().mkdirs();
            destinationFile.createNewFile();

            File tempFile = File.createTempFile(name, extension);
            FileUtils.copyStreamToFile(new RawInputStreamFacade(getClass().getResourceAsStream(sourceUrl)),
                    tempFile);
            mavenFileFilter.copyFile(tempFile, destinationFile, true, project, null, true, "UTF-8", session);
            tempFile.delete();
        } catch (Exception e) {
            throw new MojoExecutionException(
                    "Unable to copy resouce " + sourceUrl + " to " + name + "." + extension, e);
        }
    }

    private void copyAndInterpolate(File sourceDir, File destinationDir) throws MojoExecutionException {
        destinationDir.mkdirs();

        getLog().debug("Copying and interpolating dir '" + sourceDir + "'");
        try {
            DirectoryScanner scanner = new DirectoryScanner();

            scanner.setBasedir(sourceDir);
            scanner.addDefaultExcludes();
            scanner.scan();

            String[] files = scanner.getIncludedFiles();
            for (String file : files) {
                String extension = FileUtils.getExtension(file);

                File source = new File(sourceDir, file);

                File dest = new File(destinationDir, file);
                dest.getParentFile().mkdirs();

                if (Arrays.asList("zip", "jar", "tar.gz").contains(extension)) {
                    // just copy know binaries
                    FileUtils.copyFile(source, dest);
                } else {
                    mavenFileFilter.copyFile(source, dest, true, project, null, false, "UTF-8", session);
                }
            }
        } catch (MavenFilteringException e) {
            throw new MojoExecutionException("Failed to copy : " + sourceDir, e);
        } catch (IOException e) {
            throw new MojoExecutionException("Failed to copy : " + sourceDir, e);
        }
    }

    private Artifact setupMaven() throws MojoExecutionException, MojoFailureException {
        if (mavenArtifact == null) {
            mavenArtifact = new MavenArtifact("org.apache.maven", "apache-maven", "bin", "tar.gz");
        }
        Artifact artifact = getMavenArtifact(mavenArtifact);

        if (!this.markerExist("maven")) {
            unpack(artifact.getFile(), mavenLocation, artifact.getType());
            this.createMarkerFile("maven");
        }

        return artifact;
    }

    private void init() {
        destination.mkdirs();
        resourcesDestinationLocation.mkdirs();

        if (nexusBundleArtifact == null) {
            throw new RuntimeException("Missing 'nexusBundleArtifact' configuration");
        }
    }

    private void setupPlugins(Collection<MavenArtifact> nexusPluginsArtifacts, File libsFolder, File pluginsFolder)
            throws MojoFailureException, MojoExecutionException {

        Set<Artifact> plugins = new LinkedHashSet<Artifact>();
        for (MavenArtifact plugin : nexusPluginsArtifacts) {
            Artifact pluginArtifact = getMavenArtifact(plugin);
            plugins.add(pluginArtifact);
        }

        nexusPluginsArtifacts = new LinkedHashSet<MavenArtifact>(nexusPluginsArtifacts);
        Collection<Artifact> nonTransitivePlugins = getNonTransitivePlugins(plugins);
        for (Artifact artifact : nonTransitivePlugins) {
            final MavenArtifact ma = new MavenArtifact(artifact.getGroupId(), artifact.getArtifactId(),
                    artifact.getClassifier(), artifact.getType());
            ma.setVersion(artifact.getVersion());
            nexusPluginsArtifacts.add(ma);
        }

        nexusPluginsArtifacts = filterOutExcludedPlugins(nexusPluginsArtifacts);

        final Map<String, File> bundlePlugins = useBundlePluginsIfPresent ? listPlugins(pluginsFolder) : null;

        for (MavenArtifact plugin : nexusPluginsArtifacts) {
            getLog().info("Setting up plugin: " + plugin);

            Artifact pluginArtifact = getMavenArtifact(plugin);

            File dest;
            if (plugin.getOutputDirectory() != null) {
                dest = plugin.getOutputDirectory();
            } else if (plugin.getOutputProperty() != null) {
                dest = new File(project.getProperties().getProperty(plugin.getOutputProperty()));
            } else if ("bundle".equals(pluginArtifact.getClassifier()) && "zip".equals(pluginArtifact.getType())) {
                dest = pluginsFolder;
            } else {
                dest = libsFolder;
            }

            String type = pluginArtifact.getType();

            if ("jar".equals(type)) {
                // System.out.println( "copying jar: "+ pluginArtifact.getFile().getAbsolutePath() + " to: "+
                // dest.getAbsolutePath() );
                copy(pluginArtifact.getFile(), dest);
            } else if ("zip".equals(type) || "tar.gz".equals(type)) {
                File file = pluginArtifact.getFile();
                if (file == null || !file.isFile()) {
                    throw new MojoFailureException(
                            "Could not properly resolve artifact " + pluginArtifact + ", got " + file);
                }
                final String pluginKey = pluginArtifact.getGroupId() + ":" + pluginArtifact.getArtifactId();
                if (!useBundlePluginsIfPresent || !bundlePlugins.containsKey(pluginKey)) {
                    unpack(file, dest, type);
                }
            } else {
                throw new MojoFailureException("Invalid plugin type: " + type);
            }
        }
    }

    private void copy(File sourceFile, File destinationDir) throws MojoExecutionException {
        getLog().info("Copying " + sourceFile + " to: " + destinationDir);

        try {
            FileUtils.copyFileToDirectory(sourceFile, destinationDir);
        } catch (IOException e) {
            throw new MojoExecutionException("Failed to copy : " + sourceFile, e);
        }
    }

    private String getPath(File nexusBaseDir) {
        try {
            return nexusBaseDir.getCanonicalPath();
        } catch (IOException e) {
            return nexusBaseDir.getAbsolutePath();
        }
    }

    private void unpack(File sourceFile, File destDirectory, String type) throws MojoExecutionException {
        destDirectory.mkdirs();

        getLog().info("Unpacking (" + type + ") " + sourceFile + " to: " + destDirectory);
        UnArchiver unarchiver;
        try {
            unarchiver = (UnArchiver) plexus.lookup(UnArchiver.ROLE, type);

            unarchiver.setSourceFile(sourceFile);
            unarchiver.setDestDirectory(destDirectory);
            try {
                unarchiver.extract();
            } catch (Exception e) {
                throw new MojoExecutionException("Unable to unpack " + sourceFile, e);
            }
        } catch (ComponentLookupException ce) {
            getLog().warn("Invalid packaging type " + type);

            try {
                FileUtils.copyFileToDirectory(sourceFile, destDirectory);
            } catch (IOException e) {
                throw new MojoExecutionException("Unable to copy " + sourceFile, e);
            }
        }

    }

    protected Artifact getMavenArtifact(MavenArtifact mavenArtifact)
            throws MojoExecutionException, MojoFailureException {
        Artifact artifact;
        if (mavenArtifact.getVersion() != null) {
            artifact = artifactFactory.createArtifactWithClassifier(mavenArtifact.getGroupId(),
                    mavenArtifact.getArtifactId(), mavenArtifact.getVersion(), mavenArtifact.getType(),
                    mavenArtifact.getClassifier());
        } else {
            Set<Artifact> projectArtifacts = getFilteredArtifacts(mavenArtifact.getGroupId(),
                    mavenArtifact.getArtifactId(), mavenArtifact.getType(), mavenArtifact.getClassifier());

            if (projectArtifacts.isEmpty()) {
                throw new MojoFailureException(
                        "Maven artifact: '" + mavenArtifact.toString() + "' not found on dependencies list");
            } else if (projectArtifacts.size() != 1) {
                throw new MojoFailureException(
                        "More then one artifact found on dependencies list: '" + mavenArtifact.toString() + "'");
            }

            artifact = projectArtifacts.iterator().next();
        }

        if ("nexus-plugin".equals(mavenArtifact.getType())) {
            artifact = artifactFactory.createArtifactWithClassifier(artifact.getGroupId(), artifact.getArtifactId(),
                    artifact.getVersion(), "zip", "bundle");
        }

        return resolve(artifact);
    }

    @SuppressWarnings("unchecked")
    private Set<Artifact> getFilteredArtifacts(String groupId, String artifactId, String type, String classifier)
            throws MojoExecutionException {
        Set<Artifact> projectArtifacts = new LinkedHashSet<Artifact>();

        projectArtifacts.addAll(project.getAttachedArtifacts());
        projectArtifacts.addAll(project.getArtifacts());

        FilterArtifacts filter = getFilters(groupId, artifactId, type, classifier);

        return filtterArtifacts(projectArtifacts, filter);
    }

    @SuppressWarnings("unchecked")
    private Set<Artifact> filtterArtifacts(Set<Artifact> projectArtifacts, FilterArtifacts filter)
            throws MojoExecutionException {
        // perform filtering
        try {
            projectArtifacts = filter.filter(projectArtifacts);
        } catch (ArtifactFilterException e) {
            throw new MojoExecutionException("Error filtering artifacts", e);
        }

        return projectArtifacts;
    }

    private FilterArtifacts getFilters(String groupId, String artifactId, String type, String classifier) {
        FilterArtifacts filter = new FilterArtifacts();

        if (type != null) {
            filter.addFilter(new TypeFilter(type, null));
        }
        if (classifier != null) {
            filter.addFilter(new ClassifierFilter(classifier, null));
        }
        if (groupId != null) {
            filter.addFilter(new GroupIdFilter(groupId, null));
        }
        if (artifactId != null) {
            filter.addFilter(new ArtifactIdFilter(artifactId, null));
        }

        if (excludeTestDependencies) {
            filter.addFilter(new TestScopeFilter());
        }
        return filter;
    }

    protected Artifact resolve(Artifact artifact) throws MojoExecutionException {
        if (!artifact.isResolved()) {
            try {
                resolver.resolve(artifact, remoteRepositories, localRepository);
            } catch (AbstractArtifactResolutionException e) {
                throw new MojoExecutionException("Unable to resolve artifact: " + artifact, e);
            }
        }

        return artifact;
    }

    protected Collection<Artifact> getNexusPlugins() throws MojoExecutionException {

        Set<Artifact> projectArtifacts = new LinkedHashSet<Artifact>();
        projectArtifacts.addAll(getFilteredArtifacts(null, null, "zip", "bundle"));
        projectArtifacts.addAll(getFilteredArtifacts(null, null, "nexus-plugin", null));
        projectArtifacts.addAll(getNonTransitivePlugins(projectArtifacts));

        List<Artifact> resolvedArtifacts = new ArrayList<Artifact>();

        for (Artifact artifact : projectArtifacts) {
            Artifact ra = artifactFactory.createArtifactWithClassifier(artifact.getGroupId(),
                    artifact.getArtifactId(), artifact.getVersion(), artifact.getType(), artifact.getClassifier());

            resolvedArtifacts.add(resolve(ra));
        }

        return resolvedArtifacts;
    }

    @SuppressWarnings("unchecked")
    private Collection<Artifact> getNonTransitivePlugins(Set<Artifact> projectArtifacts)
            throws MojoExecutionException {
        Collection<Artifact> deps = new LinkedHashSet<Artifact>();

        for (Artifact artifact : projectArtifacts) {
            Artifact pomArtifact = artifactFactory.createArtifact(artifact.getGroupId(), artifact.getArtifactId(),
                    artifact.getVersion(), artifact.getClassifier(), "pom");
            Set<Artifact> result;
            try {
                MavenProject pomProject = mavenProjectBuilder.buildFromRepository(pomArtifact, remoteRepositories,
                        localRepository);

                Set<Artifact> artifacts = pomProject.createArtifacts(artifactFactory, null, null);
                artifacts = filterOutSystemDependencies(artifacts);
                ArtifactResolutionResult arr = resolver.resolveTransitively(artifacts, pomArtifact, localRepository,
                        remoteRepositories, artifactMetadataSource, null);
                result = arr.getArtifacts();
            } catch (Exception e) {
                throw new MojoExecutionException("Failed to resolve non-transitive deps " + e.getMessage(), e);
            }

            LinkedHashSet<Artifact> plugins = new LinkedHashSet<Artifact>();
            plugins.addAll(filtterArtifacts(result, getFilters(null, null, "nexus-plugin", null)));
            plugins.addAll(filtterArtifacts(result, getFilters(null, null, "zip", "bundle")));

            plugins.addAll(getNonTransitivePlugins(plugins));

            if (!plugins.isEmpty()) {
                getLog().debug("Adding non-transitive dependencies for: " + artifact + " -\n"
                        + plugins.toString().replace(',', '\n'));
            }

            deps.addAll(plugins);
        }

        return deps;
    }

    private Set<Artifact> filterOutSystemDependencies(Set<Artifact> artifacts) {
        return Sets.filter(artifacts, new Predicate<Artifact>() {
            @Override
            public boolean apply(Artifact a) {
                return !"system".equals(a.getScope());
            }
        });
    }

    private Set<MavenArtifact> filterOutExcludedPlugins(Collection<MavenArtifact> artifacts) {
        if (exclusions == null) {
            return Sets.newLinkedHashSet(artifacts);
        }

        return Sets.filter(Sets.newLinkedHashSet(artifacts), new Predicate<MavenArtifact>() {

            @Override
            public boolean apply(MavenArtifact a) {
                for (String exclusion : exclusions) {
                    String[] pieces = exclusion.split(":");
                    if (pieces.length != 2) {
                        throw new IllegalArgumentException("Invalid exclusion " + exclusion);
                    }
                    String groupId = pieces[0];
                    String artifactId = pieces[1];

                    if ("*".equals(groupId)) {
                        if (artifactId.equals(a.getArtifactId())) {
                            return false;
                        }
                    } else {
                        if (groupId.equals(a.getGroupId()) && artifactId.equals(a.getArtifactId())) {
                            return false;
                        }
                    }
                }

                return true;
            }
        });
    }

    public void contextualize(Context context) throws ContextException {
        plexus = (PlexusContainer) context.get(PlexusConstants.PLEXUS_KEY);
    }

    private boolean markerExist(String markerName) {
        File marker = new File(project.getBuild().getDirectory(), markerName + ".marker");
        return marker.exists();
    }

    private void createMarkerFile(String markerName) {
        File marker = new File(project.getBuild().getDirectory(), markerName + ".marker");
        try {
            if (!marker.createNewFile()) {
                this.getLog().warn("Failed to create marker file: " + marker.getAbsolutePath()
                        + " bundle will be extracted every time you run the build.");
            }
        } catch (IOException e) {
            this.getLog().warn("Failed to create marker file: " + marker.getAbsolutePath()
                    + " bundle will be extracted every time you run the build.");
        }
    }

    private Map<String, File> listPlugins(final File pluginsDir) {
        final Map<String, File> plugins = Maps.newHashMap();
        final File[] foundPlugins = pluginsDir.listFiles(new FileFilter() {
            @Override
            public boolean accept(final File file) {
                return file.isDirectory();
            }
        });
        if (foundPlugins != null && foundPlugins.length > 0) {
            for (final File plugin : foundPlugins) {
                final Optional<File> mainJar = getPluginMainJar(plugin);
                if (mainJar.isPresent()) {
                    final Optional<String> gaCoordinates = getPluginGACoordinates(mainJar.get());
                    if (gaCoordinates.isPresent()) {
                        plugins.put(gaCoordinates.get(), plugin);
                    }
                }
            }
        }
        return plugins;
    }

    private Optional<String> getPluginGACoordinates(final File mainJar) {
        try {
            final ZipFile jarFile = new ZipFile(mainJar);
            final Enumeration<? extends ZipEntry> entries = jarFile.entries();
            if (entries != null) {
                while (entries.hasMoreElements()) {
                    final ZipEntry zipEntry = entries.nextElement();
                    if (zipEntry.getName().startsWith("META-INF/maven")
                            && zipEntry.getName().endsWith("pom.properties")) {
                        final Properties props = new Properties();
                        props.load(jarFile.getInputStream(zipEntry));
                        return Optional.of(props.getProperty("groupId") + ":" + props.getProperty("artifactId"));
                    }
                }
            }
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
        return Optional.absent();
    }

    private Optional<File> getPluginMainJar(final File plugin) {
        final String[] mainJars = plugin.list(new FilenameFilter() {
            @Override
            public boolean accept(final File dir, final String name) {
                return name.endsWith(".jar");
            }
        });
        if (mainJars != null && mainJars.length > 0) {
            if (mainJars.length > 1) {
                throw new IllegalStateException(
                        "Plugin '" + plugin.getAbsolutePath() + "' contains more then one jar");
            } else {
                return Optional.of(new File(plugin, mainJars[0]));
            }
        }
        return Optional.absent();
    }

}