net.sf.nvn.plugin.BootstrapperMojo.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.nvn.plugin.BootstrapperMojo.java

Source

/*******************************************************************************
 * Copyright (c) 2010, Schley Andrew Kutz All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * - Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * - Neither the name of the Schley Andrew Kutz nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/

package net.sf.nvn.plugin;

import static net.sf.nvn.commons.StringUtils.quote;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.maven.plugin.MojoExecutionException;

/**
 * A MOJO for creating a bootstrapper for an MSI package and its prerequisites.
 * 
 * @goal bootstrap
 * @phase package
 * @description A MOJO for creating a bootstrapper for an MSI package and its
 *              prerequisites.
 * @requiresDependencyResolution
 */
public class BootstrapperMojo extends AbstractExeMojo {
    /**
     * When this parameter is set to true the informational version will use the
     * NVN version instead of the Maven version.
     * 
     * @parameter
     */
    boolean useNvnInformationalVersion;

    /**
     * The bootstrapper's icon file.
     * 
     * @parameter
     * @required
     */
    File icon;

    /**
     * The bootstrapper's license file.
     * 
     * @parameter
     * @required
     */
    File license;

    /**
     * The background image used when displaying the license screen and final
     * screen.
     * 
     * @parameter
     * @required
     */
    File licensePageBackground;

    /**
     * The banner image used when displaying the installer's progress.
     * 
     * @parameter
     * @required
     */
    File installPageBackground;

    /**
     * @parameter
     * @required
     */
    String endPageUrlLink;

    /**
     * @parameter
     * @required
     */
    String endPageUrlText;

    /**
     * The pre-requisite configuration object.
     * 
     * @parameter
     * @required
     */
    InstallPackageConfig installPackageConfig;

    /**
     * @parameter
     */
    RegKeyConfig regKeyConfig;

    /**
     * @parameter
     */
    RegValueConfig regValueConfig;

    /**
     * @parameter
     */
    RunningProcessConfig runningProcessConfig;

    /**
     * @parameter
     */
    ProductRemoverConfig productRemoverConfig;

    /**
     * The name of the output file to create sans extension.
     * 
     * @parameter
     * @required
     */
    String outputFileName;

    /**
     * The name of the product the bootstrapper is responsible for installing.
     * 
     * @parameter
     * @required
     */
    String productName;

    /**
     * Settings this parameter to true embeds a special manifest inside the
     * bootstrapper executable that requests UAC privilege escalation on Windows
     * Vista and newer operating systems.
     * 
     * @parameter default-value="true"
     */
    boolean demandUac;

    private File outProjFile;

    private File manifest;

    private File bootstrapExe;

    @Override
    int getExecutions() {
        return 2;
    }

    protected boolean skipExec(int execution) {
        return execution == 1 && !this.demandUac;
    }

    @Override
    String getArgs(int execution) {
        if (execution == 0) {
            return quote(this.outProjFile.toString());
        } else {
            return String.format("-manifest %s -outputresource:%s;#1", quote(getPath(this.manifest)),
                    quote(getPath(this.bootstrapExe)));
        }
    }

    @Override
    File getCommand(int execution) {
        if (execution == 0) {
            return new File("msbuild.exe");
        } else {
            return new File("mt.exe");
        }
    }

    @Override
    void preExecute() throws MojoExecutionException {
        // Create a temporary directory for the project files.
        File tmpDir = new File(System.getProperty("java.io.tmpdir"));
        this.tmpProjDir = new File(tmpDir, UUID.randomUUID().toString());

        if (!this.tmpProjDir.mkdir()) {
            throw new MojoExecutionException("Error creating project directory: " + tmpProjDir);
        }

        // Copy the icon file.
        File appIco = new File(tmpProjDir, "app.ico");
        try {
            FileUtils.copyFile(this.icon, appIco);
        } catch (IOException e) {
            throw new MojoExecutionException(
                    String.format("Error copying icon file from %s to %s", this.icon, appIco), e);
        }

        initInstallPackagesFileParts();
        initProjFile();
        initResFile();
        initAssemblyInfoFile();
        initInstallResourcesFile();
        initEtcFiles();
    }

