001    package org.codehaus.mojo.exec;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *     http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import org.apache.maven.artifact.Artifact;
023    import org.apache.maven.artifact.factory.ArtifactFactory;
024    import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
025    import org.apache.maven.artifact.repository.ArtifactRepository;
026    import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
027    import org.apache.maven.artifact.resolver.ArtifactResolver;
028    import org.apache.maven.plugin.MojoExecutionException;
029    import org.apache.maven.plugin.MojoFailureException;
030    import org.apache.maven.project.MavenProject;
031    import org.apache.maven.project.MavenProjectBuilder;
032    import org.apache.maven.project.artifact.MavenMetadataSource;
033    
034    import java.io.File;
035    import java.lang.reflect.Method;
036    import java.net.MalformedURLException;
037    import java.net.URL;
038    import java.net.URLClassLoader;
039    import java.util.ArrayList;
040    import java.util.Collection;
041    import java.util.Collections;
042    import java.util.HashSet;
043    import java.util.Iterator;
044    import java.util.List;
045    import java.util.Properties;
046    import java.util.Set;
047    
048    /**
049     * Executes the supplied java class in the current VM with the enclosing project's
050     * dependencies as classpath.
051     *
052     * @author <a href="mailto:kaare.nilsen@gmail.com">Kaare Nilsen</a>, <a href="mailto:dsmiley@mitre.org">David Smiley</a>
053     * @goal java
054     * @requiresDependencyResolution test
055     * @execute phase="validate"
056     * @since 1.0
057     */
058    public class ExecJavaMojo
059        extends AbstractExecMojo
060    {
061        /**
062         * @component
063         */
064        private ArtifactResolver artifactResolver;
065    
066        /**
067         * @component
068         */
069        private ArtifactFactory artifactFactory;
070    
071        /**
072         * @component
073         */
074        private ArtifactMetadataSource metadataSource;
075    
076        /**
077         * @parameter expression="${localRepository}"
078         * @required
079         * @readonly
080         * @since 1.0
081         */
082        private ArtifactRepository localRepository;
083    
084        /**
085         * @parameter expression="${project.remoteArtifactRepositories}"
086         * @required
087         * @readonly
088         * @since 1.1-beta-1
089         */
090        private List remoteRepositories;
091    
092        /**
093         * @component
094         * @since 1.0
095         */
096        private MavenProjectBuilder projectBuilder;
097    
098        /**
099         * @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    }