All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mule.runtime.module.artifact.classloader.RegionClassLoader Maven / Gradle / Ivy

/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */

package org.mule.runtime.module.artifact.classloader;

import static java.lang.Integer.toHexString;
import static java.lang.String.format;
import static java.lang.System.identityHashCode;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang.ClassUtils.getPackageName;
import static org.mule.runtime.api.util.Preconditions.checkArgument;
import org.mule.runtime.module.artifact.classloader.exception.ClassNotFoundInRegionException;
import org.mule.runtime.module.artifact.descriptor.ArtifactDescriptor;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import sun.misc.CompoundEnumeration;

/**
 * Defines a classloader for a Mule artifact composed of other artifacts.
 * 

* Each artifact in the region can provide classes and resources to other artifacts. What is shared is defined on each artifact by * providing a {@link ArtifactClassLoaderFilter}. Lookup policy for each classloader added to the region must be aware of any * dependency between members of the region and updated accordingly. *

* For any member X of the region, if it has a dependency against another region member Y, then X must add all the exported * packages from Y as PARENT_FIRST. (to indicate that X wants to load those Y's packages) *

* For any member X of the region, if there is another region member Y that is not a dependency, then X must add all the exported * packages from Y as CHILD_ONLY. (to indicate that X does not want to load those Y's packages) *

* Only a region member can export a given package, but same resources can be exported by many members. The order in which the * resources are found will depend on the order in which the class loaders were added to the region. */ public class RegionClassLoader extends MuleDeployableArtifactClassLoader { protected static final String REGION_OWNER_CANNOT_BE_REMOVED_ERROR = "Region owner cannot be removed"; static { registerAsParallelCapable(); } private final List registeredClassLoaders = new ArrayList<>(); private final Map packageMapping = new HashMap<>(); private final Map> resourceMapping = new HashMap<>(); private ArtifactClassLoader ownerClassLoader; /** * Creates a new region. * * @param artifactId artifact unique ID for the artifact owning the created class loader instance. Non empty. * @param artifactDescriptor descriptor for the artifact owning the created class loader instance. Non null. * @param parent parent classloader for the region. Non null * @param lookupPolicy lookup policy to use on the region */ public RegionClassLoader(String artifactId, ArtifactDescriptor artifactDescriptor, ClassLoader parent, ClassLoaderLookupPolicy lookupPolicy) { super(artifactId, artifactDescriptor, new URL[0], parent, lookupPolicy, emptyList()); } @Override public List getArtifactPluginClassLoaders() { return registeredClassLoaders.stream().map(r -> r.unfilteredClassLoader).collect(toList()); } /** * Adds a class loader to the region. * * @param artifactClassLoader classloader to add. Non null. * @param filter filter used to provide access to the added classloader. Non null * @throws IllegalArgumentException if the class loader is already a region member. */ public synchronized void addClassLoader(ArtifactClassLoader artifactClassLoader, ArtifactClassLoaderFilter filter) { checkArgument(artifactClassLoader != null, "artifactClassLoader cannot be null"); checkArgument(filter != null, "filter cannot be null"); RegionMemberClassLoader registeredClassLoader = findRegisteredClassLoader(artifactClassLoader); if (artifactClassLoader == ownerClassLoader || registeredClassLoader != null) { throw new IllegalArgumentException(createClassLoaderAlreadyInRegionError(artifactClassLoader.getArtifactId())); } if (ownerClassLoader == null) { ownerClassLoader = artifactClassLoader; } else { registeredClassLoaders.add(new RegionMemberClassLoader(artifactClassLoader, filter)); } filter.getExportedClassPackages().forEach(p -> { LookupStrategy packageLookupStrategy = getClassLoaderLookupPolicy().getPackageLookupStrategy(p); if (!(packageLookupStrategy instanceof ChildFirstLookupStrategy)) { throw new IllegalStateException(illegalPackageMappingError(p, packageLookupStrategy)); } else if (packageMapping.containsKey(p)) { throw new IllegalStateException(duplicatePackageMappingError(p)); } else { packageMapping.put(p, artifactClassLoader); } }); for (String exportedResource : filter.getExportedResources()) { List classLoaders = resourceMapping.get(exportedResource); if (classLoaders == null) { classLoaders = new ArrayList<>(); resourceMapping.put(exportedResource, classLoaders); } classLoaders.add(artifactClassLoader); } } static String illegalPackageMappingError(String p, LookupStrategy packageLookupStrategy) { return format("Attempt to map package '%s' which was already defined on the region lookup policy with '%s'", p, packageLookupStrategy.getClass().getName()); } static String duplicatePackageMappingError(String packageName) { return "Attempt to redefine mapping for package: " + packageName; } private RegionMemberClassLoader findRegisteredClassLoader(ArtifactClassLoader artifactClassLoader) { for (RegionMemberClassLoader registeredClassLoader : registeredClassLoaders) { if (registeredClassLoader.unfilteredClassLoader == artifactClassLoader) { return registeredClassLoader; } } return null; } /** * Removes a class loader member from the region. *

* Only region members that do not export any package or resoruce can be removed from the region as they are not visible to * other members. * * @param artifactClassLoader class loader to remove. Non null * @return true if the class loader is a region member and was removed, false if it is not a region member. * @throws IllegalArgumentException if the class loader is the region owner or is a regiion member that exports packages or * resources. */ public synchronized boolean removeClassLoader(ArtifactClassLoader artifactClassLoader) { checkArgument(artifactClassLoader != null, "artifactClassLoader cannot be null"); if (ownerClassLoader == artifactClassLoader) { throw new IllegalArgumentException(REGION_OWNER_CANNOT_BE_REMOVED_ERROR); } RegionMemberClassLoader registeredClassLoader = findRegisteredClassLoader(artifactClassLoader); int index = registeredClassLoaders.indexOf(registeredClassLoader); if (index < 0) { return false; } if (!registeredClassLoader.filter.getExportedClassPackages().isEmpty() || !registeredClassLoader.filter.getExportedResources().isEmpty()) { throw new IllegalArgumentException(createCannotRemoveClassLoaderError(artifactClassLoader.getArtifactId())); } registeredClassLoaders.remove(index); return true; } @Override public Class findLocalClass(String name) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { final String packageName = getPackageName(name); final ArtifactClassLoader artifactClassLoader = packageMapping.get(packageName); if (artifactClassLoader != null) { try { return artifactClassLoader.findLocalClass(name); } catch (ClassNotFoundException e) { throw new ClassNotFoundInRegionException(name, getArtifactId(), artifactClassLoader.getArtifactId(), e); } } else { throw new ClassNotFoundInRegionException(name, getArtifactId()); } } } @Override public final URL findResource(final String name) { URL resource = null; final List artifactClassLoaders = resourceMapping.get(name); if (artifactClassLoaders != null) { for (ArtifactClassLoader artifactClassLoader : artifactClassLoaders) { resource = artifactClassLoader.getClassLoader().getResource(name); if (resource != null) { break; } } } return resource; } @Override public final Enumeration findResources(final String name) throws IOException { final List artifactClassLoaders = resourceMapping.get(name); List> enumerations = new ArrayList<>(registeredClassLoaders.size()); if (artifactClassLoaders != null) { for (ArtifactClassLoader artifactClassLoader : artifactClassLoaders) { final Enumeration partialResources = artifactClassLoader.findResources(name); if (partialResources.hasMoreElements()) { enumerations.add(partialResources); } } } return new CompoundEnumeration<>(enumerations.toArray(new Enumeration[0])); } @Override public void dispose() { registeredClassLoaders.stream().map(c -> c.unfilteredClassLoader).forEach(classLoader -> { disposeClassLoader(classLoader); }); registeredClassLoaders.clear(); disposeClassLoader(ownerClassLoader); super.dispose(); } private void disposeClassLoader(ArtifactClassLoader classLoader) { try { classLoader.dispose(); } catch (Exception e) { final String message = "Error disposing classloader for '{}'. This can cause a memory leak"; if (logger.isDebugEnabled()) { logger.debug(message, classLoader.getArtifactId(), e); } else { logger.error(message, classLoader.getArtifactId()); } } } @Override public URL findLocalResource(String resourceName) { URL resource = getOwnerClassLoader().findLocalResource(resourceName); if (resource == null && getParent() instanceof LocalResourceLocator) { resource = ((LocalResourceLocator) getParent()).findLocalResource(resourceName); } return resource; } private ArtifactClassLoader getOwnerClassLoader() { return ownerClassLoader; } @Override public String toString() { return format("%s[%s] -> %s@%s", getClass().getName(), getArtifactId(), packageMapping.toString(), toHexString(identityHashCode(this))); } static String createCannotRemoveClassLoaderError(String artifactId) { return format("Cannot remove classloader '%s' as it exports at least a package or resource", artifactId); } static String createClassLoaderAlreadyInRegionError(String artifactId) { return "Region already contains classloader for artifact:" + artifactId; } private static class RegionMemberClassLoader { final ArtifactClassLoader unfilteredClassLoader; final ArtifactClassLoaderFilter filter; private RegionMemberClassLoader(ArtifactClassLoader unfilteredClassLoader, ArtifactClassLoaderFilter filter) { this.unfilteredClassLoader = unfilteredClassLoader; this.filter = filter; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy