org.rapidoid.plugin.app.AbstractRapidoidMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.rapidoid.plugin.app.AbstractRapidoidMojo.java

Source

package org.rapidoid.plugin.app;

/*
 * #%L
 * Rapidoid App Plugin
 * %%
 * Copyright (C) 2014 - 2017 Nikolche Mihajlovski and contributors
 * %%
 * Licensed 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.
 * #L%
 */

import javassist.Modifier;
import javassist.bytecode.ClassFile;
import javassist.bytecode.MethodInfo;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.invoker.*;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.http.HttpReq;
import org.rapidoid.http.HttpResp;
import org.rapidoid.io.IO;
import org.rapidoid.lambda.Predicate;
import org.rapidoid.log.GlobalCfg;
import org.rapidoid.scan.Scan;
import org.rapidoid.u.U;
import org.rapidoid.util.Msc;
import org.rapidoid.util.MscOpts;

import java.io.*;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.*;
import java.nio.file.attribute.FileAttribute;
import java.util.*;

@Authors("Nikolche Mihajlovski")
@Since("5.3.0")
public abstract class AbstractRapidoidMojo extends AbstractMojo {

    protected static final String ABORT = "Aborting the build!";

    protected void failIf(boolean failureCondition, String msg, Object... args) throws MojoExecutionException {
        if (failureCondition) {
            throw new MojoExecutionException(U.frmt(msg, args));
        }
    }

    protected boolean request(HttpReq req) {
        HttpResp resp = req.execute();

        switch (resp.code()) {
        case 200:
            return true;

        case 404:
            getLog().error(U.frmt("Couldn't find: %s", req.url()));
            return false;

        default:
            String msg = "Unexpected response received from: %s! Response code: %s, full response:\n\n%s\n";
            getLog().error(U.frmt(msg, req.url(), resp.code(), resp.body()));
            return false;
        }
    }

    protected String createTempFile(String prefix, String suffix, FileAttribute<?>... attrs)
            throws MojoExecutionException {
        String assemblyFile;

        try {
            assemblyFile = Files.createTempFile(prefix, suffix, attrs).toAbsolutePath().toString();

        } catch (IOException e) {
            throw new MojoExecutionException("Couldn't create temporary file! " + ABORT, e);
        }

        return assemblyFile;
    }

    protected String createTempDir(String prefix, FileAttribute<?>... attrs) throws MojoExecutionException {
        String assemblyFile;

        try {
            assemblyFile = Files.createTempDirectory(prefix, attrs).toAbsolutePath().toString();

        } catch (IOException e) {
            throw new MojoExecutionException("Couldn't create temporary directory! " + ABORT, e);
        }

        return assemblyFile;
    }

    protected void invoke(MavenSession session, List<String> goals, boolean updateSnapshots,
            Map<String, String> properties) throws MojoExecutionException {
        if (MscOpts.hasMavenEmbedder() && GlobalCfg.get("maven.home") == null) {
            invokeEmbedded(session, goals, updateSnapshots, properties);
        } else {
            invokeInstalled(session, goals, updateSnapshots, properties);
        }
    }

    protected void invokeEmbedded(MavenSession session, List<String> goals, boolean updateSnapshots,
            Map<String, String> properties) throws MojoExecutionException {
        EmbeddedMavenCli cli = new EmbeddedMavenCli(session);
        cli.execute(goals, session.getRequest().getBaseDirectory(), updateSnapshots, properties);
    }

    protected void invokeInstalled(MavenSession session, List<String> goals, boolean updateSnapshots,
            Map<String, String> properties) throws MojoExecutionException {

        InvocationRequest request = new DefaultInvocationRequest();

        request.setPomFile(session.getRequest().getPom());
        request.setGoals(goals);
        request.setAlsoMake(true);
        request.setUpdateSnapshots(updateSnapshots);

        Properties props = new Properties();
        props.putAll(properties);
        request.setProperties(props);

        Invoker invoker = new DefaultInvoker();

        boolean success;

        try {
            InvocationResult result = invoker.execute(request);
            success = result.getExitCode() == 0 && result.getExecutionException() == null;

        } catch (MavenInvocationException e) {
            throw new MojoExecutionException("Invocation error! " + ABORT, e);
        }

        failIf(!success, "An error occurred. " + ABORT);
    }

    protected String buildUberJar(MavenProject project, MavenSession session) throws MojoExecutionException {

        List<String> goals = U.list("package", "org.apache.maven.plugins:maven-assembly-plugin:2.6:single");

        String assemblyFile = createTempFile("app-assembly-", ".xml");

        IO.save(assemblyFile, IO.load("uber-jar.xml"));

        Map<String, String> properties = U.map();
        properties.put("skipTests", "true");
        properties.put("descriptor", assemblyFile);
        properties.put("assembly.appendAssemblyId", "true");
        properties.put("assembly.attach", "false");

        invoke(session, goals, false, properties);

        boolean deleted = new File(assemblyFile).delete();
        if (!deleted)
            getLog().warn("Couldn't delete the temporary assembly descriptor file!");

        List<String> appJars = IO.find("*-uber-jar.jar").in(project.getBuild().getDirectory()).getNames();

        failIf(appJars.size() != 1, "Cannot find the deployment JAR (found %s candidates)! " + ABORT,
                appJars.size());

        String uberJar = U.first(appJars);

        try {
            Path uberJarPath = Paths.get(uberJar);
            Path appJar = uberJarPath.getParent().resolve("app.jar");
            Files.move(uberJarPath, appJar, StandardCopyOption.REPLACE_EXISTING);
            uberJar = appJar.toFile().getAbsolutePath();
        } catch (IOException e) {
            throw new MojoExecutionException("Couldn't rename the file! " + ABORT, e);
        }

        String mainClass = findMainClass(project);
        getLog().info("");
        getLog().info("The main class is: " + mainClass);

        try {
            addJarManifest(uberJar, project, mainClass);
        } catch (IOException e) {
            throw new MojoExecutionException("Couldn't add the JAR manifest! " + ABORT, e);
        }

        String size = Msc.fileSizeReadable(uberJar);

        getLog().info("");
        getLog().info("Successfully packaged the application with dependencies:");
        getLog().info(U.frmt("%s (size: %s).", uberJar, size));
        getLog().info("");

        return uberJar;
    }

