001: /*
002: * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: */
007: package winstone;
008:
009: import java.io.IOException;
010: import java.util.ArrayList;
011: import java.util.List;
012: import java.util.Map;
013:
014: import javax.servlet.ServletException;
015: import javax.servlet.ServletRequest;
016: import javax.servlet.ServletRequestWrapper;
017: import javax.servlet.ServletResponse;
018: import javax.servlet.ServletResponseWrapper;
019:
020: /**
021: * This class implements both the RequestDispatcher and FilterChain components. On
022: * the first call to include() or forward(), it starts the filter chain execution
023: * if one exists. On the final doFilter() or if there is no chain, we call the include()
024: * or forward() again, and the servlet is executed.
025: *
026: * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
027: * @version $Id: RequestDispatcher.java,v 1.18 2007/04/23 02:55:35 rickknowles Exp $
028: */
029: public class RequestDispatcher implements
030: javax.servlet.RequestDispatcher, javax.servlet.FilterChain {
031:
032: static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
033: static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
034: static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
035: static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
036: static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";
037:
038: static final String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri";
039: static final String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path";
040: static final String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
041: static final String FORWARD_PATH_INFO = "javax.servlet.forward.path_info";
042: static final String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string";
043:
044: static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
045: static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type";
046: static final String ERROR_MESSAGE = "javax.servlet.error.message";
047: static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
048: static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
049: static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
050:
051: private WebAppConfiguration webAppConfig;
052: private ServletConfiguration servletConfig;
053:
054: private String servletPath;
055: private String pathInfo;
056: private String queryString;
057: private String requestURI;
058:
059: private Integer errorStatusCode;
060: private Throwable errorException;
061: private String errorSummaryMessage;
062:
063: private AuthenticationHandler authHandler;
064:
065: private Mapping forwardFilterPatterns[];
066: private Mapping includeFilterPatterns[];
067: private FilterConfiguration matchingFilters[];
068: private int matchingFiltersEvaluated;
069:
070: private Boolean doInclude;
071: private boolean isErrorDispatch;
072: private boolean useRequestAttributes;
073:
074: private WebAppConfiguration includedWebAppConfig;
075: private ServletConfiguration includedServletConfig;
076:
077: /**
078: * Constructor. This initializes the filter chain and sets up the details
079: * needed to handle a servlet excecution, such as security constraints,
080: * filters, etc.
081: */
082: public RequestDispatcher(WebAppConfiguration webAppConfig,
083: ServletConfiguration servletConfig) {
084: this .servletConfig = servletConfig;
085: this .webAppConfig = webAppConfig;
086:
087: this .matchingFiltersEvaluated = 0;
088: }
089:
090: public void setForNamedDispatcher(Mapping forwardFilterPatterns[],
091: Mapping includeFilterPatterns[]) {
092: this .forwardFilterPatterns = forwardFilterPatterns;
093: this .includeFilterPatterns = includeFilterPatterns;
094: this .matchingFilters = null; // set after the call to forward or include
095: this .useRequestAttributes = false;
096: this .isErrorDispatch = false;
097: }
098:
099: public void setForURLDispatcher(String servletPath,
100: String pathInfo, String queryString,
101: String requestURIInsideWebapp,
102: Mapping forwardFilterPatterns[],
103: Mapping includeFilterPatterns[]) {
104: this .servletPath = servletPath;
105: this .pathInfo = pathInfo;
106: this .queryString = queryString;
107: this .requestURI = requestURIInsideWebapp;
108:
109: this .forwardFilterPatterns = forwardFilterPatterns;
110: this .includeFilterPatterns = includeFilterPatterns;
111: this .matchingFilters = null; // set after the call to forward or include
112: this .useRequestAttributes = true;
113: this .isErrorDispatch = false;
114: }
115:
116: public void setForErrorDispatcher(String servletPath,
117: String pathInfo, String queryString, int statusCode,
118: String summaryMessage, Throwable exception,
119: String errorHandlerURI, Mapping errorFilterPatterns[]) {
120: this .servletPath = servletPath;
121: this .pathInfo = pathInfo;
122: this .queryString = queryString;
123: this .requestURI = errorHandlerURI;
124:
125: this .errorStatusCode = new Integer(statusCode);
126: this .errorException = exception;
127: this .errorSummaryMessage = summaryMessage;
128: this .matchingFilters = getMatchingFilters(errorFilterPatterns,
129: this .webAppConfig, servletPath
130: + (pathInfo == null ? "" : pathInfo),
131: getName(), "ERROR", (servletPath != null));
132: this .useRequestAttributes = true;
133: this .isErrorDispatch = true;
134: }
135:
136: public void setForInitialDispatcher(String servletPath,
137: String pathInfo, String queryString,
138: String requestURIInsideWebapp,
139: Mapping requestFilterPatterns[],
140: AuthenticationHandler authHandler) {
141: this .servletPath = servletPath;
142: this .pathInfo = pathInfo;
143: this .queryString = queryString;
144: this .requestURI = requestURIInsideWebapp;
145: this .authHandler = authHandler;
146: this .matchingFilters = getMatchingFilters(
147: requestFilterPatterns, this .webAppConfig, servletPath
148: + (pathInfo == null ? "" : pathInfo),
149: getName(), "REQUEST", (servletPath != null));
150: this .useRequestAttributes = false;
151: this .isErrorDispatch = false;
152: }
153:
154: public String getName() {
155: return this .servletConfig.getServletName();
156: }
157:
158: /**
159: * Includes the execution of a servlet into the current request
160: *
161: * Note this method enters itself twice: once with the initial call, and once again
162: * when all the filters have completed.
163: */
164: public void include(ServletRequest request, ServletResponse response)
165: throws ServletException, IOException {
166:
167: // On the first call, log and initialise the filter chain
168: if (this .doInclude == null) {
169: Logger.log(Logger.DEBUG, Launcher.RESOURCES,
170: "RequestDispatcher.IncludeMessage", new String[] {
171: getName(), this .requestURI });
172:
173: WinstoneRequest wr = getUnwrappedRequest(request);
174: // Add the query string to the included query string stack
175: wr.addIncludeQueryParameters(this .queryString);
176:
177: // Set request attributes
178: if (useRequestAttributes) {
179: wr.addIncludeAttributes(this .webAppConfig
180: .getContextPath()
181: + this .requestURI, this .webAppConfig
182: .getContextPath(), this .servletPath,
183: this .pathInfo, this .queryString);
184: }
185: // Add another include buffer to the response stack
186: WinstoneResponse wresp = getUnwrappedResponse(response);
187: wresp.startIncludeBuffer();
188:
189: this .includedServletConfig = wr.getServletConfig();
190: this .includedWebAppConfig = wr.getWebAppConfig();
191: wr.setServletConfig(this .servletConfig);
192: wr.setWebAppConfig(this .webAppConfig);
193: wresp.setWebAppConfig(this .webAppConfig);
194:
195: this .doInclude = Boolean.TRUE;
196: }
197:
198: if (this .matchingFilters == null) {
199: this .matchingFilters = getMatchingFilters(
200: this .includeFilterPatterns, this .webAppConfig,
201: this .servletPath
202: + (this .pathInfo == null ? ""
203: : this .pathInfo), getName(),
204: "INCLUDE", (this .servletPath != null));
205: }
206: try {
207: // Make sure the filter chain is exhausted first
208: if (this .matchingFiltersEvaluated < this .matchingFilters.length) {
209: doFilter(request, response);
210: finishInclude(request, response);
211: } else {
212: try {
213: this .servletConfig.execute(request, response,
214: this .webAppConfig.getContextPath()
215: + this .requestURI);
216: } finally {
217: if (this .matchingFilters.length == 0) {
218: finishInclude(request, response);
219: }
220: }
221: }
222: } catch (Throwable err) {
223: finishInclude(request, response);
224: if (err instanceof ServletException) {
225: throw (ServletException) err;
226: } else if (err instanceof IOException) {
227: throw (IOException) err;
228: } else if (err instanceof Error) {
229: throw (Error) err;
230: } else {
231: throw (RuntimeException) err;
232: }
233: }
234: }
235:
236: private void finishInclude(ServletRequest request,
237: ServletResponse response) throws IOException {
238: WinstoneRequest wr = getUnwrappedRequest(request);
239: wr.removeIncludeQueryString();
240:
241: // Set request attributes
242: if (useRequestAttributes) {
243: wr.removeIncludeAttributes();
244: }
245: // Remove the include buffer from the response stack
246: WinstoneResponse wresp = getUnwrappedResponse(response);
247: wresp.finishIncludeBuffer();
248:
249: if (this .includedServletConfig != null) {
250: wr.setServletConfig(this .includedServletConfig);
251: this .includedServletConfig = null;
252: }
253:
254: if (this .includedWebAppConfig != null) {
255: wr.setWebAppConfig(this .includedWebAppConfig);
256: wresp.setWebAppConfig(this .includedWebAppConfig);
257: this .includedWebAppConfig = null;
258: }
259: }
260:
261: /**
262: * Forwards to another servlet, and when it's finished executing that other
263: * servlet, cut off execution.
264: *
265: * Note this method enters itself twice: once with the initial call, and once again
266: * when all the filters have completed.
267: */
268: public void forward(ServletRequest request, ServletResponse response)
269: throws ServletException, IOException {
270:
271: // Only on the first call to forward, we should set any forwarding attributes
272: if (this .doInclude == null) {
273: Logger.log(Logger.DEBUG, Launcher.RESOURCES,
274: "RequestDispatcher.ForwardMessage", new String[] {
275: getName(), this .requestURI });
276: if (response.isCommitted()) {
277: throw new IllegalStateException(
278: Launcher.RESOURCES
279: .getString("RequestDispatcher.ForwardCommitted"));
280: }
281:
282: WinstoneRequest req = getUnwrappedRequest(request);
283: WinstoneResponse rsp = getUnwrappedResponse(response);
284:
285: // Clear the include stack if one has been accumulated
286: rsp.resetBuffer();
287: req.clearIncludeStackForForward();
288: rsp.clearIncludeStackForForward();
289:
290: // Set request attributes (because it's the first step in the filter chain of a forward or error)
291: if (useRequestAttributes) {
292: req.setAttribute(FORWARD_REQUEST_URI, req
293: .getRequestURI());
294: req.setAttribute(FORWARD_CONTEXT_PATH, req
295: .getContextPath());
296: req.setAttribute(FORWARD_SERVLET_PATH, req
297: .getServletPath());
298: req.setAttribute(FORWARD_PATH_INFO, req.getPathInfo());
299: req.setAttribute(FORWARD_QUERY_STRING, req
300: .getQueryString());
301:
302: if (this .isErrorDispatch) {
303: req.setAttribute(ERROR_REQUEST_URI, req
304: .getRequestURI());
305: req.setAttribute(ERROR_STATUS_CODE,
306: this .errorStatusCode);
307: req
308: .setAttribute(
309: ERROR_MESSAGE,
310: errorSummaryMessage != null ? errorSummaryMessage
311: : "");
312: if (req.getServletConfig() != null) {
313: req.setAttribute(ERROR_SERVLET_NAME, req
314: .getServletConfig().getServletName());
315: }
316:
317: if (this .errorException != null) {
318: req.setAttribute(ERROR_EXCEPTION_TYPE,
319: this .errorException.getClass());
320: req.setAttribute(ERROR_EXCEPTION,
321: this .errorException);
322: }
323:
324: // Revert back to the original request and response
325: rsp.setErrorStatusCode(this .errorStatusCode
326: .intValue());
327: request = req;
328: response = rsp;
329: }
330: }
331:
332: req.setServletPath(this .servletPath);
333: req.setPathInfo(this .pathInfo);
334: req.setRequestURI(this .webAppConfig.getContextPath()
335: + this .requestURI);
336: req.setForwardQueryString(this .queryString);
337: req.setWebAppConfig(this .webAppConfig);
338: req.setServletConfig(this .servletConfig);
339: req.setRequestAttributeListeners(this .webAppConfig
340: .getRequestAttributeListeners());
341:
342: rsp.setWebAppConfig(this .webAppConfig);
343:
344: // Forwards haven't set up the filter pattern set yet
345: if (this .matchingFilters == null) {
346: this .matchingFilters = getMatchingFilters(
347: this .forwardFilterPatterns, this .webAppConfig,
348: this .servletPath
349: + (this .pathInfo == null ? ""
350: : this .pathInfo), getName(),
351: "FORWARD", (this .servletPath != null));
352: }
353:
354: // Otherwise we are an initial or error dispatcher, so check security if initial -
355: // if we should not continue, return
356: else if (!this .isErrorDispatch
357: && !continueAfterSecurityCheck(request, response)) {
358: return;
359: }
360:
361: this .doInclude = Boolean.FALSE;
362: }
363:
364: // Make sure the filter chain is exhausted first
365: boolean outsideFilter = (this .matchingFiltersEvaluated == 0);
366: if (this .matchingFiltersEvaluated < this .matchingFilters.length) {
367: doFilter(request, response);
368: } else {
369: this .servletConfig.execute(request, response,
370: this .webAppConfig.getContextPath()
371: + this .requestURI);
372: }
373: // Stop any output after the final filter has been executed (e.g. from forwarding servlet)
374: if (outsideFilter) {
375: WinstoneResponse rsp = getUnwrappedResponse(response);
376: rsp.flushBuffer();
377: rsp.getWinstoneOutputStream().setClosed(true);
378: }
379: }
380:
381: private boolean continueAfterSecurityCheck(ServletRequest request,
382: ServletResponse response) throws IOException,
383: ServletException {
384: // Evaluate security constraints
385: if (this .authHandler != null) {
386: return this .authHandler.processAuthentication(request,
387: response, this .servletPath
388: + (this .pathInfo == null ? ""
389: : this .pathInfo));
390: } else {
391: return true;
392: }
393: }
394:
395: /**
396: * Handles the processing of the chain of filters, so that we process them
397: * all, then pass on to the main servlet
398: */
399: public void doFilter(ServletRequest request,
400: ServletResponse response) throws ServletException,
401: IOException {
402: // Loop through the filter mappings until we hit the end
403: while (this .matchingFiltersEvaluated < this .matchingFilters.length) {
404:
405: FilterConfiguration filter = this .matchingFilters[this .matchingFiltersEvaluated++];
406: Logger.log(Logger.DEBUG, Launcher.RESOURCES,
407: "RequestDispatcher.ExecutingFilter", filter
408: .getFilterName());
409: filter.execute(request, response, this );
410: return;
411: }
412:
413: // Forward / include as requested in the beginning
414: if (this .doInclude == null)
415: return; // will never happen, because we can't call doFilter before forward/include
416: else if (this .doInclude.booleanValue())
417: include(request, response);
418: else
419: forward(request, response);
420: }
421:
422: /**
423: * Caches the filter matching, so that if the same URL is requested twice, we don't recalculate the
424: * filter matching every time.
425: */
426: private static FilterConfiguration[] getMatchingFilters(
427: Mapping filterPatterns[], WebAppConfiguration webAppConfig,
428: String fullPath, String servletName,
429: String filterChainType, boolean isURLBasedMatch) {
430:
431: String cacheKey = null;
432: if (isURLBasedMatch) {
433: cacheKey = filterChainType + ":URI:" + fullPath;
434: } else {
435: cacheKey = filterChainType + ":Servlet:" + servletName;
436: }
437: FilterConfiguration matchingFilters[] = null;
438: Map cache = webAppConfig.getFilterMatchCache();
439: synchronized (cache) {
440: matchingFilters = (FilterConfiguration[]) cache
441: .get(cacheKey);
442: if (matchingFilters == null) {
443: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
444: "RequestDispatcher.CalcFilterChain", cacheKey);
445: List outFilters = new ArrayList();
446: for (int n = 0; n < filterPatterns.length; n++) {
447: // Get the pattern and eval it, bumping up the eval'd count
448: Mapping filterPattern = filterPatterns[n];
449:
450: // If the servlet name matches this name, execute it
451: if ((filterPattern.getLinkName() != null)
452: && (filterPattern.getLinkName().equals(
453: servletName) || filterPattern
454: .getLinkName().equals("*"))) {
455: outFilters.add(webAppConfig.getFilters().get(
456: filterPattern.getMappedTo()));
457: }
458: // If the url path matches this filters mappings
459: else if ((filterPattern.getLinkName() == null)
460: && isURLBasedMatch
461: && filterPattern
462: .match(fullPath, null, null)) {
463: outFilters.add(webAppConfig.getFilters().get(
464: filterPattern.getMappedTo()));
465: }
466: }
467: matchingFilters = (FilterConfiguration[]) outFilters
468: .toArray(new FilterConfiguration[0]);
469: cache.put(cacheKey, matchingFilters);
470: } else {
471: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
472: "RequestDispatcher.UseCachedFilterChain",
473: cacheKey);
474: }
475: }
476: return matchingFilters;
477: }
478:
479: /**
480: * Unwrap back to the original container allocated request object
481: */
482: protected WinstoneRequest getUnwrappedRequest(ServletRequest request) {
483: ServletRequest workingRequest = request;
484: while (workingRequest instanceof ServletRequestWrapper) {
485: workingRequest = ((ServletRequestWrapper) workingRequest)
486: .getRequest();
487: }
488: return (WinstoneRequest) workingRequest;
489: }
490:
491: /**
492: * Unwrap back to the original container allocated response object
493: */
494: protected WinstoneResponse getUnwrappedResponse(
495: ServletResponse response) {
496: ServletResponse workingResponse = response;
497: while (workingResponse instanceof ServletResponseWrapper) {
498: workingResponse = ((ServletResponseWrapper) workingResponse)
499: .getResponse();
500: }
501: return (WinstoneResponse) workingResponse;
502: }
503: }
|