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 MavenProject project = session.getCurrentProject(); 337 338 if ( plugin.isExtensions() ) 339 { 340 ExtensionRealmCache.CacheRecord extensionRecord; 341 try 342 { 343 RepositorySystemSession repositorySession = session.getRepositorySession(); 344 extensionRecord = setupExtensionsRealm( project, plugin, repositorySession ); 345 } 346 catch ( PluginManagerException e ) 347 { 348 // extensions realm is expected to be fully setup at this point 349 // any exception means a problem in maven code, not a user error 350 throw new IllegalStateException( e ); 351 } 352 353 ClassRealm pluginRealm = extensionRecord.realm; 354 List<Artifact> pluginArtifacts = extensionRecord.artifacts; 355 356 for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() ) 357 { 358 componentDescriptor.setRealm( pluginRealm ); 359 } 360 361 pluginDescriptor.setClassRealm( pluginRealm ); 362 pluginDescriptor.setArtifacts( pluginArtifacts ); 363 } 364 else 365 { 366 Map<String, ClassLoader> foreignImports = calcImports( project, parent, imports ); 367 368 PluginRealmCache.Key cacheKey = 369 pluginRealmCache.createKey( plugin, parent, foreignImports, filter, 370 project.getRemotePluginRepositories(), session.getRepositorySession() ); 371 372 PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get( cacheKey ); 373 374 if ( cacheRecord != null ) 375 { 376 pluginDescriptor.setClassRealm( cacheRecord.realm ); 377 pluginDescriptor.setArtifacts( new ArrayList<Artifact>( cacheRecord.artifacts ) ); 378 for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() ) 379 { 380 componentDescriptor.setRealm( cacheRecord.realm ); 381 } 382 } 383 else 384 { 385 createPluginRealm( pluginDescriptor, session, parent, foreignImports, filter ); 386 387 cacheRecord = 388 pluginRealmCache.put( cacheKey, pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts() ); 389 } 390 391 pluginRealmCache.register( project, cacheKey, cacheRecord ); 392 } 393 } 394 395 private void createPluginRealm( PluginDescriptor pluginDescriptor, MavenSession session, ClassLoader parent, 396 Map<String, ClassLoader> foreignImports, DependencyFilter filter ) 397 throws PluginResolutionException, PluginContainerException 398 { 399 Plugin plugin = pluginDescriptor.getPlugin(); 400 401 if ( plugin == null ) 402 { 403 throw new IllegalArgumentException( "incomplete plugin descriptor, plugin missing" ); 404 } 405 406 Artifact pluginArtifact = pluginDescriptor.getPluginArtifact(); 407 408 if ( pluginArtifact == null ) 409 { 410 throw new IllegalArgumentException( "incomplete plugin descriptor, plugin artifact missing" ); 411 } 412 413 MavenProject project = session.getCurrentProject(); 414 415 final ClassRealm pluginRealm; 416 final List<Artifact> pluginArtifacts; 417 418 RepositorySystemSession repositorySession = session.getRepositorySession(); 419 DependencyFilter dependencyFilter = project.getExtensionDependencyFilter(); 420 dependencyFilter = AndDependencyFilter.newInstance( dependencyFilter, filter ); 421 422 DependencyNode root = 423 pluginDependenciesResolver.resolve( plugin, RepositoryUtils.toArtifact( pluginArtifact ), 424 dependencyFilter, project.getRemotePluginRepositories(), 425 repositorySession ); 426 427 PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); 428 root.accept( nlg ); 429 430 pluginArtifacts = toMavenArtifacts( root, nlg ); 431 432 pluginRealm = 433 classRealmManager.createPluginRealm( plugin, parent, null, foreignImports, 434 toAetherArtifacts( pluginArtifacts ) ); 435 436 discoverPluginComponents( pluginRealm, plugin, pluginDescriptor ); 437 438 pluginDescriptor.setClassRealm( pluginRealm ); 439 pluginDescriptor.setArtifacts( pluginArtifacts ); 440 } 441 442 private void discoverPluginComponents( final ClassRealm pluginRealm, Plugin plugin, 443 PluginDescriptor pluginDescriptor ) 444 throws PluginContainerException 445 { 446 try 447 { 448 if ( pluginDescriptor != null ) 449 { 450 for ( ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents() ) 451 { 452 componentDescriptor.setRealm( pluginRealm ); 453 container.addComponentDescriptor( componentDescriptor ); 454 } 455 } 456 457 ( (DefaultPlexusContainer) container ).discoverComponents( pluginRealm, 458 new SessionScopeModule( container ), 459 new MojoExecutionScopeModule( container ) ); 460 } 461 catch ( ComponentLookupException e ) 462 { 463 throw new PluginContainerException( plugin, pluginRealm, "Error in component graph of plugin " 464 + plugin.getId() + ": " + e.getMessage(), e ); 465 } 466 catch ( CycleDetectedInComponentGraphException e ) 467 { 468 throw new PluginContainerException( plugin, pluginRealm, "Error in component graph of plugin " 469 + plugin.getId() + ": " + e.getMessage(), e ); 470 } 471 } 472 473 private List<org.eclipse.aether.artifact.Artifact> toAetherArtifacts( final List<Artifact> pluginArtifacts ) 474 { 475 return new ArrayList<org.eclipse.aether.artifact.Artifact>( RepositoryUtils.toArtifacts( pluginArtifacts ) ); 476 } 477 478 private List<Artifact> toMavenArtifacts( DependencyNode root, PreorderNodeListGenerator nlg ) 479 { 480 List<Artifact> artifacts = new ArrayList<Artifact>( nlg.getNodes().size() ); 481 RepositoryUtils.toArtifacts( artifacts, Collections.singleton( root ), Collections.<String>emptyList(), null ); 482 for ( Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); ) 483 { 484 Artifact artifact = it.next(); 485 if ( artifact.getFile() == null ) 486 { 487 it.remove(); 488 } 489 } 490 return artifacts; 491 } 492 493 private Map<String, ClassLoader> calcImports( MavenProject project, ClassLoader parent, List<String> imports ) 494 { 495 Map<String, ClassLoader> foreignImports = new HashMap<String, ClassLoader>(); 496 497 ClassLoader projectRealm = project.getClassRealm(); 498 if ( projectRealm != null ) 499 { 500 foreignImports.put( "", projectRealm ); 501 } 502 else 503 { 504 foreignImports.put( "", classRealmManager.getMavenApiRealm() ); 505 } 506 507 if ( parent != null && imports != null ) 508 { 509 for ( String parentImport : imports ) 510 { 511 foreignImports.put( parentImport, parent ); 512 } 513 } 514 515 return foreignImports; 516 } 517 518 public <T> T getConfiguredMojo( Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution ) 519 throws PluginConfigurationException, PluginContainerException 520 { 521 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); 522 523 PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor(); 524 525 ClassRealm pluginRealm = pluginDescriptor.getClassRealm(); 526 527 if ( logger.isDebugEnabled() ) 528 { 529 logger.debug( "Configuring mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm ); 530 } 531 532 // We are forcing the use of the plugin realm for all lookups that might occur during 533 // the lifecycle that is part of the lookup. Here we are specifically trying to keep 534 // lookups that occur in contextualize calls in line with the right realm. 535 ClassRealm oldLookupRealm = container.setLookupRealm( pluginRealm ); 536 537 ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); 538 Thread.currentThread().setContextClassLoader( pluginRealm ); 539 540 try 541 { 542 T mojo; 543 544 try 545 { 546 mojo = container.lookup( mojoInterface, mojoDescriptor.getRoleHint() ); 547 } 548 catch ( ComponentLookupException e ) 549 { 550 Throwable cause = e.getCause(); 551 while ( cause != null && !( cause instanceof LinkageError ) 552 && !( cause instanceof ClassNotFoundException ) ) 553 { 554 cause = cause.getCause(); 555 } 556 557 if ( ( cause instanceof NoClassDefFoundError ) || ( cause instanceof ClassNotFoundException ) ) 558 { 559 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 ); 560 PrintStream ps = new PrintStream( os ); 561 ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '" 562 + pluginDescriptor.getId() + "'. A required class is missing: " + cause.getMessage() ); 563 pluginRealm.display( ps ); 564 565 throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause ); 566 } 567 else if ( cause instanceof LinkageError ) 568 { 569 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 ); 570 PrintStream ps = new PrintStream( os ); 571 ps.println( "Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '" 572 + pluginDescriptor.getId() + "' due to an API incompatibility: " + e.getClass().getName() 573 + ": " + cause.getMessage() ); 574 pluginRealm.display( ps ); 575 576 throw new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), cause ); 577 } 578 579 throw new PluginContainerException( mojoDescriptor, pluginRealm, "Unable to load the mojo '" 580 + mojoDescriptor.getGoal() + "' (or one of its required components) from the plugin '" 581 + pluginDescriptor.getId() + "'", e ); 582 } 583 584 if ( mojo instanceof ContextEnabled ) 585 { 586 MavenProject project = session.getCurrentProject(); 587 588 Map<String, Object> pluginContext = session.getPluginContext( pluginDescriptor, project ); 589 590 if ( pluginContext != null ) 591 { 592 pluginContext.put( "project", project ); 593 594 pluginContext.put( "pluginDescriptor", pluginDescriptor ); 595 596 ( (ContextEnabled) mojo ).setPluginContext( pluginContext ); 597 } 598 } 599 600 if ( mojo instanceof Mojo ) 601 { 602 Logger mojoLogger = loggerManager.getLoggerForComponent( mojoDescriptor.getImplementation() ); 603 ( (Mojo) mojo ).setLog( new DefaultLog( mojoLogger ) ); 604 } 605 606 Xpp3Dom dom = mojoExecution.getConfiguration(); 607 608 PlexusConfiguration pomConfiguration; 609 610 if ( dom == null ) 611 { 612 pomConfiguration = new XmlPlexusConfiguration( "configuration" ); 613 } 614 else 615 { 616 pomConfiguration = new XmlPlexusConfiguration( dom ); 617 } 618 619 ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator( session, mojoExecution ); 620 621 populatePluginFields( mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator ); 622 623 return mojo; 624 } 625 finally 626 { 627 Thread.currentThread().setContextClassLoader( oldClassLoader ); 628 container.setLookupRealm( oldLookupRealm ); 629 } 630 } 631 632 private void populatePluginFields( Object mojo, MojoDescriptor mojoDescriptor, ClassRealm pluginRealm, 633 PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator ) 634 throws PluginConfigurationException 635 { 636 ComponentConfigurator configurator = null; 637 638 String configuratorId = mojoDescriptor.getComponentConfigurator(); 639 640 if ( StringUtils.isEmpty( configuratorId ) ) 641 { 642 configuratorId = "basic"; 643 } 644 645 try 646 { 647 // TODO: could the configuration be passed to lookup and the configurator known to plexus via the descriptor 648 // so that this method could entirely be handled by a plexus lookup? 649 configurator = container.lookup( ComponentConfigurator.class, configuratorId ); 650 651 ConfigurationListener listener = new DebugConfigurationListener( logger ); 652 653 ValidatingConfigurationListener validator = 654 new ValidatingConfigurationListener( mojo, mojoDescriptor, listener ); 655 656 logger.debug( "Configuring mojo '" + mojoDescriptor.getId() + "' with " + configuratorId 657 + " configurator -->" ); 658 659 configurator.configureComponent( mojo, configuration, expressionEvaluator, pluginRealm, validator ); 660 661 logger.debug( "-- end configuration --" ); 662 663 Collection<Parameter> missingParameters = validator.getMissingParameters(); 664 if ( !missingParameters.isEmpty() ) 665 { 666 if ( "basic".equals( configuratorId ) ) 667 { 668 throw new PluginParameterException( mojoDescriptor, new ArrayList<Parameter>( missingParameters ) ); 669 } 670 else 671 { 672 /* 673 * NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the 674 * hard way. 675 */ 676 validateParameters( mojoDescriptor, configuration, expressionEvaluator ); 677 } 678 } 679 } 680 catch ( ComponentConfigurationException e ) 681 { 682 String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId(); 683 if ( e.getFailedConfiguration() != null ) 684 { 685 message += " for parameter " + e.getFailedConfiguration().getName(); 686 } 687 message += ": " + e.getMessage(); 688 689 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), message, e ); 690 } 691 catch ( ComponentLookupException e ) 692 { 693 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), 694 "Unable to retrieve component configurator " + configuratorId 695 + " for configuration of mojo " + mojoDescriptor.getId(), e ); 696 } 697 catch ( NoClassDefFoundError e ) 698 { 699 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 ); 700 PrintStream ps = new PrintStream( os ); 701 ps.println( "A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": " 702 + e.getMessage() ); 703 pluginRealm.display( ps ); 704 705 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e ); 706 } 707 catch ( LinkageError e ) 708 { 709 ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 ); 710 PrintStream ps = new PrintStream( os ); 711 ps.println( "An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId() 712 + ": " + e.getClass().getName() + ": " + e.getMessage() ); 713 pluginRealm.display( ps ); 714 715 throw new PluginConfigurationException( mojoDescriptor.getPluginDescriptor(), os.toString(), e ); 716 } 717 finally 718 { 719 if ( configurator != null ) 720 { 721 try 722 { 723 container.release( configurator ); 724 } 725 catch ( ComponentLifecycleException e ) 726 { 727 logger.debug( "Failed to release mojo configurator - ignoring." ); 728 } 729 } 730 } 731 } 732 733 private void validateParameters( MojoDescriptor mojoDescriptor, PlexusConfiguration configuration, 734 ExpressionEvaluator expressionEvaluator ) 735 throws ComponentConfigurationException, PluginParameterException 736 { 737 if ( mojoDescriptor.getParameters() == null ) 738 { 739 return; 740 } 741 742 List<Parameter> invalidParameters = new ArrayList<Parameter>(); 743 744 for ( Parameter parameter : mojoDescriptor.getParameters() ) 745 { 746 if ( !parameter.isRequired() ) 747 { 748 continue; 749 } 750 751 Object value = null; 752 753 PlexusConfiguration config = configuration.getChild( parameter.getName(), false ); 754 if ( config != null ) 755 { 756 String expression = config.getValue( null ); 757 758 try 759 { 760 value = expressionEvaluator.evaluate( expression ); 761 762 if ( value == null ) 763 { 764 value = config.getAttribute( "default-value", null ); 765 } 766 } 767 catch ( ExpressionEvaluationException e ) 768 { 769 String msg = 770 "Error evaluating the expression '" + expression + "' for configuration value '" 771 + configuration.getName() + "'"; 772 throw new ComponentConfigurationException( configuration, msg, e ); 773 } 774 } 775 776 if ( value == null && ( config == null || config.getChildCount() <= 0 ) ) 777 { 778 invalidParameters.add( parameter ); 779 } 780 } 781 782 if ( !invalidParameters.isEmpty() ) 783 { 784 throw new PluginParameterException( mojoDescriptor, invalidParameters ); 785 } 786 } 787 788 public void releaseMojo( Object mojo, MojoExecution mojoExecution ) 789 { 790 if ( mojo != null ) 791 { 792 try 793 { 794 container.release( mojo ); 795 } 796 catch ( ComponentLifecycleException e ) 797 { 798 String goalExecId = mojoExecution.getGoal(); 799 800 if ( mojoExecution.getExecutionId() != null ) 801 { 802 goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}"; 803 } 804 805 logger.debug( "Error releasing mojo for " + goalExecId, e ); 806 } 807 } 808 } 809 810 public ExtensionRealmCache.CacheRecord setupExtensionsRealm( MavenProject project, Plugin plugin, 811 RepositorySystemSession session ) 812 throws PluginManagerException 813 { 814 @SuppressWarnings( "unchecked" ) 815 Map<String, ExtensionRealmCache.CacheRecord> pluginRealms = 816 (Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue( KEY_EXTENSIONS_REALMS ); 817 if ( pluginRealms == null ) 818 { 819 pluginRealms = new HashMap<String, ExtensionRealmCache.CacheRecord>(); 820 project.setContextValue( KEY_EXTENSIONS_REALMS, pluginRealms ); 821 } 822 823 final String pluginKey = plugin.getId(); 824 825 ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get( pluginKey ); 826 if ( extensionRecord != null ) 827 { 828 return extensionRecord; 829 } 830 831 final List<RemoteRepository> repositories = project.getRemotePluginRepositories(); 832 833 // resolve plugin version as necessary 834 if ( plugin.getVersion() == null ) 835 { 836 PluginVersionRequest versionRequest = new DefaultPluginVersionRequest( plugin, session, repositories ); 837 try 838 { 839 plugin.setVersion( pluginVersionResolver.resolve( versionRequest ).getVersion() ); 840 } 841 catch ( PluginVersionResolutionException e ) 842 { 843 throw new PluginManagerException( plugin, e.getMessage(), e ); 844 } 845 } 846 847 // resolve plugin artifacts 848 List<Artifact> artifacts; 849 PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey( plugin, null, repositories, session ); 850 PluginArtifactsCache.CacheRecord recordArtifacts; 851 try 852 { 853 recordArtifacts = pluginArtifactsCache.get( cacheKey ); 854 } 855 catch ( PluginResolutionException e ) 856 { 857 throw new PluginManagerException( plugin, e.getMessage(), e ); 858 } 859 if ( recordArtifacts != null ) 860 { 861 artifacts = recordArtifacts.artifacts; 862 } 863 else 864 { 865 try 866 { 867 artifacts = resolveExtensionArtifacts( plugin, repositories, session ); 868 recordArtifacts = pluginArtifactsCache.put( cacheKey, artifacts ); 869 } 870 catch ( PluginResolutionException e ) 871 { 872 pluginArtifactsCache.put( cacheKey, e ); 873 pluginArtifactsCache.register( project, cacheKey, recordArtifacts ); 874 throw new PluginManagerException( plugin, e.getMessage(), e ); 875 } 876 } 877 pluginArtifactsCache.register( project, cacheKey, recordArtifacts ); 878 879 // create and cache extensions realms 880 final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey( artifacts ); 881 extensionRecord = extensionRealmCache.get( extensionKey ); 882 if ( extensionRecord == null ) 883 { 884 ClassRealm extensionRealm = classRealmManager.createExtensionRealm( plugin, 885 toAetherArtifacts( artifacts ) ); 886 887 // TODO figure out how to use the same PluginDescriptor when running mojos 888 889 PluginDescriptor pluginDescriptor = null; 890 if ( plugin.isExtensions() && !artifacts.isEmpty() ) 891 { 892 // ignore plugin descriptor parsing errors at this point 893 // these errors will reported during calculation of project build execution plan 894 try 895 { 896 pluginDescriptor = extractPluginDescriptor( artifacts.get( 0 ), plugin ); 897 } 898 catch ( PluginDescriptorParsingException e ) 899 { 900 // ignore, see above 901 } 902 catch ( InvalidPluginDescriptorException e ) 903 { 904 // ignore, see above 905 } 906 } 907 908 discoverPluginComponents( extensionRealm, plugin, pluginDescriptor ); 909 910 ExtensionDescriptor extensionDescriptor = null; 911 Artifact extensionArtifact = artifacts.get( 0 ); 912 try 913 { 914 extensionDescriptor = extensionDescriptorBuilder.build( extensionArtifact.getFile() ); 915 } 916 catch ( IOException e ) 917 { 918 String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage(); 919 if ( logger.isDebugEnabled() ) 920 { 921 logger.error( message, e ); 922 } 923 else 924 { 925 logger.error( message ); 926 } 927 } 928 extensionRecord = extensionRealmCache.put( extensionKey, extensionRealm, extensionDescriptor, artifacts ); 929 } 930 extensionRealmCache.register( project, extensionKey, extensionRecord ); 931 pluginRealms.put( pluginKey, extensionRecord ); 932 933 return extensionRecord; 934 } 935 936 private List<Artifact> resolveExtensionArtifacts( Plugin extensionPlugin, List<RemoteRepository> repositories, 937 RepositorySystemSession session ) 938 throws PluginResolutionException 939 { 940 DependencyNode root = pluginDependenciesResolver.resolve( extensionPlugin, null, null, repositories, session ); 941 PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); 942 root.accept( nlg ); 943 return toMavenArtifacts( root, nlg ); 944 } 945 946}