    private void addJarManifest(String uberJar, MavenProject project, String mainClass) throws IOException {
        Path path = Paths.get(uberJar);
        URI uri = URI.create("jar:" + path.toUri());

        String user = System.getProperty("user.name");

        String manifestContent = IO.load("manifest-template.mf").replace("$user", user)
                .replace("$java", Msc.javaVersion()).replace("$name", project.getName())
                .replace("$version", project.getVersion()).replace("$groupId", project.getGroupId())
                .replace("$organization",
                        project.getOrganization() != null ? U.or(project.getOrganization().getName(), "?") : "?")
                .replace("$url", U.or(project.getUrl(), "?")).replace("$main", U.safe(mainClass));

        try (FileSystem fs = FileSystems.newFileSystem(uri, U.<String, Object>map())) {
            Path manifest = fs.getPath("META-INF/MANIFEST.MF");
            try (Writer writer = Files.newBufferedWriter(manifest, StandardCharsets.UTF_8,
                    StandardOpenOption.CREATE)) {
                writer.write(manifestContent);
            }
        }
    }

    protected String findMainClass(MavenProject project) {
        List<String> mainClasses = U.list();

        try {
            for (String path : project.getCompileClasspathElements()) {

                if (new File(path).isDirectory()) {
                    getLog().info("Scanning classpath directory: " + path);
                    scanForMainClass(path, mainClasses);

                } else if (!path.endsWith(".jar")) {
                    getLog().warn("Ignoring classpath entry: " + path);
                }
            }

        } catch (Exception e) {
            throw U.rte(e);
        }

        switch (mainClasses.size()) {
        case 0:
            getLog().warn("Couldn't find the main class!");
            return null;

        case 1:
            return U.first(mainClasses);

        default:
            getLog().warn("Found multiple main classes, trying to pick the right one: " + mainClasses);
            return pickMainClass(mainClasses, project);
        }
    }

    private String pickMainClass(List<String> mainClasses, MavenProject project) {

        // the.group.id.Main
        String byGroupId = project.getGroupId() + ".Main";
        if (mainClasses.contains(byGroupId))
            return byGroupId;

        List<String> namedMain = U.list();
        List<String> withGroupIdPkg = U.list();

        for (String name : mainClasses) {
            if (name.equals("Main"))
                return "Main";

            if (name.endsWith(".Main")) {
                namedMain.add(name);
            }

            if (name.startsWith(project.getGroupId() + ".")) {
                withGroupIdPkg.add(name);
            }
        }

        // the.group.id.foo.bar.Main
        getLog().info("Candidates by group ID: " + withGroupIdPkg);
        if (withGroupIdPkg.size() == 1)
            return U.single(withGroupIdPkg);

        // foo.bar.Main
        getLog().info("Candidates named Main: " + namedMain);
        if (namedMain.size() == 1)
            return U.single(namedMain);

        namedMain.retainAll(withGroupIdPkg);
        getLog().info("Candidates by group ID - named Main: " + namedMain);
        if (namedMain.size() == 1)
            return U.single(namedMain);

        // the.group.id.foo.bar.Main (the shortest name)
        Collections.sort(withGroupIdPkg, new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return s1.length() - s2.length();
            }
        });
        getLog().info("Candidates by group ID - picking one with the shortest name: " + withGroupIdPkg);

        return U.first(withGroupIdPkg);
    }

    private static void scanForMainClass(String path, Collection<String> mainClasses) {
        mainClasses.addAll(Scan.classpath(path).bytecodeFilter(new Predicate<InputStream>() {
            @Override
            public boolean eval(InputStream input) throws Exception {
                return hasMainMethod(new ClassFile(new DataInputStream(input)));
            }
        }).getAll());
    }

    private static boolean hasMainMethod(ClassFile cls) {
        int flags = cls.getAccessFlags();

        if (Modifier.isInterface(flags) || Modifier.isAnnotation(flags) || Modifier.isEnum(flags))
            return false;

        for (Object m : cls.getMethods()) {
            if (m instanceof MethodInfo) {
                if (isMainMethod((MethodInfo) m))
                    return true;
            }
        }

        return false;
    }

    private static boolean isMainMethod(MethodInfo method) {
        int flags = method.getAccessFlags();

        return method.getName().equals("main") && Modifier.isPublic(flags) && Modifier.isStatic(flags)
                && U.eq(method.getDescriptor(), "([Ljava/lang/String;)V"); // TODO find more elegant solution
    }

}