| /* |
| * 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. |
| */ |
| package org.apache.maven.classrealm; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.inject.Singleton; |
| |
| import java.io.File; |
| import java.net.MalformedURLException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.TreeMap; |
| |
| import org.apache.maven.artifact.ArtifactUtils; |
| import org.apache.maven.classrealm.ClassRealmRequest.RealmType; |
| import org.apache.maven.extension.internal.CoreExports; |
| 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.eclipse.aether.artifact.Artifact; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Manages the class realms used by Maven. <strong>Warning:</strong> 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. |
| * |
| */ |
| @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 https://issues.apache.org/jira/browse/MNG-4747. |
| * <p> |
| * 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 = LoggerFactory.getLogger(getClass()); |
| |
| private final ClassWorld world; |
| |
| private final ClassRealm containerRealm; |
| |
| // this is a live injected collection |
| private final List<ClassRealmManagerDelegate> 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<String> providedArtifacts; |
| |
| @Inject |
| public DefaultClassRealmManager( |
| PlexusContainer container, List<ClassRealmManagerDelegate> delegates, CoreExports exports) { |
| this.world = ((MutablePlexusContainer) container).getClassWorld(); |
| this.containerRealm = container.getContainerRealm(); |
| this.delegates = delegates; |
| |
| Map<String, ClassLoader> foreignImports = exports.getExportedPackages(); |
| |
| this.mavenApiRealm = createRealm( |
| API_REALMID, |
| RealmType.Core, |
| null /* parent */, |
| null /* parentImports */, |
| foreignImports, |
| null /* artifacts */); |
| |
| this.providedArtifacts = exports.getExportedArtifacts(); |
| } |
| |
| private ClassRealm newRealm(String id) { |
| synchronized (world) { |
| String realmId = id; |
| |
| Random random = new Random(); |
| |
| while (true) { |
| try { |
| ClassRealm classRealm = world.newRealm(realmId, null); |
| |
| 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<String> parentImports, |
| Map<String, ClassLoader> foreignImports, |
| List<Artifact> artifacts) { |
| List<ClassRealmConstituent> constituents = new ArrayList<>(artifacts == null ? 0 : artifacts.size()); |
| |
| if (artifacts != null && !artifacts.isEmpty()) { |
| for (Artifact artifact : artifacts) { |
| if (!isProvidedArtifact(artifact) && artifact.getFile() != null) { |
| constituents.add(new ArtifactClassRealmConstituent(artifact)); |
| } else if (logger.isDebugEnabled()) { |
| logger.debug(" Excluded: {}", getId(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); |
| |
| populateRealm(classRealm, constituents); |
| |
| return classRealm; |
| } |
| |
| public ClassRealm getCoreRealm() { |
| return containerRealm; |
| } |
| |
| public ClassRealm createProjectRealm(Model model, List<Artifact> artifacts) { |
| Objects.requireNonNull(model, "model cannot be null"); |
| |
| 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<Artifact> artifacts) { |
| Objects.requireNonNull(plugin, "plugin cannot be null"); |
| |
| Map<String, ClassLoader> foreignImports = Collections.singletonMap("", getMavenApiRealm()); |
| |
| return createRealm( |
| getKey(plugin, true), RealmType.Extension, PARENT_CLASSLOADER, null, foreignImports, artifacts); |
| } |
| |
| private boolean isProvidedArtifact(Artifact artifact) { |
| return providedArtifacts.contains(artifact.getGroupId() + ":" + artifact.getArtifactId()); |
| } |
| |
| public ClassRealm createPluginRealm( |
| Plugin plugin, |
| ClassLoader parent, |
| List<String> parentImports, |
| Map<String, ClassLoader> foreignImports, |
| List<Artifact> artifacts) { |
| Objects.requireNonNull(plugin, "plugin cannot be null"); |
| |
| 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 + ((cls != null && !cls.isEmpty()) ? ':' + cls : "") + ':' + ver; |
| } |
| |
| private void callDelegates( |
| ClassRealm classRealm, |
| RealmType type, |
| ClassLoader parent, |
| List<String> parentImports, |
| Map<String, ClassLoader> foreignImports, |
| List<ClassRealmConstituent> constituents) { |
| List<ClassRealmManagerDelegate> 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 void populateRealm(ClassRealm classRealm, List<ClassRealmConstituent> constituents) { |
| logger.debug("Populating class realm {}", classRealm.getId()); |
| |
| for (ClassRealmConstituent constituent : constituents) { |
| File file = constituent.getFile(); |
| |
| if (logger.isDebugEnabled()) { |
| String id = getId(constituent); |
| logger.debug(" Included: {}", id); |
| } |
| |
| try { |
| classRealm.addURL(file.toURI().toURL()); |
| } catch (MalformedURLException e) { |
| // Not going to happen |
| logger.error(e.getMessage(), e); |
| } |
| } |
| } |
| |
| private void wireRealm(ClassRealm classRealm, List<String> parentImports, Map<String, ClassLoader> foreignImports) { |
| if (foreignImports != null && !foreignImports.isEmpty()) { |
| logger.debug("Importing foreign packages into class realm {}", classRealm.getId()); |
| |
| for (Map.Entry<String, ClassLoader> entry : foreignImports.entrySet()) { |
| ClassLoader importedRealm = entry.getValue(); |
| String imp = entry.getKey(); |
| |
| logger.debug(" Imported: {} < {}", imp, getId(importedRealm)); |
| |
| classRealm.importFrom(importedRealm, imp); |
| } |
| } |
| |
| if (parentImports != null && !parentImports.isEmpty()) { |
| logger.debug("Importing parent packages into class realm {}", classRealm.getId()); |
| |
| for (String imp : parentImports) { |
| logger.debug(" Imported: {} < {}", imp, getId(classRealm.getParentClassLoader())); |
| |
| classRealm.importFromParent(imp); |
| } |
| } |
| } |
| |
| private static Object getId(ClassLoader classLoader) { |
| if (classLoader instanceof ClassRealm) { |
| return ((ClassRealm) classLoader).getId(); |
| } |
| return classLoader; |
| } |
| } |