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