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 }