/* * Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 David Berkman * * This file is part of the SmallMind Code Project. * * The SmallMind Code Project is free software, you can redistribute * it and/or modify it under either, at your discretion... * * 1) The terms of GNU Affero General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * ...or... * * 2) The terms of the Apache License, Version 2.0. * * The SmallMind Code Project is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License or Apache License for more details. * * You should have received a copy of the GNU Affero General Public License * and the Apache License along with the SmallMind Code Project. If not, see * <http://www.gnu.org/licenses/> or <http://www.apache.org/licenses/LICENSE-2.0>. * * Additional permission under the GNU Affero GPL version 3 section 7 * ------------------------------------------------------------------ * If you modify this Program, or any covered work, by linking or * combining it with other code, such other code is not for that reason * alone subject to any of the requirements of the GNU Affero GPL * version 3. */ package org.smallmind.spark.tanukisoft.mojo; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.nio.channels.FileChannel; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import freemarker.template.Configuration; import freemarker.template.Template; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; 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.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.smallmind.nutsnbolts.freemarker.ClassPathTemplateLoader; import org.smallmind.nutsnbolts.maven.CompressionType; // Generates Tanukisoft based os service wrappers @Mojo(name = "generate-wrapper", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true) public class GenerateWrapperMojo extends AbstractMojo { private static final EnvironmentArgument[] NO_ENV_ARGS = new EnvironmentArgument[0]; private static final String[] NO_SIMPLE_ARGS = new String[0]; private static final String RESOURCE_BASE_PATH = GenerateWrapperMojo.class.getPackage().getName().replace('.', '/'); @Parameter(readonly = true, property = "project") private MavenProject project; @Parameter private File licenseFile; @Parameter private Dependency[] dependencies; @Parameter(required = true) private String operatingSystem; @Parameter(required = true) private String wrapperListener; @Parameter(defaultValue = "application") private String applicationDir; @Parameter(property = "project.artifactId") private String applicationName; @Parameter(property = "project.name") private String applicationLongName; @Parameter(property = "project.description") private String applicationDescription; @Parameter private String[] jvmArgs; @Parameter(defaultValue = "0") private int jvmInitMemoryMB; @Parameter(defaultValue = "0") private int jvmMaxMemoryMB; @Parameter private String runAs; @Parameter private String withPassword; @Parameter private String umask; @Parameter(defaultValue = "0") private int waitAfterStartup; @Parameter(defaultValue = "java") private String javaCommand; @Parameter private String[] appParameters; @Parameter private EnvironmentArgument[] envArgs; @Parameter private String[] serviceDependencies; @Parameter private String[] configurations; @Parameter private String[] toSourceFiles; @Parameter(defaultValue = "false") private boolean useUpstart; @Parameter(defaultValue = "false") private boolean useSystemD; @Parameter(defaultValue = "zip") private String compression; @Parameter(defaultValue = "true") private boolean createArtifact; @Parameter(defaultValue = "true") private boolean includeVersion; @Parameter(defaultValue = "true") private boolean compactClasspath; @Parameter(defaultValue = "false") private boolean verbose; @Parameter(defaultValue = "false") private boolean skip; public void execute () throws MojoExecutionException, MojoFailureException { if (!skip) { File binDirectory; File libDirectory; File confDirectory; OSType osType; CompressionType compressionType; HashMap<String, Object> freemarkerMap; LinkedList<String> classpathElementList; List<Dependency> additionalDependencies; Iterator<Dependency> additionalDependencyIter; StringBuilder aggregateFilesToSource; try { osType = OSType.valueOf(operatingSystem.replace('-', '_').toUpperCase()); if (verbose) { getLog().info(String.format("Using os type(%s)...", osType.name())); } } catch (Exception exception) { throw new MojoExecutionException(String.format("Unknown operating system type(%s) - valid choices are %s", operatingSystem, Arrays.toString(OSType.values())), exception); } try { compressionType = CompressionType.valueOf(compression.replace('-', '_').toUpperCase()); if (verbose) { getLog().info(String.format("Using compression type(%s)...", compressionType.name())); } } catch (Exception exception) { throw new MojoExecutionException(String.format("Unknown compression type(%s) - valid choices are %s", compression, Arrays.toString(CompressionType.values())), exception); } createDirectory("bin", binDirectory = new File(project.getBuild().getDirectory() + System.getProperty("file.separator") + applicationDir + System.getProperty("file.separator") + constructArtifactName(includeVersion, false) + System.getProperty("file.separator") + "bin")); createDirectory("lib", libDirectory = new File(project.getBuild().getDirectory() + System.getProperty("file.separator") + applicationDir + System.getProperty("file.separator") + constructArtifactName(includeVersion, false) + System.getProperty("file.separator") + "lib")); createDirectory("conf", confDirectory = new File(project.getBuild().getDirectory() + System.getProperty("file.separator") + applicationDir + System.getProperty("file.separator") + constructArtifactName(includeVersion, false) + System.getProperty("file.separator") + "conf")); if (licenseFile != null) { try { if (verbose) { getLog().info(String.format("Copying license file(%s)...", licenseFile.getAbsolutePath())); } copyToDestination(new FileInputStream(licenseFile), confDirectory.getAbsolutePath(), licenseFile.getName()); } catch (IOException ioException) { throw new MojoExecutionException("Problem in copying your license file into the application conf directory", ioException); } } if (configurations != null) { for (String configuration : configurations) { File configurationFile = new File(configuration); try { if (verbose) { getLog().info(String.format("Copying configuration(%s)...", configurationFile.getName())); } copyToDestination(configurationFile, confDirectory.getAbsolutePath(), configurationFile.getName()); } catch (IOException ioException) { throw new MojoExecutionException(String.format("Problem in copying the configuration(%s) into the application conf directory", configurationFile.getAbsolutePath()), ioException); } } } aggregateFilesToSource = new StringBuilder(); if (toSourceFiles != null) { for (String toSourceFile : toSourceFiles) { if (aggregateFilesToSource.length() > 0) { aggregateFilesToSource.append(';'); } aggregateFilesToSource.append(toSourceFile); } } freemarkerMap = new HashMap<>(); freemarkerMap.put("applicationName", applicationName); freemarkerMap.put("applicationLongName", applicationLongName); freemarkerMap.put("applicationDescription", (applicationDescription != null) ? applicationDescription : String.format("%s generated project", GenerateWrapperMojo.class.getSimpleName())); freemarkerMap.put("javaCommand", javaCommand); freemarkerMap.put("wrapperListener", wrapperListener); freemarkerMap.put("jvmArgs", (jvmArgs != null) ? jvmArgs : NO_SIMPLE_ARGS); freemarkerMap.put("envArgs", (envArgs != null) ? envArgs : NO_ENV_ARGS); if (jvmInitMemoryMB > 0) { freemarkerMap.put("jvmInitMemoryMB", jvmInitMemoryMB); } if (jvmMaxMemoryMB > 0) { freemarkerMap.put("jvmMaxMemoryMB", jvmMaxMemoryMB); } if ((runAs != null) && (runAs.length() > 0)) { freemarkerMap.put("runAs", runAs); } if ((withPassword != null) && (withPassword.length()) > 0) { freemarkerMap.put("withPassword", withPassword); } if ((umask != null) && (umask.length() > 0)) { freemarkerMap.put("umask", umask); } freemarkerMap.put("useUpstart", useUpstart); freemarkerMap.put("useSystemD", useSystemD); freemarkerMap.put("waitAfterStartup", String.valueOf(waitAfterStartup)); if (appParameters == null) { freemarkerMap.put("appParameters", new String[] {wrapperListener}); } else { String[] modifiedAppParameters = new String[appParameters.length + 1]; modifiedAppParameters[0] = wrapperListener; System.arraycopy(appParameters, 0, modifiedAppParameters, 1, appParameters.length); freemarkerMap.put("appParameters", modifiedAppParameters); } freemarkerMap.put("serviceDependencies", (serviceDependencies != null) ? serviceDependencies : NO_SIMPLE_ARGS); classpathElementList = new LinkedList<String>(); freemarkerMap.put("classpathElements", classpathElementList); if (compactClasspath) { classpathElementList.add("*"); } freemarkerMap.put("filesToSource", aggregateFilesToSource.toString()); additionalDependencies = (dependencies != null) ? Arrays.asList(dependencies) : null; for (Artifact artifact : project.getRuntimeArtifacts()) { try { if (verbose) { getLog().info(String.format("Copying dependency(%s)...", artifact.getFile().getName())); } if (additionalDependencies != null) { additionalDependencyIter = additionalDependencies.iterator(); while (additionalDependencyIter.hasNext()) { if (additionalDependencyIter.next().matchesArtifact(artifact)) { additionalDependencyIter.remove(); } } } if (!compactClasspath) { classpathElementList.add(artifact.getFile().getName()); } copyToDestination(artifact.getFile(), libDirectory.getAbsolutePath(), artifact.getFile().getName()); } catch (IOException ioException) { throw new MojoExecutionException(String.format("Problem in copying a dependency(%s) into the application library", artifact), ioException); } } if (additionalDependencies != null) { for (Dependency dependency : additionalDependencies) { for (Artifact artifact : project.getDependencyArtifacts()) { if (dependency.matchesArtifact(artifact)) { try { if (verbose) { getLog().info(String.format("Copying additional dependency(%s)...", artifact.getFile().getName())); } if (!compactClasspath) { classpathElementList.add(artifact.getFile().getName()); } copyToDestination(artifact.getFile(), libDirectory.getAbsolutePath(), artifact.getFile().getName()); } catch (IOException ioException) { throw new MojoExecutionException(String.format("Problem in copying an additional dependency(%s) into the application library", artifact), ioException); } } } } } if (!project.getArtifact().getType().equals("jar")) { File jarFile; jarFile = new File(constructCompressedArtifactPath(project.getBuild().getDirectory(), CompressionType.JAR, false)); try { if (verbose) { getLog().info(String.format("Creating and copying output jar(%s)...", jarFile.getName())); } CompressionType.JAR.compress(jarFile, new File(project.getBuild().getOutputDirectory())); if (!compactClasspath) { classpathElementList.add(jarFile.getName()); } copyToDestination(jarFile, libDirectory.getAbsolutePath(), jarFile.getName()); } catch (IOException ioException) { throw new MojoExecutionException(String.format("Problem in creating or copying the output jar(%s) into the application library", jarFile.getName()), ioException); } } else { try { if (verbose) { getLog().info(String.format("Copying build artifact(%s)...", project.getArtifact().getFile().getName())); } if (!compactClasspath) { classpathElementList.add(project.getArtifact().getFile().getName()); } copyToDestination(project.getArtifact().getFile(), libDirectory.getAbsolutePath(), project.getArtifact().getFile().getName()); } catch (IOException ioException) { throw new MojoExecutionException(String.format("Problem in copying the build artifact(%s) into the application library", project.getArtifact()), ioException); } } try { if (verbose) { getLog().info(String.format("Copying wrapper library(%s)...", osType.getLibrary())); } copyToDestination(getResourceAsStream(getWrapperFilePath("lib", osType.getLibrary())), libDirectory.getAbsolutePath(), osType.getLibrary()); copyToDestination(getResourceAsStream(getWrapperFilePath("lib", osType.getLibrary())), libDirectory.getAbsolutePath(), osType.getOsStyle().getLibrary()); } catch (IOException ioException) { throw new MojoExecutionException(String.format("Problem in copying the wrapper library(%s) into the application library", osType.getLibrary()), ioException); } try { if (verbose) { getLog().info(String.format("Copying wrapper executable(%s)...", osType.getExecutable())); } copyToDestination(getResourceAsStream(getWrapperFilePath("bin", osType.getExecutable())), binDirectory.getAbsolutePath(), osType.getExecutable()); } catch (IOException ioException) { throw new MojoExecutionException(String.format("Problem in copying the wrapper executable(%s) into the application binaries", osType.getExecutable()), ioException); } try { if (verbose) { getLog().info("Copying wrapper scripts..."); } switch (osType.getOsStyle()) { case UNIX: processFreemarkerTemplate(getWrapperFilePath("bin", "freemarker.sh.script.in"), binDirectory, applicationName + ".sh", freemarkerMap); break; case WINDOWS: copyToDestination(getResourceAsStream(getWrapperFilePath("bin", "AppCommand.bat.in")), binDirectory.getAbsolutePath(), applicationName + ".bat"); copyToDestination(getResourceAsStream(getWrapperFilePath("bin", "InstallApp-NT.bat.in")), binDirectory.getAbsolutePath(), "Install" + applicationName + "-NT.bat"); copyToDestination(getResourceAsStream(getWrapperFilePath("bin", "UninstallApp-NT.bat.in")), binDirectory.getAbsolutePath(), "Uninstall" + applicationName + "-NT.bat"); break; default: throw new MojoExecutionException(String.format("Unknown os style(%s)", osType.getOsStyle().name())); } } catch (IOException ioException) { throw new MojoExecutionException("Problem in copying the wrapper scripts into the application binaries", ioException); } if (verbose) { getLog().info("Processing the configuration template..."); } processFreemarkerTemplate(getWrapperFilePath("conf", "freemarker.wrapper.conf.in"), confDirectory, "wrapper.conf", freemarkerMap); if (createArtifact) { File compressedFile; compressedFile = new File(constructCompressedArtifactPath(project.getBuild().getDirectory(), compressionType, true)); try { if (verbose) { getLog().info(String.format("Creating aggregated %s(%s)...", compressionType.getExtension(), compressedFile.getName())); } compressionType.compress(compressedFile, new File(project.getBuild().getDirectory() + System.getProperty("file.separator") + applicationDir)); } catch (IOException ioException) { throw new MojoExecutionException(String.format("Problem in creating the aggregated %s(%s)", compressionType.getExtension(), compressedFile.getName()), ioException); } } } } private String constructArtifactName (boolean includeVersion, boolean aggregateArtifact) { StringBuilder nameBuilder; nameBuilder = new StringBuilder(applicationName); if (includeVersion) { nameBuilder.append('-').append(project.getVersion()); } if (project.getArtifact().getClassifier() != null) { nameBuilder.append('-').append(project.getArtifact().getClassifier()); } if (aggregateArtifact) { nameBuilder.append("-app"); } return nameBuilder.toString(); } private String constructCompressedArtifactPath (String outputPath, CompressionType artifactCompressionType, boolean aggregateArtifact) { return new StringBuilder(outputPath).append(System.getProperty("file.separator")).append(constructArtifactName(true, aggregateArtifact)).append('.').append(artifactCompressionType.getExtension()).toString(); } private void processFreemarkerTemplate (String templatePath, File outputDir, String destinationName, HashMap<String, Object> interpolationMap) throws MojoExecutionException { Configuration freemarkerConf; Template freemarkerTemplate; FileWriter fileWriter; freemarkerConf = new Configuration(); freemarkerConf.setTagSyntax(freemarker.template.Configuration.SQUARE_BRACKET_TAG_SYNTAX); freemarkerConf.setTemplateLoader(new ClassPathTemplateLoader(GenerateWrapperMojo.class)); try { freemarkerTemplate = freemarkerConf.getTemplate(templatePath); } catch (IOException ioException) { throw new MojoExecutionException(String.format("Unable to load template(%s) for translation", destinationName), ioException); } try { fileWriter = new FileWriter(outputDir.getAbsolutePath() + System.getProperty("file.separator") + destinationName); } catch (IOException ioException) { throw new MojoExecutionException(String.format("Problem in creating a writer for the template(%s) file", destinationName), ioException); } try { freemarkerTemplate.process(interpolationMap, fileWriter); } catch (Exception exception) { throw new MojoExecutionException(String.format("Problem in processing the template(%s)", destinationName), exception); } try { fileWriter.close(); } catch (IOException ioException) { throw new MojoExecutionException(String.format("Problem in closing the template(%s) writer", destinationName), ioException); } } private void createDirectory (String dirType, File dirFile) throws MojoExecutionException { if (!dirFile.isDirectory()) { if (!dirFile.mkdirs()) { throw new MojoExecutionException(String.format("Unable to create the '%s' application directory(%s)", dirType, dirFile.getAbsolutePath())); } } } private String getWrapperFilePath (String dirType, String fileName) { StringBuilder pathBuilder; pathBuilder = new StringBuilder(RESOURCE_BASE_PATH); pathBuilder.append('/'); pathBuilder.append(dirType); pathBuilder.append('/'); pathBuilder.append(fileName); return pathBuilder.toString(); } private InputStream getResourceAsStream (String filePath) throws MojoExecutionException { InputStream inputStream; if ((inputStream = GenerateWrapperMojo.class.getClassLoader().getResourceAsStream(filePath)) == null) { throw new MojoExecutionException(String.format("Unable to find resource at the specified path(%s)", filePath)); } return inputStream; } private void copyToDestination (File file, String destinationPath, String destinationName) throws IOException { FileInputStream inputStream; FileOutputStream outputStream; FileChannel readChannel; FileChannel writeChannel; long bytesTransferred; long currentPosition = 0; readChannel = (inputStream = new FileInputStream(file)).getChannel(); writeChannel = (outputStream = new FileOutputStream(destinationPath + System.getProperty("file.separator") + destinationName)).getChannel(); while ((currentPosition < readChannel.size()) && (bytesTransferred = readChannel.transferTo(currentPosition, 8192, writeChannel)) >= 0) { currentPosition += bytesTransferred; } outputStream.close(); inputStream.close(); } private void copyToDestination (InputStream inputStream, String destinationPath, String destinationName) throws IOException { FileOutputStream outputStream; byte[] buffer = new byte[8192]; int bytesRead; outputStream = new FileOutputStream(destinationPath + System.getProperty("file.separator") + destinationName); while ((bytesRead = inputStream.read(buffer)) >= 0) { outputStream.write(buffer, 0, bytesRead); } outputStream.close(); inputStream.close(); } }