View Javadoc
1   /**
2    * Copyright By Grandsoft Company Limited.  
3    * 2012-1-13 下午12:35:48
4    */
5   package gboat2.base.plugin.struts.convention;
6   
7   import gboat2.base.bridge.GboatAppConstants;
8   import gboat2.base.bridge.debug.DefaultDebugHook;
9   import gboat2.base.bridge.util.BundleUtil;
10  
11  import java.io.File;
12  import java.lang.reflect.Method;
13  import java.lang.reflect.Modifier;
14  import java.net.MalformedURLException;
15  import java.util.Map;
16  
17  import javax.servlet.ServletContext;
18  
19  import org.apache.commons.lang3.StringUtils;
20  import org.apache.struts2.ServletActionContext;
21  import org.apache.struts2.convention.ConventionUnknownHandler;
22  import org.apache.struts2.osgi.DefaultBundleAccessor;
23  import org.osgi.framework.Bundle;
24  import org.springframework.util.ReflectionUtils;
25  
26  import com.opensymphony.xwork2.ActionContext;
27  import com.opensymphony.xwork2.ActionInvocation;
28  import com.opensymphony.xwork2.ObjectFactory;
29  import com.opensymphony.xwork2.Result;
30  import com.opensymphony.xwork2.config.Configuration;
31  import com.opensymphony.xwork2.config.entities.ActionConfig;
32  import com.opensymphony.xwork2.config.entities.ResultTypeConfig;
33  import com.opensymphony.xwork2.inject.Container;
34  import com.opensymphony.xwork2.inject.Inject;
35  import com.opensymphony.xwork2.util.logging.Logger;
36  import com.opensymphony.xwork2.util.logging.LoggerFactory;
37  
38  /**
39   * 扩展 Struts2 内置的 {@link ConventionUnknownHandler},使其支持 osgi 环境下 Action 类的加载
40   * @date 2012-1-13
41   * @author lysming
42   * @since 1.0
43   */
44  public class GboatConventionUnknownHandler extends ConventionUnknownHandler {
45  
46      private static final Logger LOG = LoggerFactory.getLogger(GboatConventionUnknownHandler.class);
47      private ActionContext actionContext;
48      private String nameSeparator;
49  
50  	@Inject
51  	public GboatConventionUnknownHandler(Configuration configuration, ObjectFactory objectFactory,
52              ServletContext servletContext, Container container,
53              @Inject("struts.convention.default.parent.package") String defaultParentPackageName,
54              @Inject("struts.convention.redirect.to.slash") String redirectToSlash,
55              @Inject("struts.convention.action.name.separator") String nameSeparator) {
56  	    super(configuration, objectFactory, servletContext, container, defaultParentPackageName, redirectToSlash, nameSeparator);
57  	    this.nameSeparator = nameSeparator;
58      }
59  	
60  	@Override
61  	public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode)  {
62          Result result = super.handleUnknownResult(actionContext, actionName, actionConfig, resultCode);
63          if (result == null) {
64              DefaultBundleAccessor.getInstance().loadResource(actionName);
65          }
66          this.actionContext = actionContext;
67          return result;
68      }
69  
70  	@Override
71      protected Resource findResource(Map<String, ResultTypeConfig> resultsByExtension, String... parts) {
72  	    String pathPrefix = string(parts); 
73          DefaultDebugHook debugHook = DefaultDebugHook.getInstance();
74          
75          ActionInvocation invocation = actionContext.getActionInvocation();
76  	    Bundle bundle = BundleUtil.getBundleForRequestJsp(pathPrefix, invocation, servletContext);
77  	    
78  	    String extendForView = getExtendForView(invocation);
79  	    String[] canonicalPaths = null;
80          boolean traceEnabled = LOG.isTraceEnabled();
81          for (String ext : resultsByExtension.keySet()) {
82              // 如果设置了 extendForView, 则先检查 xxx-extend.jsp 是否存在,不存在的时候再检查 xxx.jsp
83              canonicalPaths = (StringUtils.isBlank(extendForView) ? new String[] { canonicalize(pathPrefix + "." + ext) }
84                      : new String[] { canonicalize(pathPrefix + nameSeparator + extendForView + "." + ext),
85                              canonicalize(pathPrefix + "." + ext) });
86              for (String canonicalPath : canonicalPaths) {
87                  if (bundle != null) {
88                      // 是否为调试模式
89                      boolean isDevMode = debugHook.isBundleDebugEnabled(bundle.getSymbolicName());
90                      if (isDevMode) { // 调试模式下,检查是否存在源代码文件
91                          File sourceFile = new File(debugHook.getSourceFilePath(bundle.getSymbolicName(), canonicalPath));
92                          if (traceEnabled) {
93                              LOG.trace("Checking for [#0]", sourceFile.getAbsolutePath());
94                          }
95                          if (sourceFile.isFile())
96                              return new Resource(canonicalPath, ext);
97                      }
98  
99                      if (traceEnabled) {
100                         LOG.trace("Checking for [#0] from bundle [#1]", canonicalPath, bundle.getSymbolicName());
101                     }
102                     // 检查 Bundle jar 包中是否存在资源文件
103                     if (bundle.getResource(canonicalPath) != null)
104                         return new Resource(canonicalPath, ext);
105                 }
106 
107                 if (traceEnabled) {
108                     LOG.trace("Checking for [#0]", canonicalPath);
109                 }
110                 try {
111                     // 检查 webapp 目录下是否存在资源文件
112                     if (servletContext.getResource(canonicalPath) != null)
113                         return new Resource(canonicalPath, ext);
114                 } catch (MalformedURLException e) {
115                     if (LOG.isErrorEnabled()) {
116                         LOG.error("Unable to parse path to the web application resource [#0] skipping...", canonicalPath);
117                     }
118                 }
119             }
120         }
121         return null;
122     }
123 
124     @Override
125     protected Result findResult(String path, String resultCode, String ext, ActionContext actionContext, Map<String, ResultTypeConfig> resultsByExtension) {
126         DefaultDebugHook debugHook = DefaultDebugHook.getInstance();
127         ActionInvocation invocation = actionContext.getActionInvocation();
128         Bundle bundle = BundleUtil.getBundleForRequestJsp(path, invocation, servletContext);
129         
130         String extendForView = getExtendForView(invocation);
131         String suffix = "." + ext;
132         String[] paths = (StringUtils.isBlank(extendForView) ? new String[] { path } : new String[] {
133                 (StringUtils.removeEnd(path, suffix) + nameSeparator + extendForView + suffix), path });
134         for (String p : paths) {
135             if(bundle != null) {
136                 boolean traceEnabled = LOG.isTraceEnabled();
137                 // 是否为调试模式
138                 boolean isDevMode = debugHook.isBundleDebugEnabled(bundle.getSymbolicName());
139                 if(isDevMode) { // 调试模式下,检查是否存在源代码文件
140                     File sourceFile = new File(debugHook.getSourceFilePath(bundle.getSymbolicName(), p));
141                     if (traceEnabled) {
142                         LOG.trace("Checking for [#0]", sourceFile.getAbsolutePath());
143                     }
144                     if(sourceFile.isFile())
145                         return buildResult(p, resultCode, resultsByExtension.get(ext), actionContext);
146                 }
147                 
148                 if (traceEnabled)
149                     LOG.trace("Checking bundle resource for [#0]", p);
150                 // 检查 Bundle jar 包中是否有对应的资源文件
151                 if (bundle.getEntry(p) != null) 
152                     return buildResult(p, resultCode, resultsByExtension.get(ext), actionContext);
153             }
154             Result result = super.findResult(p, resultCode, ext, actionContext, resultsByExtension);
155             if (result != null) {
156                 return result;
157             }
158         }
159         return null;
160     }
161 
162     @Override
163     protected Result buildResult(String path, String resultCode, ResultTypeConfig config, ActionContext invocationContext) {
164         // 调用父类的方法创建 Result 实例
165         Result result = super.buildResult(path, resultCode, config, invocationContext);
166         /*
167          * 往 request 中设置一个 key 为“javax.servlet.include.servlet_path”的 attribute,
168          * 在调用 org.apache.struts2.components.Include.getContextRelativePath(request, relativePath) 方法的时候需要用到。
169          * 注:使用 <s:include>、<g2:include> 和 <g2:page extend="..."> 标签时均会调用到 Include 类的 getContextRelativePath方法
170          */
171         ServletActionContext.getRequest().setAttribute(GboatAppConstants.INCLUDE_SERVLET_PATH_KEY, path);
172         return result;
173     }
174     
175     private String getExtendForView(ActionInvocation invocation) {
176         Object action = invocation.getAction();
177         Method method = ReflectionUtils.findMethod(action.getClass(), "getExtendForView");
178         // 如果 getExtendForView 方法存在,且非私有, 则访问该方法
179         if (method != null && !Modifier.isPrivate(method.getModifiers())) {
180             if (!method.isAccessible()) {
181                 method.setAccessible(true);
182             }
183             return (String) ReflectionUtils.invokeMethod(method, action);
184         }
185         return null;
186     }
187 }