    private File tmpProjDir;

    private String getString(File file) {
        return file.getPath().replaceAll("\\\\", "\\\\\\\\");
    }

    private void initInstallPackagesFileParts() throws MojoExecutionException {
        for (int x = 0; x < this.installPackageConfig.installPackages.size(); ++x) {
            InstallPackage ip = this.installPackageConfig.installPackages.get(x);

            int chunks = calcChunks(ip);
            ip.fileParts = new File[chunks];

            FileInputStream fis;

            try {
                fis = new FileInputStream(ip.file);
            } catch (FileNotFoundException e) {
                throw new MojoExecutionException("Error creating input stream for: " + ip.file);
            }

            for (int y = 0; y < chunks; ++y) {
                byte[] page = new byte[4096];
                int read = 0;
                int total = 0;

                try {
                    ip.fileParts[y] = new File(super.mavenProject.getBuild().getDirectory(),
                            String.format("InstallPackage%02d_%02d", x, y));

                    ip.fileParts[y].delete();

                    FileOutputStream fos = new FileOutputStream(ip.fileParts[y]);

                    while ((read = fis.read(page)) != -1) {
                        fos.write(page, 0, read);

                        total += read;

                        if (total >= CHUNK_SIZE) {
                            break;
                        }
                    }

                    fos.close();
                } catch (IOException e) {
                    throw new MojoExecutionException("Error chunking file: " + ip.file);
                }
            }

            try {
                fis.close();
            } catch (IOException e) {
                throw new MojoExecutionException("Error closing : " + ip.file);
            }
        }
    }

    private void initEtcFiles() throws MojoExecutionException {
        initFile("Properties/Resources.Designer.cs");
        initFile("RunningProcess.cs");
        initFile("InstallManager.cs");
        initFile("InstallPackage.cs");
        initFile("Program.cs");
        initFile("RegUtils.cs");
        initFile("RegValue.cs");
        initFile("RegKey.cs");
        initFile("MsiUtil.cs");
        initFile("ProductRemover.cs");
        initFile("InstallCheck.cs");
        initFile("ExtensionAttribute.cs");

        initFile("BeginAndEndPage.cs");
        initFile("BeginAndEndPage.Designer.cs");
        initFile("BeginAndEndPage.resx");

        initFile("EndPage.cs");
        initFile("EndPage.Designer.cs");
        initFile("EndPage.resx");

        initFile("Page.cs");
        initFile("Page.Designer.cs");
        initFile("Page.resx");

        initFile("LicensePage.cs");
        initFile("LicensePage.Designer.cs");
        initFile("LicensePage.resx");

        initFile("InstallPage.cs");
        initFile("InstallPage.Designer.cs");
        initFile("InstallPage.resx");

        initFile("MainForm.cs");
        initFile("MainForm.Designer.cs");
        initFile("MainForm.resx");
    }

    private void initFile(String path) throws MojoExecutionException {
        InputStream stream = this.getClass().getResourceAsStream("/nvnbootstrapper/" + path);

        if (stream == null) {
            throw new MojoExecutionException("Error getting stream for '/nvnbootstrapper/" + path + "'");
        }

        String content;

        try {
            content = IOUtils.toString(stream);
        } catch (IOException e) {
            throw new MojoExecutionException("Error getting string for " + path, e);
        }

        File outFile = new File(this.tmpProjDir, path);

        try {
            FileUtils.writeStringToFile(outFile, content);
        } catch (IOException e) {
            throw new MojoExecutionException("Error writing data to: " + outFile, e);
        }
    }

