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