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