View Javadoc

1   package org.apache.maven.extension;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static org.apache.maven.container.ContainerUtils.findChildComponentHints;
23  
24  import org.apache.maven.MavenArtifactFilterManager; 
25  import org.apache.maven.artifact.Artifact;
26  import org.apache.maven.artifact.ArtifactUtils;
27  import org.apache.maven.artifact.factory.ArtifactFactory;
28  import org.apache.maven.artifact.handler.ArtifactHandler;
29  import org.apache.maven.artifact.manager.WagonManager;
30  import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
31  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
32  import org.apache.maven.artifact.metadata.ResolutionGroup;
33  import org.apache.maven.artifact.repository.ArtifactRepository;
34  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
35  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
36  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
37  import org.apache.maven.artifact.resolver.ArtifactResolver;
38  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
39  import org.apache.maven.model.Extension;
40  import org.apache.maven.plugin.DefaultPluginManager;
41  import org.apache.maven.project.MavenProject;
42  import org.apache.maven.wagon.Wagon;
43  import org.codehaus.classworlds.ClassRealm;
44  import org.codehaus.classworlds.ClassWorld;
45  import org.codehaus.classworlds.DuplicateRealmException;
46  import org.codehaus.classworlds.NoSuchRealmException;
47  import org.codehaus.plexus.DefaultPlexusContainer;
48  import org.codehaus.plexus.PlexusConstants;
49  import org.codehaus.plexus.PlexusContainer;
50  import org.codehaus.plexus.PlexusContainerException;
51  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
52  import org.codehaus.plexus.context.Context;
53  import org.codehaus.plexus.context.ContextException;
54  import org.codehaus.plexus.logging.AbstractLogEnabled;
55  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
56  import org.codehaus.plexus.util.xml.Xpp3Dom;
57  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
58  
59  import java.io.File;
60  import java.io.InputStream;
61  import java.io.InputStreamReader;
62  import java.util.Collections;
63  import java.util.HashMap;
64  import java.util.Iterator;
65  import java.util.LinkedHashSet;
66  import java.util.Map;
67  import java.util.Set;
68  import java.util.jar.JarFile;
69  
70  /**
71   * Used to locate extensions.
72   *
73   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
74   * @author Jason van Zyl
75   * @version $Id: DefaultExtensionManager.java 796891 2009-07-22 22:03:26Z jdcasey $
76   */
77  public class DefaultExtensionManager
78      extends AbstractLogEnabled
79      implements ExtensionManager, Contextualizable
80  {
81      private ArtifactResolver artifactResolver;
82  
83      private ArtifactFactory artifactFactory;
84  
85      private ArtifactMetadataSource artifactMetadataSource;
86  
87      private DefaultPlexusContainer container;
88  
89      private ArtifactFilter artifactFilter = MavenArtifactFilterManager.createExtensionFilter();
90  
91      private WagonManager wagonManager;
92  
93      private PlexusContainer extensionContainer;
94  
95      private static final String CONTAINER_NAME = "extensions";
96  
97      public void addExtension( Extension extension,
98                                MavenProject project,
99                                ArtifactRepository localRepository )
100         throws ArtifactResolutionException, PlexusContainerException, ArtifactNotFoundException
101     {
102         String extensionId = ArtifactUtils.versionlessKey( extension.getGroupId(), extension.getArtifactId() );
103 
104         getLogger().debug( "Initialising extension: " + extensionId );
105 
106         Artifact artifact = (Artifact) project.getExtensionArtifactMap().get( extensionId );
107 
108         if ( artifact != null )
109         {
110             ArtifactFilter filter = new ProjectArtifactExceptionFilter( artifactFilter, project.getArtifact() );
111 
112             ResolutionGroup resolutionGroup;
113             try
114             {
115                 resolutionGroup = artifactMetadataSource.retrieve( artifact, localRepository,
116                                                                    project.getRemoteArtifactRepositories() );
117             }
118             catch ( ArtifactMetadataRetrievalException e )
119             {
120                 throw new ArtifactResolutionException( "Unable to download metadata from repository for plugin '" +
121                     artifact.getId() + "': " + e.getMessage(), artifact, e );
122             }
123 
124             // We use the same hack here to make sure that plexus 1.1 is available for extensions that do
125             // not declare plexus-utils but need it. MNG-2900
126             Set rgArtifacts = resolutionGroup.getArtifacts();
127             rgArtifacts = DefaultPluginManager.checkPlexusUtils( rgArtifacts, artifactFactory );
128 
129             Set dependencies = new LinkedHashSet();
130             dependencies.add( artifact );
131             dependencies.addAll( rgArtifacts );
132 
133             // Make sure that we do not influence the dependenecy resolution of extensions with the project's
134             // dependencyManagement
135 
136             ArtifactResolutionResult result = artifactResolver.resolveTransitively( dependencies, project.getArtifact(),
137                                                                                     Collections.EMPTY_MAP,
138                                                                                     //project.getManagedVersionMap(),
139                                                                                     localRepository,
140                                                                                     project.getRemoteArtifactRepositories(),
141                                                                                     artifactMetadataSource, filter );
142 
143             // gross hack for some backwards compat (MNG-2749)
144             // if it is a lone artifact, then we assume it to be a resource package, and put it in the main container
145             // as before. If it has dependencies, that's when we risk conflict and exile to the child container
146             // jvz: we have to make this 2 because plexus is always added now.
147 
148             Set artifacts = result.getArtifacts();
149 
150             // Lifecycles are loaded by the Lifecycle executor by looking up lifecycle definitions from the
151             // core container. So we need to look if an extension has a lifecycle mapping and use the container
152             // and not an extension container. (MNG-2831)
153 
154             if ( extensionContainsLifeycle( artifact.getFile() ) )
155             {
156                 for ( Iterator i = artifacts.iterator(); i.hasNext(); )
157                 {
158                     Artifact a = (Artifact) i.next();
159 
160                     if ( artifactFilter.include( a ) )
161                     {
162                         getLogger().debug( "Adding extension to core container: " + a.getFile() );
163 
164                         container.addJarResource( a.getFile() );
165                     }
166                 }
167             }
168             else if ( artifacts.size() == 2 )
169             {
170                 for ( Iterator i = artifacts.iterator(); i.hasNext(); )
171                 {
172                     Artifact a = (Artifact) i.next();
173 
174                     if ( !a.getArtifactId().equals( "plexus-utils" ) )
175                     {
176                         a = project.replaceWithActiveArtifact( a );
177 
178                         getLogger().debug( "Adding extension to core container: " + a.getFile() );
179 
180                         container.addJarResource( a.getFile() );
181                     }
182                 }
183             }
184             else
185             {
186                 // create a child container for the extension
187                 // TODO: this could surely be simpler/different on trunk with the new classworlds
188 
189                 if ( extensionContainer == null )
190                 {
191                     extensionContainer = createContainer();
192                 }
193 
194                 for ( Iterator i = result.getArtifacts().iterator(); i.hasNext(); )
195                 {
196                     Artifact a = (Artifact) i.next();
197 
198                     a = project.replaceWithActiveArtifact( a );
199 
200                     getLogger().debug( "Adding to extension classpath: " + a.getFile() );
201 
202                     extensionContainer.addJarResource( a.getFile() );
203                 }
204 
205                 if ( getLogger().isDebugEnabled() )
206                 {
207                     getLogger().debug( "Extension container contents:" );
208                     extensionContainer.getContainerRealm().display();
209                 }
210             }
211         }
212     }
213 
214     private PlexusContainer createContainer()
215         throws PlexusContainerException
216     {
217         DefaultPlexusContainer child = new DefaultPlexusContainer();
218 
219         ClassWorld classWorld = container.getClassWorld();
220         child.setClassWorld( classWorld );
221 
222         ClassRealm childRealm = null;
223 
224         // note: ideally extensions would live in their own realm, but this would mean that things like wagon-scm would
225         // have no way to obtain SCM extensions
226         String childRealmId = "plexus.core.child-container[" + CONTAINER_NAME + "]";
227         try
228         {
229             childRealm = classWorld.getRealm( childRealmId );
230         }
231         catch ( NoSuchRealmException e )
232         {
233             try
234             {
235                 childRealm = classWorld.newRealm( childRealmId );
236             }
237             catch ( DuplicateRealmException impossibleError )
238             {
239                 getLogger().error( "An impossible error has occurred. After getRealm() failed, newRealm() " +
240                     "produced duplication error on same id!", impossibleError );
241             }
242         }
243 
244         childRealm.setParent( container.getContainerRealm() );
245 
246         child.setCoreRealm( childRealm );
247 
248         child.setName( CONTAINER_NAME );
249 
250         // This is what we are skipping - we use the parent realm, but not the parent container since otherwise
251         // we won't reload component descriptors that already exist in there
252 //        child.setParentPlexusContainer( this );
253 
254         // ----------------------------------------------------------------------
255         // Set all the child elements from the parent that were set
256         // programmatically.
257         // ----------------------------------------------------------------------
258 
259         child.setLoggerManager( container.getLoggerManager() );
260 
261         child.initialize();
262 
263         child.start();
264 
265         return child;
266     }
267 
268     public void registerWagons()
269     {
270         if ( extensionContainer != null )
271         {
272             Set<String> wagons = findChildComponentHints( Wagon.ROLE, container, extensionContainer );
273             if ( wagons != null && !wagons.isEmpty() )
274             {
275                 getLogger().debug( "Wagons to register: " + wagons );
276                 wagonManager.registerWagons( wagons, extensionContainer );
277             }
278         }
279         else
280         {
281             getLogger().debug( "Wagons could not be registered as the extension container was never created" );
282         }
283     }
284     
285     @SuppressWarnings( "unchecked" )
286     public Map<String, ArtifactHandler> getArtifactTypeHandlers()
287     {
288         Map<String, ArtifactHandler> result = new HashMap<String, ArtifactHandler>();
289         
290         if ( extensionContainer != null )
291         {
292             try
293             {
294                 result.putAll( extensionContainer.lookupMap( ArtifactHandler.ROLE ) );
295             }
296             catch ( ComponentLookupException e )
297             {
298                 getLogger().debug( "ArtifactHandler extensions could not be loaded: " + e.getMessage(), e );
299             }
300         }
301         else
302         {
303             try
304             {
305                 result.putAll( container.lookupMap( ArtifactHandler.ROLE ) );
306             }
307             catch ( ComponentLookupException e )
308             {
309                 getLogger().debug( "ArtifactHandler extensions could not be loaded: " + e.getMessage(), e );
310             }
311         }
312         
313         return result;
314     }
315 
316     public void contextualize( Context context )
317         throws ContextException
318     {
319         container = (DefaultPlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
320     }
321 
322     private static final class ProjectArtifactExceptionFilter
323         implements ArtifactFilter
324     {
325         private ArtifactFilter passThroughFilter;
326 
327         private String projectDependencyConflictId;
328 
329         ProjectArtifactExceptionFilter( ArtifactFilter passThroughFilter,
330                                         Artifact projectArtifact )
331         {
332             this.passThroughFilter = passThroughFilter;
333             projectDependencyConflictId = projectArtifact.getDependencyConflictId();
334         }
335 
336         public boolean include( Artifact artifact )
337         {
338             String depConflictId = artifact.getDependencyConflictId();
339 
340             return projectDependencyConflictId.equals( depConflictId ) || passThroughFilter.include( artifact );
341         }
342     }
343 
344     private boolean extensionContainsLifeycle( File extension )
345     {
346         JarFile f;
347 
348         try
349         {
350             f = new JarFile( extension );
351 
352             InputStream is = f.getInputStream( f.getEntry( "META-INF/plexus/components.xml" ) );
353 
354             if ( is == null )
355             {
356                 return false;
357             }
358 
359             Xpp3Dom dom = Xpp3DomBuilder.build( new InputStreamReader( is ) );
360 
361             Xpp3Dom[] components = dom.getChild( "components" ).getChildren( "component" );
362 
363             for ( int i = 0; i < components.length; i++ )
364             {
365                 if ( components[i].getChild( "role" ).getValue().equals( "org.apache.maven.lifecycle.mapping.LifecycleMapping" ) )
366                 {
367                     return true;
368                 }
369             }
370         }
371         catch( Exception e )
372         {
373             // do nothing
374         }
375 
376         return false;
377     }
378 }