/*
* 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.deployment.model.internal;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.mule.runtime.api.util.Preconditions.checkArgument;
import static org.mule.runtime.api.util.Preconditions.checkState;
import static org.mule.runtime.module.artifact.classloader.ParentFirstLookupStrategy.PARENT_FIRST;
import org.mule.runtime.core.util.UUID;
import org.mule.runtime.deployment.model.api.plugin.ArtifactPluginDescriptor;
import org.mule.runtime.module.artifact.classloader.ArtifactClassLoader;
import org.mule.runtime.module.artifact.classloader.ArtifactClassLoaderFactory;
import org.mule.runtime.module.artifact.classloader.ArtifactClassLoaderFilter;
import org.mule.runtime.module.artifact.classloader.ChildFirstLookupStrategy;
import org.mule.runtime.module.artifact.classloader.ClassLoaderLookupPolicy;
import org.mule.runtime.module.artifact.classloader.DefaultArtifactClassLoaderFilter;
import org.mule.runtime.module.artifact.classloader.LookupStrategy;
import org.mule.runtime.module.artifact.classloader.RegionClassLoader;
import org.mule.runtime.module.artifact.descriptor.ArtifactDescriptor;
import org.mule.runtime.module.artifact.descriptor.ClassLoaderModel;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for all artifacts class loader filters.
*
* @param <T> the type of the filer.
* @since 4.0
*/
public abstract class AbstractArtifactClassLoaderBuilder<T extends AbstractArtifactClassLoaderBuilder> {
public static final String PLUGIN_CLASSLOADER_IDENTIFIER = "/plugin/";
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected final ArtifactClassLoaderFactory artifactPluginClassLoaderFactory;
private Set<ArtifactPluginDescriptor> artifactPluginDescriptors = new HashSet<>();
private String artifactId = UUID.getUUID();
protected ArtifactDescriptor artifactDescriptor;
private ArtifactClassLoader parentClassLoader;
protected List<ArtifactClassLoader> artifactPluginClassLoaders = new ArrayList<>();
/**
* Creates an {@link AbstractArtifactClassLoaderBuilder}.
*
* @param artifactPluginClassLoaderFactory factory to create class loaders for each used plugin. Non be not null.
*/
public AbstractArtifactClassLoaderBuilder(ArtifactClassLoaderFactory<ArtifactPluginDescriptor> artifactPluginClassLoaderFactory) {
checkArgument(artifactPluginClassLoaderFactory != null, "artifactPluginClassLoaderFactory cannot be null");
this.artifactPluginClassLoaderFactory = artifactPluginClassLoaderFactory;
}
/**
* Implementation must redefine this method and it should provide the root class loader which is going to be used as parent
* class loader for every other class loader created by this builder.
*
* @return the root class loader for all other class loaders
*/
protected abstract ArtifactClassLoader getParentClassLoader();
/**
* @param artifactId unique identifier for this artifact. For instance, for Applications, it can be the app name. Must be not
* null.
* @return the builder
*/
public T setArtifactId(String artifactId) {
checkArgument(artifactId != null, "artifact id cannot be null");
this.artifactId = artifactId;
return (T) this;
}
/**
* @param artifactPluginDescriptors set of plugins descriptors that will be used by the application.
* @return the builder
*/
public T addArtifactPluginDescriptors(ArtifactPluginDescriptor... artifactPluginDescriptors) {
checkArgument(artifactPluginDescriptors != null, "artifact plugin descriptors cannot be null");
this.artifactPluginDescriptors.addAll(asList(artifactPluginDescriptors));
return (T) this;
}
/**
* @param artifactDescriptor the descriptor of the artifact for which the class loader is going to be created.
* @return the builder
*/
public T setArtifactDescriptor(ArtifactDescriptor artifactDescriptor) {
this.artifactDescriptor = artifactDescriptor;
return (T) this;
}
/**
* Creates a new {@code ArtifactClassLoader} using the provided configuration. It will create the proper class loader hierarchy
* and filters the artifact resources and plugins classes and resources are resolve correctly.
*
* @return a {@code ArtifactClassLoader} created from the provided configuration.
* @throws IOException exception cause when it was not possible to access the file provided as dependencies
*/
public ArtifactClassLoader build() throws IOException {
checkState(artifactDescriptor != null, "artifact descriptor cannot be null");
parentClassLoader = getParentClassLoader();
checkState(parentClassLoader != null, "parent class loader cannot be null");
final String artifactId = getArtifactId(artifactDescriptor);
RegionClassLoader regionClassLoader =
new RegionClassLoader(artifactId, artifactDescriptor, parentClassLoader.getClassLoader(),
parentClassLoader.getClassLoaderLookupPolicy());
ArtifactClassLoaderFilter artifactClassLoaderFilter =
createArtifactClassLoaderFilter(artifactDescriptor.getClassLoaderModel(),
parentClassLoader.getClassLoaderLookupPolicy());
Map<String, LookupStrategy> appAdditionalLookupStrategy = new HashMap<>();
artifactClassLoaderFilter.getExportedClassPackages().stream().forEach(p -> appAdditionalLookupStrategy.put(p, PARENT_FIRST));
final List<ArtifactClassLoader> pluginClassLoaders =
createPluginClassLoaders(artifactId, regionClassLoader, artifactPluginDescriptors,
regionClassLoader.getClassLoaderLookupPolicy().extend(appAdditionalLookupStrategy));
final ArtifactClassLoader artifactClassLoader = createArtifactClassLoader(artifactId, regionClassLoader);
regionClassLoader.addClassLoader(artifactClassLoader, artifactClassLoaderFilter);
int artifactPluginIndex = 0;
for (ArtifactPluginDescriptor artifactPluginDescriptor : artifactPluginDescriptors) {
final ArtifactClassLoaderFilter classLoaderFilter =
createPluginClassLoaderFilter(artifactPluginDescriptor, artifactDescriptor.getClassLoaderModel().getExportedPackages(),
parentClassLoader.getClassLoaderLookupPolicy());
regionClassLoader.addClassLoader(pluginClassLoaders.get(artifactPluginIndex), classLoaderFilter);
artifactPluginIndex++;
}
return artifactClassLoader;
}
/**
* Creates the class loader for the artifact being built.
*
* @param artifactId identifies the artifact being created. Non empty.
* @param regionClassLoader class loader containing the artifact and dependant class loaders. Non null.
* @return
*/
protected abstract ArtifactClassLoader createArtifactClassLoader(String artifactId, RegionClassLoader regionClassLoader);
private ArtifactClassLoaderFilter createArtifactClassLoaderFilter(ClassLoaderModel classLoaderModel,
ClassLoaderLookupPolicy classLoaderLookupPolicy) {
Set<String> artifactExportedPackages = sanitizeExportedPackages(classLoaderLookupPolicy,
classLoaderModel.getExportedPackages());
return new DefaultArtifactClassLoaderFilter(artifactExportedPackages, classLoaderModel.getExportedResources());
}
private ArtifactClassLoaderFilter createPluginClassLoaderFilter(ArtifactPluginDescriptor pluginDescriptor,
Set<String> parentArtifactExportedPackages,
ClassLoaderLookupPolicy classLoaderLookupPolicy) {
Set<String> sanitizedArtifactExportedPackages =
sanitizeExportedPackages(classLoaderLookupPolicy, pluginDescriptor.getClassLoaderModel().getExportedPackages());
Set<String> replacedPackages =
parentArtifactExportedPackages.stream().filter(p -> sanitizedArtifactExportedPackages.contains(p)).collect(toSet());
if (!replacedPackages.isEmpty()) {
sanitizedArtifactExportedPackages.removeAll(replacedPackages);
logger.warn("Exported packages from plugin '" + pluginDescriptor.getName() + "' are provided by the artifact owner: "
+ replacedPackages);
}
return new DefaultArtifactClassLoaderFilter(sanitizedArtifactExportedPackages,
pluginDescriptor.getClassLoaderModel().getExportedResources());
}
private Set<String> sanitizeExportedPackages(ClassLoaderLookupPolicy classLoaderLookupPolicy,
Set<String> artifactExportedPackages) {
Set<String> sanitizedArtifactExportedPackages = new HashSet<>(artifactExportedPackages);
Set<String> containerProvidedPackages = sanitizedArtifactExportedPackages.stream().filter(p -> {
LookupStrategy lookupStrategy = classLoaderLookupPolicy.getPackageLookupStrategy(p);
return !(lookupStrategy instanceof ChildFirstLookupStrategy);
}).collect(toSet());
if (!containerProvidedPackages.isEmpty()) {
sanitizedArtifactExportedPackages.removeAll(containerProvidedPackages);
logger.warn("Exported packages from artifact '" + artifactDescriptor.getName() + "' are provided by parent class loader: "
+ containerProvidedPackages);
}
return sanitizedArtifactExportedPackages;
}
protected abstract String getArtifactId(ArtifactDescriptor artifactDescriptor);
private List<ArtifactClassLoader> createPluginClassLoaders(String artifactId, ArtifactClassLoader parent,
Set<ArtifactPluginDescriptor> artifactPluginDescriptors,
ClassLoaderLookupPolicy appExportedPackagesLookupPolicy) {
List<ArtifactClassLoader> classLoaders = new LinkedList<>();
for (ArtifactPluginDescriptor artifactPluginDescriptor : artifactPluginDescriptors) {
artifactPluginDescriptor.setArtifactPluginDescriptors(artifactPluginDescriptors);
final String pluginArtifactId = getArtifactPluginId(artifactId, artifactPluginDescriptor.getName());
final ArtifactClassLoader artifactClassLoader =
artifactPluginClassLoaderFactory.create(pluginArtifactId, artifactPluginDescriptor, parent.getClassLoader(),
appExportedPackagesLookupPolicy);
artifactPluginClassLoaders.add(artifactClassLoader);
classLoaders.add(artifactClassLoader);
}
return classLoaders;
}
/**
* @param parentArtifactId identifier of the artifact that owns the plugin. Non empty.
* @param pluginName name of the plugin. Non empty.
* @return the unique identifier for the plugin inside the parent artifact.
*/
public static String getArtifactPluginId(String parentArtifactId, String pluginName) {
checkArgument(!isEmpty(parentArtifactId), "parentArtifactId cannot be empty");
checkArgument(!isEmpty(pluginName), "pluginName cannot be empty");
return parentArtifactId + PLUGIN_CLASSLOADER_IDENTIFIER + pluginName;
}
}