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