1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
311
312
313 if (du == null) {
314
315 Dispatcher.setInstance(dispatcher);
316
317
318
319 dispatcher.prepare(request, response);
320 } else {
321 dispatcher = du;
322 }
323
324 try {
325
326
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
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
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
389 chain.doFilter(request, response);
390 }
391
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 }