/* * 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.application; import static java.io.File.separator; import static java.lang.String.format; import static java.util.Collections.emptySet; import static java.util.stream.Collectors.toList; import static org.apache.commons.io.FileUtils.listFiles; import static org.apache.commons.io.FileUtils.toFile; import static org.apache.commons.lang.SystemUtils.LINE_SEPARATOR; import static org.mule.runtime.api.util.Preconditions.checkArgument; import static org.mule.runtime.core.config.bootstrap.ArtifactType.APP; import static org.mule.runtime.deployment.model.api.application.ApplicationDescriptor.DEFAULT_ARTIFACT_PROPERTIES_RESOURCE; import static org.mule.runtime.deployment.model.api.application.ApplicationDescriptor.DEFAULT_CONFIGURATION_RESOURCE; import static org.mule.runtime.deployment.model.api.application.ApplicationDescriptor.DEFAULT_CONFIGURATION_RESOURCE_LOCATION; import static org.mule.runtime.deployment.model.api.application.ApplicationDescriptor.MULE_APPLICATION_JSON; import static org.mule.runtime.deployment.model.api.plugin.ArtifactPluginDescriptor.MULE_ARTIFACT_FOLDER; import static org.mule.runtime.deployment.model.api.plugin.ArtifactPluginDescriptor.MULE_PLUGIN_CLASSIFIER; import static org.mule.runtime.module.artifact.descriptor.BundleScope.COMPILE; import static org.mule.runtime.module.deployment.impl.internal.artifact.ArtifactFactoryUtils.getDeploymentFile; import org.mule.runtime.api.deployment.meta.MuleApplicationModel; import org.mule.runtime.api.deployment.meta.MuleArtifactLoaderDescriptor; import org.mule.runtime.api.deployment.persistence.MuleApplicationModelJsonSerializer; import org.mule.runtime.api.meta.MuleVersion; import org.mule.runtime.container.api.MuleFoldersUtil; import org.mule.runtime.core.util.PropertiesUtils; import org.mule.runtime.deployment.model.api.application.ApplicationDescriptor; import org.mule.runtime.deployment.model.api.plugin.ArtifactPluginDescriptor; import org.mule.runtime.deployment.model.api.plugin.ArtifactPluginRepository; import org.mule.runtime.module.artifact.descriptor.ArtifactDescriptorCreateException; import org.mule.runtime.module.artifact.descriptor.ArtifactDescriptorFactory; import org.mule.runtime.module.artifact.descriptor.BundleDependency; import org.mule.runtime.module.artifact.descriptor.BundleDescriptor; import org.mule.runtime.module.artifact.descriptor.BundleDescriptorLoader; 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 org.mule.runtime.module.artifact.util.FileJarExplorer; import org.mule.runtime.module.artifact.util.JarExplorer; import org.mule.runtime.module.artifact.util.JarInfo; import org.mule.runtime.module.deployment.impl.internal.artifact.DescriptorLoaderRepository; import org.mule.runtime.module.deployment.impl.internal.artifact.LoaderNotFoundException; import org.mule.runtime.module.deployment.impl.internal.plugin.ArtifactPluginDescriptorLoader; import com.google.common.collect.ImmutableList; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Creates artifact descriptor for application */ public class ApplicationDescriptorFactory implements ArtifactDescriptorFactory<ApplicationDescriptor> { public static final String SYSTEM_PROPERTY_OVERRIDE = "-O"; private static final String UNKNOWN = "unknown"; private static final Logger logger = LoggerFactory.getLogger(ApplicationDescriptorFactory.class); private static final String MULE_CONFIG_FILES_FOLDER = "mule"; private final ArtifactPluginRepository applicationPluginRepository; private final ArtifactPluginDescriptorLoader artifactPluginDescriptorLoader; private final DescriptorLoaderRepository descriptorLoaderRepository; public ApplicationDescriptorFactory(ArtifactPluginDescriptorLoader artifactPluginDescriptorLoader, ArtifactPluginRepository applicationPluginRepository, DescriptorLoaderRepository descriptorLoaderRepository) { checkArgument(artifactPluginDescriptorLoader != null, "ApplicationPluginDescriptorFactory cannot be null"); checkArgument(applicationPluginRepository != null, "ApplicationPluginRepository cannot be null"); this.applicationPluginRepository = applicationPluginRepository; this.artifactPluginDescriptorLoader = artifactPluginDescriptorLoader; this.descriptorLoaderRepository = descriptorLoaderRepository; } public ApplicationDescriptor create(File artifactFolder) throws ArtifactDescriptorCreateException { ApplicationDescriptor applicationDescriptor; final File mulePluginJsonFile = new File(artifactFolder, MULE_ARTIFACT_FOLDER + separator + MULE_APPLICATION_JSON); if (mulePluginJsonFile.exists()) { applicationDescriptor = loadFromJsonDescriptor(artifactFolder, mulePluginJsonFile); } else { applicationDescriptor = createFromProperties(artifactFolder); } return applicationDescriptor; } protected static String invalidClassLoaderModelIdError(File pluginFolder, MuleArtifactLoaderDescriptor classLoaderModelLoaderDescriptor) { return format("The identifier '%s' for a class loader model descriptor is not supported (error found while reading plugin '%s')", classLoaderModelLoaderDescriptor.getId(), pluginFolder.getAbsolutePath()); } private BundleDescriptor getBundleDescriptor(File appFolder, MuleApplicationModel muleApplicationModel) { BundleDescriptorLoader bundleDescriptorLoader; try { bundleDescriptorLoader = descriptorLoaderRepository.get(muleApplicationModel.getBundleDescriptorLoader().getId(), APP, BundleDescriptorLoader.class); } catch (LoaderNotFoundException e) { throw new ArtifactDescriptorCreateException(invalidBundleDescriptorLoaderIdError(appFolder, muleApplicationModel .getBundleDescriptorLoader())); } try { return bundleDescriptorLoader.load(appFolder, muleApplicationModel.getBundleDescriptorLoader().getAttributes(), APP); } catch (InvalidDescriptorLoaderException e) { throw new ArtifactDescriptorCreateException(e); } } protected static String invalidBundleDescriptorLoaderIdError(File pluginFolder, MuleArtifactLoaderDescriptor bundleDescriptorLoader) { return format("The identifier '%s' for a bundle descriptor loader is not supported (error found while reading plugin '%s')", bundleDescriptorLoader.getId(), pluginFolder.getAbsolutePath()); } private ApplicationDescriptor loadFromJsonDescriptor(File applicationFolder, File muleApplicationJsonFile) { final MuleApplicationModel muleApplicationModel = getMuleApplicationJsonDescriber(muleApplicationJsonFile); final ApplicationDescriptor descriptor = new ApplicationDescriptor(applicationFolder.getName()); descriptor.setArtifactLocation(applicationFolder); descriptor.setRootFolder(applicationFolder); descriptor.setBundleDescriptor(getBundleDescriptor(applicationFolder, muleApplicationModel)); descriptor.setMinMuleVersion(new MuleVersion(muleApplicationModel.getMinMuleVersion())); muleApplicationModel.getDomain().ifPresent(domain -> { descriptor.setDomain(domain); }); List<String> muleApplicationModelConfigs = muleApplicationModel.getConfigs(); if (muleApplicationModelConfigs != null && !muleApplicationModelConfigs.isEmpty()) { descriptor.setConfigResources(muleApplicationModelConfigs.stream().map(configFile -> appendMuleFolder(configFile)) .collect(toList())); List<File> configFiles = descriptor.getConfigResources() .stream() .map(config -> new File(applicationFolder, config)).collect(toList()); descriptor.setConfigResourcesFile(configFiles.toArray(new File[configFiles.size()])); descriptor.setAbsoluteResourcePaths(configFiles.stream().map(configFile -> configFile.getAbsolutePath()).collect(toList()) .toArray(new String[configFiles.size()])); } else { File configFile = new File(applicationFolder, appendMuleFolder(DEFAULT_CONFIGURATION_RESOURCE)); descriptor.setConfigResourcesFile(new File[] {configFile}); descriptor.setConfigResources(ImmutableList.<String>builder().add(DEFAULT_CONFIGURATION_RESOURCE_LOCATION).build()); descriptor.setAbsoluteResourcePaths(new String[] {configFile.getAbsolutePath()}); } muleApplicationModel.getClassLoaderModelLoaderDescriptor().ifPresent(classLoaderModelLoaderDescriptor -> { ClassLoaderModel classLoaderModel = getClassLoaderModel(applicationFolder, classLoaderModelLoaderDescriptor); descriptor.setClassLoaderModel(classLoaderModel); try { descriptor.setPlugins(createArtifactPluginDescriptors(classLoaderModel)); } catch (IOException e) { throw new IllegalStateException(e); } }); File appClassesFolder = getAppClassesFolder(descriptor); // get a ref to an optional app props file (right next to the descriptor) setApplicationProperties(descriptor, new File(appClassesFolder, DEFAULT_ARTIFACT_PROPERTIES_RESOURCE)); return descriptor; } private String appendMuleFolder(String configFile) { return MULE_CONFIG_FILES_FOLDER + File.separator + configFile; } private MuleApplicationModel getMuleApplicationJsonDescriber(File jsonFile) { try (InputStream stream = new FileInputStream(jsonFile)) { return new MuleApplicationModelJsonSerializer().deserialize(IOUtils.toString(stream)); } catch (IOException e) { throw new IllegalArgumentException(format("Could not read extension describer on plugin '%s'", jsonFile.getAbsolutePath()), e); } } private ClassLoaderModel getClassLoaderModel(File applicationFolder, MuleArtifactLoaderDescriptor classLoaderModelLoaderDescriptor) { ClassLoaderModelLoader classLoaderModelLoader; try { classLoaderModelLoader = descriptorLoaderRepository.get(classLoaderModelLoaderDescriptor.getId(), APP, ClassLoaderModelLoader.class); } catch (LoaderNotFoundException e) { throw new ArtifactDescriptorCreateException(invalidClassLoaderModelIdError(applicationFolder, classLoaderModelLoaderDescriptor)); } final ClassLoaderModel classLoaderModel; try { classLoaderModel = classLoaderModelLoader.load(applicationFolder, classLoaderModelLoaderDescriptor.getAttributes(), APP); } catch (InvalidDescriptorLoaderException e) { throw new ArtifactDescriptorCreateException(e); } return classLoaderModel; } public ApplicationDescriptor createFromProperties(File artifactFolder) throws ArtifactDescriptorCreateException { if (!artifactFolder.exists()) { throw new IllegalArgumentException(format("Application directory does not exist: '%s'", artifactFolder)); } final String appName = artifactFolder.getName(); ApplicationDescriptor desc; try { final File deployPropertiesFile = getDeploymentFile(artifactFolder); if (deployPropertiesFile != null) { // lookup the implementation by extension final PropertiesDescriptorParser descriptorParser = new PropertiesDescriptorParser(); desc = descriptorParser.parse(artifactFolder, deployPropertiesFile, appName); } else { desc = new EmptyApplicationDescriptor(artifactFolder); } File appClassesFolder = getAppClassesFolder(desc); URL[] libraries = findLibraries(desc); URL[] sharedLibraries = findSharedLibraries(desc); // get a ref to an optional app props file (right next to the descriptor) setApplicationProperties(desc, new File(appClassesFolder, DEFAULT_ARTIFACT_PROPERTIES_RESOURCE)); List<URL> urls = getApplicationResourceUrls(appClassesFolder.toURI().toURL(), libraries, sharedLibraries); if (!urls.isEmpty() && logger.isInfoEnabled()) { logArtifactRuntimeUrls(appName, urls); } ClassLoaderModel.ClassLoaderModelBuilder classLoaderModelBuilder = new ClassLoaderModel.ClassLoaderModelBuilder(); for (URL url : urls) { classLoaderModelBuilder.containing(url); } JarInfo jarInfo = findApplicationResources(desc, sharedLibraries); classLoaderModelBuilder.exportingPackages(jarInfo.getPackages()) .exportingResources(jarInfo.getResources()); classLoaderModelBuilder.dependingOn(getPluginDependencies(artifactFolder)); ClassLoaderModel classLoaderModel = classLoaderModelBuilder.build(); desc.setClassLoaderModel(classLoaderModel); desc.setPlugins(createArtifactPluginDescriptors(classLoaderModel)); } catch (IOException e) { throw new ArtifactDescriptorCreateException("Unable to create application descriptor", e); } return desc; } private Set<BundleDependency> getPluginDependencies(File applicationFolder) throws MalformedURLException { Set<BundleDependency> plugins = new HashSet<>(); File pluginsFolders = new File(applicationFolder, "plugins"); if (pluginsFolders.exists()) { File[] files = pluginsFolders.listFiles(); if (!pluginsFolders.exists()) { return plugins; } BundleDescriptor bundleDescriptor = new BundleDescriptor.Builder().setArtifactId(UNKNOWN).setGroupId(UNKNOWN).setVersion(UNKNOWN) .setClassifier(MULE_PLUGIN_CLASSIFIER).build(); for (File file : files) { plugins.add(new BundleDependency.Builder().setBundleUrl(file.toURL()).setScope(COMPILE) .setDescriptor(bundleDescriptor) .build()); } } return plugins; } private Set<ArtifactPluginDescriptor> createArtifactPluginDescriptors(ClassLoaderModel classLoaderModel) throws IOException { Set<ArtifactPluginDescriptor> pluginDescriptors = new HashSet<>(); for (BundleDependency bundleDependency : classLoaderModel.getDependencies()) { if (bundleDependency.getDescriptor().isPlugin()) { File pluginFile = toFile(bundleDependency.getBundleUrl()); pluginDescriptors.add(artifactPluginDescriptorLoader.load(pluginFile)); } } return pluginDescriptors; } private URL[] findLibraries(ApplicationDescriptor descriptor) throws MalformedURLException { return findJars(getAppLibFolder(descriptor)).toArray(new URL[0]); } protected File getAppLibFolder(ApplicationDescriptor descriptor) { return MuleFoldersUtil.getAppLibFolder(descriptor.getName()); } private URL[] findSharedLibraries(ApplicationDescriptor descriptor) throws MalformedURLException { return findJars(getAppSharedLibsFolder(descriptor)).toArray(new URL[0]); } protected File getAppSharedLibsFolder(ApplicationDescriptor descriptor) { return MuleFoldersUtil.getAppSharedLibsFolder(descriptor.getName()); } private JarInfo findApplicationResources(ApplicationDescriptor descriptor, URL[] sharedLibraries) { final JarInfo librariesInfo = findExportedResources(sharedLibraries); final JarInfo classesInfo; try { final File appClassesFolder = getAppClassesFolder(descriptor); if (appClassesFolder.exists()) { classesInfo = findExportedResources(appClassesFolder.toURI().toURL()); } else { classesInfo = new JarInfo(emptySet(), emptySet()); } } catch (MalformedURLException e) { throw new RuntimeException("Cannot read application classes folder", e); } librariesInfo.getPackages().addAll(classesInfo.getPackages()); librariesInfo.getResources().addAll(classesInfo.getResources()); return librariesInfo; } protected File getAppClassesFolder(ApplicationDescriptor descriptor) { return MuleFoldersUtil.getAppClassesFolder(descriptor.getName()); } private JarInfo findExportedResources(URL... libraries) { Set<String> packages = new HashSet<>(); Set<String> resources = new HashSet<>(); final JarExplorer jarExplorer = new FileJarExplorer(); for (URL library : libraries) { final JarInfo jarInfo = jarExplorer.explore(library); packages.addAll(jarInfo.getPackages()); resources.addAll(jarInfo.getResources()); } return new JarInfo(packages, resources); } protected List<URL> findJars(File dir) throws MalformedURLException { List<URL> result = new LinkedList<>(); if (dir.exists() && dir.canRead()) { @SuppressWarnings("unchecked") Collection<File> jars = listFiles(dir, new String[] {"jar"}, false); for (File jar : jars) { result.add(jar.toURI().toURL()); } } return result; } private List<ArtifactPluginDescriptor> getAllApplicationPlugins(Set<ArtifactPluginDescriptor> plugins) { final List<ArtifactPluginDescriptor> result = new LinkedList<>(applicationPluginRepository.getContainerArtifactPluginDescriptors()); result.addAll(plugins); // Sorts plugins by name to ensure consistent deployment result.sort(new Comparator<ArtifactPluginDescriptor>() { @Override public int compare(ArtifactPluginDescriptor descriptor1, ArtifactPluginDescriptor descriptor2) { return descriptor1.getName().compareTo(descriptor2.getName()); } }); return result; } public void setApplicationProperties(ApplicationDescriptor desc, File appPropsFile) { // ugh, no straightforward way to convert a HashTable to a map Map<String, String> m = new HashMap<>(); if (appPropsFile.exists() && appPropsFile.canRead()) { final Properties props; try { props = PropertiesUtils.loadProperties(appPropsFile.toURI().toURL()); } catch (IOException e) { throw new IllegalArgumentException("Unable to obtain application properties file URL", e); } for (Object key : props.keySet()) { m.put(key.toString(), props.getProperty(key.toString())); } } // Override with any system properties prepended with "-O" for ("override")) Properties sysProps = System.getProperties(); for (Map.Entry<Object, Object> entry : sysProps.entrySet()) { String key = entry.getKey().toString(); if (key.startsWith(SYSTEM_PROPERTY_OVERRIDE)) { m.put(key.substring(SYSTEM_PROPERTY_OVERRIDE.length()), entry.getValue().toString()); } } desc.setAppProperties(m); } private List<URL> getApplicationResourceUrls(URL classesFolderUrl, URL[] libraries, URL[] sharedLibraries) { List<URL> urls = new LinkedList<>(); urls.add(classesFolderUrl); for (URL url : libraries) { urls.add(url); } for (URL url : sharedLibraries) { urls.add(url); } return urls; } private void logArtifactRuntimeUrls(String appName, List<URL> urls) { StringBuilder sb = new StringBuilder(); sb.append(String.format("[%s] Loading the following jars:%n", appName)); sb.append("=============================").append(LINE_SEPARATOR); for (URL url : urls) { sb.append(url).append(LINE_SEPARATOR); } sb.append("=============================").append(LINE_SEPARATOR); logger.info(sb.toString()); } }