org.apache.tuscany.maven.zip.AbstractZipMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.tuscany.maven.zip.AbstractZipMojo.java

Source

package org.apache.tuscany.maven.zip;

/*
 * 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 org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.model.Resource;
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.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.InterpolationFilterReader;
import org.codehaus.plexus.util.StringUtils;

import java.io.BufferedReader;
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.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * Based on code from the Maven WAR plugin 2.0.2 AbstractWarMojo
 */
public abstract class AbstractZipMojo extends AbstractMojo {
    /**
     * The maven project.
     *
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    /**
     * The directory containing generated classes.
     *
     * @parameter expression="${project.build.outputDirectory}"
     * @required
     * @readonly
     */
    private File classesDirectory;

    /**
     * Whether a JAR file will be created for the classes in the webapp. Using this optional configuration
     * parameter will make the generated classes to be archived into a jar file
     * and the classes directory will then be excluded from the webapp.
     *
     * @parameter expression="${archiveClasses}" default-value="false"
     */
    private boolean archiveClasses;

    /**
     * The Jar archiver needed for archiving classes directory into jar file.
     *
     * @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#jar}"
     * @required
     */
    private JarArchiver jarArchiver;

    /**
     * The directory where the SCA ZIP contribution is built.
     *
     * @parameter expression="${project.build.directory}/${project.build.finalName}"
     * @required
     */
    private File zipDirectory;

    /**
     * Filters (property files) to include during the interpolation of the pom.xml.
     *
     * @parameter expression="${project.build.filters}"
     */
    private List filters;

    /**
     * Directory to unpack dependent WARs into if needed
     *
     * @parameter expression="${project.build.directory}/war/work"
     * @required
     */
    private File workDirectory;

    /**
     * To look up Archiver/UnArchiver implementations
     *
     * @parameter expression="${component.org.codehaus.plexus.archiver.manager.ArchiverManager}"
     * @required
     */
    protected ArchiverManager archiverManager;

    private static final String META_INF = "META-INF";

    private static final String[] DEFAULT_INCLUDES = { "**/**" };

    /**
     * The comma separated list of tokens to include in the SCA ZIP.
     * Default is '**'.
     *
     * @parameter alias="includes"
     */
    private String warSourceIncludes = "**";

    /**
     * The comma separated list of tokens to exclude from the WAR.
     *
     * @parameter alias="excludes"
     */
    private String warSourceExcludes;

    /**
     * The comma separated list of tokens to include when doing
     * a war overlay.
     * Default is '**'
     *
     * @parameter
     */
    private String dependentWarIncludes = "**";

    /**
     * The comma separated list of tokens to exclude when doing
     * a war overlay.
     *
     * @parameter
     */
    private String dependentWarExcludes;

    /**
     * The maven archive configuration to use.
     *
     * @parameter
     */
    protected MavenArchiveConfiguration archive = new MavenArchiveConfiguration();

    private static final String[] EMPTY_STRING_ARRAY = {};

    public MavenProject getProject() {
        return project;
    }

    public void setProject(MavenProject project) {
        this.project = project;
    }

    public File getClassesDirectory() {
        return classesDirectory;
    }

    public void setClassesDirectory(File classesDirectory) {
        this.classesDirectory = classesDirectory;
    }

    public File getZipDirectory() {
        return zipDirectory;
    }

    public void setScaZipDirectory(File scaZipDirectory) {
        this.zipDirectory = scaZipDirectory;
    }

    /**
     * Returns a string array of the excludes to be used
     * when assembling/copying the war.
     *
     * @return an array of tokens to exclude
     */
    protected String[] getExcludes() {
        List excludeList = new ArrayList();
        if (StringUtils.isNotEmpty(warSourceExcludes)) {
            excludeList.addAll(Arrays.asList(StringUtils.split(warSourceExcludes, ",")));
        }

        return (String[]) excludeList.toArray(EMPTY_STRING_ARRAY);
    }

    /**
     * Returns a string array of the includes to be used
     * when assembling/copying the war.
     *
     * @return an array of tokens to include
     */
    protected String[] getIncludes() {
        return StringUtils.split(StringUtils.defaultString(warSourceIncludes), ",");
    }

    /**
     * Returns a string array of the excludes to be used
     * when adding dependent wars as an overlay onto this war.
     *
     * @return an array of tokens to exclude
     */
    protected String[] getDependentWarExcludes() {
        String[] excludes;
        if (StringUtils.isNotEmpty(dependentWarExcludes)) {
            excludes = StringUtils.split(dependentWarExcludes, ",");
        } else {
            excludes = EMPTY_STRING_ARRAY;
        }
        return excludes;
    }

