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