io.fabric8.vertx.maven.plugin.utils.PackageHelper.java Source code

Java tutorial

Introduction

Here is the source code for io.fabric8.vertx.maven.plugin.utils.PackageHelper.java

Source

/*
 *
 *   Copyright (c) 2016 Red Hat, Inc.
 *
 *   Red Hat 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.
 */

package io.fabric8.vertx.maven.plugin.utils;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.asset.ByteArrayAsset;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.importer.ZipImporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class PackageHelper {

    private final JavaArchive archive;
    private final Attributes.Name MAIN_VERTICLE = new Attributes.Name("Main-Verticle");
    private String mainVerticle;
    private String mainClass;
    private Set<Optional<File>> compileAndRuntimeDeps;
    private Set<Optional<File>> transitiveDeps;
    private Log log;
    private String outputFileName;

    public PackageHelper(String mainClass, String mainVerticle) {
        this.archive = ShrinkWrap.create(JavaArchive.class);
        this.mainClass = mainClass;
        this.mainVerticle = mainVerticle;
    }

    public PackageHelper mainClass(String mainClass) {
        this.mainClass = mainClass;
        return this;
    }

    public PackageHelper mainVerticle(String mainVerticle) {
        this.mainVerticle = mainVerticle;
        return this;
    }

    /**
     * @param dir
     * @param primaryArtifactFile
     * @return
     * @throws IOException
     */
    public File build(Path dir, File primaryArtifactFile) {
        File classes = new File(dir.toFile(), "classes");
        build(classes, primaryArtifactFile);
        return createFatJar(dir);
    }

    /**
     * @param primaryArtifactFile
     */
    private synchronized void build(File classes, File primaryArtifactFile) {
        if (primaryArtifactFile != null && primaryArtifactFile.isFile()) {
            this.archive.as(ZipImporter.class).importFrom(primaryArtifactFile);
        } else if (classes.isDirectory()) {
            this.archive.addAsResource(classes, "/");
        } else {
            throw new RuntimeException("Cannot build the fat jar for the Vert.x application, neither jar file nor "
                    + "classes are present");
        }

        addDependencies();
        generateManifest();
    }

    /**
     *
     */
    protected void addDependencies() {
        Set<Optional<File>> all = new LinkedHashSet<>(compileAndRuntimeDeps);
        all.addAll(transitiveDeps);

        all.stream().filter(Optional::isPresent).forEach(dep -> {
            File f = dep.get();
            if (log.isDebugEnabled()) {
                log.debug("Adding Dependency :" + f.toString());
            }
            this.archive.as(ZipImporter.class).importFrom(f);
        });
    }

    /**
     *
     */
    protected void generateManifest() {
        Manifest manifest = new Manifest();
        Attributes attributes = manifest.getMainAttributes();
        attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
        attributes.put(Attributes.Name.MAIN_CLASS, mainClass);
        //This is a typical situation when application is launched with custom launcher
        if (mainVerticle != null) {
            attributes.put(MAIN_VERTICLE, mainVerticle);
        }

        try {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            manifest.write(bout);
            bout.close();
            byte[] bytes = bout.toByteArray();
            //TODO: merge existing manifest with current one
            this.archive.setManifest(new ByteArrayAsset(bytes));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * @param dir
     * @return
     */
    private synchronized File createFatJar(Path dir) {

        File jarFile = null;

        try {
            jarFile = new File(dir.toFile(), outputFileName);
            boolean useTmpFile = false;
            File theCreatedFile = jarFile;
            if (jarFile.isFile()) {
                useTmpFile = true;
                theCreatedFile = new File(dir.toFile(), outputFileName + "__");
            }

            if (!jarFile.getParentFile().exists() && !jarFile.getParentFile().mkdirs()) {
                log.error("Failed to create parent directories for :" + jarFile.getAbsolutePath());
            }

            ZipExporter zipExporter = this.archive.as(ZipExporter.class);

            try (FileOutputStream jarOut = new FileOutputStream(theCreatedFile)) {
                zipExporter.exportTo(jarOut);
            }

            if (useTmpFile) {
                jarFile.delete();
                theCreatedFile.renameTo(jarFile);
            }

        } catch (Exception e) {
            log.error("Error building fat jar ", e);
        }

        return jarFile;
    }

    /**
     * This method will perform the service provider combination by `combining` contents of same spi
     * across the dependencies
     *
     * @param project          - the Maven project (must not be {@code null}
     * @param backupDir        - the {@link File} path that can be used to perform backups
     * @param targetJarFile    - the vertx fat jar file where the spi files will be updated - typically remove and add
     * @throws MojoExecutionException - any error that might occur while doing relocation
     */
    public void combineServiceProviders(MavenProject project, Path backupDir, File targetJarFile)
            throws MojoExecutionException {

        try {

            Path vertxJarOriginalFile = FileUtils.backup(targetJarFile, backupDir.toFile());

            JavaArchive targetJar = ShrinkWrap.createFromZipFile(JavaArchive.class, vertxJarOriginalFile.toFile());

            List<JavaArchive> archives = Stream.concat(compileAndRuntimeDeps.stream(), transitiveDeps.stream())
                    .filter(Optional::isPresent).map(f -> ShrinkWrap.createFromZipFile(JavaArchive.class, f.get()))
                    .collect(Collectors.toList());

            JavaArchive serviceCombinedArchive = new ServiceCombinerUtil().withLog(log)
                    .withProject(project.getArtifactId(), project.getVersion())
                    .withClassesDirectory(new File(project.getBuild().getOutputDirectory())).combine(archives);

            serviceCombinedArchive.get("/META-INF/services").getChildren().forEach(n -> {
                Asset asset = n.getAsset();
                ArchivePath archivePath = n.getPath();
                if (log.isDebugEnabled()) {
                    try {
                        log.debug("Asset Content: " + FileUtils.read(asset.openStream()));
                        log.debug("Adding asset:" + n.getPath());
                    } catch (IOException e) {
                        // Ignore it.
                    }
                }
                targetJar.delete(archivePath);
                targetJar.add(asset, archivePath);
            });

            //delete old vertx jar file
            Files.deleteIfExists(Paths.get(targetJarFile.toURI()));

            //Create new fat jar with merged SPI
            ZipExporter zipExporter = targetJar.as(ZipExporter.class);

            try (FileOutputStream jarOut = new FileOutputStream(targetJarFile)) {
                zipExporter.exportTo(jarOut);
            }

            org.apache.commons.io.FileUtils.deleteQuietly(vertxJarOriginalFile.toFile());
        } catch (Exception e) {
            throw new MojoExecutionException("Unable to combine SPI files for " + project.getArtifactId(), e);
        }
    }

    /**
     * @param compileAndRuntimeDeps
     * @return
     */

    public PackageHelper compileAndRuntimeDeps(Set<Optional<File>> compileAndRuntimeDeps) {

        this.compileAndRuntimeDeps = compileAndRuntimeDeps;

        return this;
    }

    /**
     * @param transitiveDeps
     * @return
     */

    public PackageHelper transitiveDeps(Set<Optional<File>> transitiveDeps) {

        this.transitiveDeps = transitiveDeps;

        return this;
    }

    /**
     * @param log
     * @return
     */
    public PackageHelper log(Log log) {
        this.log = log;
        return this;
    }

    public PackageHelper withOutputName(String output) {
        this.outputFileName = output;
        return this;
    }
}