View Javadoc

1   /*
2    * $Id: FilterDispatcher.java 674498 2008-07-07 14:10:42Z mrdon $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts2.dispatcher;
23  
24  import java.io.IOException;
25  import java.util.Enumeration;
26  import java.util.HashMap;
27  import java.util.Map;
28  
29  import javax.servlet.Filter;
30  import javax.servlet.FilterChain;
31  import javax.servlet.FilterConfig;
32  import javax.servlet.ServletContext;
33  import javax.servlet.ServletException;
34  import javax.servlet.ServletRequest;
35  import javax.servlet.ServletResponse;
36  import javax.servlet.http.HttpServletRequest;
37  import javax.servlet.http.HttpServletResponse;
38  
39  import org.apache.struts2.RequestUtils;
40  import org.apache.struts2.StrutsStatics;
41  import org.apache.struts2.dispatcher.mapper.ActionMapper;
42  import org.apache.struts2.dispatcher.mapper.ActionMapping;
43  import org.apache.struts2.dispatcher.ng.filter.FilterHostConfig;
44  import org.apache.struts2.util.ClassLoaderUtils;
45  
46  import com.opensymphony.xwork2.ActionContext;
47  import com.opensymphony.xwork2.config.Configuration;
48  import com.opensymphony.xwork2.config.ConfigurationProvider;
49  import com.opensymphony.xwork2.inject.Inject;
50  import com.opensymphony.xwork2.util.ValueStack;
51  import com.opensymphony.xwork2.util.ValueStackFactory;
52  import com.opensymphony.xwork2.util.logging.Logger;
53  import com.opensymphony.xwork2.util.logging.LoggerFactory;
54  import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
55  
56  /***
57   * Master filter for Struts that handles four distinct
58   * responsibilities:
59   * <p/>
60   * <ul>
61   * <p/>
62   * <li>Executing actions</li>
63   * <p/>
64   * <li>Cleaning up the {@link ActionContext} (see note)</li>
65   * <p/>
66   * <li>Serving static content</li>
67   * <p/>
68   * <li>Kicking off XWork's interceptor chain for the request lifecycle</li>
69   * <p/>
70   * </ul>
71   * <p/>
72   * <p/> <b>IMPORTANT</b>: this filter must be mapped to all requests. Unless you know exactly what you are doing, always
73   * map to this URL pattern: /*
74   * <p/>
75   * <p/> <b>Executing actions</b>
76   * <p/>
77   * <p/> This filter executes actions by consulting the {@link ActionMapper} and determining if the requested URL should
78   * invoke an action. If the mapper indicates it should, <b>the rest of the filter chain is stopped</b> and the action is
79   * invoked. This is important, as it means that filters like the SiteMesh filter must be placed <b>before</b> this
80   * filter or they will not be able to decorate the output of actions.
81   * <p/>
82   * <p/> <b>Cleaning up the {@link ActionContext}</b>
83   * <p/>
84   * <p/> This filter will also automatically clean up the {@link ActionContext} for you, ensuring that no memory leaks
85   * take place. However, this can sometimes cause problems integrating with other products like SiteMesh. See {@link
86   * ActionContextCleanUp} for more information on how to deal with this.
87   * <p/>
88   * <p/> <b>Serving static content</b>
89   * <p/>
90   * <p/> This filter also serves common static content needed when using various parts of Struts, such as JavaScript
91   * files, CSS files, etc. It works by looking for requests to /struts/*, and then mapping the value after "/struts/"
92   * to common packages in Struts and, optionally, in your class path. By default, the following packages are
93   * automatically searched:
94   * <p/>
95   * <ul>
96   * <p/>
97   * <li>org.apache.struts2.static</li>
98   * <p/>
99   * <li>template</li>
100  * <p/>
101  * </ul>
102  * <p/>
103  * <p/> This means that you can simply request /struts/xhtml/styles.css and the XHTML UI theme's default stylesheet
104  * will be returned. Likewise, many of the AJAX UI components require various JavaScript files, which are found in the
105  * org.apache.struts2.static package. If you wish to add additional packages to be searched, you can add a comma
106  * separated (space, tab and new line will do as well) list in the filter init parameter named "packages". <b>Be
107  * careful</b>, however, to expose any packages that may have sensitive information, such as properties file with
108  * database access credentials.
109  * <p/>
110  * <p/>
111  * <p/>
112  * <p>
113  * <p/>
114  * This filter supports the following init-params:
115  * <!-- START SNIPPET: params -->
116  * <p/>
117  * <ul>
118  * <p/>
119  * <li><b>config</b> - a comma-delimited list of XML configuration files to load.</li>
120  * <p/>
121  * <li><b>actionPackages</b> - a comma-delimited list of Java packages to scan for Actions.</li>
122  * <p/>
123  * <li><b>configProviders</b> - a comma-delimited list of Java classes that implement the
124  * {@link ConfigurationProvider} interface that should be used for building the {@link Configuration}.</li>
125  * <p/>
126  * <li><b>loggerFactory</b> - The class name of the {@link LoggerFactory} implementation.</li>
127  * <p/>
128  * <li><b>*</b> - any other parameters are treated as framework constants.</li>
129  * <p/>
130  * </ul>
131  * <p/>
132  * <!-- END SNIPPET: params -->
133  * <p/>
134  * </p>
135  * <p/>
136  * To use a custom {@link Dispatcher}, the <code>createDispatcher()</code> method could be overriden by
137  * the subclass.
138  *
139  * @version $Date: 2008-07-07 10:10:42 -0400 (Mon, 07 Jul 2008) $ $Id: FilterDispatcher.java 674498 2008-07-07 14:10:42Z mrdon $
140  * @deprecated Since Struts 2.1.3, use {@link org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter} instead or
141  * {@link org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter} and {@link org.apache.struts2.dispatcher.ng.filter.StrutsExecuteFilter}
142  * if needing using the {@link ActionContextCleanUp} filter in addition to this one
143  *
144  * @see ActionMapper
145  * @see ActionContextCleanUp
146  * @see org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
147  * @see org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter
148  * @see org.apache.struts2.dispatcher.ng.filter.StrutsExecuteFilter
149  */
150 public class FilterDispatcher implements StrutsStatics, Filter {
151 
152     /***
153      * Provide a logging instance.
154      */
155     private Logger log;
156 
157     /***
158      * Provide ActionMapper instance, set by injection.
159      */
160     private ActionMapper actionMapper;
161 
162     /***
163      * Provide FilterConfig instance, set on init.
164      */
165     private FilterConfig filterConfig;
166 
167     /***
168      * Expose Dispatcher instance to subclass.
169      */
170     protected Dispatcher dispatcher;
171 
172     /***
173      * Loads stattic resources, set by injection
174      */
175     protected StaticContentLoader staticResourceLoader;
176 
177     /***
178      * Initializes the filter by creating a default dispatcher
179      * and setting the default packages for static resources.
180      *
181      * @param filterConfig The filter configuration
182      */
183     public void init(FilterConfig filterConfig) throws ServletException {
184         try {
185             this.filterConfig = filterConfig;
186 
187             initLogging();
188 
189             dispatcher = createDispatcher(filterConfig);
190             dispatcher.init();
191             dispatcher.getContainer().inject(this);
192 
193             staticResourceLoader.setHostConfig(new FilterHostConfig(filterConfig));
194         } finally {
195             ActionContext.setContext(null);
196         }
197     }
198 
199     private void initLogging() {
200         String factoryName = filterConfig.getInitParameter("loggerFactory");
201         if (factoryName != null) {
202             try {
203                 Class cls = ClassLoaderUtils.loadClass(factoryName, this.getClass());
204                 LoggerFactory fac = (LoggerFactory) cls.newInstance();
205                 LoggerFactory.setLoggerFactory(fac);
206             } catch (InstantiationException e) {
207                 System.err.println("Unable to instantiate logger factory: " + factoryName + ", using default");
208                 e.printStackTrace();
209             } catch (IllegalAccessException e) {
210                 System.err.println("Unable to access logger factory: " + factoryName + ", using default");
211                 e.printStackTrace();
212             } catch (ClassNotFoundException e) {
213                 System.err.println("Unable to locate logger factory class: " + factoryName + ", using default");
214                 e.printStackTrace();
215             }
216         }
217 
218         log = LoggerFactory.getLogger(FilterDispatcher.class);
219 
220     }
221 
222     /***
223      * Calls dispatcher.cleanup,
224      * which in turn releases local threads and destroys any DispatchListeners.
225      *
226      * @see javax.servlet.Filter#destroy()
227      */
228     public void destroy() {
229         if (dispatcher == null) {
230             log.warn("something is seriously wrong, Dispatcher is not initialized (null) ");
231         } else {
232             try {
233                 dispatcher.cleanup();
234             } finally {
235                 ActionContext.setContext(null);
236             }
237         }
238     }
239 
240     /***
241      * Create a default {@link Dispatcher} that subclasses can override
242      * with a custom Dispatcher, if needed.
243      *
244      * @param filterConfig Our FilterConfig
245      * @return Initialized Dispatcher
246      */
247     protected Dispatcher createDispatcher(FilterConfig filterConfig) {
248         Map<String, String> params = new HashMap<String, String>();
249         for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements();) {
250             String name = (String) e.nextElement();
251             String value = filterConfig.getInitParameter(name);
252             params.put(name, value);
253         }
254         return new Dispatcher(filterConfig.getServletContext(), params);
255     }
256 
257     /***
258      * Modify state of StrutsConstants.STRUTS_STATIC_CONTENT_LOADER setting.
259      * @param staticResourceLoader val New setting
260      */
261     @Inject
262     public void setStaticResourceLoader(StaticContentLoader staticResourceLoader) {
263         this.staticResourceLoader = staticResourceLoader;
264     }
265 
266     /***
267      * Modify ActionMapper instance.
268      * @param mapper New instance
269      */
270     @Inject
271     public void setActionMapper(ActionMapper mapper) {
272         actionMapper = mapper;
273     }
274 
275     /***
276      * Provide a workaround for some versions of WebLogic.
277      * <p/>
278      * Servlet 2.3 specifies that the servlet context can be retrieved from the session. Unfortunately, some versions of
279      * WebLogic can only retrieve the servlet context from the filter config. Hence, this method enables subclasses to
280      * retrieve the servlet context from other sources.
281      *
282      * @return the servlet context.
283      */
284     protected ServletContext getServletContext() {
285         return filterConfig.getServletContext();
286     }
287 
288     /***
289      * Expose the FilterConfig instance.
290      *
291      * @return Our FilterConfit instance
292      */
293     protected FilterConfig getFilterConfig() {
294         return filterConfig;
295     }
296 
297     /***
298      * Wrap and return the given request, if needed, so as to to transparently
299      * handle multipart data as a wrapped class around the given request.
300      *
301      * @param request  Our ServletRequest object
302      * @param response Our ServerResponse object
303      * @return Wrapped HttpServletRequest object
304      * @throws ServletException on any error
305      */
306     protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {
307 
308         Dispatcher du = Dispatcher.getInstance();
309 
310         // Prepare and wrap the request if the cleanup filter hasn't already, cleanup filter should be
311         // configured first before struts2 dispatcher filter, hence when its cleanup filter's turn,
312         // static instance of Dispatcher should be null.
313         if (du == null) {
314 
315             Dispatcher.setInstance(dispatcher);
316 
317             // prepare the request no matter what - this ensures that the proper character encoding
318             // is used before invoking the mapper (see WW-9127)
319             dispatcher.prepare(request, response);
320         } else {
321             dispatcher = du;
322         }
323 
324         try {
325             // Wrap request first, just in case it is multipart/form-data
326             // parameters might not be accessible through before encoding (ww-1278)
327             request = dispatcher.wrapRequest(request, getServletContext());
328         } catch (IOException e) {
329             String message = "Could not wrap servlet request with MultipartRequestWrapper!";
330             log.error(message, e);
331             throw new ServletException(message, e);
332         }
333 
334         return request;
335     }
336 
337     /***
338      * Process an action or handle a request a static resource.
339      * <p/>
340      * The filter tries to match the request to an action mapping.
341      * If mapping is found, the action processes is delegated to the dispatcher's serviceAction method.
342      * If action processing fails, doFilter will try to create an error page via the dispatcher.
343      * <p/>
344      * Otherwise, if the request is for a static resource,
345      * the resource is copied directly to the response, with the appropriate caching headers set.
346      * <p/>
347      * If the request does not match an action mapping, or a static resource page,
348      * then it passes through.
349      *
350      * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
351      */
352     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
353 
354         HttpServletRequest request = (HttpServletRequest) req;
355         HttpServletResponse response = (HttpServletResponse) res;
356         ServletContext servletContext = getServletContext();
357 
358         String timerKey = "FilterDispatcher_doFilter: ";
359         try {
360 
361             // FIXME: this should be refactored better to not duplicate work with the action invocation
362             ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
363             ActionContext ctx = new ActionContext(stack.getContext());
364             ActionContext.setContext(ctx);
365 
366             UtilTimerStack.push(timerKey);
367             request = prepareDispatcherAndWrapRequest(request, response);
368             ActionMapping mapping;
369             try {
370                 mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());
371             } catch (Exception ex) {
372                 log.error("error getting ActionMapping", ex);
373                 dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
374                 return;
375             }
376 
377             if (mapping == null) {
378                 // there is no action in this request, should we look for a static resource?
379                 String resourcePath = RequestUtils.getServletPath(request);
380 
381                 if ("".equals(resourcePath) && null != request.getPathInfo()) {
382                     resourcePath = request.getPathInfo();
383                 }
384 
385                 if (staticResourceLoader.canHandle(resourcePath)) {
386                     staticResourceLoader.findStaticResource(resourcePath, request, response);
387                 } else {
388                     // this is a normal request, let it pass through
389                     chain.doFilter(request, response);
390                 }
391                 // The framework did its job here
392                 return;
393             }
394 
395             dispatcher.serviceAction(request, response, servletContext, mapping);
396 
397         } finally {
398             try {
399                 ActionContextCleanUp.cleanUp(req);
400             } finally {
401                 UtilTimerStack.pop(timerKey);
402             }
403         }
404     }
405 }