au.net.causal.maven.plugins.boxdb.JavaRunner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of boxdb-maven-plugin Show documentation
Show all versions of boxdb-maven-plugin Show documentation
Maven plugin to start databases using Docker and VMs
package au.net.causal.maven.plugins.boxdb;
import au.net.causal.maven.plugins.boxdb.db.RunnerDependency;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.DependencyResolutionException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Runs Java classes with static main methods or other methods against objects. Uses Maven to look up and
* resolve dependencies.
*/
public class JavaRunner
{
private final List extends Path> jarFiles;
private final String mainClassName;
private final ClassLoaderCache classLoaderCache;
private Thread runnerThread;
/**
* Creates a runner.
*
* @param jarFiles list of JAR files that will form the classpath for looking up the main class.
* @param mainClassName binary name of the class to load.
* @param classLoaderCache classloader cache used to avoid repeatedly creating classloaders from the same
* dependencies.
*
* @throws NullPointerException if any parameter is null.
*/
public JavaRunner(List extends Path> jarFiles, String mainClassName, ClassLoaderCache classLoaderCache)
{
Objects.requireNonNull(jarFiles, "jarFiles == null");
Objects.requireNonNull(mainClassName, "mainClassName == null");
Objects.requireNonNull(classLoaderCache, "classLoaderCache == null");
this.jarFiles = new ArrayList<>(jarFiles);
this.mainClassName = mainClassName;
this.classLoaderCache = classLoaderCache;
}
/**
* Creates a runner from dependencies.
*
* @param mainClassName the binary name of the class.
* @param dependencies dependencies that will form the classpath used to load the main class.
* @param repositorySystem used for looking up dependencies with Maven.
* @param repositorySystemSession used for looking up dependencies with Maven.
* @param remoteRepositories used for looking up dependencies with Maven.
* @param classLoaderCache classloader cache used to avoid repeatedly creating classloaders from the same
* dependencies.
*
* @return the created runner.
*
* @throws DependencyResolutionException if required dependencies could not be found by Maven.
*/
public static JavaRunner createFromDependencies(String mainClassName,
List extends RunnerDependency> dependencies,
RepositorySystem repositorySystem,
RepositorySystemSession repositorySystemSession,
List remoteRepositories,
ClassLoaderCache classLoaderCache)
throws DependencyResolutionException
{
return new JavaRunner(DependencyUtils.resolveDependencies(dependencies, repositorySystem, repositorySystemSession, remoteRepositories),
mainClassName, classLoaderCache);
}
/**
* Creates the class object using reflection using the Java runner's specifications.
*
* @return the created class.
*
* @throws IOException if an I/O error occurs.
* @throws ClassNotFoundException if the class could not be found.
*/
public Class> makeClass()
throws IOException, ClassNotFoundException
{
List urls = new ArrayList<>(jarFiles.size());
for (Path jarFile : jarFiles)
{
urls.add(jarFile.toUri().toURL());
}
URL[] urlArray = urls.toArray(new URL[urls.size()]);
URLClassLoader loader = classLoaderCache.create(urlArray);
return Class.forName(mainClassName, true, loader);
}
/**
* Creates the main class then looks up the main method using reflection.
*
* @return the main method of the main class.
*
* @throws IOException if an I/O error occurs.
* @throws ClassNotFoundException if the main class could not be found.
* @throws NoSuchMethodException if the main method could not be found on the main class.
*/
private Method createMainMethod()
throws IOException, ClassNotFoundException, NoSuchMethodException
{
Class> mainClass = makeClass();
return mainClass.getMethod("main", String[].class);
}
/**
* Executes the main method synchronously, waiting for completion.
*
* @param args arguments passed to the main method.
*
* @throws IOException if an I/O error occurs.
* @throws ClassNotFoundException if a class could not be found using reflection.
* @throws NoSuchMethodException if the main method was not found on the class.
* @throws InvocationTargetException if an error occurs running the main method.
*/
public void execute(String... args)
throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException
{
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
try
{
Method mainMethod = createMainMethod();
Thread.currentThread().setContextClassLoader(mainMethod.getDeclaringClass().getClassLoader());
runMain(mainMethod, args);
}
finally
{
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
/**
* Starts executing the main method, does not wait for completion.
*
* @param args arguments to pass to the main method.
*
* @throws IOException if an I/O error occurs.
* @throws ClassNotFoundException if a class could not be found using reflection.
* @throws NoSuchMethodException if the main method was not found on the class.
*/
public void executeAsync(String... args)
throws IOException, ClassNotFoundException, NoSuchMethodException
{
Method mainMethod = createMainMethod();
runnerThread = new Thread(() -> runMainSafely(mainMethod, args));
runnerThread.setContextClassLoader(mainMethod.getDeclaringClass().getClassLoader());
runnerThread.setName("JavaRunner-" + mainMethod.getDeclaringClass().getSimpleName());
//runnerThread.setUncaughtExceptionHandler();
runnerThread.start();
}
/**
* Runs a custom method from the main class asynchronously on an instance, without waiting for completion.
*
* @param method the method to run.
* @param target the target instance object whose method to run.
* @param args arguments to the method.
*/
public void runMethodAsync(Method method, Object target, Object... args)
{
runnerThread = new Thread(() -> runMethod(method, target, args));
runnerThread.setContextClassLoader(method.getDeclaringClass().getClassLoader());
runnerThread.setName("JavaRunner-" + method.getDeclaringClass().getSimpleName());
//runnerThread.setUncaughtExceptionHandler();
runnerThread.start();
}
/**
* Runs a custom method from the main class synchronously, waiting for it to complete.
*
* @param method the method to run.
* @param target the target instance object whose method to run.
* @param args arguments to the method.
*
* @throws RuntimeException if an error occurs running the method.
*/
public void runMethod(Method method, Object target, Object... args)
{
try
{
method.invoke(target, args);
}
catch (ReflectiveOperationException e)
{
throw new RuntimeException(e);
}
}
/**
* Runs the static main method, wrapping thrown exceptions in runtime exceptions.
*
* @param mainMethod the main method to execute.
* @param args arguments to pass the main method.
*
* @throws RuntimeException if an error occurs running the main method.
*/
private void runMainSafely(Method mainMethod, String... args)
{
try
{
mainMethod.invoke(null, new Object[] {args});
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
/**
* Run the static main method, wrapping thrown exceptions if needed.
*
* @param mainMethod the main method to execute.
* @param args arguments to pass to the main method.
*
* @throws InvocationTargetException if an invocation or reflective operation occurs.
*/
private void runMain(Method mainMethod, String... args)
throws InvocationTargetException
{
try
{
mainMethod.invoke(null, new Object[] {args});
}
catch (InvocationTargetException e)
{
throw e;
}
catch (Exception e)
{
throw new InvocationTargetException(e);
}
}
/**
* Wait for a previously asynchronously executed method to finish execution.
*
* @throws InterruptedException if interrupted while waiting.
*/
public void waitForAsyncCompletion()
throws InterruptedException
{
if (runnerThread == null)
throw new IllegalStateException("Thread never started");
runnerThread.join();
}
}