    private void initProjFile() throws MojoExecutionException {
        InputStream stream = this.getClass().getResourceAsStream("/nvnbootstrapper/NvnBootstrapper.csproj");

        if (stream == null) {
            throw new MojoExecutionException("Error getting stream for '/nvnbootstrapper/NvnBootstrapper.csproj'");
        }

        String content;

        try {
            content = IOUtils.toString(stream);
        } catch (IOException e) {
            throw new MojoExecutionException("Error getting string for NvnBootstrapper.csproj", e);
        }

        content = content.replaceAll("\\$\\{BuildConfig\\}", getBuildConfig());
        content = content.replaceAll("\\$\\{ProjectGuid\\}", UUID.randomUUID().toString());
        content = content.replaceAll("\\$\\{AssemblyName\\}", this.outputFileName);

        StringBuilder buff = new StringBuilder();

        buff.append(String.format("<None Include=\"%s\" />\n", getString(this.icon)));

        buff.append(String.format("<None Include=\"%s\" />\n", getString(this.license)));

        buff.append(String.format("<None Include=\"%s\" />\n", getString(this.licensePageBackground)));

        buff.append(String.format("<None Include=\"%s\" />\n", getString(this.installPageBackground)));

        for (InstallPackage ip : this.installPackageConfig.installPackages) {
            for (File filePart : ip.fileParts) {
                buff.append(String.format("    <None Include=\"%s\" />\r\n", getString(filePart)));
            }
        }

        content = content.replaceAll("\\$\\{Resources\\}", buff.toString());

        this.outProjFile = new File(this.tmpProjDir, "NvnBootstrapper.csproj");

        try {
            FileUtils.writeStringToFile(this.outProjFile, content);
        } catch (IOException e) {
            throw new MojoExecutionException("Error writing data to: " + this.outProjFile, e);
        }

        info("Created project file at " + this.outProjFile);
    }

    private void initResFile() throws MojoExecutionException {
        InputStream stream = this.getClass().getResourceAsStream("/nvnbootstrapper/Properties/Resources.resx");

        if (stream == null) {
            throw new MojoExecutionException(
                    "Error getting stream for '/nvnbootstrapper/Properties/Resources.resx'");
        }

        String content;

        try {
            content = IOUtils.toString(stream);
        } catch (IOException e) {
            throw new MojoExecutionException("Error getting string for Resources.resx", e);
        }

        content = content.replaceAll("\\$\\{Icon\\}", getString(this.icon));
        content = content.replaceAll("\\$\\{License\\}", getString(this.license));
        content = content.replaceAll("\\$\\{LicensePageBackground\\}", getString(this.licensePageBackground));
        content = content.replaceAll("\\$\\{InstallPageBackground\\}", getString(this.installPageBackground));

        StringBuilder buff = new StringBuilder();

        for (InstallPackage ip : this.installPackageConfig.installPackages) {
            for (File filePart : ip.fileParts) {
                String dataEntry = String.format(
                        "  <data name=\"%s\" " + "type=\"System.Resources.ResXFileRef, "
                                + "System.Windows.Forms\"><value>%s;System.Byte[], "
                                + "mscorlib, Version=2.0.0.0,Culture=neutral, "
                                + "PublicKeyToken=b77a5c561934e089</value></data>\r\n",
                        FilenameUtils.getBaseName(filePart.toString()), getString(filePart));
                buff.append(dataEntry);
            }
        }

        content = content.replaceAll("\\$\\{InstallPackages\\}", buff.toString());

        File outFile = new File(this.tmpProjDir, "Properties\\Resources.resx");

        try {
            FileUtils.writeStringToFile(outFile, content);
        } catch (IOException e) {
            throw new MojoExecutionException("Error writing data to: " + outFile, e);
        }
    }

