001package org.apache.maven.classrealm;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
023import java.net.MalformedURLException;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.LinkedHashSet;
028import java.util.List;
029import java.util.Map;
030import java.util.Random;
031import java.util.Set;
032import java.util.TreeMap;
033
034import org.apache.maven.artifact.ArtifactUtils;
035import org.apache.maven.classrealm.ClassRealmRequest.RealmType;
036import org.apache.maven.model.Model;
037import org.apache.maven.model.Plugin;
038import org.codehaus.plexus.MutablePlexusContainer;
039import org.codehaus.plexus.PlexusContainer;
040import org.codehaus.plexus.classworlds.ClassWorld;
041import org.codehaus.plexus.classworlds.realm.ClassRealm;
042import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
043import org.codehaus.plexus.component.annotations.Component;
044import org.codehaus.plexus.component.annotations.Requirement;
045import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
046import org.codehaus.plexus.logging.Logger;
047import org.codehaus.plexus.util.StringUtils;
048import org.eclipse.aether.artifact.Artifact;
049
050/**
051 * Manages the class realms used by Maven. <strong>Warning:</strong> This is an internal utility class that is only
052 * public for technical reasons, it is not part of the public API. In particular, this class can be changed or deleted
053 * without prior notice.
054 * 
055 * @author Benjamin Bentmann
056 */
057@Component( role = ClassRealmManager.class )
058public class DefaultClassRealmManager
059    implements ClassRealmManager
060{
061
062    @Requirement
063    private Logger logger;
064
065    @Requirement
066    protected PlexusContainer container;
067
068    private ClassRealm mavenRealm;
069
070    private ClassWorld getClassWorld()
071    {
072        return ( (MutablePlexusContainer) container ).getClassWorld();
073    }
074
075    private ClassRealm newRealm( String id )
076    {
077        ClassWorld world = getClassWorld();
078
079        synchronized ( world )
080        {
081            String realmId = id;
082
083            Random random = new Random();
084
085            while ( true )
086            {
087                try
088                {
089                    ClassRealm classRealm = world.newRealm( realmId, null );
090
091                    if ( logger.isDebugEnabled() )
092                    {
093                        logger.debug( "Created new class realm " + realmId );
094                    }
095
096                    return classRealm;
097                }
098                catch ( DuplicateRealmException e )
099                {
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        imports.put( "javax.enterprise.inject.*", coreRealm );
207
208        // com.google
209        //
210        // We may potentially want to export these, but right now I'm not sure that anything Guice specific needs
211        // to be made available to plugin authors. If we find people are getting fancy and want to take advantage
212        // of Guice specifics we can expose that later. Really some testing needs to be done to see full hiding
213        // of Guice has any impact on what we may categorize as a standard JSR-330 based Tesla/Maven plugin.
214        //
215        // imports.put( "com.google.inject.*", coreRealm );
216        // imports.put( "com.google.inject.binder.*", coreRealm );
217        // imports.put( "com.google.inject.matcher.*", coreRealm );
218        // imports.put( "com.google.inject.name.*", coreRealm );
219        // imports.put( "com.google.inject.spi.*", coreRealm );
220        // imports.put( "com.google.inject.util.*", coreRealm );
221
222        // SLF4J
223        imports.put( "org.slf4j.*", coreRealm );
224    }
225
226    /**
227     * Creates a new class realm with the specified parent and imports.
228     * 
229     * @param baseRealmId The base id to use for the new realm, must not be {@code null}.
230     * @param type The type of the class realm, must not be {@code null}.
231     * @param parent The parent realm for the new realm, may be {@code null}.
232     * @param parentImports The packages/types to import from the parent realm, may be {@code null}.
233     * @param foreignImports The packages/types to import from foreign realms, may be {@code null}.
234     * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a
235     *            missing file) will automatically be excluded from the realm.
236     * @return The created class realm, never {@code null}.
237     */
238    private ClassRealm createRealm( String baseRealmId, RealmType type, ClassLoader parent, List<String> parentImports,
239                                    Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
240    {
241        Set<String> artifactIds = new LinkedHashSet<String>();
242
243        List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>();
244
245        if ( artifacts != null )
246        {
247            for ( Artifact artifact : artifacts )
248            {
249                artifactIds.add( getId( artifact ) );
250                if ( artifact.getFile() != null )
251                {
252                    constituents.add( new ArtifactClassRealmConstituent( artifact ) );
253                }
254            }
255        }
256
257        if ( parentImports != null )
258        {
259            parentImports = new ArrayList<String>( parentImports );
260        }
261        else
262        {
263            parentImports = new ArrayList<String>();
264        }
265
266        if ( foreignImports != null )
267        {
268            foreignImports = new TreeMap<String, ClassLoader>( foreignImports );
269        }
270        else
271        {
272            foreignImports = new TreeMap<String, ClassLoader>();
273        }
274
275        ClassRealm classRealm = newRealm( baseRealmId );
276
277        if ( parent != null )
278        {
279            classRealm.setParentClassLoader( parent );
280        }
281
282        callDelegates( classRealm, type, parent, parentImports, foreignImports, constituents );
283
284        wireRealm( classRealm, parentImports, foreignImports );
285
286        Set<String> includedIds = populateRealm( classRealm, constituents );
287
288        if ( logger.isDebugEnabled() )
289        {
290            artifactIds.removeAll( includedIds );
291
292            for ( String id : artifactIds )
293            {
294                logger.debug( "  Excluded: " + id );
295            }
296        }
297
298        return classRealm;
299    }
300
301    public ClassRealm getCoreRealm()
302    {
303        return container.getContainerRealm();
304    }
305
306    public ClassRealm createProjectRealm( Model model, List<Artifact> artifacts )
307    {
308        if ( model == null )
309        {
310            throw new IllegalArgumentException( "model missing" );
311        }
312
313        ClassLoader parent = getMavenApiRealm();
314
315        return createRealm( getKey( model ), RealmType.Project, parent, null, null, artifacts );
316    }
317
318    private static String getKey( Model model )
319    {
320        return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion();
321    }
322
323    public ClassRealm createExtensionRealm( Plugin plugin, List<Artifact> artifacts )
324    {
325        if ( plugin == null )
326        {
327            throw new IllegalArgumentException( "extension plugin missing" );
328        }
329
330        ClassLoader parent = ClassLoader.getSystemClassLoader();
331
332        Map<String, ClassLoader> foreignImports =
333            Collections.<String, ClassLoader> singletonMap( "", getMavenApiRealm() );
334
335        return createRealm( getKey( plugin, true ), RealmType.Extension, parent, null, foreignImports, artifacts );
336    }
337
338    public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List<String> parentImports,
339                                         Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
340    {
341        if ( plugin == null )
342        {
343            throw new IllegalArgumentException( "plugin missing" );
344        }
345
346        if ( parent == null )
347        {
348            parent = ClassLoader.getSystemClassLoader();
349        }
350
351        return createRealm( getKey( plugin, false ), RealmType.Plugin, parent, parentImports, foreignImports, artifacts );
352    }
353
354    private static String getKey( Plugin plugin, boolean extension )
355    {
356        String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() );
357        return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":"
358            + version;
359    }
360
361    private static String getId( Artifact artifact )
362    {
363        return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(),
364                      artifact.getClassifier(), artifact.getBaseVersion() );
365    }
366
367    private static String getId( ClassRealmConstituent constituent )
368    {
369        return getId( constituent.getGroupId(), constituent.getArtifactId(), constituent.getType(),
370                      constituent.getClassifier(), constituent.getVersion() );
371    }
372
373    private static String getId( String gid, String aid, String type, String cls, String ver )
374    {
375        return gid + ':' + aid + ':' + type + ( StringUtils.isNotEmpty( cls ) ? ':' + cls : "" ) + ':' + ver;
376    }
377
378    private List<ClassRealmManagerDelegate> getDelegates()
379    {
380        try
381        {
382            return container.lookupList( ClassRealmManagerDelegate.class );
383        }
384        catch ( ComponentLookupException e )
385        {
386            logger.error( "Failed to lookup class realm delegates: " + e.getMessage(), e );
387
388            return Collections.emptyList();
389        }
390    }
391
392    private void callDelegates( ClassRealm classRealm, RealmType type, ClassLoader parent, List<String> parentImports,
393                                Map<String, ClassLoader> foreignImports, List<ClassRealmConstituent> constituents )
394    {
395        List<ClassRealmManagerDelegate> delegates = getDelegates();
396
397        if ( !delegates.isEmpty() )
398        {
399            ClassRealmRequest request =
400                new DefaultClassRealmRequest( type, parent, parentImports, foreignImports, constituents );
401
402            for ( ClassRealmManagerDelegate delegate : delegates )
403            {
404                try
405                {
406                    delegate.setupRealm( classRealm, request );
407                }
408                catch ( Exception e )
409                {
410                    logger.error( delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": "
411                        + e.getMessage(), e );
412                }
413            }
414        }
415    }
416
417    private Set<String> populateRealm( ClassRealm classRealm, List<ClassRealmConstituent> constituents )
418    {
419        Set<String> includedIds = new LinkedHashSet<String>();
420
421        if ( logger.isDebugEnabled() )
422        {
423            logger.debug( "Populating class realm " + classRealm.getId() );
424        }
425
426        for ( ClassRealmConstituent constituent : constituents )
427        {
428            File file = constituent.getFile();
429
430            String id = getId( constituent );
431            includedIds.add( id );
432
433            if ( logger.isDebugEnabled() )
434            {
435                logger.debug( "  Included: " + id );
436            }
437
438            try
439            {
440                classRealm.addURL( file.toURI().toURL() );
441            }
442            catch ( MalformedURLException e )
443            {
444                // Not going to happen
445                logger.error( e.getMessage(), e );
446            }
447        }
448
449        return includedIds;
450    }
451
452    private void wireRealm( ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports )
453    {
454        if ( foreignImports != null && !foreignImports.isEmpty() )
455        {
456            if ( logger.isDebugEnabled() )
457            {
458                logger.debug( "Importing foreign packages into class realm " + classRealm.getId() );
459            }
460
461            for ( Map.Entry<String, ClassLoader> entry : foreignImports.entrySet() )
462            {
463                ClassLoader importedRealm = entry.getValue();
464                String imp = entry.getKey();
465
466                if ( logger.isDebugEnabled() )
467                {
468                    logger.debug( "  Imported: " + imp + " < " + getId( importedRealm ) );
469                }
470
471                classRealm.importFrom( importedRealm, imp );
472            }
473        }
474
475        if ( parentImports != null && !parentImports.isEmpty() )
476        {
477            if ( logger.isDebugEnabled() )
478            {
479                logger.debug( "Importing parent packages into class realm " + classRealm.getId() );
480            }
481
482            for ( String imp : parentImports )
483            {
484                if ( logger.isDebugEnabled() )
485                {
486                    logger.debug( "  Imported: " + imp + " < " + getId( classRealm.getParentClassLoader() ) );
487                }
488
489                classRealm.importFromParent( imp );
490            }
491        }
492    }
493
494    private String getId( ClassLoader classLoader )
495    {
496        if ( classLoader instanceof ClassRealm )
497        {
498            return ( (ClassRealm) classLoader ).getId();
499        }
500        return String.valueOf( classLoader );
501    }
502
503}