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

org.codehaus.mojo.exec.ExecJavaMojo Maven / Gradle / Ivy

package org.codehaus.mojo.exec;

import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ResolutionErrorHandler;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.repository.RepositorySystem;

/**
 * Executes the supplied java class in the current VM with the enclosing project's dependencies as classpath.
 * 
 * @author Kaare Nilsen ([email protected]), David Smiley ([email protected])
 * @since 1.0
 */
@Mojo( name = "java", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST )
public class ExecJavaMojo
    extends AbstractExecMojo
{
    @Component
    private RepositorySystem repositorySystem;

    @Component
    private ResolutionErrorHandler resolutionErrorHandler;


    /**
     * @since 1.0
     */
    @Component
    private ProjectBuilder projectBuilder;

    /**
     * The main class to execute.
* With Java 9 and above you can prefix it with the modulename, e.g. com.greetings/com.greetings.Main * Without modulename the classpath will be used, with modulename a new modulelayer will be created. * * @since 1.0 */ @Parameter( required = true, property = "exec.mainClass" ) private String mainClass; /** * Forces the creation of fork join common pool to avoids the threads to be owned by the isolated thread group * and prevent a proper shutdown. * If set to zero the default parallelism is used to precreate all threads, * if negative it is ignored else the value is the one used to create the fork join threads. * * @since 3.0.1 */ @Parameter( property = "exec.preloadCommonPool", defaultValue = "0" ) private int preloadCommonPool; /** * The class arguments. * * @since 1.0 */ @Parameter( property = "exec.arguments" ) private String[] arguments; /** * A list of system properties to be passed. Note: as the execution is not forked, some system properties required * by the JVM cannot be passed here. Use MAVEN_OPTS or the exec:exec instead. See the user guide for more * information. * * @since 1.0 */ @Parameter private Property[] systemProperties; /** * Indicates if mojo should be kept running after the mainclass terminates. Use full for server like apps with * daemon threads. * * @deprecated since 1.1-alpha-1 * @since 1.0 */ @Parameter( property = "exec.keepAlive", defaultValue = "false" ) @Deprecated private boolean keepAlive; /** * Indicates if the project dependencies should be used when executing the main class. * * @since 1.1-beta-1 */ @Parameter( property = "exec.includeProjectDependencies", defaultValue = "true" ) private boolean includeProjectDependencies; /** * Indicates if this plugin's dependencies should be used when executing the main class. *

* This is useful when project dependencies are not appropriate. Using only the plugin dependencies can be * particularly useful when the project is not a java project. For example a mvn project using the csharp plugins * only expects to see dotnet libraries as dependencies. * * @since 1.1-beta-1 */ @Parameter( property = "exec.includePluginsDependencies", defaultValue = "false" ) private boolean includePluginDependencies; /** * Whether to interrupt/join and possibly stop the daemon threads upon quitting.
* If this is false, maven does nothing about the daemon threads. When maven has no more work to do, * the VM will normally terminate any remaining daemon threads. *

* In certain cases (in particular if maven is embedded), you might need to keep this enabled to make sure threads * are properly cleaned up to ensure they don't interfere with subsequent activity. In that case, see * {@link #daemonThreadJoinTimeout} and {@link #stopUnresponsiveDaemonThreads} for further tuning. *

* * @since 1.1-beta-1 */ @Parameter( property = "exec.cleanupDaemonThreads", defaultValue = "true" ) private boolean cleanupDaemonThreads; /** * This defines the number of milliseconds to wait for daemon threads to quit following their interruption.
* This is only taken into account if {@link #cleanupDaemonThreads} is true. A value <=0 means to * not timeout (i.e. wait indefinitely for threads to finish). Following a timeout, a warning will be logged. *

* Note: properly coded threads should terminate upon interruption but some threads may prove problematic: as * the VM does interrupt daemon threads, some code may not have been written to handle interruption properly. For * example java.util.Timer is known to not handle interruptions in JDK <= 1.6. So it is not possible for us to * infinitely wait by default otherwise maven could hang. A sensible default value has been chosen, but this default * value may change in the future based on user feedback. *

* * @since 1.1-beta-1 */ @Parameter( property = "exec.daemonThreadJoinTimeout", defaultValue = "15000" ) private long daemonThreadJoinTimeout; /** * Wether to call {@link Thread#stop()} following a timing out of waiting for an interrupted thread to finish. This * is only taken into account if {@link #cleanupDaemonThreads} is true and the * {@link #daemonThreadJoinTimeout} threshold has been reached for an uncooperative thread. If this is * false, or if {@link Thread#stop()} fails to get the thread to stop, then a warning is logged and * Maven will continue on while the affected threads (and related objects in memory) linger on. Consider setting * this to true if you are invoking problematic code that you can't fix. An example is * {@link java.util.Timer} which doesn't respond to interruption. To have Timer fixed, vote for * this bug. * * @since 1.1-beta-1 */ @Parameter( property = "exec.stopUnresponsiveDaemonThreads", defaultValue = "false" ) private boolean stopUnresponsiveDaemonThreads; /** * Deprecated this is not needed anymore. * * @deprecated since 1.1-alpha-1 * @since 1.0 */ @Parameter( property = "exec.killAfter", defaultValue = "-1" ) @Deprecated private long killAfter; private Properties originalSystemProperties; /** * Additional elements to be appended to the classpath. * * @since 1.3 */ @Parameter private List additionalClasspathElements; /** * List of file to exclude from the classpath. * It matches the jar name, for example {@code slf4j-simple-1.7.30.jar}. * * @since 3.0.1 */ @Parameter private List classpathFilenameExclusions; /** * Execute goal. * * @throws MojoExecutionException execution of the main class or one of the threads it generated failed. * @throws MojoFailureException something bad happened... */ public void execute() throws MojoExecutionException, MojoFailureException { if ( isSkip() ) { getLog().info( "skipping execute as per configuration" ); return; } if ( killAfter != -1 ) { getLog().warn( "Warning: killAfter is now deprecated. Do you need it ? Please comment on MEXEC-6." ); } if ( null == arguments ) { arguments = new String[0]; } if ( getLog().isDebugEnabled() ) { StringBuffer msg = new StringBuffer( "Invoking : " ); msg.append( mainClass ); msg.append( ".main(" ); for ( int i = 0; i < arguments.length; i++ ) { if ( i > 0 ) { msg.append( ", " ); } msg.append( arguments[i] ); } msg.append( ")" ); getLog().debug( msg ); } if ( preloadCommonPool >= 0 ) { preloadCommonPool(); } IsolatedThreadGroup threadGroup = new IsolatedThreadGroup( mainClass /* name */ ); Thread bootstrapThread = new Thread( threadGroup, new Runnable() { public void run() { int sepIndex = mainClass.indexOf( '/' ); final String bootClassName; if ( sepIndex >= 0 ) { bootClassName = mainClass.substring( sepIndex + 1 ); } else { bootClassName = mainClass; } try { Class bootClass = Thread.currentThread().getContextClassLoader().loadClass( bootClassName ); MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle mainHandle = lookup.findStatic( bootClass, "main", MethodType.methodType( void.class, String[].class ) ); mainHandle.invoke( arguments ); } catch ( IllegalAccessException | NoSuchMethodException | NoSuchMethodError e ) { // just pass it on Thread.currentThread().getThreadGroup().uncaughtException( Thread.currentThread(), new Exception( "The specified mainClass doesn't contain a main method with appropriate signature.", e ) ); } catch ( InvocationTargetException e ) { // use the cause if available to improve the plugin execution output Throwable exceptionToReport = e.getCause() != null ? e.getCause() : e; Thread.currentThread().getThreadGroup().uncaughtException( Thread.currentThread(), exceptionToReport ); } catch ( Throwable e ) { // just pass it on Thread.currentThread().getThreadGroup().uncaughtException( Thread.currentThread(), e ); } } }, mainClass + ".main()" ); URLClassLoader classLoader = getClassLoader(); bootstrapThread.setContextClassLoader( classLoader ); setSystemProperties(); bootstrapThread.start(); joinNonDaemonThreads( threadGroup ); // It's plausible that spontaneously a non-daemon thread might be created as we try and shut down, // but it's too late since the termination condition (only daemon threads) has been triggered. if ( keepAlive ) { getLog().warn( "Warning: keepAlive is now deprecated and obsolete. Do you need it? Please comment on MEXEC-6." ); waitFor( 0 ); } if ( cleanupDaemonThreads ) { terminateThreads( threadGroup ); try { threadGroup.destroy(); } catch ( IllegalThreadStateException e ) { getLog().warn( "Couldn't destroy threadgroup " + threadGroup, e ); } } if ( classLoader != null ) { try { classLoader.close(); } catch ( IOException e ) { getLog().error(e.getMessage(), e); } } if ( originalSystemProperties != null ) { System.setProperties( originalSystemProperties ); } synchronized ( threadGroup ) { if ( threadGroup.uncaughtException != null ) { throw new MojoExecutionException( "An exception occurred while executing the Java class. " + threadGroup.uncaughtException.getMessage(), threadGroup.uncaughtException ); } } registerSourceRoots(); } /** * To avoid the exec:java to consider common pool threads leaked, let's pre-create them. */ private void preloadCommonPool() { try { // ensure common pool exists in the jvm final ExecutorService es = ForkJoinPool.commonPool(); final int max = preloadCommonPool > 0 ? preloadCommonPool : ForkJoinPool.getCommonPoolParallelism(); final CountDownLatch preLoad = new CountDownLatch( 1 ); for ( int i = 0; i < max; i++ ) { es.submit(() -> { try { preLoad.await(); } catch ( InterruptedException e ) { Thread.currentThread().interrupt(); } }); } preLoad.countDown(); } catch (final Exception e) { getLog().debug(e.getMessage() + ", skipping commonpool earger init"); } } /** * a ThreadGroup to isolate execution and collect exceptions. */ class IsolatedThreadGroup extends ThreadGroup { private Throwable uncaughtException; // synchronize access to this public IsolatedThreadGroup( String name ) { super( name ); } public void uncaughtException( Thread thread, Throwable throwable ) { if ( throwable instanceof ThreadDeath ) { return; // harmless } synchronized ( this ) { if ( uncaughtException == null ) // only remember the first one { uncaughtException = throwable; // will be reported eventually } } getLog().warn( throwable ); } } private void joinNonDaemonThreads( ThreadGroup threadGroup ) { boolean foundNonDaemon; do { foundNonDaemon = false; Collection threads = getActiveThreads( threadGroup ); for ( Thread thread : threads ) { if ( thread.isDaemon() ) { continue; } foundNonDaemon = true; // try again; maybe more threads were created while we were busy joinThread( thread, 0 ); } } while ( foundNonDaemon ); } private void joinThread( Thread thread, long timeoutMsecs ) { try { getLog().debug( "joining on thread " + thread ); thread.join( timeoutMsecs ); } catch ( InterruptedException e ) { Thread.currentThread().interrupt(); // good practice if don't throw getLog().warn( "interrupted while joining against thread " + thread, e ); // not expected! } if ( thread.isAlive() ) // generally abnormal { getLog().warn( "thread " + thread + " was interrupted but is still alive after waiting at least " + timeoutMsecs + "msecs" ); } } private void terminateThreads( ThreadGroup threadGroup ) { long startTime = System.currentTimeMillis(); Set uncooperativeThreads = new HashSet(); // these were not responsive to interruption for ( Collection threads = getActiveThreads( threadGroup ); !threads.isEmpty(); threads = getActiveThreads( threadGroup ), threads.removeAll( uncooperativeThreads ) ) { // Interrupt all threads we know about as of this instant (harmless if spuriously went dead (! isAlive()) // or if something else interrupted it ( isInterrupted() ). for ( Thread thread : threads ) { getLog().debug( "interrupting thread " + thread ); thread.interrupt(); } // Now join with a timeout and call stop() (assuming flags are set right) for ( Thread thread : threads ) { if ( !thread.isAlive() ) { continue; // and, presumably it won't show up in getActiveThreads() next iteration } if ( daemonThreadJoinTimeout <= 0 ) { joinThread( thread, 0 ); // waits until not alive; no timeout continue; } long timeout = daemonThreadJoinTimeout - ( System.currentTimeMillis() - startTime ); if ( timeout > 0 ) { joinThread( thread, timeout ); } if ( !thread.isAlive() ) { continue; } uncooperativeThreads.add( thread ); // ensure we don't process again if ( stopUnresponsiveDaemonThreads ) { getLog().warn( "thread " + thread + " will be Thread.stop()'ed" ); thread.stop(); } else { getLog().warn( "thread " + thread + " will linger despite being asked to die via interruption" ); } } } if ( !uncooperativeThreads.isEmpty() ) { getLog().warn( "NOTE: " + uncooperativeThreads.size() + " thread(s) did not finish despite being asked to" + " via interruption. This is not a problem with exec:java, it is a problem with the running code." + " Although not serious, it should be remedied." ); } else { int activeCount = threadGroup.activeCount(); if ( activeCount != 0 ) { // TODO this may be nothing; continue on anyway; perhaps don't even log in future Thread[] threadsArray = new Thread[1]; threadGroup.enumerate( threadsArray ); getLog().debug( "strange; " + activeCount + " thread(s) still active in the group " + threadGroup + " such as " + threadsArray[0] ); } } } private Collection getActiveThreads( ThreadGroup threadGroup ) { Thread[] threads = new Thread[threadGroup.activeCount()]; int numThreads = threadGroup.enumerate( threads ); Collection result = new ArrayList( numThreads ); for ( int i = 0; i < threads.length && threads[i] != null; i++ ) { result.add( threads[i] ); } return result; // note: result should be modifiable } /** * Pass any given system properties to the java system properties. */ private void setSystemProperties() { if ( systemProperties != null ) { originalSystemProperties = System.getProperties(); for ( Property systemProperty : systemProperties ) { String value = systemProperty.getValue(); System.setProperty( systemProperty.getKey(), value == null ? "" : value ); } } } /** * Set up a classloader for the execution of the main class. * * @return the classloader * @throws MojoExecutionException if a problem happens */ private URLClassLoader getClassLoader() throws MojoExecutionException { List classpathURLs = new ArrayList<>(); this.addRelevantPluginDependenciesToClasspath( classpathURLs ); this.addRelevantProjectDependenciesToClasspath( classpathURLs ); this.addAdditionalClasspathElements( classpathURLs ); try { return URLClassLoaderBuilder.builder() .setLogger( getLog() ) .setPaths( classpathURLs ) .setExclusions( classpathFilenameExclusions ) .build(); } catch ( NullPointerException | IOException e ) { throw new MojoExecutionException( e.getMessage(), e ); } } private void addAdditionalClasspathElements( List path ) { if ( additionalClasspathElements != null ) { for ( String classPathElement : additionalClasspathElements ) { Path file = Paths.get( classPathElement ); if ( !file.isAbsolute() ) { file = project.getBasedir().toPath().resolve( file ); } getLog().debug( "Adding additional classpath element: " + file + " to classpath" ); path.add( file ); } } } /** * Add any relevant project dependencies to the classpath. Indirectly takes includePluginDependencies and * ExecutableDependency into consideration. * * @param path classpath of {@link java.net.URL} objects * @throws MojoExecutionException if a problem happens */ private void addRelevantPluginDependenciesToClasspath( List path ) throws MojoExecutionException { if ( hasCommandlineArgs() ) { arguments = parseCommandlineArgs(); } for ( Artifact classPathElement : this.determineRelevantPluginDependencies() ) { getLog().debug( "Adding plugin dependency artifact: " + classPathElement.getArtifactId() + " to classpath" ); path.add( classPathElement.getFile().toPath() ); } } /** * Add any relevant project dependencies to the classpath. Takes includeProjectDependencies into consideration. * * @param path classpath of {@link java.net.URL} objects * @throws MojoExecutionException if a problem happens */ private void addRelevantProjectDependenciesToClasspath( List path ) throws MojoExecutionException { if ( this.includeProjectDependencies ) { getLog().debug( "Project Dependencies will be included." ); List artifacts = new ArrayList<>(); List theClasspathFiles = new ArrayList<>(); collectProjectArtifactsAndClasspath( artifacts, theClasspathFiles ); for ( Path classpathFile : theClasspathFiles ) { getLog().debug( "Adding to classpath : " + classpathFile ); path.add( classpathFile ); } for ( Artifact classPathElement : artifacts ) { getLog().debug( "Adding project dependency artifact: " + classPathElement.getArtifactId() + " to classpath" ); path.add( classPathElement.getFile().toPath() ); } } else { getLog().debug( "Project Dependencies will be excluded." ); } } /** * Determine all plugin dependencies relevant to the executable. Takes includePlugins, and the executableDependency * into consideration. * * @return a set of Artifact objects. (Empty set is returned if there are no relevant plugin dependencies.) * @throws MojoExecutionException if a problem happens resolving the plufin dependencies */ private Set determineRelevantPluginDependencies() throws MojoExecutionException { Set relevantDependencies; if ( this.includePluginDependencies ) { if ( this.executableDependency == null ) { getLog().debug( "All Plugin Dependencies will be included." ); relevantDependencies = new HashSet( this.getPluginDependencies() ); } else { getLog().debug( "Selected plugin Dependencies will be included." ); Artifact executableArtifact = this.findExecutableArtifact(); relevantDependencies = this.resolveExecutableDependencies( executableArtifact ); } } else { relevantDependencies = Collections.emptySet(); getLog().debug( "Plugin Dependencies will be excluded." ); } return relevantDependencies; } /** * Resolve the executable dependencies for the specified project * * @param executablePomArtifact the project's POM * @return a set of Artifacts * @throws MojoExecutionException if a failure happens */ private Set resolveExecutableDependencies( Artifact executablePomArtifact ) throws MojoExecutionException { try { ArtifactResolutionRequest request = new ArtifactResolutionRequest() .setArtifact( executablePomArtifact ) .setLocalRepository( getSession().getLocalRepository() ) .setRemoteRepositories( getSession().getRequest().getRemoteRepositories() ) .setForceUpdate( getSession().getRequest().isUpdateSnapshots() ) .setOffline( getSession().isOffline() ) .setResolveTransitively( true ); ArtifactResolutionResult result = repositorySystem.resolve( request ); resolutionErrorHandler.throwErrors( request, result ); return result.getArtifacts(); } catch ( ArtifactResolutionException ex ) { throw new MojoExecutionException( "Encountered problems resolving dependencies of the executable " + "in preparation for its execution.", ex ); } } /** * Stop program execution for nn millis. * * @param millis the number of millis-seconds to wait for, 0 stops program forever. */ private void waitFor( long millis ) { Object lock = new Object(); synchronized ( lock ) { try { lock.wait( millis ); } catch ( InterruptedException e ) { Thread.currentThread().interrupt(); // good practice if don't throw getLog().warn( "Spuriously interrupted while waiting for " + millis + "ms", e ); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy