| package org.apache.maven.extension; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import static org.apache.maven.container.ContainerUtils.findChildComponentHints; |
| |
| import org.apache.maven.MavenArtifactFilterManager; |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.artifact.ArtifactUtils; |
| import org.apache.maven.artifact.factory.ArtifactFactory; |
| import org.apache.maven.artifact.handler.ArtifactHandler; |
| import org.apache.maven.artifact.manager.WagonManager; |
| import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException; |
| import org.apache.maven.artifact.metadata.ArtifactMetadataSource; |
| import org.apache.maven.artifact.metadata.ResolutionGroup; |
| import org.apache.maven.artifact.repository.ArtifactRepository; |
| import org.apache.maven.artifact.resolver.ArtifactNotFoundException; |
| import org.apache.maven.artifact.resolver.ArtifactResolutionException; |
| import org.apache.maven.artifact.resolver.ArtifactResolutionResult; |
| import org.apache.maven.artifact.resolver.ArtifactResolver; |
| import org.apache.maven.artifact.resolver.filter.ArtifactFilter; |
| import org.apache.maven.model.Extension; |
| import org.apache.maven.plugin.DefaultPluginManager; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.wagon.Wagon; |
| import org.codehaus.classworlds.ClassRealm; |
| import org.codehaus.classworlds.ClassWorld; |
| import org.codehaus.classworlds.DuplicateRealmException; |
| import org.codehaus.classworlds.NoSuchRealmException; |
| import org.codehaus.plexus.DefaultPlexusContainer; |
| import org.codehaus.plexus.PlexusConstants; |
| import org.codehaus.plexus.PlexusContainer; |
| import org.codehaus.plexus.PlexusContainerException; |
| import org.codehaus.plexus.component.repository.exception.ComponentLookupException; |
| import org.codehaus.plexus.context.Context; |
| import org.codehaus.plexus.context.ContextException; |
| import org.codehaus.plexus.logging.AbstractLogEnabled; |
| import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable; |
| import org.codehaus.plexus.util.xml.Xpp3Dom; |
| import org.codehaus.plexus.util.xml.Xpp3DomBuilder; |
| |
| import java.io.File; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.jar.JarFile; |
| |
| /** |
| * Used to locate extensions. |
| * |
| * @author <a href="mailto:brett@apache.org">Brett Porter</a> |
| * @author Jason van Zyl |
| * @version $Id$ |
| */ |
| public class DefaultExtensionManager |
| extends AbstractLogEnabled |
| implements ExtensionManager, Contextualizable |
| { |
| private ArtifactResolver artifactResolver; |
| |
| private ArtifactFactory artifactFactory; |
| |
| private ArtifactMetadataSource artifactMetadataSource; |
| |
| private DefaultPlexusContainer container; |
| |
| private ArtifactFilter artifactFilter = MavenArtifactFilterManager.createExtensionFilter(); |
| |
| private WagonManager wagonManager; |
| |
| private PlexusContainer extensionContainer; |
| |
| private static final String CONTAINER_NAME = "extensions"; |
| |
| public void addExtension( Extension extension, |
| MavenProject project, |
| ArtifactRepository localRepository ) |
| throws ArtifactResolutionException, PlexusContainerException, ArtifactNotFoundException |
| { |
| String extensionId = ArtifactUtils.versionlessKey( extension.getGroupId(), extension.getArtifactId() ); |
| |
| getLogger().debug( "Initialising extension: " + extensionId ); |
| |
| Artifact artifact = (Artifact) project.getExtensionArtifactMap().get( extensionId ); |
| |
| if ( artifact != null ) |
| { |
| ArtifactFilter filter = new ProjectArtifactExceptionFilter( artifactFilter, project.getArtifact() ); |
| |
| ResolutionGroup resolutionGroup; |
| try |
| { |
| resolutionGroup = artifactMetadataSource.retrieve( artifact, localRepository, |
| project.getRemoteArtifactRepositories() ); |
| } |
| catch ( ArtifactMetadataRetrievalException e ) |
| { |
| throw new ArtifactResolutionException( "Unable to download metadata from repository for plugin '" + |
| artifact.getId() + "': " + e.getMessage(), artifact, e ); |
| } |
| |
| // We use the same hack here to make sure that plexus 1.1 is available for extensions that do |
| // not declare plexus-utils but need it. MNG-2900 |
| Set rgArtifacts = resolutionGroup.getArtifacts(); |
| rgArtifacts = DefaultPluginManager.checkPlexusUtils( rgArtifacts, artifactFactory ); |
| |
| Set dependencies = new LinkedHashSet(); |
| dependencies.add( artifact ); |
| dependencies.addAll( rgArtifacts ); |
| |
| // Make sure that we do not influence the dependenecy resolution of extensions with the project's |
| // dependencyManagement |
| |
| ArtifactResolutionResult result = artifactResolver.resolveTransitively( dependencies, project.getArtifact(), |
| Collections.EMPTY_MAP, |
| //project.getManagedVersionMap(), |
| localRepository, |
| project.getRemoteArtifactRepositories(), |
| artifactMetadataSource, filter ); |
| |
| // gross hack for some backwards compat (MNG-2749) |
| // if it is a lone artifact, then we assume it to be a resource package, and put it in the main container |
| // as before. If it has dependencies, that's when we risk conflict and exile to the child container |
| // jvz: we have to make this 2 because plexus is always added now. |
| |
| Set artifacts = result.getArtifacts(); |
| |
| // Lifecycles are loaded by the Lifecycle executor by looking up lifecycle definitions from the |
| // core container. So we need to look if an extension has a lifecycle mapping and use the container |
| // and not an extension container. (MNG-2831) |
| |
| if ( extensionContainsLifeycle( artifact.getFile() ) ) |
| { |
| for ( Iterator i = artifacts.iterator(); i.hasNext(); ) |
| { |
| Artifact a = (Artifact) i.next(); |
| |
| if ( artifactFilter.include( a ) ) |
| { |
| getLogger().debug( "Adding extension to core container: " + a.getFile() ); |
| |
| container.addJarResource( a.getFile() ); |
| } |
| } |
| } |
| else if ( artifacts.size() == 2 ) |
| { |
| for ( Iterator i = artifacts.iterator(); i.hasNext(); ) |
| { |
| Artifact a = (Artifact) i.next(); |
| |
| if ( !a.getArtifactId().equals( "plexus-utils" ) ) |
| { |
| a = project.replaceWithActiveArtifact( a ); |
| |
| getLogger().debug( "Adding extension to core container: " + a.getFile() ); |
| |
| container.addJarResource( a.getFile() ); |
| } |
| } |
| } |
| else |
| { |
| // create a child container for the extension |
| // TODO: this could surely be simpler/different on trunk with the new classworlds |
| |
| if ( extensionContainer == null ) |
| { |
| extensionContainer = createContainer(); |
| } |
| |
| for ( Iterator i = result.getArtifacts().iterator(); i.hasNext(); ) |
| { |
| Artifact a = (Artifact) i.next(); |
| |
| a = project.replaceWithActiveArtifact( a ); |
| |
| getLogger().debug( "Adding to extension classpath: " + a.getFile() ); |
| |
| extensionContainer.addJarResource( a.getFile() ); |
| } |
| |
| if ( getLogger().isDebugEnabled() ) |
| { |
| getLogger().debug( "Extension container contents:" ); |
| extensionContainer.getContainerRealm().display(); |
| } |
| } |
| } |
| } |
| |
| private PlexusContainer createContainer() |
| throws PlexusContainerException |
| { |
| DefaultPlexusContainer child = new DefaultPlexusContainer(); |
| |
| ClassWorld classWorld = container.getClassWorld(); |
| child.setClassWorld( classWorld ); |
| |
| ClassRealm childRealm = null; |
| |
| // note: ideally extensions would live in their own realm, but this would mean that things like wagon-scm would |
| // have no way to obtain SCM extensions |
| String childRealmId = "plexus.core.child-container[" + CONTAINER_NAME + "]"; |
| try |
| { |
| childRealm = classWorld.getRealm( childRealmId ); |
| } |
| catch ( NoSuchRealmException e ) |
| { |
| try |
| { |
| childRealm = classWorld.newRealm( childRealmId ); |
| } |
| catch ( DuplicateRealmException impossibleError ) |
| { |
| getLogger().error( "An impossible error has occurred. After getRealm() failed, newRealm() " + |
| "produced duplication error on same id!", impossibleError ); |
| } |
| } |
| |
| childRealm.setParent( container.getContainerRealm() ); |
| |
| child.setCoreRealm( childRealm ); |
| |
| child.setName( CONTAINER_NAME ); |
| |
| // This is what we are skipping - we use the parent realm, but not the parent container since otherwise |
| // we won't reload component descriptors that already exist in there |
| // child.setParentPlexusContainer( this ); |
| |
| // ---------------------------------------------------------------------- |
| // Set all the child elements from the parent that were set |
| // programmatically. |
| // ---------------------------------------------------------------------- |
| |
| child.setLoggerManager( container.getLoggerManager() ); |
| |
| child.initialize(); |
| |
| child.start(); |
| |
| return child; |
| } |
| |
| public void registerWagons() |
| { |
| if ( extensionContainer != null ) |
| { |
| Set<String> wagons = findChildComponentHints( Wagon.ROLE, container, extensionContainer ); |
| if ( wagons != null && !wagons.isEmpty() ) |
| { |
| getLogger().debug( "Wagons to register: " + wagons ); |
| wagonManager.registerWagons( wagons, extensionContainer ); |
| } |
| } |
| else |
| { |
| getLogger().debug( "Wagons could not be registered as the extension container was never created" ); |
| } |
| } |
| |
| @SuppressWarnings( "unchecked" ) |
| public Map<String, ArtifactHandler> getArtifactTypeHandlers() |
| { |
| Map<String, ArtifactHandler> result = new HashMap<String, ArtifactHandler>(); |
| |
| if ( extensionContainer != null ) |
| { |
| try |
| { |
| result.putAll( extensionContainer.lookupMap( ArtifactHandler.ROLE ) ); |
| } |
| catch ( ComponentLookupException e ) |
| { |
| getLogger().debug( "ArtifactHandler extensions could not be loaded: " + e.getMessage(), e ); |
| } |
| } |
| else |
| { |
| try |
| { |
| result.putAll( container.lookupMap( ArtifactHandler.ROLE ) ); |
| } |
| catch ( ComponentLookupException e ) |
| { |
| getLogger().debug( "ArtifactHandler extensions could not be loaded: " + e.getMessage(), e ); |
| } |
| } |
| |
| return result; |
| } |
| |
| public void contextualize( Context context ) |
| throws ContextException |
| { |
| container = (DefaultPlexusContainer) context.get( PlexusConstants.PLEXUS_KEY ); |
| } |
| |
| private static final class ProjectArtifactExceptionFilter |
| implements ArtifactFilter |
| { |
| private ArtifactFilter passThroughFilter; |
| |
| private String projectDependencyConflictId; |
| |
| ProjectArtifactExceptionFilter( ArtifactFilter passThroughFilter, |
| Artifact projectArtifact ) |
| { |
| this.passThroughFilter = passThroughFilter; |
| projectDependencyConflictId = projectArtifact.getDependencyConflictId(); |
| } |
| |
| public boolean include( Artifact artifact ) |
| { |
| String depConflictId = artifact.getDependencyConflictId(); |
| |
| return projectDependencyConflictId.equals( depConflictId ) || passThroughFilter.include( artifact ); |
| } |
| } |
| |
| private boolean extensionContainsLifeycle( File extension ) |
| { |
| JarFile f; |
| |
| try |
| { |
| f = new JarFile( extension ); |
| |
| InputStream is = f.getInputStream( f.getEntry( "META-INF/plexus/components.xml" ) ); |
| |
| if ( is == null ) |
| { |
| return false; |
| } |
| |
| Xpp3Dom dom = Xpp3DomBuilder.build( new InputStreamReader( is ) ); |
| |
| Xpp3Dom[] components = dom.getChild( "components" ).getChildren( "component" ); |
| |
| for ( int i = 0; i < components.length; i++ ) |
| { |
| if ( components[i].getChild( "role" ).getValue().equals( "org.apache.maven.lifecycle.mapping.LifecycleMapping" ) ) |
| { |
| return true; |
| } |
| } |
| } |
| catch( Exception e ) |
| { |
| // do nothing |
| } |
| |
| return false; |
| } |
| } |