org.apache.maven.plugin.resources.remote.ProcessRemoteResourcesMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.maven.plugin.resources.remote.ProcessRemoteResourcesMojo.java

Source

package org.apache.maven.plugin.resources.remote;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.io.output.DeferredFileOutputStream;
import org.apache.maven.ProjectDependenciesResolver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
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.ArtifactResolver;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Model;
import org.apache.maven.model.Organization;
import org.apache.maven.model.Resource;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.resources.remote.io.xpp3.RemoteResourcesBundleXpp3Reader;
import org.apache.maven.plugin.resources.remote.io.xpp3.SupplementalDataModelXpp3Reader;
import org.apache.maven.plugins.annotations.Component;
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.InvalidProjectModelException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.artifact.InvalidDependencyVersionException;
import org.apache.maven.project.inheritance.ModelInheritanceAssembler;
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.FilterArtifacts;
import org.apache.maven.shared.artifact.filter.collection.GroupIdFilter;
import org.apache.maven.shared.artifact.filter.collection.ProjectTransitivityFilter;
import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
import org.apache.maven.shared.filtering.MavenFileFilter;
import org.apache.maven.shared.filtering.MavenFileFilterRequest;
import org.apache.maven.shared.filtering.MavenFilteringException;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.log.LogChute;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.codehaus.plexus.resource.ResourceManager;
import org.codehaus.plexus.resource.loader.FileResourceLoader;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.WriterFactory;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * <p>
 * Pull down resourceBundles containing remote resources and process the resources contained inside. When that is done
 * the resources are injected into the current (in-memory) Maven project, making them available to the process-resources
 * phase.
 * </p>
 * <p>
 * Resources that end in ".vm" are treated as velocity templates. For those, the ".vm" is stripped off for the final
 * artifact name and it's fed through velocity to have properties expanded, conditions processed, etc...
 * </p>
 * <p/>
 * Resources that don't end in ".vm" are copied "as is".
 */
// NOTE: Removed the following in favor of maven-artifact-resolver library, for MRRESOURCES-41
// If I leave this intact, interdependent projects within the reactor that haven't been built
// (remember, this runs in the generate-resources phase) will cause the build to fail.
//
// @requiresDependencyResolution test
@Mojo(name = "process", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true)
public class ProcessRemoteResourcesMojo extends AbstractMojo implements LogChute {

    private static final String TEMPLATE_SUFFIX = ".vm";

    /**
     * <p>
     * In cases where a local resource overrides one from a remote resource bundle, that resource should be filtered if
     * the resource set specifies it. In those cases, this parameter defines the list of delimiters for filterable
     * expressions. These delimiters are specified in the form 'beginToken*endToken'. If no '*' is given, the delimiter
     * is assumed to be the same for start and end.
     * </p>
     * <p>
     * So, the default filtering delimiters might be specified as:
     * </p>
     * 
     * <pre>
     * &lt;delimiters&gt;
     *   &lt;delimiter&gt;${*}&lt/delimiter&gt;
     *   &lt;delimiter&gt;@&lt/delimiter&gt;
     * &lt;/delimiters&gt;
     * </pre>
     * <p/>
     * Since the '@' delimiter is the same on both ends, we don't need to specify '@*@' (though we can).
     *
     * @since 1.1
     */
    @Parameter
    protected List<String> filterDelimiters;

    /**
     * @since 1.1
     */
    @Parameter(defaultValue = "true")
    protected boolean useDefaultFilterDelimiters;

    /**
     * If true, only generate resources in the directory of the root project in a multimodule build.
     * Dependencies from all modules will be aggregated before resource-generation takes place.
     *
     * @since 1.1
     */
    @Parameter(defaultValue = "false")
    protected boolean runOnlyAtExecutionRoot;

    /**
     * Used for calculation of execution-root for {@link ProcessRemoteResourcesMojo#runOnlyAtExecutionRoot}.
     */
    @Parameter(defaultValue = "${basedir}", readonly = true, required = true)
    protected File basedir;

    /**
     * The character encoding scheme to be applied when filtering resources.
     */
    @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
    protected String encoding;

    /**
     * The local repository taken from Maven's runtime. Typically $HOME/.m2/repository.
     */
    @Parameter(defaultValue = "${localRepository}", readonly = true, required = true)
    private ArtifactRepository localRepository;

