View Javadoc

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