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

org.jboss.modules.Module Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 org.jboss.modules;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLConnection;
import java.security.AccessController;
import java.security.PermissionCollection;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import org.jboss.modules._private.ModulesPrivateAccess;
import org.jboss.modules.filter.ClassFilter;
import org.jboss.modules.filter.ClassFilters;
import org.jboss.modules.filter.PathFilter;
import org.jboss.modules.filter.PathFilters;
import org.jboss.modules.log.ModuleLogger;
import org.jboss.modules.log.NoopModuleLogger;

import __redirected.__JAXPRedirected;
import org.jboss.modules.security.ModularPermissionFactory;

/**
 * A module is a unit of classes and other resources, along with the specification of what is imported and exported
 * by this module from and to other modules.  Modules are created by {@link ModuleLoader}s which build modules from
 * various configuration information and resource roots.
 *
 * @author David M. Lloyd
 * @author John Bailey
 * @author Flavia Rainone
 * @author Jason T. Greene
 * @author [email protected]
 *
 * @apiviz.landmark
*/
public final class Module {

    private static final AtomicReference BOOT_MODULE_LOADER;

    static {
        log = NoopModuleLogger.getInstance();
        BOOT_MODULE_LOADER = new AtomicReference();
        EMPTY_CLASS_FILTERS = new FastCopyHashSet(0);
        EMPTY_PATH_FILTERS = new FastCopyHashSet(0);
        GET_DEPENDENCIES = new RuntimePermission("getDependencies");
        GET_CLASS_LOADER = new RuntimePermission("getClassLoader");
        GET_BOOT_MODULE_LOADER = new RuntimePermission("getBootModuleLoader");
        ACCESS_MODULE_LOGGER = new RuntimePermission("accessModuleLogger");
        ADD_CONTENT_HANDLER_FACTORY = new RuntimePermission("addContentHandlerFactory");
        ADD_URL_STREAM_HANDLER_FACTORY = new RuntimePermission("addURLStreamHandlerFactory");

        final String pkgsString = AccessController.doPrivileged(new PropertyReadAction("jboss.modules.system.pkgs"));
        final List list = new ArrayList();
        list.add("java.");
        list.add("sun.reflect.");
        list.add("__redirected.");
        if (pkgsString != null) {
            int i;
            int nc = -1;
            do {
                i = nc + 1;
                nc = pkgsString.indexOf(',', i);
                String part;
                if (nc == -1) {
                    part = pkgsString.substring(i).trim();
                } else {
                    part = pkgsString.substring(i, nc).trim();
                }
                if (part.length() > 0) {
                    list.add((part + ".").intern());
                }
            } while (nc != -1);
        }
        systemPackages = list.toArray(list.toArray(new String[list.size()]));
        final ListIterator iterator = list.listIterator();
        // http://youtrack.jetbrains.net/issue/IDEA-72097
        //noinspection WhileLoopReplaceableByForEach
        while (iterator.hasNext()) {
            iterator.set(iterator.next().replace('.', '/'));
        }
        systemPaths = list.toArray(list.toArray(new String[list.size()]));

        AccessController.doPrivileged(new PrivilegedAction() {
            public Void run() {
                try {
                    URL.setURLStreamHandlerFactory(ModularURLStreamHandlerFactory.INSTANCE);
                } catch (Throwable t) {
                    // todo log a warning or something
                }
                try {
                    URLConnection.setContentHandlerFactory(ModularContentHandlerFactory.INSTANCE);
                } catch (Throwable t) {
                    // todo log a warning or something
                }

                __JAXPRedirected.initAll();

                return null;
            }
        });
    }

    // static properties

    static final String[] systemPackages;
    static final String[] systemPaths;

    static final ModulesPrivateAccess PRIVATE_ACCESS = new ModulesPrivateAccess() {
        public ModuleClassLoader getClassLoaderOf(final Module module) {
            return module.getClassLoaderPrivate();
        }
    };

    /**
     * Private access for module internal code.  Throws {@link SecurityException} for user code.
     *
     * @throws SecurityException always
     */
    public static ModulesPrivateAccess getPrivateAccess() {
        if (CallerContext.getCallingClass() == ModularPermissionFactory.class) {
            return PRIVATE_ACCESS;
        }
        throw new SecurityException();
    }

    /**
     * The system-wide module logger, which may be changed via {@link #setModuleLogger(org.jboss.modules.log.ModuleLogger)}.
     */
    static volatile ModuleLogger log;

    private static final FastCopyHashSet EMPTY_CLASS_FILTERS;
    private static final FastCopyHashSet EMPTY_PATH_FILTERS;

    // immutable properties

    /**
     * The identifier of this module.
     */
    private final ModuleIdentifier identifier;
    /**
     * The name of the main class, if any (may be {@code null}).
     */
    private final String mainClassName;
    /**
     * The module class loader for this module.
     */
    private final ModuleClassLoader moduleClassLoader;
    /**
     * The module loader which created this module.
     */
    private final ModuleLoader moduleLoader;
    /**
     * The fallback local loader, if any is defined.
     */
    private final LocalLoader fallbackLoader;
    /**
     * The properties map specified when this module was defined.
     */
    private final Map properties;
    /**
     * The assigned permission collection.
     */
    private final PermissionCollection permissionCollection;

    // mutable properties

    /**
     * The linkage state.
     */
    private volatile Linkage linkage = Linkage.NONE;

    // private constants

    private static final RuntimePermission GET_DEPENDENCIES;
    private static final RuntimePermission GET_CLASS_LOADER;
    private static final RuntimePermission GET_BOOT_MODULE_LOADER;
    private static final RuntimePermission ACCESS_MODULE_LOGGER;
    private static final RuntimePermission ADD_CONTENT_HANDLER_FACTORY;
    private static final RuntimePermission ADD_URL_STREAM_HANDLER_FACTORY;

