View Javadoc
1   /**
2    * copyright by Grandsoft Company Limited.  
3    * 2012-1-4
4    */
5   /**    
6    * 文件名:GboatPackageBasedActionConfigBuilder.java    
7    *    
8    * 版本信息:    
9    * 日期:2012-1-4    
10   * Copyright 广联达软件股份有限公司 2012     
11   * 版权所有    
12   *    
13   */
14  package gboat2.base.plugin.struts.convention;
15  
16  import java.io.IOException;
17  import java.net.URL;
18  import java.util.ArrayList;
19  import java.util.HashSet;
20  import java.util.List;
21  import java.util.Set;
22  import java.util.regex.Pattern;
23  
24  import org.apache.commons.lang3.StringUtils;
25  import org.apache.struts2.convention.PackageBasedActionConfigBuilder;
26  
27  import com.opensymphony.xwork2.FileManager;
28  import com.opensymphony.xwork2.FileManagerFactory;
29  import com.opensymphony.xwork2.ObjectFactory;
30  import com.opensymphony.xwork2.config.Configuration;
31  import com.opensymphony.xwork2.inject.Container;
32  import com.opensymphony.xwork2.inject.Inject;
33  import com.opensymphony.xwork2.util.TextParseUtil;
34  import com.opensymphony.xwork2.util.finder.ClassFinder;
35  import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
36  import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate;
37  import com.opensymphony.xwork2.util.finder.Test;
38  import com.opensymphony.xwork2.util.finder.UrlSet;
39  import com.opensymphony.xwork2.util.logging.Logger;
40  import com.opensymphony.xwork2.util.logging.LoggerFactory;
41  
42  /**
43   * 重载 Struts2 内置的 {@link PackageBasedActionConfigBuilder#findActions()},
44   * 解决 OSGI bundle 内部 Action 初始化时寻找父类 BaseActionSupport 的问题。<br>
45   * 使用方法为在 struts.xml 中加入如下配置:<br><code>
46   * &lt;bean name="gboat2" type="org.apache.struts2.convention.ActionConfigBuilder" class="gboat2.base.plugin.struts.GboatPackageBasedActionConfigBuilder" /&gt;<br>
47   * &lt;constant name="struts.convention.actionConfigBuilder" value="gboat2" /&gt;</code>
48   * @author lysming
49   * @since 1.0
50   * @see org.apache.struts2.convention.PackageBasedActionConfigBuilder.java
51   */
52  public class GboatPackageBasedActionConfigBuilder extends PackageBasedActionConfigBuilder {
53  
54  	private static final Logger LOG = LoggerFactory.getLogger(PackageBasedActionConfigBuilder.class);
55  
56  	private String[] actionPackages;
57  
58  	private String[] packageLocators;
59  
60  	private boolean disablePackageLocatorsScanning = false;
61  
62  	private Set<String> fileProtocols;
63  
64  	private boolean excludeParentClassLoader;
65  
66  	private String[] includeJars;
67  
68  	private FileManager fileManager;
69  
70  	/**
71  	 * Constructs actions based on a list of packages.
72  	 * 
73  	 * @param configuration
74  	 *            The XWork configuration that the new package configs and
75  	 *            action configs are added to.
76  	 * @param container
77  	 *            Xwork Container
78  	 * @param objectFactory
79  	 *            The ObjectFactory used to create the actions and such.
80  	 * @param redirectToSlash
81  	 *            A boolean parameter that controls whether or not this will
82  	 *            create an action for indexes. If this is set to true, index
83  	 *            actions are not created because the unknown handler will
84  	 *            redirect from /foo to /foo/. The only action that is created
85  	 *            is to the empty action in the namespace (e.g. the namespace
86  	 *            /foo and the action "").
87  	 * @param defaultParentPackage
88  	 *            The default parent package for all the configuration.
89  	 */
90  	@Inject
91  	public GboatPackageBasedActionConfigBuilder(Configuration configuration,
92  	        Container container, ObjectFactory objectFactory,
93  			@Inject("struts.convention.redirect.to.slash") String redirectToSlash,
94  			@Inject("struts.convention.default.parent.package") String defaultParentPackage) {
95  		super(configuration, container, objectFactory, redirectToSlash, defaultParentPackage);
96  	}
97  
98  	@Inject
99  	public void setFileManagerFactory(FileManagerFactory fileManagerFactory) {
100 		this.fileManager = fileManagerFactory.getFileManager();
101 	}
102 
103 	@SuppressWarnings("rawtypes")
104     @Override
105     protected Set<Class> findActions() {
106         Set<Class> classes = new HashSet<Class>();
107         try {
108             if (actionPackages != null
109                     || (packageLocators != null && !disablePackageLocatorsScanning)) {
110                 // 该处传递的 extractBaseInterfaces 为 false
111                 Test<String> classPackageTest = getClassPackageTest();
112                 ClassFinder finder = new ClassFinder(getClassLoaderInterface(),
113                         buildUrlSet().getUrls(), false, this.fileProtocols, classPackageTest);
114                 Test<ClassFinder.ClassInfo> test = getActionClassTest();
115                 classes.addAll(finder.findClasses(test));
116             }
117         } catch (Exception ex) {
118             if (LOG.isErrorEnabled())
119                 LOG.error("Unable to scan named packages", ex);
120         }
121         return classes;
122     }
123 
124 	/**
125 	 * 同PackageBasedActionConfigBuilder的实现一致
126 	 * 
127 	 * @return UrlSet
128 	 * @throws IOException
129 	 *             io异常
130 	 */
131 	private UrlSet buildUrlSet() throws IOException {
132 		ClassLoaderInterface classLoaderInterface = getClassLoaderInterface();
133 		// 检查是否存在META-INF
134 		URL mi = classLoaderInterface.getResource("META-INF");
135 		if (mi == null) {
136 			LOG.error("META-INF can't be found in {}", classLoaderInterface.toString());
137 			return new UrlSet(new ArrayList<URL>());
138 		}
139 		UrlSet urlSet = new UrlSet(classLoaderInterface, this.fileProtocols);
140 
141 		// excluding the urls found by the parent class loader is desired, but
142 		// fails in JBoss (all urls are removed)
143 		if (excludeParentClassLoader) {
144 			// exclude parent of classloaders
145 			ClassLoaderInterface parent = classLoaderInterface.getParent();
146 			// if reload is enabled, we need to step up one level, otherwise the
147 			// UrlSet will be empty
148 			// this happens because the parent of the realoding class loader is
149 			// the web app classloader
150 			if (parent != null && isReloadEnabled())
151 				parent = parent.getParent();
152 
153 			if (parent != null)
154 				urlSet = urlSet.exclude(parent);
155 
156 			try {
157 				// This may fail in some sandboxes, ie GAE
158 				ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
159 				urlSet = urlSet.exclude(new ClassLoaderInterfaceDelegate(systemClassLoader.getParent()));
160 
161 			} catch (SecurityException e) {
162 				if (LOG.isWarnEnabled())
163 					LOG.warn("Could not get the system classloader due to security constraints, there may be improper urls left to scan");
164 			}
165 		}
166 
167 		// try to find classes dirs inside war files
168 		urlSet = urlSet.includeClassesUrl(classLoaderInterface, new UrlSet.FileProtocolNormalizer() {
169 			public URL normalizeToFileProtocol(URL url) {
170 				return fileManager.normalizeToFileProtocol(url);
171 			}
172 		});
173 
174 		urlSet = urlSet.excludeJavaExtDirs();
175 		urlSet = urlSet.excludeJavaEndorsedDirs();
176 		try {
177 			urlSet = urlSet.excludeJavaHome();
178 		} catch (NullPointerException e) {
179 			// This happens in GAE since the sandbox contains no java.home
180 			// directory
181 			if (LOG.isWarnEnabled())
182 				LOG.warn("Could not exclude JAVA_HOME, is this a sandbox jvm?");
183 		}
184 		urlSet = urlSet.excludePaths(System.getProperty("sun.boot.class.path", ""));
185 		urlSet = urlSet.exclude(".*/JavaVM.framework/.*");
186 
187 		if (includeJars == null) {
188 			urlSet = urlSet.exclude(".*?\\.jar(!/|/)?");
189 		} else {
190 			// jar urls regexes were specified
191 			List<URL> rawIncludedUrls = urlSet.getUrls();
192 			Set<URL> includeUrls = new HashSet<URL>();
193 			boolean[] patternUsed = new boolean[includeJars.length];
194 
195 			String convertedUrl;
196 			for (URL url : rawIncludedUrls) {
197 				if (fileProtocols.contains(url.getProtocol())) {
198 					// it is a jar file, make sure it macthes at least a url
199 					// regex
200 					for (int i = 0; i < includeJars.length; i++) {
201 						String includeJar = includeJars[i];
202 						convertedUrl = url.toExternalForm().replaceAll("\\\\", "/");
203 						if (Pattern.matches(includeJar, convertedUrl)) {
204 							includeUrls.add(url);
205 							patternUsed[i] = true;
206 							break;
207 						}
208 					}
209 				} else {
210 					// it is not a jar
211 					includeUrls.add(url);
212 				}
213 			}
214 
215 			if (LOG.isWarnEnabled()) {
216 				for (int i = 0; i < patternUsed.length; i++) {
217 					if (!patternUsed[i]) {
218 						LOG.warn("The includeJars pattern [#0] did not match any jars in the classpath", includeJars[i]);
219 					}
220 				}
221 			}
222 			return new UrlSet(includeUrls);
223 		}
224 
225 		return urlSet;
226 	}
227 
228 	/**
229 	 * @param actionPackages
230 	 *            (Optional) An optional list of action packages that this
231 	 *            should create configuration for.
232 	 */
233 	@Inject(value = "struts.convention.action.packages", required = false)
234 	public void setActionPackages(String actionPackages) {
235 		super.setActionPackages(actionPackages);
236 		if (StringUtils.isNotBlank(actionPackages)) {
237 			this.actionPackages = actionPackages.split("\\s*[,]\\s*");
238 		}
239 	}
240 
241 	/**
242 	 * @param packageLocators
243 	 *            (Optional) A list of names used to find action packages.
244 	 */
245 	@Inject(value = "struts.convention.package.locators", required = false)
246 	public void setPackageLocators(String packageLocators) {
247 		super.setPackageLocators(packageLocators);
248 		this.packageLocators = packageLocators.split("\\s*[,]\\s*");
249 	}
250 
251 	/**
252 	 * @param disablePackageLocatorsScanning
253 	 *            If set to true, only the named packages will be scanned
254 	 */
255 	@Inject(value = "struts.convention.package.locators.disable", required = false)
256 	public void setDisablePackageLocatorsScanning(String disablePackageLocatorsScanning) {
257 		super.setDisablePackageLocatorsScanning(disablePackageLocatorsScanning);
258 		this.disablePackageLocatorsScanning = "true".equals(disablePackageLocatorsScanning);
259 	}
260 
261 	/**
262 	 * File URLs whose protocol are in these list will be processed as jars
263 	 * containing classes
264 	 * 
265 	 * @param fileProtocols
266 	 *            Comma separated list of file protocols that will be considered
267 	 *            as jar files and scanned
268 	 */
269 	@Inject("struts.convention.action.fileProtocols")
270 	public void setFileProtocols(String fileProtocols) {
271 		super.setFileProtocols(fileProtocols);
272 		if (StringUtils.isNotBlank(fileProtocols)) {
273 			this.fileProtocols = TextParseUtil.commaDelimitedStringToSet(fileProtocols);
274 		}
275 	}
276 
277 	/**
278 	 * Exclude URLs found by the parent class loader. Defaults to "true", set to
279 	 * true for JBoss
280 	 * 
281 	 * @see org.apache.struts2.convention.PackageBasedActionConfigBuilder#setExcludeParentClassLoader(java.lang.String)
282 	 * @param exclude
283 	 *            例外
284 	 */
285 	@Inject("struts.convention.exclude.parentClassLoader")
286 	public void setExcludeParentClassLoader(String exclude) {
287 		super.setExcludeParentClassLoader(exclude);
288 		this.excludeParentClassLoader = "true".equals(exclude);
289 	}
290 
291 	/**
292 	 * @param includeJars
293 	 *            Comma separated list of regular expressions of jars to be
294 	 *            included.
295 	 */
296 	@Inject(value = "struts.convention.action.includeJars", required = false)
297 	public void setIncludeJars(String includeJars) {
298 		super.setIncludeJars(includeJars);
299 		if (StringUtils.isNotEmpty(includeJars))
300 			this.includeJars = includeJars.split("\\s*[,]\\s*");
301 	}
302 }