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