/*
* JBoss, Home of Professional Open Source.
* Copyright 2007, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.classloading.spi.dependency;
import java.io.IOException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.jboss.classloader.spi.ClassLoaderSystem;
import org.jboss.classloader.spi.DelegateLoader;
import org.jboss.classloader.spi.ParentPolicy;
import org.jboss.classloader.spi.filter.ClassFilter;
import org.jboss.classloading.spi.helpers.NameAndVersionSupport;
import org.jboss.classloading.spi.metadata.Capability;
import org.jboss.classloading.spi.metadata.ClassLoadingMetaDataFactory;
import org.jboss.classloading.spi.metadata.ExportAll;
import org.jboss.classloading.spi.metadata.ExportPackages;
import org.jboss.classloading.spi.metadata.OptionalPackages;
import org.jboss.classloading.spi.metadata.Requirement;
import org.jboss.classloading.spi.visitor.ResourceFilter;
import org.jboss.classloading.spi.visitor.ResourceVisitor;
import org.jboss.dependency.spi.ControllerContext;
import org.jboss.dependency.spi.ControllerState;
/**
* Module.
*
* @author <a href="adrian@jboss.org">Adrian Brock</a>
* @version $Revision: 1.1 $
*/
public abstract class Module extends NameAndVersionSupport
{
/** The modules by classloader */
private static Map<ClassLoader, Module> modulesByClassLoader = new ConcurrentHashMap<ClassLoader, Module>();
/** The context name */
private String contextName;
/** Our cached capabilities */
private List<Capability> capabilities;
/** Our cached requirements */
private List<Requirement> requirements;
/** The controller context */
private ControllerContext context;
/** The domain */
private Domain domain;
/** The classloading space */
private ClassLoadingSpace space;
/** The requirements */
private List<RequirementDependencyItem> requirementDependencies;
/**
* Register a classloader for a module
*
* @param module the module
* @param classLoader the classloader
* @throws IllegalArgumentException for a null parameter
*/
protected static void registerModuleClassLoader(Module module, ClassLoader classLoader)
{
if (module == null)
throw new IllegalArgumentException("Null module");
if (classLoader == null)
throw new IllegalArgumentException("Null classloader");
modulesByClassLoader.put(classLoader, module);
}
/**
* Register a classloader for a module
*
* @param module the module
* @param classLoader the classloader
* @throws IllegalArgumentException for a null parameter
*/
protected static void unregisterModuleClassLoader(Module module, ClassLoader classLoader)
{
if (module == null)
throw new IllegalArgumentException("Null module");
if (classLoader == null)
throw new IllegalArgumentException("Null classloader");
modulesByClassLoader.remove(classLoader);
}
/**
* Create a new Module with the default version
*
* @param name the name
* @throws IllegalArgumentException for a null parameter
*/
public Module(String name)
{
this(name, name, null);
}
/**
* Create a new Module with the given version
*
* @param name the name
* @param version pass null for the default version
* @throws IllegalArgumentException for a null parameter
*/
public Module(String name, Object version)
{
this(name, name, version);
}
/**
* Create a new Module with the given version
*
* @param name the name
* @param contextName the real name of the module in the controller
* @param version pass null for the default version
* @throws IllegalArgumentException for a null parameter
*/
public Module(String name, String contextName, Object version)
{
super(name, version);
if (contextName == null)
contextName = name + version.toString();
this.contextName = contextName;
}
/**
* Get the context name
*
* @return the context name
*/
public String getContextName()
{
return contextName;
}
/**
* Get the domain.
*
* @return the domain.
*/
Domain getDomain()
{
return domain;
}
void setDomain(Domain domain)
{
this.domain = domain;
}
Domain checkDomain()
{
Domain result = domain;
if (result == null)
throw new IllegalStateException("Domain is not set for " + this);
return result;
}
/**
* Whether this is a valid module
*
* @return true when valid
*/
public boolean isValid()
{
return domain != null;
}
/**
* Get the domain name.
*
* @return the domain name.
*/
public String getDomainName()
{
return null;
}
/**
* Get the determined domain name.
*
* @return the determined domain.
*/
public String getDeterminedDomainName()
{
String domainName = getDomainName();
if (domainName == null)
domainName = ClassLoaderSystem.DEFAULT_DOMAIN_NAME;
return domainName;
}
/**
* Get the parent domain name.
*
* @return the parent domain name.
*/
public String getParentDomainName()
{
return null;
}
/**
* Get the determined parentDomain name.
*
* @return the parentDomain.
*/
public String getDeterminedParentDomainName()
{
String parentDomain = getParentDomainName();
if (parentDomain == null)
{
if (ClassLoaderSystem.DEFAULT_DOMAIN_NAME.equals(getDeterminedDomainName()) == false)
return ClassLoaderSystem.DEFAULT_DOMAIN_NAME;
}
return parentDomain;
}
/**
* Get the classloading space.
*
* @return the space.
*/
ClassLoadingSpace getClassLoadingSpace()
{
return space;
}
/**
* Set the classloading space.
*
* @param space the space.
*/
void setClassLoadingSpace(ClassLoadingSpace space)
{
this.space = space;
}
/**
* Get the export all for the module
*
* @return the export all
*/
public ExportAll getExportAll()
{
return null;
}
/**
* Get a filter for the included packages
*
* @return the excluded packages
*/
public ClassFilter getIncluded()
{
return null;
}
/**
* Get a filter for the excluded packages
*
* @return the excluded packages
*/
public ClassFilter getExcluded()
{
return null;
}
/**
* Get a filter for the excluded export packages
*
* @return the excluded packages
*/
public ClassFilter getExcludedExport()
{
return null;
}
/**
* Get the import all for the module
*
* @return the import all
*/
public boolean isImportAll()
{
return false;
}
/**
* Get delegate policy
*
* @return the delegation policy
*/
public boolean isJ2seClassLoadingCompliance()
{
return true;
}
public ParentPolicy getDeterminedParentPolicy()
{
if (isJ2seClassLoadingCompliance())
return ParentPolicy.BEFORE;
else
return ParentPolicy.AFTER_BUT_ONLY_JAVA_BEFORE;
}
/**
* Whether to cache<p>
*
* @return true to cache
*/
protected boolean isCacheable()
{
return true;
}
/**
* Whether to cache misses<p>
*
* @return true to cache misses
*/
protected boolean isBlackListable()
{
return true;
}
/**
* Find the module for a classloader
*
* @param cl the classloader
* @return the module or null if the classloader does not correspond to a registered module classloader
* @throws SecurityException if the caller doesn't have <code>new RuntimePermision("getClassLoader")</code>
*/
static Module getModuleForClassLoader(ClassLoader cl)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new RuntimePermission("getClassLoader"));
// Determine the module (if any) for the classloader
if (cl != null)
return modulesByClassLoader.get(cl);
// Unknown
return null;
}
/**
* Find the module that loads a class
*
* @param className the class name
* @return the module or null if the class is not loaded by a registered module classloader
* @throws ClassNotFoundException when the class is not found
* @throws IllegalStateException when the module is not associated with a classloader
*/
public Module getModuleForClass(String className) throws ClassNotFoundException
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new RuntimePermission("getClassLoader"));
ClassLoader cl = getClassLoaderForClass(className);
// Determine the module (if any) for the classloader
if (cl != null)
return modulesByClassLoader.get(cl);
// Unknown
return null;
}
/**
* Get the classloader for a class name
*
* @param className the class name
* @return the class
* @throws ClassNotFoundException when the class is not found
* @throws IllegalStateException when the module is not associated with a classloader
*/
protected ClassLoader getClassLoaderForClass(String className) throws ClassNotFoundException
{
// Determine the classloader for this class
final Class<?> clazz = loadClass(className);
SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>()
{
public ClassLoader run()
{
return clazz.getClassLoader();
}
});
}
return clazz.getClassLoader();
}
/**
* Load a class for this module
*
* @param className the class name
* @return the class
* @throws ClassNotFoundException when the class is not found
* @throws IllegalStateException when the module is not associated with a classloader
*/
public Class<?> loadClass(String className) throws ClassNotFoundException
{
ClassLoader classLoader = getClassLoader();
if (classLoader == null)
throw new IllegalStateException("No classloader for this module " + this);
return classLoader.loadClass(className);
}
/**
* Get a resource for this module
*
* @param resourceName the resource name
* @return the class
* @throws IllegalStateException when the module is not associated with a classloader
*/
public URL getResource(String resourceName)
{
ClassLoader classLoader = getClassLoader();
if (classLoader == null)
throw new IllegalStateException("No classloader for this module " + this);
return classLoader.getResource(resourceName);
}
/**
* Get resources for this module
*
* @param resourceName the resource name
* @return the class
* @throws IOException for an error
* @throws IllegalStateException when the module is not associated with a classloader
*/
public Enumeration<URL> getResources(String resourceName) throws IOException
{
ClassLoader classLoader = getClassLoader();
if (classLoader == null)
throw new IllegalStateException("No classloader for this module " + this);
return classLoader.getResources(resourceName);
}
/**
* Get the classloader for this module
*
* @return the classloader
*/
protected ClassLoader getClassLoader()
{
return null;
}
/**
* Visit the resources in this module
* using the filter defined on the visitor
*
* @param visitor the visitor
*/
public void visit(ResourceVisitor visitor)
{
if (visitor == null)
throw new IllegalArgumentException("Null visitor");
visit(visitor, visitor.getFilter());
}
/**
* Visit the resources in this module
* using the given filter
*
* @param visitor the visitor
* @param filter the filter
*/
public void visit(ResourceVisitor visitor, ResourceFilter filter)
{
visit(visitor, filter, null);
}
/**
* Visit the resources in this module using the given filter(s)
* <p/>
* Typically the filter is used to determine which types of files to visit, e.g. .class files.
* While the recurseFilter determines which jars/directories to recurse into.
*
* @param visitor the visitor
* @param filter the filter
* @param recurseFilter the recursion filter (null means recurse into everything)
* @param urls the urls we should visit
*/
public void visit(ResourceVisitor visitor, ResourceFilter filter, ResourceFilter recurseFilter, URL... urls)
{
throw new UnsupportedOperationException("The module " + getContextName() + " does not support filtering: " + getClass().getName());
}
/**
* Get the delegate loaders for this module
*
* @return the delegates
*/
public List<? extends DelegateLoader> getDelegates()
{
if (requirementDependencies == null || requirementDependencies.isEmpty())
return null;
List<DelegateLoader> result = new CopyOnWriteArrayList<DelegateLoader>();
List<DelegateLoader> dynamic = new CopyOnWriteArrayList<DelegateLoader>();
Set<Module> visited = new HashSet<Module>();
addDelegates(this, result, dynamic, visited, false);
// Make sure the dynamic delegates are last
result.addAll(dynamic);
return result;
}
/**
* Get the dependency items
*
* @return the depenency items
*/
protected List<RequirementDependencyItem> getRequirementDependencyItems()
{
return requirementDependencies;
}
/**
* Add delegates
*
* @param module the module to add delegates from
* @param delegates the current list of delegates
* @param dynamic the dynamic delegates
* @param visited the visited modules
* @param reExport whether to only add re-exports
*/
protected void addDelegates(Module module, List<DelegateLoader> delegates, List<DelegateLoader> dynamic, Set<Module> visited, boolean reExport)
{
// Check whether we already did this module
if (visited.contains(module))
return;
visited.add(module);
List<RequirementDependencyItem> dependencies = module.getRequirementDependencyItems();
if (dependencies == null || dependencies.isEmpty())
return;
for (RequirementDependencyItem item : dependencies)
{
Requirement requirement = item.getRequirement();
// If we are looking at everything or this is a re-export
if (reExport == false || requirement.isReExport())
{
// Sanity checks
if (item.isResolved() == false)
throw new IllegalStateException("Item not resolved: " + item);
// Dynamic requirement, create it lazily
if (requirement.isDynamic())
{
DelegateLoader delegate = createLazyDelegateLoader(checkDomain(), item);
dynamic.add(delegate);
continue;
}
String name = (String) item.getIDependOn();
if (name == null)
{
// Optional requirement, just ignore
if (requirement.isOptional())
continue;
// Something has gone wrong
throw new IllegalStateException("No iDependOn for item: " + item);
}
Module iDependOnModule = checkDomain().getModule(name);
if (iDependOnModule == null)
throw new IllegalStateException("Module not found with name: " + name);
// Determine the delegate loader for the module
Module other = item.getModule();
DelegateLoader delegate = iDependOnModule.getDelegateLoader(other, requirement);
// Check for re-export by the module
if (requirement.wantReExports())
addDelegates(iDependOnModule, delegates, dynamic, visited, true);
// We want a module's re-exports (i.e. part of its imports) before the module itself
if (delegate != null)
delegates.add(delegate);
}
}
}
/**
* Create a lazy delegate loader
*
* @param domain the domain
* @param item the dependency item
* @return the delegate loader
*/
public abstract DelegateLoader createLazyDelegateLoader(Domain domain, RequirementDependencyItem item);
/**
* Get the delegate loader
*
* @param requiringModule the requiring module
* @param requirement the requirement
* @return the delegate loader
*/
public abstract DelegateLoader getDelegateLoader(Module requiringModule, Requirement requirement);
/**
* Get the capabilities.
*
* @return the capabilities.
*/
public List<Capability> getCapabilities()
{
// Have we already worked this out?
if (capabilities != null)
return capabilities;
// Are there any configured ones?
List<Capability> capabilities = determineCapabilities();
// Use the defaults
if (capabilities == null)
capabilities = defaultCapabilities();
//Add global capabilities
capabilities = checkDomain().mergeGlobalCapabilities(capabilities);
// Cache it
this.capabilities = capabilities;
return capabilities;
}
/**
* Determine the capabilities
*
* @return the capabilities
*/
protected List<Capability> determineCapabilities()
{
return null;
}
/**
* Determine the default capabilities.<p>
*
* By default it is just the module capability
*
* @return the capabilities
*/
protected List<Capability> defaultCapabilities()
{
List<Capability> capabilities = new CopyOnWriteArrayList<Capability>();
ClassLoadingMetaDataFactory factory = ClassLoadingMetaDataFactory.getInstance();
Capability capability = factory.createModule(getName(), getVersion());
capabilities.add(capability);
return capabilities;
}
/**
* Get the package names
*
* @return the package names
*/
public String[] getPackageNames()
{
List<String> packageNames = determinePackageNames(true);
return packageNames.toArray(new String[packageNames.size()]);
}
/**
* Determine the package names
*
* TODO JBCL-23 Better handling of conflicts for optional packages
* @param optional whether to include optional packages
* @return the package names
*/
public List<String> determinePackageNames(boolean optional)
{
List<String> packageNames = Collections.emptyList();
List<Capability> capabilities = getCapabilities();
if (capabilities != null && capabilities.isEmpty() == false)
{
for (Capability capability : capabilities)
{
if (capability instanceof ExportPackages)
{
ExportPackages exported = (ExportPackages) capability;
Set<String> exportPackages = exported.getPackageNames(this);
if (exportPackages != null)
{
if (packageNames.isEmpty())
packageNames = new ArrayList<String>();
packageNames.addAll(exportPackages);
}
}
}
}
List<Requirement> requirements = getRequirements();
if (requirements != null && requirements.isEmpty() == false)
{
for (Requirement requirement : getRequirements())
{
if (requirement instanceof ExportPackages)
{
ExportPackages exported = (ExportPackages) requirement;
Set<String> exportPackages = exported.getPackageNames(this);
if (optional || requirement.isOptional() == false)
{
if (exportPackages != null && exportPackages.isEmpty() == false)
{
if (packageNames.isEmpty())
packageNames = new ArrayList<String>();
packageNames.addAll(exportPackages);
}
}
}
else if (optional == false && requirement instanceof OptionalPackages)
{
OptionalPackages exported = (OptionalPackages) requirement;
Set<String> optionalPackages = exported.getOptionalPackageNames(this);
if (optionalPackages != null && packageNames.isEmpty() == false)
packageNames.removeAll(optionalPackages);
}
}
}
return packageNames;
}
/**
* Get the state for the classloader
*
* @return the state
*/
public ControllerState getClassLoaderState()
{
return ControllerState.INSTALLED;
}
/**
* Get the requirements.
*
* @return the requirements.
*/
public List<Requirement> getRequirements()
{
// Have we already worked this out?
if (requirements != null)
return requirements;
// Are there any configured ones?
List<Requirement> requirements = determineRequirements();
// Cache it
this.requirements = requirements;
return requirements;
}
/**
* Determine the requirements.
*
* @return the requirements.
*/
public List<Requirement> determineRequirements()
{
return Collections.emptyList();
}
/**
* Return a URL where dynamic classes can be stored
*
* @return the url or null if there isn't one
*/
public URL getDynamicClassRoot()
{
return null;
}
List<RequirementDependencyItem> getDependencies()
{
return requirementDependencies;
}
/**
* Create the dependencies for the module
*/
protected void createDependencies()
{
ControllerState classLoaderState = getClassLoaderState();
List<Requirement> requirements = getRequirements();
if (requirements != null)
{
requirementDependencies = new ArrayList<RequirementDependencyItem>();
for (Requirement requirement : requirements)
{
RequirementDependencyItem item = new RequirementDependencyItem(this, requirement, classLoaderState);
addIDependOn(item);
requirementDependencies.add(item);
}
}
}
/**
* Remove dependencies
*/
protected void removeDependencies()
{
if (requirementDependencies != null && requirementDependencies.isEmpty() == false)
{
for (RequirementDependencyItem item : requirementDependencies)
removeIDependOn(item);
}
requirementDependencies = null;
}
/**
* Get the controller context.
*
* @return the controller context.
*/
protected ControllerContext getControllerContext()
{
return context;
}
/**
* Set the controller context
*
* @param context the context
*/
protected void setControllerContext(ControllerContext context)
{
this.context = context;
}
/**
* Add a dependency
*
* @param item the dependency item
*/
protected void addIDependOn(RequirementDependencyItem item)
{
if (context == null)
throw new IllegalStateException("No controller context");
context.getDependencyInfo().addIDependOn(item);
}
/**
* Remove a dependency
*
* @param item the dependency item
*/
protected void removeIDependOn(RequirementDependencyItem item)
{
if (context == null)
throw new IllegalStateException("No controller context");
context.getDependencyInfo().removeIDependOn(item);
}
/**
* Resolve a requirement
*
* @param dependency the dependency the dependency
* @param resolveSpace whether to resolve the module in the classloading space
* @return the resolved name or null if not resolved
*/
protected Module resolveModule(RequirementDependencyItem dependency, boolean resolveSpace)
{
ClassLoadingSpace space = getClassLoadingSpace();
if (resolveSpace && space != null)
space.resolve(this);
Requirement requirement = dependency.getRequirement();
return checkDomain().resolveModule(this, requirement);
}
/**
* Release the module
*/
public void release()
{
Domain domain = this.domain;
if (domain != null)
domain.removeModule(this);
reset();
}
/**
* Reset the module
*/
public void reset()
{
ClassLoader classLoader = getClassLoader();
if (classLoader != null)
unregisterModuleClassLoader(this, classLoader);
this.capabilities = null;
this.requirements = null;
}
@Override
public boolean equals(Object obj)
{
if (obj == this)
return true;
if (obj == null || obj instanceof Module == false)
return false;
return super.equals(obj);
}
}