001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.maven;
018    
019    import java.io.File;
020    import java.lang.reflect.Method;
021    import java.net.MalformedURLException;
022    import java.net.URL;
023    import java.net.URLClassLoader;
024    import java.util.ArrayList;
025    import java.util.Arrays;
026    import java.util.Collection;
027    import java.util.Collections;
028    import java.util.HashSet;
029    import java.util.Iterator;
030    import java.util.List;
031    import java.util.Properties;
032    import java.util.Set;
033    
034    import org.apache.maven.artifact.Artifact;
035    import org.apache.maven.artifact.factory.ArtifactFactory;
036    import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
037    import org.apache.maven.artifact.repository.ArtifactRepository;
038    import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
039    import org.apache.maven.artifact.resolver.ArtifactResolver;
040    import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
041    import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
042    import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
043    import org.apache.maven.artifact.versioning.VersionRange;
044    import org.apache.maven.model.Dependency;
045    import org.apache.maven.model.Exclusion;
046    import org.apache.maven.plugin.MojoExecutionException;
047    import org.apache.maven.plugin.MojoFailureException;
048    import org.apache.maven.project.MavenProject;
049    import org.apache.maven.project.MavenProjectBuilder;
050    import org.apache.maven.project.artifact.MavenMetadataSource;
051    import org.codehaus.mojo.exec.AbstractExecMojo;
052    import org.codehaus.mojo.exec.ExecutableDependency;
053    import org.codehaus.mojo.exec.Property;
054    
055    /**
056     * Runs a CamelContext using any Spring XML configuration files found in
057     * <code>META-INF/spring/*.xml</code> and <code>camel-*.xml</code> and
058     * starting up the context.
059     *
060     * @goal run
061     * @requiresDependencyResolution runtime
062     * @execute phase="test-compile"
063     */
064    public class RunMojo extends AbstractExecMojo {
065    
066        // TODO
067        // this code is based on a copy-and-paste of maven-exec-plugin
068        //
069        // If we could avoid the mega-cut-n-paste it would really really help!
070        // ideally all I wanna do is auto-default 2 values!
071        // namely the main and the command line arguments..
072    
073        /**
074         * The maven project.
075         *
076         * @parameter expression="${project}"
077         * @required
078         * @readonly
079         */
080        protected MavenProject project;
081    
082        /**
083         * The duration to run the application for which by default is in
084         * milliseconds. A value <= 0 will run forever.
085         * Adding a s indicates seconds - eg "5s" means 5 seconds.
086         *
087         * @parameter expression="${camel.duration}"
088         *            default-value="-1"
089         *
090         */
091        protected String duration;
092    
093        /**
094         * The DOT output directory name used to generate the DOT diagram of the
095         * route definitions
096         *
097         * @parameter expression="${project.build.directory}/site/cameldoc"
098         * @readonly
099         */
100        protected String dotDir;
101    
102        /**
103         * Allows the DOT file generation to be enabled
104         *
105         * @parameter expression="false"
106         */
107        protected boolean useDot;
108    
109        /**
110         * @component
111         */
112        private ArtifactResolver artifactResolver;
113    
114        /**
115         * @component
116         */
117        private ArtifactFactory artifactFactory;
118    
119        /**
120         * @component
121         */
122        private ArtifactMetadataSource metadataSource;
123    
124        /**
125         * @parameter expression="${localRepository}"
126         * @required
127         * @readonly
128         */
129        private ArtifactRepository localRepository;
130    
131        /**
132         * @parameter expression="${project.remoteArtifactRepositories}"
133         */
134        private List remoteRepositories;
135    
136        /**
137         * @component
138         */
139        private MavenProjectBuilder projectBuilder;
140    
141        /**
142         * @parameter expression="${plugin.artifacts}"
143         * @readonly
144         */
145        private List pluginDependencies;
146    
147        /**
148         * Whether to enable the tracer or not
149         *
150         * @parameter expression="${camel.trace}"
151         *            default-value="false"
152         * @required
153         */
154        private boolean trace;
155    
156        /**
157         * Output all routes to the specified XML file
158         *
159         * @parameter expression="${camel.routesOutputFile}"
160         */
161        private String routesOutputFile;    
162        
163        /**
164         * The main class to execute.
165         *
166         * @parameter expression="${camel.mainClass}"
167         *            default-value="org.apache.camel.spring.Main"
168         * @required
169         */
170        private String mainClass;
171    
172        /**
173         * The basedPackages that spring java config want to gets.
174         *
175         * @parameter expression="${camel.basedPackages}"
176         */
177        private String basedPackages;
178    
179        /**
180         * The configClasses that spring java config want to gets.
181         *
182         * @parameter expression="${camel.configClasses}"
183         */
184        private String configClasses;
185        
186        /**
187         * The classpath based application context uri that spring want to gets.
188         *
189         * @parameter expression="${camel.applicationContextUri}"
190         */
191        private String applicationContextUri;
192    
193        /**
194         * The filesystem based application context uri that spring want to gets.
195         *
196         * @parameter expression="${camel.fileApplicationContextUri}"
197         */
198        private String fileApplicationContextUri;
199    
200        /**
201         * The class arguments.
202         *
203         * @parameter expression="${camel.arguments}"
204         */
205        private String[] arguments;
206    
207        /**
208         * A list of system properties to be passed. Note: as the execution is not
209         * forked, some system properties required by the JVM cannot be passed here.
210         * Use MAVEN_OPTS or the exec:exec instead. See the user guide for more
211         * information.
212         *
213         * @parameter
214         */
215        private Property[] systemProperties;
216    
217        /**
218         * Deprecated; this is not needed anymore. Indicates if mojo should be kept
219         * running after the mainclass terminates. Usefull for serverlike apps with
220         * deamonthreads.
221         *
222         * @parameter expression="${camel.keepAlive}" default-value="false"
223         */
224        private boolean keepAlive;
225    
226        /**
227         * Indicates if the project dependencies should be used when executing the
228         * main class.
229         *
230         * @parameter expression="${camel.includeProjectDependencies}"
231         *            default-value="true"
232         */
233        private boolean includeProjectDependencies;
234    
235        /**
236         * Indicates if this plugin's dependencies should be used when executing the
237         * main class. <p/> This is useful when project dependencies are not
238         * appropriate. Using only the plugin dependencies can be particularly
239         * useful when the project is not a java project. For example a mvn project
240         * using the csharp plugins only expects to see dotnet libraries as
241         * dependencies.
242         *
243         * @parameter expression="${camel.includePluginDependencies}"
244         *            default-value="false"
245         */
246        private boolean includePluginDependencies;
247    
248        /**
249         * If provided the ExecutableDependency identifies which of the plugin
250         * dependencies contains the executable class. This will have the affect of
251         * only including plugin dependencies required by the identified
252         * ExecutableDependency. <p/> If includeProjectDependencies is set to
253         * <code>true</code>, all of the project dependencies will be included on
254         * the executable's classpath. Whether a particular project dependency is a
255         * dependency of the identified ExecutableDependency will be irrelevant to
256         * its inclusion in the classpath.
257         *
258         * @parameter
259         * @optional
260         */
261        private ExecutableDependency executableDependency;
262    
263        /**
264         * Wether to interrupt/join and possibly stop the daemon threads upon
265         * quitting. <br/> If this is <code>false</code>, maven does nothing
266         * about the daemon threads. When maven has no more work to do, the VM will
267         * normally terminate any remaining daemon threads.
268         * <p>
269         * In certain cases (in particular if maven is embedded), you might need to
270         * keep this enabled to make sure threads are properly cleaned up to ensure
271         * they don't interfere with subsequent activity. In that case, see
272         * {@link #daemonThreadJoinTimeout} and
273         * {@link #stopUnresponsiveDaemonThreads} for further tuning.
274         * </p>
275         *
276         * @parameter expression="${camel.cleanupDaemonThreads} default-value="true"
277         */
278        private boolean cleanupDaemonThreads;
279    
280        /**
281         * This defines the number of milliseconds to wait for daemon threads to
282         * quit following their interruption.<br/> This is only taken into account
283         * if {@link #cleanupDaemonThreads} is <code>true</code>. A value &lt;=0
284         * means to not timeout (i.e. wait indefinitely for threads to finish).
285         * Following a timeout, a warning will be logged.
286         * <p>
287         * Note: properly coded threads <i>should</i> terminate upon interruption
288         * but some threads may prove problematic: as the VM does interrupt daemon
289         * threads, some code may not have been written to handle interruption
290         * properly. For example java.util.Timer is known to not handle
291         * interruptions in JDK &lt;= 1.6. So it is not possible for us to
292         * infinitely wait by default otherwise maven could hang. A sensible default
293         * value has been chosen, but this default value <i>may change</i> in the
294         * future based on user feedback.
295         * </p>
296         *
297         * @parameter expression="${camel.daemonThreadJoinTimeout}"
298         *            default-value="15000"
299         */
300        private long daemonThreadJoinTimeout;
301    
302        /**
303         * Wether to call {@link Thread#stop()} following a timing out of waiting
304         * for an interrupted thread to finish. This is only taken into account if
305         * {@link #cleanupDaemonThreads} is <code>true</code> and the
306         * {@link #daemonThreadJoinTimeout} threshold has been reached for an
307         * uncooperative thread. If this is <code>false</code>, or if
308         * {@link Thread#stop()} fails to get the thread to stop, then a warning is
309         * logged and Maven will continue on while the affected threads (and related
310         * objects in memory) linger on. Consider setting this to <code>true</code>
311         * if you are invoking problematic code that you can't fix. An example is
312         * {@link java.util.Timer} which doesn't respond to interruption. To have
313         * <code>Timer</code> fixed, vote for <a
314         * href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6336543">this
315         * bug</a>.
316         *
317         * @parameter expression="${camel.stopUnresponsiveDaemonThreads}
318         *            default-value="false"
319         */
320        private boolean stopUnresponsiveDaemonThreads;
321    
322        /**
323         * Deprecated this is not needed anymore.
324         *
325         * @parameter expression="${camel.killAfter}" default-value="-1"
326         */
327        private long killAfter;
328    
329        private Properties originalSystemProperties;
330    
331        /**
332         * Execute goal.
333         *
334         * @throws MojoExecutionException execution of the main class or one of the
335         *                 threads it generated failed.
336         * @throws MojoFailureException something bad happened...
337         */
338        public void execute() throws MojoExecutionException, MojoFailureException {
339            boolean usingSpringJavaConfigureMain = false;
340            if (killAfter != -1) {
341                getLog().warn("Warning: killAfter is now deprecated. Do you need it ? Please comment on MEXEC-6.");
342            }
343    
344            // lets create the command line arguments to pass in...
345            List<String> args = new ArrayList<String>();
346            if (dotDir != null && useDot) {
347                args.add("-o");
348                args.add(dotDir);
349            }
350            if (trace) {
351                args.add("-t");
352            }
353    
354            if (routesOutputFile != null) {
355                args.add("-output");
356                args.add(routesOutputFile);
357            }        
358            
359            if (applicationContextUri != null) {
360                args.add("-ac");
361                args.add(applicationContextUri);
362            } else if (fileApplicationContextUri != null) {
363                args.add("-fa");
364                args.add(fileApplicationContextUri);
365            }
366            
367            if (configClasses != null) {
368                args.add("-cc");
369                args.add(configClasses);
370                usingSpringJavaConfigureMain = true;
371            }        
372            if (basedPackages != null) {
373                args.add("-bp");
374                args.add(basedPackages);
375                usingSpringJavaConfigureMain = true;
376            }
377     
378            args.add("-d");
379            args.add(duration);
380            if (arguments != null) {
381                args.addAll(Arrays.asList(arguments));
382            }
383            arguments = new String[args.size()];
384            args.toArray(arguments);
385            
386            if (usingSpringJavaConfigureMain) {
387                mainClass = "org.apache.camel.spring.javaconfig.Main";
388                getLog().info("Using the org.apache.camel.spring.javaconfig.Main to initiate a CamelContext");
389            }
390            
391            if (getLog().isDebugEnabled()) {
392                StringBuffer msg = new StringBuffer("Invoking : ");
393                msg.append(mainClass);
394                msg.append(".main(");
395                for (int i = 0; i < arguments.length; i++) {
396                    if (i > 0) {
397                        msg.append(", ");
398                    }
399                    msg.append(arguments[i]);
400                }
401                msg.append(")");
402                getLog().debug(msg);
403            }
404    
405            IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(mainClass /* name */);
406            Thread bootstrapThread = new Thread(threadGroup, new Runnable() {
407                public void run() {
408                    try {
409                        Method main = Thread.currentThread().getContextClassLoader().loadClass(mainClass)
410                            .getMethod("main", new Class[] {String[].class});
411                        if (!main.isAccessible()) {
412                            getLog().debug("Setting accessibility to true in order to invoke main().");
413                            main.setAccessible(true);
414                        }
415                        main.invoke(main, new Object[] {arguments});
416                    } catch (Exception e) { // just pass it on
417                        // let it be printed so end users can see the exception on the console
418                        System.err.println("*************************************");
419                        System.err.println("Error occurred while running main from: " + mainClass);
420                        e.printStackTrace();
421                        System.err.println("*************************************");
422                        Thread.currentThread().getThreadGroup().uncaughtException(Thread.currentThread(), e);
423                    }
424                }
425            }, mainClass + ".main()");
426            bootstrapThread.setContextClassLoader(getClassLoader());
427            setSystemProperties();
428    
429            bootstrapThread.start();
430            joinNonDaemonThreads(threadGroup);
431            // It's plausible that spontaneously a non-daemon thread might be
432            // created as we try and shut down,
433            // but it's too late since the termination condition (only daemon
434            // threads) has been triggered.
435            if (keepAlive) {
436                getLog().warn("Warning: keepAlive is now deprecated and obsolete. Do you need it? Please comment on MEXEC-6.");
437                waitFor(0);
438            }
439    
440            if (cleanupDaemonThreads) {
441    
442                terminateThreads(threadGroup);
443    
444                try {
445                    threadGroup.destroy();
446                } catch (IllegalThreadStateException e) {
447                    getLog().warn("Couldn't destroy threadgroup " + threadGroup, e);
448                }
449            }
450    
451            if (originalSystemProperties != null) {
452                System.setProperties(originalSystemProperties);
453            }
454    
455            synchronized (threadGroup) {
456                if (threadGroup.uncaughtException != null) {
457                    throw new MojoExecutionException(null, threadGroup.uncaughtException);
458                }
459            }
460    
461            registerSourceRoots();
462        }
463    
464        class IsolatedThreadGroup extends ThreadGroup {
465            Throwable uncaughtException; // synchronize access to this
466    
467            public IsolatedThreadGroup(String name) {
468                super(name);
469            }
470    
471            public void uncaughtException(Thread thread, Throwable throwable) {
472                if (throwable instanceof ThreadDeath) {
473                    return; // harmless
474                }
475                boolean doLog = false;
476                synchronized (this) {
477                    // only remember the first one
478                    if (uncaughtException == null) {
479                        uncaughtException = throwable; // will be reported
480                        // eventually
481                    } else {
482                        doLog = true;
483                    }
484                }
485                if (doLog) {
486                    getLog().warn("an additional exception was thrown", throwable);
487                }
488            }
489        }
490    
491        private void joinNonDaemonThreads(ThreadGroup threadGroup) {
492            boolean foundNonDaemon;
493            do {
494                foundNonDaemon = false;
495                Collection threads = getActiveThreads(threadGroup);
496                for (Iterator iter = threads.iterator(); iter.hasNext();) {
497                    Thread thread = (Thread)iter.next();
498                    if (thread.isDaemon()) {
499                        continue;
500                    }
501                    foundNonDaemon = true; // try again; maybe more threads were
502                    // created while we were busy
503                    joinThread(thread, 0);
504                }
505            } while (foundNonDaemon);
506        }
507    
508        private void joinThread(Thread thread, long timeoutMsecs) {
509            try {
510                getLog().debug("joining on thread " + thread);
511                thread.join(timeoutMsecs);
512            } catch (InterruptedException e) {
513                Thread.currentThread().interrupt(); // good practice if don't throw
514                getLog().warn("interrupted while joining against thread " + thread, e); // not
515                // expected!
516            }
517            // generally abnormal
518            if (thread.isAlive()) {
519                getLog().warn("thread " + thread + " was interrupted but is still alive after waiting at least "
520                                  + timeoutMsecs + "msecs");
521            }
522        }
523    
524        private void terminateThreads(ThreadGroup threadGroup) {
525            long startTime = System.currentTimeMillis();
526            Set uncooperativeThreads = new HashSet(); // these were not responsive
527            // to interruption
528            for (Collection threads = getActiveThreads(threadGroup); !threads.isEmpty(); threads = getActiveThreads(threadGroup), threads
529                .removeAll(uncooperativeThreads)) {
530                // Interrupt all threads we know about as of this instant (harmless
531                // if spuriously went dead (! isAlive())
532                // or if something else interrupted it ( isInterrupted() ).
533                for (Iterator iter = threads.iterator(); iter.hasNext();) {
534                    Thread thread = (Thread)iter.next();
535                    getLog().debug("interrupting thread " + thread);
536                    thread.interrupt();
537                }
538                // Now join with a timeout and call stop() (assuming flags are set
539                // right)
540                for (Iterator iter = threads.iterator(); iter.hasNext();) {
541                    Thread thread = (Thread)iter.next();
542                    if (!thread.isAlive()) {
543                        continue; // and, presumably it won't show up in
544                        // getActiveThreads() next iteration
545                    }
546                    if (daemonThreadJoinTimeout <= 0) {
547                        joinThread(thread, 0); // waits until not alive; no timeout
548                        continue;
549                    }
550                    long timeout = daemonThreadJoinTimeout - (System.currentTimeMillis() - startTime);
551                    if (timeout > 0) {
552                        joinThread(thread, timeout);
553                    }
554                    if (!thread.isAlive()) {
555                        continue;
556                    }
557                    uncooperativeThreads.add(thread); // ensure we don't process
558                    // again
559                    if (stopUnresponsiveDaemonThreads) {
560                        getLog().warn("thread " + thread + " will be Thread.stop()'ed");
561                        thread.stop();
562                    } else {
563                        getLog().warn("thread " + thread
564                                + " will linger despite being asked to die via interruption");
565                    }
566                }
567            }
568            if (!uncooperativeThreads.isEmpty()) {
569                getLog().warn("NOTE: "
570                        + uncooperativeThreads.size()
571                              + " thread(s) did not finish despite being asked to "
572                              + " via interruption. This is not a problem with exec:java, it is a problem with the running code."
573                              + " Although not serious, it should be remedied.");
574            } else {
575                int activeCount = threadGroup.activeCount();
576                if (activeCount != 0) {
577                    // TODO this may be nothing; continue on anyway; perhaps don't
578                    // even log in future
579                    Thread[] threadsArray = new Thread[1];
580                    threadGroup.enumerate(threadsArray);
581                    getLog().debug("strange; " + activeCount + " thread(s) still active in the group "
582                                       + threadGroup + " such as " + threadsArray[0]);
583                }
584            }
585        }
586    
587        private Collection getActiveThreads(ThreadGroup threadGroup) {
588            Thread[] threads = new Thread[threadGroup.activeCount()];
589            int numThreads = threadGroup.enumerate(threads);
590            Collection result = new ArrayList(numThreads);
591            for (int i = 0; i < threads.length && threads[i] != null; i++) {
592                result.add(threads[i]);
593            }
594            // note: result should be modifiable
595            return result;
596        }
597    
598        /**
599         * Pass any given system properties to the java system properties.
600         */
601        private void setSystemProperties() {
602            if (systemProperties != null) {
603                originalSystemProperties = System.getProperties();
604                for (int i = 0; i < systemProperties.length; i++) {
605                    Property systemProperty = systemProperties[i];
606                    String value = systemProperty.getValue();
607                    System.setProperty(systemProperty.getKey(), value == null ? "" : value);
608                }
609            }
610        }
611    
612        /**
613         * Set up a classloader for the execution of the main class.
614         *
615         * @return the classloader
616         * @throws MojoExecutionException
617         */
618        private ClassLoader getClassLoader() throws MojoExecutionException {
619            List classpathURLs = new ArrayList();
620            this.addRelevantPluginDependenciesToClasspath(classpathURLs);
621            this.addRelevantProjectDependenciesToClasspath(classpathURLs);
622    
623            getLog().info("Classpath = " + classpathURLs);
624            return new URLClassLoader((URL[])classpathURLs.toArray(new URL[classpathURLs.size()]));
625        }
626    
627        /**
628         * Add any relevant project dependencies to the classpath. Indirectly takes
629         * includePluginDependencies and ExecutableDependency into consideration.
630         *
631         * @param path classpath of {@link java.net.URL} objects
632         * @throws MojoExecutionException
633         */
634        private void addRelevantPluginDependenciesToClasspath(List path) throws MojoExecutionException {
635            if (hasCommandlineArgs()) {
636                arguments = parseCommandlineArgs();
637            }
638    
639            try {
640                Iterator iter = this.determineRelevantPluginDependencies().iterator();
641                while (iter.hasNext()) {
642                    Artifact classPathElement = (Artifact)iter.next();
643                    getLog().debug("Adding plugin dependency artifact: " + classPathElement.getArtifactId()
644                                       + " to classpath");
645                    path.add(classPathElement.getFile().toURL());
646                }
647            } catch (MalformedURLException e) {
648                throw new MojoExecutionException("Error during setting up classpath", e);
649            }
650    
651        }
652    
653        /**
654         * Add any relevant project dependencies to the classpath. Takes
655         * includeProjectDependencies into consideration.
656         *
657         * @param path classpath of {@link java.net.URL} objects
658         * @throws MojoExecutionException
659         */
660        private void addRelevantProjectDependenciesToClasspath(List path) throws MojoExecutionException {
661            if (this.includeProjectDependencies) {
662                try {
663                    getLog().debug("Project Dependencies will be included.");
664    
665                    URL mainClasses = new File(project.getBuild().getOutputDirectory()).toURL();
666                    getLog().debug("Adding to classpath : " + mainClasses);
667                    path.add(mainClasses);
668    
669                    Set dependencies = project.getArtifacts();
670    
671                    // system scope dependencies are not returned by maven 2.0. See
672                    // MEXEC-17
673                    dependencies.addAll(getAllNonTestScopedDependencies());
674    
675                    Iterator iter = dependencies.iterator();
676                    while (iter.hasNext()) {
677                        Artifact classPathElement = (Artifact)iter.next();
678                        getLog().debug("Adding project dependency artifact: " + classPathElement.getArtifactId()
679                                           + " to classpath");
680                        File file = classPathElement.getFile();
681                        if (file != null) {
682                            path.add(file.toURL());
683                        }
684                    }
685    
686                } catch (MalformedURLException e) {
687                    throw new MojoExecutionException("Error during setting up classpath", e);
688                }
689            } else {
690                getLog().debug("Project Dependencies will be excluded.");
691            }
692    
693        }
694    
695        private Collection getAllNonTestScopedDependencies() throws MojoExecutionException {
696            List answer = new ArrayList();
697    
698            for (Iterator artifacts = getAllDependencies().iterator(); artifacts.hasNext();) {
699                Artifact artifact = (Artifact)artifacts.next();
700    
701                // do not add test artifacts
702                if (!artifact.getScope().equals(Artifact.SCOPE_TEST)) {
703                    answer.add(artifact);
704                }
705            }
706            return answer;
707        }
708    
709        // generic method to retrieve all the transitive dependencies
710        private Collection getAllDependencies() throws MojoExecutionException {
711            List artifacts = new ArrayList();
712    
713            for (Iterator dependencies = project.getDependencies().iterator(); dependencies.hasNext();) {
714                Dependency dependency = (Dependency)dependencies.next();
715    
716                String groupId = dependency.getGroupId();
717                String artifactId = dependency.getArtifactId();
718    
719                VersionRange versionRange;
720                try {
721                    versionRange = VersionRange.createFromVersionSpec(dependency.getVersion());
722                } catch (InvalidVersionSpecificationException e) {
723                    throw new MojoExecutionException("unable to parse version", e);
724                }
725    
726                String type = dependency.getType();
727                if (type == null) {
728                    type = "jar";
729                }
730                String classifier = dependency.getClassifier();
731                boolean optional = dependency.isOptional();
732                String scope = dependency.getScope();
733                if (scope == null) {
734                    scope = Artifact.SCOPE_COMPILE;
735                }
736    
737                Artifact art = this.artifactFactory.createDependencyArtifact(groupId, artifactId, versionRange,
738                                                                             type, classifier, scope, optional);
739    
740                if (scope.equalsIgnoreCase(Artifact.SCOPE_SYSTEM)) {
741                    art.setFile(new File(dependency.getSystemPath()));
742                }
743    
744                List exclusions = new ArrayList();
745                for (Iterator j = dependency.getExclusions().iterator(); j.hasNext();) {
746                    Exclusion e = (Exclusion)j.next();
747                    exclusions.add(e.getGroupId() + ":" + e.getArtifactId());
748                }
749    
750                ArtifactFilter newFilter = new ExcludesArtifactFilter(exclusions);
751    
752                art.setDependencyFilter(newFilter);
753    
754                artifacts.add(art);
755            }
756    
757            return artifacts;
758        }
759    
760        /**
761         * Determine all plugin dependencies relevant to the executable. Takes
762         * includePlugins, and the executableDependency into consideration.
763         *
764         * @return a set of Artifact objects. (Empty set is returned if there are no
765         *         relevant plugin dependencies.)
766         * @throws MojoExecutionException
767         */
768        private Set determineRelevantPluginDependencies() throws MojoExecutionException {
769            Set relevantDependencies;
770            if (this.includePluginDependencies) {
771                if (this.executableDependency == null) {
772                    getLog().debug("All Plugin Dependencies will be included.");
773                    relevantDependencies = new HashSet(this.pluginDependencies);
774                } else {
775                    getLog().debug("Selected plugin Dependencies will be included.");
776                    Artifact executableArtifact = this.findExecutableArtifact();
777                    Artifact executablePomArtifact = this.getExecutablePomArtifact(executableArtifact);
778                    relevantDependencies = this.resolveExecutableDependencies(executablePomArtifact);
779                }
780            } else {
781                relevantDependencies = Collections.EMPTY_SET;
782                getLog().debug("Plugin Dependencies will be excluded.");
783            }
784            return relevantDependencies;
785        }
786    
787        /**
788         * Get the artifact which refers to the POM of the executable artifact.
789         *
790         * @param executableArtifact this artifact refers to the actual assembly.
791         * @return an artifact which refers to the POM of the executable artifact.
792         */
793        private Artifact getExecutablePomArtifact(Artifact executableArtifact) {
794            return this.artifactFactory.createBuildArtifact(executableArtifact.getGroupId(), executableArtifact
795                .getArtifactId(), executableArtifact.getVersion(), "pom");
796        }
797    
798        /**
799         * Examine the plugin dependencies to find the executable artifact.
800         *
801         * @return an artifact which refers to the actual executable tool (not a POM)
802         * @throws MojoExecutionException
803         */
804        private Artifact findExecutableArtifact() throws MojoExecutionException {
805            // ILimitedArtifactIdentifier execToolAssembly =
806            // this.getExecutableToolAssembly();
807    
808            Artifact executableTool = null;
809            for (Iterator iter = this.pluginDependencies.iterator(); iter.hasNext();) {
810                Artifact pluginDep = (Artifact)iter.next();
811                if (this.executableDependency.matches(pluginDep)) {
812                    executableTool = pluginDep;
813                    break;
814                }
815            }
816    
817            if (executableTool == null) {
818                throw new MojoExecutionException("No dependency of the plugin matches the specified executableDependency."
819                                                     + "  Specified executableToolAssembly is: "
820                                                     + executableDependency.toString());
821            }
822    
823            return executableTool;
824        }
825    
826        private Set resolveExecutableDependencies(Artifact executablePomArtifact) throws MojoExecutionException {
827    
828            Set executableDependencies;
829            try {
830                MavenProject executableProject = this.projectBuilder.buildFromRepository(executablePomArtifact,
831                                                                                         this.remoteRepositories,
832                                                                                         this.localRepository);
833    
834                // get all of the dependencies for the executable project
835                List dependencies = executableProject.getDependencies();
836    
837                // make Artifacts of all the dependencies
838                Set dependencyArtifacts = MavenMetadataSource.createArtifacts(this.artifactFactory, dependencies,
839                                                                              null, null, null);
840    
841                // not forgetting the Artifact of the project itself
842                dependencyArtifacts.add(executableProject.getArtifact());
843    
844                // resolve all dependencies transitively to obtain a comprehensive
845                // list of assemblies
846                ArtifactResolutionResult result = artifactResolver.resolveTransitively(dependencyArtifacts,
847                                                                                       executablePomArtifact,
848                                                                                       Collections.EMPTY_MAP,
849                                                                                       this.localRepository,
850                                                                                       this.remoteRepositories,
851                                                                                       metadataSource, null,
852                                                                                       Collections.EMPTY_LIST);
853                executableDependencies = result.getArtifacts();
854    
855            } catch (Exception ex) {
856                throw new MojoExecutionException("Encountered problems resolving dependencies of the executable "
857                                                 + "in preparation for its execution.", ex);
858            }
859    
860            return executableDependencies;
861        }
862    
863        /**
864         * Stop program execution for nn millis.
865         *
866         * @param millis the number of millis-seconds to wait for, <code>0</code>
867         *                stops program forever.
868         */
869        private void waitFor(long millis) {
870            Object lock = new Object();
871            synchronized (lock) {
872                try {
873                    lock.wait(millis);
874                } catch (InterruptedException e) {
875                    Thread.currentThread().interrupt(); // good practice if don't throw
876                    getLog().warn("Spuriously interrupted while waiting for " + millis + "ms", e);
877                }
878            }
879        }
880    
881    }