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        // 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}