View Javadoc
1   package org.codehaus.mojo.exec;
2   
3   import java.io.IOException;
4   import java.lang.invoke.MethodHandle;
5   import java.lang.invoke.MethodHandles;
6   import java.lang.invoke.MethodType;
7   import java.lang.reflect.InvocationTargetException;
8   import java.net.URLClassLoader;
9   import java.nio.file.Path;
10  import java.nio.file.Paths;
11  import java.util.ArrayList;
12  import java.util.Collection;
13  import java.util.Collections;
14  import java.util.HashSet;
15  import java.util.List;
16  import java.util.Properties;
17  import java.util.Set;
18  import java.util.concurrent.CountDownLatch;
19  import java.util.concurrent.ExecutorService;
20  import java.util.concurrent.ForkJoinPool;
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
24  import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
25  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
26  import org.apache.maven.artifact.resolver.ResolutionErrorHandler;
27  import org.apache.maven.plugin.MojoExecutionException;
28  import org.apache.maven.plugin.MojoFailureException;
29  import org.apache.maven.plugins.annotations.Component;
30  import org.apache.maven.plugins.annotations.Mojo;
31  import org.apache.maven.plugins.annotations.Parameter;
32  import org.apache.maven.plugins.annotations.ResolutionScope;
33  import org.apache.maven.project.ProjectBuilder;
34  import org.apache.maven.repository.RepositorySystem;
35  
36  /**
37   * Executes the supplied java class in the current VM with the enclosing project's dependencies as classpath.
38   * 
39   * @author Kaare Nilsen (kaare.nilsen@gmail.com), David Smiley (dsmiley@mitre.org)
40   * @since 1.0
41   */
42  @Mojo( name = "java", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST )
43  public class ExecJavaMojo
44      extends AbstractExecMojo
45  {
46      @Component
47      private RepositorySystem repositorySystem;
48  
49      @Component
50      private ResolutionErrorHandler resolutionErrorHandler;
51  
52  
53      /**
54       * @since 1.0
55       */
56      @Component
57      private ProjectBuilder projectBuilder;
58  
59      /**
60       * The main class to execute.<br>
61       * With Java 9 and above you can prefix it with the modulename, e.g. <code>com.greetings/com.greetings.Main</code>
62       * Without modulename the classpath will be used, with modulename a new modulelayer will be created.
63       * 
64       * @since 1.0
65       */
66      @Parameter( required = true, property = "exec.mainClass" )
67      private String mainClass;
68  
69  
70      /**
71       * Forces the creation of fork join common pool to avoids the threads to be owned by the isolated thread group
72       * and prevent a proper shutdown.
73       * If set to zero the default parallelism is used to precreate all threads,
74       * if negative it is ignored else the value is the one used to create the fork join threads.
75       *
76       * @since 3.0.1
77       */
78      @Parameter( property = "exec.preloadCommonPool", defaultValue = "0" )
79      private int preloadCommonPool;
80  
81      /**
82       * The class arguments.
83       * 
84       * @since 1.0
85       */
86      @Parameter( property = "exec.arguments" )
87      private String[] arguments;
88  
89      /**
90       * A list of system properties to be passed. Note: as the execution is not forked, some system properties required
91       * by the JVM cannot be passed here. Use MAVEN_OPTS or the exec:exec instead. See the user guide for more
92       * information.
93       * 
94       * @since 1.0
95       */
96      @Parameter
97      private Property[] systemProperties;
98  
99      /**
100      * Indicates if mojo should be kept running after the mainclass terminates. Use full for server like apps with
101      * daemon threads.
102      * 
103      * @deprecated since 1.1-alpha-1
104      * @since 1.0
105      */
106     @Parameter( property = "exec.keepAlive", defaultValue = "false" )
107     @Deprecated
108     private boolean keepAlive;
109 
110     /**
111      * Indicates if the project dependencies should be used when executing the main class.
112      * 
113      * @since 1.1-beta-1
114      */
115     @Parameter( property = "exec.includeProjectDependencies", defaultValue = "true" )
116     private boolean includeProjectDependencies;
117 
118     /**
119      * Indicates if this plugin's dependencies should be used when executing the main class.
120      * <p/>
121      * This is useful when project dependencies are not appropriate. Using only the plugin dependencies can be
122      * particularly useful when the project is not a java project. For example a mvn project using the csharp plugins
123      * only expects to see dotnet libraries as dependencies.
124      * 
125      * @since 1.1-beta-1
126      */
127     @Parameter( property = "exec.includePluginsDependencies", defaultValue = "false" )
128     private boolean includePluginDependencies;
129 
130     /**
131      * Whether to interrupt/join and possibly stop the daemon threads upon quitting. <br/>
132      * If this is <code>false</code>, maven does nothing about the daemon threads. When maven has no more work to do,
133      * the VM will normally terminate any remaining daemon threads.
134      * <p>
135      * In certain cases (in particular if maven is embedded), you might need to keep this enabled to make sure threads
136      * are properly cleaned up to ensure they don't interfere with subsequent activity. In that case, see
137      * {@link #daemonThreadJoinTimeout} and {@link #stopUnresponsiveDaemonThreads} for further tuning.
138      * </p>
139      * 
140      * @since 1.1-beta-1
141      */
142     @Parameter( property = "exec.cleanupDaemonThreads", defaultValue = "true" )
143     private boolean cleanupDaemonThreads;
144 
145     /**
146      * This defines the number of milliseconds to wait for daemon threads to quit following their interruption.<br/>
147      * This is only taken into account if {@link #cleanupDaemonThreads} is <code>true</code>. A value &lt;=0 means to
148      * not timeout (i.e. wait indefinitely for threads to finish). Following a timeout, a warning will be logged.
149      * <p>
150      * Note: properly coded threads <i>should</i> terminate upon interruption but some threads may prove problematic: as
151      * the VM does interrupt daemon threads, some code may not have been written to handle interruption properly. For
152      * example java.util.Timer is known to not handle interruptions in JDK &lt;= 1.6. So it is not possible for us to
153      * infinitely wait by default otherwise maven could hang. A sensible default value has been chosen, but this default
154      * value <i>may change</i> in the future based on user feedback.
155      * </p>
156      * 
157      * @since 1.1-beta-1
158      */
159     @Parameter( property = "exec.daemonThreadJoinTimeout", defaultValue = "15000" )
160     private long daemonThreadJoinTimeout;
161 
162     /**
163      * Wether to call {@link Thread#stop()} following a timing out of waiting for an interrupted thread to finish. This
164      * is only taken into account if {@link #cleanupDaemonThreads} is <code>true</code> and the
165      * {@link #daemonThreadJoinTimeout} threshold has been reached for an uncooperative thread. If this is
166      * <code>false</code>, or if {@link Thread#stop()} fails to get the thread to stop, then a warning is logged and
167      * Maven will continue on while the affected threads (and related objects in memory) linger on. Consider setting
168      * this to <code>true</code> if you are invoking problematic code that you can't fix. An example is
169      * {@link java.util.Timer} which doesn't respond to interruption. To have <code>Timer</code> fixed, vote for
170      * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6336543">this bug</a>.
171      * 
172      * @since 1.1-beta-1
173      */
174     @Parameter( property = "exec.stopUnresponsiveDaemonThreads", defaultValue = "false" )
175     private boolean stopUnresponsiveDaemonThreads;
176 
177     /**
178      * Deprecated this is not needed anymore.
179      * 
180      * @deprecated since 1.1-alpha-1
181      * @since 1.0
182      */
183     @Parameter( property = "exec.killAfter", defaultValue = "-1" )
184     @Deprecated
185     private long killAfter;
186 
187     private Properties originalSystemProperties;
188 
189     /**
190      * Additional elements to be appended to the classpath.
191      * 
192      * @since 1.3
193      */
194     @Parameter
195     private List<String> additionalClasspathElements;
196 
197     /**
198      * List of file to exclude from the classpath.
199      * It matches the jar name, for example {@code slf4j-simple-1.7.30.jar}.
200      *
201      * @since 3.0.1
202      */
203     @Parameter
204     private List<String> classpathFilenameExclusions;
205 
206     /**
207      * Execute goal.
208      * 
209      * @throws MojoExecutionException execution of the main class or one of the threads it generated failed.
210      * @throws MojoFailureException something bad happened...
211      */
212     public void execute()
213         throws MojoExecutionException, MojoFailureException
214     {
215         if ( isSkip() )
216         {
217             getLog().info( "skipping execute as per configuration" );
218             return;
219         }
220         if ( killAfter != -1 )
221         {
222             getLog().warn( "Warning: killAfter is now deprecated. Do you need it ? Please comment on MEXEC-6." );
223         }
224 
225         if ( null == arguments )
226         {
227             arguments = new String[0];
228         }
229 
230         if ( getLog().isDebugEnabled() )
231         {
232             StringBuffer msg = new StringBuffer( "Invoking : " );
233             msg.append( mainClass );
234             msg.append( ".main(" );
235             for ( int i = 0; i < arguments.length; i++ )
236             {
237                 if ( i > 0 )
238                 {
239                     msg.append( ", " );
240                 }
241                 msg.append( arguments[i] );
242             }
243             msg.append( ")" );
244             getLog().debug( msg );
245         }
246 
247         if ( preloadCommonPool >= 0 )
248         {
249             preloadCommonPool();
250         }
251 
252         IsolatedThreadGroup threadGroup = new IsolatedThreadGroup( mainClass /* name */ );
253         Thread bootstrapThread = new Thread( threadGroup, new Runnable()
254         {
255             public void run()
256             {
257                 int sepIndex = mainClass.indexOf( '/' );
258 
259                 final String bootClassName;
260                 if ( sepIndex >= 0 )
261                 {
262                     bootClassName = mainClass.substring( sepIndex + 1 );
263                 }
264                 else 
265                 {
266                     bootClassName = mainClass;
267                 }
268                 
269                 try
270                 {
271                     Class<?> bootClass = Thread.currentThread().getContextClassLoader().loadClass( bootClassName );
272                     
273                     MethodHandles.Lookup lookup = MethodHandles.lookup();
274 
275                     MethodHandle mainHandle =
276                         lookup.findStatic( bootClass, "main",
277                                                  MethodType.methodType( void.class, String[].class ) );
278                     
279                     mainHandle.invoke( arguments );
280                 }
281                 catch ( IllegalAccessException | NoSuchMethodException | NoSuchMethodError e )
282                 { // just pass it on
283                     Thread.currentThread().getThreadGroup().uncaughtException( Thread.currentThread(),
284                                                                                new Exception( "The specified mainClass doesn't contain a main method with appropriate signature.",
285                                                                                               e ) );
286                 }
287                 catch ( InvocationTargetException e )
288                 { // use the cause if available to improve the plugin execution output
289                    Throwable exceptionToReport = e.getCause() != null ? e.getCause() : e;
290                    Thread.currentThread().getThreadGroup().uncaughtException( Thread.currentThread(), exceptionToReport );
291                 }
292                 catch ( Throwable e )
293                 { // just pass it on
294                     Thread.currentThread().getThreadGroup().uncaughtException( Thread.currentThread(), e );
295                 }
296             }
297         }, mainClass + ".main()" );
298         URLClassLoader classLoader = getClassLoader();
299         bootstrapThread.setContextClassLoader( classLoader );
300         setSystemProperties();
301 
302         bootstrapThread.start();
303         joinNonDaemonThreads( threadGroup );
304         // It's plausible that spontaneously a non-daemon thread might be created as we try and shut down,
305         // but it's too late since the termination condition (only daemon threads) has been triggered.
306         if ( keepAlive )
307         {
308             getLog().warn( "Warning: keepAlive is now deprecated and obsolete. Do you need it? Please comment on MEXEC-6." );
309             waitFor( 0 );
310         }
311 
312         if ( cleanupDaemonThreads )
313         {
314 
315             terminateThreads( threadGroup );
316 
317             try
318             {
319                 threadGroup.destroy();
320             }
321             catch ( IllegalThreadStateException e )
322             {
323                 getLog().warn( "Couldn't destroy threadgroup " + threadGroup, e );
324             }
325         }
326 
327         if ( classLoader != null )
328         {
329             try
330             {
331                 classLoader.close();
332             }
333             catch ( IOException e )
334             {
335                 getLog().error(e.getMessage(), e);
336             }
337         }
338 
339         if ( originalSystemProperties != null )
340         {
341             System.setProperties( originalSystemProperties );
342         }
343 
344         synchronized ( threadGroup )
345         {
346             if ( threadGroup.uncaughtException != null )
347             {
348                 throw new MojoExecutionException( "An exception occurred while executing the Java class. "
349                     + threadGroup.uncaughtException.getMessage(), threadGroup.uncaughtException );
350             }
351         }
352 
353         registerSourceRoots();
354     }
355 
356     /**
357      * To avoid the exec:java to consider common pool threads leaked, let's pre-create them.
358      */
359     private void preloadCommonPool()
360     {
361         try
362         {
363             // ensure common pool exists in the jvm
364             final ExecutorService es = ForkJoinPool.commonPool();
365             final int max = preloadCommonPool > 0
366                     ? preloadCommonPool :
367                     ForkJoinPool.getCommonPoolParallelism();
368             final CountDownLatch preLoad = new CountDownLatch( 1 );
369             for ( int i = 0;
370                   i < max;
371                   i++ )
372             {
373                 es.submit(() -> {
374                     try
375                     {
376                         preLoad.await();
377                     }
378                     catch ( InterruptedException e )
379                     {
380                         Thread.currentThread().interrupt();
381                     }
382                 });
383             }
384             preLoad.countDown();
385         }
386         catch (final Exception e)
387         {
388             getLog().debug(e.getMessage() + ", skipping commonpool earger init");
389         }
390     }
391 
392     /**
393      * a ThreadGroup to isolate execution and collect exceptions.
394      */
395     class IsolatedThreadGroup
396         extends ThreadGroup
397     {
398         private Throwable uncaughtException; // synchronize access to this
399 
400         public IsolatedThreadGroup( String name )
401         {
402             super( name );
403         }
404 
405         public void uncaughtException( Thread thread, Throwable throwable )
406         {
407             if ( throwable instanceof ThreadDeath )
408             {
409                 return; // harmless
410             }
411             synchronized ( this )
412             {
413                 if ( uncaughtException == null ) // only remember the first one
414                 {
415                     uncaughtException = throwable; // will be reported eventually
416                 }
417             }
418             getLog().warn( throwable );
419         }
420     }
421 
422     private void joinNonDaemonThreads( ThreadGroup threadGroup )
423     {
424         boolean foundNonDaemon;
425         do
426         {
427             foundNonDaemon = false;
428             Collection<Thread> threads = getActiveThreads( threadGroup );
429             for ( Thread thread : threads )
430             {
431                 if ( thread.isDaemon() )
432                 {
433                     continue;
434                 }
435                 foundNonDaemon = true; // try again; maybe more threads were created while we were busy
436                 joinThread( thread, 0 );
437             }
438         }
439         while ( foundNonDaemon );
440     }
441 
442     private void joinThread( Thread thread, long timeoutMsecs )
443     {
444         try
445         {
446             getLog().debug( "joining on thread " + thread );
447             thread.join( timeoutMsecs );
448         }
449         catch ( InterruptedException e )
450         {
451             Thread.currentThread().interrupt(); // good practice if don't throw
452             getLog().warn( "interrupted while joining against thread " + thread, e ); // not expected!
453         }
454         if ( thread.isAlive() ) // generally abnormal
455         {
456             getLog().warn( "thread " + thread + " was interrupted but is still alive after waiting at least "
457                 + timeoutMsecs + "msecs" );
458         }
459     }
460 
461     private void terminateThreads( ThreadGroup threadGroup )
462     {
463         long startTime = System.currentTimeMillis();
464         Set<Thread> uncooperativeThreads = new HashSet<Thread>(); // these were not responsive to interruption
465         for ( Collection<Thread> threads = getActiveThreads( threadGroup ); !threads.isEmpty(); threads =
466             getActiveThreads( threadGroup ), threads.removeAll( uncooperativeThreads ) )
467         {
468             // Interrupt all threads we know about as of this instant (harmless if spuriously went dead (! isAlive())
469             // or if something else interrupted it ( isInterrupted() ).
470             for ( Thread thread : threads )
471             {
472                 getLog().debug( "interrupting thread " + thread );
473                 thread.interrupt();
474             }
475             // Now join with a timeout and call stop() (assuming flags are set right)
476             for ( Thread thread : threads )
477             {
478                 if ( !thread.isAlive() )
479                 {
480                     continue; // and, presumably it won't show up in getActiveThreads() next iteration
481                 }
482                 if ( daemonThreadJoinTimeout <= 0 )
483                 {
484                     joinThread( thread, 0 ); // waits until not alive; no timeout
485                     continue;
486                 }
487                 long timeout = daemonThreadJoinTimeout - ( System.currentTimeMillis() - startTime );
488                 if ( timeout > 0 )
489                 {
490                     joinThread( thread, timeout );
491                 }
492                 if ( !thread.isAlive() )
493                 {
494                     continue;
495                 }
496                 uncooperativeThreads.add( thread ); // ensure we don't process again
497                 if ( stopUnresponsiveDaemonThreads )
498                 {
499                     getLog().warn( "thread " + thread + " will be Thread.stop()'ed" );
500                     thread.stop();
501                 }
502                 else
503                 {
504                     getLog().warn( "thread " + thread + " will linger despite being asked to die via interruption" );
505                 }
506             }
507         }
508         if ( !uncooperativeThreads.isEmpty() )
509         {
510             getLog().warn( "NOTE: " + uncooperativeThreads.size() + " thread(s) did not finish despite being asked to"
511                 + " via interruption. This is not a problem with exec:java, it is a problem with the running code."
512                 + " Although not serious, it should be remedied." );
513         }
514         else
515         {
516             int activeCount = threadGroup.activeCount();
517             if ( activeCount != 0 )
518             {
519                 // TODO this may be nothing; continue on anyway; perhaps don't even log in future
520                 Thread[] threadsArray = new Thread[1];
521                 threadGroup.enumerate( threadsArray );
522                 getLog().debug( "strange; " + activeCount + " thread(s) still active in the group " + threadGroup
523                     + " such as " + threadsArray[0] );
524             }
525         }
526     }
527 
528     private Collection<Thread> getActiveThreads( ThreadGroup threadGroup )
529     {
530         Thread[] threads = new Thread[threadGroup.activeCount()];
531         int numThreads = threadGroup.enumerate( threads );
532         Collection<Thread> result = new ArrayList<Thread>( numThreads );
533         for ( int i = 0; i < threads.length && threads[i] != null; i++ )
534         {
535             result.add( threads[i] );
536         }
537         return result; // note: result should be modifiable
538     }
539 
540     /**
541      * Pass any given system properties to the java system properties.
542      */
543     private void setSystemProperties()
544     {
545         if ( systemProperties != null )
546         {
547             originalSystemProperties = System.getProperties();
548             for ( Property systemProperty : systemProperties )
549             {
550                 String value = systemProperty.getValue();
551                 System.setProperty( systemProperty.getKey(), value == null ? "" : value );
552             }
553         }
554     }
555 
556     /**
557      * Set up a classloader for the execution of the main class.
558      * 
559      * @return the classloader
560      * @throws MojoExecutionException if a problem happens
561      */
562     private URLClassLoader getClassLoader()
563         throws MojoExecutionException
564     {
565         List<Path> classpathURLs = new ArrayList<>();
566         this.addRelevantPluginDependenciesToClasspath( classpathURLs );
567         this.addRelevantProjectDependenciesToClasspath( classpathURLs );
568         this.addAdditionalClasspathElements( classpathURLs );
569         
570         try
571         {
572             return URLClassLoaderBuilder.builder()
573                     .setLogger( getLog() )
574                     .setPaths( classpathURLs )
575                     .setExclusions( classpathFilenameExclusions )
576                     .build();
577         }
578         catch ( NullPointerException | IOException e )
579         {
580             throw new MojoExecutionException( e.getMessage(), e );
581         }
582 
583     }
584 
585     private void addAdditionalClasspathElements( List<Path> path )
586     {
587         if ( additionalClasspathElements != null )
588         {
589             for ( String classPathElement : additionalClasspathElements )
590             {
591                 Path file = Paths.get( classPathElement );
592                 if ( !file.isAbsolute() )
593                 {
594                     file = project.getBasedir().toPath().resolve( file );
595                 }
596                 getLog().debug( "Adding additional classpath element: " + file + " to classpath" );
597                 path.add( file );
598             }
599         }
600     }
601 
602     /**
603      * Add any relevant project dependencies to the classpath. Indirectly takes includePluginDependencies and
604      * ExecutableDependency into consideration.
605      * 
606      * @param path classpath of {@link java.net.URL} objects
607      * @throws MojoExecutionException if a problem happens
608      */
609     private void addRelevantPluginDependenciesToClasspath( List<Path> path )
610         throws MojoExecutionException
611     {
612         if ( hasCommandlineArgs() )
613         {
614             arguments = parseCommandlineArgs();
615         }
616 
617         for ( Artifact classPathElement : this.determineRelevantPluginDependencies() )
618         {
619             getLog().debug( "Adding plugin dependency artifact: " + classPathElement.getArtifactId()
620                 + " to classpath" );
621             path.add( classPathElement.getFile().toPath() );
622         }
623     }
624 
625     /**
626      * Add any relevant project dependencies to the classpath. Takes includeProjectDependencies into consideration.
627      * 
628      * @param path classpath of {@link java.net.URL} objects
629      * @throws MojoExecutionException if a problem happens
630      */
631     private void addRelevantProjectDependenciesToClasspath( List<Path> path )
632         throws MojoExecutionException
633     {
634         if ( this.includeProjectDependencies )
635         {
636             getLog().debug( "Project Dependencies will be included." );
637 
638             List<Artifact> artifacts = new ArrayList<>();
639             List<Path> theClasspathFiles = new ArrayList<>();
640 
641             collectProjectArtifactsAndClasspath( artifacts, theClasspathFiles );
642 
643             for ( Path classpathFile : theClasspathFiles )
644             {
645                 getLog().debug( "Adding to classpath : " + classpathFile );
646                 path.add( classpathFile );
647             }
648 
649             for ( Artifact classPathElement : artifacts )
650             {
651                 getLog().debug( "Adding project dependency artifact: " + classPathElement.getArtifactId()
652                     + " to classpath" );
653                 path.add( classPathElement.getFile().toPath() );
654             }
655         }
656         else
657         {
658             getLog().debug( "Project Dependencies will be excluded." );
659         }
660 
661     }
662 
663     /**
664      * Determine all plugin dependencies relevant to the executable. Takes includePlugins, and the executableDependency
665      * into consideration.
666      * 
667      * @return a set of Artifact objects. (Empty set is returned if there are no relevant plugin dependencies.)
668      * @throws MojoExecutionException if a problem happens resolving the plufin dependencies
669      */
670     private Set<Artifact> determineRelevantPluginDependencies()
671         throws MojoExecutionException
672     {
673         Set<Artifact> relevantDependencies;
674         if ( this.includePluginDependencies )
675         {
676             if ( this.executableDependency == null )
677             {
678                 getLog().debug( "All Plugin Dependencies will be included." );
679                 relevantDependencies = new HashSet<Artifact>( this.getPluginDependencies() );
680             }
681             else
682             {
683                 getLog().debug( "Selected plugin Dependencies will be included." );
684                 Artifact executableArtifact = this.findExecutableArtifact();
685                 relevantDependencies = this.resolveExecutableDependencies( executableArtifact );
686             }
687         }
688         else
689         {
690             relevantDependencies = Collections.emptySet();
691             getLog().debug( "Plugin Dependencies will be excluded." );
692         }
693         return relevantDependencies;
694     }
695 
696     /**
697      * Resolve the executable dependencies for the specified project
698      * 
699      * @param executablePomArtifact the project's POM
700      * @return a set of Artifacts
701      * @throws MojoExecutionException if a failure happens
702      */
703     private Set<Artifact> resolveExecutableDependencies( Artifact executablePomArtifact )
704         throws MojoExecutionException
705     {
706         try
707         {
708             ArtifactResolutionRequest request = new ArtifactResolutionRequest()
709                 .setArtifact( executablePomArtifact )
710                 .setLocalRepository( getSession().getLocalRepository() )
711                 .setRemoteRepositories( getSession().getRequest().getRemoteRepositories() )
712                 .setForceUpdate( getSession().getRequest().isUpdateSnapshots() )
713                 .setOffline( getSession().isOffline() )
714                 .setResolveTransitively( true );
715 
716             ArtifactResolutionResult result = repositorySystem.resolve( request );
717             resolutionErrorHandler.throwErrors( request, result );
718 
719             return result.getArtifacts();
720         }
721         catch ( ArtifactResolutionException ex )
722         {
723             throw new MojoExecutionException( "Encountered problems resolving dependencies of the executable "
724                 + "in preparation for its execution.", ex );
725         }
726     }
727 
728     /**
729      * Stop program execution for nn millis.
730      * 
731      * @param millis the number of millis-seconds to wait for, <code>0</code> stops program forever.
732      */
733     private void waitFor( long millis )
734     {
735         Object lock = new Object();
736         synchronized ( lock )
737         {
738             try
739             {
740                 lock.wait( millis );
741             }
742             catch ( InterruptedException e )
743             {
744                 Thread.currentThread().interrupt(); // good practice if don't throw
745                 getLog().warn( "Spuriously interrupted while waiting for " + millis + "ms", e );
746             }
747         }
748     }
749 
750 }