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

org.mobicents.servlet.sip.annotations.AnnotationsClassLoader Maven / Gradle / Ivy

There is a newer version: 4.0.128
Show newest version
package org.mobicents.servlet.sip.annotations;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.security.AccessControlException;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import javax.naming.NamingException;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.loader.Constants;
//import org.apache.catalina.loader.Reloader;
import org.apache.catalina.loader.ResourceEntry;
import org.apache.log4j.Logger;
import org.apache.naming.JndiPermission;
import org.apache.tomcat.util.IntrospectionUtils;
//import org.apache.catalina.util.StringManager;
import org.apache.tomcat.util.res.StringManager;

/**
 * Specialized web application class loader.
 * 

* This class loader is a full reimplementation of the * URLClassLoader from the JDK. It is desinged to be fully * compatible with a normal URLClassLoader, although its internal * behavior may be completely different. *

* IMPLEMENTATION NOTE - This class loader faithfully follows * the delegation model recommended in the specification. The system class * loader will be queried first, then the local repositories, and only then * delegation to the parent class loader will occur. This allows the web * application to override any shared class except the classes from J2SE. * Special handling is provided from the JAXP XML parser interfaces, the JNDI * interfaces, and the classes from the servlet API, which are never loaded * from the webapp repository. *

* IMPLEMENTATION NOTE - Due to limitations in Jasper * compilation technology, any repository which contains classes from * the servlet API will be ignored by the class loader. *

* IMPLEMENTATION NOTE - The class loader generates source * URLs which include the full JAR URL when a class is loaded from a JAR file, * which allows setting security permission at the class level, even when a * class is contained inside a JAR. *

* IMPLEMENTATION NOTE - Local repositories are searched in * the order they are added via the initial constructor and/or any subsequent * calls to addRepository() or addJar(). *

* IMPLEMENTATION NOTE - No check for sealing violations or * security is made unless a security manager is present. * * @author Remy Maucherat * @author Craig R. McClanahan * * @author Vladimir Ralev: * This class is forked to allow adding jar directories and method access changes. */ /** * @author jean * */ public class AnnotationsClassLoader extends URLClassLoader implements Lifecycle { private static transient final Logger log = Logger.getLogger(AnnotationsClassLoader.class); public static final boolean ENABLE_CLEAR_REFERENCES = Boolean.valueOf(System.getProperty("org.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES", "true")).booleanValue(); private static final String CLASS_FILE_SUFFIX = ".class"; private static final String SERVICES_PREFIX = "/META-INF/services/"; protected class PrivilegedFindResource implements PrivilegedAction { protected File file; protected String path; PrivilegedFindResource(File file, String path) { this.file = file; this.path = path; } public Object run() { return findResourceInternal(file, path); } } // ------------------------------------------------------- Static Variables /** * The set of trigger classes that will cause a proposed repository not * to be added if this class is visible to the class loader that loaded * this factory class. Typically, trigger classes will be listed for * components that have been integrated into the JDK for later versions, * but where the corresponding JAR files are required to run on * earlier versions. */ static final String[] triggers = { "javax.servlet.Servlet" // Servlet API }; /** * Set of package names which are not allowed to be loaded from a webapp * class loader without delegating first. */ protected static final String[] packageTriggers = { }; /** * The string manager for this package. */ protected static final StringManager sm = StringManager.getManager(Constants.Package); /** * Use anti JAR locking code, which does URL rerouting when accessing * resources. */ boolean antiJARLocking = false; // ----------------------------------------------------------- Constructors /** * Construct a new ClassLoader with no defined repositories and no * parent ClassLoader. */ public AnnotationsClassLoader() { super(new URL[0]); this.parent = getParent(); system = getSystemClassLoader(); securityManager = System.getSecurityManager(); if (securityManager != null) { refreshPolicy(); } } /** * Construct a new ClassLoader with no defined repositories and no * parent ClassLoader. */ public AnnotationsClassLoader(ClassLoader parent) { super(new URL[0], parent); this.parent = getParent(); system = getSystemClassLoader(); securityManager = System.getSecurityManager(); if (securityManager != null) { refreshPolicy(); } } // ----------------------------------------------------- Instance Variables /** * Associated directory context giving access to the resources in this * webapp. */ protected WebResourceRoot resources = null; /** * The cache of ResourceEntry for classes and resources we have loaded, * keyed by resource name. */ protected Map resourceEntries = new HashMap(); /** * The list of not found resources. */ protected Map notFoundResources = new HashMap(); /** * Should this class loader delegate to the parent class loader * before searching its own repositories (i.e. the * usual Java2 delegation model)? If set to false, * this class loader will search its own repositories first, and * delegate to the parent only if the class or resource is not * found locally. */ protected boolean delegate = false; private final HashMap jarModificationTimes = new HashMap<>(); /** * Last time a JAR was accessed. */ protected long lastJarAccessed = 0L; /** * The list of local repositories, in the order they should be searched * for locally loaded classes or resources. */ protected String[] repositories = new String[0]; /** * Repositories URLs, used to cache the result of getURLs. */ protected URL[] repositoryURLs = null; /** * Repositories translated as path in the work directory (for Jasper * originally), but which is used to generate fake URLs should getURLs be * called. */ protected File[] files = new File[0]; /** * The list of JARs, in the order they should be searched * for locally loaded classes or resources. */ protected JarFile[] jarFiles = new JarFile[0]; /** * The list of JARs, in the order they should be searched * for locally loaded classes or resources. */ protected File[] jarRealFiles = new File[0]; /** * The path which will be monitored for added Jar files. */ protected String jarPath = null; /** * The list of JARs, in the order they should be searched * for locally loaded classes or resources. */ protected String[] jarNames = new String[0]; /** * The list of JARs last modified dates, in the order they should be * searched for locally loaded classes or resources. */ protected long[] lastModifiedDates = new long[0]; /** * The list of resources which should be checked when checking for * modifications. */ protected String[] paths = new String[0]; /** * A list of read File and Jndi Permission's required if this loader * is for a web application context. */ protected ArrayList permissionList = new ArrayList(); /** * Path where resources loaded from JARs will be extracted. */ protected File loaderDir = null; /** * The PermissionCollection for each CodeSource for a web * application context. */ protected Map loaderPC = new HashMap(); /** * Instance of the SecurityManager installed. */ protected SecurityManager securityManager = null; /** * The parent class loader. */ protected ClassLoader parent = null; /** * The system class loader. */ protected ClassLoader system = null; /** * Has this component been started? * HACK to simulate running app for parsing annotations. */ protected boolean started = true; /** * Has external repositories. */ protected boolean hasExternalRepositories = false; /** * need conversion for properties files */ protected boolean needConvert = false; /** * All permission. */ protected Permission allPermission = new java.security.AllPermission(); // ------------------------------------------------------------- Properties /** * Get associated resources. */ public WebResourceRoot getResources() { return this.resources; } /** * Set associated resources. */ public void setResources(WebResourceRoot resources) { this.resources = resources; } /** * Return the "delegate first" flag for this class loader. */ public boolean getDelegate() { return (this.delegate); } /** * Set the "delegate first" flag for this class loader. * * @param delegate The new "delegate first" flag */ public void setDelegate(boolean delegate) { this.delegate = delegate; } /** * @return Returns the antiJARLocking. */ public boolean getAntiJARLocking() { return antiJARLocking; } /** * @param antiJARLocking The antiJARLocking to set. */ public void setAntiJARLocking(boolean antiJARLocking) { this.antiJARLocking = antiJARLocking; } /** * If there is a Java SecurityManager create a read FilePermission * or JndiPermission for the file directory path. * * @param path file directory path */ public void addPermission(String path) { if (path == null) { return; } if (securityManager != null) { Permission permission = null; if( path.startsWith("jndi:") || path.startsWith("jar:jndi:") ) { if (!path.endsWith("/")) { path = path + "/"; } permission = new JndiPermission(path + "*"); addPermission(permission); } else { if (!path.endsWith(File.separator)) { permission = new FilePermission(path, "read"); addPermission(permission); path = path + File.separator; } permission = new FilePermission(path + "-", "read"); addPermission(permission); } } } /** * If there is a Java SecurityManager create a read FilePermission * or JndiPermission for URL. * * @param url URL for a file or directory on local system */ public void addPermission(URL url) { if (url != null) { addPermission(url.toString()); } } /** * If there is a Java SecurityManager create a Permission. * * @param permission The permission */ public void addPermission(Permission permission) { if ((securityManager != null) && (permission != null)) { permissionList.add(permission); } } /** * Return the JAR path. */ public String getJarPath() { return this.jarPath; } /** * Change the Jar path. */ public void setJarPath(String jarPath) { this.jarPath = jarPath; } /** * Change the work directory. */ public void setWorkDir(File workDir) { this.loaderDir = new File(workDir, "loader"); } /** * Utility method for use in subclasses. * Must be called before Lifecycle methods to have any effect. */ protected void setParentClassLoader(ClassLoader pcl) { parent = pcl; } // ------------------------------------------------------- Reloader Methods /** * Add a new repository to the set of places this ClassLoader can look for * classes to be loaded. * * @param repository Name of a source of classes to be loaded, such as a * directory pathname, a JAR file pathname, or a ZIP file pathname * * @exception IllegalArgumentException if the specified repository is * invalid or does not exist */ public void addRepository(String repository) { // Ignore any of the standard repositories, as they are set up using // either addJar or addRepository if (repository.startsWith("/WEB-INF/lib") || repository.startsWith("/WEB-INF/classes")) return; // Add this repository to our underlying class loader try { URL url = new URL(repository); super.addURL(url); hasExternalRepositories = true; repositoryURLs = null; } catch (MalformedURLException e) { IllegalArgumentException iae = new IllegalArgumentException ("Invalid repository: " + repository); iae.initCause(e); throw iae; } } /** * Add a new repository to the set of places this ClassLoader can look for * classes to be loaded. * * @param repository Name of a source of classes to be loaded, such as a * directory pathname, a JAR file pathname, or a ZIP file pathname * * @exception IllegalArgumentException if the specified repository is * invalid or does not exist */ synchronized void addRepository(String repository, File file) { // Note : There should be only one (of course), but I think we should // keep this a bit generic if (repository == null) return; if (log.isDebugEnabled()) log.debug("addRepository(" + repository + ")"); int i; // Add this repository to our internal list String[] result = new String[repositories.length + 1]; for (i = 0; i < repositories.length; i++) { result[i] = repositories[i]; } result[repositories.length] = repository; repositories = result; // Add the file to the list File[] result2 = new File[files.length + 1]; for (i = 0; i < files.length; i++) { result2[i] = files[i]; } result2[files.length] = file; files = result2; } public void addJarDir(String dirPath) { File dir = new File(dirPath); if(!dir.isDirectory()) { log.info("No libraries loaded from this directory: " + dir.getAbsolutePath()); return; } File[] files = dir.listFiles(); for(File file:files) { if(!file.isDirectory() && file.getName().indexOf(".jar") != -1) { try { addJar(file.getName(), new JarFile(file), file ); } catch (IOException e) { log.error("An exception occured when trying to add the following jar to the AnnotationsClassLoader : " + file.getAbsolutePath(), e); } } else { log.info(file.getAbsolutePath() + " is a directory in " + dirPath + " and as such will be skipped."); } } } synchronized void addJar(String jar, JarFile jarFile, File file) throws IOException { if (jar == null) return; if (jarFile == null) return; if (file == null) return; if (log.isDebugEnabled()) log.debug("addJar(" + jar + ")"); int i; if ((jarPath != null) && (jar.startsWith(jarPath))) { String jarName = jar.substring(jarPath.length()); while (jarName.startsWith("/")) jarName = jarName.substring(1); String[] result = new String[jarNames.length + 1]; for (i = 0; i < jarNames.length; i++) { result[i] = jarNames[i]; } result[jarNames.length] = jarName; jarNames = result; } // Register the JAR for tracking long lastModified = resources.getClassLoaderResource(jar).getLastModified(); String[] result = new String[paths.length + 1]; for (i = 0; i < paths.length; i++) { result[i] = paths[i]; } result[paths.length] = jar; paths = result; long[] result3 = new long[lastModifiedDates.length + 1]; for (i = 0; i < lastModifiedDates.length; i++) { result3[i] = lastModifiedDates[i]; } result3[lastModifiedDates.length] = lastModified; lastModifiedDates = result3; // If the JAR currently contains invalid classes, don't actually use it // for classloading if (!validateJarFile(file)) return; JarFile[] result2 = new JarFile[jarFiles.length + 1]; for (i = 0; i < jarFiles.length; i++) { result2[i] = jarFiles[i]; } result2[jarFiles.length] = jarFile; jarFiles = result2; // Add the file to the list File[] result4 = new File[jarRealFiles.length + 1]; for (i = 0; i < jarRealFiles.length; i++) { result4[i] = jarRealFiles[i]; } result4[jarRealFiles.length] = file; jarRealFiles = result4; } /** * Return a String array of the current repositories for this class * loader. If there are no repositories, a zero-length array is * returned.For security reason, returns a clone of the Array (since * String are immutable). */ public String[] findRepositories() { return ((String[])repositories.clone()); } /** * Have one or more classes or resources been modified so that a reload * is appropriate? */ public boolean modified() { if (log.isDebugEnabled()) log.debug("modified()"); for (Entry entry : resourceEntries.entrySet()) { long cachedLastModified = entry.getValue().lastModified; long lastModified = resources.getClassLoaderResource( entry.getKey()).getLastModified(); if (lastModified != cachedLastModified) { if( log.isDebugEnabled() ) log.debug(sm.getString("webappClassLoader.resourceModified", entry.getKey(), new Date(cachedLastModified), new Date(lastModified))); return true; } } // Check if JARs have been added or removed WebResource[] jars = resources.listResources("/WEB-INF/lib"); // Filter out non-JAR resources int jarCount = 0; for (WebResource jar : jars) { if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) { jarCount++; Long recordedLastModified = jarModificationTimes.get(jar.getName()); if (recordedLastModified == null) { // Jar has been added log.info(sm.getString("webappClassLoader.jarsAdded", resources.getContext().getName())); return true; } if (recordedLastModified.longValue() != jar.getLastModified()) { // Jar has been changed log.info(sm.getString("webappClassLoader.jarsModified", resources.getContext().getName())); return true; } } } if (jarCount < jarModificationTimes.size()){ log.info(sm.getString("webappClassLoader.jarsRemoved", resources.getContext().getName())); return true; } // No classes have been modified return false; } /** * Render a String representation of this object. */ public String toString() { StringBuffer sb = new StringBuffer("WebappClassLoader\r\n"); sb.append(" delegate: "); sb.append(delegate); sb.append("\r\n"); sb.append(" repositories:\r\n"); if (repositories != null) { for (int i = 0; i < repositories.length; i++) { sb.append(" "); sb.append(repositories[i]); sb.append("\r\n"); } } if (this.parent != null) { sb.append("----------> Parent Classloader:\r\n"); sb.append(this.parent.toString()); sb.append("\r\n"); } return (sb.toString()); } // ---------------------------------------------------- ClassLoader Methods /** * Add the specified URL to the classloader. */ protected void addURL(URL url) { super.addURL(url); hasExternalRepositories = true; repositoryURLs = null; } /** * Find the specified class in our local repositories, if possible. If * not found, throw ClassNotFoundException. * * @param name Name of the class to be loaded * * @exception ClassNotFoundException if the class was not found */ public Class findClass(String name) throws ClassNotFoundException { if (log.isDebugEnabled()) log.debug(" findClass(" + name + ")"); // Cannot load anything from local repositories if class loader is stopped if (!started) { throw new ClassNotFoundException(name); } // (1) Permission to define this class when using a SecurityManager if (securityManager != null) { int i = name.lastIndexOf('.'); if (i >= 0) { try { if (log.isTraceEnabled()) log.trace(" securityManager.checkPackageDefinition"); securityManager.checkPackageDefinition(name.substring(0,i)); } catch (Exception se) { if (log.isTraceEnabled()) log.trace(" -->Exception-->ClassNotFoundException", se); throw new ClassNotFoundException(name, se); } } } // Ask our superclass to locate this class, if possible // (throws ClassNotFoundException if it is not found) Class clazz = null; try { if (log.isTraceEnabled()) log.trace(" findClassInternal(" + name + ")"); try { clazz = findClassInternal(name); } catch(ClassNotFoundException cnfe) { if (!hasExternalRepositories) { throw cnfe; } } catch(AccessControlException ace) { throw new ClassNotFoundException(name, ace); } catch (RuntimeException e) { if (log.isTraceEnabled()) log.trace(" -->RuntimeException Rethrown", e); throw e; } if ((clazz == null) && hasExternalRepositories) { try { clazz = super.findClass(name); } catch(AccessControlException ace) { throw new ClassNotFoundException(name, ace); } catch (RuntimeException e) { if (log.isTraceEnabled()) log.trace(" -->RuntimeException Rethrown", e); throw e; } } if (clazz == null) { if (log.isDebugEnabled()) log.debug(" --> Returning ClassNotFoundException"); throw new ClassNotFoundException(name); } } catch (ClassNotFoundException e) { if (log.isTraceEnabled()) log.trace(" --> Passing on ClassNotFoundException"); throw e; } // Return the class we have located if (log.isTraceEnabled()) log.debug(" Returning class " + clazz); if (log.isTraceEnabled()) log.debug(" Loaded by " + clazz.getClassLoader()); return (clazz); } /** * Find the specified resource in our local repository, and return a * URL refering to it, or null if this resource * cannot be found. * * @param name Name of the resource to be found */ public URL findResource(final String name) { if (log.isDebugEnabled()) log.debug(" findResource(" + name + ")"); URL url = null; ResourceEntry entry = (ResourceEntry) resourceEntries.get(name); if (entry == null) { entry = findResourceInternal(name, name); } if (entry != null) { url = entry.source; } if ((url == null) && hasExternalRepositories) url = super.findResource(name); if (log.isDebugEnabled()) { if (url != null) log.debug(" --> Returning '" + url.toString() + "'"); else log.debug(" --> Resource not found, returning null"); } return (url); } /** * Return an enumeration of URLs representing all of the * resources with the given name. If no resources with this name are * found, return an empty enumeration. * * @param name Name of the resources to be found * * @exception IOException if an input/output error occurs */ @Override public Enumeration findResources(String name) throws IOException { if (log.isDebugEnabled()) log.debug(" findResources(" + name + ")"); checkStateForResourceLoading(name); LinkedHashSet result = new LinkedHashSet<>(); String path = nameToPath(name); WebResource[] webResources = resources.getClassLoaderResources(path); for (WebResource webResource : webResources) { if (webResource.exists()) { result.add(webResource.getURL()); } } // Adding the results of a call to the superclass if (hasExternalRepositories) { Enumeration otherResourcePaths = super.findResources(name); while (otherResourcePaths.hasMoreElements()) { result.add(otherResourcePaths.nextElement()); } } return Collections.enumeration(result); } private String binaryNameToPath(String binaryName, boolean withLeadingSlash) { // 1 for leading '/', 6 for ".class" StringBuilder path = new StringBuilder(7 + binaryName.length()); if (withLeadingSlash) { path.append('/'); } path.append(binaryName.replace('.', '/')); path.append(CLASS_FILE_SUFFIX); return path.toString(); } private String nameToPath(String name) { if (name.startsWith("/")) { return name; } StringBuilder path = new StringBuilder( 1 + name.length()); path.append('/'); path.append(name); return path.toString(); } /** * Find the resource with the given name. A resource is some data * (images, audio, text, etc.) that can be accessed by class code in a * way that is independent of the location of the code. The name of a * resource is a "/"-separated path name that identifies the resource. * If the resource cannot be found, return null. *

* This method searches according to the following algorithm, returning * as soon as it finds the appropriate URL. If the resource cannot be * found, returns null. *

    *
  • If the delegate property is set to true, * call the getResource() method of the parent class * loader, if any.
  • *
  • Call findResource() to find this resource in our * locally defined repositories.
  • *
  • Call the getResource() method of the parent class * loader, if any.
  • *
* * @param name Name of the resource to return a URL for */ public URL getResource(String name) { if (log.isDebugEnabled()) log.debug("getResource(" + name + ")"); URL url = null; // (1) Delegate to parent if requested if (delegate) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader " + parent); ClassLoader loader = parent; if (loader == null) loader = system; url = loader.getResource(name); if (url != null) { if (log.isDebugEnabled()) log.debug(" --> Returning '" + url.toString() + "'"); return (url); } } // (2) Search local repositories url = findResource(name); if (url != null) { // Locating the repository for special handling in the case // of a JAR if (antiJARLocking) { ResourceEntry entry = (ResourceEntry) resourceEntries.get(name); try { String repository = entry.codeBase.toString(); if ((repository.endsWith(".jar")) && (!(name.endsWith(".class")))) { // Copy binary content to the work directory if not present File resourceFile = new File(loaderDir, name); url = getURI(resourceFile); } } catch (Exception e) { // Ignore } } if (log.isDebugEnabled()) log.debug(" --> Returning '" + url.toString() + "'"); return (url); } // (3) Delegate to parent unconditionally if not already attempted if( !delegate ) { ClassLoader loader = parent; if (loader == null) loader = system; url = loader.getResource(name); if (url != null) { if (log.isDebugEnabled()) log.debug(" --> Returning '" + url.toString() + "'"); return (url); } } // (4) Resource was not found if (log.isDebugEnabled()) log.debug(" --> Resource not found, returning null"); return (null); } /** * Find the resource with the given name, and return an input stream * that can be used for reading it. The search order is as described * for getResource(), after checking to see if the resource * data has been previously cached. If the resource cannot be found, * return null. * * @param name Name of the resource to return an input stream for */ public InputStream getResourceAsStream(String name) { if (log.isDebugEnabled()) log.debug("getResourceAsStream(" + name + ")"); InputStream stream = null; // (0) Check for a cached copy of this resource stream = findLoadedResource(name); if (stream != null) { if (log.isDebugEnabled()) log.debug(" --> Returning stream from cache"); return (stream); } // (1) Delegate to parent if requested if (delegate) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader " + parent); ClassLoader loader = parent; if (loader == null) loader = system; stream = loader.getResourceAsStream(name); if (stream != null) { // FIXME - cache??? if (log.isDebugEnabled()) log.debug(" --> Returning stream from parent"); return (stream); } } // (2) Search local repositories if (log.isDebugEnabled()) log.debug(" Searching local repositories"); URL url = findResource(name); if (url != null) { // FIXME - cache??? if (log.isDebugEnabled()) log.debug(" --> Returning stream from local"); stream = findLoadedResource(name); try { if (hasExternalRepositories && (stream == null)) stream = url.openStream(); } catch (IOException e) { ; // Ignore } if (stream != null) return (stream); } // (3) Delegate to parent unconditionally if (!delegate) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader unconditionally " + parent); ClassLoader loader = parent; if (loader == null) loader = system; stream = loader.getResourceAsStream(name); if (stream != null) { // FIXME - cache??? if (log.isDebugEnabled()) log.debug(" --> Returning stream from parent"); return (stream); } } // (4) Resource was not found if (log.isDebugEnabled()) log.debug(" --> Resource not found, returning null"); return (null); } /** * Load the class with the specified name. This method searches for * classes in the same manner as loadClass(String, boolean) * with false as the second argument. * * @param name Name of the class to be loaded * * @exception ClassNotFoundException if the class was not found */ public Class loadClass(String name) throws ClassNotFoundException { return (loadClass(name, false)); } /** * Load the class with the specified name, searching using the following * algorithm until it finds and returns the class. If the class cannot * be found, returns ClassNotFoundException. *
    *
  • Call findLoadedClass(String) to check if the * class has already been loaded. If it has, the same * Class object is returned.
  • *
  • If the delegate property is set to true, * call the loadClass() method of the parent class * loader, if any.
  • *
  • Call findClass() to find this class in our locally * defined repositories.
  • *
  • Call the loadClass() method of our parent * class loader, if any.
  • *
* If the class was found using the above steps, and the * resolve flag is true, this method will then * call resolveClass(Class) on the resulting Class object. * * @param name Name of the class to be loaded * @param resolve If true then resolve the class * * @exception ClassNotFoundException if the class was not found */ public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { if (log.isDebugEnabled()) log.debug("loadClass(" + name + ", " + resolve + ")"); Class clazz = null; /* Log access to stopped classloader if (!started) { try { throw new IllegalStateException(); } catch (IllegalStateException e) { log.info(sm.getString("webappClassLoader.stopped", name), e); } }*/ // (0) Check our previously loaded local class cache clazz = findLoadedClass0(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.1) Check our previously loaded class cache clazz = findLoadedClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.2) Try loading the class with the system class loader, to prevent // the webapp from overriding J2SE classes try { clazz = system.loadClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { // Ignore } // (0.5) Permission to access this class when using a SecurityManager if (securityManager != null) { int i = name.lastIndexOf('.'); if (i >= 0) { try { securityManager.checkPackageAccess(name.substring(0,i)); } catch (SecurityException se) { String error = "Security Violation, attempt to use " + "Restricted Class: " + name; log.info(error, se); throw new ClassNotFoundException(error, se); } } } boolean delegateLoad = delegate || filter(name); // (1) Delegate to our parent if requested if (delegateLoad) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader1 " + parent); ClassLoader loader = parent; if (loader == null) loader = system; try { clazz = loader.loadClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } // (2) Search local repositories if (log.isDebugEnabled()) log.debug(" Searching local repositories"); try { clazz = findClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from local repository"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } // (3) Delegate to parent unconditionally if (!delegateLoad) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader at end: " + parent); ClassLoader loader = parent; if (loader == null) loader = system; try { clazz = loader.loadClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } throw new ClassNotFoundException(name); } /** * Get the Permissions for a CodeSource. If this instance * of WebappClassLoader is for a web application context, * add read FilePermission or JndiPermissions for the base * directory (if unpacked), * the context URL, and jar file resources. * * @param codeSource where the code was loaded from * @return PermissionCollection for CodeSource */ protected PermissionCollection getPermissions(CodeSource codeSource) { String codeUrl = codeSource.getLocation().toString(); PermissionCollection pc; if ((pc = (PermissionCollection)loaderPC.get(codeUrl)) == null) { pc = super.getPermissions(codeSource); if (pc != null) { Iterator perms = permissionList.iterator(); while (perms.hasNext()) { Permission p = (Permission)perms.next(); pc.add(p); } loaderPC.put(codeUrl,pc); } } return (pc); } /** * Returns the search path of URLs for loading classes and resources. * This includes the original list of URLs specified to the constructor, * along with any URLs subsequently appended by the addURL() method. * @return the search path of URLs for loading classes and resources. */ public URL[] getURLs() { if (repositoryURLs != null) { return repositoryURLs; } URL[] external = super.getURLs(); int filesLength = files.length; int jarFilesLength = jarRealFiles.length; int length = filesLength + jarFilesLength + external.length; int i; try { URL[] urls = new URL[length]; for (i = 0; i < length; i++) { if (i < filesLength) { urls[i] = getURL(files[i], true); } else if (i < filesLength + jarFilesLength) { urls[i] = getURL(jarRealFiles[i - filesLength], true); } else { urls[i] = external[i - filesLength - jarFilesLength]; } } repositoryURLs = urls; } catch (MalformedURLException e) { repositoryURLs = new URL[0]; } return repositoryURLs; } // ------------------------------------------------------ Lifecycle Methods /** * Add a lifecycle event listener to this component. * * @param listener The listener to add */ public void addLifecycleListener(LifecycleListener listener) { } /** * Get the lifecycle listeners associated with this lifecycle. If this * Lifecycle has no listeners registered, a zero-length array is returned. */ public LifecycleListener[] findLifecycleListeners() { return new LifecycleListener[0]; } /** * Remove a lifecycle event listener from this component. * * @param listener The listener to remove */ public void removeLifecycleListener(LifecycleListener listener) { } /** * Start the class loader. * * @exception LifecycleException if a lifecycle error occurs */ @Override public void start() throws LifecycleException { // state = LifecycleState.STARTING_PREP; // // WebResource classes = resources.getResource("/WEB-INF/classes"); // if (classes.isDirectory() && classes.canRead()) { // localRepositories.add(classes.getURL()); // } // WebResource[] jars = resources.listResources("/WEB-INF/lib"); // for (WebResource jar : jars) { // if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) { // localRepositories.add(jar.getURL()); // jarModificationTimes.put( // jar.getName(), Long.valueOf(jar.getLastModified())); // } // } // // state = LifecycleState.STARTING; String encoding = null; try { encoding = System.getProperty("file.encoding"); } catch (SecurityException e) { return; } if (encoding.indexOf("EBCDIC")!=-1) { needConvert = true; } // state = LifecycleState.STARTED; } /** * Stop the class loader. * * @exception LifecycleException if a lifecycle error occurs */ public void stop() throws LifecycleException { // Clearing references should be done before setting started to // false, due to possible side effects clearReferences(); // Annotations dont need a running app. //started = false; int length = files.length; for (int i = 0; i < length; i++) { files[i] = null; } length = jarFiles.length; for (int i = 0; i < length; i++) { try { if (jarFiles[i] != null) { jarFiles[i].close(); } } catch (IOException e) { // Ignore } jarFiles[i] = null; } notFoundResources.clear(); resourceEntries.clear(); resources = null; repositories = null; repositoryURLs = null; files = null; jarFiles = null; jarRealFiles = null; jarPath = null; jarNames = null; lastModifiedDates = null; paths = null; hasExternalRepositories = false; parent = null; permissionList.clear(); loaderPC.clear(); if (loaderDir != null) { deleteDir(loaderDir); } } /** * Used to periodically signal to the classloader to release * JAR resources. */ public void closeJARs(boolean force) { if (jarFiles.length > 0) { synchronized (jarFiles) { if (force || (System.currentTimeMillis() > (lastJarAccessed + 90000))) { for (int i = 0; i < jarFiles.length; i++) { try { if (jarFiles[i] != null) { jarFiles[i].close(); jarFiles[i] = null; } } catch (IOException e) { if (log.isDebugEnabled()) { log.debug("Failed to close JAR", e); } } } } } } } // ------------------------------------------------------ Protected Methods /** * Clear references. */ protected void clearReferences() { // Unregister any JDBC drivers loaded by this classloader Enumeration drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = (Driver) drivers.nextElement(); if (driver.getClass().getClassLoader() == this) { try { DriverManager.deregisterDriver(driver); } catch (SQLException e) { log.warn("SQL driver deregistration failed", e); } } } // Null out any static or final fields from loaded classes, // as a workaround for apparent garbage collection bugs if (ENABLE_CLEAR_REFERENCES) { Iterator loadedClasses = ((HashMap)((HashMap) resourceEntries).clone()).values().iterator(); while (loadedClasses.hasNext()) { ResourceEntry entry = (ResourceEntry) loadedClasses.next(); if (entry.loadedClass != null) { Class clazz = entry.loadedClass; try { Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; int mods = field.getModifiers(); if (field.getType().isPrimitive() || (field.getName().indexOf("$") != -1)) { continue; } if (Modifier.isStatic(mods)) { try { field.setAccessible(true); if (Modifier.isFinal(mods)) { if (!((field.getType().getName().startsWith("java.")) || (field.getType().getName().startsWith("javax.")))) { nullInstance(field.get(null)); } } else { field.set(null, null); if (log.isDebugEnabled()) { log.debug("Set field " + field.getName() + " to null in class " + clazz.getName()); } } } catch (Throwable t) { if (log.isDebugEnabled()) { log.debug("Could not set field " + field.getName() + " to null in class " + clazz.getName(), t); } } } } } catch (Throwable t) { if (log.isDebugEnabled()) { log.debug("Could not clean fields for class " + clazz.getName(), t); } } } } } // Clear the IntrospectionUtils cache. IntrospectionUtils.clear(); // Clear the classloader reference in common-logging org.apache.juli.logging.LogFactory.release(this); // Clear the classloader reference in the VM's bean introspector java.beans.Introspector.flushCaches(); } protected void nullInstance(Object instance) { if (instance == null) { return; } Field[] fields = instance.getClass().getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; int mods = field.getModifiers(); if (field.getType().isPrimitive() || (field.getName().indexOf("$") != -1)) { continue; } try { field.setAccessible(true); if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) { // Doing something recursively is too risky continue; } else { Object value = field.get(instance); if (null != value) { Class valueClass = value.getClass(); if (!loadedByThisOrChild(valueClass)) { if (log.isDebugEnabled()) { log.debug("Not setting field " + field.getName() + " to null in object of class " + instance.getClass().getName() + " because the referenced object was of type " + valueClass.getName() + " which was not loaded by this WebappClassLoader."); } } else { field.set(instance, null); if (log.isDebugEnabled()) { log.debug("Set field " + field.getName() + " to null in class " + instance.getClass().getName()); } } } } } catch (Throwable t) { if (log.isDebugEnabled()) { log.debug("Could not set field " + field.getName() + " to null in object instance of class " + instance.getClass().getName(), t); } } } } /** * Determine whether a class was loaded by this class loader or one of * its child class loaders. */ protected boolean loadedByThisOrChild(Class clazz) { boolean result = false; for (ClassLoader classLoader = clazz.getClassLoader(); null != classLoader; classLoader = classLoader.getParent()) { if (classLoader.equals(this)) { result = true; break; } } return result; } /** * Used to periodically signal to the classloader to release JAR resources. */ protected boolean openJARs() { if (started && (jarFiles.length > 0)) { lastJarAccessed = System.currentTimeMillis(); if (jarFiles[0] == null) { for (int i = 0; i < jarFiles.length; i++) { try { jarFiles[i] = new JarFile(jarRealFiles[i]); } catch (IOException e) { if (log.isDebugEnabled()) { log.debug("Failed to open JAR", e); } return false; } } } } return true; } /** * Find specified class in local repositories. * * @return the loaded class, or null if the class isn't found */ protected Class findClassInternal(String name) throws ClassNotFoundException { if (!validate(name)) throw new ClassNotFoundException(name); String tempPath = name.replace('.', '/'); String classPath = tempPath + ".class"; ResourceEntry entry = null; entry = findResourceInternal(name, classPath); if (entry == null) throw new ClassNotFoundException(name); Class clazz = entry.loadedClass; if (clazz != null) return clazz; synchronized (this) { if (entry.binaryContent == null && entry.loadedClass == null) throw new ClassNotFoundException(name); // Looking up the package String packageName = null; int pos = name.lastIndexOf('.'); if (pos != -1) packageName = name.substring(0, pos); Package pkg = null; if (packageName != null) { pkg = getPackage(packageName); // Define the package (if null) if (pkg == null) { try { if (entry.manifest == null) { definePackage(packageName, null, null, null, null, null, null, null); } else { definePackage(packageName, entry.manifest, entry.codeBase); } } catch (IllegalArgumentException e) { // Ignore: normal error due to dual definition of package } pkg = getPackage(packageName); } } if (securityManager != null) { // Checking sealing if (pkg != null) { boolean sealCheck = true; if (pkg.isSealed()) { sealCheck = pkg.isSealed(entry.codeBase); } else { sealCheck = (entry.manifest == null) || !isPackageSealed(packageName, entry.manifest); } if (!sealCheck) throw new SecurityException ("Sealing violation loading " + name + " : Package " + packageName + " is sealed."); } } if (entry.loadedClass == null) { clazz = defineClass(name, entry.binaryContent, 0, entry.binaryContent.length, new CodeSource(entry.codeBase, entry.certificates)); entry.loadedClass = clazz; entry.binaryContent = null; entry.source = null; entry.codeBase = null; entry.manifest = null; entry.certificates = null; } else { clazz = entry.loadedClass; } } return clazz; } /** * Find specified resource in local repositories. This block * will execute under an AccessControl.doPrivilege block. * * @return the loaded resource, or null if the resource isn't found */ protected ResourceEntry findResourceInternal(File file, String path){ ResourceEntry entry = new ResourceEntry(); try { entry.source = getURI(new File(file, path)); entry.codeBase = getURL(new File(file, path), false); } catch (MalformedURLException e) { return null; } return entry; } protected void checkStateForClassLoading(String className) throws ClassNotFoundException { // It is not permitted to load new classes once the web application has // been stopped. try { checkStateForResourceLoading(className); } catch (IllegalStateException ise) { ClassNotFoundException cnfe = new ClassNotFoundException(); cnfe.initCause(ise); throw cnfe; } } protected void checkStateForResourceLoading(String resource) throws IllegalStateException { // It is not permitted to load resources once the web application has // been stopped. if (!started) { String msg = sm.getString("webappClassLoader.stopped", resource); IllegalStateException ise = new IllegalStateException(msg); log.info(msg, ise); throw ise; } } /** * Find specified resource in local repositories. * * @return the loaded resource, or null if the resource isn't found */ protected ResourceEntry findResourceInternal(final String name, final String path) { checkStateForResourceLoading(name); if (name == null || path == null) { return null; } ResourceEntry entry = resourceEntries.get(path); if (entry != null) { return entry; } boolean isClassResource = path.endsWith(CLASS_FILE_SUFFIX); boolean isCacheable = isClassResource; if (!isCacheable) { isCacheable = path.startsWith(SERVICES_PREFIX); } WebResource resource = null; boolean fileNeedConvert = false; resource = resources.getClassLoaderResource(path); if (!resource.exists()) { return null; } entry = new ResourceEntry(); entry.source = resource.getURL(); entry.codeBase = resource.getCodeBase(); entry.lastModified = resource.getLastModified(); if (needConvert && path.endsWith(".properties")) { fileNeedConvert = true; } /* Only cache the binary content if there is some content * available one of the following is true: * a) It is a class file since the binary content is only cached * until the class has been loaded * or * b) The file needs conversion to address encoding issues (see * below) * or * c) The resource is a service provider configuration file located * under META=INF/services * * In all other cases do not cache the content to prevent * excessive memory usage if large resources are present (see * https://issues.apache.org/bugzilla/show_bug.cgi?id=53081). */ if (isCacheable || fileNeedConvert) { byte[] binaryContent = resource.getContent(); if (binaryContent != null) { if (fileNeedConvert) { // Workaround for certain files on platforms that use // EBCDIC encoding, when they are read through FileInputStream. // See commit message of rev.303915 for details // http://svn.apache.org/viewvc?view=revision&revision=303915 String str = new String(binaryContent); try { binaryContent = str.getBytes(StandardCharsets.UTF_8); } catch (Exception e) { return null; } } entry.binaryContent = binaryContent; // The certificates and manifest are made available as a side // effect of reading the binary content entry.certificates = resource.getCertificates(); } } entry.manifest = resource.getManifest(); // if (isClassResource && entry.binaryContent != null && // this.transformers.size() > 0) { // // If the resource is a class just being loaded, decorate it // // with any attached transformers // String className = name.endsWith(CLASS_FILE_SUFFIX) ? // name.substring(0, name.length() - CLASS_FILE_SUFFIX.length()) : name; // String internalName = className.replace(".", "/"); // // for (ClassFileTransformer transformer : this.transformers) { // try { // byte[] transformed = transformer.transform( // this, internalName, null, null, entry.binaryContent // ); // if (transformed != null) { // entry.binaryContent = transformed; // } // } catch (IllegalClassFormatException e) { // log.error(sm.getString("webappClassLoader.transformError", name), e); // return null; // } // } // } // Add the entry in the local resource repository synchronized (resourceEntries) { // Ensures that all the threads which may be in a race to load // a particular class all end up with the same ResourceEntry // instance ResourceEntry entry2 = resourceEntries.get(path); if (entry2 == null) { resourceEntries.put(path, entry); } else { entry = entry2; } } return entry; } /** * Returns true if the specified package name is sealed according to the * given manifest. */ protected boolean isPackageSealed(String name, Manifest man) { String path = name.replace('.', '/') + '/'; Attributes attr = man.getAttributes(path); String sealed = null; if (attr != null) { sealed = attr.getValue(Name.SEALED); } if (sealed == null) { if ((attr = man.getMainAttributes()) != null) { sealed = attr.getValue(Name.SEALED); } } return "true".equalsIgnoreCase(sealed); } /** * Finds the resource with the given name if it has previously been * loaded and cached by this class loader, and return an input stream * to the resource data. If this resource has not been cached, return * null. * * @param name Name of the resource to return */ protected InputStream findLoadedResource(String name) { ResourceEntry entry = (ResourceEntry) resourceEntries.get(name); if (entry != null) { if (entry.binaryContent != null) return new ByteArrayInputStream(entry.binaryContent); } return (null); } /** * Finds the class with the given name if it has previously been * loaded and cached by this class loader, and return the Class object. * If this class has not been cached, return null. * * @param name Name of the resource to return */ protected Class findLoadedClass0(String name) { ResourceEntry entry = (ResourceEntry) resourceEntries.get(name); if (entry != null) { return entry.loadedClass; } return (null); // FIXME - findLoadedResource() } /** * Refresh the system policy file, to pick up eventual changes. */ protected void refreshPolicy() { try { // The policy file may have been modified to adjust // permissions, so we're reloading it when loading or // reloading a Context Policy policy = Policy.getPolicy(); policy.refresh(); } catch (AccessControlException e) { // Some policy files may restrict this, even for the core, // so this exception is ignored } } /** * Filter classes. * * @param name class name * @return true if the class should be filtered */ protected boolean filter(String name) { if (name == null) return false; // Looking up the package String packageName = null; int pos = name.lastIndexOf('.'); if (pos != -1) packageName = name.substring(0, pos); else return false; for (int i = 0; i < packageTriggers.length; i++) { if (packageName.startsWith(packageTriggers[i])) return true; } return false; } /** * Validate a classname. As per SRV.9.7.2, we must restict loading of * classes from J2SE (java.*) and classes of the servlet API * (javax.servlet.*). That should enhance robustness and prevent a number * of user error (where an older version of servlet.jar would be present * in /WEB-INF/lib). * * @param name class name * @return true if the name is valid */ protected boolean validate(String name) { if (name == null) return false; if (name.startsWith("java.")) return false; return true; } /** * Check the specified JAR file, and return true if it does * not contain any of the trigger classes. * * @param jarfile The JAR file to be checked * * @exception IOException if an input/output error occurs */ protected boolean validateJarFile(File jarfile) throws IOException { if (triggers == null) return (true); JarFile jarFile = new JarFile(jarfile); for (int i = 0; i < triggers.length; i++) { Class clazz = null; try { if (parent != null) { clazz = parent.loadClass(triggers[i]); } else { clazz = Class.forName(triggers[i]); } } catch (Throwable t) { clazz = null; } if (clazz == null) continue; String name = triggers[i].replace('.', '/') + ".class"; if (log.isDebugEnabled()) log.debug(" Checking for " + name); JarEntry jarEntry = jarFile.getJarEntry(name); if (jarEntry != null) { log.info("validateJarFile(" + jarfile + ") - jar not loaded. See Servlet Spec 2.3, " + "section 9.7.2. Offending class: " + name); jarFile.close(); return (false); } } jarFile.close(); return (true); } /** * Get URL. */ protected URL getURL(File file, boolean encoded) throws MalformedURLException { File realFile = file; try { realFile = realFile.getCanonicalFile(); } catch (IOException e) { // Ignore } if(encoded) { return getURI(realFile); } else { return realFile.toURL(); } } /** * Get URL. */ protected URL getURI(File file) throws MalformedURLException { File realFile = file; try { realFile = realFile.getCanonicalFile(); } catch (IOException e) { // Ignore } return realFile.toURI().toURL(); } /** * Delete the specified directory, including all of its contents and * subdirectories recursively. * * @param dir File object representing the directory to be deleted */ protected static void deleteDir(File dir) { String files[] = dir.list(); if (files == null) { files = new String[0]; } for (int i = 0; i < files.length; i++) { File file = new File(dir, files[i]); if (file.isDirectory()) { deleteDir(file); } else { try { file.delete(); } catch (SecurityException se) { log.error("The file " + file.getAbsolutePath() + " couldn't be deleted"); } } } try { dir.delete(); } catch (SecurityException se) { log.error("The directory " + dir.getAbsolutePath() + " couldn't be deleted"); } } @Override public void init() throws LifecycleException { // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @Override public void destroy() throws LifecycleException { // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @Override public LifecycleState getState() { // TODO Auto-generated method stub throw new UnsupportedOperationException(); } @Override public String getStateName() { // TODO Auto-generated method stub throw new UnsupportedOperationException(); } }