/* * 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.deployment.impl.internal.maven; import static java.lang.Boolean.getBoolean; import static java.lang.String.format; import static java.util.Collections.emptyMap; import static java.util.Optional.of; import static java.util.stream.Collectors.toSet; import static org.mule.maven.client.api.model.BundleScope.PROVIDED; import static org.mule.runtime.api.util.Preconditions.checkArgument; import static org.mule.runtime.container.api.MuleFoldersUtil.getMuleHomeFolder; import static org.mule.runtime.deployment.model.api.plugin.ArtifactPluginDescriptor.MULE_PLUGIN_CLASSIFIER; import static org.mule.runtime.deployment.model.api.plugin.MavenClassLoaderConstants.EXPORTED_PACKAGES; import static org.mule.runtime.deployment.model.api.plugin.MavenClassLoaderConstants.EXPORTED_RESOURCES; import static org.mule.runtime.deployment.model.api.plugin.MavenClassLoaderConstants.MAVEN; import static org.mule.runtime.module.reboot.MuleContainerBootstrapUtils.isStandalone; import org.mule.maven.client.api.LocalRepositorySupplierFactory; import org.mule.maven.client.api.MavenClient; import org.mule.runtime.api.deployment.meta.MuleArtifactLoaderDescriptor; import org.mule.runtime.api.exception.MuleRuntimeException; import org.mule.runtime.api.i18n.I18nMessageFactory; import org.mule.runtime.core.config.bootstrap.ArtifactType; import org.mule.runtime.deployment.model.api.plugin.MavenClassLoaderConstants; import org.mule.runtime.globalconfig.api.GlobalConfigLoader; import org.mule.runtime.module.artifact.descriptor.BundleDependency; import org.mule.runtime.module.artifact.descriptor.BundleDescriptor; import org.mule.runtime.module.artifact.descriptor.BundleScope; import org.mule.runtime.module.artifact.descriptor.ClassLoaderModel; import org.mule.runtime.module.artifact.descriptor.ClassLoaderModelLoader; import org.mule.runtime.module.artifact.descriptor.InvalidDescriptorLoaderException; import java.io.File; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Supplier; import org.apache.maven.model.Model; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract implementation of {@link ClassLoaderModelLoader} that resolves the dependencies for all the mule artifacts and create * the {@link ClassLoaderModel}. It lets the implementations of this class to add artifact's specific class loader URLs * * @since 4.0 */ public abstract class AbstractMavenClassLoaderModelLoader implements ClassLoaderModelLoader { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); private final LocalRepositorySupplierFactory localRepositorySupplierFactory; private MavenClient mavenClient; public AbstractMavenClassLoaderModelLoader(MavenClient mavenClient, LocalRepositorySupplierFactory localRepositorySupplierFactory) { this.mavenClient = mavenClient; this.localRepositorySupplierFactory = localRepositorySupplierFactory; } @Override public String getId() { return MAVEN; } /** * Given an artifact location, it will resolve its dependencies on a Maven based mechanism. It will assume there's a repository * folder to look for the artifacts in it (which includes both JAR files as well as POM ones). * <p/> * It takes care of the transitive compile and runtime dependencies, from which will take the URLs to add them to the resulting * {@link ClassLoaderModel}, and it will also consume all Mule plugin dependencies so that further validations can check whether * or not all plugins are loaded in memory before running an application. * <p/> * Finally, it will also tell the resulting {@link ClassLoaderModel} which packages and/or resources has to export, consuming * the attributes from the {@link MuleArtifactLoaderDescriptor#getAttributes()} map. * * @param artifactFile {@link File} where the current plugin to work with. * @param attributes a set of attributes to work with, where the current implementation of this class will look for * {@link MavenClassLoaderConstants#EXPORTED_PACKAGES} and {@link MavenClassLoaderConstants#EXPORTED_RESOURCES} * @return a {@link ClassLoaderModel} loaded with all its dependencies and URLs. */ @Override public final ClassLoaderModel load(File artifactFile, Map<String, Object> attributes, ArtifactType artifactType) throws InvalidDescriptorLoaderException { return createClassLoaderModel(artifactFile, attributes); } private ClassLoaderModel createClassLoaderModel(File artifactFile, Map<String, Object> attributes) throws InvalidDescriptorLoaderException { File containerRepository = null; if (isStandalone() && !getBoolean("mule.mode.embedded")) { containerRepository = new File(getMuleHomeFolder(), "repository"); if (!containerRepository.exists()) { if (!containerRepository.mkdirs()) { // check again since it may have been created already. if (!containerRepository.exists()) { throw new MuleRuntimeException(I18nMessageFactory .createStaticMessage("Failure creating repository folder in MULE_HOME folder " + containerRepository.getAbsolutePath())); } } } } File localMavenRepositoryLocation = GlobalConfigLoader.getMavenConfig().getLocalMavenRepositoryLocation(); Supplier<File> compositeRepoLocationSupplier = localRepositorySupplierFactory .composeSuppliers(localRepositorySupplierFactory.artifactFolderRepositorySupplier(artifactFile, localMavenRepositoryLocation), localRepositorySupplierFactory.fixedFolderSupplier(localMavenRepositoryLocation)); File mavenRepository = compositeRepoLocationSupplier.get(); List<org.mule.maven.client.api.model.BundleDependency> dependencies = mavenClient.resolveArtifactDependencies(artifactFile, enabledTestDependencies(), of(mavenRepository), of(new File(mavenRepository, ".mule"))); final ClassLoaderModel.ClassLoaderModelBuilder classLoaderModelBuilder = new ClassLoaderModel.ClassLoaderModelBuilder(); classLoaderModelBuilder .exportingPackages(new HashSet<>(getAttribute(attributes, EXPORTED_PACKAGES))) .exportingResources(new HashSet<>(getAttribute(attributes, EXPORTED_RESOURCES))); Set<BundleDependency> bundleDependencies = dependencies.stream().filter(mavenClientDependency -> !mavenClientDependency.getScope().equals(PROVIDED)) .map(mavenClientDependency -> convertBundleDependency(mavenClientDependency)).collect(toSet()); loadUrls(artifactFile, classLoaderModelBuilder, bundleDependencies); classLoaderModelBuilder.dependingOn(bundleDependencies); return classLoaderModelBuilder.build(); } protected BundleDependency convertBundleDependency(org.mule.maven.client.api.model.BundleDependency mavenClientDependency) { BundleDependency.Builder builder = new BundleDependency.Builder() .setScope(BundleScope.valueOf(mavenClientDependency.getScope().name())) .setBundleUrl(mavenClientDependency.getBundleUrl()) .setDescriptor(convertBundleDescriptor(mavenClientDependency.getDescriptor())); return builder.build(); } private BundleDescriptor convertBundleDescriptor(org.mule.maven.client.api.model.BundleDescriptor descriptor) { BundleDescriptor.Builder builder = new BundleDescriptor.Builder().setGroupId(descriptor.getGroupId()) .setArtifactId(descriptor.getArtifactId()) // Use baseVersion as it will refer to the unresolved meta version (case of SNAPSHOTS instead of timestamp versions) .setVersion(descriptor.getBaseVersion()) .setType(descriptor.getType()); descriptor.getClassifier().ifPresent(builder::setClassifier); return builder.build(); } /** * Loads the {@link ClassLoaderModel} from an artifact with the provided maven pom model. * * @param artifactFile the artifact folder * @return a {@link ClassLoaderModel} loaded with all its dependencies and URLs. * @throws InvalidDescriptorLoaderException */ public final ClassLoaderModel load(File artifactFile) throws InvalidDescriptorLoaderException { return createClassLoaderModel(artifactFile, emptyMap()); } /** * Template method to get the unresolved pom model from the artifact file * * @param artifactFile the artifact file * @return the pom model */ protected Model loadPomModel(File artifactFile) { return mavenClient.getRawPomModel(artifactFile); } /** * Loads the URLs of the class loader for this artifact. * <p> * It let's implementations to add artifact specific URLs by letting them override * {@link #addArtifactSpecificClassloaderConfiguration(File, ClassLoaderModel.ClassLoaderModelBuilder, Set)} * * @param artifactFile the artifact file for which the {@link ClassLoaderModel} is being generated. * @param classLoaderModelBuilder the builder of the {@link ClassLoaderModel} * @param dependencies the dependencies resolved for this artifact. */ protected void loadUrls(File artifactFile, ClassLoaderModel.ClassLoaderModelBuilder classLoaderModelBuilder, Set<BundleDependency> dependencies) { addArtifactSpecificClassloaderConfiguration(artifactFile, classLoaderModelBuilder, dependencies); addDependenciesToClasspathUrls(classLoaderModelBuilder, dependencies); } private void addDependenciesToClasspathUrls(ClassLoaderModel.ClassLoaderModelBuilder classLoaderModelBuilder, Set<BundleDependency> dependencies) { dependencies.stream() .filter(dependency -> !MULE_PLUGIN_CLASSIFIER.equals(dependency.getDescriptor().getClassifier().orElse(null))) .filter(dependency -> dependency.getBundleUrl() != null) .forEach(dependency -> { try { try { classLoaderModelBuilder.containing(dependency.getBundleUrl().toURI().toURL()); } catch (URISyntaxException e) { throw new MuleRuntimeException(e); } } catch (MalformedURLException e) { throw new MuleRuntimeException(e); } }); } private List<String> getAttribute(Map<String, Object> attributes, String attribute) { final Object attributeObject = attributes.getOrDefault(attribute, new ArrayList<String>()); checkArgument(attributeObject instanceof List, format("The '%s' attribute must be of '%s', found '%s'", attribute, List.class.getName(), attributeObject.getClass().getName())); return (List<String>) attributeObject; } /** * Template method to enable/disable test dependencies as part of the artifact classpath. * * @return true if test dependencies must be part of the artifact classpath, false otherwise. */ protected boolean enabledTestDependencies() { return false; } /** * Template method to add artifact specific configuration to the * {@link org.mule.runtime.module.artifact.descriptor.ClassLoaderModel.ClassLoaderModelBuilder} * * @param artifactFile the artifact file from which the classloader model is generated. * @param classLoaderModelBuilder the builder used to generate {@link ClassLoaderModel} of the artifact. * @param dependencies the set of dependencies of the artifact. */ protected void addArtifactSpecificClassloaderConfiguration(File artifactFile, ClassLoaderModel.ClassLoaderModelBuilder classLoaderModelBuilder, Set<BundleDependency> dependencies) { } }