All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.seedstack.maven.PackageMojo Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2013-2021, The SeedStack authors 
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.seedstack.maven;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.DefaultMavenProjectHelper;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.plexus.util.IOUtil;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.util.filter.AndDependencyFilter;
import org.eclipse.aether.util.filter.ScopeDependencyFilter;
import org.seedstack.maven.components.resolver.ArtifactResolver;

import java.io.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;

/**
 * Defines the package goal. This goal packages a SeedStack project as a capsule.
 *
 * @author [email protected]
 */
@Mojo(name = "package", threadSafe = true, defaultPhase = LifecyclePhase.PACKAGE)
public class PackageMojo extends AbstractSeedStackMojo {
    private static final String CAPSULE_GROUP_ID = "io.nextflow";
    private static final String CAPSULE_ARTIFACT_ID = "capsule";
    private static final String CAPSULE_CLASS = "Capsule";
    private static final String SEEDSTACK_CAPLET_CLASS = "SeedStackCaplet";
    private static final String PREMAIN_CLASS = "Premain-Class";
    private static final String APPLICATION_CLASS = "Application-Class";
    private static final String APPLICATION_NAME = "Application-Name";
    private static final String ALLOW_SNAPSHOTS = "Allow-Snapshots";
    private static final String JVM_ARGS = "JVM-Args";
    private static final String ENVIRONMENT_VARIABLES = "Environment-Variables";
    private static final String SYSTEM_PROPERTIES = "System-Properties";
    private static final String APP_CLASS_PATH = "App-Class-Path";

    @Parameter(defaultValue = "${project}", required = true, readonly = true)
    private MavenProject mavenProject;

    @Parameter(defaultValue = "${session}", required = true, readonly = true)
    private MavenSession mavenSession;

    @Parameter(defaultValue = "${project.build.finalName}", required = true, readonly = true)
    private String finalName;

    @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
    private File classesDirectory;

    @Parameter(defaultValue = "${project.build.directory}")
    private File outputDirectory;

    @Parameter(property = "capsuleVersion")
    private String capsuleVersion;

    @Parameter(property = "allowSnapshots")
    private String allowSnapshots;

    @Parameter(property = "classpathEntries")
    private List classpathEntries;

    @Parameter(property = "systemProperties")
    private List systemProperties;

    @Parameter(property = "environmentVariables")
    private List environmentVariables;

    @Parameter(property = "jvmArgs")
    private List jvmArgs;

    @Component
    private BuildPluginManager buildPluginManager;

    @Component
    private ArtifactResolver artifactResolver;

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        MavenProjectHelper helper = new DefaultMavenProjectHelper();

        if (capsuleVersion == null) {
            capsuleVersion = artifactResolver.getHighestVersion(mavenProject, CAPSULE_GROUP_ID, CAPSULE_ARTIFACT_ID, false);
        }

        if (!outputDirectory.exists()) {
            boolean success = outputDirectory.mkdirs();
            if (!success) throw new MojoFailureException("Unable to create output directory");
        }

        getLog().info("Packaging SeedStack application using built-in Capsule fork");

        File capsuleFile;
        try {
            capsuleFile = buildStandalone();
        } catch (Exception e) {
            throw new MojoExecutionException("Unable to build standalone Capsule", e);
        }

