| /* |
| * Copyright 1999,2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| // Modified by Google |
| |
| package org.apache.catalina.loader; |
| |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.beans.PropertyChangeSupport; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.FilePermission; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Method; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.net.URLStreamHandlerFactory; |
| import java.util.ArrayList; |
| import java.util.jar.JarFile; |
| |
| import javax.management.MBeanRegistration; |
| import javax.management.MBeanServer; |
| import javax.management.ObjectName; |
| import javax.naming.Binding; |
| import javax.naming.NameClassPair; |
| import javax.naming.NamingEnumeration; |
| import javax.naming.NamingException; |
| import javax.naming.directory.DirContext; |
| import javax.servlet.ServletContext; |
| |
| import org.apache.catalina.Container; |
| import org.apache.catalina.Context; |
| import org.apache.catalina.DefaultContext; |
| import org.apache.catalina.Engine; |
| import org.apache.catalina.Globals; |
| import org.apache.catalina.Lifecycle; |
| import org.apache.catalina.LifecycleException; |
| import org.apache.catalina.LifecycleListener; |
| import org.apache.catalina.Loader; |
| import org.apache.catalina.Logger; |
| import org.apache.catalina.core.StandardContext; |
| import org.apache.catalina.util.LifecycleSupport; |
| import org.apache.catalina.util.StringManager; |
| import org.apache.commons.modeler.Registry; |
| import org.apache.naming.resources.DirContextURLStreamHandler; |
| import org.apache.naming.resources.DirContextURLStreamHandlerFactory; |
| import org.apache.naming.resources.Resource; |
| |
| |
| /** |
| * Classloader implementation which is specialized for handling web |
| * applications in the most efficient way, while being Catalina aware (all |
| * accesses to resources are made through the DirContext interface). |
| * This class loader supports detection of modified |
| * Java classes, which can be used to implement auto-reload support. |
| * <p> |
| * This class loader is configured by adding the pathnames of directories, |
| * JAR files, and ZIP files with the <code>addRepository()</code> method, |
| * prior to calling <code>start()</code>. When a new class is required, |
| * these repositories will be consulted first to locate the class. If it |
| * is not present, the system class loader will be used instead. |
| * |
| * @author Craig R. McClanahan |
| * @author Remy Maucherat |
| * @version $Revision: 1.27 $ $Date: 2004/03/02 12:31:57 $ |
| */ |
| |
| public class WebappLoader |
| implements Lifecycle, Loader, PropertyChangeListener, MBeanRegistration { |
| |
| // ----------------------------------------------------------- Constructors |
| |
| |
| /** |
| * Construct a new WebappLoader with no defined parent class loader |
| * (so that the actual parent will be the system class loader). |
| */ |
| public WebappLoader() { |
| |
| this(null); |
| |
| } |
| |
| |
| /** |
| * Construct a new WebappLoader with the specified class loader |
| * to be defined as the parent of the ClassLoader we ultimately create. |
| * |
| * @param parent The parent class loader |
| */ |
| public WebappLoader(ClassLoader parent) { |
| super(); |
| this.parentClassLoader = parent; |
| } |
| |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| |
| /** |
| * First load of the class. |
| */ |
| private static boolean first = true; |
| |
| |
| /** |
| * The class loader being managed by this Loader component. |
| */ |
| private WebappClassLoader classLoader = null; |
| |
| |
| /** |
| * The Container with which this Loader has been associated. |
| */ |
| private Container container = null; |
| |
| |
| /** |
| * The debugging detail level for this component. |
| */ |
| private int debug = 0; |
| |
| |
| /** |
| * The DefaultContext with which this Loader is associated. |
| */ |
| protected DefaultContext defaultContext = null; |
| |
| |
| /** |
| * The "follow standard delegation model" flag that will be used to |
| * configure our ClassLoader. |
| */ |
| private boolean delegate = false; |
| |
| |
| /** |
| * The descriptive information about this Loader implementation. |
| */ |
| private static final String info = |
| "org.apache.catalina.loader.WebappLoader/1.0"; |
| |
| |
| /** |
| * The lifecycle event support for this component. |
| */ |
| protected LifecycleSupport lifecycle = new LifecycleSupport(this); |
| |
| |
| /** |
| * The Java class name of the ClassLoader implementation to be used. |
| * This class should extend WebappClassLoader, otherwise, a different |
| * loader implementation must be used. |
| */ |
| private String loaderClass = |
| "org.apache.catalina.loader.WebappClassLoader"; |
| |
| |
| /** |
| * The parent class loader of the class loader we will create. |
| */ |
| private ClassLoader parentClassLoader = null; |
| |
| |
| /** |
| * The reloadable flag for this Loader. |
| */ |
| private boolean reloadable = false; |
| |
| |
| /** |
| * The set of repositories associated with this class loader. |
| */ |
| private String repositories[] = new String[0]; |
| |
| |
| /** |
| * The string manager for this package. |
| */ |
| protected static final StringManager sm = |
| StringManager.getManager(Constants.Package); |
| |
| |
| /** |
| * Has this component been started? |
| */ |
| private boolean started = false; |
| |
| |
| /** |
| * The property change support for this component. |
| */ |
| protected PropertyChangeSupport support = new PropertyChangeSupport(this); |
| |
| |
| /** |
| * Classpath set in the loader. |
| */ |
| private String classpath = null; |
| |
| |
| /** |
| * Repositories that are set in the loader, for JMX. |
| */ |
| private ArrayList loaderRepositories = null; |
| |
| |
| // ------------------------------------------------------------- Properties |
| |
| |
| /** |
| * Return the Java class loader to be used by this Container. |
| */ |
| public ClassLoader getClassLoader() { |
| |
| /* |
| * GOOGLE: When tomcat is embedded (as we are doing), classLoader is |
| * always null. Returning the parentClassLoader seems to do the right |
| * thing. |
| */ |
| if (classLoader != null) { |
| return classLoader; |
| } else { |
| return parentClassLoader; |
| } |
| |
| } |
| |
| |
| /** |
| * Return the Container with which this Logger has been associated. |
| */ |
| public Container getContainer() { |
| |
| return (container); |
| |
| } |
| |
| |
| /** |
| * Set the Container with which this Logger has been associated. |
| * |
| * @param container The associated Container |
| */ |
| public void setContainer(Container container) { |
| |
| // Deregister from the old Container (if any) |
| if ((this.container != null) && (this.container instanceof Context)) |
| ((Context) this.container).removePropertyChangeListener(this); |
| |
| // Process this property change |
| Container oldContainer = this.container; |
| this.container = container; |
| support.firePropertyChange("container", oldContainer, this.container); |
| |
| // Register with the new Container (if any) |
| if ((this.container != null) && (this.container instanceof Context)) { |
| setReloadable( ((Context) this.container).getReloadable() ); |
| ((Context) this.container).addPropertyChangeListener(this); |
| } |
| |
| } |
| |
| |
| /** |
| * Return the DefaultContext with which this Loader is associated. |
| * XXX What is that ??? |
| */ |
| public DefaultContext getDefaultContext() { |
| |
| return (this.defaultContext); |
| |
| } |
| |
| |
| /** |
| * Set the DefaultContext with which this Loader is associated. |
| * |
| * @param defaultContext The newly associated DefaultContext |
| */ |
| public void setDefaultContext(DefaultContext defaultContext) { |
| |
| DefaultContext oldDefaultContext = this.defaultContext; |
| this.defaultContext = defaultContext; |
| support.firePropertyChange("defaultContext", oldDefaultContext, this.defaultContext); |
| |
| } |
| |
| |
| /** |
| * Return the debugging detail level for this component. |
| */ |
| public int getDebug() { |
| |
| return (this.debug); |
| |
| } |
| |
| |
| /** |
| * Set the debugging detail level for this component. |
| * |
| * @param debug The new debugging detail level |
| */ |
| public void setDebug(int debug) { |
| |
| int oldDebug = this.debug; |
| this.debug = debug; |
| support.firePropertyChange("debug", new Integer(oldDebug), |
| new Integer(this.debug)); |
| |
| } |
| |
| |
| /** |
| * Return the "follow standard delegation model" flag used to configure |
| * our ClassLoader. |
| */ |
| public boolean getDelegate() { |
| |
| return (this.delegate); |
| |
| } |
| |
| |
| /** |
| * Set the "follow standard delegation model" flag used to configure |
| * our ClassLoader. |
| * |
| * @param delegate The new flag |
| */ |
| public void setDelegate(boolean delegate) { |
| |
| boolean oldDelegate = this.delegate; |
| this.delegate = delegate; |
| support.firePropertyChange("delegate", new Boolean(oldDelegate), |
| new Boolean(this.delegate)); |
| |
| } |
| |
| |
| /** |
| * Return descriptive information about this Loader implementation and |
| * the corresponding version number, in the format |
| * <code><description>/<version></code>. |
| */ |
| public String getInfo() { |
| |
| return (info); |
| |
| } |
| |
| |
| /** |
| * Return the ClassLoader class name. |
| */ |
| public String getLoaderClass() { |
| |
| return (this.loaderClass); |
| |
| } |
| |
| |
| /** |
| * Set the ClassLoader class name. |
| * |
| * @param loaderClass The new ClassLoader class name |
| */ |
| public void setLoaderClass(String loaderClass) { |
| |
| this.loaderClass = loaderClass; |
| |
| } |
| |
| |
| /** |
| * Return the reloadable flag for this Loader. |
| */ |
| public boolean getReloadable() { |
| |
| return (this.reloadable); |
| |
| } |
| |
| |
| /** |
| * Set the reloadable flag for this Loader. |
| * |
| * @param reloadable The new reloadable flag |
| */ |
| public void setReloadable(boolean reloadable) { |
| |
| // Process this property change |
| boolean oldReloadable = this.reloadable; |
| this.reloadable = reloadable; |
| support.firePropertyChange("reloadable", |
| new Boolean(oldReloadable), |
| new Boolean(this.reloadable)); |
| |
| } |
| |
| |
| // --------------------------------------------------------- Public Methods |
| |
| |
| /** |
| * Add a property change listener to this component. |
| * |
| * @param listener The listener to add |
| */ |
| public void addPropertyChangeListener(PropertyChangeListener listener) { |
| |
| support.addPropertyChangeListener(listener); |
| |
| } |
| |
| |
| /** |
| * Add a new repository to the set of repositories for this class loader. |
| * |
| * @param repository Repository to be added |
| */ |
| public void addRepository(String repository) { |
| |
| if (log.isDebugEnabled()) |
| log.debug(sm.getString("webappLoader.addRepository", repository)); |
| |
| for (int i = 0; i < repositories.length; i++) { |
| if (repository.equals(repositories[i])) |
| return; |
| } |
| String results[] = new String[repositories.length + 1]; |
| for (int i = 0; i < repositories.length; i++) |
| results[i] = repositories[i]; |
| results[repositories.length] = repository; |
| repositories = results; |
| |
| if (started && (classLoader != null)) { |
| classLoader.addRepository(repository); |
| if( loaderRepositories != null ) loaderRepositories.add(repository); |
| setClassPath(); |
| } |
| |
| } |
| |
| |
| /** |
| * Return the set of repositories defined for this class loader. |
| * If none are defined, 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()); |
| |
| } |
| |
| public String[] getRepositories() { |
| return ((String[])repositories.clone()); |
| } |
| |
| /** Extra repositories for this loader |
| */ |
| public String getRepositoriesString() { |
| StringBuffer sb=new StringBuffer(); |
| for( int i=0; i<repositories.length ; i++ ) { |
| sb.append( repositories[i]).append(":"); |
| } |
| return sb.toString(); |
| } |
| |
| public String[] getLoaderRepositories() { |
| if( loaderRepositories==null ) return null; |
| String res[]=new String[ loaderRepositories.size()]; |
| loaderRepositories.toArray(res); |
| return res; |
| } |
| |
| public String getLoaderRepositoriesString() { |
| String repositories[]=getLoaderRepositories(); |
| StringBuffer sb=new StringBuffer(); |
| for( int i=0; i<repositories.length ; i++ ) { |
| sb.append( repositories[i]).append(":"); |
| } |
| return sb.toString(); |
| } |
| |
| |
| /** |
| * Classpath, as set in org.apache.catalina.jsp_classpath context |
| * property |
| * |
| * @return The classpath |
| */ |
| public String getClasspath() { |
| return classpath; |
| } |
| |
| |
| /** |
| * Has the internal repository associated with this Loader been modified, |
| * such that the loaded classes should be reloaded? |
| */ |
| public boolean modified() { |
| |
| return (classLoader.modified()); |
| |
| } |
| |
| |
| /** |
| * Used to periodically signal to the classloader to release JAR resources. |
| */ |
| public void closeJARs(boolean force) { |
| if (classLoader !=null){ |
| classLoader.closeJARs(force); |
| } |
| } |
| |
| |
| /** |
| * Remove a property change listener from this component. |
| * |
| * @param listener The listener to remove |
| */ |
| public void removePropertyChangeListener(PropertyChangeListener listener) { |
| |
| support.removePropertyChangeListener(listener); |
| |
| } |
| |
| |
| /** |
| * Return a String representation of this component. |
| */ |
| public String toString() { |
| |
| StringBuffer sb = new StringBuffer("WebappLoader["); |
| if (container != null) |
| sb.append(container.getName()); |
| sb.append("]"); |
| return (sb.toString()); |
| |
| } |
| |
| |
| // ------------------------------------------------------ Lifecycle Methods |
| |
| |
| /** |
| * Add a lifecycle event listener to this component. |
| * |
| * @param listener The listener to add |
| */ |
| public void addLifecycleListener(LifecycleListener listener) { |
| |
| lifecycle.addLifecycleListener(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 lifecycle.findLifecycleListeners(); |
| |
| } |
| |
| |
| /** |
| * Remove a lifecycle event listener from this component. |
| * |
| * @param listener The listener to remove |
| */ |
| public void removeLifecycleListener(LifecycleListener listener) { |
| |
| lifecycle.removeLifecycleListener(listener); |
| |
| } |
| |
| private boolean initialized=false; |
| |
| public void init() { |
| initialized=true; |
| |
| if( oname==null ) { |
| // not registered yet - standalone or API |
| if( container instanceof StandardContext) { |
| // Register ourself. The container must be a webapp |
| try { |
| StandardContext ctx=(StandardContext)container; |
| Engine eng=(Engine)ctx.getParent().getParent(); |
| String path = ctx.getPath(); |
| if (path.equals("")) { |
| path = "/"; |
| } |
| oname=new ObjectName(ctx.getEngineName() + ":type=Loader,path=" + |
| path + ",host=" + ctx.getParent().getName()); |
| Registry.getRegistry(null, null).registerComponent(this, oname, null); |
| controller=oname; |
| } catch (Exception e) { |
| log.error("Error registering loader", e ); |
| } |
| } |
| } |
| |
| if( container == null ) { |
| // JMX created the loader |
| // TODO |
| |
| } |
| } |
| |
| public void destroy() { |
| if( controller==oname ) { |
| // Self-registration, undo it |
| Registry.getRegistry(null, null).unregisterComponent(oname); |
| oname = null; |
| } |
| initialized = false; |
| |
| } |
| |
| /** |
| * Start this component, initializing our associated class loader. |
| * |
| * @exception LifecycleException if a lifecycle error occurs |
| */ |
| public void start() throws LifecycleException { |
| // Validate and update our current component state |
| if( ! initialized ) init(); |
| if (started) |
| throw new LifecycleException |
| (sm.getString("webappLoader.alreadyStarted")); |
| if (log.isDebugEnabled()) |
| log.debug(sm.getString("webappLoader.starting")); |
| lifecycle.fireLifecycleEvent(START_EVENT, null); |
| started = true; |
| |
| if (container.getResources() == null) { |
| log.info("No resources for " + container); |
| return; |
| } |
| // Register a stream handler factory for the JNDI protocol |
| URLStreamHandlerFactory streamHandlerFactory = |
| new DirContextURLStreamHandlerFactory(); |
| if (first) { |
| first = false; |
| try { |
| URL.setURLStreamHandlerFactory(streamHandlerFactory); |
| } catch (Exception e) { |
| // Log and continue anyway, this is not critical |
| log.error("Error registering jndi stream handler", e); |
| } catch (Throwable t) { |
| // This is likely a dual registration |
| log.info("Dual registration of jndi stream handler: " |
| + t.getMessage()); |
| } |
| } |
| |
| // Construct a class loader based on our current repositories list |
| try { |
| |
| classLoader = createClassLoader(); |
| classLoader.setResources(container.getResources()); |
| classLoader.setDebug(this.debug); |
| classLoader.setDelegate(this.delegate); |
| |
| for (int i = 0; i < repositories.length; i++) { |
| classLoader.addRepository(repositories[i]); |
| } |
| |
| // Configure our repositories |
| setRepositories(); |
| setClassPath(); |
| |
| setPermissions(); |
| |
| if (classLoader instanceof Lifecycle) |
| ((Lifecycle) classLoader).start(); |
| |
| // Binding the Webapp class loader to the directory context |
| DirContextURLStreamHandler.bind |
| ((ClassLoader) classLoader, this.container.getResources()); |
| |
| StandardContext ctx=(StandardContext)container; |
| Engine eng=(Engine)ctx.getParent().getParent(); |
| String path = ctx.getPath(); |
| if (path.equals("")) { |
| path = "/"; |
| } |
| ObjectName cloname = new ObjectName |
| (ctx.getEngineName() + ":type=WebappClassLoader,path=" |
| + path + ",host=" + ctx.getParent().getName()); |
| Registry.getRegistry(null, null) |
| .registerComponent(classLoader, cloname, null); |
| |
| } catch (Throwable t) { |
| log.error( "LifecycleException ", t ); |
| throw new LifecycleException("start: ", t); |
| } |
| |
| } |
| |
| |
| /** |
| * Stop this component, finalizing our associated class loader. |
| * |
| * @exception LifecycleException if a lifecycle error occurs |
| */ |
| public void stop() throws LifecycleException { |
| |
| // Validate and update our current component state |
| if (!started) |
| throw new LifecycleException |
| (sm.getString("webappLoader.notStarted")); |
| if (log.isDebugEnabled()) |
| log.debug(sm.getString("webappLoader.stopping")); |
| lifecycle.fireLifecycleEvent(STOP_EVENT, null); |
| started = false; |
| |
| // Remove context attributes as appropriate |
| if (container instanceof Context) { |
| ServletContext servletContext = |
| ((Context) container).getServletContext(); |
| servletContext.removeAttribute(Globals.CLASS_PATH_ATTR); |
| } |
| |
| // Throw away our current class loader |
| if (classLoader instanceof Lifecycle) |
| ((Lifecycle) classLoader).stop(); |
| DirContextURLStreamHandler.unbind((ClassLoader) classLoader); |
| |
| try { |
| StandardContext ctx=(StandardContext)container; |
| Engine eng=(Engine)ctx.getParent().getParent(); |
| String path = ctx.getPath(); |
| if (path.equals("")) { |
| path = "/"; |
| } |
| ObjectName cloname = new ObjectName |
| (ctx.getEngineName() + ":type=WebappClassLoader,path=" |
| + path + ",host=" + ctx.getParent().getName()); |
| Registry.getRegistry(null, null).unregisterComponent(cloname); |
| } catch (Throwable t) { |
| log.error( "LifecycleException ", t ); |
| } |
| |
| classLoader = null; |
| |
| destroy(); |
| |
| } |
| |
| |
| // ----------------------------------------- PropertyChangeListener Methods |
| |
| |
| /** |
| * Process property change events from our associated Context. |
| * |
| * @param event The property change event that has occurred |
| */ |
| public void propertyChange(PropertyChangeEvent event) { |
| |
| // Validate the source of this event |
| if (!(event.getSource() instanceof Context)) |
| return; |
| Context context = (Context) event.getSource(); |
| |
| // Process a relevant property change |
| if (event.getPropertyName().equals("reloadable")) { |
| try { |
| setReloadable |
| ( ((Boolean) event.getNewValue()).booleanValue() ); |
| } catch (NumberFormatException e) { |
| log.error(sm.getString("webappLoader.reloadable", |
| event.getNewValue().toString())); |
| } |
| } |
| |
| } |
| |
| |
| // ------------------------------------------------------- Private Methods |
| |
| |
| /** |
| * Create associated classLoader. |
| */ |
| private WebappClassLoader createClassLoader() |
| throws Exception { |
| |
| Class clazz = Class.forName(loaderClass); |
| WebappClassLoader classLoader = null; |
| |
| if (parentClassLoader == null) { |
| parentClassLoader = Thread.currentThread().getContextClassLoader(); |
| } |
| Class[] argTypes = { ClassLoader.class }; |
| Object[] args = { parentClassLoader }; |
| Constructor constr = clazz.getConstructor(argTypes); |
| classLoader = (WebappClassLoader) constr.newInstance(args); |
| |
| return classLoader; |
| |
| } |
| |
| |
| /** |
| * Log a message on the Logger associated with our Container (if any) |
| * |
| * @param message Message to be logged |
| */ |
| private void log(String message) { |
| |
| Logger logger = null; |
| if (container != null) |
| logger = container.getLogger(); |
| if (logger != null) |
| logger.log("WebappLoader[" + container.getName() + "]: " |
| + message); |
| else { |
| String containerName = null; |
| if (container != null) |
| containerName = container.getName(); |
| System.out.println("WebappLoader[" + containerName |
| + "]: " + message); |
| } |
| |
| } |
| |
| |
| /** |
| * Log a message on the Logger associated with our Container (if any) |
| * |
| * @param message Message to be logged |
| * @param throwable Associated exception |
| */ |
| private void log(String message, Throwable throwable) { |
| |
| Logger logger = null; |
| if (container != null) |
| logger = container.getLogger(); |
| if (logger != null) { |
| logger.log("WebappLoader[" + container.getName() + "] " |
| + message, throwable); |
| } else { |
| String containerName = null; |
| if (container != null) |
| containerName = container.getName(); |
| System.out.println("WebappLoader[" + containerName |
| + "]: " + message); |
| System.out.println("" + throwable); |
| throwable.printStackTrace(System.out); |
| } |
| |
| } |
| |
| |
| /** |
| * Configure associated class loader permissions. |
| */ |
| private void setPermissions() { |
| |
| if (System.getSecurityManager() == null) |
| return; |
| if (!(container instanceof Context)) |
| return; |
| |
| // Tell the class loader the root of the context |
| ServletContext servletContext = |
| ((Context) container).getServletContext(); |
| |
| // Assigning permissions for the work directory |
| File workDir = |
| (File) servletContext.getAttribute(Globals.WORK_DIR_ATTR); |
| if (workDir != null) { |
| try { |
| String workDirPath = workDir.getCanonicalPath(); |
| classLoader.addPermission |
| (new FilePermission(workDirPath, "read,write")); |
| classLoader.addPermission |
| (new FilePermission(workDirPath + File.separator + "-", |
| "read,write,delete")); |
| } catch (IOException e) { |
| // Ignore |
| } |
| } |
| |
| try { |
| |
| URL rootURL = servletContext.getResource("/"); |
| classLoader.addPermission(rootURL); |
| |
| String contextRoot = servletContext.getRealPath("/"); |
| if (contextRoot != null) { |
| try { |
| contextRoot = (new File(contextRoot)).getCanonicalPath(); |
| classLoader.addPermission(contextRoot); |
| } catch (IOException e) { |
| // Ignore |
| } |
| } |
| |
| URL classesURL = servletContext.getResource("/WEB-INF/classes/"); |
| classLoader.addPermission(classesURL); |
| URL libURL = servletContext.getResource("/WEB-INF/lib/"); |
| classLoader.addPermission(libURL); |
| |
| if (contextRoot != null) { |
| |
| if (libURL != null) { |
| File rootDir = new File(contextRoot); |
| File libDir = new File(rootDir, "WEB-INF/lib/"); |
| try { |
| String path = libDir.getCanonicalPath(); |
| classLoader.addPermission(path); |
| } catch (IOException e) { |
| } |
| } |
| |
| } else { |
| |
| if (workDir != null) { |
| if (libURL != null) { |
| File libDir = new File(workDir, "WEB-INF/lib/"); |
| try { |
| String path = libDir.getCanonicalPath(); |
| classLoader.addPermission(path); |
| } catch (IOException e) { |
| } |
| } |
| if (classesURL != null) { |
| File classesDir = new File(workDir, "WEB-INF/classes/"); |
| try { |
| String path = classesDir.getCanonicalPath(); |
| classLoader.addPermission(path); |
| } catch (IOException e) { |
| } |
| } |
| } |
| |
| } |
| |
| } catch (MalformedURLException e) { |
| } |
| |
| } |
| |
| |
| /** |
| * Configure the repositories for our class loader, based on the |
| * associated Context. |
| */ |
| private void setRepositories() { |
| |
| if (!(container instanceof Context)) |
| return; |
| ServletContext servletContext = |
| ((Context) container).getServletContext(); |
| if (servletContext == null) |
| return; |
| |
| loaderRepositories=new ArrayList(); |
| // Loading the work directory |
| File workDir = |
| (File) servletContext.getAttribute(Globals.WORK_DIR_ATTR); |
| if (workDir == null) { |
| log.info("No work dir for " + servletContext); |
| } |
| |
| if( log.isDebugEnabled()) |
| log.debug(sm.getString("webappLoader.deploy", workDir.getAbsolutePath())); |
| |
| classLoader.setWorkDir(workDir); |
| |
| DirContext resources = container.getResources(); |
| |
| // Setting up the class repository (/WEB-INF/classes), if it exists |
| |
| String classesPath = "/WEB-INF/classes"; |
| DirContext classes = null; |
| |
| try { |
| Object object = resources.lookup(classesPath); |
| if (object instanceof DirContext) { |
| classes = (DirContext) object; |
| } |
| } catch(NamingException e) { |
| // Silent catch: it's valid that no /WEB-INF/classes collection |
| // exists |
| } |
| |
| if (classes != null) { |
| |
| File classRepository = null; |
| |
| String absoluteClassesPath = |
| servletContext.getRealPath(classesPath); |
| |
| if (absoluteClassesPath != null) { |
| |
| classRepository = new File(absoluteClassesPath); |
| |
| } else { |
| |
| classRepository = new File(workDir, classesPath); |
| classRepository.mkdirs(); |
| copyDir(classes, classRepository); |
| |
| } |
| |
| if(log.isDebugEnabled()) |
| log.debug(sm.getString("webappLoader.classDeploy", classesPath, |
| classRepository.getAbsolutePath())); |
| |
| |
| // Adding the repository to the class loader |
| classLoader.addRepository(classesPath + "/", classRepository); |
| loaderRepositories.add(classesPath + "/" ); |
| |
| } |
| |
| // Setting up the JAR repository (/WEB-INF/lib), if it exists |
| |
| String libPath = "/WEB-INF/lib"; |
| |
| classLoader.setJarPath(libPath); |
| |
| DirContext libDir = null; |
| // Looking up directory /WEB-INF/lib in the context |
| try { |
| Object object = resources.lookup(libPath); |
| if (object instanceof DirContext) |
| libDir = (DirContext) object; |
| } catch(NamingException e) { |
| // Silent catch: it's valid that no /WEB-INF/lib collection |
| // exists |
| } |
| |
| if (libDir != null) { |
| |
| boolean copyJars = false; |
| String absoluteLibPath = servletContext.getRealPath(libPath); |
| |
| File destDir = null; |
| |
| if (absoluteLibPath != null) { |
| destDir = new File(absoluteLibPath); |
| } else { |
| copyJars = true; |
| destDir = new File(workDir, libPath); |
| destDir.mkdirs(); |
| } |
| |
| // Looking up directory /WEB-INF/lib in the context |
| try { |
| // GOOGLE: "enum" is a reserved word in JDK 1.5 |
| NamingEnumeration enum_ = resources.listBindings(libPath); |
| while (enum_.hasMoreElements()) { |
| |
| Binding binding = (Binding) enum_.nextElement(); |
| String filename = libPath + "/" + binding.getName(); |
| if (!filename.endsWith(".jar")) |
| continue; |
| |
| // Copy JAR in the work directory, always (the JAR file |
| // would get locked otherwise, which would make it |
| // impossible to update it or remove it at runtime) |
| File destFile = new File(destDir, binding.getName()); |
| |
| if( log.isDebugEnabled()) |
| log.debug(sm.getString("webappLoader.jarDeploy", filename, |
| destFile.getAbsolutePath())); |
| |
| Resource jarResource = (Resource) binding.getObject(); |
| if (copyJars) { |
| if (!copy(jarResource.streamContent(), |
| new FileOutputStream(destFile))) |
| continue; |
| } |
| |
| try { |
| JarFile jarFile = new JarFile(destFile); |
| classLoader.addJar(filename, jarFile, destFile); |
| } catch (Exception ex) { |
| // Catch the exception if there is an empty jar file |
| // Should ignore and continute loading other jar files |
| // in the dir |
| } |
| |
| loaderRepositories.add( filename ); |
| |
| } |
| } catch (NamingException e) { |
| // Silent catch: it's valid that no /WEB-INF/lib directory |
| // exists |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| |
| } |
| |
| } |
| |
| |
| /** |
| * Set the appropriate context attribute for our class path. This |
| * is required only because Jasper depends on it. |
| */ |
| private void setClassPath() { |
| |
| // Validate our current state information |
| if (!(container instanceof Context)) |
| return; |
| ServletContext servletContext = |
| ((Context) container).getServletContext(); |
| if (servletContext == null) |
| return; |
| |
| if (container instanceof StandardContext) { |
| String baseClasspath = |
| ((StandardContext) container).getCompilerClasspath(); |
| if (baseClasspath != null) { |
| servletContext.setAttribute(Globals.CLASS_PATH_ATTR, |
| baseClasspath); |
| return; |
| } |
| } |
| |
| StringBuffer classpath = new StringBuffer(); |
| |
| // Assemble the class path information from our class loader chain |
| ClassLoader loader = getClassLoader(); |
| int layers = 0; |
| int n = 0; |
| while (loader != null) { |
| if (!(loader instanceof URLClassLoader)) { |
| String cp=getClasspath( loader ); |
| if( cp==null ) { |
| log.info( "Unknown loader " + loader + " " + loader.getClass()); |
| break; |
| } else { |
| if (n > 0) |
| classpath.append(File.pathSeparator); |
| classpath.append(cp); |
| n++; |
| } |
| break; |
| //continue; |
| } |
| URL repositories[] = |
| ((URLClassLoader) loader).getURLs(); |
| for (int i = 0; i < repositories.length; i++) { |
| String repository = repositories[i].toString(); |
| if (repository.startsWith("file://")) |
| repository = repository.substring(7); |
| else if (repository.startsWith("file:")) |
| repository = repository.substring(5); |
| else if (repository.startsWith("jndi:")) |
| repository = |
| servletContext.getRealPath(repository.substring(5)); |
| else |
| continue; |
| if (repository == null) |
| continue; |
| if (n > 0) |
| classpath.append(File.pathSeparator); |
| classpath.append(repository); |
| n++; |
| } |
| loader = loader.getParent(); |
| layers++; |
| } |
| |
| this.classpath=classpath.toString(); |
| |
| // Store the assembled class path as a servlet context attribute |
| servletContext.setAttribute(Globals.CLASS_PATH_ATTR, |
| classpath.toString()); |
| |
| } |
| |
| // try to extract the classpath from a loader that is not URLClassLoader |
| private String getClasspath( ClassLoader loader ) { |
| try { |
| Method m=loader.getClass().getMethod("getClasspath", new Class[] {}); |
| if( log.isTraceEnabled()) |
| log.trace("getClasspath " + m ); |
| if( m==null ) return null; |
| Object o=m.invoke( loader, new Object[] {} ); |
| if( log.isDebugEnabled() ) |
| log.debug("gotClasspath " + o); |
| if( o instanceof String ) |
| return (String)o; |
| return null; |
| } catch( Exception ex ) { |
| if (log.isDebugEnabled()) |
| log.debug("getClasspath ", ex); |
| } |
| return null; |
| } |
| |
| /** |
| * Copy directory. |
| */ |
| private boolean copyDir(DirContext srcDir, File destDir) { |
| |
| try { |
| |
| // GOOGLE: "enum" is a reserved word in JDK 1.5 |
| NamingEnumeration enum_ = srcDir.list(""); |
| while (enum_.hasMoreElements()) { |
| NameClassPair ncPair = |
| (NameClassPair) enum_.nextElement(); |
| String name = ncPair.getName(); |
| Object object = srcDir.lookup(name); |
| File currentFile = new File(destDir, name); |
| if (object instanceof Resource) { |
| InputStream is = ((Resource) object).streamContent(); |
| OutputStream os = new FileOutputStream(currentFile); |
| if (!copy(is, os)) |
| return false; |
| } else if (object instanceof InputStream) { |
| OutputStream os = new FileOutputStream(currentFile); |
| if (!copy((InputStream) object, os)) |
| return false; |
| } else if (object instanceof DirContext) { |
| currentFile.mkdir(); |
| copyDir((DirContext) object, currentFile); |
| } |
| } |
| |
| } catch (NamingException e) { |
| return false; |
| } catch (IOException e) { |
| return false; |
| } |
| |
| return true; |
| |
| } |
| |
| |
| /** |
| * Copy a file to the specified temp directory. This is required only |
| * because Jasper depends on it. |
| */ |
| private boolean copy(InputStream is, OutputStream os) { |
| |
| try { |
| byte[] buf = new byte[4096]; |
| while (true) { |
| int len = is.read(buf); |
| if (len < 0) |
| break; |
| os.write(buf, 0, len); |
| } |
| is.close(); |
| os.close(); |
| } catch (IOException e) { |
| return false; |
| } |
| |
| return true; |
| |
| } |
| |
| |
| private static org.apache.commons.logging.Log log= |
| org.apache.commons.logging.LogFactory.getLog( WebappLoader.class ); |
| |
| private ObjectName oname; |
| private MBeanServer mserver; |
| private String domain; |
| private ObjectName controller; |
| |
| public ObjectName preRegister(MBeanServer server, |
| ObjectName name) throws Exception { |
| oname=name; |
| mserver=server; |
| domain=name.getDomain(); |
| |
| return name; |
| } |
| |
| public void postRegister(Boolean registrationDone) { |
| } |
| |
| public void preDeregister() throws Exception { |
| } |
| |
| public void postDeregister() { |
| } |
| |
| public ObjectName getController() { |
| return controller; |
| } |
| |
| public void setController(ObjectName controller) { |
| this.controller = controller; |
| } |
| |
| } |