FileDocCategorySizeDatePackage
FilterDispatcher.javaAPI DocExample21534Mon Jul 23 13:26:38 BST 2007org.apache.struts2.dispatcher

FilterDispatcher

public class FilterDispatcher extends Object implements org.apache.struts2.StrutsStatics, Filter
Master filter for Struts that handles four distinct responsibilities:
  • Executing actions
  • Cleaning up the {@link ActionContext} (see note)
  • Serving static content
  • Kicking off XWork's interceptor chain for the request lifecycle

IMPORTANT: this filter must be mapped to all requests. Unless you know exactly what you are doing, always map to this URL pattern: /*

Executing actions

This filter executes actions by consulting the {@link ActionMapper} and determining if the requested URL should invoke an action. If the mapper indicates it should, the rest of the filter chain is stopped and the action is invoked. This is important, as it means that filters like the SiteMesh filter must be placed before this filter or they will not be able to decorate the output of actions.

Cleaning up the {@link ActionContext}

This filter will also automatically clean up the {@link ActionContext} for you, ensuring that no memory leaks take place. However, this can sometimes cause problems integrating with other products like SiteMesh. See {@link ActionContextCleanUp} for more information on how to deal with this.

Serving static content

This filter also serves common static content needed when using various parts of Struts, such as JavaScript files, CSS files, etc. It works by looking for requests to /struts/*, and then mapping the value after "/struts/" to common packages in Struts and, optionally, in your class path. By default, the following packages are automatically searched:

  • org.apache.struts2.static
  • template

This means that you can simply request /struts/xhtml/styles.css and the XHTML UI theme's default stylesheet will be returned. Likewise, many of the AJAX UI components require various JavaScript files, which are found in the org.apache.struts2.static package. If you wish to add additional packages to be searched, you can add a comma separated (space, tab and new line will do as well) list in the filter init parameter named "packages". Be careful, however, to expose any packages that may have sensitive information, such as properties file with database access credentials.

This filter supports the following init-params:

  • config - a comma-delimited list of XML configuration files to load.
  • actionPackages - a comma-delimited list of Java packages to scan for Actions.
  • configProviders - a comma-delimited list of Java classes that implement the {@link com.opensymphony.xwork2.config.ConfigurationProvider} interface that should be used for building the {@link com.opensymphony.xwork2.config.Configuration}.
  • * - any other parameters are treated as framework constants.

To use a custom {@link Dispatcher}, the createDispatcher() method could be overriden by the subclass.
see
ActionMapper
see
ActionContextCleanUp
version
$Date: 2007-04-19 11:00:20 -0400 (Thu, 19 Apr 2007) $ $Id: FilterDispatcher.java 530439 2007-04-19 15:00:20Z hermanns $

Fields Summary
private static final Log
LOG
Provide a logging instance.
private String[]
pathPrefixes
Store set of path prefixes to use with static resources.
private final Calendar
lastModifiedCal
Provide a formatted date for setting heading information when caching static content.
private static boolean
serveStatic
Store state of StrutsConstants.STRUTS_SERVE_STATIC_CONTENT setting.
private static boolean
serveStaticBrowserCache
Store state of StrutsConstants.STRUTS_SERVE_STATIC_BROWSER_CACHE setting.
private static String
encoding
Store state of StrutsConstants.STRUTS_I18N_ENCODING setting.
private static org.apache.struts2.dispatcher.mapper.ActionMapper
actionMapper
Provide ActionMapper instance, set by injection.
private FilterConfig
filterConfig
Provide FilterConfig instance, set on init.
protected Dispatcher
dispatcher
Expose Dispatcher instance to subclass.
Constructors Summary
Methods Summary
protected voidcopy(java.io.InputStream input, java.io.OutputStream output)
Copy bytes from the input stream to the output stream.

param
input The input stream
param
output The output stream
throws
IOException If anything goes wrong

        final byte[] buffer = new byte[4096];
        int n;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
        }
        output.flush(); // WW-1526
    
protected DispatchercreateDispatcher(javax.servlet.FilterConfig filterConfig)
Create a default {@link Dispatcher} that subclasses can override with a custom Dispatcher, if needed.

param
filterConfig Our FilterConfig
return
Initialized Dispatcher

        Map<String,String> params = new HashMap<String,String>();
        for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements(); ) {
            String name = (String) e.nextElement();
            String value = filterConfig.getInitParameter(name);
            params.put(name, value);
        }
        return new Dispatcher(filterConfig.getServletContext(), params);
    
public voiddestroy()
Calls dispatcher.cleanup, which in turn releases local threads and destroys any DispatchListeners.

see
javax.servlet.Filter#destroy()

        if (dispatcher == null) {
            LOG.warn("something is seriously wrong, Dispatcher is not initialized (null) ");
        } else {
            dispatcher.cleanup();
        }
    
public voiddoFilter(javax.servlet.ServletRequest req, javax.servlet.ServletResponse res, javax.servlet.FilterChain chain)
Process an action or handle a request a static resource.

The filter tries to match the request to an action mapping. If mapping is found, the action processes is delegated to the dispatcher's serviceAction method. If action processing fails, doFilter will try to create an error page via the dispatcher.

Otherwise, if the request is for a static resource, the resource is copied directly to the response, with the appropriate caching headers set.

If the request does not match an action mapping, or a static resource page, then it passes through.

see
javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)



        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        ServletContext servletContext = getServletContext();

        String timerKey = "FilterDispatcher_doFilter: ";
        try {
            UtilTimerStack.push(timerKey);
            request = prepareDispatcherAndWrapRequest(request, response);
            ActionMapping mapping;
            try {
                mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());
            } catch (Exception ex) {
                LOG.error("error getting ActionMapping", ex);
                dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
                return;
            }

            if (mapping == null) {
                // there is no action in this request, should we look for a static resource?
                String resourcePath = RequestUtils.getServletPath(request);

                if ("".equals(resourcePath) && null != request.getPathInfo()) {
                    resourcePath = request.getPathInfo();
                }

                if (serveStatic && resourcePath.startsWith("/struts")) {
                    String name = resourcePath.substring("/struts".length());
                    findStaticResource(name, request, response);
                } else {
                    // this is a normal request, let it pass through
                    chain.doFilter(request, response);
                }
                // The framework did its job here
                return;
            }

            dispatcher.serviceAction(request, response, servletContext, mapping);

        } finally {
            try {
                ActionContextCleanUp.cleanUp(req);
            } finally {
                UtilTimerStack.pop(timerKey);
            }
        }
    
protected java.io.InputStreamfindInputStream(java.lang.String name, java.lang.String packagePrefix)
Look for a static resource in the classpath.

param
name The resource name
param
packagePrefix The package prefix to use to locate the resource
return
The inputstream of the resource
throws
IOException If there is a problem locating the resource

        String resourcePath;
        if (packagePrefix.endsWith("/") && name.startsWith("/")) {
            resourcePath = packagePrefix + name.substring(1);
        } else {
            resourcePath = packagePrefix + name;
        }

        resourcePath = URLDecoder.decode(resourcePath, encoding);

        return ClassLoaderUtil.getResourceAsStream(resourcePath, getClass());
    
protected voidfindStaticResource(java.lang.String name, javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response)
Locate a static resource and copy directly to the response, setting the appropriate caching headers.

param
name The resource name
param
request The request
param
response The response
throws
IOException If anything goes wrong

        if (!name.endsWith(".class")) {
            for (String pathPrefix : pathPrefixes) {
                InputStream is = findInputStream(name, pathPrefix);
                if (is != null) {
                    Calendar cal = Calendar.getInstance();
                    
                    // check for if-modified-since, prior to any other headers
                    long ifModifiedSince = 0;
                    try {
                    	ifModifiedSince = request.getDateHeader("If-Modified-Since");
                    } catch (Exception e) {
                    	LOG.warn("Invalid If-Modified-Since header value: '" + request.getHeader("If-Modified-Since") + "', ignoring");
                    }
    				long lastModifiedMillis = lastModifiedCal.getTimeInMillis();
    				long now = cal.getTimeInMillis();
                    cal.add(Calendar.DAY_OF_MONTH, 1);
                    long expires = cal.getTimeInMillis();
                    
    				if (ifModifiedSince > 0 && ifModifiedSince <= lastModifiedMillis) {
    					// not modified, content is not sent - only basic headers and status SC_NOT_MODIFIED
                        response.setDateHeader("Expires", expires);
    					response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
    					is.close();
    					return;
    				}
                	
                	// set the content-type header
                    String contentType = getContentType(name);
                    if (contentType != null) {
                        response.setContentType(contentType);
                    }

                    if (serveStaticBrowserCache) {
                    	// set heading information for caching static content
                        response.setDateHeader("Date", now);
                        response.setDateHeader("Expires", expires);
                        response.setDateHeader("Retry-After", expires);
                        response.setHeader("Cache-Control", "public");
                        response.setDateHeader("Last-Modified", lastModifiedMillis);
                    } else {
                        response.setHeader("Cache-Control", "no-cache");
                        response.setHeader("Pragma", "no-cache");
                        response.setHeader("Expires", "-1");
                    }

                    try {
                        copy(is, response.getOutputStream());
                    } finally {
                        is.close();
                    }
                    return;
                }
            }
        }

        response.sendError(HttpServletResponse.SC_NOT_FOUND);
    
protected java.lang.StringgetContentType(java.lang.String name)
Determine the content type for the resource name.

param
name The resource name
return
The mime type

        // NOT using the code provided activation.jar to avoid adding yet another dependency
        // this is generally OK, since these are the main files we server up
        if (name.endsWith(".js")) {
            return "text/javascript";
        } else if (name.endsWith(".css")) {
            return "text/css";
        } else if (name.endsWith(".html")) {
            return "text/html";
        } else if (name.endsWith(".txt")) {
            return "text/plain";
        } else if (name.endsWith(".gif")) {
            return "image/gif";
        } else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
            return "image/jpeg";
        } else if (name.endsWith(".png")) {
            return "image/png";
        } else {
            return null;
        }
    
protected javax.servlet.FilterConfiggetFilterConfig()
Expose the FilterConfig instance.

return
Our FilterConfit instance

        return filterConfig;
    
protected javax.servlet.ServletContextgetServletContext()
Provide a workaround for some versions of WebLogic.

Servlet 2.3 specifies that the servlet context can be retrieved from the session. Unfortunately, some versions of WebLogic can only retrieve the servlet context from the filter config. Hence, this method enables subclasses to retrieve the servlet context from other sources.

return
the servlet context.

        return filterConfig.getServletContext();
    
public voidinit(javax.servlet.FilterConfig filterConfig)
Initializes the filter by creating a default dispatcher and setting the default packages for static resources.

param
filterConfig The filter configuration


                              
          
    	 this.filterConfig = filterConfig;
    	 
        dispatcher = createDispatcher(filterConfig);
        dispatcher.init();
       
        String param = filterConfig.getInitParameter("packages");
        String packages = "org.apache.struts2.static template org.apache.struts2.interceptor.debugging";
        if (param != null) {
            packages = param + " " + packages;
        }
        this.pathPrefixes = parse(packages);
    
protected java.lang.String[]parse(java.lang.String packages)
Create a string array from a comma-delimited list of packages.

param
packages A comma-delimited String listing packages
return
A string array of packages

        if (packages == null) {
            return null;
        }
        List<String> pathPrefixes = new ArrayList<String>();

        StringTokenizer st = new StringTokenizer(packages, ", \n\t");
        while (st.hasMoreTokens()) {
            String pathPrefix = st.nextToken().replace('.", '/");
            if (!pathPrefix.endsWith("/")) {
                pathPrefix += "/";
            }
            pathPrefixes.add(pathPrefix);
        }

        return pathPrefixes.toArray(new String[pathPrefixes.size()]);
    
protected javax.servlet.http.HttpServletRequestprepareDispatcherAndWrapRequest(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response)
Wrap and return the given request, if needed, so as to to transparently handle multipart data as a wrapped class around the given request.

param
request Our ServletRequest object
param
response Our ServerResponse object
return
Wrapped HttpServletRequest object
throws
ServletException on any error


        Dispatcher du = Dispatcher.getInstance();

        // Prepare and wrap the request if the cleanup filter hasn't already, cleanup filter should be
        // configured first before struts2 dispatcher filter, hence when its cleanup filter's turn,
        // static instance of Dispatcher should be null.
        if (du == null) {

            Dispatcher.setInstance(dispatcher);

            // prepare the request no matter what - this ensures that the proper character encoding
            // is used before invoking the mapper (see WW-9127)
            dispatcher.prepare(request, response);
        } else {
            dispatcher = du;
        }
        
        try {
            // Wrap request first, just in case it is multipart/form-data
            // parameters might not be accessible through before encoding (ww-1278)
            request = dispatcher.wrapRequest(request, getServletContext());
        } catch (IOException e) {
            String message = "Could not wrap servlet request with MultipartRequestWrapper!";
            LOG.error(message, e);
            throw new ServletException(message, e);
        }

        return request;
    
public static voidsetActionMapper(org.apache.struts2.dispatcher.mapper.ActionMapper mapper)
Modify ActionMapper instance.

param
mapper New instance

        actionMapper = mapper;
    
public static voidsetEncoding(java.lang.String val)
Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting.

param
val New setting

        encoding = val;
    
public static voidsetServeStaticBrowserCache(java.lang.String val)
Modify state of StrutsConstants.STRUTS_SERVE_STATIC_BROWSER_CACHE setting.

param
val New setting

        serveStaticBrowserCache = "true".equals(val);
    
public static voidsetServeStaticContent(java.lang.String val)
Modify state of StrutsConstants.STRUTS_SERVE_STATIC_CONTENT setting.

param
val New setting

        serveStatic = "true".equals(val);