    private void initAssemblyInfoFile() throws MojoExecutionException {
        InputStream stream = this.getClass().getResourceAsStream("/nvnbootstrapper/Properties/AssemblyInfo.cs");

        if (stream == null) {
            throw new MojoExecutionException(
                    "Error getting stream for '/nvnbootstrapper/Properties/AssemblyInfo.cs'");
        }

        String content;

        try {
            content = IOUtils.toString(stream);
        } catch (IOException e) {
            throw new MojoExecutionException("Error getting string for AssemblyInfo.cs", e);
        }

        content = content.replaceAll("\\$\\{AssemblyTitle\\}", this.productName);
        content = content.replaceAll("\\$\\{AssemblyProduct\\}", this.productName);
        content = content.replaceAll("\\$\\{AssemblyCompany\\}", super.mavenProject.getOrganization().getName());
        content = content.replaceAll("\\$\\{Year\\}", new SimpleDateFormat("yyyy").format(new Date()).toString());
        content = content.replaceAll("\\$\\{AssemblyGuid\\}", UUID.randomUUID().toString());
        content = content.replaceAll("\\$\\{NvnVersion\\}", super.getNvnVersion().toString());

        if (this.useNvnInformationalVersion) {
            content = content.replaceAll("\\$\\{NvnVersionWithPrefixAndSuffix\\}",
                    super.getNvnVersion().toString());
        } else {
            content = content.replaceAll("\\$\\{NvnVersionWithPrefixAndSuffix\\}",
                    super.getNvnVersion().toStringWithPrefixAndSuffix());
        }

        File outFile = new File(this.tmpProjDir, "Properties\\AssemblyInfo.cs");

        try {
            FileUtils.writeStringToFile(outFile, content);
        } catch (IOException e) {
            throw new MojoExecutionException("Error writing data to: " + outFile, e);
        }
    }

    private void initInstallResourcesFile() throws MojoExecutionException {
        InputStream stream = this.getClass().getResourceAsStream("/nvnbootstrapper/InstallResources.cs");

        if (stream == null) {
            throw new MojoExecutionException("Error getting stream for '/nvnbootstrapper/InstallResources.cs'");
        }

        String content;

        try {
            content = IOUtils.toString(stream);
        } catch (IOException e) {
            throw new MojoExecutionException("Error getting string for InstallResources.cs", e);
        }

        content = content.replaceAll("\\$\\{ProductName\\}", this.productName);
        content = content.replaceAll("\\$\\{EndPageUrlLink\\}", this.endPageUrlLink);
        content = content.replaceAll("\\$\\{EndPageUrlText\\}", this.endPageUrlText);

        StringBuilder buff = new StringBuilder();

        if (this.productRemoverConfig == null) {
            buff.append("public static ProductRemover[] ProductRemovers = new ProductRemovers[0];\r\n");
        } else {
            buff.append("public static ProductRemover[] ProductRemovers = new[]\r\n");
            buff.append("{\r\n");

            for (ProductRemover pr : this.productRemoverConfig.productRemovers) {
                String l = String.format(
                        "new ProductRemover {ProductCode=@\"%s\", Name=@\"%s\", Message=@\"%s\"},\r\n",
                        pr.productCode, pr.name, pr.message);
                buff.append(l);
            }

            buff.append("};\r\n");
        }

        if (this.regKeyConfig == null) {
            buff.append("public static RegKey[] RegKeys = new RegKey[0];\r\n");
        } else {
            buff.append("public static RegKey[] RegKeys = new[]\r\n");
            buff.append("{\r\n");

            for (RegKey rk : this.regKeyConfig.regKeys) {
                buff.append(rk.toString());
            }

            buff.append("};\r\n");
        }

        if (this.regValueConfig == null) {
            buff.append("public static RegValue[] RegValues = new RegValue[0];\r\n");
        } else {
            buff.append("public static RegValue[] RegValues = new[]\r\n");
            buff.append("{\r\n");

            for (RegValue rv : this.regValueConfig.regValues) {
                buff.append(rv.toString());
            }

            buff.append("};\r\n");
        }

        if (this.runningProcessConfig == null) {
            buff.append("public static RunningProcess[] RunningProcesses = new RunningProcess[0];\r\n");
        } else {
            buff.append("public static RunningProcess[] RunningProcesses = new[]\r\n");
            buff.append("{\r\n");

            for (RunningProcess rp : this.runningProcessConfig.runningProcesses) {
                buff.append(rp.toString());
            }

            buff.append("};\r\n");
        }

        buff.append("public static InstallPackage[] InstallPackages = new[]\r\n");
        buff.append("{\r\n");

        for (InstallPackage ip : this.installPackageConfig.installPackages) {
            buff.append(ip.toString());
        }

        buff.append("};\r\n");

        content = content.replaceAll("\\$\\{InstallResources\\}", buff.toString());

        File outFile = new File(this.tmpProjDir, "InstallResources.cs");

        try {
            FileUtils.writeStringToFile(outFile, content);
        } catch (IOException e) {
            throw new MojoExecutionException("Error writing data to: " + outFile, e);
        }
    }

