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