View Javadoc

    1   //========================================================================
    2   //$Id: WebAppContext.java,v 1.5 2005/11/16 22:02:45 gregwilkins Exp $
    3   //Copyright 2004-2006 Mort Bay Consulting Pty. Ltd.
    4   //------------------------------------------------------------------------
    5   //Licensed under the Apache License, Version 2.0 (the "License");
    6   //you may not use this file except in compliance with the License.
    7   //You may obtain a copy of the License at
    8   //http://www.apache.org/licenses/LICENSE-2.0
    9   //Unless required by applicable law or agreed to in writing, software
    10  //distributed under the License is distributed on an "AS IS" BASIS,
    11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //See the License for the specific language governing permissions and
    13  //limitations under the License.
    14  //========================================================================
    15
    16  package org.mortbay.jetty.webapp;
    17
    18  import java.io.File;
    19  import java.io.IOException;
    20  import java.net.MalformedURLException;
    21  import java.security.PermissionCollection;
    22  import java.util.EventListener;
    23  import java.util.HashMap;
    24  import java.util.Map;
    25
    26  import javax.servlet.ServletException;
    27  import javax.servlet.http.HttpServletRequest;
    28  import javax.servlet.http.HttpServletResponse;
    29  import javax.servlet.http.HttpSessionActivationListener;
    30  import javax.servlet.http.HttpSessionAttributeListener;
    31  import javax.servlet.http.HttpSessionBindingListener;
    32  import javax.servlet.http.HttpSessionListener;
    33
    34  import org.mortbay.component.AbstractLifeCycle;
    35  import org.mortbay.jetty.Connector;
    36  import org.mortbay.jetty.HandlerContainer;
    37  import org.mortbay.jetty.Server;
    38  import org.mortbay.jetty.deployer.ContextDeployer;
    39  import org.mortbay.jetty.deployer.WebAppDeployer;
    40  import org.mortbay.jetty.handler.ContextHandler;
    41  import org.mortbay.jetty.handler.ContextHandlerCollection;
    42  import org.mortbay.jetty.handler.ErrorHandler;
    43  import org.mortbay.jetty.handler.HandlerCollection;
    44  import org.mortbay.jetty.security.SecurityHandler;
    45  import org.mortbay.jetty.servlet.Context;
    46  import org.mortbay.jetty.servlet.ErrorPageErrorHandler;
    47  import org.mortbay.jetty.servlet.ServletHandler;
    48  import org.mortbay.jetty.servlet.SessionHandler;
    49  import org.mortbay.log.Log;
    50  import org.mortbay.resource.JarResource;
    51  import org.mortbay.resource.Resource;
    52  import org.mortbay.util.IO;
    53  import org.mortbay.util.LazyList;
    54  import org.mortbay.util.Loader;
    55  import org.mortbay.util.StringUtil;
    56  import org.mortbay.util.URIUtil;
    57  import org.mortbay.util.UrlEncoded;
    58
    59  /* ------------------------------------------------------------ */
    60  /** Web Application Context Handler.
    61   * The WebAppContext handler is an extension of ContextHandler that
    62   * coordinates the construction and configuration of nested handlers:
    63   * {@link org.mortbay.jetty.security.SecurityHandler}, {@link org.mortbay.jetty.servlet.SessionHandler}
    64   * and {@link org.mortbay.jetty.servlet.ServletHandler}.
    65   * The handlers are configured by pluggable configuration classes, with
    66   * the default being  {@link org.mortbay.jetty.webapp.WebXmlConfiguration} and
    67   * {@link org.mortbay.jetty.webapp.JettyWebXmlConfiguration}.
    68   *
    69   * @org.apache.xbean.XBean description="Creates a servlet web application at a given context from a resource base"
    70   *
    71   * @author gregw
    72   *
    73   */
    74  public class WebAppContext extends Context
    75  {
    76      public final static String WEB_DEFAULTS_XML="org/mortbay/jetty/webapp/webdefault.xml";
    77      public final static String ERROR_PAGE="org.mortbay.jetty.error_page";
    78
    79      private static String[] __dftConfigurationClasses =
    80      {
    81          "org.mortbay.jetty.webapp.WebInfConfiguration",
    82          "org.mortbay.jetty.webapp.WebXmlConfiguration",
    83          "org.mortbay.jetty.webapp.JettyWebXmlConfiguration",
    84          "org.mortbay.jetty.webapp.TagLibConfiguration"
    85      } ;
    86      private String[] _configurationClasses=__dftConfigurationClasses;
    87      private Configuration[] _configurations;
    88      private String _defaultsDescriptor=WEB_DEFAULTS_XML;
    89      private String _descriptor=null;
    90      private String _overrideDescriptor=null;
    91      private boolean _distributable=false;
    92      private boolean _extractWAR=true;
    93      private boolean _copyDir=false;
    94      private boolean _logUrlOnStart =false;
    95      private boolean _parentLoaderPriority= Boolean.getBoolean("org.mortbay.jetty.webapp.parentLoaderPriority");
    96      private PermissionCollection _permissions;
    97      private String[] _systemClasses = {"java.","javax.servlet.","javax.xml.","org.mortbay.","org.xml.","org.w3c.", "org.apache.commons.logging.", "org.apache.log4j."};
    98      private String[] _serverClasses = {"-org.mortbay.jetty.plus.jaas.", "org.mortbay.jetty.", "org.slf4j."}; // TODO hide all mortbay classes
    99      private File _tmpDir;
    100     private boolean _isExistingTmpDir;
    101     private String _war;
    102     private String _extraClasspath;
    103     private Throwable _unavailableException;
    104
    105
    106     private transient Map _resourceAliases;
    107     private transient boolean _ownClassLoader=false;
    108     private transient boolean _unavailable;
    109
    110     public static ContextHandler getCurrentWebAppContext()
    111     {
    112         ContextHandler.SContext context=ContextHandler.getCurrentContext();
    113         if (context!=null)
    114         {
    115             ContextHandler handler = context.getContextHandler();
    116             if (handler instanceof WebAppContext)
    117                 return (ContextHandler)handler;
    118         }
    119         return null;
    120     }
    121
    122     /* ------------------------------------------------------------ */
    123     /**  Add Web Applications.
    124      * Add auto webapplications to the server.  The name of the
    125      * webapp directory or war is used as the context name. If the
    126      * webapp matches the rootWebApp it is added as the "/" context.
    127      * @param server Must not be <code>null</code>
    128      * @param webapps Directory file name or URL to look for auto
    129      * webapplication.
    130      * @param defaults The defaults xml filename or URL which is
    131      * loaded before any in the web app. Must respect the web.dtd.
    132      * If null the default defaults file is used. If the empty string, then
    133      * no defaults file is used.
    134      * @param extract If true, extract war files
    135      * @param java2CompliantClassLoader True if java2 compliance is applied to all webapplications
    136      * @exception IOException
    137      * @deprecated use {@link org.mortbay.jetty.deployer.WebAppDeployer} or {@link org.mortbay.jetty.deployer.ContextDeployer}
    138      */
    139     public static void addWebApplications(Server server,
    140                                           String webapps,
    141                                           String defaults,
    142                                           boolean extract,
    143                                           boolean java2CompliantClassLoader)
    144         throws IOException
    145     {
    146         addWebApplications(server, webapps, defaults, __dftConfigurationClasses, extract, java2CompliantClassLoader);
    147     }
    148
    149     /* ------------------------------------------------------------ */
    150     /**  Add Web Applications.
    151      * Add auto webapplications to the server.  The name of the
    152      * webapp directory or war is used as the context name. If the
    153      * webapp matches the rootWebApp it is added as the "/" context.
    154      * @param server Must not be <code>null</code>.
    155      * @param webapps Directory file name or URL to look for auto
    156      * webapplication.
    157      * @param defaults The defaults xml filename or URL which is
    158      * loaded before any in the web app. Must respect the web.dtd.
    159      * If null the default defaults file is used. If the empty string, then
    160      * no defaults file is used.
    161      * @param configurations Array of classnames of {@link Configuration} implementations to apply.
    162      * @param extract If true, extract war files
    163      * @param java2CompliantClassLoader True if java2 compliance is applied to all webapplications
    164      * @exception IOException
    165      * @throws IllegalAccessException
    166      * @throws InstantiationException
    167      * @deprecated use {@link org.mortbay.jetty.deployer.WebAppDeployer} or {@link org.mortbay.jetty.deployer.ContextDeployer}
    168      */
    169     public static void addWebApplications(Server server,
    170                                           String webapps,
    171                                           String defaults,
    172                                           String[] configurations,
    173                                           boolean extract,
    174                                           boolean java2CompliantClassLoader)
    175         throws IOException
    176     {
    177         HandlerCollection contexts = (HandlerCollection)server.getChildHandlerByClass(ContextHandlerCollection.class);
    178         if (contexts==null)
    179             contexts = (HandlerCollection)server.getChildHandlerByClass(HandlerCollection.class);
    180
    181         addWebApplications(contexts,webapps,defaults,configurations,extract,java2CompliantClassLoader);
    182     }
    183
    184     /* ------------------------------------------------------------ */
    185     /**  Add Web Applications.
    186      * Add auto webapplications to the server.  The name of the
    187      * webapp directory or war is used as the context name. If the
    188      * webapp is called "root" it is added as the "/" context.
    189      * @param contexts A HandlerContainer to which the contexts will be added
    190      * @param webapps Directory file name or URL to look for auto
    191      * webapplication.
    192      * @param defaults The defaults xml filename or URL which is
    193      * loaded before any in the web app. Must respect the web.dtd.
    194      * If null the default defaults file is used. If the empty string, then
    195      * no defaults file is used.
    196      * @param configurations Array of classnames of {@link Configuration} implementations to apply.
    197      * @param extract If true, extract war files
    198      * @param java2CompliantClassLoader True if java2 compliance is applied to all webapplications
    199      * @exception IOException
    200      * @throws IllegalAccessException
    201      * @throws InstantiationException
    202      * @deprecated use {@link WebAppDeployer} or {@link ContextDeployer}
    203      */
    204     public static void addWebApplications(HandlerContainer contexts,
    205                                           String webapps,
    206                                           String defaults,
    207                                           boolean extract,
    208                                           boolean java2CompliantClassLoader)
    209     throws IOException
    210     {
    211         addWebApplications(contexts, webapps, defaults, __dftConfigurationClasses, extract, java2CompliantClassLoader);
    212     }
    213
    214     /* ------------------------------------------------------------ */
    215     /**  Add Web Applications.
    216      * Add auto webapplications to the server.  The name of the
    217      * webapp directory or war is used as the context name. If the
    218      * webapp is called "root" it is added as the "/" context.
    219      * @param contexts A HandlerContainer to which the contexts will be added
    220      * @param webapps Directory file name or URL to look for auto
    221      * webapplication.
    222      * @param defaults The defaults xml filename or URL which is
    223      * loaded before any in the web app. Must respect the web.dtd.
    224      * If null the default defaults file is used. If the empty string, then
    225      * no defaults file is used.
    226      * @param configurations Array of classnames of {@link Configuration} implementations to apply.
    227      * @param extract If true, extract war files
    228      * @param java2CompliantClassLoader True if java2 compliance is applied to all webapplications
    229      * @exception IOException
    230      * @throws IllegalAccessException
    231      * @throws InstantiationException
    232      * @deprecated use {@link WebAppDeployer} or {@link ContextDeployer}
    233      */
    234     public static void addWebApplications(HandlerContainer contexts,
    235                                           String webapps,
    236                                           String defaults,
    237                                           String[] configurations,
    238                                           boolean extract,
    239                                           boolean java2CompliantClassLoader)
    240         throws IOException
    241     {
    242         Log.warn("Deprecated configuration used for "+webapps);
    243         WebAppDeployer deployer = new WebAppDeployer();
    244         deployer.setContexts(contexts);
    245         deployer.setWebAppDir(webapps);
    246         deployer.setConfigurationClasses(configurations);
    247         deployer.setExtract(extract);
    248         deployer.setParentLoaderPriority(java2CompliantClassLoader);
    249         try
    250         {
    251             deployer.start();
    252         }
    253         catch(IOException e)
    254         {
    255             throw e;
    256         }
    257         catch(Exception e)
    258         {
    259             throw new RuntimeException(e);
    260         }
    261     }
    262
    263     /* ------------------------------------------------------------ */
    264     public WebAppContext()
    265     {
    266         this(null,null,null,null);
    267     }
    268
    269     /* ------------------------------------------------------------ */
    270     /**
    271      * @param contextPath The context path
    272      * @param webApp The URL or filename of the webapp directory or war file.
    273      */
    274     public WebAppContext(String webApp,String contextPath)
    275     {
    276         super(null,contextPath,SESSIONS|SECURITY);
    277         setContextPath(contextPath);
    278         setWar(webApp);
    279         setErrorHandler(new ErrorPageErrorHandler());
    280     }
    281
    282     /* ------------------------------------------------------------ */
    283     /**
    284      * @param parent The parent HandlerContainer.
    285      * @param contextPath The context path
    286      * @param webApp The URL or filename of the webapp directory or war file.
    287      */
    288     public WebAppContext(HandlerContainer parent, String webApp, String contextPath)
    289     {
    290         super(parent,contextPath,SESSIONS|SECURITY);
    291         setWar(webApp);
    292         setErrorHandler(new ErrorPageErrorHandler());
    293     }
    294
    295     /* ------------------------------------------------------------ */
    296     /**
    297      */
    298     public WebAppContext(SecurityHandler securityHandler,SessionHandler sessionHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
    299     {
    300         super(null,
    301               sessionHandler!=null?sessionHandler:new SessionHandler(),
    302               securityHandler!=null?securityHandler:new SecurityHandler(),
    303               servletHandler!=null?servletHandler:new ServletHandler(),
    304               null);
    305
    306         setErrorHandler(errorHandler!=null?errorHandler:new ErrorPageErrorHandler());
    307     }
    308
    309     /* ------------------------------------------------------------ */
    310     /** Get an exception that caused the webapp to be unavailable
    311      * @return A throwable if the webapp is unavailable or null
    312      */
    313     public Throwable getUnavailableException()
    314     {
    315         return _unavailableException;
    316     }
    317
    318
    319     /* ------------------------------------------------------------ */
    320     /** Set Resource Alias.
    321      * Resource aliases map resource uri's within a context.
    322      * They may optionally be used by a handler when looking for
    323      * a resource.
    324      * @param alias
    325      * @param uri
    326      */
    327     public void setResourceAlias(String alias, String uri)
    328     {
    329         if (_resourceAliases == null)
    330             _resourceAliases= new HashMap(5);
    331         _resourceAliases.put(alias, uri);
    332     }
    333
    334     /* ------------------------------------------------------------ */
    335     public Map getResourceAliases()
    336     {
    337         if (_resourceAliases == null)
    338             return null;
    339         return _resourceAliases;
    340     }
    341
    342     /* ------------------------------------------------------------ */
    343     public void setResourceAliases(Map map)
    344     {
    345         _resourceAliases = map;
    346     }
    347
    348     /* ------------------------------------------------------------ */
    349     public String getResourceAlias(String alias)
    350     {
    351         if (_resourceAliases == null)
    352             return null;
    353         return (String)_resourceAliases.get(alias);
    354     }
    355
    356     /* ------------------------------------------------------------ */
    357     public String removeResourceAlias(String alias)
    358     {
    359         if (_resourceAliases == null)
    360             return null;
    361         return (String)_resourceAliases.remove(alias);
    362     }
    363
    364     /* ------------------------------------------------------------ */
    365     /* (non-Javadoc)
    366      * @see org.mortbay.jetty.handler.ContextHandler#setClassLoader(java.lang.ClassLoader)
    367      */
    368     public void setClassLoader(ClassLoader classLoader)
    369     {
    370         super.setClassLoader(classLoader);
    371         if (classLoader!=null && classLoader instanceof WebAppClassLoader)
    372             ((WebAppClassLoader)classLoader).setName(getDisplayName());
    373     }
    374
    375     /* ------------------------------------------------------------ */
    376     public Resource getResource(String uriInContext) throws MalformedURLException
    377     {
    378         IOException ioe= null;
    379         Resource resource= null;
    380         int loop=0;
    381         while (uriInContext!=null && loop++<100)
    382         {
    383             try
    384             {
    385                 resource= super.getResource(uriInContext);
    386                 if (resource != null && resource.exists())
    387                     return resource;
    388
    389                 uriInContext = getResourceAlias(uriInContext);
    390             }
    391             catch (IOException e)
    392             {
    393                 Log.ignore(e);
    394                 if (ioe==null)
    395                     ioe= e;
    396             }
    397         }
    398
    399         if (ioe != null && ioe instanceof MalformedURLException)
    400             throw (MalformedURLException)ioe;
    401
    402         return resource;
    403     }
    404
    405
    406     /* ------------------------------------------------------------ */
    407     /**
    408      * @see org.mortbay.jetty.handler.ContextHandler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
    409      */
    410     public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
    411     throws IOException, ServletException
    412     {
    413         if (_unavailable)
    414         {
    415             response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
    416         }
    417         else
    418             super.handle(target, request, response, dispatch);
    419     }
    420
    421     /* ------------------------------------------------------------ */
    422     /*
    423      * @see org.mortbay.thread.AbstractLifeCycle#doStart()
    424      */
    425     protected void doStart() throws Exception
    426     {
    427         try
    428         {
    429             // Setup configurations
    430             loadConfigurations();
    431
    432             for (int i=0;i<_configurations.length;i++)
    433                 _configurations[i].setWebAppContext(this);
    434
    435             // Configure classloader
    436             _ownClassLoader=false;
    437             if (getClassLoader()==null)
    438             {
    439                 WebAppClassLoader classLoader = new WebAppClassLoader(this);
    440                 setClassLoader(classLoader);
    441                 _ownClassLoader=true;
    442             }
    443
    444             if (Log.isDebugEnabled())
    445             {
    446                 ClassLoader loader = getClassLoader();
    447                 Log.debug("Thread Context class loader is: " + loader);
    448                 loader=loader.getParent();
    449                 while(loader!=null)
    450                 {
    451                     Log.debug("Parent class loader is: " + loader);
    452                     loader=loader.getParent();
    453                 }
    454             }
    455
    456             for (int i=0;i<_configurations.length;i++)
    457                 _configurations[i].configureClassLoader();
    458
    459             getTempDirectory();
    460             if (_tmpDir!=null && !_isExistingTmpDir && !isTempWorkDirectory())
    461             {
    462                 File sentinel = new File(_tmpDir, ".active");
    463                 if(!sentinel.exists())
    464                     sentinel.mkdir();
    465             }
    466
    467             super.doStart();
    468
    469             if (isLogUrlOnStart())
    470                 dumpUrl();
    471         }
    472         catch (Exception e)
    473         {
    474             //start up of the webapp context failed, make sure it is not started
    475             Log.warn("Failed startup of context "+this, e);
    476             _unavailableException=e;
    477             _unavailable = true;
    478         }
    479     }
    480
    481     /* ------------------------------------------------------------ */
    482     /*
    483      * Dumps the current web app name and URL to the log
    484      */
    485     public void dumpUrl()
    486     {
    487         Connector[] connectors = getServer().getConnectors();
    488         for (int i=0;i<connectors.length;i++)
    489         {
    490             String connectorName = connectors[i].getName();
    491             String displayName = getDisplayName();
    492             if (displayName == null)
    493                 displayName = "WebApp@"+connectors.hashCode();
    494
    495             Log.info(displayName + " at http://" + connectorName + getContextPath());
    496         }
    497     }
    498
    499     /* ------------------------------------------------------------ */
    500     /*
    501      * @see org.mortbay.thread.AbstractLifeCycle#doStop()
    502      */
    503     protected void doStop() throws Exception
    504     {
    505         super.doStop();
    506
    507         try
    508         {
    509             // Configure classloader
    510             for (int i=_configurations.length;i-->0;)
    511                 _configurations[i].deconfigureWebApp();
    512             _configurations=null;
    513
    514             // restore security handler
    515             if (_securityHandler.getHandler()==null)
    516             {
    517                 _sessionHandler.setHandler(_securityHandler);
    518                 _securityHandler.setHandler(_servletHandler);
    519             }
    520
    521             // delete temp directory if we had to create it or if it isn't called work
    522             if (_tmpDir!=null && !_isExistingTmpDir && !isTempWorkDirectory()) //_tmpDir!=null && !"work".equals(_tmpDir.getName()))
    523             {
    524                 IO.delete(_tmpDir);
    525                 _tmpDir=null;
    526             }
    527         }
    528         finally
    529         {
    530             if (_ownClassLoader)
    531                 setClassLoader(null);
    532
    533             _unavailable = false;
    534             _unavailableException=null;
    535         }
    536     }
    537
    538     /* ------------------------------------------------------------ */
    539     /**
    540      * @return Returns the configurations.
    541      */
    542     public String[] getConfigurationClasses()
    543     {
    544         return _configurationClasses;
    545     }
    546
    547     /* ------------------------------------------------------------ */
    548     /**
    549      * @return Returns the configurations.
    550      */
    551     public Configuration[] getConfigurations()
    552     {
    553         return _configurations;
    554     }
    555
    556     /* ------------------------------------------------------------ */
    557     /**
    558      * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
    559      * @return Returns the defaultsDescriptor.
    560      */
    561     public String getDefaultsDescriptor()
    562     {
    563         return _defaultsDescriptor;
    564     }
    565
    566     /* ------------------------------------------------------------ */
    567     /**
    568      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
    569      * @return Returns the Override Descriptor.
    570      */
    571     public String getOverrideDescriptor()
    572     {
    573         return _overrideDescriptor;
    574     }
    575
    576     /* ------------------------------------------------------------ */
    577     /**
    578      * @return Returns the permissions.
    579      */
    580     public PermissionCollection getPermissions()
    581     {
    582         return _permissions;
    583     }
    584
    585
    586     /* ------------------------------------------------------------ */
    587     /**
    588      * @return Returns the serverClasses.
    589      */
    590     public String[] getServerClasses()
    591     {
    592         return _serverClasses;
    593     }
    594
    595
    596     /* ------------------------------------------------------------ */
    597     /**
    598      * @return Returns the systemClasses.
    599      */
    600     public String[] getSystemClasses()
    601     {
    602         return _systemClasses;
    603     }
    604
    605     /* ------------------------------------------------------------ */
    606     /**
    607      * Get a temporary directory in which to unpack the war etc etc.
    608      * The algorithm for determining this is to check these alternatives
    609      * in the order shown:
    610      *
    611      * <p>A. Try to use an explicit directory specifically for this webapp:</p>
    612      * <ol>
    613      * <li>
    614      * Iff an explicit directory is set for this webapp, use it. Do NOT set
    615      * delete on exit.
    616      * </li>
    617      * <li>
    618      * Iff javax.servlet.context.tempdir context attribute is set for
    619      * this webapp && exists && writeable, then use it. Do NOT set delete on exit.
    620      * </li>
    621      * </ol>
    622      *
    623      * <p>B. Create a directory based on global settings. The new directory
    624      * will be called "Jetty_"+host+"_"+port+"__"+context+"_"+virtualhost
    625      * Work out where to create this directory:
    626      * <ol>
    627      * <li>
    628      * Iff $(jetty.home)/work exists create the directory there. Do NOT
    629      * set delete on exit. Do NOT delete contents if dir already exists.
    630      * </li>
    631      * <li>
    632      * Iff WEB-INF/work exists create the directory there. Do NOT set
    633      * delete on exit. Do NOT delete contents if dir already exists.
    634      * </li>
    635      * <li>
    636      * Else create dir in $(java.io.tmpdir). Set delete on exit. Delete
    637      * contents if dir already exists.
    638      * </li>
    639      * </ol>
    640      *
    641      * @return
    642      */
    643     public File getTempDirectory()
    644     {
    645         if (_tmpDir!=null && _tmpDir.isDirectory() && _tmpDir.canWrite())
    646             return _tmpDir;
    647
    648         // Initialize temporary directory
    649         //
    650         // I'm afraid that this is very much black magic.
    651         // but if you can think of better....
    652         Object t = getAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR);
    653
    654         if (t!=null && (t instanceof File))
    655         {
    656             _tmpDir=(File)t;
    657             if (_tmpDir.isDirectory() && _tmpDir.canWrite())
    658                 return _tmpDir;
    659         }
    660
    661         if (t!=null && (t instanceof String))
    662         {
    663             try
    664             {
    665                 _tmpDir=new File((String)t);
    666
    667                 if (_tmpDir.isDirectory() && _tmpDir.canWrite())
    668                 {
    669                     if(Log.isDebugEnabled())Log.debug("Converted to File "+_tmpDir+" for "+this);
    670                     setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR,_tmpDir);
    671                     return _tmpDir;
    672                 }
    673             }
    674             catch(Exception e)
    675             {
    676                 Log.warn(Log.EXCEPTION,e);
    677             }
    678         }
    679
    680         // No tempdir so look for a work directory to use as tempDir base
    681         File work=null;
    682         try
    683         {
    684             File w=new File(System.getProperty("jetty.home"),"work");
    685             if (w.exists() && w.canWrite() && w.isDirectory())
    686                 work=w;
    687             else if (getBaseResource()!=null)
    688             {
    689                 Resource web_inf = getWebInf();
    690                 if (web_inf !=null && web_inf.exists())
    691                 {
    692                     w=new File(web_inf.getFile(),"work");
    693                     if (w.exists() && w.canWrite() && w.isDirectory())
    694                         work=w;
    695                 }
    696             }
    697         }
    698         catch(Exception e)
    699         {
    700             Log.ignore(e);
    701         }
    702
    703         // No tempdir set so make one!
    704         try
    705         {
    706
    707            String temp = getCanonicalNameForWebAppTmpDir();
    708
    709             if (work!=null)
    710                 _tmpDir=new File(work,temp);
    711             else
    712             {
    713                 _tmpDir=new File(System.getProperty("java.io.tmpdir"),temp);
    714
    715                 if (_tmpDir.exists())
    716                 {
    717                     if(Log.isDebugEnabled())Log.debug("Delete existing temp dir "+_tmpDir+" for "+this);
    718                     if (!IO.delete(_tmpDir))
    719                     {
    720                         if(Log.isDebugEnabled())Log.debug("Failed to delete temp dir "+_tmpDir);
    721                     }
    722
    723                     if (_tmpDir.exists())
    724                     {
    725                         String old=_tmpDir.toString();
    726                         _tmpDir=File.createTempFile(temp+"_","");
    727                         if (_tmpDir.exists())
    728                             _tmpDir.delete();
    729                         Log.warn("Can't reuse "+old+", using "+_tmpDir);
    730                     }
    731                 }
    732             }
    733
    734             if (!_tmpDir.exists())
    735                 _tmpDir.mkdir();
    736
    737             //if not in a dir called "work" then we want to delete it on jvm exit
    738             if (!isTempWorkDirectory())
    739                 _tmpDir.deleteOnExit();
    740             if(Log.isDebugEnabled())Log.debug("Created temp dir "+_tmpDir+" for "+this);
    741         }
    742         catch(Exception e)
    743         {
    744             _tmpDir=null;
    745             Log.ignore(e);
    746         }
    747
    748         if (_tmpDir==null)
    749         {
    750             try{
    751                 // that didn't work, so try something simpler (ish)
    752                 _tmpDir=File.createTempFile("JettyContext","");
    753                 if (_tmpDir.exists())
    754                     _tmpDir.delete();
    755                 _tmpDir.mkdir();
    756                 _tmpDir.deleteOnExit();
    757                 if(Log.isDebugEnabled())Log.debug("Created temp dir "+_tmpDir+" for "+this);
    758             }
    759             catch(IOException e)
    760             {
    761                 Log.warn("tmpdir",e); System.exit(1);
    762             }
    763         }
    764
    765         setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR,_tmpDir);
    766         return _tmpDir;
    767     }
    768
    769     /**
    770      * Check if the _tmpDir itself is called "work", or if the _tmpDir
    771      * is in a directory called "work".
    772      * @return
    773      */
    774     public boolean isTempWorkDirectory ()
    775     {
    776         if (_tmpDir == null)
    777             return false;
    778         if (_tmpDir.getName().equalsIgnoreCase("work"))
    779             return true;
    780         File t = _tmpDir.getParentFile();
    781         if (t == null)
    782             return false;
    783         return (t.getName().equalsIgnoreCase("work"));
    784     }
    785
    786     /* ------------------------------------------------------------ */
    787     /**
    788      * @return Returns the war as a file or URL string (Resource)
    789      */
    790     public String getWar()
    791     {
    792         if (_war==null)
    793             _war=getResourceBase();
    794         return _war;
    795     }
    796
    797     /* ------------------------------------------------------------ */
    798     public Resource getWebInf() throws IOException
    799     {
    800         resolveWebApp();
    801
    802         // Iw there a WEB-INF directory?
    803         Resource web_inf= super.getBaseResource().addPath("WEB-INF/");
    804         if (!web_inf.exists() || !web_inf.isDirectory())
    805             return null;
    806
    807         return web_inf;
    808     }
    809
    810     /* ------------------------------------------------------------ */
    811     /**
    812      * @return Returns the distributable.
    813      */
    814     public boolean isDistributable()
    815     {
    816         return _distributable;
    817     }
    818
    819     /* ------------------------------------------------------------ */
    820     /**
    821      * @return Returns the extractWAR.
    822      */
    823     public boolean isExtractWAR()
    824     {
    825         return _extractWAR;
    826     }
    827
    828     /* ------------------------------------------------------------ */
    829     /**
    830      * @return True if the webdir is copied (to allow hot replacement of jars)
    831      */
    832     public boolean isCopyWebDir()
    833     {
    834         return _copyDir;
    835     }
    836
    837     /* ------------------------------------------------------------ */
    838     /**
    839      * @return Returns the java2compliant.
    840      */
    841     public boolean isParentLoaderPriority()
    842     {
    843         return _parentLoaderPriority;
    844     }
    845
    846     /* ------------------------------------------------------------ */
    847     protected void loadConfigurations()
    848     	throws Exception
    849     {
    850         if (_configurations!=null)
    851             return;
    852         if (_configurationClasses==null)
    853             _configurationClasses=__dftConfigurationClasses;
    854
    855         _configurations = new Configuration[_configurationClasses.length];
    856         for (int i=0;i<_configurations.length;i++)
    857         {
    858             _configurations[i]=(Configuration)Loader.loadClass(this.getClass(), _configurationClasses[i]).newInstance();
    859         }
    860     }
    861
    862     /* ------------------------------------------------------------ */
    863     protected boolean isProtectedTarget(String target)
    864     {
    865         while (target.startsWith("//"))
    866             target=URIUtil.compactPath(target);
    867
    868         return StringUtil.startsWithIgnoreCase(target, "/web-inf") || StringUtil.startsWithIgnoreCase(target, "/meta-inf");
    869     }
    870
    871
    872     /* ------------------------------------------------------------ */
    873     public String toString()
    874     {
    875         return this.getClass().getName()+"@"+Integer.toHexString(hashCode())+"{"+getContextPath()+","+(_war==null?getResourceBase():_war)+"}";
    876     }
    877
    878     /* ------------------------------------------------------------ */
    879     /** Resolve Web App directory
    880      * If the BaseResource has not been set, use the war resource to
    881      * derive a webapp resource (expanding WAR if required).
    882      */
    883     protected void resolveWebApp() throws IOException
    884     {
    885         Resource web_app = super.getBaseResource();
    886         if (web_app == null)
    887         {
    888             if (_war==null || _war.length()==0)
    889                 _war=getResourceBase();
    890
    891             // Set dir or WAR
    892             web_app= Resource.newResource(_war);
    893
    894             // Accept aliases for WAR files
    895             if (web_app.getAlias() != null)
    896             {
    897                 Log.debug(web_app + " anti-aliased to " + web_app.getAlias());
    898                 web_app= Resource.newResource(web_app.getAlias());
    899             }
    900
    901             if (Log.isDebugEnabled())
    902                 Log.debug("Try webapp=" + web_app + ", exists=" + web_app.exists() + ", directory=" + web_app.isDirectory());
    903
    904             // Is the WAR usable directly?
    905             if (web_app.exists() && !web_app.isDirectory() && !web_app.toString().startsWith("jar:"))
    906             {
    907                 // No - then lets see if it can be turned into a jar URL.
    908                 Resource jarWebApp= Resource.newResource("jar:" + web_app + "!/");
    909                 if (jarWebApp.exists() && jarWebApp.isDirectory())
    910                 {
    911                     web_app= jarWebApp;
    912                 }
    913             }
    914
    915             // If we should extract or the URL is still not usable
    916             if (web_app.exists()  && (
    917                (_copyDir && web_app.getFile()!= null && web_app.getFile().isDirectory())
    918                ||
    919                (_extractWAR && web_app.getFile()!= null && !web_app.getFile().isDirectory())
    920                ||
    921                (_extractWAR && web_app.getFile() == null)
    922                ||
    923                !web_app.isDirectory()
    924                ))
    925             {
    926                 // Then extract it if necessary.
    927                 File extractedWebAppDir= new File(getTempDirectory(), "webapp");
    928
    929                 if (web_app.getFile()!=null && web_app.getFile().isDirectory())
    930                 {
    931                     // Copy directory
    932                     Log.info("Copy " + web_app.getFile() + " to " + extractedWebAppDir);
    933                     IO.copyDir(web_app.getFile(),extractedWebAppDir);
    934                 }
    935                 else
    936                 {
    937                     if (!extractedWebAppDir.exists())
    938                     {
    939                         //it hasn't been extracted before so extract it
    940                         extractedWebAppDir.mkdir();
    941                         Log.info("Extract " + _war + " to " + extractedWebAppDir);
    942                         JarResource.extract(web_app, extractedWebAppDir, false);
    943                     }
    944                     else
    945                     {
    946                         //only extract if the war file is newer
    947                         if (web_app.lastModified() > extractedWebAppDir.lastModified())
    948                         {
    949                             extractedWebAppDir.delete();
    950                             extractedWebAppDir.mkdir();
    951                             Log.info("Extract " + _war + " to " + extractedWebAppDir);
    952                             JarResource.extract(web_app, extractedWebAppDir, false);
    953                         }
    954                     }
    955                 }
    956
    957                 web_app= Resource.newResource(extractedWebAppDir.getCanonicalPath());
    958
    959             }
    960
    961             // Now do we have something usable?
    962             if (!web_app.exists() || !web_app.isDirectory())
    963             {
    964                 Log.warn("Web application not found " + _war);
    965                 throw new java.io.FileNotFoundException(_war);
    966             }
    967
    968             if (Log.isDebugEnabled())
    969                 Log.debug("webapp=" + web_app);
    970
    971             // ResourcePath
    972             super.setBaseResource(web_app);
    973         }
    974     }
    975
    976
    977     /* ------------------------------------------------------------ */
    978     /**
    979      * @param configurations The configuration class names.  If setConfigurations is not called
    980      * these classes are used to create a configurations array.
    981      */
    982     public void setConfigurationClasses(String[] configurations)
    983     {
    984         if (isRunning())
    985             throw new IllegalStateException("Running");
    986         _configurationClasses = configurations==null?null:(String[])configurations.clone();
    987     }
    988
    989     /* ------------------------------------------------------------ */
    990     /**
    991      * @param configurations The configurations to set.
    992      */
    993     public void setConfigurations(Configuration[] configurations)
    994     {
    995         if (isRunning())
    996             throw new IllegalStateException("Running");
    997         _configurations = configurations==null?null:(Configuration[])configurations.clone();
    998     }
    999
    1000     /* ------------------------------------------------------------ */
    1001     /**
    1002      * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
    1003      * @param defaultsDescriptor The defaultsDescriptor to set.
    1004      */
    1005     public void setDefaultsDescriptor(String defaultsDescriptor)
    1006     {
    1007         if (isRunning())
    1008             throw new IllegalStateException("Running");
    1009         _defaultsDescriptor = defaultsDescriptor;
    1010     }
    1011
    1012     /* ------------------------------------------------------------ */
    1013     /**
    1014      * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
    1015      * @param defaultsDescriptor The overrideDescritpor to set.
    1016      */
    1017     public void setOverrideDescriptor(String overrideDescriptor)
    1018     {
    1019         if (isRunning())
    1020             throw new IllegalStateException("Running");
    1021         _overrideDescriptor = overrideDescriptor;
    1022     }
    1023
    1024     /* ------------------------------------------------------------ */
    1025     /**
    1026      * @return the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
    1027      */
    1028     public String getDescriptor()
    1029     {
    1030         return _descriptor;
    1031     }
    1032
    1033     /* ------------------------------------------------------------ */
    1034     /**
    1035      * @param descriptor the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
    1036      */
    1037     public void setDescriptor(String descriptor)
    1038     {
    1039         if (isRunning())
    1040             throw new IllegalStateException("Running");
    1041         _descriptor=descriptor;
    1042     }
    1043
    1044     /* ------------------------------------------------------------ */
    1045     /**
    1046      * @param distributable The distributable to set.
    1047      */
    1048     public void setDistributable(boolean distributable)
    1049     {
    1050         this._distributable = distributable;
    1051     }
    1052
    1053     /* ------------------------------------------------------------ */
    1054     public void setEventListeners(EventListener[] eventListeners)
    1055     {
    1056         if (_sessionHandler!=null)
    1057             _sessionHandler.clearEventListeners();
    1058
    1059         super.setEventListeners(eventListeners);
    1060
    1061         for (int i=0; eventListeners!=null && i<eventListeners.length;i ++)
    1062         {
    1063             EventListener listener = eventListeners[i];
    1064
    1065             if ((listener instanceof HttpSessionActivationListener)
    1066                             || (listener instanceof HttpSessionAttributeListener)
    1067                             || (listener instanceof HttpSessionBindingListener)
    1068                             || (listener instanceof HttpSessionListener))
    1069             {
    1070                 if (_sessionHandler!=null)
    1071                     _sessionHandler.addEventListener(listener);
    1072             }
    1073
    1074         }
    1075     }
    1076
    1077     /* ------------------------------------------------------------ */
    1078     /** Add EventListener
    1079      * Conveniance method that calls {@link #setEventListeners(EventListener[])}
    1080      * @param listener
    1081      */
    1082     public void addEventListener(EventListener listener)
    1083     {
    1084         setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(), listener, EventListener.class));
    1085     }
    1086
    1087
    1088     /* ------------------------------------------------------------ */
    1089     /**
    1090      * @param extractWAR True if war files are extracted
    1091      */
    1092     public void setExtractWAR(boolean extractWAR)
    1093     {
    1094         _extractWAR = extractWAR;
    1095     }
    1096
    1097     /* ------------------------------------------------------------ */
    1098     /**
    1099      *
    1100      * @param copy True if the webdir is copied (to allow hot replacement of jars)
    1101      */
    1102     public void setCopyWebDir(boolean copy)
    1103     {
    1104         _copyDir = copy;
    1105     }
    1106
    1107     /* ------------------------------------------------------------ */
    1108     /**
    1109      * @param java2compliant The java2compliant to set.
    1110      */
    1111     public void setParentLoaderPriority(boolean java2compliant)
    1112     {
    1113         _parentLoaderPriority = java2compliant;
    1114     }
    1115
    1116     /* ------------------------------------------------------------ */
    1117     /**
    1118      * @param permissions The permissions to set.
    1119      */
    1120     public void setPermissions(PermissionCollection permissions)
    1121     {
    1122         _permissions = permissions;
    1123     }
    1124
    1125     /* ------------------------------------------------------------ */
    1126     /**
    1127      * @param serverClasses The serverClasses to set.
    1128      */
    1129     public void setServerClasses(String[] serverClasses)
    1130     {
    1131         _serverClasses = serverClasses==null?null:(String[])serverClasses.clone();
    1132     }
    1133
    1134     /* ------------------------------------------------------------ */
    1135     /**
    1136      * @param systemClasses The systemClasses to set.
    1137      */
    1138     public void setSystemClasses(String[] systemClasses)
    1139     {
    1140         _systemClasses = systemClasses==null?null:(String[])systemClasses.clone();
    1141     }
    1142
    1143
    1144     /* ------------------------------------------------------------ */
    1145     /** Set temporary directory for context.
    1146      * The javax.servlet.context.tempdir attribute is also set.
    1147      * @param dir Writable temporary directory.
    1148      */
    1149     public void setTempDirectory(File dir)
    1150     {
    1151         if (isStarted())
    1152             throw new IllegalStateException("Started");
    1153
    1154         if (dir!=null)
    1155         {
    1156             try{dir=new File(dir.getCanonicalPath());}
    1157             catch (IOException e){Log.warn(Log.EXCEPTION,e);}
    1158         }
    1159
    1160         if (dir!=null && !dir.exists())
    1161         {
    1162             dir.mkdir();
    1163             dir.deleteOnExit();
    1164         }
    1165         else if (dir != null)
    1166             _isExistingTmpDir = true;
    1167
    1168         if (dir!=null && ( !dir.exists() || !dir.isDirectory() || !dir.canWrite()))
    1169             throw new IllegalArgumentException("Bad temp directory: "+dir);
    1170
    1171         _tmpDir=dir;
    1172         setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR,_tmpDir);
    1173     }
    1174
    1175     /* ------------------------------------------------------------ */
    1176     /**
    1177      * @param war The war to set as a file name or URL
    1178      */
    1179     public void setWar(String war)
    1180     {
    1181         _war = war;
    1182     }
    1183
    1184
    1185     /* ------------------------------------------------------------ */
    1186     /**
    1187      * @return Comma or semicolon separated path of filenames or URLs
    1188      * pointing to directories or jar files. Directories should end
    1189      * with '/'.
    1190      */
    1191     public String getExtraClasspath()
    1192     {
    1193         return _extraClasspath;
    1194     }
    1195
    1196     /* ------------------------------------------------------------ */
    1197     /**
    1198      * @param extraClasspath Comma or semicolon separated path of filenames or URLs
    1199      * pointing to directories or jar files. Directories should end
    1200      * with '/'.
    1201      */
    1202     public void setExtraClasspath(String extraClasspath)
    1203     {
    1204         _extraClasspath=extraClasspath;
    1205     }
    1206
    1207     /* ------------------------------------------------------------ */
    1208     public boolean isLogUrlOnStart()
    1209     {
    1210         return _logUrlOnStart;
    1211     }
    1212
    1213     /* ------------------------------------------------------------ */
    1214     /**
    1215      * Sets whether or not the web app name and URL is logged on startup
    1216      *
    1217      * @param logOnStart whether or not the log message is created
    1218      */
    1219     public void setLogUrlOnStart(boolean logOnStart)
    1220     {
    1221         this._logUrlOnStart = logOnStart;
    1222     }
    1223
    1224     /* ------------------------------------------------------------ */
    1225     protected void startContext()
    1226         throws Exception
    1227     {
    1228         // Configure defaults
    1229         for (int i=0;i<_configurations.length;i++)
    1230             _configurations[i].configureDefaults();
    1231
    1232         // Is there a WEB-INF work directory
    1233         Resource web_inf=getWebInf();
    1234         if (web_inf!=null)
    1235         {
    1236             Resource work= web_inf.addPath("work");
    1237             if (work.exists()
    1238                             && work.isDirectory()
    1239                             && work.getFile() != null
    1240                             && work.getFile().canWrite()
    1241                             && getAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR) == null)
    1242                 setAttribute(ServletHandler.__J_S_CONTEXT_TEMPDIR, work.getFile());
    1243         }
    1244
    1245         // Configure webapp
    1246         for (int i=0;i<_configurations.length;i++)
    1247             _configurations[i].configureWebApp();
    1248
    1249
    1250         super.startContext();
    1251     }
    1252
    1253     /**
    1254      * Create a canonical name for a webapp tmp directory.
    1255      * The form of the name is:
    1256      *  "Jetty_"+host+"_"+port+"__"+resourceBase+"_"+context+"_"+virtualhost+base36 hashcode of whole string
    1257      *
    1258      *  host and port uniquely identify the server
    1259      *  context and virtual host uniquely identify the webapp
    1260      * @return
    1261      */
    1262     private String getCanonicalNameForWebAppTmpDir ()
    1263     {
    1264         StringBuffer canonicalName = new StringBuffer();
    1265         canonicalName.append("Jetty");
    1266
    1267         //get the host and the port from the first connector
    1268         Connector[] connectors = getServer().getConnectors();
    1269
    1270
    1271         //Get the host
    1272         canonicalName.append("_");
    1273         String host = (connectors==null||connectors[0]==null?"":connectors[0].getHost());
    1274         if (host == null)
    1275             host = "0.0.0.0";
    1276         canonicalName.append(host.replace('.', '_'));
    1277
    1278         //Get the port
    1279         canonicalName.append("_");
    1280         //try getting the real port being listened on
    1281         int port = (connectors==null||connectors[0]==null?0:connectors[0].getLocalPort());
    1282         //if not available (eg no connectors or connector not started),
    1283         //try getting one that was configured.
    1284         if (port < 0)
    1285             port = connectors[0].getPort();
    1286         canonicalName.append(port);
    1287
    1288
    1289         //Resource  base
    1290         canonicalName.append("_");
    1291         try
    1292         {
    1293             Resource resource = super.getBaseResource();
    1294             if (resource == null)
    1295             {
    1296                 if (_war==null || _war.length()==0)
    1297                     resource=Resource.newResource(getResourceBase());
    1298
    1299                 // Set dir or WAR
    1300                 resource= Resource.newResource(_war);
    1301             }
    1302
    1303             String tmp = URIUtil.decodePath(resource.getURL().getPath());
    1304             if (tmp.endsWith("/"))
    1305                 tmp = tmp.substring(0, tmp.length()-1);
    1306             if (tmp.endsWith("!"))
    1307                 tmp = tmp.substring(0, tmp.length() -1);
    1308             //get just the last part which is the filename
    1309             int i = tmp.lastIndexOf("/");
    1310
    1311             canonicalName.append(tmp.substring(i+1, tmp.length()));
    1312         }
    1313         catch (Exception e)
    1314         {
    1315             Log.warn("Can't generate resourceBase as part of webapp tmp dir name", e);
    1316         }
    1317
    1318         //Context name
    1319         canonicalName.append("_");
    1320         String contextPath = getContextPath();
    1321         contextPath=contextPath.replace('/','_');
    1322         contextPath=contextPath.replace('\\','_');
    1323         canonicalName.append(contextPath);
    1324
    1325         //Virtual host (if there is one)
    1326         canonicalName.append("_");
    1327         String[] vhosts = getVirtualHosts();
    1328         canonicalName.append((vhosts==null||vhosts[0]==null?"":vhosts[0]));
    1329
    1330         //base36 hash of the whole string for uniqueness
    1331         String hash = Integer.toString(canonicalName.toString().hashCode(),36);
    1332         canonicalName.append("_");
    1333         canonicalName.append(hash);
    1334
    1335         // sanitize
    1336         for (int i=0;i<canonicalName.length();i++)
    1337         {
    1338         	char c=canonicalName.charAt(i);
    1339         	if (!Character.isJavaIdentifierPart(c))
    1340         		canonicalName.setCharAt(i,'.');
    1341         }
    1342
    1343         return canonicalName.toString();
    1344     }
    1345 }