/* * 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. */ 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.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.core.StandardContext; import org.apache.catalina.util.LifecycleSupport; import org.apache.catalina.util.StringManager; import org.apache.naming.resources.DirContextURLStreamHandler; import org.apache.naming.resources.DirContextURLStreamHandlerFactory; import org.apache.naming.resources.Resource; import org.apache.tomcat.util.modeler.Registry; /** * 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. *

* This class loader is configured by adding the pathnames of directories, * JAR files, and ZIP files with the addRepository() method, * prior to calling start(). 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$ $Date$ */ 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 "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() { return ((ClassLoader) classLoader); } /** * 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 "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 * <description>/<version>. */ 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(); } } /** * Execute a periodic task, such as reloading, etc. This method will be * invoked inside the classloading context of this container. Unexpected * throwables will be caught and logged. */ public void backgroundProcess() { if (reloadable && modified()) { try { Thread.currentThread().setContextClassLoader (WebappLoader.class.getClassLoader()); if (container instanceof StandardContext) { ((StandardContext) container).reload(); } } finally { if (container.getLoader() != null) { Thread.currentThread().setContextClassLoader (container.getLoader().getClassLoader()); } } } else { closeJARs(false); } } /** * 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 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 { NamingEnumeration enumeration = srcDir.list(""); while (enumeration.hasMoreElements()) { NameClassPair ncPair = (NameClassPair) enumeration.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.jboss.logging.Logger log= org.jboss.logging.Logger.getLogger( 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; } }