    /**
     * Construct a new instance from a module specification.
     *
     * @param spec the module specification
     * @param moduleLoader the module loader
     */
    Module(final ConcreteModuleSpec spec, final ModuleLoader moduleLoader) {
        this.moduleLoader = moduleLoader;

        // Initialize state from the spec.
        identifier = spec.getModuleIdentifier();
        mainClassName = spec.getMainClass();
        fallbackLoader = spec.getFallbackLoader();
        permissionCollection = spec.getPermissionCollection();
        //noinspection ThisEscapedInObjectConstruction
        final ModuleClassLoader.Configuration configuration = new ModuleClassLoader.Configuration(this, spec.getAssertionSetting(), spec.getResourceLoaders(), spec.getClassFileTransformer());
        final ModuleClassLoaderFactory factory = spec.getModuleClassLoaderFactory();
        ModuleClassLoader moduleClassLoader = null;
        if (factory != null) moduleClassLoader = factory.create(configuration);
        if (moduleClassLoader == null) moduleClassLoader = new ModuleClassLoader(configuration);
        this.moduleClassLoader = moduleClassLoader;
        final Map properties = spec.getProperties();
        this.properties = properties.isEmpty() ? Collections.emptyMap() : new LinkedHashMap(properties);
    }

    LocalLoader getFallbackLoader() {
        return fallbackLoader;
    }

    Dependency[] getDependenciesInternal() {
        return linkage.getDependencies();
    }

    DependencySpec[] getDependencySpecsInternal() {
        return linkage.getDependencySpecs();
    }

    ModuleClassLoader getClassLoaderPrivate() {
        return moduleClassLoader;
    }