    /**
     * List of Remote Repositories used by the resolver.
     */
    @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true)
    private List<ArtifactRepository> remoteArtifactRepositories;

    /**
     * The current Maven project.
     */
    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;

    /**
     * The directory where processed resources will be placed for packaging.
     */
    @Parameter(defaultValue = "${project.build.directory}/maven-shared-archive-resources")
    private File outputDirectory;

    /**
     * The directory containing extra information appended to the generated resources.
     */
    @Parameter(defaultValue = "${basedir}/src/main/appended-resources")
    private File appendedResourcesDirectory;

    /**
     * Supplemental model data. Useful when processing
     * artifacts with incomplete POM metadata.
     * <p/>
     * By default, this Mojo looks for supplemental model data in the file
     * "${appendedResourcesDirectory}/supplemental-models.xml".
     *
     * @since 1.0-alpha-5
     */
    @Parameter
    private String[] supplementalModels;

    /**
     * List of artifacts that are added to the search path when looking
     * for supplementalModels, expressed with <code>groupId:artifactId:version[:type[:classifier]]</code> format.
     *
     * @since 1.1
     */
    @Parameter
    private List<String> supplementalModelArtifacts;

    /**
     * Map of artifacts to supplemental project object models.
     */
    private Map<String, Model> supplementModels;

    /**
     * Merges supplemental data model with artifact
     * metadata. Useful when processing artifacts with
     * incomplete POM metadata.
     */
    @Component
    private ModelInheritanceAssembler inheritanceAssembler;

    /**
     * The resource bundles that will be retrieved and processed,
     * expressed with <code>groupId:artifactId:version[:type[:classifier]]</code> format.
     */
    @Parameter(required = true)
    private List<String> resourceBundles;

    /**
     * Skip remote-resource processing
     *
     * @since 1.0-alpha-5
     */
    @Parameter(property = "remoteresources.skip", defaultValue = "false")
    private boolean skip;

    /**
     * Attaches the resources to the main build of the project as a resource
     * directory.
     *
     * @since 1.5
     */
    @Parameter(defaultValue = "true", property = "attachToMain")
    private boolean attachToMain;

    /**
     * Attaches the resources to the test build of the project as a resource
     * directory.
     *
     * @since 1.5
     */
    @Parameter(defaultValue = "true", property = "attachToTest")
    private boolean attachToTest;

    /**
     * Additional properties to be passed to velocity.
     * <p/>
     * Several properties are automatically added:<br/>
     * project - the current MavenProject <br/>
     * projects - the list of dependency projects<br/>
     * projectTimespan - the timespan of the current project (requires inceptionYear in pom)<br/>
     * locator - the ResourceManager that can be used to retrieve additional resources<br/>
     * <p/>
     * See <a
     * href="http://maven.apache.org/ref/current/maven-project/apidocs/org/apache/maven/project/MavenProject.html"> the
     * javadoc for MavenProject</a> for information about the properties on the MavenProject.
     */
    @Parameter
    private Map<String, Object> properties = new HashMap<String, Object>();

    /**
     * Whether to include properties defined in the project when filtering resources.
     *
     * @since 1.2
     */
    @Parameter(defaultValue = "false")
    protected boolean includeProjectProperties = false;

    /**
     * When the result of velocity transformation fits in memory, it is compared with the actual contents on disk
     * to eliminate unnecessary destination file overwrite. This improves build times since further build steps
     * typically rely on the modification date.
     *
     * @since 1.6
     */
    @Parameter(defaultValue = "5242880")
    protected int velocityFilterInMemoryThreshold = 5 * 1024 * 1024;

    /**
     * The list of resources defined for the project.
     */
    @Parameter(defaultValue = "${project.resources}", readonly = true, required = true)
    private List<Resource> resources;

    /**
     * Artifact Resolver, needed to resolve and download the {@code resourceBundles}.
     */
    @Component
    private ArtifactResolver artifactResolver;

    /**
     * Filtering support, for local resources that override those in the remote bundle.
     */
    @Component
    private MavenFileFilter fileFilter;

    /**
     * Artifact factory, needed to create artifacts.
     */
    @Component
    private ArtifactFactory artifactFactory;

    /**
     * The Maven session.
     */
    @Parameter(defaultValue = "${session}", readonly = true, required = true)
    private MavenSession mavenSession;

    /**
     * ProjectBuilder, needed to create projects from the artifacts.
     */
    @Component(role = MavenProjectBuilder.class)
    private MavenProjectBuilder mavenProjectBuilder;

    /**
     */
    @Component
    private ResourceManager locator;

    /**
     * Scope to include. An Empty string indicates all scopes (default is "runtime").
     *
     * @since 1.0
     */
    @Parameter(property = "includeScope", defaultValue = "runtime")
    protected String includeScope;

    /**
     * Scope to exclude. An Empty string indicates no scopes (default).
     *
     * @since 1.0
     */
    @Parameter(property = "excludeScope", defaultValue = "")
    protected String excludeScope;

    /**
     * When resolving project dependencies, specify the scopes to include.
     * The default is the same as "includeScope" if there are no exclude scopes set.
     * Otherwise, it defaults to "test" to grab all the dependencies so the
     * exclude filters can filter out what is not needed.
     * 
     * @since 1.5
     */
    @Parameter
    private String[] resolveScopes;

    /**
     * Comma separated list of Artifact names too exclude.
     *
     * @since 1.0
     */
    @Parameter(property = "excludeArtifactIds", defaultValue = "")
    protected String excludeArtifactIds;

    /**
     * Comma separated list of Artifact names to include.
     *
     * @since 1.0
     */
    @Parameter(property = "includeArtifactIds", defaultValue = "")
    protected String includeArtifactIds;

    /**
     * Comma separated list of GroupId Names to exclude.
     *
     * @since 1.0
     */
    @Parameter(property = "excludeGroupIds", defaultValue = "")
    protected String excludeGroupIds;

    /**
     * Comma separated list of GroupIds to include.
     *
     * @since 1.0
     */
    @Parameter(property = "includeGroupIds", defaultValue = "")
    protected String includeGroupIds;

    /**
     * If we should exclude transitive dependencies
     *
     * @since 1.0
     */
    @Parameter(property = "excludeTransitive", defaultValue = "false")
    protected boolean excludeTransitive;

    /**
     */
    @Component(hint = "default")
    protected ProjectDependenciesResolver dependencyResolver;

    private VelocityEngine velocity;

    @SuppressWarnings("unchecked")
    public void execute() throws MojoExecutionException {
        if (skip) {
            getLog().info("Skipping remote resources execution.");
            return;
        }

        if (StringUtils.isEmpty(encoding)) {
            getLog().warn("File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
                    + ", i.e. build is platform dependent!");
        }

        if (runOnlyAtExecutionRoot && !project.isExecutionRoot()) {
            getLog().info(
                    "Skipping remote-resource generation in this project because it's not the Execution Root");
            return;
        }
        if (resolveScopes == null) {
            if (excludeScope == null || "".equals(excludeScope)) {
                resolveScopes = new String[] { this.includeScope };
            } else {
                resolveScopes = new String[] { Artifact.SCOPE_TEST };
            }
        }
        velocity = new VelocityEngine();
        velocity.setProperty(VelocityEngine.RUNTIME_LOG_LOGSYSTEM, this);
        velocity.setProperty("resource.loader", "classpath");
        velocity.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
        velocity.init();

        if (supplementalModels == null) {
            File sups = new File(appendedResourcesDirectory, "supplemental-models.xml");
            if (sups.exists()) {
                try {
                    supplementalModels = new String[] { sups.toURI().toURL().toString() };
                } catch (MalformedURLException e) {
                    // ignore
                    getLog().debug("URL issue with supplemental-models.xml: " + e.toString());
                }
            }
        }

        addSupplementalModelArtifacts();
        locator.addSearchPath(FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath());
        if (appendedResourcesDirectory != null) {
            locator.addSearchPath(FileResourceLoader.ID, appendedResourcesDirectory.getAbsolutePath());
        }
        locator.addSearchPath("url", "");
        locator.setOutputDirectory(new File(project.getBuild().getDirectory()));

        if (includeProjectProperties) {
            final Properties projectProperties = project.getProperties();
            for (Object key : projectProperties.keySet()) {
                properties.put(key.toString(), projectProperties.get(key).toString());
            }
        }

        ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());

            validate();

            List<File> resourceBundleArtifacts = downloadBundles(resourceBundles);
            supplementModels = loadSupplements(supplementalModels);

            VelocityContext context = new VelocityContext(properties);
            configureVelocityContext(context);

            RemoteResourcesClassLoader classLoader = new RemoteResourcesClassLoader(null);

            initalizeClassloader(classLoader, resourceBundleArtifacts);
            Thread.currentThread().setContextClassLoader(classLoader);

            processResourceBundles(classLoader, context);

            try {
                if (outputDirectory.exists()) {
                    // ----------------------------------------------------------------------------
                    // Push our newly generated resources directory into the MavenProject so that
                    // these resources can be picked up by the process-resources phase.
                    // ----------------------------------------------------------------------------
                    Resource resource = new Resource();
                    resource.setDirectory(outputDirectory.getAbsolutePath());
                    // MRRESOURCES-61 handle main and test resources separately
                    if (attachToMain) {
                        project.getResources().add(resource);
                    }
                    if (attachToTest) {
                        project.getTestResources().add(resource);
                    }

                    // ----------------------------------------------------------------------------
                    // Write out archiver dot file
                    // ----------------------------------------------------------------------------
                    File dotFile = new File(project.getBuild().getDirectory(), ".plxarc");
                    FileUtils.mkdir(dotFile.getParentFile().getAbsolutePath());
                    FileUtils.fileWrite(dotFile.getAbsolutePath(), outputDirectory.getName());
                }
            } catch (IOException e) {
                throw new MojoExecutionException("Error creating dot file for archiving instructions.", e);
            }
        } finally {
            Thread.currentThread().setContextClassLoader(origLoader);
        }
    }

    private void addSupplementalModelArtifacts() throws MojoExecutionException {
        if (supplementalModelArtifacts != null && !supplementalModelArtifacts.isEmpty()) {
            List<File> artifacts = downloadBundles(supplementalModelArtifacts);

            for (File artifact : artifacts) {
                if (artifact.isDirectory()) {
                    locator.addSearchPath(FileResourceLoader.ID, artifact.getAbsolutePath());
                } else {
                    try {
                        locator.addSearchPath("jar", "jar:" + artifact.toURI().toURL().toExternalForm());
                    } catch (MalformedURLException e) {
                        throw new MojoExecutionException("Could not use jar " + artifact.getAbsolutePath(), e);
                    }
                }
            }

        }
    }

    @SuppressWarnings("unchecked")
    protected List<MavenProject> getProjects() throws MojoExecutionException {
        List<MavenProject> projects = new ArrayList<MavenProject>();

        // add filters in well known order, least specific to most specific
        FilterArtifacts filter = new FilterArtifacts();

        Set<Artifact> artifacts = resolveProjectArtifacts();
        if (this.excludeTransitive) {
            Set<Artifact> depArtifacts;
            if (runOnlyAtExecutionRoot) {
                depArtifacts = aggregateProjectDependencyArtifacts();
            } else {
                depArtifacts = project.getDependencyArtifacts();
            }
            filter.addFilter(new ProjectTransitivityFilter(depArtifacts, true));
        }

        filter.addFilter(new ScopeFilter(this.includeScope, this.excludeScope));
        filter.addFilter(new GroupIdFilter(this.includeGroupIds, this.excludeGroupIds));
        filter.addFilter(new ArtifactIdFilter(this.includeArtifactIds, this.excludeArtifactIds));

        // perform filtering
        try {
            artifacts = filter.filter(artifacts);
        } catch (ArtifactFilterException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }

        for (Artifact artifact : artifacts) {
            try {
                List<ArtifactRepository> remoteRepo = remoteArtifactRepositories;
                if (artifact.isSnapshot()) {
                    VersionRange rng = VersionRange.createFromVersion(artifact.getBaseVersion());
                    artifact = artifactFactory.createDependencyArtifact(artifact.getGroupId(),
                            artifact.getArtifactId(), rng, artifact.getType(), artifact.getClassifier(),
                            artifact.getScope(), null, artifact.isOptional());
                }

                getLog().debug("Building project for " + artifact);
                MavenProject p;
                try {
                    p = mavenProjectBuilder.buildFromRepository(artifact, remoteRepo, localRepository);
                } catch (InvalidProjectModelException e) {
                    getLog().warn("Invalid project model for artifact [" + artifact.getArtifactId() + ":"
                            + artifact.getGroupId() + ":" + artifact.getVersion() + "]. "
                            + "It will be ignored by the remote resources Mojo.");
                    continue;
                }

                String supplementKey = generateSupplementMapKey(p.getModel().getGroupId(),
                        p.getModel().getArtifactId());

                if (supplementModels.containsKey(supplementKey)) {
                    Model mergedModel = mergeModels(p.getModel(), supplementModels.get(supplementKey));
                    MavenProject mergedProject = new MavenProject(mergedModel);
                    projects.add(mergedProject);
                    mergedProject.setArtifact(artifact);
                    mergedProject.setVersion(artifact.getVersion());
                    getLog().debug(
                            "Adding project with groupId [" + mergedProject.getGroupId() + "] (supplemented)");
                } else {
                    projects.add(p);
                    getLog().debug("Adding project with groupId [" + p.getGroupId() + "]");
                }
            } catch (ProjectBuildingException e) {
                throw new MojoExecutionException(e.getMessage(), e);
            }
        }
        Collections.sort(projects, new ProjectComparator());
        return projects;
    }

    private Set<Artifact> resolveProjectArtifacts() throws MojoExecutionException {
        // CHECKSTYLE_OFF: LineLength
        try {
            if (runOnlyAtExecutionRoot) {
                List<MavenProject> projects = mavenSession.getSortedProjects();
                return dependencyResolver.resolve(projects, Arrays.asList(resolveScopes), mavenSession);
            } else {
                return dependencyResolver.resolve(project, Arrays.asList(resolveScopes), mavenSession);
            }
        } catch (ArtifactResolutionException e) {
            throw new MojoExecutionException(
                    "Failed to resolve dependencies for one or more projects in the reactor. Reason: "
                            + e.getMessage(),
                    e);
        } catch (ArtifactNotFoundException e) {
            throw new MojoExecutionException(
                    "Failed to resolve dependencies for one or more projects in the reactor. Reason: "
                            + e.getMessage(),
                    e);
        }
        // CHECKSTYLE_ON: LineLength
    }

    @SuppressWarnings("unchecked")
    private Set<Artifact> aggregateProjectDependencyArtifacts() throws MojoExecutionException {
        Set<Artifact> artifacts = new LinkedHashSet<Artifact>();

        List<MavenProject> projects = mavenSession.getSortedProjects();
        for (MavenProject p : projects) {
            if (p.getDependencyArtifacts() == null) {
                try {
                    Set<Artifact> depArtifacts = p.createArtifacts(artifactFactory, null, null);

                    if (depArtifacts != null && !depArtifacts.isEmpty()) {
                        for (Artifact artifact : depArtifacts) {
                            if (artifact.getVersion() == null && artifact.getVersionRange() != null) {
                                // Version is required for equality comparison between artifacts,
                                // but it is not needed for our purposes of filtering out
                                // transitive dependencies (which requires only groupId/artifactId).
                                // Therefore set an arbitrary version if missing.
                                artifact.setResolvedVersion(Artifact.LATEST_VERSION);
                            }
                            artifacts.add(artifact);
                        }
                    }
                } catch (InvalidDependencyVersionException e) {
                    throw new MojoExecutionException("Failed to create dependency artifacts for: " + p.getId()
                            + ". Reason: " + e.getMessage(), e);
                }
            }
        }

        return artifacts;
    }

    protected Map<Organization, List<MavenProject>> getProjectsSortedByOrganization(List<MavenProject> projects)
            throws MojoExecutionException {
        Map<Organization, List<MavenProject>> organizations = new TreeMap<Organization, List<MavenProject>>(
                new OrganizationComparator());
        List<MavenProject> unknownOrganization = new ArrayList<MavenProject>();

        for (MavenProject p : projects) {
            if (p.getOrganization() != null && StringUtils.isNotEmpty(p.getOrganization().getName())) {
                List<MavenProject> sortedProjects = organizations.get(p.getOrganization());
                if (sortedProjects == null) {
                    sortedProjects = new ArrayList<MavenProject>();
                }
                sortedProjects.add(p);

                organizations.put(p.getOrganization(), sortedProjects);
            } else {
                unknownOrganization.add(p);
            }
        }
        if (!unknownOrganization.isEmpty()) {
            Organization unknownOrg = new Organization();
            unknownOrg.setName("an unknown organization");
            organizations.put(unknownOrg, unknownOrganization);
        }

        return organizations;
    }

    protected boolean copyResourceIfExists(File file, String relFileName, VelocityContext context)
            throws IOException, MojoExecutionException {
        for (Resource resource : resources) {
            File resourceDirectory = new File(resource.getDirectory());

            if (!resourceDirectory.exists()) {
                continue;
            }

            // TODO - really should use the resource includes/excludes and name mapping
            File source = new File(resourceDirectory, relFileName);
            File templateSource = new File(resourceDirectory, relFileName + TEMPLATE_SUFFIX);

            if (!source.exists() && templateSource.exists()) {
                source = templateSource;
            }

            if (source.exists() && !source.equals(file)) {
                if (source == templateSource) {
                    Reader reader = null;
                    Writer writer = null;
                    DeferredFileOutputStream os = new DeferredFileOutputStream(velocityFilterInMemoryThreshold,
                            file);
                    try {

                        if (encoding != null) {
                            reader = new InputStreamReader(new FileInputStream(source), encoding);
                            writer = new OutputStreamWriter(os, encoding);
                        } else {
                            reader = ReaderFactory.newPlatformReader(source);
                            writer = WriterFactory.newPlatformWriter(os);
                        }

                        velocity.evaluate(context, writer, "", reader);
                    } catch (ParseErrorException e) {
                        throw new MojoExecutionException("Error rendering velocity resource: " + source, e);
                    } catch (MethodInvocationException e) {
                        throw new MojoExecutionException("Error rendering velocity resource: " + source, e);
                    } catch (ResourceNotFoundException e) {
                        throw new MojoExecutionException("Error rendering velocity resource: " + source, e);
                    } finally {
                        IOUtil.close(writer);
                        IOUtil.close(reader);
                    }
                    fileWriteIfDiffers(os);
                } else if (resource.isFiltering()) {

                    MavenFileFilterRequest req = setupRequest(resource, source, file);

                    try {
                        fileFilter.copyFile(req);
                    } catch (MavenFilteringException e) {
                        throw new MojoExecutionException("Error filtering resource: " + source, e);
                    }
                } else {
                    FileUtils.copyFile(source, file);
                }

                // exclude the original (so eclipse doesn't complain about duplicate resources)
                resource.addExclude(relFileName);

                return true;
            }

        }
        return false;
    }

    /**
     * If the transformation result fits in memory and the destination file already exists
     * then both are compared.
     * <p>If destination file is byte-by-byte equal, then it is not overwritten.
     * This improves subsequent compilation times since upstream plugins property see that
     * the resource was not modified.
     * <p>Note: the method should be called after {@link org.apache.commons.io.output.DeferredFileOutputStream#close}
     *
     * @param outStream Deferred stream
     * @throws IOException
     */
    private void fileWriteIfDiffers(DeferredFileOutputStream outStream) throws IOException {
        File file = outStream.getFile();
        if (outStream.isThresholdExceeded()) {
            getLog().info("File " + file + " was overwritten due to content limit threshold "
                    + outStream.getThreshold() + " reached");
            return;
        }
        boolean needOverwrite = true;

        if (file.exists()) {
            InputStream is = new FileInputStream(file);
            InputStream newContents = new ByteArrayInputStream(outStream.getData());
            try {
                needOverwrite = !IOUtil.contentEquals(is, newContents);
                if (getLog().isDebugEnabled()) {
                    getLog().debug("File " + file + " contents " + (needOverwrite ? "differs" : "does not differ"));
                }
            } finally {
                IOUtil.close(is);
            }
        }

        if (!needOverwrite) {
            getLog().info("File " + file + " is up to date");
            return;
        }
        getLog().info("Writing " + file);
        OutputStream os = new FileOutputStream(file);
        try {
            outStream.writeTo(os);
        } finally {
            IOUtil.close(os);
        }
    }

    private MavenFileFilterRequest setupRequest(Resource resource, File source, File file) {
        MavenFileFilterRequest req = new MavenFileFilterRequest();
        req.setFrom(source);
        req.setTo(file);
        req.setFiltering(resource.isFiltering());

        req.setMavenProject(project);
        req.setMavenSession(mavenSession);
        req.setInjectProjectBuildFilters(true);

        if (encoding != null) {
            req.setEncoding(encoding);
        }

        if (filterDelimiters != null && !filterDelimiters.isEmpty()) {
            LinkedHashSet<String> delims = new LinkedHashSet<String>();
            if (useDefaultFilterDelimiters) {
                delims.addAll(req.getDelimiters());
            }

            for (String delim : filterDelimiters) {
                if (delim == null) {
                    delims.add("${*}");
                } else {
                    delims.add(delim);
                }
            }

            req.setDelimiters(delims);
        }

        return req;
    }

    protected void validate() throws MojoExecutionException {
        int bundleCount = 1;

        for (String artifactDescriptor : resourceBundles) {
            // groupId:artifactId:version, groupId:artifactId:version:type
            // or groupId:artifactId:version:type:classifier
            String[] s = StringUtils.split(artifactDescriptor, ":");

            if (s.length < 3 || s.length > 5) {
                String position;

                if (bundleCount == 1) {
                    position = "1st";
                } else if (bundleCount == 2) {
                    position = "2nd";
                } else if (bundleCount == 3) {
                    position = "3rd";
                } else {
                    position = bundleCount + "th";
                }

                throw new MojoExecutionException("The " + position
                        + " resource bundle configured must specify a groupId, artifactId, "
                        + " version and, optionally, type and classifier for a remote resource bundle. "
                        + "Must be of the form <resourceBundle>groupId:artifactId:version</resourceBundle>, "
                        + "<resourceBundle>groupId:artifactId:version:type</resourceBundle> or "
                        + "<resourceBundle>groupId:artifactId:version:type:classifier</resourceBundle>");
            }

            bundleCount++;
        }

    }

    protected void configureVelocityContext(VelocityContext context) throws MojoExecutionException {
        String inceptionYear = project.getInceptionYear();
        String year = new SimpleDateFormat("yyyy").format(new Date());

        if (StringUtils.isEmpty(inceptionYear)) {
            if (getLog().isDebugEnabled()) {
                getLog().debug("inceptionYear not specified, defaulting to " + year);
            }

            inceptionYear = year;
        }
        context.put("project", project);
        List<MavenProject> projects = getProjects();
        context.put("projects", projects);
        context.put("projectsSortedByOrganization", getProjectsSortedByOrganization(projects));
        context.put("presentYear", year);
        context.put("locator", locator);

        if (inceptionYear.equals(year)) {
            context.put("projectTimespan", year);
        } else {
            context.put("projectTimespan", inceptionYear + "-" + year);
        }
    }

    private List<File> downloadBundles(List<String> bundles) throws MojoExecutionException {
        List<File> bundleArtifacts = new ArrayList<File>();

        try {
            for (String artifactDescriptor : bundles) {
                // groupId:artifactId:version[:type[:classifier]]
                String[] s = artifactDescriptor.split(":");

                File artifactFile = null;
                // check if the artifact is part of the reactor
                if (mavenSession != null) {
                    List<MavenProject> list = mavenSession.getSortedProjects();
                    for (MavenProject p : list) {
                        if (s[0].equals(p.getGroupId()) && s[1].equals(p.getArtifactId())
                                && s[2].equals(p.getVersion())) {
                            if (s.length >= 4 && "test-jar".equals(s[3])) {
                                artifactFile = new File(p.getBuild().getTestOutputDirectory());
                            } else {
                                artifactFile = new File(p.getBuild().getOutputDirectory());
                            }
                        }
                    }
                }
                if (artifactFile == null || !artifactFile.exists()) {
                    String type = (s.length >= 4 ? s[3] : "jar");
                    String classifier = (s.length == 5 ? s[4] : null);
                    Artifact artifact = artifactFactory.createDependencyArtifact(s[0], s[1],
                            VersionRange.createFromVersion(s[2]), type, classifier, Artifact.SCOPE_RUNTIME);

                    artifactResolver.resolve(artifact, remoteArtifactRepositories, localRepository);

                    artifactFile = artifact.getFile();
                }
                bundleArtifacts.add(artifactFile);
            }
        } catch (ArtifactResolutionException e) {
            throw new MojoExecutionException("Error downloading resources archive.", e);
        } catch (ArtifactNotFoundException e) {
            throw new MojoExecutionException("Resources archive cannot be found.", e);
        }

        return bundleArtifacts;
    }

    private void initalizeClassloader(RemoteResourcesClassLoader cl, List<File> artifacts)
            throws MojoExecutionException {
        try {
            for (File artifact : artifacts) {
                cl.addURL(artifact.toURI().toURL());
            }
        } catch (MalformedURLException e) {
            throw new MojoExecutionException("Unable to configure resources classloader: " + e.getMessage(), e);
        }
    }

    protected void processResourceBundles(RemoteResourcesClassLoader classLoader, VelocityContext context)
            throws MojoExecutionException {
        InputStreamReader reader = null;

        try {
            // CHECKSTYLE_OFF: LineLength
            for (Enumeration<URL> e = classLoader.getResources(BundleRemoteResourcesMojo.RESOURCES_MANIFEST); e
                    .hasMoreElements();) {
                URL url = e.nextElement();

                try {
                    reader = new InputStreamReader(url.openStream());

                    RemoteResourcesBundleXpp3Reader bundleReader = new RemoteResourcesBundleXpp3Reader();

                    RemoteResourcesBundle bundle = bundleReader.read(reader);

                    for (String bundleResource : bundle.getRemoteResources()) {
                        String projectResource = bundleResource;

                        boolean doVelocity = false;
                        if (projectResource.endsWith(TEMPLATE_SUFFIX)) {
                            projectResource = projectResource.substring(0, projectResource.length() - 3);
                            doVelocity = true;
                        }

                        // Don't overwrite resource that are already being provided.

                        File f = new File(outputDirectory, projectResource);

                        FileUtils.mkdir(f.getParentFile().getAbsolutePath());

                        if (!copyResourceIfExists(f, projectResource, context)) {
                            if (doVelocity) {
                                DeferredFileOutputStream os = new DeferredFileOutputStream(
                                        velocityFilterInMemoryThreshold, f);
                                Writer writer;
                                if (bundle.getSourceEncoding() == null) {
                                    writer = new OutputStreamWriter(os);
                                } else {
                                    writer = new OutputStreamWriter(os, bundle.getSourceEncoding());
                                }

                                try {
                                    if (bundle.getSourceEncoding() == null) {
                                        // TODO: Is this correct? Shouldn't we behave like the rest of maven and fail
                                        // down to JVM default instead ISO-8859-1 ?
                                        velocity.mergeTemplate(bundleResource, "ISO-8859-1", context, writer);
                                    } else {
                                        velocity.mergeTemplate(bundleResource, bundle.getSourceEncoding(), context,
                                                writer);

                                    }
                                } finally {
                                    IOUtil.close(writer);
                                }
                                fileWriteIfDiffers(os);
                            } else {
                                URL resUrl = classLoader.getResource(bundleResource);
                                if (resUrl != null) {
                                    FileUtils.copyURLToFile(resUrl, f);
                                }
                            }
                            File appendedResourceFile = new File(appendedResourcesDirectory, projectResource);
                            File appendedVmResourceFile = new File(appendedResourcesDirectory,
                                    projectResource + ".vm");
                            if (appendedResourceFile.exists()) {
                                final InputStream in = new FileInputStream(appendedResourceFile);
                                final OutputStream append = new FileOutputStream(f, true);

                                try {
                                    IOUtil.copy(in, append);
                                } finally {
                                    IOUtil.close(in);
                                    IOUtil.close(append);
                                }
                            } else if (appendedVmResourceFile.exists()) {
                                PrintWriter writer;
                                FileReader freader = new FileReader(appendedVmResourceFile);

                                if (bundle.getSourceEncoding() == null) {
                                    writer = new PrintWriter(new FileWriter(f, true));
                                } else {
                                    writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f, true),
                                            bundle.getSourceEncoding()));
                                }

                                try {
                                    Velocity.init();
                                    Velocity.evaluate(context, writer, "remote-resources", freader);
                                } finally {
                                    IOUtil.close(writer);
                                    IOUtil.close(freader);
                                }
                            }

                        }
                    }
                } finally {
                    reader.close();
                }
                // CHECKSTYLE_ON: LineLength
            }
        } catch (IOException e) {
            throw new MojoExecutionException("Error finding remote resources manifests", e);
        } catch (XmlPullParserException e) {
            throw new MojoExecutionException("Error parsing remote resource bundle descriptor.", e);
        } catch (Exception e) {
            throw new MojoExecutionException("Error rendering velocity resource.", e);
        }
    }

    protected Model getSupplement(Xpp3Dom supplementModelXml) throws MojoExecutionException {
        MavenXpp3Reader modelReader = new MavenXpp3Reader();
        Model model = null;

        try {
            model = modelReader.read(new StringReader(supplementModelXml.toString()));
            String groupId = model.getGroupId();
            String artifactId = model.getArtifactId();

            if (groupId == null || groupId.trim().equals("")) {
                throw new MojoExecutionException(
                        "Supplemental project XML " + "requires that a <groupId> element be present.");
            }

            if (artifactId == null || artifactId.trim().equals("")) {
                throw new MojoExecutionException(
                        "Supplemental project XML " + "requires that a <artifactId> element be present.");
            }
        } catch (IOException e) {
            getLog().warn("Unable to read supplemental XML: " + e.getMessage(), e);
        } catch (XmlPullParserException e) {
            getLog().warn("Unable to parse supplemental XML: " + e.getMessage(), e);
        }

        return model;
    }

    protected Model mergeModels(Model parent, Model child) {
        inheritanceAssembler.assembleModelInheritance(child, parent);
        return child;
    }

    private static String generateSupplementMapKey(String groupId, String artifactId) {
        return groupId.trim() + ":" + artifactId.trim();
    }

    private Map<String, Model> loadSupplements(String models[]) throws MojoExecutionException {
        if (models == null) {
            getLog().debug("Supplemental data models won't be loaded.  " + "No models specified.");
            return Collections.emptyMap();
        }

        List<Supplement> supplements = new ArrayList<Supplement>();
        for (String set : models) {
            getLog().debug("Preparing ruleset: " + set);
            try {
                File f = locator.getResourceAsFile(set, getLocationTemp(set));

                if (null == f || !f.exists()) {
                    throw new MojoExecutionException("Cold not resolve " + set);
                }
                if (!f.canRead()) {
                    throw new MojoExecutionException("Supplemental data models won't be loaded. " + "File "
                            + f.getAbsolutePath() + " cannot be read, check permissions on the file.");
                }

                getLog().debug("Loading supplemental models from " + f.getAbsolutePath());

                SupplementalDataModelXpp3Reader reader = new SupplementalDataModelXpp3Reader();
                SupplementalDataModel supplementalModel = reader.read(new FileReader(f));
                supplements.addAll(supplementalModel.getSupplement());
            } catch (Exception e) {
                String msg = "Error loading supplemental data models: " + e.getMessage();
                getLog().error(msg, e);
                throw new MojoExecutionException(msg, e);
            }
        }

        getLog().debug("Loading supplements complete.");

        Map<String, Model> supplementMap = new HashMap<String, Model>();
        for (Supplement sd : supplements) {
            Xpp3Dom dom = (Xpp3Dom) sd.getProject();

            Model m = getSupplement(dom);
            supplementMap.put(generateSupplementMapKey(m.getGroupId(), m.getArtifactId()), m);
        }

        return supplementMap;
    }

    /**
     * Convenience method to get the location of the specified file name.
     *
     * @param name the name of the file whose location is to be resolved
     * @return a String that contains the absolute file name of the file
     */
    private String getLocationTemp(String name) {
        String loc = name;
        if (loc.indexOf('/') != -1) {
            loc = loc.substring(loc.lastIndexOf('/') + 1);
        }
        if (loc.indexOf('\\') != -1) {
            loc = loc.substring(loc.lastIndexOf('\\') + 1);
        }
        getLog().debug("Before: " + name + " After: " + loc);
        return loc;
    }

    class OrganizationComparator implements Comparator<Organization> {
        public int compare(Organization org1, Organization org2) {
            int i = compareStrings(org1.getName(), org2.getName());
            if (i == 0) {
                i = compareStrings(org1.getUrl(), org2.getUrl());
            }
            return i;
        }

        public boolean equals(Organization o1, Organization o2) {
            return compare(o1, o2) == 0;
        }

        private int compareStrings(String s1, String s2) {
            if (s1 == null && s2 == null) {
                return 0;
            } else if (s1 == null && s2 != null) {
                return 1;
            } else if (s1 != null && s2 == null) {
                return -1;
            }

            return s1.compareToIgnoreCase(s2);
        }
    }

    class ProjectComparator implements Comparator<MavenProject> {
        public int compare(MavenProject p1, MavenProject p2) {
            return p1.getArtifact().compareTo(p2.getArtifact());
        }

        public boolean equals(MavenProject p1, MavenProject p2) {
            return p1.getArtifact().equals(p2.getArtifact());
        }
    }

    /* LogChute methods */
    public void init(RuntimeServices rs) throws Exception {
    }

    public void log(int level, String message) {
        switch (level) {
        case LogChute.WARN_ID:
            getLog().warn(message);
            break;
        case LogChute.INFO_ID:
            // velocity info messages are too verbose, just consider them as debug messages...
            getLog().debug(message);
            break;
        case LogChute.DEBUG_ID:
            getLog().debug(message);
            break;
        case LogChute.ERROR_ID:
            getLog().error(message);
            break;
        default:
            getLog().debug(message);
            break;
        }
    }

    public void log(int level, String message, Throwable t) {
        switch (level) {
        case LogChute.WARN_ID:
            getLog().warn(message, t);
            break;
        case LogChute.INFO_ID:
            // velocity info messages are too verbose, just consider them as debug messages...
            getLog().debug(message, t);
            break;
        case LogChute.DEBUG_ID:
            getLog().debug(message, t);
            break;
        case LogChute.ERROR_ID:
            getLog().error(message, t);
            break;
        default:
            getLog().debug(message, t);
            break;
        }
    }

    public boolean isLevelEnabled(int level) {
        return false;
    }

}