
org.jboss.modules.ModuleLoader Maven / Gradle / Ivy
/*
* 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 static java.security.AccessController.doPrivileged;
import static org.jboss.modules.management.ObjectProperties.property;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.URL;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.jboss.modules.log.ModuleLogger;
import org.jboss.modules.management.DependencyInfo;
import org.jboss.modules.management.ModuleInfo;
import org.jboss.modules.management.ModuleLoaderMXBean;
import org.jboss.modules.management.ObjectProperties;
import org.jboss.modules.management.ResourceLoaderInfo;
import org.jboss.modules.ref.Reaper;
import org.jboss.modules.ref.Reference;
import org.jboss.modules.ref.WeakReference;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
/**
* A repository for modules, from which a module may be loaded by identifier. Module loaders may additionally
* delegate to one or more other module loaders.
*
* @author David M. Lloyd
* @author John Bailey
* @author Richard Opalka
* @author Jason T. Greene
*
* @apiviz.landmark
*/
public class ModuleLoader {
private static final RuntimePermission ML_PERM = new RuntimePermission("canCreateModuleLoader");
private static final RuntimePermission MODULE_REDEFINE_PERM = new RuntimePermission("canRedefineModule");
private static final RuntimePermission MODULE_REDEFINE_ANY_PERM = new RuntimePermission("canRedefineAnyModule");
private static final RuntimePermission MODULE_UNLOAD_ANY_PERM = new RuntimePermission("canUnloadAnyModule");
private static final RuntimePermission MODULE_ITERATE_PERM = new RuntimePermission("canIterateModules");
private static final AtomicInteger SEQ = new AtomicInteger(1);
private static final Class>[] JUST_MODULE_IDENTIFIER = { ModuleIdentifier.class };
private static volatile MBeanReg REG_REF = new TempMBeanReg();
/**
* A constant representing zero module finders.
*/
public static final ModuleFinder[] NO_FINDERS = new ModuleFinder[0];
private final ConcurrentMap moduleMap = new ConcurrentHashMap<>();
private final ModuleFinder[] finders;
private final boolean canRedefine;
private final ModuleLoaderMXBean mxBean;
private final AtomicLong linkTime = new AtomicLong();
private final AtomicLong loadTime = new AtomicLong();
private final AtomicLong classLoadTime = new AtomicLong();
private final AtomicInteger scanCount = new AtomicInteger();
private final AtomicInteger raceCount = new AtomicInteger();
private final AtomicInteger classCount = new AtomicInteger();
private final boolean overridesFindModuleByIdentifier;
private final boolean overridesPreloadModuleByIdentifier;
ModuleLoader(boolean canRedefine, boolean skipRegister) {
this(canRedefine, skipRegister, NO_FINDERS);
}
// Bypass security check for classes in this package
ModuleLoader(boolean canRedefine, boolean skipRegister, ModuleFinder[] finders) {
this.canRedefine = canRedefine;
this.finders = finders;
mxBean = skipRegister ? null : doPrivileged(new PrivilegedAction() {
public ModuleLoaderMXBean run() {
ObjectName objectName;
try {
objectName = new ObjectName("jboss.modules", ObjectProperties.properties(property("type", "ModuleLoader"), property("name", ModuleLoader.this.getClass().getSimpleName() + "-" + SEQ.incrementAndGet())));
} catch (MalformedObjectNameException e) {
return null;
}
try {
MXBeanImpl mxBean = new MXBeanImpl(ModuleLoader.this, objectName);
REG_REF.addMBean(objectName, mxBean);
return mxBean;
} catch (Throwable ignored) {
}
return null;
}
});
// determine if the subclass overrides the crusty old find-module-by-identifier method
boolean foundFindModule = false;
boolean foundPreloadModule = false;
for (Class extends ModuleLoader> clazz = getClass(); clazz != ModuleLoader.class; clazz = clazz.getSuperclass().asSubclass(ModuleLoader.class)) {
for (Method method : clazz.getDeclaredMethods()) {
if ((method.getModifiers() & (Modifier.STATIC | Modifier.PRIVATE)) == 0) {
if (method.getName().equals("findModule")) {
if (ModuleSpec.class.isAssignableFrom(method.getReturnType())) {
if (Arrays.equals(method.getParameterTypes(), JUST_MODULE_IDENTIFIER)) {
foundFindModule = true;
if (foundPreloadModule) break;
}
}
} else if (method.getName().equals("preloadModule")) {
if (Module.class.isAssignableFrom(method.getReturnType())) {
if (Arrays.equals(method.getParameterTypes(), JUST_MODULE_IDENTIFIER)) {
foundPreloadModule = true;
if (foundFindModule) break;
}
}
}
}
}
}
overridesFindModuleByIdentifier = foundFindModule;
overridesPreloadModuleByIdentifier = foundPreloadModule;
}
/**
* Construct a new instance.
*/
protected ModuleLoader() {
this(NO_FINDERS);
}
/**
* Construct a new instance.
*
* @param finders the module finders to search, in order
*/
public ModuleLoader(final ModuleFinder[] finders) {
this(checkPermissions(), false, safeClone(finders));
}
/**
* Construct a new instance.
*
* @param finder the single module finder to search (must not be {@code null})
*/
public ModuleLoader(final ModuleFinder finder) {
this(new ModuleFinder[] { finder });
}
private static ModuleFinder[] safeClone(ModuleFinder[] finders) {
if (finders == null || finders.length == 0) {
return NO_FINDERS;
}
finders = finders.clone();
for (ModuleFinder finder : finders) {
if (finder == null) {
throw new IllegalArgumentException("Module finder cannot be null");
}
}
return finders;
}
private static boolean checkPermissions() {
SecurityManager manager = System.getSecurityManager();
if (manager == null) {
return true;
}
manager.checkPermission(ML_PERM);
try {
manager.checkPermission(MODULE_REDEFINE_PERM);
return true;
} catch (SecurityException e) {
return false;
}
}
/**
* Get the module loader for a class.
*
* @param clazz the class
*
* @return the module loader or {@code null} if the class's class loader does not belong to a module loader.
*/
public static ModuleLoader forClass(Class> clazz) {
final Module module = Module.forClass(clazz);
if (module == null) {
return null;
}
return module.getModuleLoader();
}
/**
* Get the module loader for a class loader.
*
* @param classLoader the class loader
*
* @return the module loader or {@code null} if the class loader does not belong to a module loader.
*/
public static ModuleLoader forClassLoader(ClassLoader classLoader) {
final Module module = Module.forClassLoader(classLoader, true);
if (module == null) {
return null;
}
return module.getModuleLoader();
}
/**
* Get the string representation of this module loader.
*
* @return the string representation
*/
public String toString() {
return String.format("%s@%x for finders %s", getClass().getSimpleName(), hashCode(), Arrays.toString(finders));
}
/**
* Get a string representation of the given module, used in debug output and stack traces. This method may be
* overridden to provide a more detailed description. By default it returns a string consisting of the module's
* name and version (if any).
*
* @param module the module to describe
* @return the description string
*/
public String getModuleDescription(final Module module) {
final Version version = module.getVersion();
return version == null ? module.getName() : module.getName() + "@" + version;
}
/**
* Install JBoss Modules MBeans. WARNING: This method is not intended to be
* called by application code.
*/
public static void installMBeanServer() {
REG_REF.installReal();
}
/**
* Load a module based on an identifier. This method delegates to {@link #preloadModule(String)} and then
* links the returned module if necessary.
*
* @param identifier The module identifier
* @return The loaded Module
* @throws ModuleLoadException if the Module can not be loaded
* @deprecated Use {@link #loadModule(String)} instead.
*/
@Deprecated(forRemoval = true)
public final Module loadModule(ModuleIdentifier identifier) throws ModuleLoadException {
return loadModule(identifier.toString());
}
/**
* Load a module based on an identifier. This method delegates to {@link #preloadModule(String)} and then
* links the returned module if necessary.
*
* @param name The module name
* @return The loaded Module
* @throws ModuleLoadException if the Module can not be loaded
*/
public final Module loadModule(String name) throws ModuleLoadException {
final Module module = preloadModule(name);
if (module == null) {
throw new ModuleNotFoundException(name);
}
module.relinkIfNecessary();
return module;
}
/**
* Iterate the modules which can be located via this module loader.
*
* @param baseIdentifier the identifier to start with, or {@code null} to iterate all modules
* @param recursive {@code true} to find recursively nested modules, {@code false} to only find immediately nested modules
* @return an iterator for the modules in this module finder
* @throws SecurityException if the caller does not have permission to iterate module loaders
* @deprecated Use {@link #iterateModules(String, boolean)} instead.
*/
@Deprecated(forRemoval = true)
public final Iterator iterateModules(final ModuleIdentifier baseIdentifier, final boolean recursive) {
return IteratorUtils.transformingIterator(iterateModules(baseIdentifier == null ? null : baseIdentifier.toString(), recursive), ModuleIdentifier::fromString);
}
/**
* Iterate the modules which can be located via this module loader.
*
* @param baseName the identifier to start with, or {@code null} to iterate all modules; ignored if this module
* loader does not have a concept of nested modules
* @param recursive {@code true} to find recursively nested modules, {@code false} to only find immediately nested
* modules; ignored if this module finder does not have a concept of nested modules
* @return an iterator for the modules in this module finder
* @throws SecurityException if the caller does not have permission to iterate module loaders
*/
public final Iterator iterateModules(final String baseName, final boolean recursive) {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(MODULE_ITERATE_PERM);
}
return new Iterator<>() {
int idx;
Iterator nested;
public boolean hasNext() {
for (;;) {
while (nested == null) {
if (idx == finders.length) {
return false;
}
final ModuleFinder finder = finders[idx++];
if (finder instanceof IterableModuleFinder) {
nested = ((IterableModuleFinder) finder).iterateModules(baseName, recursive, ModuleLoader.this);
}
}
if (! nested.hasNext()) {
nested = null;
} else {
return true;
}
}
}
public String next() {
if (! hasNext()) {
throw new NoSuchElementException();
}
return nested.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* This compatibility method just calls {@code preloadModule(methodIdentifier.toString())} and is usually not used;
* if however a legacy subclass overrides this method, then it will be called instead of {@link #preloadModule(String)},
* and that method will redirect to this one.
*
* @deprecated Use {@link #preloadModule(String)} instead.
*
* @param identifier the module identifier (must not be {@code null})
* @return the load result, or {@code null} if the module is not found
* @throws ModuleLoadException if an error occurs
*/
@Deprecated(forRemoval = true)
protected Module preloadModule(ModuleIdentifier identifier) throws ModuleLoadException {
if (overridesPreloadModuleByIdentifier) {
return loadModuleLocal(identifier);
} else {
return preloadModule(identifier.toString());
}
}
/**
* Preload a module based on an identifier. By default, delegation is only done for system and this method otherwise simply invokes
* {@link #loadModuleLocal(String)}. A delegating module loader may delegate to the appropriate module
* loader based on loader-specific criteria (via the {@link #preloadModule(String, ModuleLoader)} method).
*
* @param name the module identifier
* @return the load result, or {@code null} if the module is not found
* @throws ModuleLoadException if an error occurs
*/
protected Module preloadModule(String name) throws ModuleLoadException {
if (overridesPreloadModuleByIdentifier) {
return preloadModule(ModuleIdentifier.fromString(name));
} else {
return loadModuleLocal(name);
}
}
/**
* Preload an "exported" module based on an identifier. By default this simply delegates to {@link #preloadModule(String)}.
*
* @param name the module name
* @return the load result, or {@code null} if the module is not found
* @throws ModuleLoadException if an error occurs
*/
protected Module preloadExportedModule(String name) throws ModuleLoadException {
return preloadModule(name);
}
/**
* Preload an "exported" module based on an identifier. By default this simply delegates to {@link #preloadModule(String)}.
*
* @param identifier the module identifier
* @return the load result, or {@code null} if the module is not found
* @throws ModuleLoadException if an error occurs
* @deprecated Use {@link #preloadModule(String)} instead.
*/
@Deprecated(forRemoval = true)
protected Module preloadExportedModule(ModuleIdentifier identifier) throws ModuleLoadException {
return preloadModule(identifier.getName());
}
/**
* Utility method to delegate to another module loader, accessible from subclasses. The delegate module loader
* will be queried for exported modules only.
*
* @param name the module name
* @param moduleLoader the module loader to delegate to
* @return the delegation result
* @throws ModuleLoadException if an error occurs
*/
protected static Module preloadModule(String name, ModuleLoader moduleLoader) throws ModuleLoadException {
return moduleLoader.preloadExportedModule(name);
}
/**
* Utility method to delegate to another module loader, accessible from subclasses. The delegate module loader
* will be queried for exported modules only.
*
* @param identifier the module identifier
* @param moduleLoader the module loader to delegate to
* @return the delegation result
* @throws ModuleLoadException if an error occurs
* @deprecated Use {@link #preloadModule(String, ModuleLoader)} instead.
*/
@Deprecated(forRemoval = true)
protected static Module preloadModule(ModuleIdentifier identifier, ModuleLoader moduleLoader) throws ModuleLoadException {
return moduleLoader.preloadExportedModule(identifier.toString());
}
/**
* Try to load a module from this module loader. Returns {@code null} if the module is not found. The returned
* module may not yet be resolved. The returned module may have a different name than the given identifier if
* the identifier is an alias for another module.
*
* @param moduleIdentifier the module identifier
* @return the module
* @throws ModuleLoadException if an error occurs while loading the module
* @deprecated Use {@link #loadModuleLocal(String)} instead.
*/
@Deprecated(forRemoval = true)
protected final Module loadModuleLocal(ModuleIdentifier moduleIdentifier) throws ModuleLoadException {
return loadModuleLocal(moduleIdentifier.toString());
}
/**
* Try to load a module from this module loader. Returns {@code null} if the module is not found. The returned
* module may not yet be resolved. The returned module may have a different name than the given identifier if
* the identifier is an alias for another module.
*
* @param name the module name
* @return the module
* @throws ModuleLoadException if an error occurs while loading the module
*/
protected final Module loadModuleLocal(String name) throws ModuleLoadException {
FutureModule futureModule = moduleMap.get(name);
if (futureModule != null) {
return futureModule.getModule();
}
FutureModule newFuture = new FutureModule(name);
futureModule = moduleMap.putIfAbsent(name, newFuture);
if (futureModule != null) {
return futureModule.getModule();
}
boolean ok = false;
try {
final ModuleLogger log = Module.log;
log.trace("Locally loading module %s from %s", name, this);
final long startTime = Metrics.getCurrentCPUTime();
final ModuleSpec moduleSpec = findModule(name);
loadTime.addAndGet(Metrics.getCurrentCPUTime() - startTime);
if (moduleSpec == null) {
log.trace("Module %s not found from %s", name, this);
return null;
}
if (! moduleSpec.getName().equals(name)) {
throw new ModuleLoadException("Module loader found a module with the wrong name");
}
final Module module;
if (moduleSpec instanceof AliasModuleSpec) {
final AliasModuleSpec aliasModuleSpec = (AliasModuleSpec) moduleSpec;
Module aliasedModule;
while (true) {
aliasedModule = loadModuleLocal(aliasModuleSpec.getAliasName());
if (aliasedModule == null) {
throw new ModuleLoadException("Alias module " + name + " is referencing not existing module");
}
final Set aliases = aliasedModule.aliases;
if (aliases == null) continue; // race condition detected, retry
synchronized (aliases) {
if (aliases == aliasedModule.aliases) {
// no race-condition with removal, go ahead
aliases.add(moduleSpec.getName());
newFuture.setModule(module = aliasedModule);
log.trace("Added module %s as alias of %s from %s", moduleSpec.getName(), aliasModuleSpec.getAliasName(), this);
ok = true;
break;
}
}
}
} else {
module = defineModule((ConcreteModuleSpec) moduleSpec, newFuture);
log.trace("Loaded module %s from %s", name, this);
ok = true;
}
return module;
} finally {
if (! ok) {
newFuture.setModule(null);
moduleMap.remove(name, newFuture);
}
}
}
/**
* Find an already-loaded module, returning {@code null} if the module isn't currently loaded. May block
* while the loaded state of the module is in question (if the module is being concurrently loaded from another
* thread, for example).
*
* @param identifier the module identifier
* @return the module, or {@code null} if it wasn't found
* @deprecated Use {@link #findLoadedModuleLocal(String)} instead.
*/
@Deprecated(forRemoval = true)
protected final Module findLoadedModuleLocal(ModuleIdentifier identifier) {
return findLoadedModuleLocal(identifier.toString());
}
/**
* Find an already-loaded module, returning {@code null} if the module isn't currently loaded. May block
* while the loaded state of the module is in question (if the module is being concurrently loaded from another
* thread, for example).
*
* @param name the module identifier
* @return the module, or {@code null} if it wasn't found
*/
protected final Module findLoadedModuleLocal(String name) {
FutureModule futureModule = moduleMap.get(name);
if (futureModule != null) {
try {
return futureModule.getModule();
} catch (ModuleNotFoundException e) {
return null;
}
} else {
return null;
}
}
/**
* Unload a module from this module loader. Note that this has no effect on existing modules which refer to the
* module being unloaded. Also, only modules from the current module loader can be unloaded. Unloading the same
* module more than once has no additional effect. This method only removes the mapping for the module; any running
* threads which are currently accessing or linked to the module will continue to function, however attempts to load
* this module will fail until a new module is loaded with the same name. Once this happens, if all references to
* the previous module are not cleared, the same module may be loaded more than once, causing possible class duplication
* and class cast exceptions if proper care is not taken.
*
* @param module the module to unload
* @throws SecurityException if an attempt is made to unload a module which does not belong to this module loader
* @throws SecurityException if the module was not defined by this module loader
* @deprecated use {@link #unloadModuleLocal(String,Module)} instead
*/
@Deprecated
protected final void unloadModuleLocal(Module module) throws SecurityException {
if (module == null) {
throw new NullPointerException("Module parameter cannot be null");
}
unloadModuleLocal(module.getName(), module);
}
/**
* Unload a module from this module loader. Note that this has no effect on existing modules which refer to the
* module being unloaded. Also, only modules from the current module loader can be unloaded. Unloading the same
* module more than once has no additional effect. This method only removes the mapping for the module; any running
* threads which are currently accessing or linked to the module will continue to function, however attempts to load
* this module will fail until a new module is loaded with the same name. Once this happens, if all references to
* the previous module are not cleared, the same module may be loaded more than once, causing possible class duplication
* and class cast exceptions if proper care is not taken.
*
* If the given {@code moduleName} is an alias of the given {@code module}
* then only alias mapping to {@code module} is removed. Aliased {@code module}
* with its canonical name and other aliases mapping is left intact.
*
* If the given {@code moduleName} is the canonical {@code module} name
* then all its alias plus canonical {@code moduleName} association mappings are removed.
*
* @param moduleId the module identifier
* @param module the module to unload
* @return true iff module was unloaded
* @throws SecurityException if an attempt is made to unload a module which does not belong to this module loader
* @throws SecurityException if the module was not defined by this module loader
*/
protected final boolean unloadModuleLocal(final String moduleId, final Module module) throws SecurityException {
if (moduleId == null || module == null) {
throw new NullPointerException("Method parameters cannot be null");
}
final ModuleLoader moduleLoader = module.getModuleLoader();
if (moduleLoader != this) {
throw new SecurityException("Attempted to unload " + module + " from a different module loader");
}
final FutureModule futureModule = moduleMap.get(moduleId);
if (futureModule != null && futureModule.module == module) {
final Set aliases = module.aliases;
if (aliases != null) {
synchronized (aliases) {
if (aliases.remove(moduleId)) {
// unregistered alias from aliased module
} else {
// cleaning aliased module and all its aliases
module.aliases = null;
for (String alias : aliases) {
moduleMap.remove(alias);
}
}
}
}
return moduleMap.remove(moduleId, futureModule);
}
return false;
}
/**
* This compatibility method just calls {@code findModule(methodIdentifier.toString())} and is usually not used;
* if however a legacy subclass overrides this method, then it will be called instead of {@link #findModule(String)},
* and that method will redirect to this one.
*
* @deprecated Use {@link #findModule(String)} instead.
*
* @param moduleIdentifier the module identifier
* @return the module specification
* @throws ModuleLoadException
*/
@Deprecated(forRemoval = true)
protected ModuleSpec findModule(final ModuleIdentifier moduleIdentifier) throws ModuleLoadException {
if (overridesFindModuleByIdentifier) {
return findModule0(moduleIdentifier.toString());
} else {
return findModule(moduleIdentifier.toString());
}
}
/**
* Find a Module's specification in this ModuleLoader by its name. This can be overriden by sub-classes to
* implement the Module loading strategy for this loader. The default implementation iterates the module finders
* provided during construction.
*
* If no module is found in this module loader with the given identifier, then this method should return {@code
* null}. If the module is found but some problem occurred (for example, a transitive dependency failed to load)
* then this method should throw a {@link ModuleLoadException} of the relevant type.
*
* @param name the module name
* @return the module specification, or {@code null} if no module is found with the given identifier
* @throws ModuleLoadException if any problems occur finding the module
*/
protected ModuleSpec findModule(final String name) throws ModuleLoadException {
if (overridesFindModuleByIdentifier) {
return findModule(ModuleIdentifier.fromString(name));
} else {
return findModule0(name);
}
}
private ModuleSpec findModule0(final String name) throws ModuleLoadException {
for (ModuleFinder finder : finders) {
if (finder != null) {
final ModuleSpec spec = finder.findModule(name, this);
if (spec != null) {
return spec;
}
}
}
return null;
}
/**
* Get the module finders configured for this module loader.
*
* @return the module finders
*/
protected final ModuleFinder[] getFinders() {
return finders.length > 0 ? finders.clone() : NO_FINDERS;
}
/**
* Defines a Module based on a specification. May only be called from {@link #loadModuleLocal(String)}.
*
* @param moduleSpec The module specification to create the Module from
* @param futureModule the future module to populate
* @return The defined Module
* @throws ModuleLoadException If any dependent modules can not be loaded
*/
private Module defineModule(final ConcreteModuleSpec moduleSpec, final FutureModule futureModule) throws ModuleLoadException {
try {
return doPrivileged(new PrivilegedExceptionAction() {
public Module run() {
final ModuleLogger log = Module.log;
final String name = moduleSpec.getName();
final Module module = new Module(moduleSpec, ModuleLoader.this);
module.getClassLoaderPrivate().recalculate();
module.setDependencies(moduleSpec.getDependenciesInternal());
log.moduleDefined(name, ModuleLoader.this);
try {
futureModule.setModule(module);
return module;
} catch (RuntimeException | Error e) {
log.trace(e, "Failed to load module %s", name);
throw e;
}
}
});
} catch (PrivilegedActionException pe) {
try {
throw pe.getException();
} catch (RuntimeException | ModuleLoadException e) {
throw e;
} catch (Exception e) {
throw new UndeclaredThrowableException(e);
}
}
}
/**
* Refreshes the paths provided by resource loaders associated with the
* specified Module. This is an advanced method that is intended to be
* called on modules that have a resource loader implementation that has
* changed and is returning different paths.
*
* @param module the module to refresh
* @throws SecurityException if the module was not defined by this module loader, or if the module loader does not
* have the required permissions associated with it
*/
protected void refreshResourceLoaders(Module module) {
if (! canRedefine) {
throw new SecurityException("Module redefinition requires canRedefineModule permission");
}
if (module.getModuleLoader() != this) {
throw new SecurityException("Module is not defined by this module loader");
}
module.getClassLoaderPrivate().recalculate();
}
/**
* Replaces the resources loaders for the specified module and refreshes the
* internal path list that is derived from the loaders. This is an advanced
* method that should be used carefully, since it alters a live module.
* Modules that import resources from the specified module will not
* automatically be updated to reflect the change. For this to occur
* {@link #relink(Module)} must be called on all of them.
*
* @param module the module to update and refresh
* @param loaders the new collection of loaders the module should use
* @throws SecurityException if the module was not defined by this module loader, or if the module loader does not
* have the required permissions associated with it
*/
protected void setAndRefreshResourceLoaders(Module module, Collection loaders) {
if (! canRedefine) {
throw new SecurityException("Module redefinition requires canRedefineModule permission");
}
if (module.getModuleLoader() != this) {
throw new SecurityException("Module is not defined by this module loader");
}
module.getClassLoaderPrivate().setResourceLoaders(loaders.toArray(ResourceLoaderSpec[]::new));
}
/**
* Relinks the dependencies associated with the specified Module. This is an
* advanced method that is intended to be called on all modules that
* directly or indirectly import dependencies that are re-exported by a module
* that has recently been updated and relinked via
* {@link #setAndRelinkDependencies(Module, java.util.List)}.
*
* @param module the module to relink
* @throws ModuleLoadException if relinking failed
* @throws SecurityException if the module was not defined by this module loader, or if the module loader does not
* have the required permissions associated with it
*/
protected void relink(Module module) throws ModuleLoadException {
if (! canRedefine) {
throw new SecurityException("Module redefinition requires canRedefineModule permission");
}
if (module.getModuleLoader() != this) {
throw new SecurityException("Module is not defined by this module loader");
}
module.relink();
}
/**
* Replaces the dependencies for the specified module and relinks against
* the new modules This is an advanced method that should be used carefully,
* since it alters a live module. Modules that import dependencies that are
* re-exported from the specified module will not automatically be updated
* to reflect the change. For this to occur {@link #relink(Module)} must be
* called on all of them.
*
* @param module the module to update and relink
* @param dependencies the new dependency list
* @throws ModuleLoadException if relinking failed
* @throws SecurityException if the module was not defined by this module loader, or if the module loader does not
* have the required permissions associated with it
*/
protected void setAndRelinkDependencies(Module module, List dependencies) throws ModuleLoadException {
if (! canRedefine) {
throw new SecurityException("Module redefinition requires canRedefineModule permission");
}
if (module.getModuleLoader() != this) {
throw new SecurityException("Module is not defined by this module loader");
}
module.setDependencies(dependencies);
module.relinkIfNecessary();
}
/**
* Get the current dependency list for a module which was defined by this module loader, without any access checks.
*
* @return the current dependency list for the module
* @throws SecurityException if the module was not defined by this module loader
*/
protected DependencySpec[] getDependencies(Module module) {
if (module.getModuleLoader() != this) {
throw new SecurityException("Module is not defined by this module loader");
}
return module.getDependencySpecsInternal().clone();
}
void addLinkTime(long amount) {
if (amount != 0L) linkTime.addAndGet(amount);
}
void addClassLoadTime(final long time) {
if (time != 0L) classLoadTime.addAndGet(time);
}
void incScanCount() {
if (Metrics.ENABLED) scanCount.getAndIncrement();
}
void incRaceCount() {
if (Metrics.ENABLED) raceCount.getAndIncrement();
}
void incClassCount() {
if (Metrics.ENABLED) classCount.getAndIncrement();
}
static final Object NOT_FOUND = new Object();
static final class FutureModule {
final String name;
volatile Object module;
FutureModule(final String name) {
this.name = name;
}
Module getModule() throws ModuleNotFoundException {
boolean intr = false;
try {
Object module = this.module;
if (module == null) synchronized (this) {
while ((module = this.module) == null) {
try {
wait();
} catch (InterruptedException e) {
intr = true;
}
}
}
if (module == NOT_FOUND) return null;
return (Module) module;
} finally {
if (intr) Thread.currentThread().interrupt();
}
}
void setModule(Module m) {
synchronized (this) {
module = m == null ? NOT_FOUND : m;
notifyAll();
}
}
}
private static final Reaper reaper = new Reaper<>() {
public void reap(final Reference reference) {
REG_REF.removeMBean(reference.getAttachment());
}
};
static final class MXBeanImpl implements ModuleLoaderMXBean {
private final Reference reference;
MXBeanImpl(final ModuleLoader moduleLoader, final ObjectName objectName) {
reference = new WeakReference<>(moduleLoader, objectName, reaper);
}
public String getDescription() {
return getModuleLoader().toString();
}
public long getLinkTime() {
return getModuleLoader().linkTime.get();
}
public long getLoadTime() {
return getModuleLoader().loadTime.get();
}
public long getClassDefineTime() {
return getModuleLoader().classLoadTime.get();
}
public int getScanCount() {
return getModuleLoader().scanCount.get();
}
public int getLoadedModuleCount() {
return getModuleLoader().moduleMap.size();
}
public int getRaceCount() {
return getModuleLoader().raceCount.get();
}
public int getClassCount() {
return getModuleLoader().classCount.get();
}
public List queryLoadedModuleNames() {
final ModuleLoader loader = getModuleLoader();
final Set names = loader.moduleMap.keySet();
final ArrayList list = new ArrayList<>(names);
list.sort(Comparator.naturalOrder());
return list;
}
public String dumpAllModuleInformation() {
final StringBuilder b = new StringBuilder();
for (String name : queryLoadedModuleNames()) {
doDumpModuleInformation(name, b);
}
return b.toString();
}
public String dumpModuleInformation(final String name) {
final StringBuilder b = new StringBuilder();
doDumpModuleInformation(name, b);
return b.toString();
}
private void doDumpModuleInformation(final String name, final StringBuilder b) {
ModuleInfo description = getModuleDescription(name);
b.append("Module ").append(name).append('\n');
b.append(" Class loader: ").append(description.getClassLoader()).append('\n');
String fallbackLoader = description.getFallbackLoader();
if (fallbackLoader != null) b.append(" Fallback loader: ").append(fallbackLoader).append('\n');
String mainClass = description.getMainClass();
if (mainClass != null) b.append(" Main Class: ").append(mainClass).append('\n');
List loaders = description.getResourceLoaders();
b.append(" Resource Loaders:\n");
for (ResourceLoaderInfo loader : loaders) {
b.append(" Loader Type: ").append(loader.getType()).append('\n');
b.append(" Paths:\n");
for (String path : loader.getPaths()) {
b.append(" ").append(path).append('\n');
}
}
b.append(" Dependencies:\n");
for (DependencyInfo dependencyInfo : description.getDependencies()) {
b.append(" Type: ").append(dependencyInfo.getDependencyType()).append('\n');
String moduleName = dependencyInfo.getModuleName();
if (moduleName != null) {
b.append(" Module Name: ").append(moduleName).append('\n');
}
if (dependencyInfo.isOptional()) b.append(" (optional)\n");
b.append(" Export Filter: ").append(dependencyInfo.getExportFilter()).append('\n');
b.append(" Import Filter: ").append(dependencyInfo.getImportFilter()).append('\n');
String localLoader = dependencyInfo.getLocalLoader();
if (localLoader != null) {
b.append(" Local Loader: ").append(localLoader).append('\n');
b.append(" Paths:\n");
for (String path : dependencyInfo.getLocalLoaderPaths()) {
b.append(" ").append(path).append('\n');
}
}
}
}
public boolean unloadModule(final String name) {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(MODULE_UNLOAD_ANY_PERM);
}
final ModuleLoader loader = getModuleLoader();
final Module module = loader.findLoadedModuleLocal(name);
if (module == null) {
return false;
} else {
loader.unloadModuleLocal(name, module);
return true;
}
}
public void refreshResourceLoaders(final String name) {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(MODULE_REDEFINE_ANY_PERM);
}
final ModuleLoader loader = getModuleLoader();
final Module module = loadModule(name, loader);
loader.refreshResourceLoaders(module);
}
public void relink(final String name) {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(MODULE_REDEFINE_ANY_PERM);
}
final ModuleLoader loader = getModuleLoader();
final Module module = loadModule(name, loader);
try {
loader.relink(module);
} catch (ModuleLoadException e) {
throw new IllegalStateException("Module load failure for module " + name + ": " + e);
}
}
public List getDependencies(final String name) {
final ModuleLoader loader = getModuleLoader();
final Module module = loadModule(name, loader);
return doGetDependencies(module);
}
private List doGetDependencies(final Module module) {
Dependency[] dependencies = module.getDependenciesInternal();
if (dependencies == null) {
return Collections.emptyList();
}
ArrayList list = new ArrayList<>(dependencies.length);
for (Dependency dependency : dependencies) {
final String dependencyType = dependency.getClass().getSimpleName();
final String exportFilter = dependency.getExportFilter().toString();
final String importFilter = dependency.getImportFilter().toString();
final DependencyInfo info;
if (dependency instanceof LocalDependency) {
final LocalDependency localDependency = (LocalDependency) dependency;
ArrayList pathList = new ArrayList<>(localDependency.getPaths());
Collections.sort(pathList);
info = new DependencyInfo(dependencyType, exportFilter, importFilter, null, null, false, localDependency.getLocalLoader().toString(), pathList);
} else if (dependency instanceof ModuleDependency) {
final ModuleDependency moduleDependency = (ModuleDependency) dependency;
info = new DependencyInfo(dependencyType, exportFilter, importFilter, moduleDependency.getModuleLoader().mxBean, moduleDependency.getName(), moduleDependency.isOptional(), null, null);
} else {
info = new DependencyInfo(dependencyType, exportFilter, importFilter, null, null, false, null, null);
}
list.add(info);
}
return list;
}
public List getResourceLoaders(final String name) {
ModuleLoader loader = getModuleLoader();
final Module module = loadModule(name, loader);
return doGetResourceLoaders(module);
}
private List doGetResourceLoaders(final Module module) {
final ModuleClassLoader classLoader = module.getClassLoaderPrivate();
final ResourceLoader[] loaders = classLoader.getResourceLoaders();
final ArrayList list = new ArrayList<>(loaders.length);
for (ResourceLoader resourceLoader : loaders) {
list.add(new ResourceLoaderInfo(resourceLoader.getClass().getName(), String.valueOf(resourceLoader.getLocation()), new ArrayList<>(resourceLoader.getPaths())));
}
return list;
}
public ModuleInfo getModuleDescription(final String name) {
ModuleLoader loader = getModuleLoader();
final Module module = loadModule(name, loader);
final List dependencies = doGetDependencies(module);
final List resourceLoaders = doGetResourceLoaders(module);
final LocalLoader fallbackLoader = module.getFallbackLoader();
final String fallbackLoaderString = fallbackLoader == null ? null : fallbackLoader.toString();
return new ModuleInfo(module.getName(), module.getModuleLoader().mxBean, dependencies, resourceLoaders, module.getMainClass(), module.getClassLoaderPrivate().toString(), fallbackLoaderString);
}
public SortedMap> getModulePathsInfo(final String name, final boolean exports) {
ModuleLoader loader = getModuleLoader();
final Module module = loadModule(name, loader);
final Map> paths;
try {
paths = module.getPathsUnchecked();
} catch (ModuleLoadError e) {
throw new IllegalArgumentException("Error loading module " + name + ": " + e);
}
final TreeMap> result = new TreeMap<>();
for (Map.Entry> entry : paths.entrySet()) {
final String path = entry.getKey();
final List loaders = entry.getValue();
if (loaders.isEmpty()) {
result.put(path, Collections.emptyList());
} else if (loaders.size() == 1) {
result.put(path, Collections.singletonList(loaders.get(0).toString()));
} else {
final ArrayList list = new ArrayList<>();
for (LocalLoader localLoader : loaders) {
list.add(localLoader.toString());
}
result.put(path, list);
}
}
return result;
}
public String getClassLocation(final String moduleName, final String className) {
final ModuleLoader loader = getModuleLoader();
final Module module = loadModule(moduleName, loader);
final Class> clazz;
try {
clazz = Class.forName(className, false, module.getClassLoaderPrivate());
} catch (ClassNotFoundException e) {
return null;
}
final ProtectionDomain pd = clazz.getProtectionDomain();
if (pd == null) {
return null;
}
final CodeSource cs = pd.getCodeSource();
if (cs == null) {
return null;
}
final URL url = cs.getLocation();
if (url == null) {
return null;
}
return url.toString();
}
private Module loadModule(final String name, final ModuleLoader loader) {
try {
final Module module = loader.findLoadedModuleLocal(name);
if (module == null) {
throw new IllegalArgumentException("Module " + name + " not found");
}
return module;
} catch (ModuleLoadError e) {
throw new IllegalArgumentException("Error loading module " + name + ": " + e);
}
}
private ModuleLoader getModuleLoader() {
final ModuleLoader loader = reference.get();
if (loader == null) {
throw new IllegalStateException("Module Loader is gone");
}
return loader;
}
}
private interface MBeanReg {
boolean addMBean(ObjectName name, Object bean);
void removeMBean(ObjectName name);
void installReal();
}
private static final class TempMBeanReg implements MBeanReg {
private final Map mappings = new LinkedHashMap<>();
public boolean addMBean(final ObjectName name, final Object bean) {
if (bean == null) {
throw new IllegalArgumentException("bean is null");
}
synchronized (ModuleLoader.class) {
if (REG_REF == this) {
return mappings.put(name, bean) == null;
} else {
return REG_REF.addMBean(name, bean);
}
}
}
public void removeMBean(final ObjectName name) {
synchronized (ModuleLoader.class) {
if (REG_REF == this) {
mappings.remove(name);
} else {
REG_REF.removeMBean(name);
}
}
}
public void installReal() {
synchronized (ModuleLoader.class) {
RealMBeanReg real = new RealMBeanReg();
if (REG_REF == this) {
REG_REF = real;
for (Map.Entry entry : mappings.entrySet()) {
real.addMBean(entry.getKey(), entry.getValue());
}
mappings.clear();
}
}
}
}
private static final class RealMBeanReg implements MBeanReg {
private final MBeanServer server;
RealMBeanReg() {
server = doPrivileged((PrivilegedAction) ManagementFactory::getPlatformMBeanServer);
}
public boolean addMBean(final ObjectName name, final Object bean) {
try {
server.registerMBean(bean, name);
return true;
} catch (Throwable e) {
}
return false;
}
public void removeMBean(final ObjectName name) {
try {
server.unregisterMBean(name);
} catch (Throwable e) {
}
}
public void installReal() {
// ignore
}
}
}