1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.core;
20
21
22 import java.io.IOException;
23
24 import javax.management.MalformedObjectNameException;
25 import javax.management.ObjectName;
26 import javax.servlet.Servlet;
27 import javax.servlet.ServletException;
28 import javax.servlet.UnavailableException;
29 import javax.servlet.http.HttpServletResponse;
30
31 import org.apache.catalina.CometEvent;
32 import org.apache.catalina.CometProcessor;
33 import org.apache.catalina.Context;
34 import org.apache.catalina.Globals;
35 import org.apache.catalina.connector.ClientAbortException;
36 import org.apache.catalina.connector.Request;
37 import org.apache.catalina.connector.Response;
38 import org.apache.catalina.util.StringManager;
39 import org.apache.catalina.valves.ValveBase;
40 import org.apache.tomcat.util.buf.MessageBytes;
41 import org.apache.tomcat.util.log.SystemLogHandler;
42
43 /**
44 * Valve that implements the default basic behavior for the
45 * <code>StandardWrapper</code> container implementation.
46 *
47 * @author Craig R. McClanahan
48 * @version $Revision: 596489 $ $Date: 2007-11-20 00:38:40 +0100 (mar., 20 nov. 2007) $
49 */
50
51 final class StandardWrapperValve
52 extends ValveBase {
53
54 // ----------------------------------------------------- Instance Variables
55
56
57 // Some JMX statistics. This vavle is associated with a StandardWrapper.
58 // We expose the StandardWrapper as JMX ( j2eeType=Servlet ). The fields
59 // are here for performance.
60 private volatile long processingTime;
61 private volatile long maxTime;
62 private volatile long minTime = Long.MAX_VALUE;
63 private volatile int requestCount;
64 private volatile int errorCount;
65
66
67 /**
68 * The string manager for this package.
69 */
70 private static final StringManager sm =
71 StringManager.getManager(Constants.Package);
72
73
74 // --------------------------------------------------------- Public Methods
75
76
77 /**
78 * Invoke the servlet we are managing, respecting the rules regarding
79 * servlet lifecycle and SingleThreadModel support.
80 *
81 * @param request Request to be processed
82 * @param response Response to be produced
83 * @param valveContext Valve context used to forward to the next Valve
84 *
85 * @exception IOException if an input/output error occurred
86 * @exception ServletException if a servlet error occurred
87 */
88 public final void invoke(Request request, Response response)
89 throws IOException, ServletException {
90
91 // Initialize local variables we may need
92 boolean unavailable = false;
93 Throwable throwable = null;
94 // This should be a Request attribute...
95 long t1=System.currentTimeMillis();
96 requestCount++;
97 StandardWrapper wrapper = (StandardWrapper) getContainer();
98 Servlet servlet = null;
99 Context context = (Context) wrapper.getParent();
100
101 // Check for the application being marked unavailable
102 if (!context.getAvailable()) {
103 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
104 sm.getString("standardContext.isUnavailable"));
105 unavailable = true;
106 }
107
108 // Check for the servlet being marked unavailable
109 if (!unavailable && wrapper.isUnavailable()) {
110 container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
111 wrapper.getName()));
112 long available = wrapper.getAvailable();
113 if ((available > 0L) && (available < Long.MAX_VALUE)) {
114 response.setDateHeader("Retry-After", available);
115 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
116 sm.getString("standardWrapper.isUnavailable",
117 wrapper.getName()));
118 } else if (available == Long.MAX_VALUE) {
119 response.sendError(HttpServletResponse.SC_NOT_FOUND,
120 sm.getString("standardWrapper.notFound",
121 wrapper.getName()));
122 }
123 unavailable = true;
124 }
125
126 // Allocate a servlet instance to process this request
127 try {
128 if (!unavailable) {
129 servlet = wrapper.allocate();
130 }
131 } catch (UnavailableException e) {
132 container.getLogger().error(
133 sm.getString("standardWrapper.allocateException",
134 wrapper.getName()), e);
135 long available = wrapper.getAvailable();
136 if ((available > 0L) && (available < Long.MAX_VALUE)) {
137 response.setDateHeader("Retry-After", available);
138 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
139 sm.getString("standardWrapper.isUnavailable",
140 wrapper.getName()));
141 } else if (available == Long.MAX_VALUE) {
142 response.sendError(HttpServletResponse.SC_NOT_FOUND,
143 sm.getString("standardWrapper.notFound",
144 wrapper.getName()));
145 }
146 } catch (ServletException e) {
147 container.getLogger().error(sm.getString("standardWrapper.allocateException",
148 wrapper.getName()), StandardWrapper.getRootCause(e));
149 throwable = e;
150 exception(request, response, e);
151 servlet = null;
152 } catch (Throwable e) {
153 container.getLogger().error(sm.getString("standardWrapper.allocateException",
154 wrapper.getName()), e);
155 throwable = e;
156 exception(request, response, e);
157 servlet = null;
158 }
159
160 // Identify if the request is Comet related now that the servlet has been allocated
161 boolean comet = false;
162 if (servlet instanceof CometProcessor
163 && request.getAttribute("org.apache.tomcat.comet.support") == Boolean.TRUE) {
164 comet = true;
165 request.setComet(true);
166 }
167
168 // Acknowledge the request
169 try {
170 response.sendAcknowledgement();
171 } catch (IOException e) {
172 request.removeAttribute(Globals.JSP_FILE_ATTR);
173 container.getLogger().warn(sm.getString("standardWrapper.acknowledgeException",
174 wrapper.getName()), e);
175 throwable = e;
176 exception(request, response, e);
177 } catch (Throwable e) {
178 container.getLogger().error(sm.getString("standardWrapper.acknowledgeException",
179 wrapper.getName()), e);
180 throwable = e;
181 exception(request, response, e);
182 servlet = null;
183 }
184 MessageBytes requestPathMB = null;
185 if (request != null) {
186 requestPathMB = request.getRequestPathMB();
187 }
188 request.setAttribute
189 (ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
190 ApplicationFilterFactory.REQUEST_INTEGER);
191 request.setAttribute
192 (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
193 requestPathMB);
194 // Create the filter chain for this request
195 ApplicationFilterFactory factory =
196 ApplicationFilterFactory.getInstance();
197 ApplicationFilterChain filterChain =
198 factory.createFilterChain(request, wrapper, servlet);
199 // Reset comet flag value after creating the filter chain
200 request.setComet(false);
201
202 // Call the filter chain for this request
203 // NOTE: This also calls the servlet's service() method
204 try {
205 String jspFile = wrapper.getJspFile();
206 if (jspFile != null)
207 request.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
208 else
209 request.removeAttribute(Globals.JSP_FILE_ATTR);
210 if ((servlet != null) && (filterChain != null)) {
211 // Swallow output if needed
212 if (context.getSwallowOutput()) {
213 try {
214 SystemLogHandler.startCapture();
215 if (comet) {
216 filterChain.doFilterEvent(request.getEvent());
217 request.setComet(true);
218 } else {
219 filterChain.doFilter(request.getRequest(),
220 response.getResponse());
221 }
222 } finally {
223 String log = SystemLogHandler.stopCapture();
224 if (log != null && log.length() > 0) {
225 context.getLogger().info(log);
226 }
227 }
228 } else {
229 if (comet) {
230 request.setComet(true);
231 filterChain.doFilterEvent(request.getEvent());
232 } else {
233 filterChain.doFilter
234 (request.getRequest(), response.getResponse());
235 }
236 }
237
238 }
239 request.removeAttribute(Globals.JSP_FILE_ATTR);
240 } catch (ClientAbortException e) {
241 request.removeAttribute(Globals.JSP_FILE_ATTR);
242 throwable = e;
243 exception(request, response, e);
244 } catch (IOException e) {
245 request.removeAttribute(Globals.JSP_FILE_ATTR);
246 container.getLogger().error(sm.getString("standardWrapper.serviceException",
247 wrapper.getName()), e);
248 throwable = e;
249 exception(request, response, e);
250 } catch (UnavailableException e) {
251 request.removeAttribute(Globals.JSP_FILE_ATTR);
252 container.getLogger().error(sm.getString("standardWrapper.serviceException",
253 wrapper.getName()), e);
254 // throwable = e;
255 // exception(request, response, e);
256 wrapper.unavailable(e);
257 long available = wrapper.getAvailable();
258 if ((available > 0L) && (available < Long.MAX_VALUE)) {
259 response.setDateHeader("Retry-After", available);
260 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
261 sm.getString("standardWrapper.isUnavailable",
262 wrapper.getName()));
263 } else if (available == Long.MAX_VALUE) {
264 response.sendError(HttpServletResponse.SC_NOT_FOUND,
265 sm.getString("standardWrapper.notFound",
266 wrapper.getName()));
267 }
268 // Do not save exception in 'throwable', because we
269 // do not want to do exception(request, response, e) processing
270 } catch (ServletException e) {
271 request.removeAttribute(Globals.JSP_FILE_ATTR);
272 Throwable rootCause = StandardWrapper.getRootCause(e);
273 if (!(rootCause instanceof ClientAbortException)) {
274 container.getLogger().error(sm.getString("standardWrapper.serviceException",
275 wrapper.getName()), rootCause);
276 }
277 throwable = e;
278 exception(request, response, e);
279 } catch (Throwable e) {
280 request.removeAttribute(Globals.JSP_FILE_ATTR);
281 container.getLogger().error(sm.getString("standardWrapper.serviceException",
282 wrapper.getName()), e);
283 throwable = e;
284 exception(request, response, e);
285 }
286
287 // Release the filter chain (if any) for this request
288 if (filterChain != null) {
289 if (request.isComet()) {
290 // If this is a Comet request, then the same chain will be used for the
291 // processing of all subsequent events.
292 filterChain.reuse();
293 } else {
294 filterChain.release();
295 }
296 }
297
298 // Deallocate the allocated servlet instance
299 try {
300 if (servlet != null) {
301 wrapper.deallocate(servlet);
302 }
303 } catch (Throwable e) {
304 container.getLogger().error(sm.getString("standardWrapper.deallocateException",
305 wrapper.getName()), e);
306 if (throwable == null) {
307 throwable = e;
308 exception(request, response, e);
309 }
310 }
311
312 // If this servlet has been marked permanently unavailable,
313 // unload it and release this instance
314 try {
315 if ((servlet != null) &&
316 (wrapper.getAvailable() == Long.MAX_VALUE)) {
317 wrapper.unload();
318 }
319 } catch (Throwable e) {
320 container.getLogger().error(sm.getString("standardWrapper.unloadException",
321 wrapper.getName()), e);
322 if (throwable == null) {
323 throwable = e;
324 exception(request, response, e);
325 }
326 }
327 long t2=System.currentTimeMillis();
328
329 long time=t2-t1;
330 processingTime += time;
331 if( time > maxTime) maxTime=time;
332 if( time < minTime) minTime=time;
333
334 }
335
336
337 /**
338 * Process a Comet event. The main differences here are to not use sendError
339 * (the response is committed), to avoid creating a new filter chain
340 * (which would work but be pointless), and a few very minor tweaks.
341 *
342 * @param request The servlet request to be processed
343 * @param response The servlet response to be created
344 *
345 * @exception IOException if an input/output error occurs, or is thrown
346 * by a subsequently invoked Valve, Filter, or Servlet
347 * @exception ServletException if a servlet error occurs, or is thrown
348 * by a subsequently invoked Valve, Filter, or Servlet
349 */
350 public void event(Request request, Response response, CometEvent event)
351 throws IOException, ServletException {
352
353 // Initialize local variables we may need
354 Throwable throwable = null;
355 // This should be a Request attribute...
356 long t1=System.currentTimeMillis();
357 // FIXME: Add a flag to count the total amount of events processed ? requestCount++;
358 StandardWrapper wrapper = (StandardWrapper) getContainer();
359 Servlet servlet = null;
360 Context context = (Context) wrapper.getParent();
361
362 // Check for the application being marked unavailable
363 boolean unavailable = !context.getAvailable() || wrapper.isUnavailable();
364
365 // Allocate a servlet instance to process this request
366 try {
367 if (!unavailable) {
368 servlet = wrapper.allocate();
369 }
370 } catch (UnavailableException e) {
371 // The response is already committed, so it's not possible to do anything
372 } catch (ServletException e) {
373 container.getLogger().error(sm.getString("standardWrapper.allocateException",
374 wrapper.getName()), StandardWrapper.getRootCause(e));
375 throwable = e;
376 exception(request, response, e);
377 servlet = null;
378 } catch (Throwable e) {
379 container.getLogger().error(sm.getString("standardWrapper.allocateException",
380 wrapper.getName()), e);
381 throwable = e;
382 exception(request, response, e);
383 servlet = null;
384 }
385
386 MessageBytes requestPathMB = null;
387 if (request != null) {
388 requestPathMB = request.getRequestPathMB();
389 }
390 request.setAttribute
391 (ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
392 ApplicationFilterFactory.REQUEST_INTEGER);
393 request.setAttribute
394 (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
395 requestPathMB);
396 // Get the current (unchanged) filter chain for this request
397 ApplicationFilterChain filterChain =
398 (ApplicationFilterChain) request.getFilterChain();
399
400 // Call the filter chain for this request
401 // NOTE: This also calls the servlet's event() method
402 try {
403 String jspFile = wrapper.getJspFile();
404 if (jspFile != null)
405 request.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
406 else
407 request.removeAttribute(Globals.JSP_FILE_ATTR);
408 if ((servlet != null) && (filterChain != null)) {
409
410 // Swallow output if needed
411 if (context.getSwallowOutput()) {
412 try {
413 SystemLogHandler.startCapture();
414 filterChain.doFilterEvent(request.getEvent());
415 } finally {
416 String log = SystemLogHandler.stopCapture();
417 if (log != null && log.length() > 0) {
418 context.getLogger().info(log);
419 }
420 }
421 } else {
422 filterChain.doFilterEvent(request.getEvent());
423 }
424
425 }
426 request.removeAttribute(Globals.JSP_FILE_ATTR);
427 } catch (ClientAbortException e) {
428 request.removeAttribute(Globals.JSP_FILE_ATTR);
429 throwable = e;
430 exception(request, response, e);
431 } catch (IOException e) {
432 request.removeAttribute(Globals.JSP_FILE_ATTR);
433 container.getLogger().error(sm.getString("standardWrapper.serviceException",
434 wrapper.getName()), e);
435 throwable = e;
436 exception(request, response, e);
437 } catch (UnavailableException e) {
438 request.removeAttribute(Globals.JSP_FILE_ATTR);
439 container.getLogger().error(sm.getString("standardWrapper.serviceException",
440 wrapper.getName()), e);
441 // Do not save exception in 'throwable', because we
442 // do not want to do exception(request, response, e) processing
443 } catch (ServletException e) {
444 request.removeAttribute(Globals.JSP_FILE_ATTR);
445 Throwable rootCause = StandardWrapper.getRootCause(e);
446 if (!(rootCause instanceof ClientAbortException)) {
447 container.getLogger().error(sm.getString("standardWrapper.serviceException",
448 wrapper.getName()), rootCause);
449 }
450 throwable = e;
451 exception(request, response, e);
452 } catch (Throwable e) {
453 request.removeAttribute(Globals.JSP_FILE_ATTR);
454 container.getLogger().error(sm.getString("standardWrapper.serviceException",
455 wrapper.getName()), e);
456 throwable = e;
457 exception(request, response, e);
458 }
459
460 // Release the filter chain (if any) for this request
461 if (filterChain != null) {
462 filterChain.reuse();
463 }
464
465 // Deallocate the allocated servlet instance
466 try {
467 if (servlet != null) {
468 wrapper.deallocate(servlet);
469 }
470 } catch (Throwable e) {
471 container.getLogger().error(sm.getString("standardWrapper.deallocateException",
472 wrapper.getName()), e);
473 if (throwable == null) {
474 throwable = e;
475 exception(request, response, e);
476 }
477 }
478
479 // If this servlet has been marked permanently unavailable,
480 // unload it and release this instance
481 try {
482 if ((servlet != null) &&
483 (wrapper.getAvailable() == Long.MAX_VALUE)) {
484 wrapper.unload();
485 }
486 } catch (Throwable e) {
487 container.getLogger().error(sm.getString("standardWrapper.unloadException",
488 wrapper.getName()), e);
489 if (throwable == null) {
490 throwable = e;
491 exception(request, response, e);
492 }
493 }
494
495 long t2=System.currentTimeMillis();
496
497 long time=t2-t1;
498 processingTime += time;
499 if( time > maxTime) maxTime=time;
500 if( time < minTime) minTime=time;
501
502 }
503
504
505 // -------------------------------------------------------- Private Methods
506
507
508 /**
509 * Handle the specified ServletException encountered while processing
510 * the specified Request to produce the specified Response. Any
511 * exceptions that occur during generation of the exception report are
512 * logged and swallowed.
513 *
514 * @param request The request being processed
515 * @param response The response being generated
516 * @param exception The exception that occurred (which possibly wraps
517 * a root cause exception
518 */
519 private void exception(Request request, Response response,
520 Throwable exception) {
521 request.setAttribute(Globals.EXCEPTION_ATTR, exception);
522 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
523
524 }
525
526 public long getProcessingTime() {
527 return processingTime;
528 }
529
530 public void setProcessingTime(long processingTime) {
531 this.processingTime = processingTime;
532 }
533
534 public long getMaxTime() {
535 return maxTime;
536 }
537
538 public void setMaxTime(long maxTime) {
539 this.maxTime = maxTime;
540 }
541
542 public long getMinTime() {
543 return minTime;
544 }
545
546 public void setMinTime(long minTime) {
547 this.minTime = minTime;
548 }
549
550 public int getRequestCount() {
551 return requestCount;
552 }
553
554 public void setRequestCount(int requestCount) {
555 this.requestCount = requestCount;
556 }
557
558 public int getErrorCount() {
559 return errorCount;
560 }
561
562 public void setErrorCount(int errorCount) {
563 this.errorCount = errorCount;
564 }
565
566 // Don't register in JMX
567
568 public ObjectName createObjectName(String domain, ObjectName parent)
569 throws MalformedObjectNameException
570 {
571 return null;
572 }
573 }