| /* |
| * Copyright 2019 the original author or authors. |
| * |
| * Licensed 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 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.Objects; |
| 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.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; |
| import org.eclipse.sisu.Priority; |
| |
| /** |
| * This class is a copy of DefaultClassRealmManager with one modification: |
| * the {@link #PARENT_CLASSLOADER} is set to null instead of classworld's classloader. |
| * The reason is that mvnd is booted by {@link org.mvndaemon.mvnd.common.MavenDaemon} |
| * instead of {@link org.codehaus.plexus.classworlds.launcher.Launcher} and classworld |
| * is contained by the maven classloader. This can cause problems with extensions |
| * as their parent contains the whole maven classloader. |
| * See <a href="https://github.com/apache/maven-mvnd/issues/690">mvnd#690</a> |
| */ |
| @Named |
| @Singleton |
| @Priority(10) |
| public class MvndClassRealmManager |
| 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 = ClassLoader.getSystemClassLoader(); |
| |
| private final Logger logger; |
| |
| 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 MvndClassRealmManager(Logger logger, PlexusContainer container, |
| List<ClassRealmManagerDelegate> delegates, CoreExportsProvider exports) { |
| this.logger = logger; |
| this.world = ((MutablePlexusContainer) container).getClassWorld(); |
| this.containerRealm = container.getContainerRealm(); |
| this.delegates = delegates; |
| |
| Map<String, ClassLoader> foreignImports = exports.get().getExportedPackages(); |
| |
| this.mavenApiRealm = createRealm(API_REALMID, ClassRealmRequest.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, ClassRealmRequest.RealmType type, ClassLoader parent, |
| List<String> parentImports, |
| Map<String, ClassLoader> foreignImports, List<Artifact> artifacts) { |
| Set<String> artifactIds = new LinkedHashSet<>(); |
| |
| List<ClassRealmConstituent> 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<String> 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<Artifact> artifacts) { |
| Objects.requireNonNull(model, "model cannot be null"); |
| |
| ClassLoader parent = getMavenApiRealm(); |
| |
| return createRealm(getKey(model), ClassRealmRequest.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"); |
| |
| ClassLoader parent = PARENT_CLASSLOADER; |
| |
| Map<String, ClassLoader> foreignImports = Collections.<String, ClassLoader> singletonMap("", getMavenApiRealm()); |
| |
| return createRealm(getKey(plugin, true), ClassRealmRequest.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<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), ClassRealmRequest.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, ClassRealmRequest.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 Set<String> populateRealm(ClassRealm classRealm, List<ClassRealmConstituent> constituents) { |
| Set<String> 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<String> parentImports, Map<String, ClassLoader> foreignImports) { |
| if (foreignImports != null && !foreignImports.isEmpty()) { |
| if (logger.isDebugEnabled()) { |
| 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(); |
| |
| 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); |
| } |
| |
| } |