    /**
     * Returns a string array of the includes to be used
     * when adding dependent wars as an overlay onto this war.
     *
     * @return an array of tokens to include
     */
    protected String[] getDependentWarIncludes() {
        return StringUtils.split(StringUtils.defaultString(dependentWarIncludes), ",");
    }

    public void buildExplodedWebapp(File webappDirectory) throws MojoExecutionException, MojoFailureException {
        getLog().info("Exploding webapp...");

        webappDirectory.mkdirs();

        try {
            buildWebapp(project, webappDirectory);
        } catch (IOException e) {
            throw new MojoExecutionException("Could not explode webapp...", e);
        }
    }

    private Map getBuildFilterProperties() throws MojoExecutionException {

        Map filterProperties = new Properties();

        // System properties
        filterProperties.putAll(System.getProperties());

        // Project properties
        filterProperties.putAll(project.getProperties());

        for (Iterator i = filters.iterator(); i.hasNext();) {
            String filtersfile = (String) i.next();

            try {
                Properties properties = PropertyUtils.loadPropertyFile(new File(filtersfile), true, true);

                filterProperties.putAll(properties);
            } catch (IOException e) {
                throw new MojoExecutionException("Error loading property file '" + filtersfile + "'", e);
            }
        }

        // can't putAll, as ReflectionProperties doesn't enumerate - so we make a composite map with the project variables as dominant
        return new CompositeMap(new ReflectionProperties(project), filterProperties);
    }

    /**
     * Copies webapp webResources from the specified directory.
     * <p/>
     * Note that the <tt>webXml</tt> parameter could be null and may
     * specify a file which is not named <tt>web.xml<tt>. If the file
     * exists, it will be copied to the <tt>META-INF</tt> directory and
     * renamed accordingly.
     *
     * @param resource         the resource to copy
     * @param webappDirectory  the target directory
     * @param filterProperties
     * @throws java.io.IOException if an error occured while copying webResources
     */
    public void copyResources(Resource resource, File webappDirectory, Map filterProperties) throws IOException {
        if (!resource.getDirectory().equals(webappDirectory.getPath())) {
            getLog().info("Copy webapp webResources to " + webappDirectory.getAbsolutePath());
            if (webappDirectory.exists()) {
                String[] fileNames = getWarFiles(resource);
                String targetPath = (resource.getTargetPath() == null) ? "" : resource.getTargetPath();
                File destination = new File(webappDirectory, targetPath);
                for (int i = 0; i < fileNames.length; i++) {
                    if (resource.isFiltering()) {
                        copyFilteredFile(new File(resource.getDirectory(), fileNames[i]),
                                new File(destination, fileNames[i]), null, getFilterWrappers(), filterProperties);
                    } else {
                        copyFileIfModified(new File(resource.getDirectory(), fileNames[i]),
                                new File(destination, fileNames[i]));
                    }
                }
            }
        }
    }

    /**
     * Generates the JAR.
     *
     * @todo Add license files in META-INF directory.
     */
    public void createJarArchive(File libDirectory) throws MojoExecutionException {
        String archiveName = project.getBuild().getFinalName() + ".jar";

        File jarFile = new File(libDirectory, archiveName);

        MavenArchiver archiver = new MavenArchiver();

        archiver.setArchiver(jarArchiver);

        archiver.setOutputFile(jarFile);

        try {
            archiver.getArchiver().addDirectory(classesDirectory, getIncludes(), getExcludes());

            archiver.createArchive(project, archive);
        } catch (Exception e) {
            // TODO: improve error handling
            throw new MojoExecutionException("Error assembling JAR", e);
        }
    }