    private final static int CHUNK_SIZE = 10485760;

    private static int calcChunks(InstallPackage ip) {
        double fsizeB = ip.file.length();
        double chunks = Math.ceil(fsizeB / (double) CHUNK_SIZE);
        return (int) chunks;
    }

    @Override
    void postExecute(MojoExecutionException executionException) throws MojoExecutionException {
        if (executionException != null) {
            publishTeamCityArtifact(this.bootstrapExe);
        }
    }

    @Override
    void postExec(int execution, Process process) throws MojoExecutionException {
        if (execution == 0 && process.exitValue() == 0) {
            File tmpBootstrapExe = new File(
                    String.format("%s\\bin\\%s\\%s.exe", this.tmpProjDir, getBuildConfig(), this.outputFileName));

            this.bootstrapExe = new File(super.mavenProject.getBuild().getDirectory(), tmpBootstrapExe.getName());

            try {
                FileUtils.copyFile(tmpBootstrapExe, bootstrapExe);
            } catch (IOException e) {
                throw new MojoExecutionException(
                        String.format("Error copying %s to %s", tmpBootstrapExe, bootstrapExe), e);
            }

            /*
             * try { FileUtils.deleteDirectory(this.tmpProjDir); } catch
             * (IOException e) { throw new
             * MojoExecutionException("Error deleting " + this.tmpProjDir, e); }
             */

            if (this.demandUac) {
                final String format = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n"
                        + "<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\r\n"
                        + "   <assemblyIdentity version=\"%1$s\" processorArchitecture=\"X86\" name=\"%2$s\" type=\"win32\"/>\r\n"
                        + "    <description>%3$s</description>\r\n"
                        + "      <trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v3\">\r\n"
                        + "         <security>\r\n" + "            <requestedPrivileges>\r\n"
                        + "               <requestedExecutionLevel level=\"requireAdministrator\"/>\r\n"
                        + "            </requestedPrivileges>\r\n" + "         </security>\r\n"
                        + "      </trustInfo>\r\n" + "</assembly>";

                String exePath = bootstrapExe.toString();
                String exeBaseName = FilenameUtils.getBaseName(exePath);
                String exeExtension = FilenameUtils.getExtension(exePath);

                String text = String.format(format, super.getNvnVersion(), exeBaseName,
                        super.mavenProject.getDescription());

                File tmpDir = new File(System.getProperty("java.io.tmpdir"));

                this.manifest = new File(tmpDir, String.format("%s.%s.manifest", exeBaseName, exeExtension));

                try {
                    FileUtils.writeStringToFile(this.manifest, text);
                } catch (IOException e) {
                    throw new MojoExecutionException("Error writing manifest file: " + this.manifest);
                }

                debug("wrote manifest file: " + this.manifest);
            }
        } else if (execution == 0 && process.exitValue() != 0) {
            debug("disabling demanduac because bootstrapper exit code: %s", process.exitValue());
            this.demandUac = false;
        }
    }

    @Override
    String getMojoName() {
        return "bootstrapper";
    }

    @Override
    boolean shouldExecute() throws MojoExecutionException {
        return true;
    }

    @Override
    boolean isProjectTypeValid() {
        return true;
    }
}