        helper.attachArtifact(mavenProject, capsuleFile, "capsule");
    }

    private File buildStandalone() throws IOException, MojoExecutionException, ArtifactResolutionException, DependencyResolutionException {
        DependencyFilter dependencyFilter = new DependencyFilter() {
            @Override
            public boolean accept(DependencyNode node, List parents) {
                return "jar".equals(node.getArtifact().getExtension());
            }
        };
        File jarFile = new File(outputDirectory, getOutputName());
        JarOutputStream jarStream = new JarOutputStream(new FileOutputStream(jarFile));

        // Manifest
        Map additionalAttributes = new HashMap<>();
        addManifest(jarStream, additionalAttributes);

        // Main JAR
        File mainJarFile = new File(outputDirectory, finalName + ".jar");
        addToJar(mainJarFile.getName(), new FileInputStream(mainJarFile), jarStream);

        // Dependencies
        for (Artifact artifact : artifactResolver.resolveProjectArtifacts(mavenProject, excludeScopes(dependencyFilter, "test", "provided"))) {
            getLog().debug("Adding " + artifact);
            if (artifact.getFile() == null) {
                throw new MojoExecutionException("Unable to find artifact " + artifact);
            } else {
                addToJar(artifact.getFile().getName(), new FileInputStream(artifact.getFile()), jarStream);
            }
        }

        // Capsule classes
        addCapsuleClasses(jarStream);

        IOUtil.close(jarStream);

        return jarFile;
    }

    private String getOutputName() {
        return String.format("%s-capsule.jar", finalName);
    }

    private void addCapsuleClasses(JarOutputStream jarOutputStream) throws IOException {
        addToJar(getClassFileName(CAPSULE_CLASS), Thread.currentThread().getContextClassLoader().getResourceAsStream(getClassFileName(CAPSULE_CLASS)), jarOutputStream);
        addToJar(getClassFileName(SEEDSTACK_CAPLET_CLASS), Thread.currentThread().getContextClassLoader().getResourceAsStream(getClassFileName(SEEDSTACK_CAPLET_CLASS)), jarOutputStream);
    }

    private String getClassFileName(String className) {
        return String.format("%s.class", className);
    }

    private JarOutputStream addManifest(JarOutputStream jar, Map additionalAttributes) throws IOException {
        Manifest manifestBuild = new Manifest();
        Attributes mainAttributes = manifestBuild.getMainAttributes();

        mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
        mainAttributes.put(Attributes.Name.MAIN_CLASS, SEEDSTACK_CAPLET_CLASS);
        mainAttributes.put(new Attributes.Name(PREMAIN_CLASS), SEEDSTACK_CAPLET_CLASS);
        mainAttributes.put(new Attributes.Name(APPLICATION_CLASS), SeedStackUtils.mainClassName);
        mainAttributes.put(new Attributes.Name(APPLICATION_NAME), this.getOutputName());

        if (allowSnapshots != null) {
            getLog().warn("Allowing SNAPSHOT dependencies in the Capsule");
            mainAttributes.put(new Attributes.Name(ALLOW_SNAPSHOTS), "true");
        }
        if (!isEmpty(classpathEntries)) {
            mainAttributes.put(new Attributes.Name(APP_CLASS_PATH), asSpacedString(classpathEntries));
        }
        if (!isEmpty(systemProperties)) {
            mainAttributes.put(new Attributes.Name(SYSTEM_PROPERTIES), asSpacedString(systemProperties));
        }
        if (!isEmpty(environmentVariables)) {
            mainAttributes.put(new Attributes.Name(ENVIRONMENT_VARIABLES), asSpacedString(environmentVariables));
        }
        if (!isEmpty(jvmArgs)) {
            mainAttributes.put(new Attributes.Name(JVM_ARGS), asSpacedString(jvmArgs));
        }
        if (additionalAttributes != null) {
            for (Map.Entry entry : additionalAttributes.entrySet()) {
                Attributes.Name name = new Attributes.Name(entry.getKey());
                String existingValue = (String) mainAttributes.get(name);
                if (!isBlank(existingValue)) {
                    mainAttributes.put(name, asSpacedString(existingValue, entry.getValue()));
                } else {
                    mainAttributes.put(name, entry.getValue());
                }
            }
        }

        ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
        manifestBuild.write(dataStream);

        return addToJar(JarFile.MANIFEST_NAME, new ByteArrayInputStream(dataStream.toByteArray()), jar);
    }

    private JarOutputStream addToJar(String name, InputStream input, JarOutputStream jar) throws IOException {
        try {
            jar.putNextEntry(new ZipEntry(name));
            IOUtil.copy(input, jar);
            jar.closeEntry();
        } catch (ZipException e) {
            // ignore
        }

        IOUtil.close(input);

        return jar;
    }

    private String asSpacedString(String... values) {
        return asSpacedString(Arrays.asList(values));
    }

    private String asSpacedString(List values) {
        StringBuilder sb = new StringBuilder();
        for (String value : values) {
            if (!isBlank(value)) {
                sb.append(value.trim()).append(" ");
            }
        }
        String result = sb.toString();
        if (result.endsWith(" ")) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    private boolean isBlank(String value) {
        return value == null || value.isEmpty();
    }

    private boolean isEmpty(List values) {
        for (String value : values) {
            if (!isBlank(value)) {
                return false;
            }
        }
        return true;
    }

    private DependencyFilter excludeScopes(DependencyFilter dependencyFilter, String... scopes) {
        if (dependencyFilter != null) {
            return new AndDependencyFilter(dependencyFilter, new ScopeDependencyFilter(scopes));
        } else {
            return new ScopeDependencyFilter(scopes);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy