/* * ALMA - Atacama Large Millimiter Array * (c) European Southern Observatory, 2004 * Copyright by ESO (in the framework of the ALMA collaboration), * All rights reserved * * This library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package alma.acs.classloading; import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import alma.acs.makesupport.AcsJarFileFinder; /** * The component-specific class loader. * Attempts to load classes directly, and only delegates to parent class loader after it failed. * This bottom-up direction of classloading in the classloader hierarchy follows the J2EE convention, * and thus violates the normal J2SE top-down direction. *
* This class requires the directories that contain component impl jars to be specified in the acs.components.classpath.jardirs
* property.
* The startup scripts must set this property.
* Other jar files (e.g. ACS jars) must be in different directories than those given by this property.
*
* It is important to call the close()
method when done with the classloader.
* Since ACS 12.3 (JDK 1.7) this method is provided by the base class.
* After changes in this area, make sure to manually run AcsComponentClassLoaderEnduranceTest to verify
* that there are no memory problems (non-heap OutOfMemoryError) as were seen in the past
* (http://jira.alma.cl/browse/COMP-4929).
*
* TODO-: this class has a few things in common with {@link alma.acs.classloading.AcsSystemClassLoader}, so
* perhaps during some future refactoring a common base class could be extracted (between URLClassLoader and these).
*/
public class AcsComponentClassLoader extends URLClassLoader
{
/**
* Name of the property that defines the directories for component implementation jar files.
* Example value: ../lib/ACScomponents:/alma/ACS-5.0/ACSSW/lib/ACScomponents:"
.
*/
public static final String PROPERTY_JARDIRS = "acs.components.classpath.jardirs";
/**
* Name of the property that flags verbose mode of the component classloader.
* Verbose mode is a debugging tool, only to be enabled locally by defining this property.
*
* If enabled to be verbose, a component classloader will log the jar files it works with,
* the classes it loads or fails to load, and also prints a message when it is getting finalized.
* The latter is useful to monitor component class unloading.
*/
public static final String PROPERTY_CLASSLOADERVERBOSE = "acs.components.classloader.verbose";
private boolean verbose;
private final Logger logger;
private final String componentName;
/**
* @param parent parent class loader (currently the container class loader)
* @param logger the container logger, for debug output (see PROPERTY_CLASSLOADERVERBOSE
).
* This is also used to derive the processName when logging exceptions.
* @param componentName used for log messages in verbose mode
*/
public AcsComponentClassLoader(ClassLoader parent, Logger logger, String componentName)
{
super(new URL[0], parent);
verbose = Boolean.getBoolean(PROPERTY_CLASSLOADERVERBOSE);
this.logger = logger;
this.componentName = componentName;
String jarDirPath = System.getProperty(PROPERTY_JARDIRS);
if (verbose) {
logger.fine("Property '" + PROPERTY_JARDIRS + "' is set to " + jarDirPath);
}
//System.out.println("Property '" + PROPERTY_JARDIRS + "' is set to " + jarDirPath);
File[] jarDirs = parseJarDirs(jarDirPath);
AcsJarFileFinder jarFinder = new AcsJarFileFinder(jarDirs, null);
jarFinder.setVerbose(verbose);
File[] allJars = jarFinder.getAllFiles();
for (int i = 0; i < allJars.length; i++) {
try {
addURL(allJars[i].toURI().toURL());
if (verbose) {
logger.finer("added " + allJars[i].getAbsolutePath() +
" to the path of the component classloader for " + componentName);
}
}
catch (Exception e) {
logger.log(Level.WARNING, "failed to add " + allJars[i].getAbsolutePath() +
" to the path of the component classloader for " + componentName, e);
}
}
if (verbose) {
// //test
// URL[] urls = getURLs();
// System.out.println("first URL is " + urls[0].toString());
// System.out.println("last URL is " + urls[urls.length-1].toString());
}
}
private File[] parseJarDirs(String jarDirPath)
{
StringTokenizer tok = new StringTokenizer(jarDirPath, File.pathSeparator);
File[] jarDirs = new File[tok.countTokens()];
for (int i = 0; tok.hasMoreTokens(); i++) {
jarDirs[i] = new File(tok.nextToken());
if (verbose) {
logger.finer("Classloader for component '" + componentName + "' will look for jar files in directory "
+ jarDirs[i].getAbsolutePath());
}
}
return jarDirs;
}
/**
* Attempts to load the given class, and only delegates to parent class loader if it failed.
* This bottom-up direction of classloading in the classloader hierarchy resembles the J2EE convention,
* and thus violates the normal J2SE top-down direction.
*
* TODO-: check if certain system or ACS classes should be skipped and delegated upward right away.
* This may improve performance, assuming a bunch of name.startsWith("java.")
etc are faster than
* findLoadedClass
and findClass
.
*
* @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
*/
protected synchronized Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// System.out.println("### load " + name);
// First, check if the class has already been loaded by this classloader
Class> c = findLoadedClass(name);
if (c == null) {
// try to load the component impl class before delegating to the parent class loader.
try {
c = findClass(name);
}
catch (ClassNotFoundException e) {
// fallthrough: try parent class loader after all;
}
if (c == null) {
// The super implementation will delegate to the parent class loader.
// This is the default for all J2SE class loaders: first try parent, then self
if (verbose) {
logger.finer("AcsComponentClassLoader will delegate loading '" + name + "' to parent CL, a "
+ getParent().getClass().getName());
}
c = super.loadClass(name, false);
}
}
else if (verbose) {
logger.finer("Class '" + name + "' already loaded by AcsComponentClassLoader for '" + componentName +
"'. Nothing to do.");
}
if (resolve) {
resolveClass(c);
}
return c;
}
/**
* Calls super.findClass(name)
and provides some System.out logging if in verbose mode.
*
* @see java.lang.ClassLoader#findClass(java.lang.String)
*/
protected Class> findClass(String name) throws ClassNotFoundException
{
Class> clazz = null;
try {
clazz = super.findClass(name);
}
catch (ClassNotFoundException e) {
// should be commented out even for verbose mode, since this method is called for any class in the J2EE bottom-up classloader approach
// if (verbose) {
// System.out.println(AcsComponentClassLoader.class.getName() + " failed to load class " + name);
// }
throw e;
}
if (verbose) {
logger.finer("Class loader for component '" + componentName + "' loaded class " + name);
}
return clazz;
}
protected void finalize() throws Throwable {
if (verbose) {
logger.finer("Class loader for component '" + componentName + "' about to be finalized.");
}
super.finalize();
}
/**
* @see ContextFinder
*/
public String getSourceObject(){
return componentName;
}
/**
* Taken from ClientLogManager.stripKnownLoggerNamespacePrefix(). Maybe it should be nice to generalize it and put it somewhere else.
* Strips the prepended constants {@link #NS_CORBA}, {@link #NS_CONTAINER}, {@link #NS_COMPONENT} etc from the logger namespace.
* This allows for a short, but possibly not unique display of the logger name.
*
* TODO: This method is probably broken and must be checked. We may just pass contextName in the ctor. * * @see ContextFinder */ public String getProcessName(){ /** logger namespace for CORBA classes (ORB, POAs, etc) */ String NS_CORBA = "alma.acs.corba"; /** logger namespace for container classes during operation */ String NS_CONTAINER = "alma.acs.container"; /** parent of logger namespaces for application components */ String NS_COMPONENT = "alma.component"; String loggerName=logger.getName(); if (loggerName != null) { // try to strip off fixed prefix from logger namespace try { if (loggerName.startsWith(NS_COMPONENT)) { loggerName = loggerName.substring(NS_COMPONENT.length()+1); } else if (loggerName.startsWith(NS_CONTAINER)) { loggerName = loggerName.substring(NS_CONTAINER.length()+1); } else if (loggerName.startsWith(NS_CORBA)) { loggerName = loggerName.substring(NS_CORBA.length()+1); } } catch (Exception e) { // fallback: use logger namespace } } return loggerName; } }