1 package org.apache.maven.plugin.internal;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.BufferedInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.PrintStream;
29 import java.io.Reader;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.jar.JarFile;
38 import java.util.zip.ZipEntry;
39
40 import org.apache.maven.RepositoryUtils;
41 import org.apache.maven.artifact.Artifact;
42 import org.apache.maven.classrealm.ClassRealmManager;
43 import org.apache.maven.execution.MavenSession;
44 import org.apache.maven.model.Plugin;
45 import org.apache.maven.monitor.logging.DefaultLog;
46 import org.apache.maven.plugin.ContextEnabled;
47 import org.apache.maven.plugin.DebugConfigurationListener;
48 import org.apache.maven.plugin.InvalidPluginDescriptorException;
49 import org.apache.maven.plugin.MavenPluginManager;
50 import org.apache.maven.plugin.MavenPluginValidator;
51 import org.apache.maven.plugin.Mojo;
52 import org.apache.maven.plugin.MojoExecution;
53 import org.apache.maven.plugin.MojoNotFoundException;
54 import org.apache.maven.plugin.PluginConfigurationException;
55 import org.apache.maven.plugin.PluginContainerException;
56 import org.apache.maven.plugin.PluginDescriptorCache;
57 import org.apache.maven.plugin.PluginDescriptorParsingException;
58 import org.apache.maven.plugin.PluginParameterException;
59 import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
60 import org.apache.maven.plugin.PluginRealmCache;
61 import org.apache.maven.plugin.PluginResolutionException;
62 import org.apache.maven.plugin.descriptor.MojoDescriptor;
63 import org.apache.maven.plugin.descriptor.Parameter;
64 import org.apache.maven.plugin.descriptor.PluginDescriptor;
65 import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
66 import org.apache.maven.project.MavenProject;
67 import org.codehaus.plexus.PlexusContainer;
68 import org.codehaus.plexus.classworlds.realm.ClassRealm;
69 import org.codehaus.plexus.component.annotations.Component;
70 import org.codehaus.plexus.component.annotations.Requirement;
71 import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
72 import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
73 import org.codehaus.plexus.component.configurator.ComponentConfigurator;
74 import org.codehaus.plexus.component.configurator.ConfigurationListener;
75 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
76 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
77 import org.codehaus.plexus.component.repository.ComponentDescriptor;
78 import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
79 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
80 import org.codehaus.plexus.configuration.PlexusConfiguration;
81 import org.codehaus.plexus.configuration.PlexusConfigurationException;
82 import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
83 import org.codehaus.plexus.logging.Logger;
84 import org.codehaus.plexus.util.IOUtil;
85 import org.codehaus.plexus.util.ReaderFactory;
86 import org.codehaus.plexus.util.StringUtils;
87 import org.codehaus.plexus.util.xml.Xpp3Dom;
88 import org.sonatype.aether.RepositorySystemSession;
89 import org.sonatype.aether.graph.DependencyFilter;
90 import org.sonatype.aether.graph.DependencyNode;
91 import org.sonatype.aether.repository.RemoteRepository;
92 import org.sonatype.aether.util.filter.AndDependencyFilter;
93 import org.sonatype.aether.util.graph.PreorderNodeListGenerator;
94
95
96
97
98
99
100
101
102
103 @Component( role = MavenPluginManager.class )
104 public class DefaultMavenPluginManager
105 implements MavenPluginManager
106 {
107
108 @Requirement
109 private Logger logger;
110
111 @Requirement
112 private PlexusContainer container;
113
114 @Requirement
115 private ClassRealmManager classRealmManager;
116
117 @Requirement
118 private PluginDescriptorCache pluginDescriptorCache;
119
120 @Requirement
121 private PluginRealmCache pluginRealmCache;
122
123 @Requirement
124 private PluginDependenciesResolver pluginDependenciesResolver;
125
126 private PluginDescriptorBuilder builder = new PluginDescriptorBuilder();
127
128 public synchronized PluginDescriptor getPluginDescriptor( Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session )
129 throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException
130 {
131 PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey( plugin, repositories, session );
132
133 PluginDescriptor pluginDescriptor = pluginDescriptorCache.get( cacheKey );
134
135 if ( pluginDescriptor == null )
136 {
137 Artifact pluginArtifact =
138 RepositoryUtils.toArtifact( pluginDependenciesResolver.resolve( plugin, repositories, session ) );
139
140 pluginDescriptor = extractPluginDescriptor( pluginArtifact, plugin );
141
142 pluginDescriptorCache.put( cacheKey, pluginDescriptor );
143 }
144
145 pluginDescriptor.setPlugin( plugin );
146
147 return pluginDescriptor;
148 }
149
150 private PluginDescriptor extractPluginDescriptor( Artifact pluginArtifact, Plugin plugin )
151 throws PluginDescriptorParsingException, InvalidPluginDescriptorException
152 {
153 PluginDescriptor pluginDescriptor = null;
154
155 File pluginFile = pluginArtifact.getFile();
156
157 try
158 {
159 if ( pluginFile.isFile() )
160 {
161 JarFile pluginJar = new JarFile( pluginFile, false );
162 try
163 {
164 ZipEntry pluginDescriptorEntry = pluginJar.getEntry( getPluginDescriptorLocation() );
165
166 if ( pluginDescriptorEntry != null )
167 {
168 InputStream is = pluginJar.getInputStream( pluginDescriptorEntry );
169
170 pluginDescriptor = parsePluginDescriptor( is, plugin, pluginFile.getAbsolutePath() );
171 }
172 }
173 finally
174 {
175 pluginJar.close();
176 }
177 }
178 else
179 {
180 File pluginXml = new File( pluginFile, getPluginDescriptorLocation() );
181
182 if ( pluginXml.isFile() )
183 {
184 InputStream is = new BufferedInputStream( new FileInputStream( pluginXml ) );
185 try
186 {
187 pluginDescriptor = parsePluginDescriptor( is, plugin, pluginXml.getAbsolutePath() );
188 }
189 finally
190 {
191 IOUtil.close( is );
192 }
193 }
194 }
195
196 if ( pluginDescriptor == null )
197 {
198 throw new IOException( "No plugin descriptor found at " + getPluginDescriptorLocation() );
199 }
200 }
201 catch ( IOException e )
202 {
203 throw new PluginDescriptorParsingException( plugin, pluginFile.getAbsolutePath(), e );
204 }
205
206 MavenPluginValidator validator = new MavenPluginValidator( pluginArtifact );
207
208 validator.validate( pluginDescriptor );
209
210 if ( validator.hasErrors() )
211 {
212 throw new InvalidPluginDescriptorException( "Invalid plugin descriptor for " + plugin.getId() + " ("
213 + pluginFile + ")", validator.getErrors() );
214 }
215
216 pluginDescriptor.setPluginArtifact( pluginArtifact );
217
218 return pluginDescriptor;
219 }
220
221 private String getPluginDescriptorLocation()
222 {
223 return "META-INF/maven/plugin.xml";
224 }
225
226 private PluginDescriptor parsePluginDescriptor( InputStream is, Plugin plugin, String descriptorLocation )
227 throws PluginDescriptorParsingException
228 {
229 try
230 {
231 Reader reader = ReaderFactory.newXmlReader( is );
232
233 PluginDescriptor pluginDescriptor = builder.build( reader, descriptorLocation );
234
235 return pluginDescriptor;
236 }
237 catch ( IOException e )
238 {
239 throw new PluginDescriptorParsingException( plugin, descriptorLocation, e );
240 }
241 catch ( PlexusConfigurationException e )
242 {
243 throw new PluginDescriptorParsingException( plugin, descriptorLocation, e );
244 }
245 }
246
247 public MojoDescriptor getMojoDescriptor( Plugin plugin, String goal, List<RemoteRepository> repositories,
248 RepositorySystemSession session )
249 throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
250 InvalidPluginDescriptorException
251 {
252 PluginDescriptor pluginDescriptor = getPluginDescriptor( plugin, repositories, session );
253
254 MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( goal );
255
256 if ( mojoDescriptor == null )
257 {
258 throw new MojoNotFoundException( goal, pluginDescriptor );
259 }
260
261 return mojoDescriptor;
262 }
263
264 public synchronized void setupPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session,
265 ClassLoader parent, List<String> imports, DependencyFilter filter )
266 throws PluginResolutionException, PluginContainerException
267 {
268 Plugin plugin = pluginDescriptor.getPlugin();
269
270 MavenProject project = session.getCurrentProject();
271
272 PluginRealmCache.Key cacheKey =
273 pluginRealmCache.createKey( plugin, parent, imports, filter, project.getRemotePluginRepositories(),
274 session.getRepositorySession() );
275
276 PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get( cacheKey );
277
278 if ( cacheRecord != null )
279 {
280 pluginDescriptor.setClassRealm( cacheRecord.realm );
281 pluginDescriptor.setArtifacts( new ArrayList<Artifact>( cacheRecord.artifacts ) );
282 }
283 else
284 {
285 createPluginRealm( pluginDescriptor, session, parent, imports, filter );
286
287 cacheRecord =
288 pluginRealmCache.put( cacheKey, pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts() );
289 }
290
291 pluginRealmCache.register( project, cacheRecord );
292 }
293
294 private void createPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session, ClassLoader parent,
295 List<String> imports, DependencyFilter filter )
296 throws PluginResolutionException, PluginContainerException
297 {
298 Plugin plugin = pluginDescriptor.getPlugin();
299
300 if ( plugin == null )
301 {
302 throw new IllegalArgumentException( "incomplete plugin descriptor, plugin missing" );
303 }
304
305 Artifact pluginArtifact = pluginDescriptor.getPluginArtifact();
306
307 if ( pluginArtifact == null )
308 {
309 throw new IllegalArgumentException( "incomplete plugin descriptor, plugin artifact missing" );
310 }
311
312 MavenProject project = session.getCurrentProject();
313
314 DependencyFilter dependencyFilter = project.getExtensionDependencyFilter();
315 dependencyFilter = AndDependencyFilter.newInstance( dependencyFilter, filter );
316
317 DependencyNode root =
318 pluginDependenciesResolver.resolve( plugin, RepositoryUtils.toArtifact( pluginArtifact ), dependencyFilter,
319 project.getRemotePluginRepositories(), session.getRepositorySession() );
320
321 PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
322 root.accept( nlg );
323
324 List<Artifact> exposedPluginArtifacts = new ArrayList<Artifact>( nlg.getNodes().size() );
325 RepositoryUtils.toArtifacts( exposedPluginArtifacts, Collections.singleton( root ),
326 Collections.<String> emptyList(), null );
327 for ( Iterator<Artifact> it = exposedPluginArtifacts.iterator(); it.hasNext(); )
328 {
329 Artifact artifact = it.next();
330 if ( artifact.getFile() == null )
331 {
332 it.remove();
333 }
334 }
335
336 List<org.sonatype.aether.artifact.Artifact> pluginArtifacts = nlg.getArtifacts( true );
337
338 Map<String, ClassLoader> foreignImports = calcImports( project, parent, imports );
339
340 ClassRealm pluginRealm =
341 classRealmManager.createPluginRealm( plugin, parent, null, foreignImports, pluginArtifacts );
342
343 pluginDescriptor.setClassRealm( pluginRealm );
344 pluginDescriptor.setArtifacts( exposedPluginArtifacts );
345
346 try
347 {
348 for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() )
349 {
350 componentDescriptor.setRealm( pluginRealm );
351 container.addComponentDescriptor( componentDescriptor );
352 }
353
354 container.discoverComponents( pluginRealm );
355 }
356 catch ( PlexusConfigurationException e )
357 {
358 throw new PluginContainerException( plugin, pluginRealm, "Error in component graph of plugin "
359 + plugin.getId() + ": " + e.getMessage(), e );
360 }
361 catch ( CycleDetectedInComponentGraphException e )
362 {
363 throw new PluginContainerException( plugin, pluginRealm, "Error in component graph of plugin "
364 + plugin.getId() + ": " + e.getMessage(), e );
365 }
366 }
367
368 private Map<String, ClassLoader> calcImports( MavenProject project, ClassLoader parent, List<String> imports )
369 {
370 Map<String, ClassLoader> foreignImports = new HashMap<String, ClassLoader>();
371
372 ClassLoader projectRealm = project.getClassRealm();
373 if ( projectRealm != null )
374 {
375 foreignImports.put( "", projectRealm );
376 }
377 else
378 {
379 foreignImports.put( "", classRealmManager.getMavenApiRealm() );
380 }
381
382 if ( parent != null && imports != null )
383 {
384 for ( String parentImport : imports )
385 {
386 foreignImports.put( parentImport, parent );
387 }
388 }
389
390 return foreignImports;
391 }
392
393 public <T> T getConfiguredMojo( Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution )
394 throws PluginConfigurationException, PluginContainerException
395 {
396 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
397
398 PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
399
400 ClassRealm pluginRealm = pluginDescriptor.getClassRealm();
401
402 if ( logger.isDebugEnabled() )
403 {
404 logger.debug( "Configuring mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm );
405 }
406
407
408
409
410 ClassRealm oldLookupRealm = container.setLookupRealm( pluginRealm );
411 container.setLookupRealm( pluginRealm );
412
413 ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
414 Thread.currentThread().setContextClassLoader( pluginRealm );
415
416 try
417 {
418 T mojo;
419
420 try
421 {
422 mojo = container.lookup( mojoInterface, mojoDescriptor.getRoleHint() );
423 }
424 catch ( ComponentLookupException e )
425 {
426 Throwable cause = e.getCause();
427 while ( cause != null && !( cause instanceof LinkageError )
428 && !( cause instanceof ClassNotFoundException ) )
429 {
430 cause = cause.getCause();
431 }
432
433 if ( ( cause instanceof NoClassDefFoundError ) || ( cause instanceof ClassNotFoundException ) )
434 {
435 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
436 PrintStream ps = new PrintStream( os );
437 ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
438 + pluginDescriptor.getId() + "'. A required class is missing: " + cause.getMessage() );
439 pluginRealm.display( ps );
440
441 throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause );
442 }
443 else if ( cause instanceof LinkageError )
444 {
445 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
446 PrintStream ps = new PrintStream( os );
447 ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
448 + pluginDescriptor.getId() + "' due to an API incompatibility: " + e.getClass().getName()
449 + ": " + cause.getMessage() );
450 pluginRealm.display( ps );
451
452 throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause );
453 }
454
455 throw new PluginContainerException( mojoDescriptor, pluginRealm, "Unable to load the mojo '"
456 + mojoDescriptor.getGoal() + "' (or one of its required components) from the plugin '"
457 + pluginDescriptor.getId() + "'", e );
458 }
459
460 if ( mojo instanceof ContextEnabled )
461 {
462 MavenProject project = session.getCurrentProject();
463
464 Map<String, Object> pluginContext = session.getPluginContext( pluginDescriptor, project );
465
466 if ( pluginContext != null )
467 {
468 pluginContext.put( "project", project );
469
470 pluginContext.put( "pluginDescriptor", pluginDescriptor );
471
472 ( (ContextEnabled) mojo ).setPluginContext( pluginContext );
473 }
474 }
475
476 if ( mojo instanceof Mojo )
477 {
478 ( (Mojo) mojo ).setLog( new DefaultLog( logger ) );
479 }
480
481 Xpp3Dom dom = mojoExecution.getConfiguration();
482
483 PlexusConfiguration pomConfiguration;
484
485 if ( dom == null )
486 {
487 pomConfiguration = new XmlPlexusConfiguration( "configuration" );
488 }
489 else
490 {
491 pomConfiguration = new XmlPlexusConfiguration( dom );
492 }
493
494 ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator( session, mojoExecution );
495
496 populatePluginFields( mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator );
497
498 return mojo;
499 }
500 finally
501 {
502 Thread.currentThread().setContextClassLoader( oldClassLoader );
503 container.setLookupRealm( oldLookupRealm );
504 }
505 }
506
507 private void populatePluginFields( Object mojo, MojoDescriptor mojoDescriptor, ClassRealm pluginRealm,
508 PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator )
509 throws PluginConfigurationException
510 {
511 ComponentConfigurator configurator = null;
512
513 String configuratorId = mojoDescriptor.getComponentConfigurator();
514
515 if ( StringUtils.isEmpty( configuratorId ) )
516 {
517 configuratorId = "basic";
518 }
519
520 try
521 {
522
523
524 configurator = container.lookup( ComponentConfigurator.class, configuratorId );
525
526 ConfigurationListener listener = new DebugConfigurationListener( logger );
527
528 ValidatingConfigurationListener validator =
529 new ValidatingConfigurationListener( mojo, mojoDescriptor, listener );
530
531 logger.debug( "Configuring mojo '" + mojoDescriptor.getId() + "' with " + configuratorId
532 + " configurator -->" );
533
534 configurator.configureComponent( mojo, configuration, expressionEvaluator, pluginRealm, validator );
535
536 logger.debug( "-- end configuration --" );
537
538 Collection<Parameter> missingParameters = validator.getMissingParameters();
539 if ( !missingParameters.isEmpty() )
540 {
541 if ( "basic".equals( configuratorId ) )
542 {
543 throw new PluginParameterException( mojoDescriptor, new ArrayList<Parameter>( missingParameters ) );
544 }
545 else
546 {
547
548
549
550
551 validateParameters( mojoDescriptor, configuration, expressionEvaluator );
552 }
553 }
554 }
555 catch ( ComponentConfigurationException e )
556 {
557 String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
558 if ( e.getFailedConfiguration() != null )
559 {
560 message += " for parameter " + e.getFailedConfiguration().getName();
561 }
562 message += ": " + e.getMessage();
563
564 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), message, e );
565 }
566 catch ( ComponentLookupException e )
567 {
568 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(),
569 "Unable to retrieve component configurator " + configuratorId
570 + " for configuration of mojo " + mojoDescriptor.getId(), e );
571 }
572 catch ( NoClassDefFoundError e )
573 {
574 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
575 PrintStream ps = new PrintStream( os );
576 ps.println( "A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
577 + e.getMessage() );
578 pluginRealm.display( ps );
579
580 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e );
581 }
582 catch ( LinkageError e )
583 {
584 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
585 PrintStream ps = new PrintStream( os );
586 ps.println( "An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId()
587 + ": " + e.getClass().getName() + ": " + e.getMessage() );
588 pluginRealm.display( ps );
589
590 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e );
591 }
592 finally
593 {
594 if ( configurator != null )
595 {
596 try
597 {
598 container.release( configurator );
599 }
600 catch ( ComponentLifecycleException e )
601 {
602 logger.debug( "Failed to release mojo configurator - ignoring." );
603 }
604 }
605 }
606 }
607
608 private void validateParameters( MojoDescriptor mojoDescriptor, PlexusConfiguration configuration,
609 ExpressionEvaluator expressionEvaluator )
610 throws ComponentConfigurationException, PluginParameterException
611 {
612 if ( mojoDescriptor.getParameters() == null )
613 {
614 return;
615 }
616
617 List<Parameter> invalidParameters = new ArrayList<Parameter>();
618
619 for ( Parameter parameter : mojoDescriptor.getParameters() )
620 {
621 if ( !parameter.isRequired() )
622 {
623 continue;
624 }
625
626 Object value = null;
627
628 PlexusConfiguration config = configuration.getChild( parameter.getName(), false );
629 if ( config != null )
630 {
631 String expression = config.getValue( null );
632
633 try
634 {
635 value = expressionEvaluator.evaluate( expression );
636
637 if ( value == null )
638 {
639 value = config.getAttribute( "default-value", null );
640 }
641 }
642 catch ( ExpressionEvaluationException e )
643 {
644 String msg =
645 "Error evaluating the expression '" + expression + "' for configuration value '"
646 + configuration.getName() + "'";
647 throw new ComponentConfigurationException( configuration, msg, e );
648 }
649 }
650
651 if ( value == null && ( config == null || config.getChildCount() <= 0 ) )
652 {
653 invalidParameters.add( parameter );
654 }
655 }
656
657 if ( !invalidParameters.isEmpty() )
658 {
659 throw new PluginParameterException( mojoDescriptor, invalidParameters );
660 }
661 }
662
663 public void releaseMojo( Object mojo, MojoExecution mojoExecution )
664 {
665 if ( mojo != null )
666 {
667 try
668 {
669 container.release( mojo );
670 }
671 catch ( ComponentLifecycleException e )
672 {
673 String goalExecId = mojoExecution.getGoal();
674
675 if ( mojoExecution.getExecutionId() != null )
676 {
677 goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}";
678 }
679
680 logger.debug( "Error releasing mojo for " + goalExecId, e );
681 }
682 }
683 }
684
685 }