View Javadoc

1   package org.apache.maven.classrealm;
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.net.MalformedURLException;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Random;
29  import java.util.Set;
30  
31  import org.apache.maven.artifact.ArtifactUtils;
32  import org.apache.maven.classrealm.ClassRealmRequest.RealmType;
33  import org.apache.maven.model.Model;
34  import org.apache.maven.model.Plugin;
35  import org.codehaus.plexus.MutablePlexusContainer;
36  import org.codehaus.plexus.PlexusContainer;
37  import org.codehaus.plexus.classworlds.ClassWorld;
38  import org.codehaus.plexus.classworlds.realm.ClassRealm;
39  import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
40  import org.codehaus.plexus.component.annotations.Component;
41  import org.codehaus.plexus.component.annotations.Requirement;
42  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
43  import org.codehaus.plexus.logging.Logger;
44  import org.codehaus.plexus.util.StringUtils;
45  import org.sonatype.aether.artifact.Artifact;
46  
47  /**
48   * Manages the class realms used by Maven. <strong>Warning:</strong> This is an internal utility class that is only
49   * public for technical reasons, it is not part of the public API. In particular, this class can be changed or deleted
50   * without prior notice.
51   * 
52   * @author Benjamin Bentmann
53   */
54  @Component( role = ClassRealmManager.class )
55  public class DefaultClassRealmManager
56      implements ClassRealmManager
57  {
58  
59      @Requirement
60      private Logger logger;
61  
62      @Requirement
63      protected PlexusContainer container;
64  
65      private ClassRealm mavenRealm;
66  
67      private ClassWorld getClassWorld()
68      {
69          return ( (MutablePlexusContainer) container ).getClassWorld();
70      }
71  
72      private ClassRealm newRealm( String id )
73      {
74          ClassWorld world = getClassWorld();
75  
76          synchronized ( world )
77          {
78              String realmId = id;
79  
80              Random random = new Random();
81  
82              while ( true )
83              {
84                  try
85                  {
86                      ClassRealm classRealm = world.newRealm( realmId, null );
87  
88                      if ( logger.isDebugEnabled() )
89                      {
90                          logger.debug( "Created new class realm " + realmId );
91                      }
92  
93                      return classRealm;
94                  }
95                  catch ( DuplicateRealmException e )
96                  {
97                      realmId = id + '-' + random.nextInt();
98                  }
99              }
100         }
101     }
102 
103     private synchronized ClassRealm getMavenRealm()
104     {
105         if ( mavenRealm == null )
106         {
107             mavenRealm = newRealm( "maven.api" );
108 
109             importMavenApi( mavenRealm );
110 
111             mavenRealm.setParentClassLoader( ClassLoader.getSystemClassLoader() );
112 
113             List<ClassRealmManagerDelegate> delegates = getDelegates();
114             if ( !delegates.isEmpty() )
115             {
116                 List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>();
117 
118                 ClassRealmRequest request =
119                     new DefaultClassRealmRequest( RealmType.Core, null, new ArrayList<String>(), constituents );
120 
121                 for ( ClassRealmManagerDelegate delegate : delegates )
122                 {
123                     delegate.setupRealm( mavenRealm, request );
124                 }
125 
126                 populateRealm( mavenRealm, constituents );
127             }
128         }
129         return mavenRealm;
130     }
131 
132     /**
133      * Creates a new class realm with the specified parent and imports.
134      * 
135      * @param baseRealmId The base id to use for the new realm, must not be {@code null}.
136      * @param type The type of the class realm, must not be {@code null}.
137      * @param parent The parent realm for the new realm, may be {@code null} to use the Maven core realm.
138      * @param imports The packages/types to import from the parent realm, may be {@code null}.
139      * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a
140      *            missing file) will automatically be excluded from the realm.
141      * @return The created class realm, never {@code null}.
142      */
143     private ClassRealm createRealm( String baseRealmId, RealmType type, ClassLoader parent, List<String> imports,
144                                     boolean importXpp3Dom, List<Artifact> artifacts )
145     {
146         Set<String> artifactIds = new LinkedHashSet<String>();
147 
148         List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>();
149 
150         if ( artifacts != null )
151         {
152             for ( Artifact artifact : artifacts )
153             {
154                 artifactIds.add( getId( artifact ) );
155                 if ( artifact.getFile() != null )
156                 {
157                     constituents.add( new ArtifactClassRealmConstituent( artifact ) );
158                 }
159             }
160         }
161 
162         if ( imports != null )
163         {
164             imports = new ArrayList<String>( imports );
165         }
166         else
167         {
168             imports = new ArrayList<String>();
169         }
170 
171         ClassRealm classRealm = newRealm( baseRealmId );
172 
173         if ( parent != null )
174         {
175             classRealm.setParentClassLoader( parent );
176         }
177         else
178         {
179             classRealm.setParentRealm( getMavenRealm() );
180         }
181 
182         List<ClassRealmManagerDelegate> delegates = getDelegates();
183         if ( !delegates.isEmpty() )
184         {
185             ClassRealmRequest request = new DefaultClassRealmRequest( type, parent, imports, constituents );
186 
187             for ( ClassRealmManagerDelegate delegate : delegates )
188             {
189                 delegate.setupRealm( classRealm, request );
190             }
191         }
192 
193         if ( importXpp3Dom )
194         {
195             importXpp3Dom( classRealm );
196         }
197 
198         if ( !imports.isEmpty() )
199         {
200             ClassLoader importedRealm = classRealm.getParentClassLoader();
201 
202             if ( logger.isDebugEnabled() )
203             {
204                 logger.debug( "Importing packages into class realm " + classRealm.getId() );
205             }
206 
207             for ( String imp : imports )
208             {
209                 if ( logger.isDebugEnabled() )
210                 {
211                     logger.debug( "  Imported: " + imp );
212                 }
213 
214                 classRealm.importFrom( importedRealm, imp );
215             }
216         }
217 
218         Set<String> includedIds = populateRealm( classRealm, constituents );
219 
220         if ( logger.isDebugEnabled() )
221         {
222             artifactIds.removeAll( includedIds );
223 
224             for ( String id : artifactIds )
225             {
226                 logger.debug( "  Excluded: " + id );
227             }
228         }
229 
230         return classRealm;
231     }
232 
233     /**
234      * Imports Xpp3Dom and associated types into the specified realm. Unlike the other archives that constitute the API
235      * realm, plexus-utils is not excluded from the plugin/project realm, yet we must ensure this class is loaded from
236      * the API realm and not from the plugin/project realm.
237      * 
238      * @param importingRealm The realm into which to import Xpp3Dom, must not be {@code null}.
239      */
240     private void importXpp3Dom( ClassRealm importingRealm )
241     {
242         ClassRealm coreRealm = getCoreRealm();
243 
244         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.util.xml.Xpp3Dom" );
245         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.util.xml.pull.XmlPullParser" );
246         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.util.xml.pull.XmlPullParserException" );
247         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.util.xml.pull.XmlSerializer" );
248     }
249 
250     /**
251      * Imports the classes/resources constituting the Maven API into the specified realm.
252      * 
253      * @param importingRealm The realm into which to import the Maven API, must not be {@code null}.
254      */
255     private void importMavenApi( ClassRealm importingRealm )
256     {
257         ClassRealm coreRealm = getCoreRealm();
258 
259         // maven-*
260         importingRealm.importFrom( coreRealm, "org.apache.maven" );
261 
262         // aether
263         importingRealm.importFrom( coreRealm, "org.sonatype.aether" );
264 
265         // plexus-classworlds
266         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.classworlds" );
267 
268         // classworlds (for legacy code)
269         importingRealm.importFrom( coreRealm, "org.codehaus.classworlds" );
270 
271         // plexus-container, plexus-component-annotations
272         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.component" );
273         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.configuration" );
274         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.container" );
275         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.context" );
276         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.lifecycle" );
277         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.logging" );
278         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.personality" );
279         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.ComponentRegistry" );
280         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.ContainerConfiguration" );
281         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.DefaultComponentRegistry" );
282         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.DefaultContainerConfiguration" );
283         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.DefaultPlexusContainer" );
284         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.DuplicateChildContainerException" );
285         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.MutablePlexusContainer" );
286         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.PlexusConstants" );
287         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.PlexusContainer" );
288         importingRealm.importFrom( coreRealm, "org.codehaus.plexus.PlexusContainerException" );
289     }
290 
291     public ClassRealm getCoreRealm()
292     {
293         return container.getContainerRealm();
294     }
295 
296     public ClassRealm createProjectRealm( Model model, List<Artifact> artifacts )
297     {
298         if ( model == null )
299         {
300             throw new IllegalArgumentException( "model missing" );
301         }
302 
303         return createRealm( getKey( model ), RealmType.Project, null, null, false, artifacts );
304     }
305 
306     private static String getKey( Model model )
307     {
308         return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion();
309     }
310 
311     public ClassRealm createExtensionRealm( Plugin plugin, List<Artifact> artifacts )
312     {
313         if ( plugin == null )
314         {
315             throw new IllegalArgumentException( "extension plugin missing" );
316         }
317 
318         return createRealm( getKey( plugin, true ), RealmType.Extension, null, null, true, artifacts );
319     }
320 
321     public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List<String> imports,
322                                          List<Artifact> artifacts )
323     {
324         if ( plugin == null )
325         {
326             throw new IllegalArgumentException( "plugin missing" );
327         }
328 
329         return createRealm( getKey( plugin, false ), RealmType.Plugin, parent, imports, true, artifacts );
330     }
331 
332     private static String getKey( Plugin plugin, boolean extension )
333     {
334         String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() );
335         return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":"
336             + version;
337     }
338 
339     private static String getId( Artifact artifact )
340     {
341         return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(), artifact.getClassifier(),
342                       artifact.getBaseVersion() );
343     }
344 
345     private static String getId( ClassRealmConstituent constituent )
346     {
347         return getId( constituent.getGroupId(), constituent.getArtifactId(), constituent.getType(),
348                       constituent.getClassifier(), constituent.getVersion() );
349     }
350 
351     private static String getId( String gid, String aid, String type, String cls, String ver )
352     {
353         return gid + ':' + aid + ':' + type + ( StringUtils.isNotEmpty( cls ) ? ':' + cls : "" ) + ':' + ver;
354     }
355 
356     private List<ClassRealmManagerDelegate> getDelegates()
357     {
358         try
359         {
360             return container.lookupList( ClassRealmManagerDelegate.class );
361         }
362         catch ( ComponentLookupException e )
363         {
364             logger.error( "Failed to lookup class realm delegates: " + e.getMessage(), e );
365 
366             return Collections.emptyList();
367         }
368     }
369 
370     private Set<String> populateRealm( ClassRealm classRealm, List<ClassRealmConstituent> constituents )
371     {
372         Set<String> includedIds = new LinkedHashSet<String>();
373 
374         if ( logger.isDebugEnabled() )
375         {
376             logger.debug( "Populating class realm " + classRealm.getId() );
377         }
378 
379         for ( ClassRealmConstituent constituent : constituents )
380         {
381             File file = constituent.getFile();
382 
383             String id = getId( constituent );
384             includedIds.add( id );
385 
386             if ( logger.isDebugEnabled() )
387             {
388                 logger.debug( "  Included: " + id );
389             }
390 
391             try
392             {
393                 classRealm.addURL( file.toURI().toURL() );
394             }
395             catch ( MalformedURLException e )
396             {
397                 // Not going to happen
398                 logger.error( e.getMessage(), e );
399             }
400         }
401 
402         return includedIds;
403     }
404 
405 }