diff --git a/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java b/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java index 764e729451..460cab49e0 100644 --- a/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java +++ b/plugins/convention/src/main/java/org/apache/struts2/convention/PackageBasedActionConfigBuilder.java @@ -24,14 +24,23 @@ import com.opensymphony.xwork2.ObjectFactory; import com.opensymphony.xwork2.config.Configuration; import com.opensymphony.xwork2.config.ConfigurationException; -import com.opensymphony.xwork2.config.entities.*; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; import com.opensymphony.xwork2.inject.Container; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.AnnotationUtils; import com.opensymphony.xwork2.util.TextParseUtil; import com.opensymphony.xwork2.util.WildcardHelper; import com.opensymphony.xwork2.util.classloader.ReloadingClassLoader; -import com.opensymphony.xwork2.util.finder.*; +import com.opensymphony.xwork2.util.finder.ClassFinder; +import com.opensymphony.xwork2.util.finder.ClassFinderFactory; +import com.opensymphony.xwork2.util.finder.ClassLoaderInterface; +import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate; +import com.opensymphony.xwork2.util.finder.Test; +import com.opensymphony.xwork2.util.finder.UrlSet; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -39,26 +48,50 @@ import org.apache.logging.log4j.Logger; import org.apache.struts2.StrutsConstants; import org.apache.struts2.StrutsException; -import org.apache.struts2.convention.annotation.*; +import org.apache.struts2.convention.annotation.Action; +import org.apache.struts2.convention.annotation.Actions; import org.apache.struts2.convention.annotation.AllowedMethods; +import org.apache.struts2.convention.annotation.DefaultInterceptorRef; +import org.apache.struts2.convention.annotation.ExceptionMapping; +import org.apache.struts2.convention.annotation.ExceptionMappings; +import org.apache.struts2.convention.annotation.Namespace; +import org.apache.struts2.convention.annotation.Namespaces; +import org.apache.struts2.convention.annotation.ParentPackage; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; /** - *

* This class implements the ActionConfigBuilder interface. - *

*/ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { private static final Logger LOG = LogManager.getLogger(PackageBasedActionConfigBuilder.class); + + private static final String DEFAULT_ACTION_SUFFIX = "Action"; + private static final String DEFAULT_METHOD = "execute"; + private static final boolean EXTRACT_BASE_INTERFACES = true; + /** + * Pattern to match the whole path with sub-path as on JDK9+ getClassLoader().getResources("") + * can return also a sub-path like "!/META-INF/versions/..." + */ + private static final String EXCLUDE_ALL_JARS_PATTERN = ".*?\\.jar(!/|/)?(.*)?"; + private static final String DEFAULT_SPLIT_PATTERN = "\\s*,\\s*"; + private final Configuration configuration; private final ActionNameBuilder actionNameBuilder; private final ResultMapBuilder resultMapBuilder; @@ -66,6 +99,9 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { private final ObjectFactory objectFactory; private final String defaultParentPackage; private final boolean redirectToSlash; + private final Set loadedFileUrls = new HashSet<>(); + private final boolean enableSmiInheritance; + private String[] actionPackages; private String[] excludePackages; private String[] packageLocators; @@ -73,20 +109,17 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { private String packageLocatorsBasePackage; private boolean disableActionScanning = false; private boolean disablePackageLocatorsScanning = false; - private Set actionSuffix = Collections.singleton("Action"); + private Set actionSuffix = Collections.singleton(DEFAULT_ACTION_SUFFIX); private boolean checkImplementsAction = true; private boolean mapAllMatches = false; - private Set loadedFileUrls = new HashSet<>(); + private boolean devMode; private ReloadingClassLoader reloadingClassLoader; private boolean reload; - private Set fileProtocols; + private Set fileProtocols = Collections.emptySet(); private boolean alwaysMapExecute; private boolean excludeParentClassLoader; private boolean slashesInActionNames; - private boolean enableSmiInheritance; - - private static final String DEFAULT_METHOD = "execute"; private boolean eagerLoading = false; private FileManager fileManager; @@ -95,16 +128,17 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder { /** * Constructs actions based on a list of packages. * - * @param configuration The XWork configuration that the new package configs and action configs - * are added to. - * @param container Xwork Container - * @param objectFactory The ObjectFactory used to create the actions and such. - * @param redirectToSlash A boolean parameter that controls whether or not this will create an - * action for indexes. If this is set to true, index actions are not created because - * the unknown handler will redirect from /foo to /foo/. The only action that is created - * is to the empty action in the namespace (e.g. the namespace /foo and the action ""). - * @param enableSmiInheritance A boolean parameter which determines if a newly created package config inherits the SMI value of its parent package config - * @param defaultParentPackage The default parent package for all the configuration. + * @param configuration The XWork configuration that the new package configs and action configs + * are added to. + * @param container Xwork Container + * @param objectFactory The ObjectFactory used to create the actions and such. + * @param redirectToSlash A boolean parameter that controls whether or not this will create an + * action for indexes. If this is set to true, index actions are not created because + * the unknown handler will redirect from /foo to /foo/. The only action that is created + * is to the empty action in the namespace (e.g. the namespace /foo and the action ""). + * @param enableSmiInheritance A boolean parameter, which determines if a newly created package config inherits + * the SMI value of its parent package config + * @param defaultParentPackage The default parent package for all the configuration. */ @Inject public PackageBasedActionConfigBuilder(Configuration configuration, Container container, ObjectFactory objectFactory, @@ -135,7 +169,7 @@ public void setDevMode(String mode) { /** * @param reload Reload configuration when classes change. Defaults to "false" and should not be used - * in production. + * in production. */ @Inject(ConventionConstants.CONVENTION_CLASSES_RELOAD) public void setReload(String reload) { @@ -157,8 +191,8 @@ public void setExcludeParentClassLoader(String exclude) { } /** - * @param alwaysMapExecute If this constant is true, and there is an "execute" method(not annotated), a mapping will be added - * pointing to it, even if there are other mapping in the class + * @param alwaysMapExecute If this constant is true, and there is an "execute" method(not annotated), a mapping will be added + * pointing to it, even if there are other mapping in the class */ @Inject(ConventionConstants.CONVENTION_ACTION_ALWAYS_MAP_EXECUTE) public void setAlwaysMapExecute(String alwaysMapExecute) { @@ -167,6 +201,7 @@ public void setAlwaysMapExecute(String alwaysMapExecute) { /** * File URLs whose protocol are in these list will be processed as jars containing classes + * * @param fileProtocols Comma separated list of file protocols that will be considered as jar files and scanned */ @Inject(ConventionConstants.CONVENTION_ACTION_FILE_PROTOCOLS) @@ -190,7 +225,7 @@ public void setDisableActionScanning(String disableActionScanning) { @Inject(value = ConventionConstants.CONVENTION_ACTION_INCLUDE_JARS, required = false) public void setIncludeJars(String includeJars) { if (StringUtils.isNotEmpty(includeJars)) { - this.includeJars = includeJars.split("\\s*[,]\\s*"); + this.includeJars = includeJars.split(DEFAULT_SPLIT_PATTERN); } } @@ -209,13 +244,13 @@ public void setDisablePackageLocatorsScanning(String disablePackageLocatorsScann @Inject(value = ConventionConstants.CONVENTION_ACTION_PACKAGES, required = false) public void setActionPackages(String actionPackages) { if (StringUtils.isNotBlank(actionPackages)) { - this.actionPackages = actionPackages.split("\\s*[,]\\s*"); + this.actionPackages = actionPackages.split(DEFAULT_SPLIT_PATTERN); } } /** * @param checkImplementsAction (Optional) Map classes that implement com.opensymphony.xwork2.Action - * as actions + * as actions */ @Inject(value = ConventionConstants.CONVENTION_ACTION_CHECK_IMPLEMENTS_ACTION, required = false) public void setCheckImplementsAction(String checkImplementsAction) { @@ -240,7 +275,7 @@ public void setActionSuffix(String actionSuffix) { @Inject(value = ConventionConstants.CONVENTION_EXCLUDE_PACKAGES, required = false) public void setExcludePackages(String excludePackages) { if (StringUtils.isNotBlank(excludePackages)) { - this.excludePackages = excludePackages.split("\\s*[,]\\s*"); + this.excludePackages = excludePackages.split(DEFAULT_SPLIT_PATTERN); } } @@ -249,7 +284,7 @@ public void setExcludePackages(String excludePackages) { */ @Inject(value = ConventionConstants.CONVENTION_PACKAGE_LOCATORS, required = false) public void setPackageLocators(String packageLocators) { - this.packageLocators = packageLocators.split("\\s*[,]\\s*"); + this.packageLocators = packageLocators.split(DEFAULT_SPLIT_PATTERN); } /** @@ -273,7 +308,7 @@ public void setMapAllMatches(String mapAllMatches) { /** * @param eagerLoading (Optional) If set, found action classes will be instantiated by the ObjectFactory to accelerate future use - * setting it up can clash with Spring managed beans + * setting it up can clash with Spring managed beans */ @Inject(value = ConventionConstants.CONVENTION_ACTION_EAGER_LOADING, required = false) public void setEagerLoading(String eagerLoading) { @@ -293,7 +328,7 @@ public void setClassFinderFactory(ClassFinderFactory classFinderFactory) { protected void initReloadClassLoader() { //when the configuration is reloaded, a new classloader will be setup if (isReloadEnabled() && reloadingClassLoader == null) - reloadingClassLoader = new ReloadingClassLoader(getClassLoader()); + reloadingClassLoader = new ReloadingClassLoader(getClassLoader()); } protected ClassLoader getClassLoader() { @@ -324,17 +359,17 @@ public void buildActionConfigs() { if (LOG.isTraceEnabled()) { LOG.trace("Loading action configurations"); if (actionPackages != null) { - LOG.trace("Actions being loaded from action packages: {}", actionPackages); + LOG.trace("Actions being loaded from action packages: {}", (Object[]) actionPackages); } if (packageLocators != null) { - LOG.trace("Actions being loaded using package locator's: {}", packageLocators); + LOG.trace("Actions being loaded using package locator's: {}", (Object[]) packageLocators); } if (excludePackages != null) { - LOG.trace("Excluding actions from packages: {}", excludePackages); + LOG.trace("Excluding actions from packages: {}", (Object[]) excludePackages); } } - Set classes = findActions(); + Set> classes = findActions(); buildConfiguration(classes); } } @@ -342,8 +377,7 @@ public void buildActionConfigs() { protected ClassLoaderInterface getClassLoaderInterface() { if (isReloadEnabled()) { return new ClassLoaderInterfaceDelegate(this.reloadingClassLoader); - } - else { + } else { /* if there is a ClassLoaderInterface in the context, use it, otherwise default to the default ClassLoaderInterface (a wrapper around the current @@ -365,9 +399,8 @@ protected boolean isReloadEnabled() { return devMode && reload; } - @SuppressWarnings("unchecked") - protected Set findActions() { - Set classes = new HashSet<>(); + protected Set> findActions() { + Set> classes = new HashSet<>(); try { if (actionPackages != null || (packageLocators != null && !disablePackageLocatorsScanning)) { @@ -438,30 +471,26 @@ private UrlSet buildUrlSet(List resourceUrls) throws IOException { } //try to find classes dirs inside war files - urlSet = urlSet.includeClassesUrl(classLoaderInterface, new UrlSet.FileProtocolNormalizer() { - public URL normalizeToFileProtocol(URL url) { - return fileManager.normalizeToFileProtocol(url); - } - }); - + urlSet = urlSet.includeClassesUrl(classLoaderInterface, url -> fileManager.normalizeToFileProtocol(url)); urlSet = urlSet.excludeJavaExtDirs() - .excludeJavaEndorsedDirs() - .excludeUserExtensionsDir(); + .excludeJavaEndorsedDirs() + .excludeUserExtensionsDir(); try { - urlSet = urlSet.excludeJavaHome(); + urlSet = urlSet.excludeJavaHome(); } catch (NullPointerException e) { - // This happens in GAE since the sandbox contains no java.home directory - LOG.warn("Could not exclude JAVA_HOME, is this a sandbox jvm?"); + // This happens in GAE since the sandbox contains no java.home directory + LOG.warn("Could not exclude JAVA_HOME, is this a sandbox jvm?"); } urlSet = urlSet.excludePaths(System.getProperty("sun.boot.class.path", "")); urlSet = urlSet.exclude(".*/JavaVM.framework/.*"); if (includeJars == null) { - urlSet = urlSet.exclude(".*?\\.jar(!/|/)?"); + LOG.debug("\"{}\" is not defined, excluding all JAR files!", ConventionConstants.CONVENTION_ACTION_INCLUDE_JARS); + urlSet = urlSet.exclude(EXCLUDE_ALL_JARS_PATTERN); } else { - if(LOG.isDebugEnabled()) { + if (LOG.isDebugEnabled()) { LOG.debug("jar urls regexes were specified: {}", Arrays.asList(includeJars)); } List rawIncludedUrls = urlSet.getUrls(); @@ -509,7 +538,7 @@ public URL normalizeToFileProtocol(URL url) { * * @param className the name of the class to test * @return true if the specified class should be included in the - * package-based action scan + * package-based action scan */ protected boolean includeClassNameInActionScan(String className) { String classPackageName = StringUtils.substringBeforeLast(className, "."); @@ -523,15 +552,15 @@ protected boolean includeClassNameInActionScan(String className) { * @return false if class package is on the {@link #excludePackages} list */ protected boolean checkExcludePackages(String classPackageName) { - if(excludePackages != null && excludePackages.length > 0) { + if (excludePackages != null && excludePackages.length > 0) { WildcardHelper wildcardHelper = new WildcardHelper(); //we really don't care about the results, just the boolean Map matchMap = new HashMap<>(); - for(String packageExclude : excludePackages) { + for (String packageExclude : excludePackages) { int[] packagePattern = wildcardHelper.compilePattern(packageExclude); - if(wildcardHelper.match(matchMap, classPackageName, packagePattern)) { + if (wildcardHelper.match(matchMap, classPackageName, packagePattern)) { return false; } } @@ -564,9 +593,9 @@ protected boolean checkActionPackages(String classPackageName) { * @return true if class package is on the {@link #packageLocators} list */ protected boolean checkPackageLocators(String classPackageName) { - if (packageLocators != null && !disablePackageLocatorsScanning && classPackageName.length() > 0 + if (packageLocators != null && !disablePackageLocatorsScanning && !classPackageName.isEmpty() && (packageLocatorsBasePackage == null || classPackageName - .startsWith(packageLocatorsBasePackage))) { + .startsWith(packageLocatorsBasePackage))) { for (String packageLocator : packageLocators) { String[] splitted = classPackageName.split("\\."); @@ -586,14 +615,10 @@ protected boolean checkPackageLocators(String classPackageName) { * is to return the result of {@link #includeClassNameInActionScan(String)}. * * @return a {@link Test} object that returns true if the specified class - * name should be included in the package scan + * name should be included in the package scan */ protected Test getClassPackageTest() { - return new Test() { - public boolean test(String className) { - return includeClassNameInActionScan(className); - } - }; + return this::includeClassNameInActionScan; } /** @@ -604,7 +629,7 @@ public boolean test(String className) { * super-classes of the specified class. * * @return a {@link Test} object that returns true if the specified class - * should be included in the package scan + * should be included in the package scan */ protected Test getActionClassTest() { return new Test() { @@ -638,8 +663,7 @@ private boolean matchesSuffix(String name) { }; } - @SuppressWarnings("unchecked") - protected void buildConfiguration(Set classes) { + protected void buildConfiguration(Set> classes) { Map packageConfigs = new HashMap<>(); for (Class actionClass : classes) { @@ -754,7 +778,7 @@ protected void buildConfiguration(Set classes) { private Set getAllowedMethods(Class actionClass) { List annotations = AnnotationUtils.findAnnotations(actionClass, AllowedMethods.class); - if (annotations == null || annotations.isEmpty()) { + if (annotations.isEmpty()) { return Collections.emptySet(); } else { Set methods = new HashSet<>(); @@ -767,6 +791,7 @@ private Set getAllowedMethods(Class actionClass) { /** * Interfaces, enums, annotations, and abstract classes cannot be instantiated. + * * @param actionClass class to check * @return returns true if the class cannot be instantiated or should be ignored */ @@ -885,7 +910,7 @@ protected Map> getActionAnnotations(Class actionClass) { } else { Action ann = method.getAnnotation(Action.class); if (ann != null) { - map.put(method.getName(), Arrays.asList(ann)); + map.put(method.getName(), Collections.singletonList(ann)); } } } @@ -894,7 +919,8 @@ protected Map> getActionAnnotations(Class actionClass) { } /** - * Builds a list of actions from an @Actions annotation, and check that they are not all empty + * Builds a list of actions from an @Actions annotation, and check that they are not all empty + * * @param actionsAnnotation Actions annotation * @return a list of Actions */ @@ -926,12 +952,12 @@ protected List checkActionsAnnotation(Actions actionsAnnotation) { */ protected void createActionConfig(PackageConfig.Builder pkgCfg, Class actionClass, String actionName, String actionMethod, Action annotation, Set allowedMethods) { - String className = actionClass.getName(); + String className = actionClass.getName(); if (annotation != null) { actionName = annotation.value().equals(Action.DEFAULT_VALUE) ? actionName : annotation.value(); actionName = StringUtils.contains(actionName, "/") && !slashesInActionNames ? StringUtils.substringAfterLast(actionName, "/") : actionName; - if(!Action.DEFAULT_VALUE.equals(annotation.className())){ - className = annotation.className(); + if (!Action.DEFAULT_VALUE.equals(annotation.className())) { + className = annotation.className(); } } @@ -988,8 +1014,10 @@ protected void createActionConfig(PackageConfig.Builder pkgCfg, Class actionC //watch class file if (isReloadEnabled()) { URL classFile = actionClass.getResource(actionClass.getSimpleName() + ".class"); - fileManager.monitorFile(classFile); - loadedFileUrls.add(classFile.toString()); + if (classFile != null) { + fileManager.monitorFile(classFile); + loadedFileUrls.add(classFile.toString()); + } } } @@ -998,7 +1026,7 @@ protected List buildExceptionMappings(ExceptionMapping[] for (ExceptionMapping exceptionMapping : exceptions) { LOG.trace("Mapping exception [{}] to result [{}] for action [{}]", exceptionMapping.exception(), - exceptionMapping.result(), actionName); + exceptionMapping.result(), actionName); ExceptionMappingConfig.Builder builder = new ExceptionMappingConfig.Builder(null, exceptionMapping .exception(), exceptionMapping.result()); builder.addParams(StringTools.createParameterMap(exceptionMapping.params())); @@ -1009,8 +1037,8 @@ protected List buildExceptionMappings(ExceptionMapping[] } protected PackageConfig.Builder getPackageConfig(final Map packageConfigs, - String actionNamespace, final String actionPackage, final Class actionClass, - Action action) { + String actionNamespace, final String actionPackage, final Class actionClass, + Action action) { if (action != null && !action.value().equals(Action.DEFAULT_VALUE)) { LOG.trace("Using non-default action namespace from the Action annotation of [{}]", action.value()); String actionName = action.value(); @@ -1067,7 +1095,7 @@ protected PackageConfig.Builder getPackageConfig(final Map * 1. Loop over all the namespaces such as /foo and see if it has an action named index * 2. If an action doesn't exists in the parent namespace of the same name, create an action * in the parent namespace of the same name as the namespace that points to the index