0001: /*
0002: * Copyright 1999,2004 The Apache Software Foundation.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package org.apache.catalina.loader;
0018:
0019: import java.beans.PropertyChangeEvent;
0020: import java.beans.PropertyChangeListener;
0021: import java.beans.PropertyChangeSupport;
0022: import java.io.File;
0023: import java.io.FileOutputStream;
0024: import java.io.FilePermission;
0025: import java.io.IOException;
0026: import java.io.InputStream;
0027: import java.io.OutputStream;
0028: import java.lang.reflect.Constructor;
0029: import java.lang.reflect.Method;
0030: import java.net.MalformedURLException;
0031: import java.net.URL;
0032: import java.net.URLClassLoader;
0033: import java.net.URLStreamHandlerFactory;
0034: import java.util.ArrayList;
0035: import java.util.jar.JarFile;
0036:
0037: import javax.management.MBeanRegistration;
0038: import javax.management.MBeanServer;
0039: import javax.management.ObjectName;
0040: import javax.naming.Binding;
0041: import javax.naming.NameClassPair;
0042: import javax.naming.NamingEnumeration;
0043: import javax.naming.NamingException;
0044: import javax.naming.directory.DirContext;
0045: import javax.servlet.ServletContext;
0046:
0047: import org.apache.catalina.Container;
0048: import org.apache.catalina.Context;
0049: import org.apache.catalina.DefaultContext;
0050: import org.apache.catalina.Engine;
0051: import org.apache.catalina.Globals;
0052: import org.apache.catalina.Lifecycle;
0053: import org.apache.catalina.LifecycleException;
0054: import org.apache.catalina.LifecycleListener;
0055: import org.apache.catalina.Loader;
0056: import org.apache.catalina.Logger;
0057: import org.apache.catalina.core.StandardContext;
0058: import org.apache.catalina.util.LifecycleSupport;
0059: import org.apache.catalina.util.StringManager;
0060: import org.apache.commons.modeler.Registry;
0061: import org.apache.naming.resources.DirContextURLStreamHandler;
0062: import org.apache.naming.resources.DirContextURLStreamHandlerFactory;
0063: import org.apache.naming.resources.Resource;
0064:
0065: /**
0066: * Classloader implementation which is specialized for handling web
0067: * applications in the most efficient way, while being Catalina aware (all
0068: * accesses to resources are made through the DirContext interface).
0069: * This class loader supports detection of modified
0070: * Java classes, which can be used to implement auto-reload support.
0071: * <p>
0072: * This class loader is configured by adding the pathnames of directories,
0073: * JAR files, and ZIP files with the <code>addRepository()</code> method,
0074: * prior to calling <code>start()</code>. When a new class is required,
0075: * these repositories will be consulted first to locate the class. If it
0076: * is not present, the system class loader will be used instead.
0077: *
0078: * @author Craig R. McClanahan
0079: * @author Remy Maucherat
0080: * @version $Revision: 1.27 $ $Date: 2004/03/02 12:31:57 $
0081: */
0082:
0083: public class WebappLoader implements Lifecycle, Loader,
0084: PropertyChangeListener, MBeanRegistration {
0085:
0086: // ----------------------------------------------------------- Constructors
0087:
0088: /**
0089: * Construct a new WebappLoader with no defined parent class loader
0090: * (so that the actual parent will be the system class loader).
0091: */
0092: public WebappLoader() {
0093:
0094: this (null);
0095:
0096: }
0097:
0098: /**
0099: * Construct a new WebappLoader with the specified class loader
0100: * to be defined as the parent of the ClassLoader we ultimately create.
0101: *
0102: * @param parent The parent class loader
0103: */
0104: public WebappLoader(ClassLoader parent) {
0105: super ();
0106: this .parentClassLoader = parent;
0107: }
0108:
0109: // ----------------------------------------------------- Instance Variables
0110:
0111: /**
0112: * First load of the class.
0113: */
0114: private static boolean first = true;
0115:
0116: /**
0117: * The class loader being managed by this Loader component.
0118: */
0119: private WebappClassLoader classLoader = null;
0120:
0121: /**
0122: * The Container with which this Loader has been associated.
0123: */
0124: private Container container = null;
0125:
0126: /**
0127: * The debugging detail level for this component.
0128: */
0129: private int debug = 0;
0130:
0131: /**
0132: * The DefaultContext with which this Loader is associated.
0133: */
0134: protected DefaultContext defaultContext = null;
0135:
0136: /**
0137: * The "follow standard delegation model" flag that will be used to
0138: * configure our ClassLoader.
0139: */
0140: private boolean delegate = false;
0141:
0142: /**
0143: * The descriptive information about this Loader implementation.
0144: */
0145: private static final String info = "org.apache.catalina.loader.WebappLoader/1.0";
0146:
0147: /**
0148: * The lifecycle event support for this component.
0149: */
0150: protected LifecycleSupport lifecycle = new LifecycleSupport(this );
0151:
0152: /**
0153: * The Java class name of the ClassLoader implementation to be used.
0154: * This class should extend WebappClassLoader, otherwise, a different
0155: * loader implementation must be used.
0156: */
0157: private String loaderClass = "org.apache.catalina.loader.WebappClassLoader";
0158:
0159: /**
0160: * The parent class loader of the class loader we will create.
0161: */
0162: private ClassLoader parentClassLoader = null;
0163:
0164: /**
0165: * The reloadable flag for this Loader.
0166: */
0167: private boolean reloadable = false;
0168:
0169: /**
0170: * The set of repositories associated with this class loader.
0171: */
0172: private String repositories[] = new String[0];
0173:
0174: /**
0175: * The string manager for this package.
0176: */
0177: protected static final StringManager sm = StringManager
0178: .getManager(Constants.Package);
0179:
0180: /**
0181: * Has this component been started?
0182: */
0183: private boolean started = false;
0184:
0185: /**
0186: * The property change support for this component.
0187: */
0188: protected PropertyChangeSupport support = new PropertyChangeSupport(
0189: this );
0190:
0191: /**
0192: * Classpath set in the loader.
0193: */
0194: private String classpath = null;
0195:
0196: /**
0197: * Repositories that are set in the loader, for JMX.
0198: */
0199: private ArrayList loaderRepositories = null;
0200:
0201: // ------------------------------------------------------------- Properties
0202:
0203: /**
0204: * Return the Java class loader to be used by this Container.
0205: */
0206: public ClassLoader getClassLoader() {
0207:
0208: return ((ClassLoader) classLoader);
0209:
0210: }
0211:
0212: /**
0213: * Return the Container with which this Logger has been associated.
0214: */
0215: public Container getContainer() {
0216:
0217: return (container);
0218:
0219: }
0220:
0221: /**
0222: * Set the Container with which this Logger has been associated.
0223: *
0224: * @param container The associated Container
0225: */
0226: public void setContainer(Container container) {
0227:
0228: // Deregister from the old Container (if any)
0229: if ((this .container != null)
0230: && (this .container instanceof Context))
0231: ((Context) this .container)
0232: .removePropertyChangeListener(this );
0233:
0234: // Process this property change
0235: Container oldContainer = this .container;
0236: this .container = container;
0237: support.firePropertyChange("container", oldContainer,
0238: this .container);
0239:
0240: // Register with the new Container (if any)
0241: if ((this .container != null)
0242: && (this .container instanceof Context)) {
0243: setReloadable(((Context) this .container).getReloadable());
0244: ((Context) this .container).addPropertyChangeListener(this );
0245: }
0246:
0247: }
0248:
0249: /**
0250: * Return the DefaultContext with which this Loader is associated.
0251: * XXX What is that ???
0252: */
0253: public DefaultContext getDefaultContext() {
0254:
0255: return (this .defaultContext);
0256:
0257: }
0258:
0259: /**
0260: * Set the DefaultContext with which this Loader is associated.
0261: *
0262: * @param defaultContext The newly associated DefaultContext
0263: */
0264: public void setDefaultContext(DefaultContext defaultContext) {
0265:
0266: DefaultContext oldDefaultContext = this .defaultContext;
0267: this .defaultContext = defaultContext;
0268: support.firePropertyChange("defaultContext", oldDefaultContext,
0269: this .defaultContext);
0270:
0271: }
0272:
0273: /**
0274: * Return the debugging detail level for this component.
0275: */
0276: public int getDebug() {
0277:
0278: return (this .debug);
0279:
0280: }
0281:
0282: /**
0283: * Set the debugging detail level for this component.
0284: *
0285: * @param debug The new debugging detail level
0286: */
0287: public void setDebug(int debug) {
0288:
0289: int oldDebug = this .debug;
0290: this .debug = debug;
0291: support.firePropertyChange("debug", new Integer(oldDebug),
0292: new Integer(this .debug));
0293:
0294: }
0295:
0296: /**
0297: * Return the "follow standard delegation model" flag used to configure
0298: * our ClassLoader.
0299: */
0300: public boolean getDelegate() {
0301:
0302: return (this .delegate);
0303:
0304: }
0305:
0306: /**
0307: * Set the "follow standard delegation model" flag used to configure
0308: * our ClassLoader.
0309: *
0310: * @param delegate The new flag
0311: */
0312: public void setDelegate(boolean delegate) {
0313:
0314: boolean oldDelegate = this .delegate;
0315: this .delegate = delegate;
0316: support.firePropertyChange("delegate",
0317: new Boolean(oldDelegate), new Boolean(this .delegate));
0318:
0319: }
0320:
0321: /**
0322: * Return descriptive information about this Loader implementation and
0323: * the corresponding version number, in the format
0324: * <code><description>/<version></code>.
0325: */
0326: public String getInfo() {
0327:
0328: return (info);
0329:
0330: }
0331:
0332: /**
0333: * Return the ClassLoader class name.
0334: */
0335: public String getLoaderClass() {
0336:
0337: return (this .loaderClass);
0338:
0339: }
0340:
0341: /**
0342: * Set the ClassLoader class name.
0343: *
0344: * @param loaderClass The new ClassLoader class name
0345: */
0346: public void setLoaderClass(String loaderClass) {
0347:
0348: this .loaderClass = loaderClass;
0349:
0350: }
0351:
0352: /**
0353: * Return the reloadable flag for this Loader.
0354: */
0355: public boolean getReloadable() {
0356:
0357: return (this .reloadable);
0358:
0359: }
0360:
0361: /**
0362: * Set the reloadable flag for this Loader.
0363: *
0364: * @param reloadable The new reloadable flag
0365: */
0366: public void setReloadable(boolean reloadable) {
0367:
0368: // Process this property change
0369: boolean oldReloadable = this .reloadable;
0370: this .reloadable = reloadable;
0371: support.firePropertyChange("reloadable", new Boolean(
0372: oldReloadable), new Boolean(this .reloadable));
0373:
0374: }
0375:
0376: // --------------------------------------------------------- Public Methods
0377:
0378: /**
0379: * Add a property change listener to this component.
0380: *
0381: * @param listener The listener to add
0382: */
0383: public void addPropertyChangeListener(
0384: PropertyChangeListener listener) {
0385:
0386: support.addPropertyChangeListener(listener);
0387:
0388: }
0389:
0390: /**
0391: * Add a new repository to the set of repositories for this class loader.
0392: *
0393: * @param repository Repository to be added
0394: */
0395: public void addRepository(String repository) {
0396:
0397: if (log.isDebugEnabled())
0398: log.debug(sm.getString("webappLoader.addRepository",
0399: repository));
0400:
0401: for (int i = 0; i < repositories.length; i++) {
0402: if (repository.equals(repositories[i]))
0403: return;
0404: }
0405: String results[] = new String[repositories.length + 1];
0406: for (int i = 0; i < repositories.length; i++)
0407: results[i] = repositories[i];
0408: results[repositories.length] = repository;
0409: repositories = results;
0410:
0411: if (started && (classLoader != null)) {
0412: classLoader.addRepository(repository);
0413: if (loaderRepositories != null)
0414: loaderRepositories.add(repository);
0415: setClassPath();
0416: }
0417:
0418: }
0419:
0420: /**
0421: * Return the set of repositories defined for this class loader.
0422: * If none are defined, a zero-length array is returned.
0423: * For security reason, returns a clone of the Array (since
0424: * String are immutable).
0425: */
0426: public String[] findRepositories() {
0427:
0428: return ((String[]) repositories.clone());
0429:
0430: }
0431:
0432: public String[] getRepositories() {
0433: return ((String[]) repositories.clone());
0434: }
0435:
0436: /** Extra repositories for this loader
0437: */
0438: public String getRepositoriesString() {
0439: StringBuffer sb = new StringBuffer();
0440: for (int i = 0; i < repositories.length; i++) {
0441: sb.append(repositories[i]).append(":");
0442: }
0443: return sb.toString();
0444: }
0445:
0446: public String[] getLoaderRepositories() {
0447: if (loaderRepositories == null)
0448: return null;
0449: String res[] = new String[loaderRepositories.size()];
0450: loaderRepositories.toArray(res);
0451: return res;
0452: }
0453:
0454: public String getLoaderRepositoriesString() {
0455: String repositories[] = getLoaderRepositories();
0456: StringBuffer sb = new StringBuffer();
0457: for (int i = 0; i < repositories.length; i++) {
0458: sb.append(repositories[i]).append(":");
0459: }
0460: return sb.toString();
0461: }
0462:
0463: /**
0464: * Classpath, as set in org.apache.catalina.jsp_classpath context
0465: * property
0466: *
0467: * @return The classpath
0468: */
0469: public String getClasspath() {
0470: return classpath;
0471: }
0472:
0473: /**
0474: * Has the internal repository associated with this Loader been modified,
0475: * such that the loaded classes should be reloaded?
0476: */
0477: public boolean modified() {
0478:
0479: return (classLoader.modified());
0480:
0481: }
0482:
0483: /**
0484: * Used to periodically signal to the classloader to release JAR resources.
0485: */
0486: public void closeJARs(boolean force) {
0487: if (classLoader != null) {
0488: classLoader.closeJARs(force);
0489: }
0490: }
0491:
0492: /**
0493: * Remove a property change listener from this component.
0494: *
0495: * @param listener The listener to remove
0496: */
0497: public void removePropertyChangeListener(
0498: PropertyChangeListener listener) {
0499:
0500: support.removePropertyChangeListener(listener);
0501:
0502: }
0503:
0504: /**
0505: * Return a String representation of this component.
0506: */
0507: public String toString() {
0508:
0509: StringBuffer sb = new StringBuffer("WebappLoader[");
0510: if (container != null)
0511: sb.append(container.getName());
0512: sb.append("]");
0513: return (sb.toString());
0514:
0515: }
0516:
0517: // ------------------------------------------------------ Lifecycle Methods
0518:
0519: /**
0520: * Add a lifecycle event listener to this component.
0521: *
0522: * @param listener The listener to add
0523: */
0524: public void addLifecycleListener(LifecycleListener listener) {
0525:
0526: lifecycle.addLifecycleListener(listener);
0527:
0528: }
0529:
0530: /**
0531: * Get the lifecycle listeners associated with this lifecycle. If this
0532: * Lifecycle has no listeners registered, a zero-length array is returned.
0533: */
0534: public LifecycleListener[] findLifecycleListeners() {
0535:
0536: return lifecycle.findLifecycleListeners();
0537:
0538: }
0539:
0540: /**
0541: * Remove a lifecycle event listener from this component.
0542: *
0543: * @param listener The listener to remove
0544: */
0545: public void removeLifecycleListener(LifecycleListener listener) {
0546:
0547: lifecycle.removeLifecycleListener(listener);
0548:
0549: }
0550:
0551: private boolean initialized = false;
0552:
0553: public void init() {
0554: initialized = true;
0555:
0556: if (oname == null) {
0557: // not registered yet - standalone or API
0558: if (container instanceof StandardContext) {
0559: // Register ourself. The container must be a webapp
0560: try {
0561: StandardContext ctx = (StandardContext) container;
0562: Engine eng = (Engine) ctx.getParent().getParent();
0563: String path = ctx.getPath();
0564: if (path.equals("")) {
0565: path = "/";
0566: }
0567: oname = new ObjectName(ctx.getEngineName()
0568: + ":type=Loader,path=" + path + ",host="
0569: + ctx.getParent().getName());
0570: Registry.getRegistry(null, null).registerComponent(
0571: this , oname, null);
0572: controller = oname;
0573: } catch (Exception e) {
0574: log.error("Error registering loader", e);
0575: }
0576: }
0577: }
0578:
0579: if (container == null) {
0580: // JMX created the loader
0581: // TODO
0582:
0583: }
0584: }
0585:
0586: public void destroy() {
0587: if (controller == oname) {
0588: // Self-registration, undo it
0589: Registry.getRegistry(null, null).unregisterComponent(oname);
0590: oname = null;
0591: }
0592: initialized = false;
0593:
0594: }
0595:
0596: /**
0597: * Start this component, initializing our associated class loader.
0598: *
0599: * @exception LifecycleException if a lifecycle error occurs
0600: */
0601: public void start() throws LifecycleException {
0602: // Validate and update our current component state
0603: if (!initialized)
0604: init();
0605: if (started)
0606: throw new LifecycleException(sm
0607: .getString("webappLoader.alreadyStarted"));
0608: if (log.isDebugEnabled())
0609: log.debug(sm.getString("webappLoader.starting"));
0610: lifecycle.fireLifecycleEvent(START_EVENT, null);
0611: started = true;
0612:
0613: if (container.getResources() == null) {
0614: log.info("No resources for " + container);
0615: return;
0616: }
0617: // Register a stream handler factory for the JNDI protocol
0618: URLStreamHandlerFactory streamHandlerFactory = new DirContextURLStreamHandlerFactory();
0619: if (first) {
0620: first = false;
0621: try {
0622: URL.setURLStreamHandlerFactory(streamHandlerFactory);
0623: } catch (Exception e) {
0624: // Log and continue anyway, this is not critical
0625: log.error("Error registering jndi stream handler", e);
0626: } catch (Throwable t) {
0627: // This is likely a dual registration
0628: log.info("Dual registration of jndi stream handler: "
0629: + t.getMessage());
0630: }
0631: }
0632:
0633: // Construct a class loader based on our current repositories list
0634: try {
0635:
0636: classLoader = createClassLoader();
0637: classLoader.setResources(container.getResources());
0638: classLoader.setDebug(this .debug);
0639: classLoader.setDelegate(this .delegate);
0640:
0641: for (int i = 0; i < repositories.length; i++) {
0642: classLoader.addRepository(repositories[i]);
0643: }
0644:
0645: // Configure our repositories
0646: setRepositories();
0647: setClassPath();
0648:
0649: setPermissions();
0650:
0651: if (classLoader instanceof Lifecycle)
0652: ((Lifecycle) classLoader).start();
0653:
0654: // Binding the Webapp class loader to the directory context
0655: DirContextURLStreamHandler.bind((ClassLoader) classLoader,
0656: this .container.getResources());
0657:
0658: StandardContext ctx = (StandardContext) container;
0659: Engine eng = (Engine) ctx.getParent().getParent();
0660: String path = ctx.getPath();
0661: if (path.equals("")) {
0662: path = "/";
0663: }
0664: ObjectName cloname = new ObjectName(ctx.getEngineName()
0665: + ":type=WebappClassLoader,path=" + path + ",host="
0666: + ctx.getParent().getName());
0667: Registry.getRegistry(null, null).registerComponent(
0668: classLoader, cloname, null);
0669:
0670: } catch (Throwable t) {
0671: log.error("LifecycleException ", t);
0672: throw new LifecycleException("start: ", t);
0673: }
0674:
0675: }
0676:
0677: /**
0678: * Stop this component, finalizing our associated class loader.
0679: *
0680: * @exception LifecycleException if a lifecycle error occurs
0681: */
0682: public void stop() throws LifecycleException {
0683:
0684: // Validate and update our current component state
0685: if (!started)
0686: throw new LifecycleException(sm
0687: .getString("webappLoader.notStarted"));
0688: if (log.isDebugEnabled())
0689: log.debug(sm.getString("webappLoader.stopping"));
0690: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
0691: started = false;
0692:
0693: // Remove context attributes as appropriate
0694: if (container instanceof Context) {
0695: ServletContext servletContext = ((Context) container)
0696: .getServletContext();
0697: servletContext.removeAttribute(Globals.CLASS_PATH_ATTR);
0698: }
0699:
0700: // Throw away our current class loader
0701: if (classLoader instanceof Lifecycle)
0702: ((Lifecycle) classLoader).stop();
0703: DirContextURLStreamHandler.unbind((ClassLoader) classLoader);
0704:
0705: try {
0706: StandardContext ctx = (StandardContext) container;
0707: Engine eng = (Engine) ctx.getParent().getParent();
0708: String path = ctx.getPath();
0709: if (path.equals("")) {
0710: path = "/";
0711: }
0712: ObjectName cloname = new ObjectName(ctx.getEngineName()
0713: + ":type=WebappClassLoader,path=" + path + ",host="
0714: + ctx.getParent().getName());
0715: Registry.getRegistry(null, null).unregisterComponent(
0716: cloname);
0717: } catch (Throwable t) {
0718: log.error("LifecycleException ", t);
0719: }
0720:
0721: classLoader = null;
0722:
0723: destroy();
0724:
0725: }
0726:
0727: // ----------------------------------------- PropertyChangeListener Methods
0728:
0729: /**
0730: * Process property change events from our associated Context.
0731: *
0732: * @param event The property change event that has occurred
0733: */
0734: public void propertyChange(PropertyChangeEvent event) {
0735:
0736: // Validate the source of this event
0737: if (!(event.getSource() instanceof Context))
0738: return;
0739: Context context = (Context) event.getSource();
0740:
0741: // Process a relevant property change
0742: if (event.getPropertyName().equals("reloadable")) {
0743: try {
0744: setReloadable(((Boolean) event.getNewValue())
0745: .booleanValue());
0746: } catch (NumberFormatException e) {
0747: log.error(sm.getString("webappLoader.reloadable", event
0748: .getNewValue().toString()));
0749: }
0750: }
0751:
0752: }
0753:
0754: // ------------------------------------------------------- Private Methods
0755:
0756: /**
0757: * Create associated classLoader.
0758: */
0759: private WebappClassLoader createClassLoader() throws Exception {
0760:
0761: Class clazz = Class.forName(loaderClass);
0762: WebappClassLoader classLoader = null;
0763:
0764: if (parentClassLoader == null) {
0765: parentClassLoader = Thread.currentThread()
0766: .getContextClassLoader();
0767: }
0768: Class[] argTypes = { ClassLoader.class };
0769: Object[] args = { parentClassLoader };
0770: Constructor constr = clazz.getConstructor(argTypes);
0771: classLoader = (WebappClassLoader) constr.newInstance(args);
0772:
0773: return classLoader;
0774:
0775: }
0776:
0777: /**
0778: * Log a message on the Logger associated with our Container (if any)
0779: *
0780: * @param message Message to be logged
0781: */
0782: private void log(String message) {
0783:
0784: Logger logger = null;
0785: if (container != null)
0786: logger = container.getLogger();
0787: if (logger != null)
0788: logger.log("WebappLoader[" + container.getName() + "]: "
0789: + message);
0790: else {
0791: String containerName = null;
0792: if (container != null)
0793: containerName = container.getName();
0794: System.out.println("WebappLoader[" + containerName + "]: "
0795: + message);
0796: }
0797:
0798: }
0799:
0800: /**
0801: * Log a message on the Logger associated with our Container (if any)
0802: *
0803: * @param message Message to be logged
0804: * @param throwable Associated exception
0805: */
0806: private void log(String message, Throwable throwable) {
0807:
0808: Logger logger = null;
0809: if (container != null)
0810: logger = container.getLogger();
0811: if (logger != null) {
0812: logger.log("WebappLoader[" + container.getName() + "] "
0813: + message, throwable);
0814: } else {
0815: String containerName = null;
0816: if (container != null)
0817: containerName = container.getName();
0818: System.out.println("WebappLoader[" + containerName + "]: "
0819: + message);
0820: System.out.println("" + throwable);
0821: throwable.printStackTrace(System.out);
0822: }
0823:
0824: }
0825:
0826: /**
0827: * Configure associated class loader permissions.
0828: */
0829: private void setPermissions() {
0830:
0831: if (System.getSecurityManager() == null)
0832: return;
0833: if (!(container instanceof Context))
0834: return;
0835:
0836: // Tell the class loader the root of the context
0837: ServletContext servletContext = ((Context) container)
0838: .getServletContext();
0839:
0840: // Assigning permissions for the work directory
0841: File workDir = (File) servletContext
0842: .getAttribute(Globals.WORK_DIR_ATTR);
0843: if (workDir != null) {
0844: try {
0845: String workDirPath = workDir.getCanonicalPath();
0846: classLoader.addPermission(new FilePermission(
0847: workDirPath, "read,write"));
0848: classLoader.addPermission(new FilePermission(
0849: workDirPath + File.separator + "-",
0850: "read,write,delete"));
0851: } catch (IOException e) {
0852: // Ignore
0853: }
0854: }
0855:
0856: try {
0857:
0858: URL rootURL = servletContext.getResource("/");
0859: classLoader.addPermission(rootURL);
0860:
0861: String contextRoot = servletContext.getRealPath("/");
0862: if (contextRoot != null) {
0863: try {
0864: contextRoot = (new File(contextRoot))
0865: .getCanonicalPath();
0866: classLoader.addPermission(contextRoot);
0867: } catch (IOException e) {
0868: // Ignore
0869: }
0870: }
0871:
0872: URL classesURL = servletContext
0873: .getResource("/WEB-INF/classes/");
0874: classLoader.addPermission(classesURL);
0875: URL libURL = servletContext.getResource("/WEB-INF/lib/");
0876: classLoader.addPermission(libURL);
0877:
0878: if (contextRoot != null) {
0879:
0880: if (libURL != null) {
0881: File rootDir = new File(contextRoot);
0882: File libDir = new File(rootDir, "WEB-INF/lib/");
0883: try {
0884: String path = libDir.getCanonicalPath();
0885: classLoader.addPermission(path);
0886: } catch (IOException e) {
0887: }
0888: }
0889:
0890: } else {
0891:
0892: if (workDir != null) {
0893: if (libURL != null) {
0894: File libDir = new File(workDir, "WEB-INF/lib/");
0895: try {
0896: String path = libDir.getCanonicalPath();
0897: classLoader.addPermission(path);
0898: } catch (IOException e) {
0899: }
0900: }
0901: if (classesURL != null) {
0902: File classesDir = new File(workDir,
0903: "WEB-INF/classes/");
0904: try {
0905: String path = classesDir.getCanonicalPath();
0906: classLoader.addPermission(path);
0907: } catch (IOException e) {
0908: }
0909: }
0910: }
0911:
0912: }
0913:
0914: } catch (MalformedURLException e) {
0915: }
0916:
0917: }
0918:
0919: /**
0920: * Configure the repositories for our class loader, based on the
0921: * associated Context.
0922: */
0923: private void setRepositories() {
0924:
0925: if (!(container instanceof Context))
0926: return;
0927: ServletContext servletContext =
0928: ((Context) container).getServletContext();
0929: if (servletContext == null)
0930: return;
0931:
0932: loaderRepositories=new ArrayList();
0933: // Loading the work directory
0934: File workDir =
0935: (File) servletContext.getAttribute(Globals.WORK_DIR_ATTR);
0936: if (workDir == null) {
0937: log.info("No work dir for " + servletContext);
0938: }
0939:
0940: if( log.isDebugEnabled())
0941: log.debug(sm.getString("webappLoader.deploy", workDir.getAbsolutePath()));
0942:
0943: classLoader.setWorkDir(workDir);
0944:
0945: DirContext resources = container.getResources();
0946:
0947: // Setting up the class repository (/WEB-INF/classes), if it exists
0948:
0949: String classesPath = "/WEB-INF/classes";
0950: DirContext classes = null;
0951:
0952: try {
0953: Object object = resources.lookup(classesPath);
0954: if (object instanceof DirContext) {
0955: classes = (DirContext) object;
0956: }
0957: } catch(NamingException e) {
0958: // Silent catch: it's valid that no /WEB-INF/classes collection
0959: // exists
0960: }
0961:
0962: if (classes != null) {
0963:
0964: File classRepository = null;
0965:
0966: String absoluteClassesPath =
0967: servletContext.getRealPath(classesPath);
0968:
0969: if (absoluteClassesPath != null) {
0970:
0971: classRepository = new File(absoluteClassesPath);
0972:
0973: } else {
0974:
0975: classRepository = new File(workDir, classesPath);
0976: classRepository.mkdirs();
0977: copyDir(classes, classRepository);
0978:
0979: }
0980:
0981: if(log.isDebugEnabled())
0982: log.debug(sm.getString("webappLoader.classDeploy", classesPath,
0983: classRepository.getAbsolutePath()));
0984:
0985:
0986: // Adding the repository to the class loader
0987: classLoader.addRepository(classesPath + "/", classRepository);
0988: loaderRepositories.add(classesPath + "/" );
0989:
0990: }
0991:
0992: // Setting up the JAR repository (/WEB-INF/lib), if it exists
0993:
0994: String libPath = "/WEB-INF/lib";
0995:
0996: classLoader.setJarPath(libPath);
0997:
0998: DirContext libDir = null;
0999: // Looking up directory /WEB-INF/lib in the context
1000: try {
1001: Object object = resources.lookup(libPath);
1002: if (object instanceof DirContext)
1003: libDir = (DirContext) object;
1004: } catch(NamingException e) {
1005: // Silent catch: it's valid that no /WEB-INF/lib collection
1006: // exists
1007: }
1008:
1009: if (libDir != null) {
1010:
1011: boolean copyJars = false;
1012: String absoluteLibPath = servletContext.getRealPath(libPath);
1013:
1014: File destDir = null;
1015:
1016: if (absoluteLibPath != null) {
1017: destDir = new File(absoluteLibPath);
1018: } else {
1019: copyJars = true;
1020: destDir = new File(workDir, libPath);
1021: destDir.mkdirs();
1022: }
1023:
1024: // Looking up directory /WEB-INF/lib in the context
1025: try {
1026: NamingEnumeration enum = resources.listBindings(libPath);
1027: while (enum.hasMoreElements()) {
1028:
1029: Binding binding = (Binding) enum.nextElement();
1030: String filename = libPath + "/" + binding.getName();
1031: if (!filename.endsWith(".jar"))
1032: continue;
1033:
1034: // Copy JAR in the work directory, always (the JAR file
1035: // would get locked otherwise, which would make it
1036: // impossible to update it or remove it at runtime)
1037: File destFile = new File(destDir, binding.getName());
1038:
1039: if( log.isDebugEnabled())
1040: log.debug(sm.getString("webappLoader.jarDeploy", filename,
1041: destFile.getAbsolutePath()));
1042:
1043: Resource jarResource = (Resource) binding.getObject();
1044: if (copyJars) {
1045: if (!copy(jarResource.streamContent(),
1046: new FileOutputStream(destFile)))
1047: continue;
1048: }
1049:
1050: try {
1051: JarFile jarFile = new JarFile(destFile);
1052: classLoader.addJar(filename, jarFile, destFile);
1053: } catch (Exception ex) {
1054: // Catch the exception if there is an empty jar file
1055: // Should ignore and continute loading other jar files
1056: // in the dir
1057: }
1058:
1059: loaderRepositories.add( filename );
1060:
1061: }
1062: } catch (NamingException e) {
1063: // Silent catch: it's valid that no /WEB-INF/lib directory
1064: // exists
1065: } catch (IOException e) {
1066: e.printStackTrace();
1067: }
1068:
1069: }
1070:
1071: }
1072:
1073: /**
1074: * Set the appropriate context attribute for our class path. This
1075: * is required only because Jasper depends on it.
1076: */
1077: private void setClassPath() {
1078:
1079: // Validate our current state information
1080: if (!(container instanceof Context))
1081: return;
1082: ServletContext servletContext = ((Context) container)
1083: .getServletContext();
1084: if (servletContext == null)
1085: return;
1086:
1087: if (container instanceof StandardContext) {
1088: String baseClasspath = ((StandardContext) container)
1089: .getCompilerClasspath();
1090: if (baseClasspath != null) {
1091: servletContext.setAttribute(Globals.CLASS_PATH_ATTR,
1092: baseClasspath);
1093: return;
1094: }
1095: }
1096:
1097: StringBuffer classpath = new StringBuffer();
1098:
1099: // Assemble the class path information from our class loader chain
1100: ClassLoader loader = getClassLoader();
1101: int layers = 0;
1102: int n = 0;
1103: while (loader != null) {
1104: if (!(loader instanceof URLClassLoader)) {
1105: String cp = getClasspath(loader);
1106: if (cp == null) {
1107: log.info("Unknown loader " + loader + " "
1108: + loader.getClass());
1109: break;
1110: } else {
1111: if (n > 0)
1112: classpath.append(File.pathSeparator);
1113: classpath.append(cp);
1114: n++;
1115: }
1116: break;
1117: //continue;
1118: }
1119: URL repositories[] = ((URLClassLoader) loader).getURLs();
1120: for (int i = 0; i < repositories.length; i++) {
1121: String repository = repositories[i].toString();
1122: if (repository.startsWith("file://"))
1123: repository = repository.substring(7);
1124: else if (repository.startsWith("file:"))
1125: repository = repository.substring(5);
1126: else if (repository.startsWith("jndi:"))
1127: repository = servletContext.getRealPath(repository
1128: .substring(5));
1129: else
1130: continue;
1131: if (repository == null)
1132: continue;
1133: if (n > 0)
1134: classpath.append(File.pathSeparator);
1135: classpath.append(repository);
1136: n++;
1137: }
1138: loader = loader.getParent();
1139: layers++;
1140: }
1141:
1142: this .classpath = classpath.toString();
1143:
1144: // Store the assembled class path as a servlet context attribute
1145: servletContext.setAttribute(Globals.CLASS_PATH_ATTR, classpath
1146: .toString());
1147:
1148: }
1149:
1150: // try to extract the classpath from a loader that is not URLClassLoader
1151: private String getClasspath(ClassLoader loader) {
1152: try {
1153: Method m = loader.getClass().getMethod("getClasspath",
1154: new Class[] {});
1155: if (log.isTraceEnabled())
1156: log.trace("getClasspath " + m);
1157: if (m == null)
1158: return null;
1159: Object o = m.invoke(loader, new Object[] {});
1160: if (log.isDebugEnabled())
1161: log.debug("gotClasspath " + o);
1162: if (o instanceof String)
1163: return (String) o;
1164: return null;
1165: } catch (Exception ex) {
1166: if (log.isDebugEnabled())
1167: log.debug("getClasspath ", ex);
1168: }
1169: return null;
1170: }
1171:
1172: /**
1173: * Copy directory.
1174: */
1175: private boolean copyDir(DirContext srcDir, File destDir) {
1176:
1177: try {
1178:
1179: NamingEnumeration enum = srcDir.list("");
1180: while (enum.hasMoreElements()) {
1181: NameClassPair ncPair =
1182: (NameClassPair) enum.nextElement();
1183: String name = ncPair.getName();
1184: Object object = srcDir.lookup(name);
1185: File currentFile = new File(destDir, name);
1186: if (object instanceof Resource) {
1187: InputStream is = ((Resource) object).streamContent();
1188: OutputStream os = new FileOutputStream(currentFile);
1189: if (!copy(is, os))
1190: return false;
1191: } else if (object instanceof InputStream) {
1192: OutputStream os = new FileOutputStream(currentFile);
1193: if (!copy((InputStream) object, os))
1194: return false;
1195: } else if (object instanceof DirContext) {
1196: currentFile.mkdir();
1197: copyDir((DirContext) object, currentFile);
1198: }
1199: }
1200:
1201: } catch (NamingException e) {
1202: return false;
1203: } catch (IOException e) {
1204: return false;
1205: }
1206:
1207: return true;
1208:
1209: }
1210:
1211: /**
1212: * Copy a file to the specified temp directory. This is required only
1213: * because Jasper depends on it.
1214: */
1215: private boolean copy(InputStream is, OutputStream os) {
1216:
1217: try {
1218: byte[] buf = new byte[4096];
1219: while (true) {
1220: int len = is.read(buf);
1221: if (len < 0)
1222: break;
1223: os.write(buf, 0, len);
1224: }
1225: is.close();
1226: os.close();
1227: } catch (IOException e) {
1228: return false;
1229: }
1230:
1231: return true;
1232:
1233: }
1234:
1235: private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
1236: .getLog(WebappLoader.class);
1237:
1238: private ObjectName oname;
1239: private MBeanServer mserver;
1240: private String domain;
1241: private ObjectName controller;
1242:
1243: public ObjectName preRegister(MBeanServer server, ObjectName name)
1244: throws Exception {
1245: oname = name;
1246: mserver = server;
1247: domain = name.getDomain();
1248:
1249: return name;
1250: }
1251:
1252: public void postRegister(Boolean registrationDone) {
1253: }
1254:
1255: public void preDeregister() throws Exception {
1256: }
1257:
1258: public void postDeregister() {
1259: }
1260:
1261: public ObjectName getController() {
1262: return controller;
1263: }
1264:
1265: public void setController(ObjectName controller) {
1266: this.controller = controller;
1267: }
1268:
1269: }
|