Struts2核心模块分析

Struts2是基于http协议的一个经典的MVC  web框架,其对Servlet部分进行封装,利用java api的其他部分进行业务逻辑处理。

了解struts2的工作过程,以tomcat容器为例,当来自客户端的请求指向tomcat容器,经过一系列(可选)的过滤器。而struts2中的核心部件FilterDispatcher会对请求进行一系列的包装和处理,分析URI问询ActionMapper指向哪一个Action,处理过程如图所示:

 

1)分析FilterDispatcher

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

 FilterDispatcher中的主要部件:ActionMapper   FilterConfig   Dispatcher 

FilterConfig:

Servlet容器中过滤器的配置对象,将配置信息传递给过滤器。提供的方法有:

public String getFilterName()返回过滤器的名称

public String getInitParameter(String name)返回指定初始化名称对应的值,name对应下图代码中的param-name,return param-value标签中的值,如果没有则返回空

<filter>
       <filter-name>encodingFilter</filter-name>
       <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
       <init-param>
           <param-name>encoding</param-name>
           <param-value>UTF-8</param-value>
       </init-param>
       <init-param>
           <param-name>forceEncoding</param-name>
           <param-value>true</param-value>
       </init-param>
   </filter>

public ServletContext getServletContext()返回给调用者一个ServletContext对象

ActionMapper:

此处的ActionMapper是由jee利用@Inject依赖注入,其默认的实现类为DefaultActionMapper,主要方法有:

ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager);

 public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
        ActionMapping mapping = new ActionMapping();
        String uri = RequestUtils.getUri(request);

        int indexOfSemicolon = uri.indexOf(";");
        uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;

        uri = dropExtension(uri, mapping);
        if (uri == null) {
            return null;
        }

        parseNameAndNamespace(uri, mapping, configManager);
        handleSpecialParameters(request, mapping);
        return parseActionName(mapping);
    }

 

ActionMapping getMappingFromActionName(String actionName);

1 public ActionMapping getMappingFromActionName(String actionName) {
2         ActionMapping mapping = new ActionMapping();
3         mapping.setName(actionName);
4         return parseActionName(mapping);
5     }

 

 String getUriFromActionMapping(ActionMapping mapping);

 1 public String getUriFromActionMapping(ActionMapping mapping) {
 2         StringBuilder uri = new StringBuilder();
 3 
 4         handleNamespace(mapping, uri);
 5         handleName(mapping, uri);
 6         handleDynamicMethod(mapping, uri);
 7         handleExtension(mapping, uri);
 8         handleParams(mapping, uri);
 9 
10         return uri.toString();
11     }

FilterDispatcher通过注入的ActionMapper实例对象中,利用请求路径和配置文件对象(通常为struts.xml)去逐一匹配nameSpace,action,method和result。如果匹配不成功便放行,如果匹配成功就利用Dispatcher去处理。

 1  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
 2 
 3         showDeprecatedWarning();
 4 
 5         HttpServletRequest request = (HttpServletRequest) req;
 6         HttpServletResponse response = (HttpServletResponse) res;
 7         ServletContext servletContext = getServletContext();
 8 
 9         String timerKey = "FilterDispatcher_doFilter: ";
10         try {
11 
12             // FIXME: this should be refactored better to not duplicate work with the action invocation
13             ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
14             ActionContext ctx = new ActionContext(stack.getContext());
15             ActionContext.setContext(ctx);
16 
17             UtilTimerStack.push(timerKey);
18             request = prepareDispatcherAndWrapRequest(request, response);
19             ActionMapping mapping;
20             try {
21                 mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());
22             } catch (Exception ex) {
23                 log.error("error getting ActionMapping", ex);
24                 dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
25                 return;
26             }
27 
28             if (mapping == null) {
29                 // there is no action in this request, should we look for a static resource?
30                 String resourcePath = RequestUtils.getServletPath(request);
31 
32                 if ("".equals(resourcePath) && null != request.getPathInfo()) {
33                     resourcePath = request.getPathInfo();
34                 }
35 
36                 if (staticResourceLoader.canHandle(resourcePath)) {
37                     staticResourceLoader.findStaticResource(resourcePath, request, response);
38                 } else {
39                     // this is a normal request, let it pass through
40                     chain.doFilter(request, response);//没有匹配成功
41                 }
42                 // The framework did its job here
43                 return;
44             }
45 
46             dispatcher.serviceAction(request, response, mapping);//匹配成功
47 
48         } finally {
49             dispatcher.cleanUpRequest(request);
50             try {
51                 ActionContextCleanUp.cleanUp(req);
52             } finally {
53                 UtilTimerStack.pop(timerKey);
54             }
55             devModeOverride.remove();
56         }
57     }

Dispatcher

下面贴出Dispatcher的全部代码

  1 /*
  2  * $Id$
  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 com.opensymphony.xwork2.*;
 25 import com.opensymphony.xwork2.config.*;
 26 import com.opensymphony.xwork2.config.entities.InterceptorMapping;
 27 import com.opensymphony.xwork2.config.entities.InterceptorStackConfig;
 28 import com.opensymphony.xwork2.config.entities.PackageConfig;
 29 import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
 30 import com.opensymphony.xwork2.inject.Container;
 31 import com.opensymphony.xwork2.inject.ContainerBuilder;
 32 import com.opensymphony.xwork2.inject.Inject;
 33 import com.opensymphony.xwork2.interceptor.Interceptor;
 34 import com.opensymphony.xwork2.util.ClassLoaderUtil;
 35 import com.opensymphony.xwork2.util.LocalizedTextUtil;
 36 import com.opensymphony.xwork2.util.ValueStack;
 37 import com.opensymphony.xwork2.util.ValueStackFactory;
 38 import com.opensymphony.xwork2.util.location.LocatableProperties;
 39 import com.opensymphony.xwork2.util.location.Location;
 40 import com.opensymphony.xwork2.util.location.LocationUtils;
 41 import com.opensymphony.xwork2.util.logging.Logger;
 42 import com.opensymphony.xwork2.util.logging.LoggerFactory;
 43 import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
 44 import org.apache.struts2.ServletActionContext;
 45 import org.apache.struts2.StrutsConstants;
 46 import org.apache.struts2.StrutsException;
 47 import org.apache.struts2.StrutsStatics;
 48 import org.apache.struts2.config.DefaultBeanSelectionProvider;
 49 import org.apache.struts2.config.DefaultPropertiesProvider;
 50 import org.apache.struts2.config.PropertiesConfigurationProvider;
 51 import org.apache.struts2.config.StrutsXmlConfigurationProvider;
 52 import org.apache.struts2.dispatcher.mapper.ActionMapping;
 53 import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
 54 import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
 55 import org.apache.struts2.util.AttributeMap;
 56 import org.apache.struts2.util.ObjectFactoryDestroyable;
 57 import org.apache.struts2.util.fs.JBossFileManager;
 58 
 59 import javax.servlet.ServletContext;
 60 import javax.servlet.ServletException;
 61 import javax.servlet.http.HttpServletRequest;
 62 import javax.servlet.http.HttpServletResponse;
 63 import java.io.File;
 64 import java.io.IOException;
 65 import java.util.*;
 66 import java.util.concurrent.CopyOnWriteArrayList;
 67 
 68 /**
 69  * A utility class the actual dispatcher delegates most of its tasks to. Each instance
 70  * of the primary dispatcher holds an instance of this dispatcher to be shared for
 71  * all requests.
 72  *
 73  * @see org.apache.struts2.dispatcher.ng.InitOperations
 74  */
 75 public class Dispatcher {
 76 
 77     /**
 78      * Provide a logging instance.
 79      */
 80     private static final Logger LOG = LoggerFactory.getLogger(Dispatcher.class);
 81 
 82     /**
 83      * Provide a thread local instance.
 84      */
 85     private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();
 86 
 87     /**
 88      * Store list of DispatcherListeners.
 89      */
 90     private static List<DispatcherListener> dispatcherListeners =
 91         new CopyOnWriteArrayList<DispatcherListener>();
 92 
 93     /**
 94      * Store ConfigurationManager instance, set on init.
 95      */
 96     private ConfigurationManager configurationManager;
 97 
 98     /**
 99      * Store state of StrutsConstants.STRUTS_DEVMODE setting.
100      */
101     private boolean devMode;
102 
103     /**
104      * Store state of StrutsConstants.DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP setting.
105      */
106     private boolean disableRequestAttributeValueStackLookup;
107 
108     /**
109      * Store state of StrutsConstants.STRUTS_I18N_ENCODING setting.
110      */
111     private String defaultEncoding;
112 
113     /**
114      * Store state of StrutsConstants.STRUTS_LOCALE setting.
115      */
116     private String defaultLocale;
117 
118     /**
119      * Store state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
120      */
121     private String multipartSaveDir;
122 
123     /**
124      * Stores the value of {@link StrutsConstants#STRUTS_MULTIPART_PARSER} setting
125      */
126     private String multipartHandlerName;
127 
128     /**
129      * Provide list of default configuration files.
130      */
131     private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";
132 
133     /**
134      * Store state of STRUTS_DISPATCHER_PARAMETERSWORKAROUND.
135      * <p/>
136      * The workaround is for WebLogic.
137      * We try to autodect WebLogic on Dispatcher init.
138      * The workaround can also be enabled manually.
139      */
140     private boolean paramsWorkaroundEnabled = false;
141 
142     /**
143      * Indicates if Dispatcher should handle exception and call sendError()
144      * Introduced to allow integration with other frameworks like Spring Security
145      */
146     private boolean handleException;
147 
148     /**
149      * Interface used to handle internal errors or missing resources
150      */
151     private DispatcherErrorHandler errorHandler;
152 
153     /**
154      * Provide the dispatcher instance for the current thread.
155      *
156      * @return The dispatcher instance
157      */
158     public static Dispatcher getInstance() {
159         return instance.get();
160     }
161 
162     /**
163      * Store the dispatcher instance for this thread.
164      *
165      * @param instance The instance
166      */
167     public static void setInstance(Dispatcher instance) {
168         Dispatcher.instance.set(instance);
169     }
170 
171     /**
172      * Add a dispatcher lifecycle listener.
173      *
174      * @param listener The listener to add
175      */
176     public static void addDispatcherListener(DispatcherListener listener) {
177         dispatcherListeners.add(listener);
178     }
179 
180     /**
181      * Remove a specific dispatcher lifecycle listener.
182      *
183      * @param listener The listener
184      */
185     public static void removeDispatcherListener(DispatcherListener listener) {
186         dispatcherListeners.remove(listener);
187     }
188 
189     private ValueStackFactory valueStackFactory;
190 
191     /**
192      * Keeps current reference to external world and must be protected to support class inheritance
193      */
194     protected ServletContext servletContext;
195     protected Map<String, String> initParams;
196 
197     /**
198      * Create the Dispatcher instance for a given ServletContext and set of initialization parameters.
199      *
200      * @param servletContext Our servlet context
201      * @param initParams The set of initialization parameters
202      */
203     public Dispatcher(ServletContext servletContext, Map<String, String> initParams) {
204         this.servletContext = servletContext;
205         this.initParams = initParams;
206     }
207 
208     /**
209      * Modify state of StrutsConstants.STRUTS_DEVMODE setting.
210      * @param mode New setting
211      */
212     @Inject(StrutsConstants.STRUTS_DEVMODE)
213     public void setDevMode(String mode) {
214         devMode = "true".equals(mode);
215     }
216 
217     /**
218      * Modify state of StrutsConstants.DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP setting.
219      * @param disableRequestAttributeValueStackLookup New setting
220      */
221     @Inject(value=StrutsConstants.STRUTS_DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP, required=false)
222     public void setDisableRequestAttributeValueStackLookup(String disableRequestAttributeValueStackLookup) {
223         this.disableRequestAttributeValueStackLookup = "true".equalsIgnoreCase(disableRequestAttributeValueStackLookup);
224     }
225 
226     /**
227      * Modify state of StrutsConstants.STRUTS_LOCALE setting.
228      * @param val New setting
229      */
230     @Inject(value=StrutsConstants.STRUTS_LOCALE, required=false)
231     public void setDefaultLocale(String val) {
232         defaultLocale = val;
233     }
234 
235     /**
236      * Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting.
237      * @param val New setting
238      */
239     @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
240     public void setDefaultEncoding(String val) {
241         defaultEncoding = val;
242     }
243 
244     /**
245      * Modify state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
246      * @param val New setting
247      */
248     @Inject(StrutsConstants.STRUTS_MULTIPART_SAVEDIR)
249     public void setMultipartSaveDir(String val) {
250         multipartSaveDir = val;
251     }
252 
253     @Inject(StrutsConstants.STRUTS_MULTIPART_PARSER)
254     public void setMultipartHandler(String val) {
255         multipartHandlerName = val;
256     }
257 
258     @Inject
259     public void setValueStackFactory(ValueStackFactory valueStackFactory) {
260         this.valueStackFactory = valueStackFactory;
261     }
262 
263     @Inject(StrutsConstants.STRUTS_HANDLE_EXCEPTION)
264     public void setHandleException(String handleException) {
265         this.handleException = Boolean.parseBoolean(handleException);
266     }
267 
268     @Inject
269     public void setDispatcherErrorHandler(DispatcherErrorHandler errorHandler) {
270         this.errorHandler = errorHandler;
271     }
272 
273     /**
274      * Releases all instances bound to this dispatcher instance.
275      */
276     public void cleanup() {
277 
278         // clean up ObjectFactory
279         ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class);
280         if (objectFactory == null) {
281             if (LOG.isWarnEnabled()) {
282             LOG.warn("Object Factory is null, something is seriously wrong, no clean up will be performed");
283             }
284         }
285         if (objectFactory instanceof ObjectFactoryDestroyable) {
286             try {
287                 ((ObjectFactoryDestroyable)objectFactory).destroy();
288             }
289             catch(Exception e) {
290                 // catch any exception that may occurred during destroy() and log it
291                 LOG.error("exception occurred while destroying ObjectFactory [#0]", e, objectFactory.toString());
292             }
293         }
294 
295         // clean up Dispatcher itself for this thread
296         instance.set(null);
297 
298         // clean up DispatcherListeners
299         if (!dispatcherListeners.isEmpty()) {
300             for (DispatcherListener l : dispatcherListeners) {
301                 l.dispatcherDestroyed(this);
302             }
303         }
304 
305         // clean up all interceptors by calling their destroy() method
306         Set<Interceptor> interceptors = new HashSet<Interceptor>();
307         Collection<PackageConfig> packageConfigs = configurationManager.getConfiguration().getPackageConfigs().values();
308         for (PackageConfig packageConfig : packageConfigs) {
309             for (Object config : packageConfig.getAllInterceptorConfigs().values()) {
310                 if (config instanceof InterceptorStackConfig) {
311                     for (InterceptorMapping interceptorMapping : ((InterceptorStackConfig) config).getInterceptors()) {
312                         interceptors.add(interceptorMapping.getInterceptor());
313                     }
314                 }
315             }
316         }
317         for (Interceptor interceptor : interceptors) {
318             interceptor.destroy();
319         }
320 
321         // Clear container holder when application is unloaded / server shutdown
322         ContainerHolder.clear();
323 
324         //cleanup action context
325         ActionContext.setContext(null);
326 
327         // clean up configuration
328         configurationManager.destroyConfiguration();
329         configurationManager = null;
330     }
331 
332     private void init_FileManager() throws ClassNotFoundException {
333         if (initParams.containsKey(StrutsConstants.STRUTS_FILE_MANAGER)) {
334             final String fileManagerClassName = initParams.get(StrutsConstants.STRUTS_FILE_MANAGER);
335             final Class<FileManager> fileManagerClass = (Class<FileManager>) Class.forName(fileManagerClassName);
336             if (LOG.isInfoEnabled()) {
337                 LOG.info("Custom FileManager specified: #0", fileManagerClassName);
338             }
339             configurationManager.addContainerProvider(new FileManagerProvider(fileManagerClass, fileManagerClass.getSimpleName()));
340         } else {
341             // add any other Struts 2 provided implementations of FileManager
342             configurationManager.addContainerProvider(new FileManagerProvider(JBossFileManager.class, "jboss"));
343         }
344         if (initParams.containsKey(StrutsConstants.STRUTS_FILE_MANAGER_FACTORY)) {
345             final String fileManagerFactoryClassName = initParams.get(StrutsConstants.STRUTS_FILE_MANAGER_FACTORY);
346             final Class<FileManagerFactory> fileManagerFactoryClass = (Class<FileManagerFactory>) Class.forName(fileManagerFactoryClassName);
347             if (LOG.isInfoEnabled()) {
348                 LOG.info("Custom FileManagerFactory specified: #0", fileManagerFactoryClassName);
349             }
350             configurationManager.addContainerProvider(new FileManagerFactoryProvider(fileManagerFactoryClass));
351         }
352     }
353 
354     private void init_DefaultProperties() {
355         configurationManager.addContainerProvider(new DefaultPropertiesProvider());
356     }
357     
358     private void init_LegacyStrutsProperties() {
359         configurationManager.addContainerProvider(new PropertiesConfigurationProvider());
360     }
361 
362     private void init_TraditionalXmlConfigurations() {
363         String configPaths = initParams.get("config");
364         if (configPaths == null) {
365             configPaths = DEFAULT_CONFIGURATION_PATHS;
366         }
367         String[] files = configPaths.split("\\s*[,]\\s*");
368         for (String file : files) {
369             if (file.endsWith(".xml")) {
370                 if ("xwork.xml".equals(file)) {
371                     configurationManager.addContainerProvider(createXmlConfigurationProvider(file, false));
372                 } else {
373                     configurationManager.addContainerProvider(createStrutsXmlConfigurationProvider(file, false, servletContext));
374                 }
375             } else {
376                 throw new IllegalArgumentException("Invalid configuration file name");
377             }
378         }
379     }
380 
381     protected XmlConfigurationProvider createXmlConfigurationProvider(String filename, boolean errorIfMissing) {
382         return new XmlConfigurationProvider(filename, errorIfMissing);
383     }
384 
385     protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, boolean errorIfMissing, ServletContext ctx) {
386         return new StrutsXmlConfigurationProvider(filename, errorIfMissing, ctx);
387     }
388 
389     private void init_CustomConfigurationProviders() {
390         String configProvs = initParams.get("configProviders");
391         if (configProvs != null) {
392             String[] classes = configProvs.split("\\s*[,]\\s*");
393             for (String cname : classes) {
394                 try {
395                     Class cls = ClassLoaderUtil.loadClass(cname, this.getClass());
396                     ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
397                     configurationManager.addContainerProvider(prov);
398                 } catch (InstantiationException e) {
399                     throw new ConfigurationException("Unable to instantiate provider: "+cname, e);
400                 } catch (IllegalAccessException e) {
401                     throw new ConfigurationException("Unable to access provider: "+cname, e);
402                 } catch (ClassNotFoundException e) {
403                     throw new ConfigurationException("Unable to locate provider class: "+cname, e);
404                 }
405             }
406         }
407     }
408 
409     private void init_FilterInitParameters() {
410         configurationManager.addContainerProvider(new ConfigurationProvider() {
411             public void destroy() {
412             }
413 
414             public void init(Configuration configuration) throws ConfigurationException {
415             }
416 
417             public void loadPackages() throws ConfigurationException {
418             }
419 
420             public boolean needsReload() {
421                 return false;
422             }
423 
424             public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
425                 props.putAll(initParams);
426             }
427         });
428     }
429 
430     private void init_AliasStandardObjects() {
431         configurationManager.addContainerProvider(new DefaultBeanSelectionProvider());
432     }
433 
434     private Container init_PreloadConfiguration() {
435         Container container = getContainer();
436 
437         boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));
438         LocalizedTextUtil.setReloadBundles(reloadi18n);
439 
440         boolean devMode = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_DEVMODE));
441         LocalizedTextUtil.setDevMode(devMode);
442 
443         return container;
444     }
445 
446     private void init_CheckWebLogicWorkaround(Container container) {
447         // test whether param-access workaround needs to be enabled
448         if (servletContext != null && servletContext.getServerInfo() != null
449                 && servletContext.getServerInfo().contains("WebLogic")) {
450             if (LOG.isInfoEnabled()) {
451             LOG.info("WebLogic server detected. Enabling Struts parameter access work-around.");
452             }
453             paramsWorkaroundEnabled = true;
454         } else {
455             paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class,
456                     StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND));
457         }
458     }
459 
460     /**
461      * Load configurations, including both XML and zero-configuration strategies,
462      * and update optional settings, including whether to reload configurations and resource files.
463      */
464     public void init() {
465 
466         if (configurationManager == null) {
467             configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
468         }
469 
470         try {
471             init_FileManager();
472             init_DefaultProperties(); // [1]
473             init_TraditionalXmlConfigurations(); // [2]
474             init_LegacyStrutsProperties(); // [3]
475             init_CustomConfigurationProviders(); // [5]
476             init_FilterInitParameters() ; // [6]
477             init_AliasStandardObjects() ; // [7]
478 
479             Container container = init_PreloadConfiguration();
480             container.inject(this);
481             init_CheckWebLogicWorkaround(container);
482 
483             if (!dispatcherListeners.isEmpty()) {
484                 for (DispatcherListener l : dispatcherListeners) {
485                     l.dispatcherInitialized(this);
486                 }
487             }
488             errorHandler.init(servletContext);
489 
490         } catch (Exception ex) {
491             if (LOG.isErrorEnabled())
492                 LOG.error("Dispatcher initialization failed", ex);
493             throw new StrutsException(ex);
494         }
495     }
496 
497     protected ConfigurationManager createConfigurationManager(String name) {
498         return new ConfigurationManager(name);
499     }
500 
501     /**
502      * @deprecated use version without ServletContext param
503      */
504     @Deprecated
505     public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
506                               ActionMapping mapping) throws ServletException {
507 
508         serviceAction(request, response, mapping);
509     }
510 
511     /**
512      * Load Action class for mapping and invoke the appropriate Action method, or go directly to the Result.
513      * <p/>
514      * This method first creates the action context from the given parameters,
515      * and then loads an <tt>ActionProxy</tt> from the given action name and namespace.
516      * After that, the Action method is executed and output channels through the response object.
517      * Actions not found are sent back to the user via the {@link Dispatcher#sendError} method,
518      * using the 404 return code.
519      * All other errors are reported by throwing a ServletException.
520      *
521      * @param request  the HttpServletRequest object
522      * @param response the HttpServletResponse object
523      * @param mapping  the action mapping object
524      * @throws ServletException when an unknown error occurs (not a 404, but typically something that
525      *                          would end up as a 5xx by the servlet container)
526      *
527      * @since 2.3.17
528      */
529     public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
530             throws ServletException {
531 
532         Map<String, Object> extraContext = createContextMap(request, response, mapping);
533 
534         // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
535         ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
536         boolean nullStack = stack == null;
537         if (nullStack) {
538             ActionContext ctx = ActionContext.getContext();
539             if (ctx != null) {
540                 stack = ctx.getValueStack();
541             }
542         }
543         if (stack != null) {
544             extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
545         }
546 
547         String timerKey = "Handling request from Dispatcher";
548         try {
549             UtilTimerStack.push(timerKey);
550             String namespace = mapping.getNamespace();
551             String name = mapping.getName();
552             String method = mapping.getMethod();
553 
554             ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
555                     namespace, name, method, extraContext, true, false);
556 
557             request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
558 
559             // if the ActionMapping says to go straight to a result, do it!
560             if (mapping.getResult() != null) {
561                 Result result = mapping.getResult();
562                 result.execute(proxy.getInvocation());
563             } else {
564                 proxy.execute();
565             }
566 
567             // If there was a previous value stack then set it back onto the request
568             if (!nullStack) {
569                 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
570             }
571         } catch (ConfigurationException e) {
572             logConfigurationException(request, e);
573             sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
574         } catch (Exception e) {
575             if (handleException || devMode) {
576                 sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
577             } else {
578                 throw new ServletException(e);
579             }
580         } finally {
581             UtilTimerStack.pop(timerKey);
582         }
583     }
584 
585     /**
586      * Performs logging of missing action/result configuration exception
587      *
588      * @param request current {@link HttpServletRequest}
589      * @param e {@link ConfigurationException} that occurred
590      */
591     protected void logConfigurationException(HttpServletRequest request, ConfigurationException e) {
592         // WW-2874 Only log error if in devMode
593         String uri = request.getRequestURI();
594         if (request.getQueryString() != null) {
595             uri = uri + "?" + request.getQueryString();
596         }
597         if (devMode) {
598             LOG.error("Could not find action or result\n#0", e, uri);
599         } else if (LOG.isWarnEnabled()) {
600             LOG.warn("Could not find action or result: #0", e, uri);
601         }
602     }
603 
604     /**
605      * @deprecated use version without servletContext param
606      */
607     @Deprecated
608     public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
609             ActionMapping mapping, ServletContext context) {
610 
611         return createContextMap(request, response, mapping);
612     }
613 
614     /**
615      * Create a context map containing all the wrapped request objects
616      *
617      * @param request The servlet request
618      * @param response The servlet response
619      * @param mapping The action mapping
620      * @return A map of context objects
621      *
622      * @since 2.3.17
623      */
624     public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
625             ActionMapping mapping) {
626 
627         // request map wrapping the http request objects
628         Map requestMap = new RequestMap(request);
629 
630         // parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately
631         Map params = new HashMap(request.getParameterMap());
632 
633         // session map wrapping the http session
634         Map session = new SessionMap(request);
635 
636         // application map wrapping the ServletContext
637         Map application = new ApplicationMap(servletContext);
638 
639         Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response);
640 
641         if (mapping != null) {
642             extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
643         }
644         return extraContext;
645     }
646 
647     /**
648      * @deprecated use version without ServletContext param
649      */
650     @Deprecated
651     public HashMap<String,Object> createContextMap(Map requestMap,
652                                     Map parameterMap,
653                                     Map sessionMap,
654                                     Map applicationMap,
655                                     HttpServletRequest request,
656                                     HttpServletResponse response,
657                                     ServletContext servletContext) {
658 
659         return createContextMap(requestMap, parameterMap, sessionMap, applicationMap, request, response);
660     }
661 
662     /**
663      * Merge all application and servlet attributes into a single <tt>HashMap</tt> to represent the entire
664      * <tt>Action</tt> context.
665      *
666      * @param requestMap     a Map of all request attributes.
667      * @param parameterMap   a Map of all request parameters.
668      * @param sessionMap     a Map of all session attributes.
669      * @param applicationMap a Map of all servlet context attributes.
670      * @param request        the HttpServletRequest object.
671      * @param response       the HttpServletResponse object.
672      * @return a HashMap representing the <tt>Action</tt> context.
673      *
674      * @since 2.3.17
675      */
676     public HashMap<String,Object> createContextMap(Map requestMap,
677                                     Map parameterMap,
678                                     Map sessionMap,
679                                     Map applicationMap,
680                                     HttpServletRequest request,
681                                     HttpServletResponse response) {
682         HashMap<String,Object> extraContext = new HashMap<String,Object>();
683         extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap));
684         extraContext.put(ActionContext.SESSION, sessionMap);
685         extraContext.put(ActionContext.APPLICATION, applicationMap);
686 
687         Locale locale;
688         if (defaultLocale != null) {
689             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
690         } else {
691             locale = request.getLocale();
692         }
693 
694         extraContext.put(ActionContext.LOCALE, locale);
695 
696         extraContext.put(StrutsStatics.HTTP_REQUEST, request);
697         extraContext.put(StrutsStatics.HTTP_RESPONSE, response);
698         extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext);
699 
700         // helpers to get access to request/session/application scope
701         extraContext.put("request", requestMap);
702         extraContext.put("session", sessionMap);
703         extraContext.put("application", applicationMap);
704         extraContext.put("parameters", parameterMap);
705 
706         AttributeMap attrMap = new AttributeMap(extraContext);
707         extraContext.put("attr", attrMap);
708 
709         return extraContext;
710     }
711 
712     /**
713      * Return the path to save uploaded files to (this is configurable).
714      *
715      * @return the path to save uploaded files to
716      */
717     private String getSaveDir() {
718         String saveDir = multipartSaveDir.trim();
719 
720         if (saveDir.equals("")) {
721             File tempdir = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
722             if (LOG.isInfoEnabled()) {
723             LOG.info("Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir");
724             }
725 
726             if (tempdir != null) {
727                 saveDir = tempdir.toString();
728                 setMultipartSaveDir(saveDir);
729             }
730         } else {
731             File multipartSaveDir = new File(saveDir);
732 
733             if (!multipartSaveDir.exists()) {
734                 if (!multipartSaveDir.mkdirs()) {
735                     String logMessage;
736                     try {
737                         logMessage = "Could not find create multipart save directory '" + multipartSaveDir.getCanonicalPath() + "'.";
738                     } catch (IOException e) {
739                         logMessage = "Could not find create multipart save directory '" + multipartSaveDir.toString() + "'.";
740                     }
741                     if (devMode) {
742                         LOG.error(logMessage);
743                     } else {
744                         if (LOG.isWarnEnabled()) {
745                             LOG.warn(logMessage);
746                         }
747                     }
748                 }
749             }
750         }
751 
752         if (LOG.isDebugEnabled()) {
753             LOG.debug("saveDir=" + saveDir);
754         }
755 
756         return saveDir;
757     }
758 
759     /**
760      * Prepare a request, including setting the encoding and locale.
761      *
762      * @param request The request
763      * @param response The response
764      */
765     public void prepare(HttpServletRequest request, HttpServletResponse response) {
766         String encoding = null;
767         if (defaultEncoding != null) {
768             encoding = defaultEncoding;
769         }
770         // check for Ajax request to use UTF-8 encoding strictly http://www.w3.org/TR/XMLHttpRequest/#the-send-method
771         if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
772             encoding = "UTF-8";
773         }
774 
775         Locale locale = null;
776         if (defaultLocale != null) {
777             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
778         }
779 
780         if (encoding != null) {
781             applyEncoding(request, encoding);
782         }
783 
784         if (locale != null) {
785             response.setLocale(locale);
786         }
787 
788         if (paramsWorkaroundEnabled) {
789             request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
790         }
791     }
792 
793     private void applyEncoding(HttpServletRequest request, String encoding) {
794         try {
795             if (!encoding.equals(request.getCharacterEncoding())) {
796                 // if the encoding is already correctly set and the parameters have been already read
797                 // do not try to set encoding because it is useless and will cause an error
798                 request.setCharacterEncoding(encoding);
799             }
800         } catch (Exception e) {
801             LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
802         }
803     }
804 
805     /**
806      * @deprecated use version without ServletContext param
807      */
808     @Deprecated
809     public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
810         return wrapRequest(request);
811     }
812 
813     /**
814      * Wrap and return the given request or return the original request object.
815      * </p>
816      * This method transparently handles multipart data as a wrapped class around the given request.
817      * Override this method to handle multipart requests in a special way or to handle other types of requests.
818      * Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is
819      * flexible - look first to that object before overriding this method to handle multipart data.
820      *
821      * @param request the HttpServletRequest object.
822      * @return a wrapped request or original request.
823      * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper
824      * @throws java.io.IOException on any error.
825      *
826      * @since 2.3.17
827      */
828     public HttpServletRequest wrapRequest(HttpServletRequest request) throws IOException {
829         // don't wrap more than once
830         if (request instanceof StrutsRequestWrapper) {
831             return request;
832         }
833 
834         String content_type = request.getContentType();
835         if (content_type != null && content_type.contains("multipart/form-data")) {
836             MultiPartRequest mpr = getMultiPartRequest();
837             LocaleProvider provider = getContainer().getInstance(LocaleProvider.class);
838             request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider, disableRequestAttributeValueStackLookup);
839         } else {
840             request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
841         }
842 
843         return request;
844     }
845 
846     /**
847      * On each request it must return a new instance as implementation could be not thread safe
848      * and thus ensure of resource clean up
849      *
850      * @return
851      */
852     protected MultiPartRequest getMultiPartRequest() {
853         MultiPartRequest mpr = null;
854         //check for alternate implementations of MultiPartRequest
855         Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class);
856         for (String multiName : multiNames) {
857             if (multiName.equals(multipartHandlerName)) {
858                 mpr = getContainer().getInstance(MultiPartRequest.class, multiName);
859             }
860         }
861         if (mpr == null ) {
862             mpr = getContainer().getInstance(MultiPartRequest.class);
863         }
864         return mpr;
865     }
866 
867     /**
868      * Removes all the files created by MultiPartRequestWrapper.
869      *
870      * @param request the HttpServletRequest object.
871      * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper
872      */
873     public void cleanUpRequest(HttpServletRequest request) {
874         ContainerHolder.clear();
875         if (!(request instanceof MultiPartRequestWrapper)) {
876             return;
877         }
878         MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
879         multiWrapper.cleanUp();
880     }
881 
882     /**
883      * Send an HTTP error response code.
884      *
885      * @param request  the HttpServletRequest object.
886      * @param response the HttpServletResponse object.
887      * @param code     the HttpServletResponse error code (see {@link javax.servlet.http.HttpServletResponse} for possible error codes).
888      * @param e        the Exception that is reported.
889      * @param ctx      the ServletContext object.
890      *
891      * @deprecated remove in version 3.0 - use version without ServletContext parameter
892      */
893     @Deprecated
894     public void sendError(HttpServletRequest request, HttpServletResponse response, ServletContext ctx, int code, Exception e) {
895         sendError(request, response, code, e);
896     }
897 
898     /**
899      * Send an HTTP error response code.
900      *
901      * @param request  the HttpServletRequest object.
902      * @param response the HttpServletResponse object.
903      * @param code     the HttpServletResponse error code (see {@link javax.servlet.http.HttpServletResponse} for possible error codes).
904      * @param e        the Exception that is reported.
905      *
906      * @since 2.3.17
907      */
908     public void sendError(HttpServletRequest request, HttpServletResponse response, int code, Exception e) {
909         errorHandler.handleError(request, response, code, e);
910     }
911 
912     /**
913      * Cleanup any resources used to initialise Dispatcher
914      */
915     public void cleanUpAfterInit() {
916         if (LOG.isDebugEnabled()) {
917             LOG.debug("Cleaning up resources used to init Dispatcher");
918         }
919         ContainerHolder.clear();
920     }
921 
922     /**
923      * Provide an accessor class for static XWork utility.
924      */
925     public static class Locator {
926         public Location getLocation(Object obj) {
927             Location loc = LocationUtils.getLocation(obj);
928             if (loc == null) {
929                 return Location.UNKNOWN;
930             }
931             return loc;
932         }
933     }
934 
935     /**
936      * Expose the ConfigurationManager instance.
937      *
938      * @return The instance
939      */
940     public ConfigurationManager getConfigurationManager() {
941         return configurationManager;
942     }
943 
944     /**
945      * Modify the ConfigurationManager instance
946      *
947      * @param mgr The configuration manager
948      * @deprecated should be removed as is used only in tests
949      */
950     public void setConfigurationManager(ConfigurationManager mgr) {
951         ContainerHolder.clear();
952         this.configurationManager = mgr;
953     }
954 
955     /**
956      * Expose the dependency injection container.
957      * @return Our dependency injection container
958      */
959     public Container getContainer() {
960         if (ContainerHolder.get() != null) {
961             return ContainerHolder.get();
962         }
963         ConfigurationManager mgr = getConfigurationManager();
964         if (mgr == null) {
965             throw new IllegalStateException("The configuration manager shouldn't be null");
966         } else {
967             Configuration config = mgr.getConfiguration();
968             if (config == null) {
969                 throw new IllegalStateException("Unable to load configuration");
970             } else {
971                 Container container = config.getContainer();
972                 ContainerHolder.store(container);
973                 return container;
974             }
975         }
976     }
977 
978 }

上面说到FilterDispatcher利用ActionMapper匹配请求路径成功后便交给Dispatcher.serviceAtion(request, response, mapping)处理,其中mapping包含里被匹配成功的Action中所有的信息,所以可以理解为一个action配置的对象,下面分析Dispatcher.service是如何处理的。

 

 1  public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
 2             throws ServletException {
 3 
 4         Map<String, Object> extraContext = createContextMap(request, response, mapping);
 5 
 6         // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
 7         ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
 8         boolean nullStack = stack == null;
 9         if (nullStack) {
10             ActionContext ctx = ActionContext.getContext();
11             if (ctx != null) {
12                 stack = ctx.getValueStack();
13             }
14         }
15         if (stack != null) {
16             extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
17         }
18 
19         String timerKey = "Handling request from Dispatcher";
20         try {
21             UtilTimerStack.push(timerKey);
22             String namespace = mapping.getNamespace();
23             String name = mapping.getName();
24             String method = mapping.getMethod();
25 
26             ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
27                     namespace, name, method, extraContext, true, false);
28 
29             request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
30 
31             // if the ActionMapping says to go straight to a result, do it!
32             if (mapping.getResult() != null) {
33                 Result result = mapping.getResult();
34                 result.execute(proxy.getInvocation());
35             } else {
36                 proxy.execute();
37             }
38 
39             // If there was a previous value stack then set it back onto the request
40             if (!nullStack) {
41                 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
42             }
43         } catch (ConfigurationException e) {
44             logConfigurationException(request, e);
45             sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
46         } catch (Exception e) {
47             if (handleException || devMode) {
48                 sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
49             } else {
50                 throw new ServletException(e);
51             }
52         } finally {
53             UtilTimerStack.pop(timerKey);
54         }
55     }

 

方法中Dispatcher先利用匹配出的mapping获得action对象的命名空间(nameSpace),actionName,method(22,23,24行),再根据这些信息创建代理对象proxy(26,27行),并执行excute(36行)方法。

 

以上是struts2的FilterDsipatcher的部分执行过程,可以作如下总结:

 

 

 

 以上是对struts2部分执行过程的简单理解,由于能力限制很多代码不能详细分析,只能梳理大致过程,至于view部分放在后面分析。

 

 

 

 

 

posted on 2017-06-20 01:13  繁花烈火  阅读(186)  评论(0)    收藏  举报