/* * * Copyright 2013 Netflix, Inc. * * 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 com.netflix.nicobar.core.module.jboss; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.jar.JarFile; import javax.annotation.Nullable; import org.jboss.modules.DependencySpec; import org.jboss.modules.Module; import org.jboss.modules.ModuleIdentifier; import org.jboss.modules.ModuleLoadException; import org.jboss.modules.ModuleSpec; import org.jboss.modules.ResourceLoader; import org.jboss.modules.ResourceLoaderSpec; import org.jboss.modules.ResourceLoaders; import org.jboss.modules.filter.MultiplePathFilterBuilder; import org.jboss.modules.filter.PathFilter; import org.jboss.modules.filter.PathFilters; import com.netflix.nicobar.core.archive.ModuleId; import com.netflix.nicobar.core.archive.ScriptArchive; import com.netflix.nicobar.core.archive.ScriptModuleSpec; import com.netflix.nicobar.core.plugin.ScriptCompilerPluginSpec; import com.netflix.nicobar.core.utils.ClassPathUtils; /** * Utility methods for working with {@link Module}s * * @author James Kojo * @author Vasanth Asokan * @author Aaron Tull */ public class JBossModuleUtils { /** Dependency specification which allows for importing the core library classes */ public static final DependencySpec NICOBAR_CORE_DEPENDENCY_SPEC; /** Dependency specification which allows for importing the core JRE classes */ public static final DependencySpec JRE_DEPENDENCY_SPEC; static { // TODO: find a maintainable way to get these values and a better place to store these constants Set<String> pathFilter = new HashSet<String>(); pathFilter.add("com/netflix/nicobar/core"); pathFilter.add("com/netflix/nicobar/core/archive"); pathFilter.add("com/netflix/nicobar/core/compile"); pathFilter.add("com/netflix/nicobar/core/module"); pathFilter.add("com/netflix/nicobar/core/module/jboss"); pathFilter.add("com/netflix/nicobar/core/plugin"); NICOBAR_CORE_DEPENDENCY_SPEC = DependencySpec.createClassLoaderDependencySpec(JBossModuleUtils.class.getClassLoader(), pathFilter); ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); JRE_DEPENDENCY_SPEC = DependencySpec.createClassLoaderDependencySpec(systemClassLoader, ClassPathUtils.getJdkPaths()); } /** * Populates a module spec builder with source files, resources and properties from the {@link ScriptArchive} * * @param moduleSpecBuilder builder to populate * @param scriptArchive {@link ScriptArchive} to copy from */ public static void populateModuleSpecWithResources(ModuleSpec.Builder moduleSpecBuilder, ScriptArchive scriptArchive) throws ModuleLoadException { Objects.requireNonNull(moduleSpecBuilder, "moduleSpecBuilder"); Objects.requireNonNull(scriptArchive, "scriptArchive"); MultiplePathFilterBuilder pathFilterBuilder = PathFilters.multiplePathFilterBuilder(true); Set<String> archiveEntryNames = scriptArchive.getArchiveEntryNames(); pathFilterBuilder.addFilter(PathFilters.in(archiveEntryNames), true); // add the root resources and classes to the module. If the root is a directory, all files under the tree // are added to the module. If it's a jar, then all files in the jar are added. URL url = scriptArchive.getRootUrl(); if (url != null) { try { File file = Paths.get(url.toURI()).toFile(); String filePath = file.getPath(); ResourceLoader rootResourceLoader = null; if (file.isDirectory()) { rootResourceLoader = ResourceLoaders.createFileResourceLoader(filePath, file); } else if (file.getPath().endsWith(".jar")) { try { rootResourceLoader = ResourceLoaders.createJarResourceLoader(filePath, new JarFile(file)); } catch (IOException e) { throw new ModuleLoadException(e); } } if (rootResourceLoader != null) { moduleSpecBuilder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(rootResourceLoader, pathFilterBuilder.create())); } } catch (URISyntaxException e) { throw new ModuleLoadException("Unsupported URL syntax: " + url, e); } } // add dependencies to the module spec ScriptModuleSpec scriptModuleSpec = scriptArchive.getModuleSpec(); // add string based properties from module spec metadata to the module spec being created Map<String, Object> archiveMetadata = scriptModuleSpec.getMetadata(); Map<String, String> stringMetadata = new HashMap<String, String>(); for (Entry<String, Object> entry: archiveMetadata.entrySet()) { if (entry.getValue() instanceof String) { stringMetadata.put(entry.getKey(), (String)entry.getValue()); } } addPropertiesToSpec(moduleSpecBuilder, stringMetadata); // override the default ModuleClassLoader to use our customer classloader moduleSpecBuilder.setModuleClassLoaderFactory(JBossModuleClassLoader.createFactory(scriptArchive)); } /** * Populates a module spec builder with core dependencies on JRE, Nicobar, itself, and compiler plugins. * * @param moduleSpecBuilder builder to populate * @param scriptArchive {@link ScriptArchive} to copy from */ public static void populateModuleSpecWithCoreDependencies(ModuleSpec.Builder moduleSpecBuilder, ScriptArchive scriptArchive) throws ModuleLoadException { Objects.requireNonNull(moduleSpecBuilder, "moduleSpecBuilder"); Objects.requireNonNull(scriptArchive, "scriptArchive"); Set<String> compilerPlugins = scriptArchive.getModuleSpec().getCompilerPluginIds(); for (String compilerPluginId : compilerPlugins) { moduleSpecBuilder.addDependency(DependencySpec.createModuleDependencySpec(getPluginModuleId(compilerPluginId), false)); } moduleSpecBuilder.addDependency(JRE_DEPENDENCY_SPEC); // TODO: Why does a module need a dependency on Nicobar itself? moduleSpecBuilder.addDependency(NICOBAR_CORE_DEPENDENCY_SPEC); moduleSpecBuilder.addDependency(DependencySpec.createLocalDependencySpec()); } /** * Populate a module spec builder with a dependencies on other modules. * @param moduleSpecBuilder builder to populate * @param moduleImportFilterPaths paths valid for importing into the module being built. * Can be null or empty to indicate that no filters should be applied. * @param dependencyExportFilterPaths export paths for the dependency being linked * @param dependentModuleIdentifier used to lookup the latest dependencies. see {@link JBossModuleLoader#getLatestRevisionIds()} */ public static void populateModuleSpecWithModuleDependency(ModuleSpec.Builder moduleSpecBuilder, @Nullable Set<String> moduleImportFilterPaths, @Nullable Set<String> dependencyExportFilterPaths, ModuleIdentifier dependentModuleIdentifier) { Objects.requireNonNull(moduleSpecBuilder, "moduleSpecBuilder"); PathFilter moduleImportFilters = buildFilters(moduleImportFilterPaths, false); PathFilter dependencyExportFilters = buildFilters(dependencyExportFilterPaths, false); PathFilter importFilters = PathFilters.all(dependencyExportFilters, moduleImportFilters); moduleSpecBuilder.addDependency(DependencySpec.createModuleDependencySpec(importFilters, dependencyExportFilters, null, dependentModuleIdentifier, false)); } /** * Populates a builder with a {@link ResourceLoaderSpec} to a filesystem resource root. * {@link ScriptArchive} * @param moduleSpecBuilder builder to populate * @param compilationRoot a path to the compilation resource root directory */ public static void populateModuleSpecWithCompilationRoot(ModuleSpec.Builder moduleSpecBuilder, Path compilationRoot) { Objects.requireNonNull(moduleSpecBuilder, "moduleSpecBuilder"); Objects.requireNonNull(compilationRoot, "resourceRoot"); ResourceLoader resourceLoader = ResourceLoaders.createFileResourceLoader(compilationRoot.toString(), compilationRoot.toFile()); moduleSpecBuilder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(resourceLoader)); } /** * Populates a {@link ModuleSpec} with a dependency on application runtime packages * specified as a set of package paths, loaded within the given classloader. This is the * primary way that a module gains access to packages defined in the application classloader. * This dependency is NOT rexported to downstream modules. * * @param moduleSpecBuilder builder to populate * @param appClassLoader a classloader the application classloader. * @param appPackages the global set of application package paths. */ public static void populateModuleSpecWithAppImports(ModuleSpec.Builder moduleSpecBuilder, ClassLoader appClassLoader, Set<String> appPackages) { Objects.requireNonNull(moduleSpecBuilder, "moduleSpecBuilder"); Objects.requireNonNull(appClassLoader, "classLoader"); moduleSpecBuilder.addDependency(DependencySpec.createClassLoaderDependencySpec(appClassLoader, appPackages, false)); } /** * Populates a {@link ModuleSpec} with runtime resources, dependencies and properties from the * {@link ScriptCompilerPluginSpec} * Helpful when creating a {@link ModuleSpec} from a ScriptLibPluginSpec * * @param moduleSpecBuilder builder to populate * @param pluginSpec {@link ScriptCompilerPluginSpec} to copy from * @param latestRevisionIds used to lookup the latest dependencies. see {@link JBossModuleLoader#getLatestRevisionIds()} */ public static void populateCompilerModuleSpec(ModuleSpec.Builder moduleSpecBuilder, ScriptCompilerPluginSpec pluginSpec, Map<ModuleId, ModuleIdentifier> latestRevisionIds) throws ModuleLoadException { Objects.requireNonNull(moduleSpecBuilder, "moduleSpecBuilder"); Objects.requireNonNull(pluginSpec, "pluginSpec"); Objects.requireNonNull(latestRevisionIds, "latestRevisionIds"); Set<Path> pluginRuntime = pluginSpec.getRuntimeResources(); for (Path resourcePath : pluginRuntime) { File file = resourcePath.toFile(); String pathString = resourcePath.toString(); ResourceLoader rootResourceLoader = null; if (file.isDirectory()) { rootResourceLoader = ResourceLoaders.createFileResourceLoader(pathString, file); } else if (pathString.endsWith(".jar")) { try { rootResourceLoader = ResourceLoaders.createJarResourceLoader(pathString, new JarFile(file)); } catch (IOException e) { throw new ModuleLoadException(e); } } if (rootResourceLoader != null) { moduleSpecBuilder.addResourceRoot(ResourceLoaderSpec.createResourceLoaderSpec(rootResourceLoader)); } } moduleSpecBuilder.addDependency(JRE_DEPENDENCY_SPEC); moduleSpecBuilder.addDependency(NICOBAR_CORE_DEPENDENCY_SPEC); moduleSpecBuilder.addDependency(DependencySpec.createLocalDependencySpec()); // add dependencies to the module spec Set<ModuleId> dependencies = pluginSpec.getModuleDependencies(); for (ModuleId scriptModuleId : dependencies) { ModuleIdentifier latestIdentifier = latestRevisionIds.get(scriptModuleId); if (latestIdentifier == null) { throw new ModuleLoadException("Cannot find dependent module: " + scriptModuleId); } moduleSpecBuilder.addDependency(DependencySpec.createModuleDependencySpec(latestIdentifier, true, false)); } Map<String, String> pluginMetadata = pluginSpec.getPluginMetadata(); addPropertiesToSpec(moduleSpecBuilder, pluginMetadata); } /** * Add properties to the {@link ModuleSpec} * * @param moduleSpecBuilder builder to populate * @param properties properties to add */ public static void addPropertiesToSpec(ModuleSpec.Builder moduleSpecBuilder, Map<String, String> properties) { for (Entry<String, String> entry : properties.entrySet()) { moduleSpecBuilder.addProperty(entry.getKey(), entry.getValue()); } } /** * Create the {@link ModuleIdentifier} for the given ScriptCompilerPluginSpec */ public static ModuleIdentifier getPluginModuleId(ScriptCompilerPluginSpec pluginSpec) { return getPluginModuleId(pluginSpec.getPluginId()); } /** * Create the {@link ModuleIdentifier} for the given ScriptCompilerPluginSpec ID */ public static ModuleIdentifier getPluginModuleId(String pluginId) { return ModuleIdentifier.create(pluginId); } /** * Helper method to create a revisionId in a consistent manner */ public static ModuleIdentifier createRevisionId(ModuleId scriptModuleId, long revisionNumber) { Objects.requireNonNull(scriptModuleId, "scriptModuleId"); return ModuleIdentifier.create(scriptModuleId.toString(), Long.toString(revisionNumber)); } /** * Build a PathFilter for a set of filter paths * * @param filterPaths the set of paths to filter on. * Can be null to indicate that no filters should be applied (accept all), * can be empty to indicate that everything should be filtered (reject all). * @param failedMatchValue the value the PathFilter returns when a path does not match. * @return a PathFilter. */ private static PathFilter buildFilters(Set<String> filterPaths, boolean failedMatchValue) { if (filterPaths == null) return PathFilters.acceptAll(); else if (filterPaths.isEmpty()) { return PathFilters.rejectAll(); } else { MultiplePathFilterBuilder builder = PathFilters.multiplePathFilterBuilder(failedMatchValue); for (String importPathGlob : filterPaths) builder.addFilter(PathFilters.match(importPathGlob), !failedMatchValue); return builder.create(); } } }