/* Copyright 2008 Ben Gunter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sourceforge.stripes.controller;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.config.Configuration;
import net.sourceforge.stripes.exception.StripesServletException;
import net.sourceforge.stripes.util.HttpUtil;
import net.sourceforge.stripes.util.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* <p>
* A servlet filter that dynamically maps URLs to {@link ActionBean}s. This filter can be used to
* allow Stripes to dispatch requests to {@link ActionBean}s based on their URL binding, even if the
* URL to which they are bound is not explicitly mapped in {@code web.xml}.
* </p>
* <p>
* One caveat must be observed when using this filter. This filter <em>MUST</em> be the last filter
* in the filter chain. When it dynamically maps an {@link ActionBean} to a URL, the filter chain is
* interrupted.
* </p>
* <p>
* {@link StripesFilter} and/or {@link DispatcherServlet} may be declared in {@code web.xml}, but
* neither is required for this filter to work. If you choose not to declare {@link StripesFilter}
* in {@code web.xml}, then this filter should be configured the way you would normally configure
* {@link StripesFilter}. However, some resources, such as JSPs, may require access to the Stripes
* {@link Configuration} through {@link StripesFilter}. If you intend to access JSPs directly, then
* {@link StripesFilter} should be explicitly mapped to {@code *.jsp}.
* </p>
* <p>
* This filter takes the following approach to determining when to dispatch an {@link ActionBean}:
* <ol>
* <li>Allow the request to process normally, trapping any HTTP errors that are returned.</li>
* <li>If no error was returned then do nothing, allowing the request to complete successfully. If
* any error other than {@code 404} was returned then send the error through. Otherwise ...</li>
* <li>Check the {@link ActionResolver} to see if an {@link ActionBean} is mapped to the URL. If
* not, then send the {@code 404} error through. Otherwise...</li>
* <li>Invoke {@link StripesFilter} and {@link DispatcherServlet}</li>
* </ol>
* </p>
* <p>
* One benefit of this approach is that static resources can be delivered from the same namespace to
* which an {@link ActionBean} is mapped using clean URLs. (For more information on clean URLs, see
* {@link UrlBinding}.) For example, if your {@code UserActionBean} is mapped to
* {@code @UrlBinding("/user/{id}/{$event}")} and you have a static file at {@code /user/icon.gif},
* then your icon will be delivered correctly because the initial request will not have returned a
* {@code 404} error.
* </p>
* <p>
* The {@code IncludeBufferSize} initialization parameter (optional, default 1024) sets the number
* of characters to be buffered by {@link TempBufferWriter} for include requests. See
* {@link TempBufferWriter} for more information.
* <p>
* This is the suggested mapping for this filter in {@code web.xml}.
* </p>
*
* <pre>
* <filter>
* <description>Dynamically maps URLs to ActionBeans.</description>
* <display-name>Stripes Dynamic Mapping Filter</display-name>
* <filter-name>DynamicMappingFilter</filter-name>
* <filter-class>
* net.sourceforge.stripes.controller.DynamicMappingFilter
* </filter-class>
* <init-param>
* <param-name>ActionResolver.Packages</param-name>
* <param-value>com.yourcompany.stripes.action</param-value>
* </init-param>
* </filter>
*
* <filter-mapping>
* <filter-name>DynamicMappingFilter</filter-name>
* <url-pattern>/*</url-pattern>
* <dispatcher>REQUEST</dispatcher>
* <dispatcher>FORWARD</dispatcher>
* <dispatcher>INCLUDE</dispatcher>
* </filter-mapping>
* </pre>
*
* @author Ben Gunter
* @since Stripes 1.5
* @see UrlBinding
*/
public class DynamicMappingFilter implements Filter {
/**
* <p>
* A {@link Writer} that passes characters to a {@link PrintWriter}. It buffers the first
* {@code N} characters written to it and automatically overflows when the number of characters
* written exceeds the limit. The size of the buffer defaults to 1024 characters, but it can be
* changed using the {@code IncludeBufferSize} filter init-param in {@code web.xml}. If
* {@code IncludeBufferSize} is zero or negative, then a {@link TempBufferWriter} will not be
* used at all. This is only a good idea if your servlet container does not write an error
* message to output when it can't find an included resource or if you only include resources
* that do not depend on this filter to be delivered, such as other servlets, JSPs, static
* resources, ActionBeans that are mapped with a prefix ({@code /action/*}) or suffix ({@code *.action}),
* etc.
* </p>
* <p>
* This writer is used to partially buffer the output of includes. Some (all?) servlet
* containers write a message to the output stream indicating if an included resource is missing
* because if the response has already been committed, they cannot send a 404 error. Since the
* filter depends on getting a 404 before it attempts to dispatch an {@code ActionBean}, that
* is problematic. So in using this writer, we assume that the length of the "missing resource"
* message will be less than the buffer size and we discard that message if we're able to map
* the included URL to an {@code ActionBean}. If there is no 404 then the output will be sent
* normally. If there is a 404 and the URL does not match an ActionBean then the "missing
* resource" message is sent through.
* </p>
*
* @author Ben Gunter
* @since Stripes 1.5
*/
public static class TempBufferWriter extends Writer {
private StringWriter buffer;
private PrintWriter out;
public TempBufferWriter(PrintWriter out) {
this.out = out;
this.buffer = new StringWriter(includeBufferSize);
}
@Override
public void close() throws IOException {
flush();
out.close();
}
@Override
public void flush() throws IOException {
overflow();
out.flush();
}
@Override
public void write(char[] chars, int offset, int length) throws IOException {
if (buffer == null) {
out.write(chars, offset, length);
}
else if (buffer.getBuffer().length() + length > includeBufferSize) {
overflow();
out.write(chars, offset, length);
}
else {
buffer.write(chars, offset, length);
}
}
/**
* Write the contents of the buffer to the underlying writer. After a call to
* this method, all future writes to this writer will pass directly to the
* underlying writer.
*/
protected void overflow() {
if (buffer != null) {
out.print(buffer.toString());
buffer = null;
}
}
}
/**
* An {@link HttpServletResponseWrapper} that traps HTTP errors by overriding
* {@code sendError(int, ..)}. The error code can be retrieved by calling
* {@link #getErrorCode()}. A call to {@link #proceed()} sends the error to the client.
*
* @author Ben Gunter
* @since Stripes 1.5
*/
public static class ErrorTrappingResponseWrapper extends HttpServletResponseWrapper {
private Integer errorCode;
private String errorMessage;
private boolean include;
private PrintWriter printWriter;
private TempBufferWriter tempBufferWriter;
/** Wrap the given {@code response}. */
public ErrorTrappingResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public void sendError(int errorCode, String errorMessage) throws IOException {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
@Override
public void sendError(int errorCode) throws IOException {
this.errorCode = errorCode;
this.errorMessage = null;
}
@Override
public PrintWriter getWriter() throws IOException {
if (isInclude() && includeBufferSize > 0) {
if (printWriter == null) {
tempBufferWriter = new TempBufferWriter(super.getWriter());
printWriter = new PrintWriter(tempBufferWriter);
}
return printWriter;
}
else {
return super.getWriter();
}
}
/** True if the currently executing request is an include. */
public boolean isInclude() {
return include;
}
/** Indicate if the currently executing request is an include. */
public void setInclude(boolean include) {
this.include = include;
}
/** Get the error code that was passed into {@code sendError(int, ..)} */
public Integer getErrorCode() {
return errorCode;
}
/** Clear error code and error message. */
@SuppressWarnings("unused")
public void clearError() {
this.errorCode = null;
this.errorMessage = null;
}
/**
* Send the error, if any, to the client. If {@code sendError(int, ..)} has not previously
* been called, then do nothing.
*/
public void proceed() throws IOException {
// Explicitly overflow the buffer so the output gets written
if (tempBufferWriter != null)
tempBufferWriter.overflow();
if (errorCode != null) {
if (errorMessage == null)
super.sendError(errorCode);
else
super.sendError(errorCode, errorMessage);
}
}
private boolean isError(int sc) {
return sc >= 400 && sc <= 500;
}
@Override
public void setStatus(int sc) {
super.setStatus(sc);
if (isError(sc)) {
this.errorCode = sc;
}
}
@Override
public void setStatus(int sc, String sm) {
super.setStatus(sc, sm);
if (isError(sc)) {
this.errorCode = sc;
this.errorMessage = sm;
}
}
}
/**
* The name of the init-param that can be used to set the size of the buffer used by
* {@link TempBufferWriter} before it overflows.
*/
public static final String INCLUDE_BUFFER_SIZE_PARAM = "IncludeBufferSize";
/**
* The attribute name used to store a reference to {@link StripesFilter} in the servlet context.
*/
public static final String CONTEXT_KEY_STRIPES_FILTER = StripesFilter.class.getName();
/**
* Request header that indicates that the current request is part of the process of trying to
* force initialization of {@link StripesFilter}. If this header is present then
* {@link DynamicMappingFilter} makes no attempt to map the request to an {@link ActionBean}.
*/
private static final String REQ_HEADER_INIT_FLAG = "X-Dynamic-Mapping-Filter-Init";
/** The size of the buffer used by {@link TempBufferWriter} before it overflows. */
private static int includeBufferSize = 1024;
/** Logger */
private static Log log = Log.getInstance(DynamicMappingFilter.class);
private FilterConfig filterConfig;
private ServletContext servletContext;
private StripesFilter stripesFilter;
private DispatcherServlet stripesDispatcher;
private boolean stripesFilterIsInternal, initializing;
public void init(final FilterConfig config) throws ServletException {
try {
String value = config.getInitParameter(INCLUDE_BUFFER_SIZE_PARAM);
if (value != null) {
includeBufferSize = Integer.valueOf(value.trim());
log.info(getClass().getSimpleName(), " include buffer size is ", includeBufferSize);
}
}
catch (Exception e) {
log.warn(e, "Could not interpret '",
config.getInitParameter(INCLUDE_BUFFER_SIZE_PARAM),
"' as a number for init-param '", INCLUDE_BUFFER_SIZE_PARAM,
"'. Using default value ", includeBufferSize, ".");
}
this.filterConfig = config;
this.servletContext = config.getServletContext();
this.stripesDispatcher = new DispatcherServlet();
this.stripesDispatcher.init(new ServletConfig() {
public String getInitParameter(String name) {
return config.getInitParameter(name);
}
public Enumeration<String> getInitParameterNames() {
return config.getInitParameterNames();
}
public ServletContext getServletContext() {
return config.getServletContext();
}
public String getServletName() {
return config.getFilterName();
}
});
}
public void destroy() {
try {
if (stripesDispatcher != null)
stripesDispatcher.destroy();
}
finally {
stripesDispatcher = null;
try {
if (stripesFilterIsInternal && stripesFilter != null)
stripesFilter.destroy();
}
finally {
stripesFilter = null;
}
}
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Wrap the response in a wrapper that catches errors (but not exceptions)
final ErrorTrappingResponseWrapper wrapper = new ErrorTrappingResponseWrapper(
(HttpServletResponse) response);
wrapper.setInclude(request.getAttribute(StripesConstants.REQ_ATTR_INCLUDE_PATH) != null);
// Catch FileNotFoundException, which some containers (e.g. GlassFish) throw instead of setting SC_NOT_FOUND
boolean fileNotFoundExceptionThrown = false;
try {
chain.doFilter(request, wrapper);
}
catch (FileNotFoundException exc) {
fileNotFoundExceptionThrown = true;
}
catch(ServletException e) {
// IBM Liberty basically wraps the FileNotFound into a ServletException
if (e.getRootCause() instanceof FileNotFoundException) {
fileNotFoundExceptionThrown = true;
} else {
// re-throw the exception
throw e;
}
}
// Check the instance field as well as request header for initialization request
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
boolean initializing = this.initializing
|| httpServletRequest.getHeader(REQ_HEADER_INIT_FLAG) != null;
// If a FileNotFoundException or SC_NOT_FOUND error occurred, then try to match an ActionBean to the URL
boolean notFound = false;
Integer errorCode = wrapper.getErrorCode();
if (errorCode!=null) {
if (errorCode==HttpServletResponse.SC_NOT_FOUND) {
notFound = true;
} else {
// special handling for WildFly,
// see http://www.stripesframework.org/jira/browse/STS-916
if ("POST".equals(httpServletRequest.getMethod())
&& errorCode == HttpServletResponse.SC_METHOD_NOT_ALLOWED) {
notFound = true;
}
}
}
if (!initializing && (notFound || fileNotFoundExceptionThrown)) {
// Get a reference to a StripesFilter instance
StripesFilter sf = getStripesFilter();
if (sf == null) {
initStripesFilter((HttpServletRequest) request, wrapper);
sf = getStripesFilter();
}
sf.doFilter(request, response, new FilterChain() {
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// Look for an ActionBean that is mapped to the URI
String uri = HttpUtil.getRequestedPath((HttpServletRequest) request);
Class<? extends ActionBean> beanType = getStripesFilter()
.getInstanceConfiguration().getActionResolver().getActionBeanType(uri);
// If found then call the dispatcher directly. Otherwise, send the error.
if (beanType == null) {
wrapper.proceed();
}
else {
stripesDispatcher.service(request, response);
}
}
});
}
else {
wrapper.proceed();
}
}
/**
* Get a reference to {@link StripesFilter}. The first time this method is called, the reference
* will be looked up in the servlet context and cached in the {@link #stripesFilter} field.
*/
protected StripesFilter getStripesFilter() {
if (stripesFilter == null) {
stripesFilter = (StripesFilter) servletContext.getAttribute(CONTEXT_KEY_STRIPES_FILTER);
if (stripesFilter != null) {
log.debug("Found StripesFilter in the servlet context.");
}
}
return stripesFilter;
}
/**
* The servlet spec allows a container to wait until a filter is required to process a request
* before it initializes the filter. Since we need to get a reference to {@link StripesFilter}
* from the servlet context, we really need {@link StripesFilter} to have been initialized at
* the time we process our first request. If that didn't happen automatically, this method does
* its best to force it to happen.
*
* @param request The current request
* @param response The current response
* @throws ServletException If anything goes wrong that simply can't be ignored.
*/
protected synchronized void initStripesFilter(HttpServletRequest request,
HttpServletResponse response) throws ServletException {
try {
// Check if another thread got into this method before the current thread
if (getStripesFilter() != null)
return;
log.info("StripesFilter not initialized. Checking the situation in web.xml ...");
Document document = parseWebXml();
NodeList filterNodes = eval("/web-app/filter/filter-class[text()='"
+ StripesFilter.class.getName() + "']/..", document, XPathConstants.NODESET);
if (filterNodes == null || filterNodes.getLength() != 1) {
String msg;
if (filterNodes == null || filterNodes.getLength() < 1) {
msg = "StripesFilter is not declared in web.xml. ";
}
else {
msg = "StripesFilter is declared multiple times in web.xml; refusing to use either one. ";
}
log.info(msg, "Initializing with \"", filterConfig.getFilterName(),
"\" configuration.");
createStripesFilter(filterConfig);
}
else {
Node filterNode = filterNodes.item(0);
final String name = eval("filter-name", filterNode, XPathConstants.STRING);
log.debug("Found StripesFilter declared as ", name, " in web.xml");
List<String> patterns = getFilterUrlPatterns(filterNode);
if (patterns.isEmpty()) {
log.info("StripesFilter is declared but not mapped in web.xml. ",
"Initializing with \"", name, "\" configuration from web.xml.");
final Map<String, String> parameters = getFilterParameters(filterNode);
createStripesFilter(new FilterConfig() {
public ServletContext getServletContext() {
return servletContext;
}
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(parameters.keySet());
}
public String getInitParameter(String name) {
return parameters.get(name);
}
public String getFilterName() {
return name;
}
});
}
else {
issueRequests(patterns, request, response);
}
}
}
catch (RuntimeException e) {
throw e;
}
catch (Exception e) {
throw new StripesServletException(
"Unhandled exception trying to force initialization of StripesFilter", e);
}
// Blow up if no StripesFilter instance could be acquired or created
if (getStripesFilter() == null) {
String msg = "There is no StripesFilter instance available in the servlet context, "
+ "and DynamicMappingFilter was unable to initialize one. See previous log "
+ "messages for more information.";
log.error(msg);
throw new StripesServletException(msg);
}
}
/**
* Parse the application's {@code web.xml} file and return a DOM {@link Document}.
*
* @throws ParserConfigurationException If thrown by the XML parser
* @throws IOException If thrown by the XML parser
* @throws SAXException If thrown by the XML parser
*/
protected Document parseWebXml() throws SAXException, IOException, ParserConfigurationException {
return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
servletContext.getResourceAsStream("/WEB-INF/web.xml"));
}
/**
* Evaluate an xpath expression against a DOM {@link Node} and return the result.
*
* @param expression The expression to evaluate
* @param source The node against which the expression will be evaluated
* @param returnType One of the constants defined in {@link XPathConstants}
* @return The result returned by {@link XPath#evaluate(String, Object, QName)}
* @throws XPathExpressionException If the xpath expression is invalid
*/
@SuppressWarnings("unchecked")
protected <T> T eval(String expression, Node source, QName returnType)
throws XPathExpressionException {
XPath xpath = XPathFactory.newInstance().newXPath();
return (T) xpath.evaluate(expression, source, returnType);
}
/**
* Get all the URL patterns to which a filter is mapped in {@code web.xml}. This includes direct
* mappings using {@code filter-mapping/url-pattern} and indirect mappings using
* {@code filter-mapping/servlet-name} and {@code servlet-mapping/url-pattern}.
*
* @param filterNode The DOM ({@code <filter>}) {@link Node} containing the filter
* declaration from {@code web.xml}
* @return A list of all the patterns to which the filter is mapped
* @throws XPathExpressionException In case of failure evaluating an xpath expression
*/
protected List<String> getFilterUrlPatterns(Node filterNode) throws XPathExpressionException {
String filterName = eval("filter-name", filterNode, XPathConstants.STRING);
Document document = filterNode.getOwnerDocument();
NodeList urlMappings = eval("/web-app/filter-mapping/filter-name[text()='" + filterName
+ "']/../url-pattern", document, XPathConstants.NODESET);
NodeList servletMappings = eval("/web-app/filter-mapping/filter-name[text()='" + filterName
+ "']/../servlet-name", document, XPathConstants.NODESET);
List<String> patterns = new ArrayList<String>();
if (urlMappings != null && urlMappings.getLength() > 0) {
for (int i = 0; i < urlMappings.getLength(); i++) {
patterns.add(urlMappings.item(i).getTextContent().trim());
}
}
if (servletMappings != null && servletMappings.getLength() > 0) {
for (int i = 0; i < servletMappings.getLength(); i++) {
String servletName = servletMappings.item(i).getTextContent().trim();
urlMappings = eval("/web-app/servlet-mapping/servlet-name[text()='" + servletName
+ "']/../url-pattern", document, XPathConstants.NODESET);
for (int j = 0; j < urlMappings.getLength(); j++) {
patterns.add(urlMappings.item(j).getTextContent().trim());
}
}
}
log.debug("Filter ", filterName, " maps to ", patterns);
return patterns;
}
/**
* Get the initialization parameters for a filter declared in {@code web.xml}.
*
* @param filterNode The DOM ({@code <filter>}) {@link Node} containing the filter
* declaration from {@code web.xml}
* @return A map of parameter names to parameter values
* @throws XPathExpressionException In case of failure evaluation an xpath expression
*/
protected Map<String, String> getFilterParameters(Node filterNode)
throws XPathExpressionException {
Map<String, String> params = new LinkedHashMap<String, String>();
NodeList paramNodes = eval("init-param", filterNode, XPathConstants.NODESET);
for (int i = 0; i < paramNodes.getLength(); i++) {
Node node = paramNodes.item(i);
String key = eval("param-name", node, XPathConstants.STRING);
String value = eval("param-value", node, XPathConstants.STRING);
params.put(key, value);
}
return params;
}
/**
* Create and initialize an instance of {@link StripesFilter} with the given configuration.
*
* @param config The filter configuration
* @throws ServletException If initialization of the filter fails
*/
protected void createStripesFilter(FilterConfig config) throws ServletException {
StripesFilter filter = new StripesFilter();
filter.init(config);
this.stripesFilter = filter;
this.stripesFilterIsInternal = true;
}
/**
* Issue a series of requests in an attempt to force an invocation (and initialization) of
* {@link StripesFilter} in the application context. All patterns will be requested first with
* an internal forward, then an include and finally with a brand new request to the address and
* port returned by {@link HttpServletRequest#getLocalAddr()} and
* {@link HttpServletRequest#getLocalPort()}, respectively.
*
* @param patterns The list of patterns to request, as specified by {@code url-pattern} elements
* in {@code web.xml}
* @param request The current request, required to process a forward or include
* @param response The current response, required to process a forward or include
*/
protected void issueRequests(List<String> patterns, HttpServletRequest request,
HttpServletResponse response) {
// Replace globs in the patterns with a random string
String random = "stripes-dmf-request-" + UUID.randomUUID();
List<String> uris = new ArrayList<String>(patterns.size());
for (String pattern : patterns) {
String uri = pattern.replace("*", random);
if (!uri.startsWith("/"))
uri = "/" + uri;
uris.add(uri);
}
// Set the HTTP method to something generally harmless
HttpServletRequestWrapper req = new HttpServletRequestWrapper(request) {
@Override
public String getMethod() {
return "OPTIONS";
}
};
// Response swallows all output
HttpServletResponseWrapper rsp = new HttpServletResponseWrapper(response) {
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new ServletOutputStream() {
@Override
public void write(int b) throws IOException {
// No output
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
};
}
@Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(getOutputStream());
}
};
// Try forward first
log.info("Found StripesFilter declared and mapped in web.xml but not yet initialized.");
Iterator<String> iterator = uris.iterator();
while (getStripesFilter() == null && iterator.hasNext()) {
String uri = iterator.next();
log.info("Try to force initialization of StripesFilter with forward to ", uri);
try {
initializing = true;
RequestDispatcher dispatcher = servletContext.getRequestDispatcher(uri);
dispatcher.forward(req, rsp);
}
catch (Exception e) {
log.debug(e, "Ignored exception during forward");
}
finally {
initializing = false;
response.reset();
}
}
// If forward failed, try include
iterator = uris.iterator();
while (getStripesFilter() == null && iterator.hasNext()) {
String uri = iterator.next();
log.info("Try to force initialization of StripesFilter with include of ", uri);
try {
initializing = true;
RequestDispatcher dispatcher = servletContext.getRequestDispatcher(uri);
dispatcher.include(req, rsp);
}
catch (Exception e) {
log.debug(e, "Ignored exception during include");
}
finally {
initializing = false;
response.reset();
}
}
// If both forward and include failed, then do something truly abominable ...
iterator = uris.iterator();
while (getStripesFilter() == null && iterator.hasNext()) {
try {
String uri = iterator.next();
log.info("Try to force initialization of StripesFilter with request to ", uri);
requestRemotely(request, uri);
}
catch (Exception e) {
log.debug(e, "Ignored exception during request");
}
}
}
/**
* Issue a new request to a path relative to the request's context. The connection is made to
* the address and port returned by {@link HttpServletRequest#getLocalAddr()} and
* {@link HttpServletRequest#getLocalPort()}, respectively.
*
* @param request The current request
* @param relativePath The context-relative path to request
*/
@SuppressWarnings("unchecked")
public void requestRemotely(HttpServletRequest request, String relativePath) {
HttpURLConnection cxn = null;
try {
// Create a new URL using the current request's protocol, port and context
String protocol = new URL(request.getRequestURL().toString()).getProtocol();
String file = request.getContextPath() + relativePath;
URL url = new URL(protocol, request.getLocalAddr(), request.getLocalPort(), file);
cxn = (HttpURLConnection) url.openConnection();
// Set the HTTP method to something generally harmless
cxn.setRequestMethod("OPTIONS");
// Copy all the request headers to the new request
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String hdr = headerNames.nextElement();
cxn.setRequestProperty(hdr, request.getHeader(hdr));
}
// Set a flag to let DMF know not to process the request
cxn.setRequestProperty(REQ_HEADER_INIT_FLAG, "true");
// Log the HTTP status
log.debug(cxn.getResponseCode(), " ", cxn.getResponseMessage(), " (", cxn
.getContentLength(), " bytes) from ", url);
}
catch (Exception e) {
log.debug(e, "Request failed trying to force initialization of StripesFilter");
}
finally {
try {
if (cxn != null) {
cxn.disconnect();
}
} catch (Exception e) {
// Ignore
}
}
}
}