1 package org.codehaus.mojo.exec;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.artifact.Artifact;
23 import org.apache.maven.artifact.factory.ArtifactFactory;
24 import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
25 import org.apache.maven.artifact.repository.ArtifactRepository;
26 import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
27 import org.apache.maven.artifact.resolver.ArtifactResolver;
28 import org.apache.maven.plugin.MojoExecutionException;
29 import org.apache.maven.plugin.MojoFailureException;
30 import org.apache.maven.project.MavenProject;
31 import org.apache.maven.project.MavenProjectBuilder;
32 import org.apache.maven.project.artifact.MavenMetadataSource;
33
34 import java.io.File;
35 import java.lang.reflect.Method;
36 import java.net.MalformedURLException;
37 import java.net.URL;
38 import java.net.URLClassLoader;
39 import java.util.ArrayList;
40 import java.util.Collection;
41 import java.util.Collections;
42 import java.util.HashSet;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Properties;
46 import java.util.Set;
47
48
49
50
51
52
53
54
55
56
57
58 public class ExecJavaMojo
59 extends AbstractExecMojo
60 {
61
62
63
64 private ArtifactResolver artifactResolver;
65
66
67
68
69 private ArtifactFactory artifactFactory;
70
71
72
73
74 private ArtifactMetadataSource metadataSource;
75
76
77
78
79
80
81
82 private ArtifactRepository localRepository;
83
84
85
86
87
88
89
90 private List remoteRepositories;
91
92
93
94
95
96 private MavenProjectBuilder projectBuilder;
97
98
99
100
101
102
103 private List pluginDependencies;
104
105
106
107
108
109
110
111
112 private String mainClass;
113
114
115
116
117
118
119
120 private String[] arguments;
121
122
123
124
125
126
127
128
129
130 private Property[] systemProperties;
131
132
133
134
135
136
137
138
139
140 private boolean keepAlive;
141
142
143
144
145
146
147
148
149 private boolean includeProjectDependencies;
150
151
152
153
154
155
156
157
158
159
160
161
162
163 private boolean includePluginDependencies;
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179 private ExecutableDependency executableDependency;
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195 private boolean cleanupDaemonThreads;
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210 private long daemonThreadJoinTimeout;
211
212
213
214
215
216
217
218
219
220
221
222
223
224 private boolean stopUnresponsiveDaemonThreads;
225
226
227
228
229
230
231
232
233 private long killAfter;
234
235 private Properties originalSystemProperties;
236
237
238
239
240
241
242 public void execute()
243 throws MojoExecutionException, MojoFailureException
244 {
245 if ( isSkip() )
246 {
247 getLog().info( "skipping execute as per configuraion" );
248 return;
249 }
250 if ( killAfter != -1 )
251 {
252 getLog().warn( "Warning: killAfter is now deprecated. Do you need it ? Please comment on MEXEC-6." );
253 }
254
255 if ( null == arguments )
256 {
257 arguments = new String[0];
258 }
259
260 if ( getLog().isDebugEnabled() )
261 {
262 StringBuffer msg = new StringBuffer( "Invoking : " );
263 msg.append( mainClass );
264 msg.append( ".main(" );
265 for ( int i = 0; i < arguments.length; i++ )
266 {
267 if ( i > 0 )
268 {
269 msg.append( ", " );
270 }
271 msg.append( arguments[i] );
272 }
273 msg.append( ")" );
274 getLog().debug( msg );
275 }
276
277 IsolatedThreadGroup threadGroup = new IsolatedThreadGroup( mainClass
278 Thread bootstrapThread = new Thread( threadGroup, new Runnable()
279 {
280 public void run()
281 {
282 try
283 {
284 Method main = Thread.currentThread().getContextClassLoader().loadClass( mainClass )
285 .getMethod( "main", new Class[]{ String[].class } );
286 if ( ! main.isAccessible() )
287 {
288 getLog().debug( "Setting accessibility to true in order to invoke main()." );
289 main.setAccessible( true );
290 }
291 main.invoke( main, new Object[]{arguments} );
292 }
293 catch ( NoSuchMethodException e )
294 {
295 Thread.currentThread().getThreadGroup().uncaughtException( Thread.currentThread(),
296 new Exception(
297 "The specified mainClass doesn't contain a main method with appropriate signature.", e
298 )
299 );
300 }
301 catch ( Exception e )
302 {
303 Thread.currentThread().getThreadGroup().uncaughtException( Thread.currentThread(), e );
304 }
305 }
306 }, mainClass + ".main()" );
307 bootstrapThread.setContextClassLoader( getClassLoader() );
308 setSystemProperties();
309
310 bootstrapThread.start();
311 joinNonDaemonThreads( threadGroup );
312
313
314 if ( keepAlive )
315 {
316 getLog().warn(
317 "Warning: keepAlive is now deprecated and obsolete. Do you need it? Please comment on MEXEC-6." );
318 waitFor( 0 );
319 }
320
321 if ( cleanupDaemonThreads )
322 {
323
324 terminateThreads( threadGroup );
325
326 try
327 {
328 threadGroup.destroy();
329 }
330 catch ( IllegalThreadStateException e )
331 {
332 getLog().warn( "Couldn't destroy threadgroup " + threadGroup, e );
333 }
334 }
335
336
337 if ( originalSystemProperties != null )
338 {
339 System.setProperties( originalSystemProperties );
340 }
341
342 synchronized ( threadGroup )
343 {
344 if ( threadGroup.uncaughtException != null )
345 {
346 throw new MojoExecutionException( "An exception occured while executing the Java class. "
347 + threadGroup.uncaughtException.getMessage(),
348 threadGroup.uncaughtException );
349 }
350 }
351
352 registerSourceRoots();
353 }
354
355
356
357
358 class IsolatedThreadGroup extends ThreadGroup
359 {
360 private Throwable uncaughtException;
361
362 public IsolatedThreadGroup( String name )
363 {
364 super( name );
365 }
366
367 public void uncaughtException( Thread thread, Throwable throwable )
368 {
369 if ( throwable instanceof ThreadDeath )
370 {
371 return;
372 }
373 boolean doLog = false;
374 synchronized ( this )
375 {
376 if ( uncaughtException == null )
377 {
378 uncaughtException = throwable;
379 }
380 else
381 {
382 doLog = true;
383 }
384 }
385 if ( doLog )
386 {
387 getLog().warn( "an additional exception was thrown", throwable );
388 }
389 }
390 }
391
392 private void joinNonDaemonThreads( ThreadGroup threadGroup )
393 {
394 boolean foundNonDaemon;
395 do
396 {
397 foundNonDaemon = false;
398 Collection threads = getActiveThreads( threadGroup );
399 for ( Iterator iter = threads.iterator(); iter.hasNext(); )
400 {
401 Thread thread = (Thread) iter.next();
402 if ( thread.isDaemon() )
403 {
404 continue;
405 }
406 foundNonDaemon = true;
407 joinThread( thread, 0 );
408 }
409 } while ( foundNonDaemon );
410 }
411
412 private void joinThread( Thread thread, long timeoutMsecs )
413 {
414 try
415 {
416 getLog().debug( "joining on thread " + thread );
417 thread.join( timeoutMsecs );
418 }
419 catch ( InterruptedException e )
420 {
421 Thread.currentThread().interrupt();
422 getLog().warn( "interrupted while joining against thread " + thread, e );
423 }
424 if ( thread.isAlive() )
425 {
426 getLog().warn( "thread " + thread + " was interrupted but is still alive after waiting at least "
427 + timeoutMsecs + "msecs" );
428 }
429 }
430
431 private void terminateThreads( ThreadGroup threadGroup )
432 {
433 long startTime = System.currentTimeMillis();
434 Set uncooperativeThreads = new HashSet();
435 for ( Collection threads = getActiveThreads( threadGroup ); !threads.isEmpty();
436 threads = getActiveThreads( threadGroup ), threads.removeAll( uncooperativeThreads ) )
437 {
438
439
440 for ( Iterator iter = threads.iterator(); iter.hasNext(); )
441 {
442 Thread thread = (Thread) iter.next();
443 getLog().debug( "interrupting thread " + thread );
444 thread.interrupt();
445 }
446
447 for ( Iterator iter = threads.iterator(); iter.hasNext(); )
448 {
449 Thread thread = (Thread) iter.next();
450 if ( ! thread.isAlive() )
451 {
452 continue;
453 }
454 if ( daemonThreadJoinTimeout <= 0 )
455 {
456 joinThread( thread, 0 );
457 continue;
458 }
459 long timeout = daemonThreadJoinTimeout
460 - ( System.currentTimeMillis() - startTime );
461 if ( timeout > 0 )
462 {
463 joinThread( thread, timeout );
464 }
465 if ( ! thread.isAlive() )
466 {
467 continue;
468 }
469 uncooperativeThreads.add( thread );
470 if ( stopUnresponsiveDaemonThreads )
471 {
472 getLog().warn( "thread " + thread + " will be Thread.stop()'ed" );
473 thread.stop();
474 }
475 else
476 {
477 getLog().warn( "thread " + thread + " will linger despite being asked to die via interruption" );
478 }
479 }
480 }
481 if ( ! uncooperativeThreads.isEmpty() )
482 {
483 getLog().warn( "NOTE: " + uncooperativeThreads.size() + " thread(s) did not finish despite being asked to "
484 + " via interruption. This is not a problem with exec:java, it is a problem with the running code."
485 + " Although not serious, it should be remedied." );
486 }
487 else
488 {
489 int activeCount = threadGroup.activeCount();
490 if ( activeCount != 0 )
491 {
492
493 Thread[] threadsArray = new Thread[1];
494 threadGroup.enumerate( threadsArray );
495 getLog().debug( "strange; " + activeCount
496 + " thread(s) still active in the group " + threadGroup + " such as " + threadsArray[0] );
497 }
498 }
499 }
500
501 private Collection getActiveThreads( ThreadGroup threadGroup )
502 {
503 Thread[] threads = new Thread[ threadGroup.activeCount() ];
504 int numThreads = threadGroup.enumerate( threads );
505 Collection result = new ArrayList( numThreads );
506 for ( int i = 0; i < threads.length && threads[i] != null; i++ )
507 {
508 result.add( threads[i] );
509 }
510 return result;
511 }
512
513
514
515
516 private void setSystemProperties()
517 {
518 if ( systemProperties != null )
519 {
520 originalSystemProperties = System.getProperties();
521 for ( int i = 0; i < systemProperties.length; i++ )
522 {
523 Property systemProperty = systemProperties[i];
524 String value = systemProperty.getValue();
525 System.setProperty( systemProperty.getKey(), value == null ? "" : value );
526 }
527 }
528 }
529
530
531
532
533
534
535
536 private ClassLoader getClassLoader()
537 throws MojoExecutionException
538 {
539 List classpathURLs = new ArrayList();
540 this.addRelevantPluginDependenciesToClasspath( classpathURLs );
541 this.addRelevantProjectDependenciesToClasspath( classpathURLs );
542 return new URLClassLoader( ( URL[] ) classpathURLs.toArray( new URL[ classpathURLs.size() ] ) );
543 }
544
545
546
547
548
549
550
551
552 private void addRelevantPluginDependenciesToClasspath( List path )
553 throws MojoExecutionException
554 {
555 if ( hasCommandlineArgs() )
556 {
557 arguments = parseCommandlineArgs();
558 }
559
560 try
561 {
562 Iterator iter = this.determineRelevantPluginDependencies().iterator();
563 while ( iter.hasNext() )
564 {
565 Artifact classPathElement = (Artifact) iter.next();
566 getLog().debug(
567 "Adding plugin dependency artifact: " + classPathElement.getArtifactId() + " to classpath" );
568 path.add( classPathElement.getFile().toURI().toURL() );
569 }
570 }
571 catch ( MalformedURLException e )
572 {
573 throw new MojoExecutionException( "Error during setting up classpath", e );
574 }
575
576 }
577
578
579
580
581
582
583
584
585 private void addRelevantProjectDependenciesToClasspath( List path )
586 throws MojoExecutionException
587 {
588 if ( this.includeProjectDependencies )
589 {
590 try
591 {
592 getLog().debug( "Project Dependencies will be included." );
593
594 List artifacts = new ArrayList();
595 List theClasspathFiles = new ArrayList();
596
597 collectProjectArtifactsAndClasspath( artifacts, theClasspathFiles );
598
599 for ( Iterator it = theClasspathFiles.iterator(); it.hasNext(); )
600 {
601 URL url = ( (File) it.next() ).toURI().toURL();
602 getLog().debug( "Adding to classpath : " + url );
603 path.add( url );
604 }
605
606 Iterator iter = artifacts.iterator();
607 while ( iter.hasNext() )
608 {
609 Artifact classPathElement = (Artifact) iter.next();
610 getLog().debug(
611 "Adding project dependency artifact: " + classPathElement.getArtifactId() + " to classpath" );
612 path.add( classPathElement.getFile().toURI().toURL() );
613 }
614
615 }
616 catch ( MalformedURLException e )
617 {
618 throw new MojoExecutionException( "Error during setting up classpath", e );
619 }
620 }
621 else
622 {
623 getLog().debug( "Project Dependencies will be excluded." );
624 }
625
626 }
627
628
629
630
631
632
633
634
635
636 private Set determineRelevantPluginDependencies()
637 throws MojoExecutionException
638 {
639 Set relevantDependencies;
640 if ( this.includePluginDependencies )
641 {
642 if ( this.executableDependency == null )
643 {
644 getLog().debug( "All Plugin Dependencies will be included." );
645 relevantDependencies = new HashSet( this.pluginDependencies );
646 }
647 else
648 {
649 getLog().debug( "Selected plugin Dependencies will be included." );
650 Artifact executableArtifact = this.findExecutableArtifact();
651 Artifact executablePomArtifact = this.getExecutablePomArtifact( executableArtifact );
652 relevantDependencies = this.resolveExecutableDependencies( executablePomArtifact );
653 }
654 }
655 else
656 {
657 relevantDependencies = Collections.EMPTY_SET;
658 getLog().debug( "Plugin Dependencies will be excluded." );
659 }
660 return relevantDependencies;
661 }
662
663
664
665
666
667
668
669 private Artifact getExecutablePomArtifact( Artifact executableArtifact )
670 {
671 return this.artifactFactory.createBuildArtifact( executableArtifact.getGroupId(),
672 executableArtifact.getArtifactId(),
673 executableArtifact.getVersion(), "pom" );
674 }
675
676
677
678
679
680
681
682 private Artifact findExecutableArtifact()
683 throws MojoExecutionException
684 {
685
686
687 Artifact executableTool = null;
688 for ( Iterator iter = this.pluginDependencies.iterator(); iter.hasNext(); )
689 {
690 Artifact pluginDep = (Artifact) iter.next();
691 if ( this.executableDependency.matches( pluginDep ) )
692 {
693 executableTool = pluginDep;
694 break;
695 }
696 }
697
698 if ( executableTool == null )
699 {
700 throw new MojoExecutionException(
701 "No dependency of the plugin matches the specified executableDependency."
702 + " Specified executableToolAssembly is: " + executableDependency.toString() );
703 }
704
705 return executableTool;
706 }
707
708
709
710
711
712
713
714 private Set resolveExecutableDependencies( Artifact executablePomArtifact )
715 throws MojoExecutionException
716 {
717
718 Set executableDependencies;
719 try
720 {
721 MavenProject executableProject = this.projectBuilder.buildFromRepository( executablePomArtifact,
722 this.remoteRepositories,
723 this.localRepository );
724
725
726 List dependencies = executableProject.getDependencies();
727
728
729 Set dependencyArtifacts =
730 MavenMetadataSource.createArtifacts( this.artifactFactory, dependencies, null, null, null );
731
732
733 dependencyArtifacts.add( executableProject.getArtifact() );
734
735
736 ArtifactResolutionResult result = artifactResolver.resolveTransitively( dependencyArtifacts,
737 executablePomArtifact,
738 Collections.EMPTY_MAP,
739 this.localRepository,
740 this.remoteRepositories,
741 metadataSource, null,
742 Collections.EMPTY_LIST );
743 executableDependencies = result.getArtifacts();
744
745 }
746 catch ( Exception ex )
747 {
748 throw new MojoExecutionException(
749 "Encountered problems resolving dependencies of the executable " + "in preparation for its execution.",
750 ex );
751 }
752
753 return executableDependencies;
754 }
755
756
757
758
759
760
761
762 private void waitFor( long millis )
763 {
764 Object lock = new Object();
765 synchronized ( lock )
766 {
767 try
768 {
769 lock.wait( millis );
770 }
771 catch ( InterruptedException e )
772 {
773 Thread.currentThread().interrupt();
774 getLog().warn( "Spuriously interrupted while waiting for " + millis + "ms", e );
775 }
776 }
777 }
778
779 }