    /**
     * Get the current dependencies of this module.
     *
     * @return the current dependencies of this module
     * @throws SecurityException if a security manager is enabled and the caller does not have the {@code getDependencies}
     * {@link RuntimePermission}
     */
    public DependencySpec[] getDependencies() throws SecurityException {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(GET_DEPENDENCIES);
        }
        return getDependencySpecsInternal().clone();
    }

    /**
     * Get an exported resource from a specific root in this module.
     *
     * @param rootPath the module root to search
     * @param resourcePath the path of the resource
     * @return the resource
     */
    public Resource getExportedResource(final String rootPath, final String resourcePath) {
        return moduleClassLoader.loadResourceLocal(rootPath, resourcePath);
    }

    /**
     * Run a module's main class, if any.
     *
     * @param args the arguments to pass
     * @throws NoSuchMethodException if there is no main method
     * @throws InvocationTargetException if the main method failed
     * @throws ClassNotFoundException if the main class is not found
     */
    public void run(final String[] args) throws NoSuchMethodException, InvocationTargetException, ClassNotFoundException {
        try {
            if (mainClassName == null) {
                throw new NoSuchMethodException("No main class defined for " + this);
            }
            final ClassLoader oldClassLoader = SecurityActions.setContextClassLoader(moduleClassLoader);
            try {
                final Class mainClass = Class.forName(mainClassName, false, moduleClassLoader);
                try {
                    Class.forName(mainClassName, true, moduleClassLoader);
                } catch (Throwable t) {
                    throw new InvocationTargetException(t, "Failed to initialize main class '" + mainClassName + "'");
                }
                final Method mainMethod = mainClass.getMethod("main", String[].class);
                final int modifiers = mainMethod.getModifiers();
                if (! Modifier.isStatic(modifiers)) {
                    throw new NoSuchMethodException("Main method is not static for " + this);
                }
                // ignore the return value
                mainMethod.invoke(null, new Object[] {args});
            } finally {
                SecurityActions.setContextClassLoader(oldClassLoader);
            }
        } catch (IllegalAccessException e) {
            // unexpected; should be public
            throw new IllegalAccessError(e.getMessage());
        }
    }

    /**
     * Get this module's identifier.
     *
     * @return the identifier
     */
    public ModuleIdentifier getIdentifier() {
        return identifier;
    }

    /**
     * Get the module loader which created this module.
     *
     * @return the module loader of this module
     */
    public ModuleLoader getModuleLoader() {
        return moduleLoader;
    }

    /**
     * Load a service loader from this module.
     *
     * @param serviceType the service type class
     * @param  the service type
     * @return the service loader
     */
    public  ServiceLoader loadService(Class serviceType) {
        return ServiceLoader.load(serviceType, moduleClassLoader);
    }

    /**
     * Load a service loader from a module in the caller's module loader. The caller's
     * module loader refers to the loader of the module of the class that calls this method.
     * Note that {@link #loadService(Class)} is more efficient since it does not need to crawl
     * the stack.
     *
     * @param  the the service type
     * @param identifier the module identifier containing the service loader
     * @param serviceType the service type class
     * @return the loaded service from the caller's module
     * @throws ModuleLoadException if the named module failed to load
     */
    public static  ServiceLoader loadServiceFromCallerModuleLoader(ModuleIdentifier identifier, Class serviceType) throws ModuleLoadException {
        return getCallerModuleLoader().loadModule(identifier).loadService(serviceType);
    }

    /**
     * Get the class loader for a module.  The class loader can be used to access non-exported classes and
     * resources of the module.
     * 

* If a security manager is present, then this method invokes the security manager's {@code checkPermission} method * with a RuntimePermission("getClassLoader") permission to verify access to the class loader. If * access is not granted, a {@code SecurityException} will be thrown. * * @return the module class loader */ public ModuleClassLoader getClassLoader() { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(GET_CLASS_LOADER); } return moduleClassLoader; } /** * Get all the paths exported by this module. * * @return the paths that are exported by this module */ public Set getExportedPaths() { return Collections.unmodifiableSet(getPathsUnchecked().keySet()); } /** * Get the module for a loaded class, or {@code null} if the class did not come from any module. * * @param clazz the class * @return the module it came from */ public static Module forClass(Class clazz) { final ClassLoader cl = clazz.getClassLoader(); return forClassLoader(cl, false); } /** * Get the module for a class loader, or {@code null} if the class loader is not associated with any module. If * the class loader is unknown, it is possible to check the parent class loader up the chain, and so on until a module is found. * * @param cl the class loader * @param search {@code true} to search up the delegation chain * @return the associated module */ public static Module forClassLoader(ClassLoader cl, boolean search) { if (cl instanceof ModuleClassLoader) { return ((ModuleClassLoader) cl).getModule(); } else if (search && cl != null) { return forClassLoader(cl.getParent(), true); } else { return null; } } /** * Gets the boot module loader. The boot module loader is the * initial loader that is established by the module framework. It typically * is based off of the environmental module path unless it is overridden by * specifying a different class name for the {@code boot.module.loader} system * property. * * @return the boot module loader */ public static ModuleLoader getBootModuleLoader() { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(GET_BOOT_MODULE_LOADER); } ModuleLoader loader; while ((loader = BOOT_MODULE_LOADER.get()) == null) { loader = DefaultBootModuleLoaderHolder.INSTANCE; if (BOOT_MODULE_LOADER.compareAndSet(null, loader)) { break; } // get it again } return loader; } static void initBootModuleLoader(ModuleLoader loader) { BOOT_MODULE_LOADER.set(loader); } /** * Gets the current module loader. The current module loader is the * loader of the module from the calling class. Note that this method * must crawl the stack to determine this, so other mechanisms are more * efficient. * * @return the current module loader, or {@code null} if this method is called outside of a module */ public static ModuleLoader getCallerModuleLoader() { Module callerModule = getCallerModule(); return callerModule == null ? null : callerModule.getModuleLoader(); } /** * Get the current thread's context module loader. This loader is the one which defined the module * whose class loader is, or is a parent of, the thread's current context class loader. If there is none, * then {@code null} is returned. * * @return the module loader, or {@code null} if none is set */ public static ModuleLoader getContextModuleLoader() { return Module.forClassLoader(Thread.currentThread().getContextClassLoader(), true).getModuleLoader(); } /** * Get a module from the current module loader. Note that this must crawl the * stack to determine this, so other mechanisms are more efficient. * @see #getCallerModuleLoader() * * @param identifier the module identifier * @return the module * @throws ModuleLoadException if the module could not be loaded */ public static Module getModuleFromCallerModuleLoader(final ModuleIdentifier identifier) throws ModuleLoadException { return getCallerModuleLoader().loadModule(identifier); } /** * Get the caller's module. The caller's module is the module containing the method that calls this * method. Note that this method crawls the stack so other ways of obtaining the * module are more efficient. * * @return the current module */ public static Module getCallerModule() { return forClass(CallerContext.getCallingClass()); } /** * Get the module with the given identifier from the module loader used by this module. * * @param identifier the module identifier * @return the module * @throws ModuleLoadException if an error occurs */ public Module getModule(final ModuleIdentifier identifier) throws ModuleLoadException { return moduleLoader.loadModule(identifier); } /** * Load a class from a module in the system module loader. * * @see #getBootModuleLoader() * * @param moduleIdentifier the identifier of the module from which the class * should be loaded * @param className the class name to load * @return the class * @throws ModuleLoadException if the module could not be loaded * @throws ClassNotFoundException if the class could not be loaded */ public static Class loadClassFromBootModuleLoader(final ModuleIdentifier moduleIdentifier, final String className) throws ModuleLoadException, ClassNotFoundException { return Class.forName(className, true, getBootModuleLoader().loadModule(moduleIdentifier).getClassLoader()); } /** * Load a class from a module in the caller's module loader. * * @see #getCallerModuleLoader() * * @param moduleIdentifier the identifier of the module from which the class * should be loaded * @param className the class name to load * @return the class * @throws ModuleLoadException if the module could not be loaded * @throws ClassNotFoundException if the class could not be loaded */ public static Class loadClassFromCallerModuleLoader(final ModuleIdentifier moduleIdentifier, final String className) throws ModuleLoadException, ClassNotFoundException { return Class.forName(className, true, getModuleFromCallerModuleLoader(moduleIdentifier).getClassLoader()); } /** * Load a class from a local loader. * * @param className the class name * @param resolve {@code true} to resolve the class after definition * @return the class */ Class loadModuleClass(final String className, final boolean resolve) throws ClassNotFoundException { for (String s : systemPackages) { if (className.startsWith(s)) { return moduleClassLoader.loadClass(className, resolve); } } final String path = pathOfClass(className); final Map> paths = getPathsUnchecked(); final List loaders = paths.get(path); if (loaders != null) { Class clazz; for (LocalLoader loader : loaders) { clazz = loader.loadClassLocal(className, resolve); if (clazz != null) { return clazz; } } } final LocalLoader fallbackLoader = this.fallbackLoader; if (fallbackLoader != null) { return fallbackLoader.loadClassLocal(className, resolve); } return null; } /** * Load a resource from a local loader. * * @param name the resource name * @return the resource URL, or {@code null} if not found */ URL getResource(final String name) { final String canonPath = PathUtils.canonicalize(name); for (String s : Module.systemPaths) { if (canonPath.startsWith(s)) { return moduleClassLoader.getResource(canonPath); } } log.trace("Attempting to find resource %s in %s", canonPath, this); final String path = pathOf(canonPath); final Map> paths = getPathsUnchecked(); final List loaders = paths.get(path); if (loaders != null) { for (LocalLoader loader : loaders) { final List resourceList = loader.loadResourceLocal(canonPath); for (Resource resource : resourceList) { return resource.getURL(); } } } final LocalLoader fallbackLoader = this.fallbackLoader; if (fallbackLoader != null) { final List resourceList = fallbackLoader.loadResourceLocal(canonPath); for (Resource resource : resourceList) { return resource.getURL(); } } return null; } /** * Load a resource from a local loader. * * @param name the resource name * @return the resource stream, or {@code null} if not found */ InputStream getResourceAsStream(final String name) throws IOException { final String canonPath = PathUtils.canonicalize(name); for (String s : Module.systemPaths) { if (canonPath.startsWith(s)) { return moduleClassLoader.getResourceAsStream(canonPath); } } log.trace("Attempting to find resource %s in %s", canonPath, this); final String path = pathOf(canonPath); final Map> paths = getPathsUnchecked(); final List loaders = paths.get(path); if (loaders != null) { for (LocalLoader loader : loaders) { final List resourceList = loader.loadResourceLocal(canonPath); for (Resource resource : resourceList) { return resource.openStream(); } } } final LocalLoader fallbackLoader = this.fallbackLoader; if (fallbackLoader != null) { final List resourceList = fallbackLoader.loadResourceLocal(canonPath); for (Resource resource : resourceList) { return resource.openStream(); } } return null; } /** * Load all resources of a given name from a local loader. * * @param name the resource name * @return the enumeration of all the matching resource URLs (may be empty) */ Enumeration getResources(final String name) { final String canonPath = PathUtils.canonicalize(PathUtils.relativize(name)); for (String s : Module.systemPaths) { if (canonPath.startsWith(s)) { try { return moduleClassLoader.getResources(canonPath); } catch (IOException e) { return ConcurrentClassLoader.EMPTY_ENUMERATION; } } } log.trace("Attempting to find all resources %s in %s", canonPath, this); final String path = pathOf(canonPath); final Map> paths = getPathsUnchecked(); final List loaders = paths.get(path); final List list = new ArrayList(); if (loaders != null) { for (LocalLoader loader : loaders) { final List resourceList = loader.loadResourceLocal(canonPath); for (Resource resource : resourceList) { list.add(resource.getURL()); } } } final LocalLoader fallbackLoader = this.fallbackLoader; if (fallbackLoader != null) { final List resourceList = fallbackLoader.loadResourceLocal(canonPath); for (Resource resource : resourceList) { list.add(resource.getURL()); } } return list.size() == 0 ? ConcurrentClassLoader.EMPTY_ENUMERATION : Collections.enumeration(list); } /** * Get an exported resource URL. * * @param name the resource name * @return the resource, or {@code null} if it was not found */ public URL getExportedResource(final String name) { return getResource(name); } /** * Get all exported resource URLs for a resource name. * * @param name the resource name * @return the resource URLs */ public Enumeration getExportedResources(final String name) { return getResources(name); } /** * Enumerate all the imported resources in this module, subject to a path filter. The filter applies to * the containing path of each resource. * * @param filter the filter to apply to the search * @return the resource iterator (possibly empty) * @throws ModuleLoadException if linking a dependency module fails for some reason */ public Iterator iterateResources(final PathFilter filter) throws ModuleLoadException { final Map> paths = getPaths(); final Iterator>> iterator = paths.entrySet().iterator(); return new Iterator() { private String path; private Iterator resourceIterator; private Iterator loaderIterator; private Resource next; public boolean hasNext() { while (next == null) { if (resourceIterator != null) { assert path != null; if (resourceIterator.hasNext()) { next = resourceIterator.next(); return true; } resourceIterator = null; } if (loaderIterator != null) { assert path != null; if (loaderIterator.hasNext()) { final LocalLoader loader = loaderIterator.next(); if (loader instanceof IterableLocalLoader) { resourceIterator = ((IterableLocalLoader)loader).iterateResources(path, false); continue; } } loaderIterator = null; } if (! iterator.hasNext()) { return false; } final Map.Entry> entry = iterator.next(); path = entry.getKey(); if (filter.accept(path)) { loaderIterator = entry.getValue().iterator(); } } return true; } public Resource next() { if (! hasNext()) throw new NoSuchElementException(); try { return next; } finally { next = null; } } public void remove() { throw new UnsupportedOperationException(); } }; } /** * Enumerate all imported resources in this module which match the given glob expression. The glob applies to * the whole resource name. * * @param glob the glob to apply * @return the iterator * @throws ModuleLoadException if linking a dependency module fails for some reason */ public Iterator globResources(final String glob) throws ModuleLoadException { String safeGlob = PathUtils.canonicalize(PathUtils.relativize(glob)); final int i = safeGlob.lastIndexOf('/'); if (i == -1) { return PathFilters.filtered(PathFilters.match(glob), iterateResources(PathFilters.acceptAll())); } else { return PathFilters.filtered(PathFilters.match(glob.substring(i + 1)), iterateResources(PathFilters.match(glob.substring(0, i)))); } } /** * Get the (unmodifiable) set of paths which are imported into this module class loader, including local paths. The * set will include all paths defined by the module's resource loaders, minus any paths excluded by filters. The * set will generally always contain an empty entry (""). The set is unordered and unsorted, and is iterable in * O(n) time and accessible in O(1) time. * * @return the set of paths * @throws ModuleLoadException if the module was previously unlinked, and there was an exception while linking */ public final Set getImportedPaths() throws ModuleLoadException { return Collections.unmodifiableSet(getPaths().keySet()); } /** * Get the path name of a class. * * @param className the binary name of the class * @return the parent path */ static String pathOfClass(final String className) { final String resourceName = className.replace('.', '/'); final String path; final int idx = resourceName.lastIndexOf('/'); if (idx > -1) { path = resourceName.substring(0, idx); } else { path = ""; } return path; } /** * Get the path name of a resource. * * @param resourceName the resource name * @return the parent path */ static String pathOf(final String resourceName) { final String path; if (resourceName.indexOf('/') == 0) { return pathOf(resourceName.substring(1)); } final int idx = resourceName.lastIndexOf('/'); if (idx > -1) { path = resourceName.substring(0, idx); } else { path = ""; } return path; } /** * Get the file name of a class. * * @param className the class name * @return the name of the corresponding class file */ static String fileNameOfClass(final String className) { return className.replace('.', '/') + ".class"; } /** * Get the property with the given name, or {@code null} if none was defined. * * @param name the property name * @return the property value */ public String getProperty(String name) { return properties.get(name); } /** * Get the property with the given name, or a default value if none was defined. * * @param name the property name * @param defaultVal the default value * @return the property value */ public String getProperty(String name, String defaultVal) { return properties.containsKey(name) ? properties.get(name) : defaultVal; } /** * Get a copy of the list of property names. * * @return the property names list */ public List getPropertyNames() { return new ArrayList(properties.keySet()); } /** * Get the string representation of this module. * * @return the string representation */ @Override public String toString() { return "Module \"" + identifier + "\"" + " from " + moduleLoader.toString(); } /** * Get the logger used by the module system. *

* If a security manager is present, then this method invokes the security manager's {@code checkPermission} method * with a RuntimePermission("accessModuleLogger") permission to verify access to the module logger. If * access is not granted, a {@code SecurityException} will be thrown. * * @return the module logger */ public static ModuleLogger getModuleLogger() { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(ACCESS_MODULE_LOGGER); } return log; } /** * Change the logger used by the module system. *

* If a security manager is present, then this method invokes the security manager's {@code checkPermission} method * with a RuntimePermission("accessModuleLogger") permission to verify access to the module logger. If * access is not granted, a {@code SecurityException} will be thrown. * * @param logger the new logger, must not be {@code null} */ public static void setModuleLogger(final ModuleLogger logger) { if (logger == null) { throw new IllegalArgumentException("logger is null"); } final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(ACCESS_MODULE_LOGGER); } logger.greeting(); log = logger; } /** * Return the start time in millis when Module.class was loaded. * * @return start time of Module.class load */ public static long getStartTime() { return StartTimeHolder.START_TIME; } /** * Register an additional module which contains content handlers. *

* If a security manager is present, then this method invokes the security manager's {@code checkPermission} method * with a RuntimePermission("addContentHandlerFactory") permission to verify access. If * access is not granted, a {@code SecurityException} will be thrown. * * @param module the module to add */ public static void registerContentHandlerFactoryModule(Module module) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(ADD_CONTENT_HANDLER_FACTORY); } ModularContentHandlerFactory.addHandlerModule(module); } /** * Register an additional module which contains URL handlers. *

* If a security manager is present, then this method invokes the security manager's {@code checkPermission} method * with a RuntimePermission("addURLStreamHandlerFactory") permission to verify access. If * access is not granted, a {@code SecurityException} will be thrown. * * @param module the module to add */ public static void registerURLStreamHandlerFactoryModule(Module module) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(ADD_URL_STREAM_HANDLER_FACTORY); } ModularURLStreamHandlerFactory.addHandlerModule(module); } /** * Get the platform identifier. This is the string that uniquely identifies the hardware and OS combination for * the current running system. * * @return the platform identifier */ public static String getPlatformIdentifier() { return NativeLibraryResourceLoader.getArchName(); } PermissionCollection getPermissionCollection() { return permissionCollection; } // Linking and resolution static final class Visited { private final Module module; private final FastCopyHashSet filters; private final FastCopyHashSet classFilters; private final FastCopyHashSet resourceFilters; private final int hashCode; Visited(final Module module, final FastCopyHashSet filters, final FastCopyHashSet classFilters, final FastCopyHashSet resourceFilters) { this.module = module; this.filters = filters; this.classFilters = classFilters; this.resourceFilters = resourceFilters; hashCode = ((resourceFilters.hashCode() * 13 + classFilters.hashCode()) * 13 + filters.hashCode()) * 13 + module.hashCode(); } public int hashCode() { return hashCode; } public boolean equals(Object other) { return other instanceof Visited && equals((Visited)other); } public boolean equals(Visited other) { return this == other || other != null && module == other.module && filters.equals(other.filters) && classFilters.equals(other.classFilters) && resourceFilters.equals(other.resourceFilters); } } private long addPaths(Dependency[] dependencies, Map> map, FastCopyHashSet filterStack, FastCopyHashSet classFilterStack, final FastCopyHashSet resourceFilterStack, Set visited) throws ModuleLoadException { long subtract = 0L; moduleLoader.incScanCount(); for (Dependency dependency : dependencies) { if (dependency instanceof ModuleDependency) { final ModuleDependency moduleDependency = (ModuleDependency) dependency; final ModuleLoader moduleLoader = moduleDependency.getModuleLoader(); final ModuleIdentifier id = moduleDependency.getIdentifier(); final Module module; try { long pauseStart = Metrics.getCurrentCPUTime(); try { module = moduleLoader.preloadModule(id); } finally { subtract += Metrics.getCurrentCPUTime() - pauseStart; } } catch (ModuleLoadException ex) { if (moduleDependency.isOptional()) { continue; } else { log.trace("Module %s, dependency %s preload failed: %s", getIdentifier(), moduleDependency.getIdentifier(), ex); throw ex; } } if (module == null) { if (!moduleDependency.isOptional()) { throw new ModuleNotFoundException(id.toString()); } continue; } final PathFilter importFilter = dependency.getImportFilter(); final FastCopyHashSet nestedFilters; final FastCopyHashSet nestedClassFilters; final FastCopyHashSet nestedResourceFilters; if (filterStack.contains(importFilter)) { nestedFilters = filterStack; } else { nestedFilters = filterStack.clone(); nestedFilters.add(importFilter); } final ClassFilter classImportFilter = dependency.getClassImportFilter(); if (classImportFilter == ClassFilters.acceptAll() || classFilterStack.contains(classImportFilter)) { nestedClassFilters = classFilterStack; } else { nestedClassFilters = classFilterStack.clone(); if (classImportFilter != ClassFilters.acceptAll()) nestedClassFilters.add(classImportFilter); } final PathFilter resourceImportFilter = dependency.getResourceImportFilter(); if (resourceImportFilter == PathFilters.acceptAll() || resourceFilterStack.contains(resourceImportFilter)) { nestedResourceFilters = resourceFilterStack; } else { nestedResourceFilters = resourceFilterStack.clone(); if (resourceImportFilter != PathFilters.acceptAll()) nestedResourceFilters.add(resourceImportFilter); } subtract += module.addExportedPaths(module.getDependenciesInternal(), map, nestedFilters, nestedClassFilters, nestedResourceFilters, visited); } else if (dependency instanceof ModuleClassLoaderDependency) { final ModuleClassLoaderDependency classLoaderDependency = (ModuleClassLoaderDependency) dependency; LocalLoader localLoader = classLoaderDependency.getLocalLoader(); for (Object filter : classFilterStack.getRawArray()) { if (filter != null && filter != ClassFilters.acceptAll()) { localLoader = createClassFilteredLocalLoader((ClassFilter) filter, localLoader); } } for (Object filter : resourceFilterStack.getRawArray()) { if (filter != null && filter != PathFilters.acceptAll()) { localLoader = createPathFilteredLocalLoader((PathFilter) filter, localLoader); } } ClassFilter classFilter = classLoaderDependency.getClassImportFilter(); if (classFilter != ClassFilters.acceptAll()) { localLoader = createClassFilteredLocalLoader(classFilter, localLoader); } PathFilter resourceFilter = classLoaderDependency.getResourceImportFilter(); if (resourceFilter != PathFilters.acceptAll()) { localLoader = createPathFilteredLocalLoader(resourceFilter, localLoader); } final PathFilter importFilter = classLoaderDependency.getImportFilter(); final Set paths = classLoaderDependency.getPaths(); for (String path : paths) { if (importFilter.accept(path)) { List list = map.get(path); if (list == null) { map.put(path, list = new ArrayList()); list.add(localLoader); } else if (! list.contains(localLoader)) { list.add(localLoader); } } } } else if (dependency instanceof LocalDependency) { final LocalDependency localDependency = (LocalDependency) dependency; LocalLoader localLoader = localDependency.getLocalLoader(); for (Object filter : classFilterStack.getRawArray()) { if (filter != null && filter != ClassFilters.acceptAll()) { localLoader = createClassFilteredLocalLoader((ClassFilter) filter, localLoader); } } for (Object filter : resourceFilterStack.getRawArray()) { if (filter != null && filter != PathFilters.acceptAll()) { localLoader = createPathFilteredLocalLoader((PathFilter) filter, localLoader); } } final ClassFilter classFilter = localDependency.getClassImportFilter(); if (classFilter != ClassFilters.acceptAll()) { localLoader = createClassFilteredLocalLoader(classFilter, localLoader); } final PathFilter resourceFilter = localDependency.getResourceImportFilter(); if (resourceFilter != PathFilters.acceptAll()) { localLoader = createPathFilteredLocalLoader(resourceFilter, localLoader); } final PathFilter importFilter = localDependency.getImportFilter(); final Set paths = localDependency.getPaths(); for (String path : paths) { if (importFilter.accept(path)) { List list = map.get(path); if (list == null) { map.put(path, list = new ArrayList()); list.add(localLoader); } else if (! list.contains(localLoader)) { list.add(localLoader); } } } } // else unknown dep type so just skip } return subtract; } private LocalLoader createPathFilteredLocalLoader(PathFilter filter, LocalLoader localLoader) { if (localLoader instanceof IterableLocalLoader) return LocalLoaders.createIterablePathFilteredLocalLoader(filter, (IterableLocalLoader) localLoader); else return LocalLoaders.createPathFilteredLocalLoader(filter, localLoader); } private LocalLoader createClassFilteredLocalLoader(ClassFilter filter, LocalLoader localLoader) { if (localLoader instanceof IterableLocalLoader) return LocalLoaders.createIterableClassFilteredLocalLoader(filter, (IterableLocalLoader) localLoader); else return LocalLoaders.createClassFilteredLocalLoader(filter, localLoader); } private long addExportedPaths(Dependency[] dependencies, Map> map, FastCopyHashSet filterStack, FastCopyHashSet classFilterStack, final FastCopyHashSet resourceFilterStack, Set visited) throws ModuleLoadException { if (!visited.add(new Visited(this, filterStack, classFilterStack, resourceFilterStack))) { return 0L; } long subtract = 0L; moduleLoader.incScanCount(); for (Dependency dependency : dependencies) { final PathFilter exportFilter = dependency.getExportFilter(); // skip non-exported dependencies altogether if (exportFilter != PathFilters.rejectAll()) { if (dependency instanceof ModuleDependency) { final ModuleDependency moduleDependency = (ModuleDependency) dependency; final ModuleLoader moduleLoader = moduleDependency.getModuleLoader(); final ModuleIdentifier id = moduleDependency.getIdentifier(); final Module module; try { long pauseStart = Metrics.getCurrentCPUTime(); try { module = moduleLoader.preloadModule(id); } finally { subtract += Metrics.getCurrentCPUTime() - pauseStart; } } catch (ModuleLoadException ex) { if (moduleDependency.isOptional()) { continue; } else { log.trace("Module %s, dependency %s preload failed: %s", getIdentifier(), moduleDependency.getIdentifier(), ex); throw ex; } } if (module == null) { if (!moduleDependency.isOptional()) { throw new ModuleNotFoundException(id.toString()); } continue; } final PathFilter importFilter = dependency.getImportFilter(); final FastCopyHashSet nestedFilters; final FastCopyHashSet nestedClassFilters; final FastCopyHashSet nestedResourceFilters; if (filterStack.contains(importFilter) && filterStack.contains(exportFilter)) { nestedFilters = filterStack; } else { nestedFilters = filterStack.clone(); nestedFilters.add(importFilter); nestedFilters.add(exportFilter); } final ClassFilter classImportFilter = dependency.getClassImportFilter(); final ClassFilter classExportFilter = dependency.getClassExportFilter(); if ((classImportFilter == ClassFilters.acceptAll() || classFilterStack.contains(classImportFilter)) && (classExportFilter == ClassFilters.acceptAll() || classFilterStack.contains(classExportFilter))) { nestedClassFilters = classFilterStack; } else { nestedClassFilters = classFilterStack.clone(); if (classImportFilter != ClassFilters.acceptAll()) nestedClassFilters.add(classImportFilter); if (classExportFilter != ClassFilters.acceptAll()) nestedClassFilters.add(classExportFilter); } final PathFilter resourceImportFilter = dependency.getResourceImportFilter(); final PathFilter resourceExportFilter = dependency.getResourceExportFilter(); if ((resourceImportFilter == PathFilters.acceptAll() || resourceFilterStack.contains(resourceImportFilter)) && (resourceExportFilter == PathFilters.acceptAll() || resourceFilterStack.contains(resourceExportFilter))) { nestedResourceFilters = resourceFilterStack; } else { nestedResourceFilters = resourceFilterStack.clone(); if (resourceImportFilter != PathFilters.acceptAll()) nestedResourceFilters.add(resourceImportFilter); if (resourceExportFilter != PathFilters.acceptAll()) nestedResourceFilters.add(resourceExportFilter); } subtract += module.addExportedPaths(module.getDependenciesInternal(), map, nestedFilters, nestedClassFilters, nestedResourceFilters, visited); } else if (dependency instanceof ModuleClassLoaderDependency) { final ModuleClassLoaderDependency classLoaderDependency = (ModuleClassLoaderDependency) dependency; LocalLoader localLoader = classLoaderDependency.getLocalLoader(); for (Object filter : classFilterStack.getRawArray()) { if (filter != null && filter != ClassFilters.acceptAll()) { localLoader = createClassFilteredLocalLoader((ClassFilter) filter, localLoader); } } for (Object filter : resourceFilterStack.getRawArray()) { if (filter != null && filter != PathFilters.acceptAll()) { localLoader = createPathFilteredLocalLoader((PathFilter) filter, localLoader); } } ClassFilter classImportFilter = classLoaderDependency.getClassImportFilter(); if (classImportFilter != ClassFilters.acceptAll()) { localLoader = createClassFilteredLocalLoader(classImportFilter, localLoader); } ClassFilter classExportFilter = classLoaderDependency.getClassExportFilter(); if (classExportFilter != ClassFilters.acceptAll()) { localLoader = createClassFilteredLocalLoader(classExportFilter, localLoader); } PathFilter resourceImportFilter = classLoaderDependency.getResourceImportFilter(); if (resourceImportFilter != PathFilters.acceptAll()) { localLoader = createPathFilteredLocalLoader(resourceImportFilter, localLoader); } PathFilter resourceExportFilter = classLoaderDependency.getResourceExportFilter(); if (resourceExportFilter != PathFilters.acceptAll()) { localLoader = createPathFilteredLocalLoader(resourceExportFilter, localLoader); } final PathFilter importFilter = classLoaderDependency.getImportFilter(); final Set paths = classLoaderDependency.getPaths(); for (String path : paths) { boolean accept = ! "_private".equals(path); if (accept) for (Object filter : filterStack.getRawArray()) { if (filter != null && ! ((PathFilter)filter).accept(path)) { accept = false; break; } } if (accept && importFilter.accept(path) && exportFilter.accept(path)) { List list = map.get(path); if (list == null) { map.put(path, list = new ArrayList(1)); list.add(localLoader); } else if (! list.contains(localLoader)) { list.add(localLoader); } } } } else if (dependency instanceof LocalDependency) { final LocalDependency localDependency = (LocalDependency) dependency; LocalLoader localLoader = localDependency.getLocalLoader(); for (Object filter : classFilterStack.getRawArray()) { if (filter != null && filter != ClassFilters.acceptAll()) { localLoader = createClassFilteredLocalLoader((ClassFilter) filter, localLoader); } } for (Object filter : resourceFilterStack.getRawArray()) { if (filter != null && filter != PathFilters.acceptAll()) { localLoader = createPathFilteredLocalLoader((PathFilter) filter, localLoader); } } ClassFilter classFilter = localDependency.getClassExportFilter(); if (classFilter != ClassFilters.acceptAll()) { localLoader = createClassFilteredLocalLoader(classFilter, localLoader); } classFilter = localDependency.getClassImportFilter(); if (classFilter != ClassFilters.acceptAll()) { localLoader = createClassFilteredLocalLoader(classFilter, localLoader); } PathFilter resourceFilter = localDependency.getResourceExportFilter(); if (resourceFilter != PathFilters.acceptAll()) { localLoader = createPathFilteredLocalLoader(resourceFilter, localLoader); } resourceFilter = localDependency.getResourceImportFilter(); if (resourceFilter != PathFilters.acceptAll()) { localLoader = createPathFilteredLocalLoader(resourceFilter, localLoader); } final Set paths = localDependency.getPaths(); for (String path : paths) { boolean accept = true; for (Object filter : filterStack.getRawArray()) { if (filter != null && ! ((PathFilter)filter).accept(path)) { accept = false; break; } } if (accept && localDependency.getImportFilter().accept(path) && localDependency.getExportFilter().accept(path)) { List list = map.get(path); if (list == null) { map.put(path, list = new ArrayList(1)); list.add(localLoader); } else if (! list.contains(localLoader)) { list.add(localLoader); } } } } // else unknown dep type so just skip } } return subtract; } Map> getPaths() throws ModuleLoadException { Linkage oldLinkage = this.linkage; Linkage linkage; Linkage.State state = oldLinkage.getState(); if (state == Linkage.State.LINKED) { return oldLinkage.getPaths(); } // slow path loop boolean intr = false; try { for (;;) { synchronized (this) { oldLinkage = this.linkage; state = oldLinkage.getState(); while (state == Linkage.State.LINKING || state == Linkage.State.NEW) try { wait(); oldLinkage = this.linkage; state = oldLinkage.getState(); } catch (InterruptedException e) { intr = true; } if (state == Linkage.State.LINKED) { return oldLinkage.getPaths(); } this.linkage = linkage = new Linkage(oldLinkage.getDependencySpecs(), oldLinkage.getDependencies(), Linkage.State.LINKING); // fall out and link } boolean ok = false; try { link(linkage); ok = true; } finally { if (! ok) { // restore original (lack of) linkage synchronized (this) { if (this.linkage == linkage) { this.linkage = oldLinkage; notifyAll(); } } } } } } finally { if (intr) { Thread.currentThread().interrupt(); } } } Map> getPathsUnchecked() { try { return getPaths(); } catch (ModuleLoadException e) { throw e.toError(); } } void link(final Linkage linkage) throws ModuleLoadException { final HashMap> importsMap = new HashMap>(); final Dependency[] dependencies = linkage.getDependencies(); final long start = Metrics.getCurrentCPUTime(); long subtractTime = 0L; try { final Set visited = new FastCopyHashSet(16); final FastCopyHashSet filterStack = new FastCopyHashSet(8); final FastCopyHashSet classFilterStack = EMPTY_CLASS_FILTERS; final FastCopyHashSet resourceFilterStack = EMPTY_PATH_FILTERS; subtractTime += addPaths(dependencies, importsMap, filterStack, classFilterStack, resourceFilterStack, visited); synchronized (this) { if (this.linkage == linkage) { this.linkage = new Linkage(linkage.getDependencySpecs(), linkage.getDependencies(), Linkage.State.LINKED, importsMap); notifyAll(); } // else all our efforts were just wasted since someone changed the deps in the meantime } } finally { moduleLoader.addLinkTime(Metrics.getCurrentCPUTime() - start - subtractTime); } } void relinkIfNecessary() throws ModuleLoadException { Linkage oldLinkage = this.linkage; Linkage linkage; if (oldLinkage.getState() != Linkage.State.UNLINKED) { return; } synchronized (this) { oldLinkage = this.linkage; if (oldLinkage.getState() != Linkage.State.UNLINKED) { return; } this.linkage = linkage = new Linkage(oldLinkage.getDependencySpecs(), oldLinkage.getDependencies(), Linkage.State.LINKING); } boolean ok = false; try { link(linkage); ok = true; } finally { if (! ok) { // restore original (lack of) linkage synchronized (this) { if (this.linkage == linkage) { this.linkage = oldLinkage; notifyAll(); } } } } } void relink() throws ModuleLoadException { link(linkage); } void setDependencies(final List dependencySpecs) { if (dependencySpecs == null) { throw new IllegalArgumentException("dependencySpecs is null"); } final DependencySpec[] specs = dependencySpecs.toArray(new DependencySpec[dependencySpecs.size()]); for (DependencySpec spec : specs) { if (spec == null) { throw new IllegalArgumentException("dependencySpecs contains a null dependency specification"); } } setDependencies(specs); } void setDependencies(final DependencySpec[] dependencySpecs) { synchronized (this) { linkage = new Linkage(dependencySpecs, calculateDependencies(dependencySpecs), Linkage.State.UNLINKED, null); notifyAll(); } } private Dependency[] calculateDependencies(final DependencySpec[] dependencySpecs) { final Dependency[] dependencies = new Dependency[dependencySpecs.length]; int i = 0; for (DependencySpec spec : dependencySpecs) { final Dependency dependency = spec.getDependency(this); dependencies[i++] = dependency; } return dependencies; } String getMainClass() { return mainClassName; } Package getPackage(final String name) { List loaders = getPathsUnchecked().get(name.replace('.', '/')); if (loaders != null) for (LocalLoader localLoader : loaders) { Package pkg = localLoader.loadPackageLocal(name); if (pkg != null) return pkg; } return null; } Package[] getPackages() { final ArrayList packages = new ArrayList(); final Map> allPaths = getPathsUnchecked(); next: for (String path : allPaths.keySet()) { String packageName = path.replace('/', '.'); for (LocalLoader loader : allPaths.get(path)) { Package pkg = loader.loadPackageLocal(packageName); if (pkg != null) { packages.add(pkg); } continue next; } } return packages.toArray(new Package[packages.size()]); } }