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.lifecycle", coreRealm );
143        imports.put( "org.apache.maven.model", coreRealm );
144        imports.put( "org.apache.maven.monitor", coreRealm );
145        imports.put( "org.apache.maven.plugin", coreRealm );
146        imports.put( "org.apache.maven.profiles", coreRealm );
147        imports.put( "org.apache.maven.project", coreRealm );
148        imports.put( "org.apache.maven.reporting", coreRealm );
149        imports.put( "org.apache.maven.repository", coreRealm );
150        imports.put( "org.apache.maven.rtinfo", coreRealm );
151        imports.put( "org.apache.maven.settings", coreRealm );
152        imports.put( "org.apache.maven.toolchain", coreRealm );
153        imports.put( "org.apache.maven.usability", coreRealm );
154
155        // wagon-api
156        imports.put( "org.apache.maven.wagon.*", coreRealm );
157        imports.put( "org.apache.maven.wagon.authentication", coreRealm );
158        imports.put( "org.apache.maven.wagon.authorization", coreRealm );
159        imports.put( "org.apache.maven.wagon.events", coreRealm );
160        imports.put( "org.apache.maven.wagon.observers", coreRealm );
161        imports.put( "org.apache.maven.wagon.proxy", coreRealm );
162        imports.put( "org.apache.maven.wagon.repository", coreRealm );
163        imports.put( "org.apache.maven.wagon.resource", coreRealm );
164
165        // aether-api, aether-spi, aether-impl
166        imports.put( "org.eclipse.aether.*", coreRealm );
167        imports.put( "org.eclipse.aether.artifact", coreRealm );
168        imports.put( "org.eclipse.aether.collection", coreRealm );
169        imports.put( "org.eclipse.aether.deployment", coreRealm );
170        imports.put( "org.eclipse.aether.graph", coreRealm );
171        imports.put( "org.eclipse.aether.impl", coreRealm );
172        imports.put( "org.eclipse.aether.internal.impl", coreRealm );
173        imports.put( "org.eclipse.aether.installation", coreRealm );
174        imports.put( "org.eclipse.aether.metadata", coreRealm );
175        imports.put( "org.eclipse.aether.repository", coreRealm );
176        imports.put( "org.eclipse.aether.resolution", coreRealm );
177        imports.put( "org.eclipse.aether.spi", coreRealm );
178        imports.put( "org.eclipse.aether.transfer", coreRealm );
179        imports.put( "org.eclipse.aether.version", coreRealm );
180
181        // plexus-classworlds
182        imports.put( "org.codehaus.plexus.classworlds", coreRealm );
183
184        // classworlds (for legacy code)
185        imports.put( "org.codehaus.classworlds", coreRealm );
186
187        // plexus-utils (for DOM-type fields in maven-model)
188        imports.put( "org.codehaus.plexus.util.xml.Xpp3Dom", coreRealm );
189        imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParser", coreRealm );
190        imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParserException", coreRealm );
191        imports.put( "org.codehaus.plexus.util.xml.pull.XmlSerializer", coreRealm );
192
193        // plexus-container, plexus-component-annotations
194        imports.put( "org.codehaus.plexus.*", coreRealm );
195        imports.put( "org.codehaus.plexus.component", coreRealm );
196        imports.put( "org.codehaus.plexus.configuration", coreRealm );
197        imports.put( "org.codehaus.plexus.container", coreRealm );
198        imports.put( "org.codehaus.plexus.context", coreRealm );
199        imports.put( "org.codehaus.plexus.lifecycle", coreRealm );
200        imports.put( "org.codehaus.plexus.logging", coreRealm );
201        imports.put( "org.codehaus.plexus.personality", coreRealm );
202
203        // javax.inject (JSR-330)
204        imports.put( "javax.inject.*", coreRealm );
205        imports.put( "javax.enterprise.inject.*", coreRealm );
206
207        // com.google
208        //
209        // We may potentially want to export these, but right now I'm not sure that anything Guice specific needs
210        // to be made available to plugin authors. If we find people are getting fancy and want to take advantage
211        // of Guice specifics we can expose that later. Really some testing needs to be done to see full hiding
212        // of Guice has any impact on what we may categorize as a standard JSR-330 based Tesla/Maven plugin.
213        //
214        // imports.put( "com.google.inject.*", coreRealm );
215        // imports.put( "com.google.inject.binder.*", coreRealm );
216        // imports.put( "com.google.inject.matcher.*", coreRealm );
217        // imports.put( "com.google.inject.name.*", coreRealm );
218        // imports.put( "com.google.inject.spi.*", coreRealm );
219        // imports.put( "com.google.inject.util.*", coreRealm );
220
221        // SLF4J
222        imports.put( "org.slf4j.*", coreRealm );
223    }
224
225    /**
226     * Creates a new class realm with the specified parent and imports.
227     * 
228     * @param baseRealmId The base id to use for the new realm, must not be {@code null}.
229     * @param type The type of the class realm, must not be {@code null}.
230     * @param parent The parent realm for the new realm, may be {@code null}.
231     * @param parentImports The packages/types to import from the parent realm, may be {@code null}.
232     * @param foreignImports The packages/types to import from foreign realms, may be {@code null}.
233     * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a
234     *            missing file) will automatically be excluded from the realm.
235     * @return The created class realm, never {@code null}.
236     */
237    private ClassRealm createRealm( String baseRealmId, RealmType type, ClassLoader parent, List<String> parentImports,
238                                    Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
239    {
240        Set<String> artifactIds = new LinkedHashSet<String>();
241
242        List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>();
243
244        if ( artifacts != null )
245        {
246            for ( Artifact artifact : artifacts )
247            {
248                artifactIds.add( getId( artifact ) );
249                if ( artifact.getFile() != null )
250                {
251                    constituents.add( new ArtifactClassRealmConstituent( artifact ) );
252                }
253            }
254        }
255
256        if ( parentImports != null )
257        {
258            parentImports = new ArrayList<String>( parentImports );
259        }
260        else
261        {
262            parentImports = new ArrayList<String>();
263        }
264
265        if ( foreignImports != null )
266        {
267            foreignImports = new TreeMap<String, ClassLoader>( foreignImports );
268        }
269        else
270        {
271            foreignImports = new TreeMap<String, ClassLoader>();
272        }
273
274        ClassRealm classRealm = newRealm( baseRealmId );
275
276        if ( parent != null )
277        {
278            classRealm.setParentClassLoader( parent );
279        }
280
281        callDelegates( classRealm, type, parent, parentImports, foreignImports, constituents );
282
283        wireRealm( classRealm, parentImports, foreignImports );
284
285        Set<String> includedIds = populateRealm( classRealm, constituents );
286
287        if ( logger.isDebugEnabled() )
288        {
289            artifactIds.removeAll( includedIds );
290
291            for ( String id : artifactIds )
292            {
293                logger.debug( "  Excluded: " + id );
294            }
295        }
296
297        return classRealm;
298    }
299
300    public ClassRealm getCoreRealm()
301    {
302        return container.getContainerRealm();
303    }
304
305    public ClassRealm createProjectRealm( Model model, List<Artifact> artifacts )
306    {
307        if ( model == null )
308        {
309            throw new IllegalArgumentException( "model missing" );
310        }
311
312        ClassLoader parent = getMavenApiRealm();
313
314        return createRealm( getKey( model ), RealmType.Project, parent, null, null, artifacts );
315    }
316
317    private static String getKey( Model model )
318    {
319        return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion();
320    }
321
322    public ClassRealm createExtensionRealm( Plugin plugin, List<Artifact> artifacts )
323    {
324        if ( plugin == null )
325        {
326            throw new IllegalArgumentException( "extension plugin missing" );
327        }
328
329        ClassLoader parent = ClassLoader.getSystemClassLoader();
330
331        Map<String, ClassLoader> foreignImports =
332            Collections.<String, ClassLoader> singletonMap( "", getMavenApiRealm() );
333
334        return createRealm( getKey( plugin, true ), RealmType.Extension, parent, null, foreignImports, artifacts );
335    }
336
337    public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List<String> parentImports,
338                                         Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
339    {
340        if ( plugin == null )
341        {
342            throw new IllegalArgumentException( "plugin missing" );
343        }
344
345        if ( parent == null )
346        {
347            parent = ClassLoader.getSystemClassLoader();
348        }
349
350        return createRealm( getKey( plugin, false ), RealmType.Plugin, parent, parentImports, foreignImports, artifacts );
351    }
352
353    private static String getKey( Plugin plugin, boolean extension )
354    {
355        String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() );
356        return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":"
357            + version;
358    }
359
360    private static String getId( Artifact artifact )
361    {
362        return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(),
363                      artifact.getClassifier(), artifact.getBaseVersion() );
364    }
365
366    private static String getId( ClassRealmConstituent constituent )
367    {
368        return getId( constituent.getGroupId(), constituent.getArtifactId(), constituent.getType(),
369                      constituent.getClassifier(), constituent.getVersion() );
370    }
371
372    private static String getId( String gid, String aid, String type, String cls, String ver )
373    {
374        return gid + ':' + aid + ':' + type + ( StringUtils.isNotEmpty( cls ) ? ':' + cls : "" ) + ':' + ver;
375    }
376
377    private List<ClassRealmManagerDelegate> getDelegates()
378    {
379        try
380        {
381            return container.lookupList( ClassRealmManagerDelegate.class );
382        }
383        catch ( ComponentLookupException e )
384        {
385            logger.error( "Failed to lookup class realm delegates: " + e.getMessage(), e );
386
387            return Collections.emptyList();
388        }
389    }
390
391    private void callDelegates( ClassRealm classRealm, RealmType type, ClassLoader parent, List<String> parentImports,
392                                Map<String, ClassLoader> foreignImports, List<ClassRealmConstituent> constituents )
393    {
394        List<ClassRealmManagerDelegate> delegates = getDelegates();
395
396        if ( !delegates.isEmpty() )
397        {
398            ClassRealmRequest request =
399                new DefaultClassRealmRequest( type, parent, parentImports, foreignImports, constituents );
400
401            for ( ClassRealmManagerDelegate delegate : delegates )
402            {
403                try
404                {
405                    delegate.setupRealm( classRealm, request );
406                }
407                catch ( Exception e )
408                {
409                    logger.error( delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": "
410                        + e.getMessage(), e );
411                }
412            }
413        }
414    }
415
416    private Set<String> populateRealm( ClassRealm classRealm, List<ClassRealmConstituent> constituents )
417    {
418        Set<String> includedIds = new LinkedHashSet<String>();
419
420        if ( logger.isDebugEnabled() )
421        {
422            logger.debug( "Populating class realm " + classRealm.getId() );
423        }
424
425        for ( ClassRealmConstituent constituent : constituents )
426        {
427            File file = constituent.getFile();
428
429            String id = getId( constituent );
430            includedIds.add( id );
431
432            if ( logger.isDebugEnabled() )
433            {
434                logger.debug( "  Included: " + id );
435            }
436
437            try
438            {
439                classRealm.addURL( file.toURI().toURL() );
440            }
441            catch ( MalformedURLException e )
442            {
443                // Not going to happen
444                logger.error( e.getMessage(), e );
445            }
446        }
447
448        return includedIds;
449    }
450
451    private void wireRealm( ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports )
452    {
453        if ( foreignImports != null && !foreignImports.isEmpty() )
454        {
455            if ( logger.isDebugEnabled() )
456            {
457                logger.debug( "Importing foreign packages into class realm " + classRealm.getId() );
458            }
459
460            for ( Map.Entry<String, ClassLoader> entry : foreignImports.entrySet() )
461            {
462                ClassLoader importedRealm = entry.getValue();
463                String imp = entry.getKey();
464
465                if ( logger.isDebugEnabled() )
466                {
467                    logger.debug( "  Imported: " + imp + " < " + getId( importedRealm ) );
468                }
469
470                classRealm.importFrom( importedRealm, imp );
471            }
472        }
473
474        if ( parentImports != null && !parentImports.isEmpty() )
475        {
476            if ( logger.isDebugEnabled() )
477            {
478                logger.debug( "Importing parent packages into class realm " + classRealm.getId() );
479            }
480
481            for ( String imp : parentImports )
482            {
483                if ( logger.isDebugEnabled() )
484                {
485                    logger.debug( "  Imported: " + imp + " < " + getId( classRealm.getParentClassLoader() ) );
486                }
487
488                classRealm.importFromParent( imp );
489            }
490        }
491    }
492
493    private String getId( ClassLoader classLoader )
494    {
495        if ( classLoader instanceof ClassRealm )
496        {
497            return ( (ClassRealm) classLoader ).getId();
498        }
499        return String.valueOf( classLoader );
500    }
501
502}