1 /*
2 * $Header: /u/cvs/Projects/EnhydraOrg/enhydra3x/Enhydra/modules/Tomcat/src/share/org/apache/tomcat/core/ServletWrapper.java,v 1.2.2.3 2000/04/07 08:37:20 peterj Exp $
3 * $Revision: 1.2.2.3 $
4 * $Date: 2000/04/07 08:37:20 $
5 *
6 * ====================================================================
7 *
8 * The Apache Software License, Version 1.1
9 *
10 * Copyright (c) 1999 The Apache Software Foundation. All rights
11 * reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 *
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in
22 * the documentation and/or other materials provided with the
23 * distribution.
24 *
25 * 3. The end-user documentation included with the redistribution, if
26 * any, must include the following acknowlegement:
27 * "This product includes software developed by the
28 * Apache Software Foundation (http://www.apache.org/)."
29 * Alternately, this acknowlegement may appear in the software itself,
30 * if and wherever such third-party acknowlegements normally appear.
31 *
32 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
33 * Foundation" must not be used to endorse or promote products derived
34 * from this software without prior written permission. For written
35 * permission, please contact apache@apache.org.
36 *
37 * 5. Products derived from this software may not be called "Apache"
38 * nor may "Apache" appear in their names without prior written
39 * permission of the Apache Group.
40 *
41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52 * SUCH DAMAGE.
53 * ====================================================================
54 *
55 * This software consists of voluntary contributions made by many
56 * individuals on behalf of the Apache Software Foundation. For more
57 * information on the Apache Software Foundation, please see
58 * <http://www.apache.org/>.
59 *
60 * [Additional notices, if required by prior licensing conditions]
61 *
62 */
63
64
65 package org.apache.tomcat.core;
66
67 import org.apache.tomcat.util;
68 import java.io;
69 import java.net;
70 import java.util;
71 import javax.servlet;
72 import javax.servlet.http;
73
74 /**
75 *
76 * @author James Duncan Davidson [duncan@eng.sun.com]
77 * @author Jason Hunter [jch@eng.sun.com]
78 * @author James Todd [gonzo@eng.sun.com]
79 * @author Harish Prabandham
80 */
81
82 //
83 // WARNING: Some of the APIs in this class are used by J2EE.
84 // Please talk to harishp@eng.sun.com before making any changes.
85 //
86 class ServletWrapper {
87
88 private StringManager sm =
89 StringManager.getManager(Constants.Package);
90 private Container container;
91 private String description = null;
92 private String servletClassName;
93 private Class servletClass;
94 private File servletClassFile;
95 private String path = null;
96 private Servlet servlet;
97 private long lastAccessed;
98 private ServletConfigImpl config;
99 private boolean isReloadable = false;
100 private long classFileLastMod = 0;
101 private int serviceCount = 0;
102
103 ServletWrapper(Container container) {
104 this.container = container;
105
106 config = new ServletConfigImpl(container.getContext());
107 }
108
109 void setReloadable(boolean reloadable) {
110 isReloadable = reloadable;
111 }
112
113 String getServletName() {
114 return config.getServletName();
115 }
116
117 void setServletName(String servletName) {
118 config.setServletName(servletName);
119 }
120
121 String getServletDescription() {
122 return this.description;
123 }
124
125 void setServletDescription(String description) {
126 this.description = description;
127 }
128
129 String getPath() {
130 return this.path;
131 }
132
133 void setPath(String path) {
134 this.path = path;
135 }
136
137 void setServletClassFile(File servletClassFile) {
138 this.servletClassFile = servletClassFile;
139 classFileLastMod = servletClassFile.lastModified();
140
141 config.setServletClassName(this.servletClassFile.getName());
142 }
143
144 String getServletClass() {
145 return this.servletClassName;
146 }
147
148 void setServletClass(String servletClassName) {
149 this.servletClassName = servletClassName;
150
151 config.setServletClassName(servletClassName);
152 }
153
154 void setServletClass(Class servletClass) {
155 this.servletClass = servletClass;
156
157 config.setServletClassName(this.servletClass.getName());
158 }
159
160 void setInitArgs(Hashtable initArgs) {
161 config.setInitArgs(initArgs);
162 }
163
164 /**
165 *
166 */
167
168 Servlet getServlet() {
169 return servlet;
170 }
171
172
173 void destroy() {
174 // Fancy sync logic is to make sure that no threads are in the
175 // handlerequest when this is called and, furthermore, that
176 // no threads go through handle request after this method starts!
177
178 if (servlet != null) {
179 synchronized (this) {
180 // Wait until there are no outstanding service calls,
181 // or until 30 seconds have passed (to avoid a hang)
182
183 while (serviceCount > 0) {
184 try {
185 wait(30000);
186
187 break;
188 } catch (InterruptedException e) { }
189 }
190
191 try {
192 final Servlet sinstance = servlet;
193 Context context = getContext();
194
195 handleInvocation(
196 context.getDestroyInterceptors().elements(),
197 new LifecycleInvocationHandler(context, servlet) {
198 void method() throws ServletException {
199 sinstance.destroy();
200 }
201 });
202 } catch(IOException ioe) {
203 // Should never come here...
204 } catch(ServletException se) {
205 // Should never come here...
206 }
207 }
208 }
209 }
210
211 void loadServlet()
212 throws ClassNotFoundException, InstantiationException,
213 IllegalAccessException, ServletException {
214 // Check if this is a JSP, they get special treatment
215
216 if (path != null &&
217 servletClass == null &&
218 servletClassName == null) {
219 // XXX XXX XXX
220 // core shouldn't depend on a particular connector!
221 // need to find out what this code does!
222 RequestAdapterImpl reqA=new RequestAdapterImpl();
223 ResponseAdapterImpl resA=new ResponseAdapterImpl();
224
225 Request request = new Request();
226 Response response = new Response();
227 request.recycle();
228 response.recycle();
229
230 request.setRequestAdapter( reqA );
231 response.setResponseAdapter( resA );
232
233 request.setResponse(response);
234 response.setRequest(request);
235
236 String requestURI = path + "?" +
237 Constants.JSP.Directive.Compile.Name + "=" +
238 Constants.JSP.Directive.Compile.Value;
239
240 reqA.setRequestURI(getContext().getPath() + path);
241 reqA.setQueryString( Constants.JSP.Directive.Compile.Name + "=" +
242 Constants.JSP.Directive.Compile.Value );
243
244 request.setContext(getContext());
245 request.getSession(true);
246
247 RequestDispatcher rd =
248 config.getServletContext().getRequestDispatcher(requestURI);
249
250 try {
251 rd.forward(request.getFacade(), response.getFacade());
252 } catch (ServletException se) {
253 } catch (IOException ioe) {
254 }
255 } else {
256 if (servletClass == null) {
257 if (servletClassName == null) {
258 String msg = sm.getString("wrapper.load.noclassname");
259
260 throw new IllegalStateException(msg);
261 }
262 servletClass = container.getLoader().loadServlet(this,
263 servletClassName);
264 }
265
266 // make sure we have a classname or class def
267 //if (servletClassName == null || servletClass == null) {
268 // String msg = sm.getString("wrapper.load.noclassname");
269 // throw new IllegalStateException(msg);
270 // }
271 //Class c = container.getLoader().loadServlet(this,
272 //servletClassName);
273
274 servlet = (Servlet)servletClass.newInstance();
275
276 config.setServletClassName(servlet.getClass().getName());
277
278 try {
279 final Servlet sinstance = servlet;
280 final ServletConfigImpl servletConfig = config;
281 Context context = getContext();
282
283 handleInvocation(context.getInitInterceptors().elements(),
284 new LifecycleInvocationHandler(context, servlet) {
285 void method() throws ServletException {
286 sinstance.init(servletConfig);
287 }
288 });
289 } catch(IOException ioe) {
290 // Should never come here...
291 }
292 }
293 }
294
295 private Context getContext() {
296 return this.container.getContext();
297 }
298
299 void handleRequest(final HttpServletRequestFacade request,
300 final HttpServletResponseFacade response)
301 throws IOException {
302 // if (isReloadable) {
303 // long lm = servletClassFile.lastModified();
304 // if (lm > classFileLastMod) {
305 // //container.recycle();
306 // }
307 // }
308 // make sure that only one thread goes through
309 // this block at a time!
310
311 synchronized (this) {
312 // XXX
313 // rather klunky - this method needs a once over
314
315 if (path != null &&
316 servletClass == null &&
317 servletClassName == null) {
318 String requestURI = path + request.getPathInfo();
319 RequestDispatcher rd =
320 request.getRequestDispatcher(requestURI);
321
322 try {
323 // Watch out, ugly code ahead...
324 // We need to do a forward or include here, but we can't
325 // easily determine which. So we try a forward, and if
326 // there's an IllegalStateException thrown, then we know
327 // we should have tried an include, so we do the include.
328 // It's so ugly I have to giggle.
329 // All this to support dispatching to named JSPs!
330 try {
331 rd.forward(request, response);
332 } catch (IllegalStateException e) {
333 rd.include(request, response);
334 }
335 } catch (ServletException se) {
336 //FIXME: markd@lutris.com: Need to log here.
337 response.sendError(404);
338 } catch (IOException ioe) {
339 //FIXME: markd@lutris.com: Need to log here.
340 response.sendError(404);
341 }
342
343 return;
344 } else {
345 if (servlet == null) {
346 try {
347 loadServlet();
348 } catch (ClassNotFoundException e) {
349 //FIXME: markd@lutris.com: Need to log here.
350 container.getContext().getLogChannel().write(com.lutris.logging.Logger.ERROR,
351 "Class not found " + servletClassName);
352 response.sendError(404);
353
354 return;
355 } catch (Exception e) {
356 // Make sure the servlet will never
357 // service a request
358
359 servlet = null;
360
361 // XXX
362 // check to see what kind of exception it was --
363 // maybe it should be reported to the user
364 // differently or at least logged differently
365
366 // XXX
367 // we really need to pick up an error file on
368 // a per context basis or, failing that from the
369 // classpath
370
371 //FIXME: markd@lutris.com: Need to log here.
372 sendInternalServletError(e, response);
373
374 return;
375 }
376 }
377 }
378 }
379
380 try {
381 synchronized(this) {
382 serviceCount++;
383 }
384
385 Context context = getContext();
386 Enumeration serviceInterceptors =
387 context.getServiceInterceptors().elements();
388 ServiceInvocationHandler serviceHandler =
389 new ServiceInvocationHandler(context, servlet,
390 request, response);
391
392 handleInvocation(serviceInterceptors, serviceHandler);
393 } catch (ServletException e) {
394 // XXX
395 // check to see if it's unavailable and set internal status
396 // appropriately
397
398 // XXX
399 // if it's an unvailable exception, we probably want
400 // to paint a different screen
401
402 handleException(request, response, e);
403
404 return;
405 } catch (SocketException e) {
406 // XXX
407 // Catch and eat all SocketExceptions
408 // *Should* only eat client disconnected socket exceptions
409
410 return;
411 } catch (Throwable e) {
412 // if we are in an include, then we should rethrow the
413 // exception
414
415 // XXX
416 // we need a better way of dealing with figuring out
417 // if we are in an include -- this particular gem
418 // will pass IllegalStateException when we are in
419 // an include 'cause users will like to know when
420 // that happens in their included servlet
421
422 //if (e instanceof IllegalStateException) {
423 // String str = (String)request.getAttribute
424 // (Constants.Attribute.RequestURI);
425 // if (str != null) {
426 // throw (IllegalStateException)e;
427 // }
428 //}
429
430 // XXX
431 // decide which exceptions we should not eat at this point
432 handleException(request, response, e);
433
434 return;
435 } finally {
436 synchronized(this) {
437 serviceCount--;
438 notifyAll();
439 }
440 }
441 }
442
443 //FIXME: markd@lutris.com: Need to log here.
444 public void handleException(HttpServletRequestFacade request,
445 HttpServletResponseFacade response,
446 Throwable t) {
447 Context context = request.getRealRequest().getContext();
448 ServletContextFacade contextFacade = context.getFacade();
449
450 // Scan the exception's inheritance tree looking for a rule
451 // that this type of exception should be forwarded
452
453 String path = null;
454 Class clazz = t.getClass();
455
456 while (path == null && clazz != null) {
457 String name = clazz.getName();
458 path = context.getErrorPage(name);
459 clazz = clazz.getSuperclass();
460 }
461
462 // If path is non-null, we should do a forward
463 // Don't do a forward if exception_type is already defined though to
464 // avoid an infinite loop.
465
466 if (path != null &&
467 request.getAttribute(
468 Constants.Attribute.ERROR_EXCEPTION_TYPE) == null) {
469 RequestDispatcher rd = contextFacade.getRequestDispatcher(path);
470
471 // XXX
472 // The spec should really be changed to allow us to include
473 // the full exception object. Oh well.
474
475 request.setAttribute(Constants.Attribute.ERROR_EXCEPTION_TYPE,
476 t.getClass().getName());
477 request.setAttribute(Constants.Attribute.ERROR_MESSAGE,
478 t.getMessage());
479
480 try {
481 try {
482 // A forward would be ideal, so reset and try it
483
484 response.getRealResponse().reset();
485 rd.forward(request, response);
486 } catch (IllegalStateException ise) {
487 // Oops, too late for a forward; settle for an include
488
489 rd.include(request, response);
490 }
491 } catch (IOException e) {
492 // Shouldn't get here
493 } catch (ServletException e) {
494 // Shouldn't get here
495 }
496 } else {
497 try {
498 sendInternalServletError(t, response);
499 } catch (IOException e) {
500 // ???
501 }
502 }
503 }
504
505 void sendInternalServletError(Throwable e,
506 HttpServletResponseFacade response)
507 throws IOException {
508 StringWriter sw = new StringWriter();
509 PrintWriter pw = new PrintWriter(sw);
510
511 pw.println("<b>Internal Servlet Error:</b><br>");
512 pw.println("<pre>");
513 e.printStackTrace(pw);
514 pw.println("</pre>");
515
516 if (e instanceof ServletException) {
517 printRootCause((ServletException) e, pw);
518 }
519
520 response.sendError(500, sw.toString());
521 }
522
523 void printRootCause(ServletException e, PrintWriter out) {
524 Throwable cause = e.getRootCause();
525
526 if (cause != null) {
527 out.println("<b>Root cause:</b>");
528 out.println("<pre>");
529 cause.printStackTrace(out);
530 out.println("</pre>");
531
532 if (cause instanceof ServletException) {
533 printRootCause((ServletException)cause, out); // recurse
534 }
535 }
536 }
537
538 private void handleInvocation(Enumeration interceptors,
539 InvocationHandler inv)
540 throws ServletException, IOException {
541 Stack iStack = new Stack();
542
543 try {
544 for (Enumeration e = interceptors; e.hasMoreElements(); ) {
545 iStack.push(e.nextElement());
546 inv.preInvoke(iStack.peek());
547 }
548
549 inv.method();
550 } catch(InterceptorException ie) {
551 } finally {
552 // in any case, we should make sure we call the
553 // postInvoke before leaving.
554
555 while (! iStack.empty()) {
556 try {
557 inv.postInvoke(iStack.pop());
558 } catch(InterceptorException ie) {
559 // can't do much ....
560 }
561 }
562 }
563 }
564 }
565
566
567 //
568 // WARNING: Some of the APIs in this class are used by J2EE.
569 // Please talk to harishp@eng.sun.com before making any changes.
570 //
571 abstract class InvocationHandler {
572 protected Servlet servlet;
573 protected Context context;
574
575 InvocationHandler(Context context, Servlet servlet) {
576 this.context = context;
577 this.servlet = servlet;
578 }
579
580 abstract void preInvoke(Object interceptor)
581 throws InterceptorException;
582
583 abstract void method()
584 throws ServletException, IOException;
585
586 abstract void postInvoke(Object interceptor)
587 throws InterceptorException;
588 }
589
590
591 //
592 // WARNING: Some of the APIs in this class are used by J2EE.
593 // Please talk to harishp@eng.sun.com before making any changes.
594 //
595 abstract class LifecycleInvocationHandler extends InvocationHandler {
596 LifecycleInvocationHandler(Context context, Servlet servlet) {
597 super(context, servlet);
598 }
599
600 void preInvoke(Object interceptor)
601 throws InterceptorException {
602 ((LifecycleInterceptor)interceptor).preInvoke(context, servlet);
603 }
604
605 void postInvoke(Object interceptor)
606 throws InterceptorException {
607 ((LifecycleInterceptor)interceptor).postInvoke(context, servlet);
608 }
609 }
610
611 //
612 // WARNING: Some of the APIs in this class are used by J2EE.
613 // Please talk to harishp@eng.sun.com before making any changes.
614 //
615 class ServiceInvocationHandler extends InvocationHandler {
616 private HttpServletRequestFacade request;
617 private HttpServletResponseFacade response;
618
619 ServiceInvocationHandler(Context context, Servlet servlet,
620 HttpServletRequestFacade request,
621 HttpServletResponseFacade response) {
622 super(context, servlet);
623
624 this.request = request;
625 this.response = response;
626 }
627
628 void preInvoke(Object interceptor)
629 throws InterceptorException {
630 ((ServiceInterceptor)interceptor).preInvoke(context, servlet,
631 request, response);
632 }
633
634 void method()
635 throws ServletException, IOException {
636 if (servlet instanceof SingleThreadModel) {
637 synchronized(servlet) {
638 servlet.service(request, response);
639 }
640 } else {
641 servlet.service(request, response);
642 }
643 }
644
645 void postInvoke(Object interceptor)
646 throws InterceptorException {
647 ((ServiceInterceptor)interceptor).postInvoke(context, servlet,
648 request, response);
649 }
650 }