package org.apache.maven.classrealm; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.File; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.TreeMap; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.apache.maven.artifact.ArtifactUtils; import org.apache.maven.classrealm.ClassRealmRequest.RealmType; import org.apache.maven.extension.internal.CoreExportsProvider; import org.apache.maven.model.Model; import org.apache.maven.model.Plugin; import org.codehaus.plexus.MutablePlexusContainer; import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.classworlds.ClassWorld; import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.codehaus.plexus.classworlds.realm.DuplicateRealmException; import org.codehaus.plexus.logging.Logger; import org.codehaus.plexus.util.StringUtils; import org.eclipse.aether.artifact.Artifact; /** * Manages the class realms used by Maven. Warning: This is an internal utility class that is only * public for technical reasons, it is not part of the public API. In particular, this class can be changed or deleted * without prior notice. * * @author Benjamin Bentmann */ @Named @Singleton public class DefaultClassRealmManager implements ClassRealmManager { public static final String API_REALMID = "maven.api"; /** * During normal command line build, ClassWorld is loaded by jvm system classloader, which only includes * plexus-classworlds jar and possibly javaagent classes, see http://jira.codehaus.org/browse/MNG-4747. *

* Using ClassWorld to determine plugin/extensions realm parent classloaders gives m2e and integration test harness * flexibility to load multiple version of maven into dedicated classloaders without assuming state of jvm system * classloader. */ private static final ClassLoader PARENT_CLASSLOADER = ClassWorld.class.getClassLoader(); private final Logger logger; private final ClassWorld world; private final ClassRealm containerRealm; // this is a live injected collection private final List delegates; private final ClassRealm mavenApiRealm; /** * Patterns of artifacts provided by maven core and exported via maven api realm. These artifacts are filtered from * plugin and build extensions realms to avoid presence of duplicate and possibly conflicting classes on classpath. */ private final Set providedArtifacts; @Inject public DefaultClassRealmManager( Logger logger, PlexusContainer container, List delegates, CoreExportsProvider exports ) { this.logger = logger; this.world = ( (MutablePlexusContainer) container ).getClassWorld(); this.containerRealm = container.getContainerRealm(); this.delegates = delegates; Map foreignImports = exports.get().getExportedPackages(); this.mavenApiRealm = createRealm( API_REALMID, RealmType.Core, null /* parent */, null /* parentImports */, foreignImports, null /* artifacts */ ); this.providedArtifacts = exports.get().getExportedArtifacts(); } private ClassRealm newRealm( String id ) { synchronized ( world ) { String realmId = id; Random random = new Random(); while ( true ) { try { ClassRealm classRealm = world.newRealm( realmId, null ); if ( logger.isDebugEnabled() ) { logger.debug( "Created new class realm " + realmId ); } return classRealm; } catch ( DuplicateRealmException e ) { realmId = id + '-' + random.nextInt(); } } } } public ClassRealm getMavenApiRealm() { return mavenApiRealm; } /** * Creates a new class realm with the specified parent and imports. * * @param baseRealmId The base id to use for the new realm, must not be {@code null}. * @param type The type of the class realm, must not be {@code null}. * @param parent The parent realm for the new realm, may be {@code null}. * @param parentImports The packages/types to import from the parent realm, may be {@code null}. * @param foreignImports The packages/types to import from foreign realms, may be {@code null}. * @param artifacts The artifacts to add to the realm, may be {@code null}. Unresolved artifacts (i.e. with a * missing file) will automatically be excluded from the realm. * @return The created class realm, never {@code null}. */ private ClassRealm createRealm( String baseRealmId, RealmType type, ClassLoader parent, List parentImports, Map foreignImports, List artifacts ) { Set artifactIds = new LinkedHashSet(); List constituents = new ArrayList(); if ( artifacts != null ) { for ( Artifact artifact : artifacts ) { if ( !isProvidedArtifact( artifact ) ) { artifactIds.add( getId( artifact ) ); if ( artifact.getFile() != null ) { constituents.add( new ArtifactClassRealmConstituent( artifact ) ); } } } } if ( parentImports != null ) { parentImports = new ArrayList( parentImports ); } else { parentImports = new ArrayList(); } if ( foreignImports != null ) { foreignImports = new TreeMap( foreignImports ); } else { foreignImports = new TreeMap(); } ClassRealm classRealm = newRealm( baseRealmId ); if ( parent != null ) { classRealm.setParentClassLoader( parent ); } callDelegates( classRealm, type, parent, parentImports, foreignImports, constituents ); wireRealm( classRealm, parentImports, foreignImports ); Set includedIds = populateRealm( classRealm, constituents ); if ( logger.isDebugEnabled() ) { artifactIds.removeAll( includedIds ); for ( String id : artifactIds ) { logger.debug( " Excluded: " + id ); } } return classRealm; } public ClassRealm getCoreRealm() { return containerRealm; } public ClassRealm createProjectRealm( Model model, List artifacts ) { if ( model == null ) { throw new IllegalArgumentException( "model missing" ); } ClassLoader parent = getMavenApiRealm(); return createRealm( getKey( model ), RealmType.Project, parent, null, null, artifacts ); } private static String getKey( Model model ) { return "project>" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion(); } public ClassRealm createExtensionRealm( Plugin plugin, List artifacts ) { if ( plugin == null ) { throw new IllegalArgumentException( "extension plugin missing" ); } ClassLoader parent = PARENT_CLASSLOADER; Map foreignImports = Collections.singletonMap( "", getMavenApiRealm() ); return createRealm( getKey( plugin, true ), RealmType.Extension, parent, null, foreignImports, artifacts ); } private boolean isProvidedArtifact( Artifact artifact ) { return providedArtifacts.contains( artifact.getGroupId() + ":" + artifact.getArtifactId() ); } public ClassRealm createPluginRealm( Plugin plugin, ClassLoader parent, List parentImports, Map foreignImports, List artifacts ) { if ( plugin == null ) { throw new IllegalArgumentException( "plugin missing" ); } if ( parent == null ) { parent = PARENT_CLASSLOADER; } return createRealm( getKey( plugin, false ), RealmType.Plugin, parent, parentImports, foreignImports, artifacts ); } private static String getKey( Plugin plugin, boolean extension ) { String version = ArtifactUtils.toSnapshotVersion( plugin.getVersion() ); return ( extension ? "extension>" : "plugin>" ) + plugin.getGroupId() + ":" + plugin.getArtifactId() + ":" + version; } private static String getId( Artifact artifact ) { return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getExtension(), artifact.getClassifier(), artifact.getBaseVersion() ); } private static String getId( ClassRealmConstituent constituent ) { return getId( constituent.getGroupId(), constituent.getArtifactId(), constituent.getType(), constituent.getClassifier(), constituent.getVersion() ); } private static String getId( String gid, String aid, String type, String cls, String ver ) { return gid + ':' + aid + ':' + type + ( StringUtils.isNotEmpty( cls ) ? ':' + cls : "" ) + ':' + ver; } private void callDelegates( ClassRealm classRealm, RealmType type, ClassLoader parent, List parentImports, Map foreignImports, List constituents ) { List delegates = new ArrayList( this.delegates ); if ( !delegates.isEmpty() ) { ClassRealmRequest request = new DefaultClassRealmRequest( type, parent, parentImports, foreignImports, constituents ); for ( ClassRealmManagerDelegate delegate : delegates ) { try { delegate.setupRealm( classRealm, request ); } catch ( Exception e ) { logger.error( delegate.getClass().getName() + " failed to setup class realm " + classRealm + ": " + e.getMessage(), e ); } } } } private Set populateRealm( ClassRealm classRealm, List constituents ) { Set includedIds = new LinkedHashSet(); if ( logger.isDebugEnabled() ) { logger.debug( "Populating class realm " + classRealm.getId() ); } for ( ClassRealmConstituent constituent : constituents ) { File file = constituent.getFile(); String id = getId( constituent ); includedIds.add( id ); if ( logger.isDebugEnabled() ) { logger.debug( " Included: " + id ); } try { classRealm.addURL( file.toURI().toURL() ); } catch ( MalformedURLException e ) { // Not going to happen logger.error( e.getMessage(), e ); } } return includedIds; } private void wireRealm( ClassRealm classRealm, List parentImports, Map foreignImports ) { if ( foreignImports != null && !foreignImports.isEmpty() ) { if ( logger.isDebugEnabled() ) { logger.debug( "Importing foreign packages into class realm " + classRealm.getId() ); } for ( Map.Entry entry : foreignImports.entrySet() ) { ClassLoader importedRealm = entry.getValue(); String imp = entry.getKey(); if ( logger.isDebugEnabled() ) { logger.debug( " Imported: " + imp + " < " + getId( importedRealm ) ); } classRealm.importFrom( importedRealm, imp ); } } if ( parentImports != null && !parentImports.isEmpty() ) { if ( logger.isDebugEnabled() ) { logger.debug( "Importing parent packages into class realm " + classRealm.getId() ); } for ( String imp : parentImports ) { if ( logger.isDebugEnabled() ) { logger.debug( " Imported: " + imp + " < " + getId( classRealm.getParentClassLoader() ) ); } classRealm.importFromParent( imp ); } } } private String getId( ClassLoader classLoader ) { if ( classLoader instanceof ClassRealm ) { return ( (ClassRealm) classLoader ).getId(); } return String.valueOf( classLoader ); } }