com.github.wix_maven.PatchMojo.java Source code

Java tutorial

Introduction

Here is the source code for com.github.wix_maven.PatchMojo.java

Source

package com.github.wix_maven;

/*
 * #%L
 * WiX Toolset (Windows Installer XML) Maven Plugin
 * %%
 * Copyright (C) 2013 - 2014 GregDomjan NetIQ
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import java.io.File;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
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.project.ProjectBuildingException;
import org.apache.maven.project.artifact.InvalidDependencyVersionException;
import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.apache.maven.shared.artifact.filter.collection.ProjectTransitivityFilter;
import org.apache.maven.shared.artifact.filter.collection.TypeFilter;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.StreamConsumer;

/**
 * Reference - 
 * transform between different versions for patch (note same format must be used for all files - current 3.6)
 * torch.exe -p -xi Error\Product.wixpdb Fixed\Product.wixpdb -out Patch.wixmst
 * torch.exe -p -xo Error\Product.msi Fixed\Product.msi -out Patch.wixmst
 *  
 * pyro.exe Patch.wixmsp -out Patch.msp -t Sample Patch.wixmst
 */
/**
 * Goal which executes WiX torch & pyro to create msp files.
 * 
 * @goal patch
 * @phase package
 * @requiresProject true
 * @requiresDependencyResolution compile
 */
public class PatchMojo extends AbstractTorchMojo {
    // TODO:  might be good to make baseline + baseArt + patchedArt  an object and make a list of them  to allow multiple changes into 1 patch 
    /**
     * ArtifactItem to use as base. (ArtifactItem contains groupId, artifactId, version, type, classifier) 
     * See <a href="./usage.html">Usage</a> for details.
     *
     * @parameter
     * @required
     */
    private ArtifactItem baseArtifactItem;

    /**
     * ArtifactItem to use as patch. (ArtifactItem contains groupId, artifactId, version, type, classifier) 
     * See <a href="./usage.html">Usage</a> for details.
     *
     * @parameter
     * @required
     */
    private ArtifactItem patchedArtifactItem;

    /**
     * Baseline id... needs to match the baseline in the patch file, why then is it needed...I don't get how this works...
     * Can we just read this from the input xml?
     * 
     * @parameter expression="${wix.baseline}"
     * @required
     */
    protected String baseline;

    /**
     * Properties catch all in case we missed some configuration. Passed directly to pyro
     * 
     * @parameter
     */
    private Properties patchProperties;

    /**
     * Project builder -- builds a model from a pom.xml
     * 
     * @component 
     */
    protected MavenProjectBuilder mavenProjectBuilder;