    /**
     * Builds the webapp for the specified project.
     * <p/>
     * Classes, libraries and tld files are copied to
     * the <tt>webappDirectory</tt> during this phase.
     *
     * @param project         the maven project
     * @param webappDirectory
     * @throws java.io.IOException if an error occured while building the webapp
     */
    public void buildWebapp(MavenProject project, File webappDirectory)
            throws MojoExecutionException, IOException, MojoFailureException {
        getLog().info("Assembling webapp " + project.getArtifactId() + " in " + webappDirectory);

        File metainfDir = new File(webappDirectory, META_INF);
        metainfDir.mkdirs();

        File libDirectory = new File(webappDirectory, "/lib");

        File webappClassesDirectory = new File(webappDirectory, "/");

        if (classesDirectory.exists() && !classesDirectory.equals(webappClassesDirectory)) {
            if (archiveClasses) {
                createJarArchive(libDirectory);
            } else {
                copyDirectoryStructureIfModified(classesDirectory, webappClassesDirectory);
            }
        }

        Set artifacts = project.getArtifacts();

        List duplicates = findDuplicates(artifacts);

        List dependentWarDirectories = new ArrayList();

        for (Iterator iter = artifacts.iterator(); iter.hasNext();) {
            Artifact artifact = (Artifact) iter.next();
            String targetFileName = getDefaultFinalName(artifact);

            getLog().debug("Processing: " + targetFileName);

            if (duplicates.contains(targetFileName)) {
                getLog().debug("Duplicate found: " + targetFileName);
                targetFileName = artifact.getGroupId() + "-" + targetFileName;
                getLog().debug("Renamed to: " + targetFileName);
            }

            // TODO: utilise appropriate methods from project builder
            ScopeArtifactFilter filter = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME);
            if (!artifact.isOptional() && filter.include(artifact)) {
                String type = artifact.getType();
                if ("jar".equals(type) || "ejb".equals(type) || "ejb-client".equals(type)) {
                    copyFileIfModified(artifact.getFile(), new File(libDirectory, targetFileName));
                } else {
                    if ("par".equals(type)) {
                        targetFileName = targetFileName.substring(0, targetFileName.lastIndexOf('.')) + ".jar";

                        getLog().debug(
                                "Copying " + artifact.getFile() + " to " + new File(libDirectory, targetFileName));

                        copyFileIfModified(artifact.getFile(), new File(libDirectory, targetFileName));
                    } else {
                        if ("war".equals(type)) {
                            dependentWarDirectories.add(unpackWarToTempDirectory(artifact));
                        } else {
                            getLog().debug("Skipping artifact of type " + type + " for WEB-INF/lib");
                        }
                    }
                }
            }
        }

