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.sonatype.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.sonatype.aether.*", coreRealm );
167            imports.put( "org.sonatype.aether.artifact", coreRealm );
168            imports.put( "org.sonatype.aether.collection", coreRealm );
169            imports.put( "org.sonatype.aether.deployment", coreRealm );
170            imports.put( "org.sonatype.aether.graph", coreRealm );
171            imports.put( "org.sonatype.aether.impl", coreRealm );
172            imports.put( "org.sonatype.aether.installation", coreRealm );
173            imports.put( "org.sonatype.aether.metadata", coreRealm );
174            imports.put( "org.sonatype.aether.repository", coreRealm );
175            imports.put( "org.sonatype.aether.resolution", coreRealm );
176            imports.put( "org.sonatype.aether.spi", coreRealm );
177            imports.put( "org.sonatype.aether.transfer", coreRealm );
178            imports.put( "org.sonatype.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-container, plexus-component-annotations
187            imports.put( "org.codehaus.plexus.*", coreRealm );
188            imports.put( "org.codehaus.plexus.component", coreRealm );
189            imports.put( "org.codehaus.plexus.configuration", coreRealm );
190            imports.put( "org.codehaus.plexus.container", coreRealm );
191            imports.put( "org.codehaus.plexus.context", coreRealm );
192            imports.put( "org.codehaus.plexus.lifecycle", coreRealm );
193            imports.put( "org.codehaus.plexus.logging", coreRealm );
194            imports.put( "org.codehaus.plexus.personality", coreRealm );
195    
196            // plexus-utils (for maven-model)
197            imports.put( "org.codehaus.plexus.util.xml.Xpp3Dom", coreRealm );
198            imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParser", coreRealm );
199            imports.put( "org.codehaus.plexus.util.xml.pull.XmlPullParserException", coreRealm );
200            imports.put( "org.codehaus.plexus.util.xml.pull.XmlSerializer", coreRealm );
201        }
202    
203        /**
204         * Creates a new class realm with the specified parent and imports.
205         * 
206         * @param baseRealmId The base id to use for the new realm, must not be {@code null}.
207         * @param type The type of the class realm, must not be {@code null}.
208         * @param parent The parent realm for the new realm, may be {@code null}.
209         * @param parentImports The packages/types to import from the parent realm, may be {@code null}.
210         * @param foreignImports The packages/types to import from foreign realms, may be {@code null}.
211         * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a
212         *            missing file) will automatically be excluded from the realm.
213         * @return The created class realm, never {@code null}.
214         */
215        private ClassRealm createRealm( String baseRealmId, RealmType type, ClassLoader parent, List<String> parentImports,
216                                        Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
217        {
218            Set<String> artifactIds = new LinkedHashSet<String>();
219    
220            List<ClassRealmConstituent> constituents = new ArrayList<ClassRealmConstituent>();
221    
222            if ( artifacts != null )
223            {
224                for ( Artifact artifact : artifacts )
225                {
226                    artifactIds.add( getId( artifact ) );
227                    if ( artifact.getFile() != null )
228                    {
229                        constituents.add( new ArtifactClassRealmConstituent( artifact ) );
230                    }
231                }
232            }
233    
234            if ( parentImports != null )
235            {
236                parentImports = new ArrayList<String>( parentImports );
237            }
238            else
239            {
240                parentImports = new ArrayList<String>();
241            }
242    
243            if ( foreignImports != null )
244            {
245                foreignImports = new TreeMap<String, ClassLoader>( foreignImports );
246            }
247            else
248            {
249                foreignImports = new TreeMap<String, ClassLoader>();
250            }
251    
252            ClassRealm classRealm = newRealm( baseRealmId );
253    
254            if ( parent != null )
255            {
256                classRealm.setParentClassLoader( parent );
257            }
258    
259            callDelegates( classRealm, type, parent, parentImports, foreignImports, constituents );
260    
261            wireRealm( classRealm, parentImports, foreignImports );
262    
263            Set<String> includedIds = populateRealm( classRealm, constituents );
264    
265            if ( logger.isDebugEnabled() )
266            {
267                artifactIds.removeAll( includedIds );
268    
269                for ( String id : artifactIds )
270                {
271                    logger.debug( "  Excluded: " + id );
272                }
273            }
274    
275            return classRealm;
276        }
277    
278        public ClassRealm getCoreRealm()
279        {
280            return container.getContainerRealm();
281        }
282    
283        public ClassRealm createProjectRealm( Model model, List<Artifact> artifacts )
284        {
285            if ( model == null )
286            {
287                throw new IllegalArgumentException( "model missing" );
288            }
289    
290            ClassLoader parent = getMavenApiRealm();
291    
292            return createRealm( getKey( model ), RealmType.Project, parent, null, null, artifacts );
293        }
294    
295        private static String getKey( Model model )
296        {
297            return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion();
298        }
299    
300        public ClassRealm createExtensionRealm( Plugin plugin, List<Artifact> artifacts )
301        {
302            if ( plugin == null )
303            {
304                throw new IllegalArgumentException( "extension plugin missing" );
305            }
306    
307            ClassLoader parent = ClassLoader.getSystemClassLoader();
308    
309            Map<String, ClassLoader> foreignImports =
310                Collections.<String, ClassLoader> singletonMap( "", getMavenApiRealm() );
311    
312            return createRealm( getKey( plugin, true ), RealmType.Extension, parent, null, foreignImports, artifacts );
313        }
314    
315        public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List<String> parentImports,
316                                             Map<String, ClassLoader> foreignImports, List<Artifact> artifacts )
317        {
318            if ( plugin == null )
319            {
320                throw new IllegalArgumentException( "plugin missing" );
321            }
322    
323            if ( parent == null )
324            {
325                parent = ClassLoader.getSystemClassLoader();
326            }
327    
328            return createRealm( getKey( plugin, false ), RealmType.Plugin, parent, parentImports, foreignImports, artifacts );
329        }
330    
331        private static String getKey( Plugin plugin, boolean extension )
332        {
333            String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() );
334            return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":"
335                + version;
336        }
337    
338        private static String getId( Artifact artifact )
339        {
340            return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(),
341                          artifact.getClassifier(), artifact.getBaseVersion() );
342        }
343    
344        private static String getId( ClassRealmConstituent constituent )
345        {
346            return getId( constituent.getGroupId(), constituent.getArtifactId(), constituent.getType(),
347                          constituent.getClassifier(), constituent.getVersion() );
348        }
349    
350        private static String getId( String gid, String aid, String type, String cls, String ver )
351        {
352            return gid + ':' + aid + ':' + type + ( StringUtils.isNotEmpty( cls ) ? ':' + cls : "" ) + ':' + ver;
353        }
354    
355        private List<ClassRealmManagerDelegate> getDelegates()
356        {
357            try
358            {
359                return container.lookupList( ClassRealmManagerDelegate.class );
360            }
361            catch ( ComponentLookupException e )
362            {
363                logger.error( "Failed to lookup class realm delegates: " + e.getMessage(), e );
364    
365                return Collections.emptyList();
366            }
367        }
368    
369        private void callDelegates( ClassRealm classRealm, RealmType type, ClassLoader parent, List<String> parentImports,
370                                    Map<String, ClassLoader> foreignImports, List<ClassRealmConstituent> constituents )
371        {
372            List<ClassRealmManagerDelegate> delegates = getDelegates();
373    
374            if ( !delegates.isEmpty() )
375            {
376                ClassRealmRequest request =
377                    new DefaultClassRealmRequest( type, parent, parentImports, foreignImports, constituents );
378    
379                for ( ClassRealmManagerDelegate delegate : delegates )
380                {
381                    try
382                    {
383                        delegate.setupRealm( classRealm, request );
384                    }
385                    catch ( Exception e )
386                    {
387                        logger.error( delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": "
388                            + e.getMessage(), e );
389                    }
390                }
391            }
392        }
393    
394        private Set<String> populateRealm( ClassRealm classRealm, List<ClassRealmConstituent> constituents )
395        {
396            Set<String> includedIds = new LinkedHashSet<String>();
397    
398            if ( logger.isDebugEnabled() )
399            {
400                logger.debug( "Populating class realm " + classRealm.getId() );
401            }
402    
403            for ( ClassRealmConstituent constituent : constituents )
404            {
405                File file = constituent.getFile();
406    
407                String id = getId( constituent );
408                includedIds.add( id );
409    
410                if ( logger.isDebugEnabled() )
411                {
412                    logger.debug( "  Included: " + id );
413                }
414    
415                try
416                {
417                    classRealm.addURL( file.toURI().toURL() );
418                }
419                catch ( MalformedURLException e )
420                {
421                    // Not going to happen
422                    logger.error( e.getMessage(), e );
423                }
424            }
425    
426            return includedIds;
427        }
428    
429        private void wireRealm( ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports )
430        {
431            if ( foreignImports != null && !foreignImports.isEmpty() )
432            {
433                if ( logger.isDebugEnabled() )
434                {
435                    logger.debug( "Importing foreign packages into class realm " + classRealm.getId() );
436                }
437    
438                for ( Map.Entry<String, ClassLoader> entry : foreignImports.entrySet() )
439                {
440                    ClassLoader importedRealm = entry.getValue();
441                    String imp = entry.getKey();
442    
443                    if ( logger.isDebugEnabled() )
444                    {
445                        logger.debug( "  Imported: " + imp + " < " + getId( importedRealm ) );
446                    }
447    
448                    classRealm.importFrom( importedRealm, imp );
449                }
450            }
451    
452            if ( parentImports != null && !parentImports.isEmpty() )
453            {
454                if ( logger.isDebugEnabled() )
455                {
456                    logger.debug( "Importing parent packages into class realm " + classRealm.getId() );
457                }
458    
459                for ( String imp : parentImports )
460                {
461                    if ( logger.isDebugEnabled() )
462                    {
463                        logger.debug( "  Imported: " + imp + " < " + getId( classRealm.getParentClassLoader() ) );
464                    }
465    
466                    classRealm.importFromParent( imp );
467                }
468            }
469        }
470    
471        private String getId( ClassLoader classLoader )
472        {
473            if ( classLoader instanceof ClassRealm )
474            {
475                return ( (ClassRealm) classLoader ).getId();
476            }
477            return String.valueOf( classLoader );
478        }
479    
480    }