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

org.rapidoid.plugin.app.AbstractRapidoidMojo Maven / Gradle / Ivy

package org.rapidoid.plugin.app;

/*
 * #%L
 * Rapidoid Build 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 goals, boolean updateSnapshots, Map 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 goals, boolean updateSnapshots, Map properties) throws MojoExecutionException {
		EmbeddedMavenCli cli = new EmbeddedMavenCli(session);
		cli.execute(goals, session.getRequest().getBaseDirectory(), updateSnapshots, properties);
	}

	protected void invokeInstalled(MavenSession session, List goals, boolean updateSnapshots, Map 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 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 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 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.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 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 mainClasses, MavenProject project) {

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

		List namedMain = U.list();
		List 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() {
			@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 mainClasses) {
		mainClasses.addAll(Scan.classpath(path).bytecodeFilter(new Predicate() {
			@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
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy