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.Map;
29  import java.util.Random;
30  import java.util.Set;
31  import java.util.TreeMap;
32  
33  import javax.inject.Inject;
34  import javax.inject.Named;
35  import javax.inject.Singleton;
36  
37  import org.apache.maven.artifact.ArtifactUtils;
38  import org.apache.maven.classrealm.ClassRealmRequest.RealmType;
39  import org.apache.maven.extension.internal.CoreExportsProvider;
40  import org.apache.maven.model.Model;
41  import org.apache.maven.model.Plugin;
42  import org.codehaus.plexus.MutablePlexusContainer;
43  import org.codehaus.plexus.PlexusContainer;
44  import org.codehaus.plexus.classworlds.ClassWorld;
45  import org.codehaus.plexus.classworlds.realm.ClassRealm;
46  import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
47  import org.codehaus.plexus.logging.Logger;
48  import org.codehaus.plexus.util.StringUtils;
49  import org.eclipse.aether.artifact.Artifact;
50  
51  /**
52   * Manages the class realms used by Maven. <strong>Warning:</strong> This is an internal utility class that is only
53   * public for technical reasons, it is not part of the public API. In particular, this class can be changed or deleted
54   * without prior notice.
55   *
56   * @author Benjamin Bentmann
57   */
58  @Named
59  @Singleton
60  public class DefaultClassRealmManager
61      implements ClassRealmManager
62  {
63      public static final String API_REALMID = "maven.api";
64  
65      /**
66       * During normal command line build, ClassWorld is loaded by jvm system classloader, which only includes
67       * plexus-classworlds jar and possibly javaagent classes, see http://jira.codehaus.org/browse/MNG-4747.
68       * <p>
69       * Using ClassWorld to determine plugin/extensions realm parent classloaders gives m2e and integration test harness
70       * flexibility to load multiple version of maven into dedicated classloaders without assuming state of jvm system
71       * classloader.
72       */
73      private static final ClassLoader PARENT_CLASSLOADER = ClassWorld.class.getClassLoader();
74  
75      private final Logger logger;
76  
77      private final ClassWorld world;
78  
79      private final ClassRealm containerRealm;
80  
81      // this is a live injected collection
82      private final List<ClassRealmManagerDelegate> delegates;
83  
84      private final ClassRealm mavenApiRealm;
85  
86      /**
87       * Patterns of artifacts provided by maven core and exported via maven api realm. These artifacts are filtered from
88       * plugin and build extensions realms to avoid presence of duplicate and possibly conflicting classes on classpath.
89       */
90      private final Set<String> providedArtifacts;
91  
92      @Inject
93      public DefaultClassRealmManager( Logger logger, PlexusContainer container,
94                                       List<ClassRealmManagerDelegate> delegates, CoreExportsProvider exports )
95      {
96          this.logger = logger;
97          this.world = ( (MutablePlexusContainer) container ).getClassWorld();
98          this.containerRealm = container.getContainerRealm();
99          this.delegates = delegates;
100 
101         Map<String, ClassLoader> foreignImports = exports.get().getExportedPackages();
102 
103         this.mavenApiRealm =
104             createRealm( API_REALMID, RealmType.Core, null /* parent */, null /* parentImports */, 
105                          foreignImports, null /* artifacts */ );
106 
107         this.providedArtifacts = exports.get().getExportedArtifacts();
108     }
109 
110     private ClassRealm newRealm( String id )
111     {
112         synchronized ( world )
113         {
114             String realmId = id;
115 
116             Random random = new Random();
117 
118             while ( true )
119             {
120                 try
121                 {
122                     ClassRealm classRealm = world.newRealm( realmId, null );
123 
124                     if ( logger.isDebugEnabled() )
125                     {
126                         logger.debug( "Created new class realm " + realmId );
127                     }
128 
129                     return classRealm;
130                 }
131                 catch ( DuplicateRealmException e )
132                 {
133                     realmId = id + '-' + random.nextInt();
134                 }
135             }
136         }
137     }
138 
139     public ClassRealm getMavenApiRealm()
140     {
141         return mavenApiRealm;
142     }
143 
144     /**
145      * Creates a new class realm with the specified parent and imports.
146      *
147      * @param baseRealmId The base id to use for the new realm, must not be {@code null}.
148      * @param type The type of the class realm, must not be {@code null}.
149      * @param parent The parent realm for the new realm, may be {@code null}.
150      * @param parentImports The packages/types to import from the parent realm, may be {@code null}.
151      * @param foreignImports The packages/types to import from foreign realms, may be {@code null}.
152      * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a
153      *            missing file) will automatically be excluded from the realm.
154      * @return The created class realm, never {@code null}.
155      */
156     private ClassRealm createRealm( String baseRealmId, RealmType type, ClassLoader parent, List<String> parentImports,
157                                     Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
158     {
159         Set<String> artifactIds = new LinkedHashSet<String>();
160 
161         List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>();
162 
163         if ( artifacts != null )
164         {
165             for ( Artifact artifact : artifacts )
166             {
167                 if ( !isProvidedArtifact( artifact ) )
168                 {
169                     artifactIds.add( getId( artifact ) );
170                     if ( artifact.getFile() != null )
171                     {
172                         constituents.add( new ArtifactClassRealmConstituent( artifact ) );
173                     }
174                 }
175             }
176         }
177 
178         if ( parentImports != null )
179         {
180             parentImports = new ArrayList<String>( parentImports );
181         }
182         else
183         {
184             parentImports = new ArrayList<String>();
185         }
186 
187         if ( foreignImports != null )
188         {
189             foreignImports = new TreeMap<String, ClassLoader>( foreignImports );
190         }
191         else
192         {
193             foreignImports = new TreeMap<String, ClassLoader>();
194         }
195 
196         ClassRealm classRealm = newRealm( baseRealmId );
197 
198         if ( parent != null )
199         {
200             classRealm.setParentClassLoader( parent );
201         }
202 
203         callDelegates( classRealm, type, parent, parentImports, foreignImports, constituents );
204 
205         wireRealm( classRealm, parentImports, foreignImports );
206 
207         Set<String> includedIds = populateRealm( classRealm, constituents );
208 
209         if ( logger.isDebugEnabled() )
210         {
211             artifactIds.removeAll( includedIds );
212 
213             for ( String id : artifactIds )
214             {
215                 logger.debug( "  Excluded: " + id );
216             }
217         }
218 
219         return classRealm;
220     }
221 
222     public ClassRealm getCoreRealm()
223     {
224         return containerRealm;
225     }
226 
227     public ClassRealm createProjectRealm( Model model, List<Artifact> artifacts )
228     {
229         if ( model == null )
230         {
231             throw new IllegalArgumentException( "model missing" );
232         }
233 
234         ClassLoader parent = getMavenApiRealm();
235 
236         return createRealm( getKey( model ), RealmType.Project, parent, null, null, artifacts );
237     }
238 
239     private static String getKey( Model model )
240     {
241         return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion();
242     }
243 
244     public ClassRealm createExtensionRealm( Plugin plugin, List<Artifact> artifacts )
245     {
246         if ( plugin == null )
247         {
248             throw new IllegalArgumentException( "extension plugin missing" );
249         }
250 
251         ClassLoader parent = PARENT_CLASSLOADER;
252 
253         Map<String, ClassLoader> foreignImports =
254             Collections.<String, ClassLoader>singletonMap( "", getMavenApiRealm() );
255 
256         return createRealm( getKey( plugin, true ), RealmType.Extension, parent, null, foreignImports, artifacts );
257     }
258 
259     private boolean isProvidedArtifact( Artifact artifact )
260     {
261         return providedArtifacts.contains( artifact.getGroupId() + ":" + artifact.getArtifactId() );
262     }
263 
264     public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List<String> parentImports,
265                                          Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
266     {
267         if ( plugin == null )
268         {
269             throw new IllegalArgumentException( "plugin missing" );
270         }
271 
272         if ( parent == null )
273         {
274             parent = PARENT_CLASSLOADER;
275         }
276 
277         return createRealm( getKey( plugin, false ), RealmType.Plugin, parent, parentImports, foreignImports,
278                             artifacts );
279     }
280 
281     private static String getKey( Plugin plugin, boolean extension )
282     {
283         String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() );
284         return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":"
285             + version;
286     }
287 
288     private static String getId( Artifact artifact )
289     {
290         return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(),
291                       artifact.getClassifier(), artifact.getBaseVersion() );
292     }
293 
294     private static String getId( ClassRealmConstituent constituent )
295     {
296         return getId( constituent.getGroupId(), constituent.getArtifactId(), constituent.getType(),
297                       constituent.getClassifier(), constituent.getVersion() );
298     }
299 
300     private static String getId( String gid, String aid, String type, String cls, String ver )
301     {
302         return gid + ':' + aid + ':' + type + ( StringUtils.isNotEmpty( cls ) ? ':' + cls : "" ) + ':' + ver;
303     }
304 
305     private void callDelegates( ClassRealm classRealm, RealmType type, ClassLoader parent, List<String> parentImports,
306                                 Map<String, ClassLoader> foreignImports, List<ClassRealmConstituent> constituents )
307     {
308         List<ClassRealmManagerDelegate> delegates = new ArrayList<ClassRealmManagerDelegate>( this.delegates );
309 
310         if ( !delegates.isEmpty() )
311         {
312             ClassRealmRequest request =
313                 new DefaultClassRealmRequest( type, parent, parentImports, foreignImports, constituents );
314 
315             for ( ClassRealmManagerDelegate delegate : delegates )
316             {
317                 try
318                 {
319                     delegate.setupRealm( classRealm, request );
320                 }
321                 catch ( Exception e )
322                 {
323                     logger.error( delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": "
324                         + e.getMessage(), e );
325                 }
326             }
327         }
328     }
329 
330     private Set<String> populateRealm( ClassRealm classRealm, List<ClassRealmConstituent> constituents )
331     {
332         Set<String> includedIds = new LinkedHashSet<String>();
333 
334         if ( logger.isDebugEnabled() )
335         {
336             logger.debug( "Populating class realm " + classRealm.getId() );
337         }
338 
339         for ( ClassRealmConstituent constituent : constituents )
340         {
341             File file = constituent.getFile();
342 
343             String id = getId( constituent );
344             includedIds.add( id );
345 
346             if ( logger.isDebugEnabled() )
347             {
348                 logger.debug( "  Included: " + id );
349             }
350 
351             try
352             {
353                 classRealm.addURL( file.toURI().toURL() );
354             }
355             catch ( MalformedURLException e )
356             {
357                 // Not going to happen
358                 logger.error( e.getMessage(), e );
359             }
360         }
361 
362         return includedIds;
363     }
364 
365     private void wireRealm( ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports )
366     {
367         if ( foreignImports != null && !foreignImports.isEmpty() )
368         {
369             if ( logger.isDebugEnabled() )
370             {
371                 logger.debug( "Importing foreign packages into class realm " + classRealm.getId() );
372             }
373 
374             for ( Map.Entry<String, ClassLoader> entry : foreignImports.entrySet() )
375             {
376                 ClassLoader importedRealm = entry.getValue();
377                 String imp = entry.getKey();
378 
379                 if ( logger.isDebugEnabled() )
380                 {
381                     logger.debug( "  Imported: " + imp + " < " + getId( importedRealm ) );
382                 }
383 
384                 classRealm.importFrom( importedRealm, imp );
385             }
386         }
387 
388         if ( parentImports != null && !parentImports.isEmpty() )
389         {
390             if ( logger.isDebugEnabled() )
391             {
392                 logger.debug( "Importing parent packages into class realm " + classRealm.getId() );
393             }
394 
395             for ( String imp : parentImports )
396             {
397                 if ( logger.isDebugEnabled() )
398                 {
399                     logger.debug( "  Imported: " + imp + " < " + getId( classRealm.getParentClassLoader() ) );
400                 }
401 
402                 classRealm.importFromParent( imp );
403             }
404         }
405     }
406 
407     private String getId( ClassLoader classLoader )
408     {
409         if ( classLoader instanceof ClassRealm )
410         {
411             return ( (ClassRealm) classLoader ).getId();
412         }
413         return String.valueOf( classLoader );
414     }
415 
416 }