View Javadoc

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