/* * 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. * <p> * This class requires the directories that contain component impl jars to be specified in the <code>acs.components.classpath.jardirs</code> * 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. * <p> * It is important to call the <code>close()</code> 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). * <p> * 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: <code>../lib/ACScomponents:/alma/ACS-5.0/ACSSW/lib/ACScomponents:"</code>. */ 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. * <p> * 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 <code>PROPERTY_CLASSLOADERVERBOSE</code>). * 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. * <p> * TODO-: check if certain system or ACS classes should be skipped and delegated upward right away. * This may improve performance, assuming a bunch of <code>name.startsWith("java.")</code> etc are faster than * <code>findLoadedClass</code> and <code>findClass</code>. * * @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 <code>super.findClass(name)</code> 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. * <p> * 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; } }