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

fish.payara.micro.impl.LauncherCreator Maven / Gradle / Ivy

/*
 *    DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 *    Copyright (c) [2020] Payara Foundation and/or its affiliates. All rights reserved.
 *
 *    The contents of this file are subject to the terms of either the GNU
 *    General Public License Version 2 only ("GPL") or the Common Development
 *    and Distribution License("CDDL") (collectively, the "License").  You
 *    may not use this file except in compliance with the License.  You can
 *    obtain a copy of the License at
 *    https://github.com/payara/Payara/blob/master/LICENSE.txt
 *    See the License for the specific
 *    language governing permissions and limitations under the License.
 *
 *    When distributing the software, include this License Header Notice in each
 *    file and include the License file at glassfish/legal/LICENSE.txt.
 *
 *    GPL Classpath Exception:
 *    The Payara Foundation designates this particular file as subject to the "Classpath"
 *    exception as provided by the Payara Foundation in the GPL Version 2 section of the License
 *    file that accompanied this code.
 *
 *    Modifications:
 *    If applicable, add the following below the License Header, with the fields
 *    enclosed by brackets [] replaced by your own identifying information:
 *    "Portions Copyright [year] [name of copyright owner]"
 *
 *    Contributor(s):
 *    If you wish your version of this file to be governed by only the CDDL or
 *    only the GPL Version 2, indicate your decision by adding "[Contributor]
 *    elects to include this software in this distribution under the [CDDL or GPL
 *    Version 2] license."  If you don't indicate a single choice of license, a
 *    recipient has the option to distribute your version of this file under
 *    either the CDDL, the GPL Version 2 or to extend the choice of license to
 *    its licensees as provided above.  However, if you add GPL Version 2 code
 *    and therefore, elected the GPL Version 2 license, then the option applies
 *    only if the new code is made subject to such option by the copyright
 *    holder.
 */

package fish.payara.micro.impl;


import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.stream.Stream;

import static java.util.stream.Collectors.joining;

class LauncherCreator {
    public static final String LAUNCHER_JAR = "launch-micro.jar";
    private static final String MAIN_CLASS = RootDirLauncher.class.getName();

    private final File rootDir;
    private final URLClassLoader classLoader;
    private final String bootJarUrl = System.getProperty(RootDirLauncher.BOOT_JAR_URL);
    private String[] classpath;
    private Attributes bootManifestAttributes;

    LauncherCreator(File root, URLClassLoader microClassLoader) {
        this.rootDir = root;
        this.classLoader = microClassLoader;
    }

    public void buildLauncher() throws IOException {
        buildClasspath();
        buildLauncherJar();
        buildEnvFile();
    }


    /**
     * Collect the classpath entries into form suitable for Class-Path manifest atrribute
     */
    private void buildClasspath() {
        URI baseUri = rootDir.toURI();
        this.classpath = Stream.of(classLoader.getURLs())
                // filter out directories - it prevents OpenJDK CDS (JDK-8209385)
                // and root/runtime/ doesn't contain any direct resources anyway
                .filter(url -> !url.getPath().endsWith("/"))
                .map(url -> relativize(baseUri, url))
                .map(URI::toString)
                .toArray(String[]::new);
    }

    private static URI relativize(URI baseUri, URL url) {
        try {
            return baseUri.relativize(url.toURI());
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("Cannot turn classpath entry into relative path "+url, e);
        }
    }


    /**
     * Create file with parameters to java executable when user cannot use java -jar
     * @throws IOException
     */
    private void buildEnvFile() throws IOException {
        try (PrintWriter envOut = new PrintWriter(new FileWriter(new File(rootDir, ".env")))) {
            envOut.print("MICRO_CLASSPATH=");
            envOut.println(
                    Stream.concat(Stream.of(LAUNCHER_JAR),Stream.of(classpath))
                            .map(LauncherCreator::uriToPath)
                            .collect(joining(File.pathSeparator)));
            // we escape opens so the file can be sourced into shell env easily.
            envOut.print("MICRO_OPENS=\"");
            envOut.print(Stream.concat(buildOpens(), buildExports()).collect(joining(" ")));
            envOut.println('"');
            envOut.print("MICRO_MAIN=");
            envOut.println(MAIN_CLASS);
        }
    }

    private static String uriToPath(String entry) {
        // additional libraries outside runtime are stored as file URIs
        // --class-path argument needs path
        return entry.startsWith("file:") ? new File(URI.create(entry)).getAbsolutePath() : entry;
    }

    private Stream buildOpens() {
        return Stream.of(bootManifestAttributes.getValue("Add-Opens").split(" "))
                .map(open -> "--add-opens "+open+"=ALL-UNNAMED");
    }

    private Stream buildExports() {
        return Stream.of(bootManifestAttributes.getValue("Add-Exports").split(" "))
                .map(export -> "--add-exports "+export+"=ALL-UNNAMED");
    }

    private void buildLauncherJar() throws IOException {
        if (bootJarUrl == null || !bootJarUrl.startsWith("file:")) {
            throw new IllegalArgumentException("Output launcher was not launched via Payara Micro Distribution artifact ("+bootJarUrl+" isn't one)");
        }

        try (JarFile bootJar = new JarFile(new File(URI.create(bootJarUrl)))) {
            this.bootManifestAttributes = bootJar.getManifest().getMainAttributes();
            Manifest mf = buildManifest();
            writeLauncher(bootJar, mf);
        }
    }

    private void writeLauncher(JarFile bootJar, Manifest mf) throws IOException {
        try (JarOutputStream out = new JarOutputStream(new FileOutputStream(new File(rootDir, LAUNCHER_JAR)), mf)) {

            // Boot classes and API are in boot jar and not in runtime directory, we need those
            // But we don't need MICRO-INF, we are already unpacked
            bootJar.stream().filter(entry -> entry.getName().startsWith("fish/"))
                    .forEach(entry -> {
                        try {
                            out.putNextEntry(entry);
                            try (InputStream input = bootJar.getInputStream(entry)) {
                                byte[] buffer = new byte[4096];
                                for (int read = 0; (read = input.read(buffer)) > 0; ) {
                                    out.write(buffer, 0, read);
                                }
                            }
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    });
        }
    }

    private Manifest buildManifest() {
        Manifest result = new Manifest();
        Attributes attr = result.getMainAttributes();
        attr.putAll(bootManifestAttributes);
        attr.putValue("Class-Path", Stream.of(this.classpath).collect(joining(" ")));
        attr.putValue("Main-Class", MAIN_CLASS);
        attr.remove("Start-Class");
        return result;
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy