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