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