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