001package org.apache.maven.plugin.internal; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.BufferedInputStream; 023import java.io.ByteArrayOutputStream; 024import java.io.File; 025import java.io.FileInputStream; 026import java.io.IOException; 027import java.io.InputStream; 028import java.io.PrintStream; 029import java.io.Reader; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.Collections; 033import java.util.HashMap; 034import java.util.Iterator; 035import java.util.List; 036import java.util.Map; 037import java.util.jar.JarFile; 038import java.util.zip.ZipEntry; 039 040import org.apache.maven.RepositoryUtils; 041import org.apache.maven.artifact.Artifact; 042import org.apache.maven.classrealm.ClassRealmManager; 043import org.apache.maven.execution.MavenSession; 044import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule; 045import org.apache.maven.model.Plugin; 046import org.apache.maven.monitor.logging.DefaultLog; 047import org.apache.maven.plugin.ContextEnabled; 048import org.apache.maven.plugin.DebugConfigurationListener; 049import org.apache.maven.plugin.ExtensionRealmCache; 050import org.apache.maven.plugin.InvalidPluginDescriptorException; 051import org.apache.maven.plugin.MavenPluginManager; 052import org.apache.maven.plugin.MavenPluginValidator; 053import org.apache.maven.plugin.Mojo; 054import org.apache.maven.plugin.MojoExecution; 055import org.apache.maven.plugin.MojoNotFoundException; 056import org.apache.maven.plugin.PluginArtifactsCache; 057import org.apache.maven.plugin.PluginConfigurationException; 058import org.apache.maven.plugin.PluginContainerException; 059import org.apache.maven.plugin.PluginDescriptorCache; 060import org.apache.maven.plugin.PluginDescriptorParsingException; 061import org.apache.maven.plugin.PluginIncompatibleException; 062import org.apache.maven.plugin.PluginManagerException; 063import org.apache.maven.plugin.PluginParameterException; 064import org.apache.maven.plugin.PluginParameterExpressionEvaluator; 065import org.apache.maven.plugin.PluginRealmCache; 066import org.apache.maven.plugin.PluginResolutionException; 067import org.apache.maven.plugin.descriptor.MojoDescriptor; 068import org.apache.maven.plugin.descriptor.Parameter; 069import org.apache.maven.plugin.descriptor.PluginDescriptor; 070import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder; 071import org.apache.maven.plugin.version.DefaultPluginVersionRequest; 072import org.apache.maven.plugin.version.PluginVersionRequest; 073import org.apache.maven.plugin.version.PluginVersionResolutionException; 074import org.apache.maven.plugin.version.PluginVersionResolver; 075import org.apache.maven.project.ExtensionDescriptor; 076import org.apache.maven.project.ExtensionDescriptorBuilder; 077import org.apache.maven.project.MavenProject; 078import org.apache.maven.rtinfo.RuntimeInformation; 079import org.apache.maven.session.scope.internal.SessionScopeModule; 080import org.codehaus.plexus.DefaultPlexusContainer; 081import org.codehaus.plexus.PlexusContainer; 082import org.codehaus.plexus.classworlds.realm.ClassRealm; 083import org.codehaus.plexus.component.annotations.Component; 084import org.codehaus.plexus.component.annotations.Requirement; 085import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException; 086import org.codehaus.plexus.component.configurator.ComponentConfigurationException; 087import org.codehaus.plexus.component.configurator.ComponentConfigurator; 088import org.codehaus.plexus.component.configurator.ConfigurationListener; 089import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException; 090import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator; 091import org.codehaus.plexus.component.repository.ComponentDescriptor; 092import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException; 093import org.codehaus.plexus.component.repository.exception.ComponentLookupException; 094import org.codehaus.plexus.configuration.PlexusConfiguration; 095import org.codehaus.plexus.configuration.PlexusConfigurationException; 096import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration; 097import org.codehaus.plexus.logging.Logger; 098import org.codehaus.plexus.logging.LoggerManager; 099import org.codehaus.plexus.util.IOUtil; 100import org.codehaus.plexus.util.ReaderFactory; 101import org.codehaus.plexus.util.StringUtils; 102import org.codehaus.plexus.util.xml.Xpp3Dom; 103import org.eclipse.aether.RepositorySystemSession; 104import org.eclipse.aether.graph.DependencyFilter; 105import org.eclipse.aether.graph.DependencyNode; 106import org.eclipse.aether.repository.RemoteRepository; 107import org.eclipse.aether.util.filter.AndDependencyFilter; 108import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; 109 110/** 111 * Provides basic services to manage Maven plugins and their mojos. This component is kept general in its design such 112 * that the plugins/mojos can be used in arbitrary contexts. In particular, the mojos can be used for ordinary build 113 * plugins as well as special purpose plugins like reports. 114 * 115 * @since 3.0 116 * @author Benjamin Bentmann 117 */ 118@Component( role = MavenPluginManager.class ) 119public class DefaultMavenPluginManager 120 implements MavenPluginManager 121{ 122 123 /** 124 * PluginId=>ExtensionRealmCache.CacheRecord map MavenProject context value key. The map is used to ensure the same 125 * class realm is used to load build extensions and load mojos for extensions=true plugins. 126 * 127 * @noreference this is part of internal implementation and may be changed or removed without notice 128 * @since 3.3.0 129 */ 130 public static final String KEY_EXTENSIONS_REALMS = DefaultMavenPluginManager.class.getName() + "/extensionsRealms"; 131 132 @Requirement 133 private Logger logger; 134 135 @Requirement 136 private LoggerManager loggerManager; 137 138 @Requirement 139 private PlexusContainer container; 140 141 @Requirement 142 private ClassRealmManager classRealmManager; 143 144 @Requirement 145 private PluginDescriptorCache pluginDescriptorCache; 146 147 @Requirement 148 private PluginRealmCache pluginRealmCache; 149 150 @Requirement 151 private PluginDependenciesResolver pluginDependenciesResolver; 152 153 @Requirement 154 private RuntimeInformation runtimeInformation; 155 156 @Requirement 157 private ExtensionRealmCache extensionRealmCache; 158 159 @Requirement 160 private PluginVersionResolver pluginVersionResolver; 161 162 @Requirement 163 private PluginArtifactsCache pluginArtifactsCache; 164 165 private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder(); 166 167 private PluginDescriptorBuilder builder = new PluginDescriptorBuilder(); 168 169 public synchronized PluginDescriptor getPluginDescriptor( Plugin plugin, List<RemoteRepository> repositories, 170 RepositorySystemSession session ) 171 throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException 172 { 173 PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey( plugin, repositories, session ); 174 175 PluginDescriptor pluginDescriptor = pluginDescriptorCache.get( cacheKey ); 176 177 if ( pluginDescriptor == null ) 178 { 179 org.eclipse.aether.artifact.Artifact artifact = 180 pluginDependenciesResolver.resolve( plugin, repositories, session ); 181 182 Artifact pluginArtifact = RepositoryUtils.toArtifact( artifact ); 183 184 pluginDescriptor = extractPluginDescriptor( pluginArtifact, plugin ); 185 186 pluginDescriptor.setRequiredMavenVersion( artifact.getProperty( "requiredMavenVersion", null ) ); 187 188 pluginDescriptorCache.put( cacheKey, pluginDescriptor ); 189 } 190 191 pluginDescriptor.setPlugin( plugin ); 192 193 return pluginDescriptor; 194 } 195 196 private PluginDescriptor extractPluginDescriptor( Artifact pluginArtifact, Plugin plugin ) 197 throws PluginDescriptorParsingException, InvalidPluginDescriptorException 198 { 199 PluginDescriptor pluginDescriptor = null; 200 201 File pluginFile = pluginArtifact.getFile(); 202 203 try 204 { 205 if ( pluginFile.isFile() ) 206 { 207 JarFile pluginJar = new JarFile( pluginFile, false ); 208 try 209 { 210 ZipEntry pluginDescriptorEntry = pluginJar.getEntry( getPluginDescriptorLocation() ); 211 212 if ( pluginDescriptorEntry != null ) 213 { 214 InputStream is = pluginJar.getInputStream( pluginDescriptorEntry ); 215 216 pluginDescriptor = parsePluginDescriptor( is, plugin, pluginFile.getAbsolutePath() ); 217 } 218 } 219 finally 220 { 221 pluginJar.close(); 222 } 223 } 224 else 225 { 226 File pluginXml = new File( pluginFile, getPluginDescriptorLocation() ); 227 228 if ( pluginXml.isFile() ) 229 { 230 InputStream is = new BufferedInputStream( new FileInputStream( pluginXml ) ); 231 try 232 { 233 pluginDescriptor = parsePluginDescriptor( is, plugin, pluginXml.getAbsolutePath() ); 234 } 235 finally 236 { 237 IOUtil.close( is ); 238 } 239 } 240 } 241 242 if ( pluginDescriptor == null ) 243 { 244 throw new IOException( "No plugin descriptor found at " + getPluginDescriptorLocation() ); 245 } 246 } 247 catch ( IOException e ) 248 { 249 throw new PluginDescriptorParsingException( plugin, pluginFile.getAbsolutePath(), e ); 250 } 251 252 MavenPluginValidator validator = new MavenPluginValidator( pluginArtifact ); 253 254 validator.validate( pluginDescriptor ); 255 256 if ( validator.hasErrors() ) 257 { 258 throw new InvalidPluginDescriptorException( "Invalid plugin descriptor for " + plugin.getId() + " (" 259 + pluginFile + ")", validator.getErrors() ); 260 } 261 262 pluginDescriptor.setPluginArtifact( pluginArtifact ); 263 264 return pluginDescriptor; 265 } 266 267 private String getPluginDescriptorLocation() 268 { 269 return "META-INF/maven/plugin.xml"; 270 } 271 272 private PluginDescriptor parsePluginDescriptor( InputStream is, Plugin plugin, String descriptorLocation ) 273 throws PluginDescriptorParsingException 274 { 275 try 276 { 277 Reader reader = ReaderFactory.newXmlReader( is ); 278 279 PluginDescriptor pluginDescriptor = builder.build( reader, descriptorLocation ); 280 281 return pluginDescriptor; 282 } 283 catch ( IOException e ) 284 { 285 throw new PluginDescriptorParsingException( plugin, descriptorLocation, e ); 286 } 287 catch ( PlexusConfigurationException e ) 288 { 289 throw new PluginDescriptorParsingException( plugin, descriptorLocation, e ); 290 } 291 } 292 293 public MojoDescriptor getMojoDescriptor( Plugin plugin, String goal, List<RemoteRepository> repositories, 294 RepositorySystemSession session ) 295 throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException, 296 InvalidPluginDescriptorException 297 { 298 PluginDescriptor pluginDescriptor = getPluginDescriptor( plugin, repositories, session ); 299 300 MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( goal ); 301 302 if ( mojoDescriptor == null ) 303 { 304 throw new MojoNotFoundException( goal, pluginDescriptor ); 305 } 306 307 return mojoDescriptor; 308 } 309 310 public void checkRequiredMavenVersion( PluginDescriptor pluginDescriptor ) 311 throws PluginIncompatibleException 312 { 313 String requiredMavenVersion = pluginDescriptor.getRequiredMavenVersion(); 314 if ( StringUtils.isNotBlank( requiredMavenVersion ) ) 315 { 316 try 317 { 318 if ( !runtimeInformation.isMavenVersion( requiredMavenVersion ) ) 319 { 320 throw new PluginIncompatibleException( pluginDescriptor.getPlugin(), "The plugin " 321 + pluginDescriptor.getId() + " requires Maven version " + requiredMavenVersion ); 322 } 323 } 324 catch ( RuntimeException e ) 325 { 326 logger.warn( "Could not verify plugin's Maven prerequisite: " + e.getMessage() ); 327 } 328 } 329 } 330 331 public synchronized void setupPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session, 332 ClassLoader parent, List<String> imports, DependencyFilter filter ) 333 throws PluginResolutionException, PluginContainerException 334 { 335 Plugin plugin = pluginDescriptor.getPlugin(); 336 337 MavenProject project = session.getCurrentProject(); 338 339 Map<String, ClassLoader> foreignImports = calcImports( project, parent, imports ); 340 341 PluginRealmCache.Key cacheKey = 342 pluginRealmCache.createKey( plugin, parent, foreignImports, filter, project.getRemotePluginRepositories(), 343 session.getRepositorySession() ); 344 345 PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get( cacheKey ); 346 347 if ( cacheRecord != null ) 348 { 349 pluginDescriptor.setClassRealm( cacheRecord.realm ); 350 pluginDescriptor.setArtifacts( new ArrayList<Artifact>( cacheRecord.artifacts ) ); 351 for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() ) 352 { 353 componentDescriptor.setRealm( cacheRecord.realm ); 354 } 355 } 356 else 357 { 358 createPluginRealm( pluginDescriptor, session, parent, foreignImports, filter ); 359 360 cacheRecord = 361 pluginRealmCache.put( cacheKey, pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts() ); 362 } 363 364 pluginRealmCache.register( project, cacheKey, cacheRecord ); 365 } 366 367 private void createPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session, ClassLoader parent, 368 Map<String, ClassLoader> foreignImports, DependencyFilter filter ) 369 throws PluginResolutionException, PluginContainerException 370 { 371 Plugin plugin = pluginDescriptor.getPlugin(); 372 373 if ( plugin == null ) 374 { 375 throw new IllegalArgumentException( "incomplete plugin descriptor, plugin missing" ); 376 } 377 378 Artifact pluginArtifact = pluginDescriptor.getPluginArtifact(); 379 380 if ( pluginArtifact == null ) 381 { 382 throw new IllegalArgumentException( "incomplete plugin descriptor, plugin artifact missing" ); 383 } 384 385 MavenProject project = session.getCurrentProject(); 386 387 final ClassRealm pluginRealm; 388 final List<Artifact> pluginArtifacts; 389 390 RepositorySystemSession repositorySession = session.getRepositorySession(); 391 if ( plugin.isExtensions() ) 392 { 393 ExtensionRealmCache.CacheRecord extensionRecord; 394 try 395 { 396 extensionRecord = setupExtensionsRealm( project, plugin, repositorySession ); 397 } 398 catch ( PluginManagerException e ) 399 { 400 // extensions realm is expected to be fully setup at this point 401 // any exception means a problem in maven code, not a user error 402 throw new IllegalStateException( e ); 403 } 404 405 pluginRealm = extensionRecord.realm; 406 pluginArtifacts = extensionRecord.artifacts; 407 408 for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() ) 409 { 410 componentDescriptor.setRealm( pluginRealm ); 411 } 412 } 413 else 414 { 415 DependencyFilter dependencyFilter = project.getExtensionDependencyFilter(); 416 dependencyFilter = AndDependencyFilter.newInstance( dependencyFilter, filter ); 417 418 DependencyNode root = 419 pluginDependenciesResolver.resolve( plugin, RepositoryUtils.toArtifact( pluginArtifact ), 420 dependencyFilter, project.getRemotePluginRepositories(), 421 repositorySession ); 422 423 PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); 424 root.accept( nlg ); 425 426 pluginArtifacts = toMavenArtifacts( root, nlg ); 427 428 pluginRealm = 429 classRealmManager.createPluginRealm( plugin, parent, null, foreignImports, 430 toAetherArtifacts( pluginArtifacts ) ); 431 432 discoverPluginComponents( pluginRealm, plugin, pluginDescriptor ); 433 } 434 435 pluginDescriptor.setClassRealm( pluginRealm ); 436 pluginDescriptor.setArtifacts( pluginArtifacts ); 437 } 438 439 private void discoverPluginComponents( final ClassRealm pluginRealm, Plugin plugin, 440 PluginDescriptor pluginDescriptor ) 441 throws PluginContainerException 442 { 443 try 444 { 445 if ( pluginDescriptor != null ) 446 { 447 for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() ) 448 { 449 componentDescriptor.setRealm( pluginRealm ); 450 container.addComponentDescriptor( componentDescriptor ); 451 } 452 } 453 454 ( (DefaultPlexusContainer) container ).discoverComponents( pluginRealm, 455 new SessionScopeModule( container ), 456 new MojoExecutionScopeModule( container ) ); 457 } 458 catch ( ComponentLookupException e ) 459 { 460 throw new PluginContainerException( plugin, pluginRealm, "Error in component graph of plugin " 461 + plugin.getId() + ": " + e.getMessage(), e ); 462 } 463 catch ( CycleDetectedInComponentGraphException e ) 464 { 465 throw new PluginContainerException( plugin, pluginRealm, "Error in component graph of plugin " 466 + plugin.getId() + ": " + e.getMessage(), e ); 467 } 468 } 469 470 private List<org.eclipse.aether.artifact.Artifact> toAetherArtifacts( final List<Artifact> pluginArtifacts ) 471 { 472 return new ArrayList<org.eclipse.aether.artifact.Artifact>( RepositoryUtils.toArtifacts( pluginArtifacts ) ); 473 } 474 475 private List<Artifact> toMavenArtifacts( DependencyNode root, PreorderNodeListGenerator nlg ) 476 { 477 List<Artifact> artifacts = new ArrayList<Artifact>( nlg.getNodes().size() ); 478 RepositoryUtils.toArtifacts( artifacts, Collections.singleton( root ), Collections.<String>emptyList(), null ); 479 for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); ) 480 { 481 Artifact artifact = it.next(); 482 if ( artifact.getFile() == null ) 483 { 484 it.remove(); 485 } 486 } 487 return artifacts; 488 } 489 490 private Map<String, ClassLoader> calcImports( MavenProject project, ClassLoader parent, List<String> imports ) 491 { 492 Map<String, ClassLoader> foreignImports = new HashMap<String, ClassLoader>(); 493 494 ClassLoader projectRealm = project.getClassRealm(); 495 if ( projectRealm != null ) 496 { 497 foreignImports.put( "", projectRealm ); 498 } 499 else 500 { 501 foreignImports.put( "", classRealmManager.getMavenApiRealm() ); 502 } 503 504 if ( parent != null && imports != null ) 505 { 506 for ( String parentImport : imports ) 507 { 508 foreignImports.put( parentImport, parent ); 509 } 510 } 511 512 return foreignImports; 513 } 514 515 public <T> T getConfiguredMojo( Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution ) 516 throws PluginConfigurationException, PluginContainerException 517 { 518 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); 519 520 PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor(); 521 522 ClassRealm pluginRealm = pluginDescriptor.getClassRealm(); 523 524 if ( logger.isDebugEnabled() ) 525 { 526 logger.debug( "Configuring mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm ); 527 } 528 529 // We are forcing the use of the plugin realm for all lookups that might occur during 530 // the lifecycle that is part of the lookup. Here we are specifically trying to keep 531 // lookups that occur in contextualize calls in line with the right realm. 532 ClassRealm oldLookupRealm = container.setLookupRealm( pluginRealm ); 533 534 ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); 535 Thread.currentThread().setContextClassLoader( pluginRealm ); 536 537 try 538 { 539 T mojo; 540 541 try 542 { 543 mojo = container.lookup( mojoInterface, mojoDescriptor.getRoleHint() ); 544 } 545 catch ( ComponentLookupException e ) 546 { 547 Throwable cause = e.getCause(); 548 while ( cause != null && !( cause instanceof LinkageError ) 549 && !( cause instanceof ClassNotFoundException ) ) 550 { 551 cause = cause.getCause(); 552 } 553 554 if ( ( cause instanceof NoClassDefFoundError ) || ( cause instanceof ClassNotFoundException ) ) 555 { 556 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 ); 557 PrintStream ps = new PrintStream( os ); 558 ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '" 559 + pluginDescriptor.getId() + "'. A required class is missing: " + cause.getMessage() ); 560 pluginRealm.display( ps ); 561 562 throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause ); 563 } 564 else if ( cause instanceof LinkageError ) 565 { 566 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 ); 567 PrintStream ps = new PrintStream( os ); 568 ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '" 569 + pluginDescriptor.getId() + "' due to an API incompatibility: " + e.getClass().getName() 570 + ": " + cause.getMessage() ); 571 pluginRealm.display( ps ); 572 573 throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause ); 574 } 575 576 throw new PluginContainerException( mojoDescriptor, pluginRealm, "Unable to load the mojo '" 577 + mojoDescriptor.getGoal() + "' (or one of its required components) from the plugin '" 578 + pluginDescriptor.getId() + "'", e ); 579 } 580 581 if ( mojo instanceof ContextEnabled ) 582 { 583 MavenProject project = session.getCurrentProject(); 584 585 Map<String, Object> pluginContext = session.getPluginContext( pluginDescriptor, project ); 586 587 if ( pluginContext != null ) 588 { 589 pluginContext.put( "project", project ); 590 591 pluginContext.put( "pluginDescriptor", pluginDescriptor ); 592 593 ( (ContextEnabled) mojo ).setPluginContext( pluginContext ); 594 } 595 } 596 597 if ( mojo instanceof Mojo ) 598 { 599 Logger mojoLogger = loggerManager.getLoggerForComponent( mojoDescriptor.getImplementation() ); 600 ( (Mojo) mojo ).setLog( new DefaultLog( mojoLogger ) ); 601 } 602 603 Xpp3Dom dom = mojoExecution.getConfiguration(); 604 605 PlexusConfiguration pomConfiguration; 606 607 if ( dom == null ) 608 { 609 pomConfiguration = new XmlPlexusConfiguration( "configuration" ); 610 } 611 else 612 { 613 pomConfiguration = new XmlPlexusConfiguration( dom ); 614 } 615 616 ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator( session, mojoExecution ); 617 618 populatePluginFields( mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator ); 619 620 return mojo; 621 } 622 finally 623 { 624 Thread.currentThread().setContextClassLoader( oldClassLoader ); 625 container.setLookupRealm( oldLookupRealm ); 626 } 627 } 628 629 private void populatePluginFields( Object mojo, MojoDescriptor mojoDescriptor, ClassRealm pluginRealm, 630 PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator ) 631 throws PluginConfigurationException 632 { 633 ComponentConfigurator configurator = null; 634 635 String configuratorId = mojoDescriptor.getComponentConfigurator(); 636 637 if ( StringUtils.isEmpty( configuratorId ) ) 638 { 639 configuratorId = "basic"; 640 } 641 642 try 643 { 644 // TODO: could the configuration be passed to lookup and the configurator known to plexus via the descriptor 645 // so that this method could entirely be handled by a plexus lookup? 646 configurator = container.lookup( ComponentConfigurator.class, configuratorId ); 647 648 ConfigurationListener listener = new DebugConfigurationListener( logger ); 649 650 ValidatingConfigurationListener validator = 651 new ValidatingConfigurationListener( mojo, mojoDescriptor, listener ); 652 653 logger.debug( "Configuring mojo '" + mojoDescriptor.getId() + "' with " + configuratorId 654 + " configurator -->" ); 655 656 configurator.configureComponent( mojo, configuration, expressionEvaluator, pluginRealm, validator ); 657 658 logger.debug( "-- end configuration --" ); 659 660 Collection<Parameter> missingParameters = validator.getMissingParameters(); 661 if ( !missingParameters.isEmpty() ) 662 { 663 if ( "basic".equals( configuratorId ) ) 664 { 665 throw new PluginParameterException( mojoDescriptor, new ArrayList<Parameter>( missingParameters ) ); 666 } 667 else 668 { 669 /* 670 * NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the 671 * hard way. 672 */ 673 validateParameters( mojoDescriptor, configuration, expressionEvaluator ); 674 } 675 } 676 } 677 catch ( ComponentConfigurationException e ) 678 { 679 String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId(); 680 if ( e.getFailedConfiguration() != null ) 681 { 682 message += " for parameter " + e.getFailedConfiguration().getName(); 683 } 684 message += ": " + e.getMessage(); 685 686 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), message, e ); 687 } 688 catch ( ComponentLookupException e ) 689 { 690 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), 691 "Unable to retrieve component configurator " + configuratorId 692 + " for configuration of mojo " + mojoDescriptor.getId(), e ); 693 } 694 catch ( NoClassDefFoundError e ) 695 { 696 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 ); 697 PrintStream ps = new PrintStream( os ); 698 ps.println( "A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": " 699 + e.getMessage() ); 700 pluginRealm.display( ps ); 701 702 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e ); 703 } 704 catch ( LinkageError e ) 705 { 706 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 ); 707 PrintStream ps = new PrintStream( os ); 708 ps.println( "An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId() 709 + ": " + e.getClass().getName() + ": " + e.getMessage() ); 710 pluginRealm.display( ps ); 711 712 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e ); 713 } 714 finally 715 { 716 if ( configurator != null ) 717 { 718 try 719 { 720 container.release( configurator ); 721 } 722 catch ( ComponentLifecycleException e ) 723 { 724 logger.debug( "Failed to release mojo configurator - ignoring." ); 725 } 726 } 727 } 728 } 729 730 private void validateParameters( MojoDescriptor mojoDescriptor, PlexusConfiguration configuration, 731 ExpressionEvaluator expressionEvaluator ) 732 throws ComponentConfigurationException, PluginParameterException 733 { 734 if ( mojoDescriptor.getParameters() == null ) 735 { 736 return; 737 } 738 739 List<Parameter> invalidParameters = new ArrayList<Parameter>(); 740 741 for ( Parameter parameter : mojoDescriptor.getParameters() ) 742 { 743 if ( !parameter.isRequired() ) 744 { 745 continue; 746 } 747 748 Object value = null; 749 750 PlexusConfiguration config = configuration.getChild( parameter.getName(), false ); 751 if ( config != null ) 752 { 753 String expression = config.getValue( null ); 754 755 try 756 { 757 value = expressionEvaluator.evaluate( expression ); 758 759 if ( value == null ) 760 { 761 value = config.getAttribute( "default-value", null ); 762 } 763 } 764 catch ( ExpressionEvaluationException e ) 765 { 766 String msg = 767 "Error evaluating the expression '" + expression + "' for configuration value '" 768 + configuration.getName() + "'"; 769 throw new ComponentConfigurationException( configuration, msg, e ); 770 } 771 } 772 773 if ( value == null && ( config == null || config.getChildCount() <= 0 ) ) 774 { 775 invalidParameters.add( parameter ); 776 } 777 } 778 779 if ( !invalidParameters.isEmpty() ) 780 { 781 throw new PluginParameterException( mojoDescriptor, invalidParameters ); 782 } 783 } 784 785 public void releaseMojo( Object mojo, MojoExecution mojoExecution ) 786 { 787 if ( mojo != null ) 788 { 789 try 790 { 791 container.release( mojo ); 792 } 793 catch ( ComponentLifecycleException e ) 794 { 795 String goalExecId = mojoExecution.getGoal(); 796 797 if ( mojoExecution.getExecutionId() != null ) 798 { 799 goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}"; 800 } 801 802 logger.debug( "Error releasing mojo for " + goalExecId, e ); 803 } 804 } 805 } 806 807 public ExtensionRealmCache.CacheRecord setupExtensionsRealm( MavenProject project, Plugin plugin, 808 RepositorySystemSession session ) 809 throws PluginManagerException 810 { 811 @SuppressWarnings( "unchecked" ) 812 Map<String, ExtensionRealmCache.CacheRecord> pluginRealms = 813 (Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue( KEY_EXTENSIONS_REALMS ); 814 if ( pluginRealms == null ) 815 { 816 pluginRealms = new HashMap<String, ExtensionRealmCache.CacheRecord>(); 817 project.setContextValue( KEY_EXTENSIONS_REALMS, pluginRealms ); 818 } 819 820 final String pluginKey = plugin.getId(); 821 822 ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get( pluginKey ); 823 if ( extensionRecord != null ) 824 { 825 return extensionRecord; 826 } 827 828 final List<RemoteRepository> repositories = project.getRemotePluginRepositories(); 829 830 // resolve plugin version as necessary 831 if ( plugin.getVersion() == null ) 832 { 833 PluginVersionRequest versionRequest = new DefaultPluginVersionRequest( plugin, session, repositories ); 834 try 835 { 836 plugin.setVersion( pluginVersionResolver.resolve( versionRequest ).getVersion() ); 837 } 838 catch ( PluginVersionResolutionException e ) 839 { 840 throw new PluginManagerException( plugin, e.getMessage(), e ); 841 } 842 } 843 844 // resolve plugin artifacts 845 List<Artifact> artifacts; 846 PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey( plugin, null, repositories, session ); 847 PluginArtifactsCache.CacheRecord recordArtifacts; 848 try 849 { 850 recordArtifacts = pluginArtifactsCache.get( cacheKey ); 851 } 852 catch ( PluginResolutionException e ) 853 { 854 throw new PluginManagerException( plugin, e.getMessage(), e ); 855 } 856 if ( recordArtifacts != null ) 857 { 858 artifacts = recordArtifacts.artifacts; 859 } 860 else 861 { 862 try 863 { 864 artifacts = resolveExtensionArtifacts( plugin, repositories, session ); 865 recordArtifacts = pluginArtifactsCache.put( cacheKey, artifacts ); 866 } 867 catch ( PluginResolutionException e ) 868 { 869 pluginArtifactsCache.put( cacheKey, e ); 870 pluginArtifactsCache.register( project, cacheKey, recordArtifacts ); 871 throw new PluginManagerException( plugin, e.getMessage(), e ); 872 } 873 } 874 pluginArtifactsCache.register( project, cacheKey, recordArtifacts ); 875 876 // create and cache extensions realms 877 final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey( artifacts ); 878 extensionRecord = extensionRealmCache.get( extensionKey ); 879 if ( extensionRecord == null ) 880 { 881 ClassRealm extensionRealm = classRealmManager.createExtensionRealm( plugin, 882 toAetherArtifacts( artifacts ) ); 883 884 // TODO figure out how to use the same PluginDescriptor when running mojos 885 886 PluginDescriptor pluginDescriptor = null; 887 if ( plugin.isExtensions() && !artifacts.isEmpty() ) 888 { 889 // ignore plugin descriptor parsing errors at this point 890 // these errors will reported during calculation of project build execution plan 891 try 892 { 893 pluginDescriptor = extractPluginDescriptor( artifacts.get( 0 ), plugin ); 894 } 895 catch ( PluginDescriptorParsingException e ) 896 { 897 // ignore, see above 898 } 899 catch ( InvalidPluginDescriptorException e ) 900 { 901 // ignore, see above 902 } 903 } 904 905 discoverPluginComponents( extensionRealm, plugin, pluginDescriptor ); 906 907 ExtensionDescriptor extensionDescriptor = null; 908 Artifact extensionArtifact = artifacts.get( 0 ); 909 try 910 { 911 extensionDescriptor = extensionDescriptorBuilder.build( extensionArtifact.getFile() ); 912 } 913 catch ( IOException e ) 914 { 915 String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage(); 916 if ( logger.isDebugEnabled() ) 917 { 918 logger.error( message, e ); 919 } 920 else 921 { 922 logger.error( message ); 923 } 924 } 925 extensionRecord = extensionRealmCache.put( extensionKey, extensionRealm, extensionDescriptor, artifacts ); 926 } 927 extensionRealmCache.register( project, extensionKey, extensionRecord ); 928 pluginRealms.put( pluginKey, extensionRecord ); 929 930 return extensionRecord; 931 } 932 933 private List<Artifact> resolveExtensionArtifacts( Plugin extensionPlugin, List<RemoteRepository> repositories, 934 RepositorySystemSession session ) 935 throws PluginResolutionException 936 { 937 DependencyNode root = pluginDependenciesResolver.resolve( extensionPlugin, null, null, repositories, session ); 938 PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); 939 root.accept( nlg ); 940 return toMavenArtifacts( root, nlg ); 941 } 942 943}