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