    public PatchMojo() {
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void addValidationOptions(Commandline cl) {
        cl.addArguments(new String[] { "-t", "patch" });

        if ("wixpdb".equals(patchedArtifactItem.getType()))
            cl.addArguments(new String[] { "-xi" });
        else
            cl.addArguments(new String[] { "-xo" });
    }

    protected void addOtherOptions(Commandline cl) {
        if (patchProperties != null && !patchProperties.isEmpty()) {
            ArrayList<String> result = new ArrayList<String>();

            for (Enumeration<Object> keys = patchProperties.keys(); keys.hasMoreElements();) {
                String key = (String) keys.nextElement();
                result.add(key);
                String value = patchProperties.getProperty(key);
                if (null != value) {
                    result.add(value);
                }
            }

            cl.addArguments(result.toArray(new String[0]));
        }
    }

    @Override
    protected String torchOutputExtension() {
        return "wixmst";
    }

    //   private DependencyTreeBuilder treeBuilder;
    protected Set<Artifact> getJARDependencySets(Artifact inputArtifact) throws MojoExecutionException {
        FilterArtifacts filter = new FilterArtifacts();
        //      filter.addFilter(new ProjectTransitivityFilter(project.getDependencyArtifacts(), true));
        filter.addFilter(new TypeFilter("jar", ""));

        // start with all artifacts.
        //@SuppressWarnings("unchecked")
        //Set<Artifact> artifacts = project.getArtifacts();
        Set<Artifact> artifacts;
        try {
            artifacts = resolveArtifactDependencies(inputArtifact);
        } catch (ArtifactResolutionException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        } catch (ArtifactNotFoundException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        } catch (ProjectBuildingException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        } catch (InvalidDependencyVersionException e) {
            throw new MojoExecutionException(e.getMessage(), e);
        }

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

        return artifacts;
    }

    private void addJARSourceRoots(Commandline cl, Artifact inputArtifact, String bindPathOpt)
            throws MojoExecutionException {
        // TODO: transitive only through direct attached jars...
        Set<Artifact> jarArtifacts = getJARDependencySets(inputArtifact);
        getLog().info("Adding " + jarArtifacts.size() + " dependent JAR paths");
        if (!jarArtifacts.isEmpty()) {
            for (Artifact jar : jarArtifacts) {
                if (null != jar.getFile().getParentFile()) { // file is ment to always be in some folder.. just in case? hacky defensive programming...
                    getLog().debug(String.format("JAR added dependency %1$s", jar.getArtifactId()));
                    // Warn: may need to make artifacts unique using groupId... but nar doesn't do that yet.
                    // when there are multiple jars with the same name,
                    // there is a conflict between requirements for reactor 'compile' build Vs 'install' build that can later be used in a patch, 
                    // the conflict is due to pathing or lack there of from compile not having the package id in the path
                    // so -b option used in linking cannot specify just the local repo, it must include the full path to versioned package folder or 'target'
                    cl.addArguments(
                            new String[] { bindPathOpt, jar.getFile().getParentFile().getAbsolutePath() + "\\" });//.getPath()
                }
            }
        }
    }

    /**
     * Prepare and execute pyro commandline tool
     *  
     * @param pyroTool
     * @param patchInputFile
     * @param transformInputFile
     * @param archOutputFile
     * @throws MojoExecutionException
     */
    protected void pyro(File pyroTool, Artifact baseInputArtifact, Artifact latestInputArtifact,
            File patchInputFile, File transformInputFile, File archOutputFile) throws MojoExecutionException {
        getLog().info(" -- Pyro : " + archOutputFile.getPath());
        Commandline cl = new Commandline();

        cl.setExecutable(pyroTool.getAbsolutePath());
        // cl.setWorkingDirectory(wxsInputDirectory);
        addToolsetGeneralOptions(cl);
        addOtherOptions(cl);

        if ("wixpdb".equals(baseInputArtifact.getType())) { // already checked that artifact types match
            if (narUnpackDirectory.exists()) { // && if any nar dependencies, otherwise it isn't needed
                cl.addArguments(new String[] { "-bt", narUnpackDirectory.getAbsolutePath() + "\\" });//.getPath()
                cl.addArguments(new String[] { "-bu", narUnpackDirectory.getAbsolutePath() + "\\" });//.getPath()
            }
            addJARSourceRoots(cl, baseInputArtifact, "-bt");
            addJARSourceRoots(cl, latestInputArtifact, "-bu");
        }

        //addOptions(cl);
        addWixExtensions(cl);
        cl.addArguments(new String[] { patchInputFile.getAbsolutePath(), "-t", baseline,
                transformInputFile.getAbsolutePath(), "-out", archOutputFile.getAbsolutePath() });
        // addOtherOptions(cl);

        pyro(cl);
    }

    /**
     * Prepare and execute the Uber pyro commandline
     * @param pyroTool
     * @param patchInputFile
     * @param transformInputFiles
     * @param archOutputFile
     * @throws MojoExecutionException
     */
    protected void pyro(File pyroTool, File patchInputFile, Map<String, File> transformInputFiles,
            File archOutputFile) throws MojoExecutionException {
        getLog().info(" -- Pyro : " + archOutputFile.getPath());
        Commandline cl = new Commandline();

        cl.setExecutable(pyroTool.getAbsolutePath());
        // cl.setWorkingDirectory(wxsInputDirectory);
        addToolsetGeneralOptions(cl);

        //      if( narUnpackDirectory.exists() )
        //         allFileSourceRoots.add(narUnpackDirectory.getAbsolutePath());

        //addOptions(cl);
        addWixExtensions(cl);
        cl.addArguments(new String[] { patchInputFile.getAbsolutePath() });
        for (Map.Entry<String, File> entry : transformInputFiles.entrySet()) {
            cl.addArguments(new String[] { "-t", baseline + "_" + entry.getKey().replace('-', '_'),
                    entry.getValue().getAbsolutePath() });
        }
        cl.addArguments(new String[] { "-out", archOutputFile.getAbsolutePath() });
        // addOtherOptions(cl);

        pyro(cl);
    }

    /**
     * Execute the given command line parsing output for pyro comments
     * @param cl
     * @throws MojoExecutionException
     */
    protected void pyro(Commandline cl) throws MojoExecutionException {
        try {
            if (verbose) {
                getLog().info(cl.toString());
            } else {
                getLog().debug(cl.toString());
            }

            // TODO: maybe should report or do something with return value.
            int returnValue = CommandLineUtils.executeCommandLine(cl, new StreamConsumer() {

                public void consumeLine(final String line) {
                    // TODO: pyro specific message handling
                    if (line.contains(" : error ")) {
                        getLog().error(line);
                    } else if (line.contains(" : warning ")) {
                        getLog().warn(line);
                    } else if (verbose) {
                        getLog().info(line);
                    } else {
                        getLog().debug(line);
                    }
                }

            }, new StreamConsumer() {

                public void consumeLine(final String line) {
                    getLog().error(line);
                }

            });

            if (returnValue != 0) {
                throw new MojoExecutionException("Problem executing pyro, return code " + returnValue);
            }
        } catch (CommandLineException e) {
            // throw new MojoExecutionException( "Error running mapping-tools.",
            // e );
            throw new MojoExecutionException("Problem executing pyro", e);
        }
    }

    protected Set<Artifact> getDependencySets() throws MojoExecutionException {
        // add filters in well known order, least specific to most specific
        FilterArtifacts filter = new FilterArtifacts();

        filter.addFilter(new ProjectTransitivityFilter(project.getDependencyArtifacts(), true));

        // filter.addFilter( new ScopeFilter( DependencyUtil.cleanToBeTokenizedString( this.includeScope ),
        // DependencyUtil.cleanToBeTokenizedString( this.excludeScope ) ) );
        //
        // filter.addFilter( new TypeFilter( DependencyUtil.cleanToBeTokenizedString( this.includeTypes ),
        // DependencyUtil.cleanToBeTokenizedString( this.excludeTypes ) ) );
        //
        // filter.addFilter( new ClassifierFilter( DependencyUtil.cleanToBeTokenizedString( this.includeClassifiers ),
        // DependencyUtil.cleanToBeTokenizedString( this.excludeClassifiers ) ) );
        //
        // filter.addFilter( new GroupIdFilter( DependencyUtil.cleanToBeTokenizedString( this.includeGroupIds ),
        // DependencyUtil.cleanToBeTokenizedString( this.excludeGroupIds ) ) );
        //
        // filter.addFilter( new ArtifactIdFilter( DependencyUtil.cleanToBeTokenizedString( this.includeArtifactIds ),
        // DependencyUtil.cleanToBeTokenizedString( this.excludeArtifactIds ) ) );

        filter.addFilter(new TypeFilter(PACK_INSTALL, ""));
        // String clasfilter = arch+"-"+culture+","+arch+"-neutral";
        // getLog().debug(clasfilter);
        // filter.addFilter( new ClassifierFilter( clasfilter, "" ) );

        // start with all artifacts.
        @SuppressWarnings("unchecked")
        Set<Artifact> artifacts = project.getArtifacts();

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

        return artifacts;
    }

    public void execute() throws MojoExecutionException, MojoFailureException {
        if (skip) {
            getLog().info(getClass().getName() + " skipped");
            return;
        }

        File torchTool = validateTool();
        File pyroTool = new File(toolDirectory, "/bin/pyro.exe");
        if (!pyroTool.exists())
            throw new MojoExecutionException("Pyro tool doesn't exist " + pyroTool.getAbsolutePath());

        defaultLocale();

        //Set<Artifact> artifacts = getDependencySets();

        for (String arch : getPlatforms()) {
            // for the Uber patch
            //         Map<String,File> archIntermediateFiles = new HashMap<String, File>();
            for (String culture : culturespecs()) {

                if (!baseArtifactItem.getType().equals(patchedArtifactItem.getType()))
                    throw new MojoExecutionException(
                            "Wix Pyro currently requires that both inputs to the patch are the same type, wixpdb or msi.");

                Artifact baseInputArtifact = getRelatedArtifact(baseArtifactItem, arch, culture,
                        baseArtifactItem.getType());
                Artifact latestInputArtifact = getRelatedArtifact(patchedArtifactItem, arch, culture,
                        patchedArtifactItem.getType());

                File baseInputFile = getRelatedArtifactFiles(baseInputArtifact);
                File latestInputFile = getRelatedArtifactFiles(latestInputArtifact);

                // 
                File archIntermediateFile = getOutput(intDirectory, arch, getPrimaryCulture(culture), "wixmst");
                torch(torchTool, baseInputFile, latestInputFile, archIntermediateFile);

                File archPatchFile = getOutput(arch, culture, "wixmsp"); // output from earlier light
                File archOutputFile = getOutput(arch, culture, getPackageOutputExtension());
                pyro(pyroTool, baseInputArtifact, latestInputArtifact, archPatchFile, archIntermediateFile,
                        archOutputFile);

                // for Uber patch            
                //            archIntermediateFiles.put(culture, archIntermediateFile);
            }
            // Uber patch..
            //         String culture = baseCulturespec();
            //         File patchFile = getOutput(arch, culture, "wixmsp"); // output from earlier light
            //         File outputFile = getOutput(arch, culture, getPackageOutputExtension());
            //         pyro(pyroTool, patchFile, archIntermediateFiles, outputFile);
        }
    }

    /**
     * Based on Maven-dependency-plugin AbstractFromConfigurationMojo.
     * 
     * Resolves the Artifact from the remote repository if necessary. If no version is specified, it will be retrieved from the dependency list or
     * from the DependencyManagement section of the pom.
     * 
     * @param artifactItem
     *            containing information about artifact from plugin configuration.
     * @return Artifact object representing the specified file.
     * @throws MojoExecutionException
     *             with a message if the version can't be found in DependencyManagement.
     */
    protected Artifact getRelatedArtifact(ArtifactItem artifactItem, String arch, String culture, String type)
            throws MojoExecutionException {

        VersionRange vr;
        try {
            vr = VersionRange.createFromVersionSpec(artifactItem.getVersion());
        } catch (InvalidVersionSpecificationException e1) {
            vr = VersionRange.createFromVersion(artifactItem.getVersion());
        }

        Set<Artifact> artifactSet = new HashSet<Artifact>();

        String classifier = arch + "-" + (culture == null ? "neutral" : getPrimaryCulture(culture));
        getArtifact(artifactItem.getGroupId(), artifactItem.getArtifactId(), type, artifactSet, vr, classifier);

        if (artifactSet.size() != 1) // this is more like an assert - we are only asking for one, and if none it already threw.
            throw new MojoExecutionException(String.format("Found multiple artifacts for : %1:%2:%3:%4:%5",
                    artifactItem.getGroupId(), artifactItem.getArtifactId(), type, vr, classifier));

        return artifactSet.iterator().next();
    }

    protected File getRelatedArtifactFiles(Artifact artifact) throws MojoExecutionException {
        if ("wixpdb".equals(artifact.getType()))
            return artifact.getFile();
        throw new MojoExecutionException("Incomplete Mojo - add tools for admin unpacking msi");
        //      else{
        //          File resolvedArtifactFile = getOutput(new File(intDirectory,"base"), arch, culture, "msi");
        //      
        //      throw new MojoExecutionException("Incomplete Mojo - add tools for admin unpacking msi");
        //      // copy artifactFile to resolvedArtifactFile
        //      // unpack resolvedArtifactFile in intdir like 
        //      //msiexec /a %newmsi% TARGETDIR="%workdir%\new" /qb /l*v "%workdir%\logs\new.log" Reboot=ReallySuppres
        //      // return resolvedArtifactFile;
        //   }

    }

    // Maven 3
    //   File repo = this.session.getLocalRepository().getBasedir();
    //   Collection<Artifact> deps = new Aether(this.getProject(), repo).resolve(
    //     new DefaultArtifact("junit", "junit-dep", "", "jar", "4.10"),
    //     JavaScopes.RUNTIME
    //   );
    protected Set<Artifact> resolveDependencyArtifacts(MavenProject theProject)
            throws ArtifactResolutionException, ArtifactNotFoundException, InvalidDependencyVersionException {
        Set<Artifact> artifacts = theProject.createArtifacts(this.factory, null,
                new ScopeArtifactFilter(Artifact.SCOPE_COMPILE));
        // doubt: scope if not null is ignoring the Provided scope elements - all examples show using some scope  

        getLog().info("Checking " + artifacts.size() + " dependents of " + theProject.getId());
        for (Artifact artifact : artifacts) {
            getLog().debug("Checking dependent " + artifact.getId());
            // resolve the new artifact
            this.resolver.resolve(artifact, this.remoteArtifactRepositories, this.localRepository);
        }
        return artifacts;
    }

    /**
     * This method resolves all transitive dependencies of an artifact.
     *
     * @param artifact the artifact used to retrieve dependencies
     * @return resolved set of dependencies
     * @throws ArtifactResolutionException
     * @throws ArtifactNotFoundException
     * @throws ProjectBuildingException
     * @throws InvalidDependencyVersionException
     *
     */
    protected Set<Artifact> resolveArtifactDependencies(Artifact artifact) throws ArtifactResolutionException,
            ArtifactNotFoundException, ProjectBuildingException, InvalidDependencyVersionException {
        Artifact pomArtifact = this.factory.createArtifact(artifact.getGroupId(), artifact.getArtifactId(),
                artifact.getVersion(), "", "pom");

        MavenProject pomProject = mavenProjectBuilder.buildFromRepository(pomArtifact,
                this.remoteArtifactRepositories, this.localRepository);

        //            List<Dependency> dependencies = pomProject.getDependencies();
        //            for( Dependency dep : dependencies ){
        //               getLog().info( "Checking dependent " + dep.getArtifactId() );
        //            factory.createDependencyArtifact(groupId, artifactId, vr, type, classifier, Artifact.SCOPE_COMPILE);
        //            }
        return resolveDependencyArtifacts(pomProject);
    }
}