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.HashMap;
27  import java.util.LinkedHashSet;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Random;
31  import java.util.Set;
32  import java.util.TreeMap;
33  
34  import org.apache.maven.artifact.ArtifactUtils;
35  import org.apache.maven.classrealm.ClassRealmRequest.RealmType;
36  import org.apache.maven.model.Model;
37  import org.apache.maven.model.Plugin;
38  import org.codehaus.plexus.MutablePlexusContainer;
39  import org.codehaus.plexus.PlexusContainer;
40  import org.codehaus.plexus.classworlds.ClassWorld;
41  import org.codehaus.plexus.classworlds.realm.ClassRealm;
42  import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
43  import org.codehaus.plexus.component.annotations.Component;
44  import org.codehaus.plexus.component.annotations.Requirement;
45  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
46  import org.codehaus.plexus.logging.Logger;
47  import org.codehaus.plexus.util.StringUtils;
48  import org.eclipse.aether.artifact.Artifact;
49  
50  /**
51   * Manages the class realms used by Maven. <strong>Warning:</strong> This is an internal utility class that is only
52   * public for technical reasons, it is not part of the public API. In particular, this class can be changed or deleted
53   * without prior notice.
54   *
55   * @author Benjamin Bentmann
56   */
57  @Component( role = ClassRealmManager.class )
58  public class DefaultClassRealmManager
59      implements ClassRealmManager
60  {
61  
62      @Requirement
63      private Logger logger;
64  
65      @Requirement
66      protected PlexusContainer container;
67  
68      private ClassRealm mavenRealm;
69  
70      private ClassWorld getClassWorld()
71      {
72          return ( (MutablePlexusContainer) container ).getClassWorld();
73      }
74  
75      private ClassRealm newRealm( String id )
76      {
77          ClassWorld world = getClassWorld();
78  
79          synchronized ( world )
80          {
81              String realmId = id;
82  
83              Random random = new Random();
84  
85              while ( true )
86              {
87                  try
88                  {
89                      ClassRealm classRealm = world.newRealm( realmId, null );
90  
91                      if ( logger.isDebugEnabled() )
92                      {
93                          logger.debug( "Created new class realm " + realmId );
94                      }
95  
96                      return classRealm;
97                  }
98                  catch ( DuplicateRealmException e )
99                  {
100                     realmId = id + '-' + random.nextInt();
101                 }
102             }
103         }
104     }
105 
106     public synchronized ClassRealm getMavenApiRealm()
107     {
108         if ( mavenRealm == null )
109         {
110             mavenRealm = newRealm( "maven.api" );
111 
112             List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>();
113 
114             List<String> parentImports = new ArrayList<String>();
115 
116             Map<String, ClassLoader> foreignImports = new HashMap<String, ClassLoader>();
117             importMavenApi( foreignImports );
118 
119             callDelegates( mavenRealm, RealmType.Core, mavenRealm.getParentClassLoader(), parentImports,
120                            foreignImports, constituents );
121 
122             wireRealm( mavenRealm, parentImports, foreignImports );
123 
124             populateRealm( mavenRealm, constituents );
125         }
126 
127         return mavenRealm;
128     }
129 
130     private void importMavenApi( Map<String, ClassLoader> imports )
131     {
132         ClassRealm coreRealm = getCoreRealm();
133 
134         // maven-*
135         imports.put( "org.apache.maven.*", coreRealm );
136         imports.put( "org.apache.maven.artifact", coreRealm );
137         imports.put( "org.apache.maven.classrealm", coreRealm );
138         imports.put( "org.apache.maven.cli", coreRealm );
139         imports.put( "org.apache.maven.configuration", coreRealm );
140         imports.put( "org.apache.maven.exception", coreRealm );
141         imports.put( "org.apache.maven.execution", coreRealm );
142         imports.put( "org.apache.maven.execution.scope", coreRealm );
143         imports.put( "org.apache.maven.lifecycle", coreRealm );
144         imports.put( "org.apache.maven.model", coreRealm );
145         imports.put( "org.apache.maven.monitor", coreRealm );
146         imports.put( "org.apache.maven.plugin", coreRealm );
147         imports.put( "org.apache.maven.profiles", coreRealm );
148         imports.put( "org.apache.maven.project", coreRealm );
149         imports.put( "org.apache.maven.reporting", coreRealm );
150         imports.put( "org.apache.maven.repository", coreRealm );
151         imports.put( "org.apache.maven.rtinfo", coreRealm );
152         imports.put( "org.apache.maven.settings", coreRealm );
153         imports.put( "org.apache.maven.toolchain", coreRealm );
154         imports.put( "org.apache.maven.usability", coreRealm );
155 
156         // wagon-api
157         imports.put( "org.apache.maven.wagon.*", coreRealm );
158         imports.put( "org.apache.maven.wagon.authentication", coreRealm );
159         imports.put( "org.apache.maven.wagon.authorization", coreRealm );
160         imports.put( "org.apache.maven.wagon.events", coreRealm );
161         imports.put( "org.apache.maven.wagon.observers", coreRealm );
162         imports.put( "org.apache.maven.wagon.proxy", coreRealm );
163         imports.put( "org.apache.maven.wagon.repository", coreRealm );
164         imports.put( "org.apache.maven.wagon.resource", coreRealm );
165 
166         // aether-api, aether-spi, aether-impl
167         imports.put( "org.eclipse.aether.*", coreRealm );
168         imports.put( "org.eclipse.aether.artifact", coreRealm );
169         imports.put( "org.eclipse.aether.collection", coreRealm );
170         imports.put( "org.eclipse.aether.deployment", coreRealm );
171         imports.put( "org.eclipse.aether.graph", coreRealm );
172         imports.put( "org.eclipse.aether.impl", coreRealm );
173         imports.put( "org.eclipse.aether.internal.impl", coreRealm );
174         imports.put( "org.eclipse.aether.installation", coreRealm );
175         imports.put( "org.eclipse.aether.metadata", coreRealm );
176         imports.put( "org.eclipse.aether.repository", coreRealm );
177         imports.put( "org.eclipse.aether.resolution", coreRealm );
178         imports.put( "org.eclipse.aether.spi", coreRealm );
179         imports.put( "org.eclipse.aether.transfer", coreRealm );
180         imports.put( "org.eclipse.aether.version", coreRealm );
181 
182         // plexus-classworlds
183         imports.put( "org.codehaus.plexus.classworlds", coreRealm );
184 
185         // classworlds (for legacy code)
186         imports.put( "org.codehaus.classworlds", coreRealm );
187 
188         // plexus-utils (for DOM-type fields in maven-model)
189         imports.put( "org.codehaus.plexus.util.xml.Xpp3Dom", coreRealm );
190         imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParser", coreRealm );
191         imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParserException", coreRealm );
192         imports.put( "org.codehaus.plexus.util.xml.pull.XmlSerializer", coreRealm );
193 
194         // plexus-container, plexus-component-annotations
195         imports.put( "org.codehaus.plexus.*", coreRealm );
196         imports.put( "org.codehaus.plexus.component", coreRealm );
197         imports.put( "org.codehaus.plexus.configuration", coreRealm );
198         imports.put( "org.codehaus.plexus.container", coreRealm );
199         imports.put( "org.codehaus.plexus.context", coreRealm );
200         imports.put( "org.codehaus.plexus.lifecycle", coreRealm );
201         imports.put( "org.codehaus.plexus.logging", coreRealm );
202         imports.put( "org.codehaus.plexus.personality", coreRealm );
203 
204         // javax.inject (JSR-330)
205         imports.put( "javax.inject.*", coreRealm );
206         // javax.enterprise.inject (JSR-299)
207         imports.put( "javax.enterprise.util.*", coreRealm );
208         imports.put( "javax.enterprise.inject.*", coreRealm );
209 
210         // com.google
211         //
212         // We may potentially want to export these, but right now I'm not sure that anything Guice specific needs
213         // to be made available to plugin authors. If we find people are getting fancy and want to take advantage
214         // of Guice specifics we can expose that later. Really some testing needs to be done to see full hiding
215         // of Guice has any impact on what we may categorize as a standard JSR-330 based Tesla/Maven plugin.
216         //
217         // imports.put( "com.google.inject.*", coreRealm );
218         // imports.put( "com.google.inject.binder.*", coreRealm );
219         // imports.put( "com.google.inject.matcher.*", coreRealm );
220         // imports.put( "com.google.inject.name.*", coreRealm );
221         // imports.put( "com.google.inject.spi.*", coreRealm );
222         // imports.put( "com.google.inject.util.*", coreRealm );
223 
224         // SLF4J
225         imports.put( "org.slf4j.*", coreRealm );
226     }
227 
228     /**
229      * Creates a new class realm with the specified parent and imports.
230      *
231      * @param baseRealmId The base id to use for the new realm, must not be {@code null}.
232      * @param type The type of the class realm, must not be {@code null}.
233      * @param parent The parent realm for the new realm, may be {@code null}.
234      * @param parentImports The packages/types to import from the parent realm, may be {@code null}.
235      * @param foreignImports The packages/types to import from foreign realms, may be {@code null}.
236      * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a
237      *            missing file) will automatically be excluded from the realm.
238      * @return The created class realm, never {@code null}.
239      */
240     private ClassRealm createRealm( String baseRealmId, RealmType type, ClassLoader parent, List<String> parentImports,
241                                     Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
242     {
243         Set<String> artifactIds = new LinkedHashSet<String>();
244 
245         List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>();
246 
247         if ( artifacts != null )
248         {
249             for ( Artifact artifact : artifacts )
250             {
251                 artifactIds.add( getId( artifact ) );
252                 if ( artifact.getFile() != null )
253                 {
254                     constituents.add( new ArtifactClassRealmConstituent( artifact ) );
255                 }
256             }
257         }
258 
259         if ( parentImports != null )
260         {
261             parentImports = new ArrayList<String>( parentImports );
262         }
263         else
264         {
265             parentImports = new ArrayList<String>();
266         }
267 
268         if ( foreignImports != null )
269         {
270             foreignImports = new TreeMap<String, ClassLoader>( foreignImports );
271         }
272         else
273         {
274             foreignImports = new TreeMap<String, ClassLoader>();
275         }
276 
277         ClassRealm classRealm = newRealm( baseRealmId );
278 
279         if ( parent != null )
280         {
281             classRealm.setParentClassLoader( parent );
282         }
283 
284         callDelegates( classRealm, type, parent, parentImports, foreignImports, constituents );
285 
286         wireRealm( classRealm, parentImports, foreignImports );
287 
288         Set<String> includedIds = populateRealm( classRealm, constituents );
289 
290         if ( logger.isDebugEnabled() )
291         {
292             artifactIds.removeAll( includedIds );
293 
294             for ( String id : artifactIds )
295             {
296                 logger.debug( "  Excluded: " + id );
297             }
298         }
299 
300         return classRealm;
301     }
302 
303     public ClassRealm getCoreRealm()
304     {
305         return container.getContainerRealm();
306     }
307 
308     public ClassRealm createProjectRealm( Model model, List<Artifact> artifacts )
309     {
310         if ( model == null )
311         {
312             throw new IllegalArgumentException( "model missing" );
313         }
314 
315         ClassLoader parent = getMavenApiRealm();
316 
317         return createRealm( getKey( model ), RealmType.Project, parent, null, null, artifacts );
318     }
319 
320     private static String getKey( Model model )
321     {
322         return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion();
323     }
324 
325     public ClassRealm createExtensionRealm( Plugin plugin, List<Artifact> artifacts )
326     {
327         if ( plugin == null )
328         {
329             throw new IllegalArgumentException( "extension plugin missing" );
330         }
331 
332         ClassLoader parent = ClassLoader.getSystemClassLoader();
333 
334         Map<String, ClassLoader> foreignImports =
335             Collections.<String, ClassLoader>singletonMap( "", getMavenApiRealm() );
336 
337         return createRealm( getKey( plugin, true ), RealmType.Extension, parent, null, foreignImports, artifacts );
338     }
339 
340     public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List<String> parentImports,
341                                          Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
342     {
343         if ( plugin == null )
344         {
345             throw new IllegalArgumentException( "plugin missing" );
346         }
347 
348         if ( parent == null )
349         {
350             parent = ClassLoader.getSystemClassLoader();
351         }
352 
353         return createRealm( getKey( plugin, false ), RealmType.Plugin, parent, parentImports, foreignImports, artifacts );
354     }
355 
356     private static String getKey( Plugin plugin, boolean extension )
357     {
358         String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() );
359         return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":"
360             + version;
361     }
362 
363     private static String getId( Artifact artifact )
364     {
365         return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(),
366                       artifact.getClassifier(), artifact.getBaseVersion() );
367     }
368 
369     private static String getId( ClassRealmConstituent constituent )
370     {
371         return getId( constituent.getGroupId(), constituent.getArtifactId(), constituent.getType(),
372                       constituent.getClassifier(), constituent.getVersion() );
373     }
374 
375     private static String getId( String gid, String aid, String type, String cls, String ver )
376     {
377         return gid + ':' + aid + ':' + type + ( StringUtils.isNotEmpty( cls ) ? ':' + cls : "" ) + ':' + ver;
378     }
379 
380     private List<ClassRealmManagerDelegate> getDelegates()
381     {
382         try
383         {
384             return container.lookupList( ClassRealmManagerDelegate.class );
385         }
386         catch ( ComponentLookupException e )
387         {
388             logger.error( "Failed to lookup class realm delegates: " + e.getMessage(), e );
389 
390             return Collections.emptyList();
391         }
392     }
393 
394     private void callDelegates( ClassRealm classRealm, RealmType type, ClassLoader parent, List<String> parentImports,
395                                 Map<String, ClassLoader> foreignImports, List<ClassRealmConstituent> constituents )
396     {
397         List<ClassRealmManagerDelegate> delegates = getDelegates();
398 
399         if ( !delegates.isEmpty() )
400         {
401             ClassRealmRequest request =
402                 new DefaultClassRealmRequest( type, parent, parentImports, foreignImports, constituents );
403 
404             for ( ClassRealmManagerDelegate delegate : delegates )
405             {
406                 try
407                 {
408                     delegate.setupRealm( classRealm, request );
409                 }
410                 catch ( Exception e )
411                 {
412                     logger.error( delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": "
413                         + e.getMessage(), e );
414                 }
415             }
416         }
417     }
418 
419     private Set<String> populateRealm( ClassRealm classRealm, List<ClassRealmConstituent> constituents )
420     {
421         Set<String> includedIds = new LinkedHashSet<String>();
422 
423         if ( logger.isDebugEnabled() )
424         {
425             logger.debug( "Populating class realm " + classRealm.getId() );
426         }
427 
428         for ( ClassRealmConstituent constituent : constituents )
429         {
430             File file = constituent.getFile();
431 
432             String id = getId( constituent );
433             includedIds.add( id );
434 
435             if ( logger.isDebugEnabled() )
436             {
437                 logger.debug( "  Included: " + id );
438             }
439 
440             try
441             {
442                 classRealm.addURL( file.toURI().toURL() );
443             }
444             catch ( MalformedURLException e )
445             {
446                 // Not going to happen
447                 logger.error( e.getMessage(), e );
448             }
449         }
450 
451         return includedIds;
452     }
453 
454     private void wireRealm( ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports )
455     {
456         if ( foreignImports != null && !foreignImports.isEmpty() )
457         {
458             if ( logger.isDebugEnabled() )
459             {
460                 logger.debug( "Importing foreign packages into class realm " + classRealm.getId() );
461             }
462 
463             for ( Map.Entry<String, ClassLoader> entry : foreignImports.entrySet() )
464             {
465                 ClassLoader importedRealm = entry.getValue();
466                 String imp = entry.getKey();
467 
468                 if ( logger.isDebugEnabled() )
469                 {
470                     logger.debug( "  Imported: " + imp + " < " + getId( importedRealm ) );
471                 }
472 
473                 classRealm.importFrom( importedRealm, imp );
474             }
475         }
476 
477         if ( parentImports != null && !parentImports.isEmpty() )
478         {
479             if ( logger.isDebugEnabled() )
480             {
481                 logger.debug( "Importing parent packages into class realm " + classRealm.getId() );
482             }
483 
484             for ( String imp : parentImports )
485             {
486                 if ( logger.isDebugEnabled() )
487                 {
488                     logger.debug( "  Imported: " + imp + " < " + getId( classRealm.getParentClassLoader() ) );
489                 }
490 
491                 classRealm.importFromParent( imp );
492             }
493         }
494     }
495 
496     private String getId( ClassLoader classLoader )
497     {
498         if ( classLoader instanceof ClassRealm )
499         {
500             return ( (ClassRealm) classLoader ).getId();
501         }
502         return String.valueOf( classLoader );
503     }
504 
505 }