        if (dependentWarDirectories.size() > 0) {
            getLog().info("Overlaying " + dependentWarDirectories.size() + " war(s).");

            // overlay dependent wars
            for (Iterator iter = dependentWarDirectories.iterator(); iter.hasNext();) {
                copyDependentWarContents((File) iter.next(), webappDirectory);
            }
        }
    }

    /**
     * Searches a set of artifacts for duplicate filenames and returns a list of duplicates.
     *
     * @param artifacts set of artifacts
     * @return List of duplicated artifacts
     */
    private List findDuplicates(Set artifacts) {
        List duplicates = new ArrayList();
        List identifiers = new ArrayList();
        for (Iterator iter = artifacts.iterator(); iter.hasNext();) {
            Artifact artifact = (Artifact) iter.next();
            String candidate = getDefaultFinalName(artifact);
            if (identifiers.contains(candidate)) {
                duplicates.add(candidate);
            } else {
                identifiers.add(candidate);
            }
        }
        return duplicates;
    }

    /**
     * Unpacks war artifacts into a temporary directory inside <tt>workDirectory</tt>
     * named with the name of the war.
     *
     * @param artifact War artifact to unpack.
     * @return Directory containing the unpacked war.
     * @throws MojoExecutionException
     */
    private File unpackWarToTempDirectory(Artifact artifact) throws MojoExecutionException {
        String name = artifact.getFile().getName();
        File tempLocation = new File(workDirectory, name.substring(0, name.length() - 4));

        boolean process = false;
        if (!tempLocation.exists()) {
            tempLocation.mkdirs();
            process = true;
        } else if (artifact.getFile().lastModified() > tempLocation.lastModified()) {
            process = true;
        }

        if (process) {
            File file = artifact.getFile();
            try {
                unpack(file, tempLocation);
            } catch (NoSuchArchiverException e) {
                this.getLog().info("Skip unpacking dependency file with unknown extension: " + file.getPath());
            }
        }

        return tempLocation;
    }

    /**
     * Unpacks the archive file.
     *
     * @param file     File to be unpacked.
     * @param location Location where to put the unpacked files.
     */
    private void unpack(File file, File location) throws MojoExecutionException, NoSuchArchiverException {
        String archiveExt = FileUtils.getExtension(file.getAbsolutePath()).toLowerCase();

        try {
            UnArchiver unArchiver = archiverManager.getUnArchiver(archiveExt);
            unArchiver.setSourceFile(file);
            unArchiver.setDestDirectory(location);
            unArchiver.setOverwrite(true);
            unArchiver.extract();
        } catch (IOException e) {
            throw new MojoExecutionException("Error unpacking file: " + file + "to: " + location, e);
        } catch (ArchiverException e) {
            throw new MojoExecutionException("Error unpacking file: " + file + "to: " + location, e);
        }
    }

    /**
     * Recursively copies contents of <tt>srcDir</tt> into <tt>targetDir</tt>.
     * This will not overwrite any existing files.
     *
     * @param srcDir    Directory containing unpacked dependent war contents
     * @param targetDir Directory to overlay srcDir into
     */
    private void copyDependentWarContents(File srcDir, File targetDir) throws MojoExecutionException {
        DirectoryScanner scanner = new DirectoryScanner();
        scanner.setBasedir(srcDir);
        scanner.setExcludes(getDependentWarExcludes());
        scanner.addDefaultExcludes();

        scanner.setIncludes(getDependentWarIncludes());

        scanner.scan();

        String[] dirs = scanner.getIncludedDirectories();
        for (int j = 0; j < dirs.length; j++) {
            new File(targetDir, dirs[j]).mkdirs();
        }

        String[] files = scanner.getIncludedFiles();

        for (int j = 0; j < files.length; j++) {
            File targetFile = new File(targetDir, files[j]);

            try {
                targetFile.getParentFile().mkdirs();
                copyFileIfModified(new File(srcDir, files[j]), targetFile);
            } catch (IOException e) {
                throw new MojoExecutionException("Error copying file '" + files[j] + "' to '" + targetFile + "'",
                        e);
            }
        }
    }

    /**
     * Returns a list of filenames that should be copied
     * over to the destination directory.
     *
     * @param sourceDir the directory to be scanned
     * @return the array of filenames, relative to the sourceDir
     */
    private String[] getWarFiles(File sourceDir) {
        DirectoryScanner scanner = new DirectoryScanner();
        scanner.setBasedir(sourceDir);
        scanner.setExcludes(getExcludes());
        scanner.addDefaultExcludes();

        scanner.setIncludes(getIncludes());

        scanner.scan();

        return scanner.getIncludedFiles();
    }

    /**
     * Returns a list of filenames that should be copied
     * over to the destination directory.
     *
     * @param resource the resource to be scanned
     * @return the array of filenames, relative to the sourceDir
     */
    private String[] getWarFiles(Resource resource) {
        DirectoryScanner scanner = new DirectoryScanner();
        scanner.setBasedir(resource.getDirectory());
        if (resource.getIncludes() != null && !resource.getIncludes().isEmpty()) {
            scanner.setIncludes((String[]) resource.getIncludes().toArray(EMPTY_STRING_ARRAY));
        } else {
            scanner.setIncludes(DEFAULT_INCLUDES);
        }
        if (resource.getExcludes() != null && !resource.getExcludes().isEmpty()) {
            scanner.setExcludes((String[]) resource.getExcludes().toArray(EMPTY_STRING_ARRAY));
        }

        scanner.addDefaultExcludes();

        scanner.scan();

        return scanner.getIncludedFiles();
    }

    /**
     * Copy file from source to destination only if source is newer than the target file.
     * If <code>destinationDirectory</code> does not exist, it
     * (and any parent directories) will be created. If a file <code>source</code> in
     * <code>destinationDirectory</code> exists, it will be overwritten.
     *
     * @param source               An existing <code>File</code> to copy.
     * @param destinationDirectory A directory to copy <code>source</code> into.
     * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
     * @throws IllegalArgumentException      if <code>destinationDirectory</code> isn't a directory.
     * @throws java.io.IOException           if <code>source</code> does not exist, the file in
     *                                       <code>destinationDirectory</code> cannot be written to, or an IO error occurs during copying.
     *                                       <p/>
     *                                       TO DO: Remove this method when Maven moves to plexus-utils version 1.4
     */
    private static void copyFileToDirectoryIfModified(File source, File destinationDirectory) throws IOException {
        // TO DO: Remove this method and use the method in WarFileUtils when Maven 2 changes
        // to plexus-utils 1.2.
        if (destinationDirectory.exists() && !destinationDirectory.isDirectory()) {
            throw new IllegalArgumentException("Destination is not a directory");
        }

        copyFileIfModified(source, new File(destinationDirectory, source.getName()));
    }

    private FilterWrapper[] getFilterWrappers() {
        return new FilterWrapper[] {
                // support ${token}
                new FilterWrapper() {
                    public Reader getReader(Reader fileReader, Map filterProperties) {
                        return new InterpolationFilterReader(fileReader, filterProperties, "${", "}");
                    }
                },
                // support @token@
                new FilterWrapper() {
                    public Reader getReader(Reader fileReader, Map filterProperties) {
                        return new InterpolationFilterReader(fileReader, filterProperties, "@", "@");
                    }
                } };
    }

    /**
     * @param from
     * @param to
     * @param encoding
     * @param wrappers
     * @param filterProperties
     * @throws IOException TO DO: Remove this method when Maven moves to plexus-utils version 1.4
     */
    private static void copyFilteredFile(File from, File to, String encoding, FilterWrapper[] wrappers,
            Map filterProperties) throws IOException {
        // buffer so it isn't reading a byte at a time!
        Reader fileReader = null;
        Writer fileWriter = null;
        try {
            // fix for MWAR-36, ensures that the parent dir are created first
            to.getParentFile().mkdirs();

            if (encoding == null || encoding.length() < 1) {
                fileReader = new BufferedReader(new FileReader(from));
                fileWriter = new FileWriter(to);
            } else {
                FileInputStream instream = new FileInputStream(from);

                FileOutputStream outstream = new FileOutputStream(to);

                fileReader = new BufferedReader(new InputStreamReader(instream, encoding));

                fileWriter = new OutputStreamWriter(outstream, encoding);
            }

            Reader reader = fileReader;
            for (int i = 0; i < wrappers.length; i++) {
                FilterWrapper wrapper = wrappers[i];
                reader = wrapper.getReader(reader, filterProperties);
            }

            IOUtil.copy(reader, fileWriter);
        } finally {
            IOUtil.close(fileReader);
            IOUtil.close(fileWriter);
        }
    }

    /**
     * Copy file from source to destination only if source timestamp is later than the destination timestamp.
     * The directories up to <code>destination</code> will be created if they don't already exist.
     * <code>destination</code> will be overwritten if it already exists.
     *
     * @param source      An existing non-directory <code>File</code> to copy bytes from.
     * @param destination A non-directory <code>File</code> to write bytes to (possibly
     *                    overwriting).
     * @throws IOException                   if <code>source</code> does not exist, <code>destination</code> cannot be
     *                                       written to, or an IO error occurs during copying.
     * @throws java.io.FileNotFoundException if <code>destination</code> is a directory
     *                                       <p/>
     *                                       TO DO: Remove this method when Maven moves to plexus-utils version 1.4
     */
    private static void copyFileIfModified(File source, File destination) throws IOException {
        // TO DO: Remove this method and use the method in WarFileUtils when Maven 2 changes
        // to plexus-utils 1.2.
        if (destination.lastModified() < source.lastModified()) {
            FileUtils.copyFile(source.getCanonicalFile(), destination);
            // preserve timestamp
            destination.setLastModified(source.lastModified());
        }
    }

    /**
     * Copies a entire directory structure but only source files with timestamp later than the destinations'.
     * <p/>
     * Note:
     * <ul>
     * <li>It will include empty directories.
     * <li>The <code>sourceDirectory</code> must exists.
     * </ul>
     *
     * @param sourceDirectory
     * @param destinationDirectory
     * @throws IOException TO DO: Remove this method when Maven moves to plexus-utils version 1.4
     */
    private static void copyDirectoryStructureIfModified(File sourceDirectory, File destinationDirectory)
            throws IOException {
        if (!sourceDirectory.exists()) {
            throw new IOException("Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ").");
        }

        File[] files = sourceDirectory.listFiles();

        String sourcePath = sourceDirectory.getAbsolutePath();

        for (int i = 0; i < files.length; i++) {
            File file = files[i];

            String dest = file.getAbsolutePath();

            dest = dest.substring(sourcePath.length() + 1);

            File destination = new File(destinationDirectory, dest);

            if (file.isFile()) {
                destination = destination.getParentFile();

                copyFileToDirectoryIfModified(file, destination);
            } else if (file.isDirectory()) {
                if (!destination.exists() && !destination.mkdirs()) {
                    throw new IOException(
                            "Could not create destination directory '" + destination.getAbsolutePath() + "'.");
                }

                copyDirectoryStructureIfModified(file, destination);
            } else {
                throw new IOException("Unknown file type: " + file.getAbsolutePath());
            }
        }
    }

    /**
     * TO DO: Remove this interface when Maven moves to plexus-utils version 1.4
     */
    private interface FilterWrapper {
        Reader getReader(Reader fileReader, Map filterProperties);
    }

    /**
     * Converts the filename of an artifact to artifactId-version.type format.
     *
     * @param artifact
     * @return converted filename of the artifact
     */
    private String getDefaultFinalName(Artifact artifact) {
        String finalName = artifact.getArtifactId() + "-" + artifact.getVersion();

        String classifier = artifact.getClassifier();
        if ((classifier != null) && !("".equals(classifier.trim()))) {
            finalName += "-" + classifier;
        }

        finalName = finalName + "." + artifact.getArtifactHandler().getExtension();
        return finalName;
    }

}