diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java index 8c980b724..4591a1b59 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java @@ -18,77 +18,41 @@ */ package com.opensymphony.xwork2.config.providers; -import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.FileManager; import com.opensymphony.xwork2.FileManagerFactory; -import com.opensymphony.xwork2.ObjectFactory; -import com.opensymphony.xwork2.config.BeanSelectionProvider; import com.opensymphony.xwork2.config.Configuration; import com.opensymphony.xwork2.config.ConfigurationException; -import com.opensymphony.xwork2.config.ConfigurationProvider; -import com.opensymphony.xwork2.config.ConfigurationUtil; -import com.opensymphony.xwork2.config.entities.ActionConfig; -import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; -import com.opensymphony.xwork2.config.entities.InterceptorConfig; -import com.opensymphony.xwork2.config.entities.InterceptorMapping; -import com.opensymphony.xwork2.config.entities.InterceptorStackConfig; -import com.opensymphony.xwork2.config.entities.PackageConfig; -import com.opensymphony.xwork2.config.entities.ResultConfig; -import com.opensymphony.xwork2.config.entities.ResultTypeConfig; -import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig; -import com.opensymphony.xwork2.config.impl.LocatableFactory; -import com.opensymphony.xwork2.inject.Container; import com.opensymphony.xwork2.inject.ContainerBuilder; import com.opensymphony.xwork2.inject.Inject; -import com.opensymphony.xwork2.inject.Scope; import com.opensymphony.xwork2.util.ClassLoaderUtil; import com.opensymphony.xwork2.util.ClassPathFinder; import com.opensymphony.xwork2.util.DomHelper; import com.opensymphony.xwork2.util.location.LocatableProperties; -import com.opensymphony.xwork2.util.location.Location; -import com.opensymphony.xwork2.util.location.LocationUtils; -import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.StrutsException; import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Modifier; import java.net.URL; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.Vector; -import java.util.function.Consumer; -import static com.opensymphony.xwork2.util.TextParseUtil.commaDelimitedStringToSet; -import static java.lang.Boolean.parseBoolean; -import static java.lang.Character.isLowerCase; -import static java.lang.Character.toUpperCase; import static java.lang.String.format; import static java.util.Collections.emptyList; -import static org.apache.commons.lang3.StringUtils.defaultString; -import static org.apache.commons.lang3.StringUtils.isNotEmpty; -import static org.apache.commons.lang3.StringUtils.trimToNull; /** - * Looks in the classpath for an XML file, "xwork.xml" by default, + * Looks in the classpath for an XML file, "struts.xml" by default, * and uses it for the XWork configuration. * * @author tmjee @@ -96,334 +60,53 @@ import static org.apache.commons.lang3.StringUtils.trimToNull; * @author Neo * @version $Revision$ */ -public abstract class XmlConfigurationProvider implements ConfigurationProvider { +public abstract class XmlConfigurationProvider extends XmlDocConfigurationProvider { private static final Logger LOG = LogManager.getLogger(XmlConfigurationProvider.class); private final String configFileName; private final Set loadedFileUrls = new HashSet<>(); - private final Map declaredPackages = new HashMap<>(); - - private List documents; private Set includedFileNames; - private ObjectFactory objectFactory; - - private Map dtdMappings = new HashMap<>(); - private Configuration configuration; - - private boolean throwExceptionOnDuplicateBeans = true; - - private FileManager fileManager; - private ValueSubstitutor valueSubstitutor; - - public XmlConfigurationProvider() { - this("struts.xml", true); - } - - public XmlConfigurationProvider(String filename) { - this(filename, true); - } - - public XmlConfigurationProvider(String filename, @Deprecated boolean notUsed) { - this.configFileName = filename; - } - - public void setThrowExceptionOnDuplicateBeans(boolean val) { - this.throwExceptionOnDuplicateBeans = val; - } - - public void setDtdMappings(Map mappings) { - this.dtdMappings = Collections.unmodifiableMap(mappings); - } - - @Inject - public void setObjectFactory(ObjectFactory objectFactory) { - this.objectFactory = objectFactory; - } + protected FileManager fileManager; @Inject public void setFileManagerFactory(FileManagerFactory fileManagerFactory) { this.fileManager = fileManagerFactory.getFileManager(); } - @Inject(required = false) - public void setValueSubstitutor(ValueSubstitutor valueSubstitutor) { - this.valueSubstitutor = valueSubstitutor; + public XmlConfigurationProvider() { + this("struts.xml"); + } + + public XmlConfigurationProvider(String filename) { + this.configFileName = filename; } /** - * Returns an unmodifiable map of DTD mappings - * - * @return map of DTD mappings + * @deprecated since 6.2.0, use {@link #XmlConfigurationProvider(String)} */ - public Map getDtdMappings() { - return dtdMappings; + @Deprecated + public XmlConfigurationProvider(String filename, @Deprecated boolean notUsed) { + this(filename); } + @Override public void init(Configuration configuration) { - this.configuration = configuration; - this.includedFileNames = configuration.getLoadedFileNames(); - loadDocuments(configFileName); - } - - public void destroy() { + super.init(configuration); + includedFileNames = configuration.getLoadedFileNames(); + documents = parseFile(configFileName); } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof XmlConfigurationProvider)) { - return false; - } - XmlConfigurationProvider xmlConfigurationProvider = (XmlConfigurationProvider) o; - return Objects.equals(configFileName, xmlConfigurationProvider.configFileName); + public void loadPackages() throws ConfigurationException { + super.loadPackages(); + documents = emptyList(); } @Override - public int hashCode() { - return ((configFileName != null) ? configFileName.hashCode() : 0); - } - - public static void iterateElementChildren(Document doc, Consumer function) { - iterateElementChildren(doc.getDocumentElement(), function); - } - - public static void iterateElementChildren(Node node, Consumer function) { - iterateChildren(node, childNode -> { - if (!(childNode instanceof Element)) { - return; - } - function.accept((Element) childNode); - }); - } - - public static void iterateChildren(Node node, Consumer function) { - NodeList children = node.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - function.accept(children.item(i)); - } - } - - public static void iterateChildrenByTagName(Element el, String tagName, Consumer function) { - NodeList childrenByTag = el.getElementsByTagName(tagName); - for (int i = 0; i < childrenByTag.getLength(); i++) { - Element childEl = (Element) childrenByTag.item(i); - function.accept(childEl); - } - } - - private void loadDocuments(String configFileName) { - try { - loadedFileUrls.clear(); - documents = loadConfigurationFiles(configFileName, null); - } catch (ConfigurationException e) { - throw e; - } catch (Exception e) { - throw new ConfigurationException("Error loading configuration file " + configFileName, e); - } - } - public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { LOG.trace("Parsing configuration file [{}]", configFileName); - Map loadedBeans = new HashMap<>(); - for (Document doc : documents) { - iterateElementChildren(doc, child -> { - switch (child.getNodeName()) { - case "bean-selection": { - registerBeanSelection(child, containerBuilder, props); - break; - } - case "bean": { - registerBean(child, loadedBeans, containerBuilder); - break; - } - case "constant": { - registerConstant(child, props); - break; - } - case "unknown-handler-stack": - registerUnknownHandlerStack(child); - break; - } - }); - } - } - - protected void registerBeanSelection(Element child, ContainerBuilder containerBuilder, LocatableProperties props) { - String name = child.getAttribute("name"); - String impl = child.getAttribute("class"); - try { - Class classImpl = ClassLoaderUtil.loadClass(impl, getClass()); - if (BeanSelectionProvider.class.isAssignableFrom(classImpl)) { - BeanSelectionProvider provider = (BeanSelectionProvider) classImpl.newInstance(); - provider.register(containerBuilder, props); - } else { - throw new ConfigurationException(format("The bean-provider: name:%s class:%s does not implement %s", name, impl, BeanSelectionProvider.class.getName()), child); - } - } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { - throw new ConfigurationException(format("Unable to load bean-provider: name:%s class:%s", name, impl), e, child); - } - } - - protected void registerBean(Element child, Map loadedBeans, ContainerBuilder containerBuilder) { - String type = child.getAttribute("type"); - String name = child.getAttribute("name"); - String impl = child.getAttribute("class"); - String onlyStatic = child.getAttribute("static"); - String scopeStr = child.getAttribute("scope"); - boolean optional = "true".equals(child.getAttribute("optional")); - Scope scope = Scope.fromString(scopeStr); - - if (name.isEmpty()) { - name = Container.DEFAULT_NAME; - } - - try { - Class classImpl = ClassLoaderUtil.loadClass(impl, getClass()); - Class classType = classImpl; - if (!type.isEmpty()) { - classType = ClassLoaderUtil.loadClass(type, getClass()); - } - if ("true".equals(onlyStatic)) { - // Force loading of class to detect no class def found exceptions - classImpl.getDeclaredClasses(); - containerBuilder.injectStatics(classImpl); - } else { - if (containerBuilder.contains(classType, name)) { - Location loc = LocationUtils.getLocation(loadedBeans.get(classType.getName() + name)); - if (throwExceptionOnDuplicateBeans) { - throw new ConfigurationException(format("Bean type %s with the name %s has already been loaded by %s", classType, name, loc), child); - } - } - - // Force loading of class to detect no class def found exceptions - classImpl.getDeclaredConstructors(); - - LOG.debug("Loaded type: {} name: {} impl: {}", type, name, impl); - containerBuilder.factory(classType, name, new LocatableFactory<>(name, classType, classImpl, scope, child), scope); - } - loadedBeans.put(classType.getName() + name, child); - } catch (Throwable ex) { - if (!optional) { - throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, child); - } else { - LOG.debug("Unable to load optional class: {}", impl); - } - } - } - - protected void registerConstant(Element child, LocatableProperties props) { - String name = child.getAttribute("name"); - String value = child.getAttribute("value"); - - if (valueSubstitutor != null) { - LOG.debug("Substituting value [{}] using [{}]", value, valueSubstitutor.getClass().getName()); - value = valueSubstitutor.substitute(value); - } - - props.setProperty(name, value, child); - } - - protected void registerUnknownHandlerStack(Element child) { - List unknownHandlerStack = new ArrayList<>(); - - iterateChildrenByTagName(child, "unknown-handler-ref", unknownHandler -> { - Location location = LocationUtils.getLocation(unknownHandler); - unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name"), location)); - }); - - if (!unknownHandlerStack.isEmpty()) { - configuration.setUnknownHandlerStack(unknownHandlerStack); - } - } - - public void loadPackages() throws ConfigurationException { - List reloads = new ArrayList<>(); - verifyPackageStructure(); - - for (Document doc : documents) { - iterateElementChildren(doc, child -> { - if ("package".equals(child.getNodeName())) { - PackageConfig cfg = addPackage(child); - if (cfg.isNeedsRefresh()) { - reloads.add(child); - } - } - }); - loadExtraConfiguration(doc); - } - - if (reloads.size() > 0) { - reloadRequiredPackages(reloads); - } - - for (Document doc : documents) { - loadExtraConfiguration(doc); - } - - documents.clear(); - declaredPackages.clear(); - configuration = null; - } - - private void verifyPackageStructure() { - DirectedGraph graph = new DirectedGraph<>(); - - for (Document doc : documents) { - iterateElementChildren(doc, child -> { - if (!"package".equals(child.getNodeName())) { - return; - } - - String packageName = child.getAttribute("name"); - declaredPackages.put(packageName, child); - graph.addNode(packageName); - - String extendsAttribute = child.getAttribute("extends"); - for (String parent : ConfigurationUtil.buildParentListFromString(extendsAttribute)) { - graph.addNode(parent); - graph.addEdge(packageName, parent); - } - }); - } - - CycleDetector detector = new CycleDetector<>(graph); - if (detector.containsCycle()) { - StringBuilder builder = new StringBuilder("The following packages participate in cycles:"); - for (String packageName : detector.getVerticesInCycles()) { - builder.append(" "); - builder.append(packageName); - } - throw new ConfigurationException(builder.toString()); - } - } - - private void reloadRequiredPackages(List reloads) { - if (reloads.isEmpty()) { - return; - } - - List result = new ArrayList<>(); - for (Element pkg : reloads) { - PackageConfig cfg = addPackage(pkg); - if (cfg.isNeedsRefresh()) { - result.add(pkg); - } - } - if (!result.isEmpty() && result.size() != reloads.size()) { - reloadRequiredPackages(result); - return; - } - - // Print out error messages for all misconfigured inheritance packages - for (Element rp : result) { - String parent = rp.getAttribute("extends"); - if (!parent.isEmpty() && ConfigurationUtil.buildParentsFromString(configuration, parent).isEmpty()) { - LOG.error("Unable to find parent packages {}", parent); - } - } + super.register(containerBuilder, props); } /** @@ -432,570 +115,23 @@ public abstract class XmlConfigurationProvider implements ConfigurationProvider * * @return true if the file has been changed since the last time we read it */ + @Override public boolean needsReload() { - for (String url : loadedFileUrls) { - if (fileManager.fileNeedsReloading(url)) { - return true; - } - } - return false; + return loadedFileUrls.stream().anyMatch(url -> fileManager.fileNeedsReloading(url)); } - protected void addAction(Element actionElement, PackageConfig.Builder packageContext) throws ConfigurationException { - String name = actionElement.getAttribute("name"); - String className = actionElement.getAttribute("class"); - //methodName should be null if it's not set - String methodName = trimToNull(actionElement.getAttribute("method")); - Location location = DomHelper.getLocationObject(actionElement); - - if (location == null) { - LOG.warn("Location null for {}", className); - } - - if (!className.isEmpty() && !verifyAction(className, name, location)) { - LOG.error("Unable to verify action [{}] with class [{}], from [{}]", name, className, location); - return; - } - - Map results; + protected List parseFile(String configFileName) { try { - results = buildResults(actionElement, packageContext); + loadedFileUrls.clear(); + return loadConfigurationFiles(configFileName, null); } catch (ConfigurationException e) { - throw new ConfigurationException("Error building results for action " + name + " in namespace " + packageContext.getNamespace(), e, actionElement); - } - - List interceptorList = buildInterceptorList(actionElement, packageContext); - - List exceptionMappings = buildExceptionMappings(actionElement, packageContext); - - Set allowedMethods = buildAllowedMethods(actionElement, packageContext); - - ActionConfig actionConfig = new ActionConfig.Builder(packageContext.getName(), name, className) - .methodName(methodName) - .addResultConfigs(results) - .addInterceptors(interceptorList) - .addExceptionMappings(exceptionMappings) - .addParams(XmlHelper.getParams(actionElement)) - .setStrictMethodInvocation(packageContext.isStrictMethodInvocation()) - .addAllowedMethod(allowedMethods) - .location(location) - .build(); - packageContext.addActionConfig(name, actionConfig); - - LOG.debug("Loaded {}{} in '{}' package: {}", - isNotEmpty(packageContext.getNamespace()) ? (packageContext.getNamespace() + "/") : "", - name, packageContext.getName(), actionConfig); - } - - protected boolean verifyAction(String className, String name, Location loc) { - if (className.contains("{")) { - LOG.debug("Action class [{}] contains a wildcard replacement value, so it can't be verified", className); - return true; - } - try { - if (objectFactory.isNoArgConstructorRequired()) { - Class clazz = objectFactory.getClassInstance(className); - if (!Modifier.isPublic(clazz.getModifiers())) { - throw new ConfigurationException("Action class [" + className + "] is not public", loc); - } - clazz.getConstructor(); - } - } catch (ClassNotFoundException e) { - LOG.debug("Class not found for action [{}]", className, e); - throw new ConfigurationException("Action class [" + className + "] not found", loc); - } catch (NoSuchMethodException e) { - LOG.debug("No constructor found for action [{}]", className, e); - throw new ConfigurationException("Action class [" + className + "] does not have a public no-arg constructor", e, loc); - } catch (RuntimeException ex) { - // Probably not a big deal, like request or session-scoped Spring beans that need a real request - LOG.info("Unable to verify action class [{}] exists at initialization", className); - LOG.debug("Action verification cause", ex); - } catch (Exception ex) { - // Default to failing fast - LOG.debug("Unable to verify action class [{}]", className, ex); - throw new ConfigurationException(ex, loc); - } - return true; - } - - /** - * Create a PackageConfig from an XML element representing it. - * - * @param packageElement the given XML element - * @return the package config - * @throws ConfigurationException in case of configuration errors - */ - protected PackageConfig addPackage(Element packageElement) throws ConfigurationException { - String packageName = packageElement.getAttribute("name"); - PackageConfig packageConfig = configuration.getPackageConfig(packageName); - if (packageConfig != null) { - LOG.debug("Package [{}] already loaded, skipping re-loading it and using existing PackageConfig [{}]", packageName, packageConfig); - return packageConfig; - } - - PackageConfig.Builder newPackage = buildPackageContext(packageElement); - - if (newPackage.isNeedsRefresh()) { - return newPackage.build(); - } - - LOG.debug("Loaded {}", newPackage); - - // add result types (and default result) to this package - addResultTypes(newPackage, packageElement); - - // load the interceptors and interceptor stacks for this package - loadInterceptors(newPackage, packageElement); - - // load the default interceptor reference for this package - loadDefaultInterceptorRef(newPackage, packageElement); - - // load the default class ref for this package - loadDefaultClassRef(newPackage, packageElement); - - // load the global result list for this package - loadGlobalResults(newPackage, packageElement); - - loadGlobalAllowedMethods(newPackage, packageElement); - - // load the global exception handler list for this package - loadGlobalExceptionMappings(newPackage, packageElement); - - // get actions - iterateChildrenByTagName(packageElement, "action", actionElement -> addAction(actionElement, newPackage)); - - // load the default action reference for this package - loadDefaultActionRef(newPackage, packageElement); - - PackageConfig cfg = newPackage.build(); - configuration.addPackageConfig(cfg.getName(), cfg); - return cfg; - } - - protected void addResultTypes(PackageConfig.Builder packageContext, Element element) { - iterateChildrenByTagName(element, "result-type", resultTypeElement -> { - String name = resultTypeElement.getAttribute("name"); - String className = resultTypeElement.getAttribute("class"); - String def = resultTypeElement.getAttribute("default"); - - Location loc = DomHelper.getLocationObject(resultTypeElement); - - Class clazz = verifyResultType(className, loc); - if (clazz == null) { - return; - } - String paramName = null; - try { - paramName = (String) clazz.getField("DEFAULT_PARAM").get(null); - } catch (Throwable t) { - LOG.debug("The result type [{}] doesn't have a default param [DEFAULT_PARAM] defined!", className, t); - } - ResultTypeConfig.Builder resultType = new ResultTypeConfig.Builder(name, className).defaultResultParam(paramName) - .location(DomHelper.getLocationObject(resultTypeElement)); - - Map params = XmlHelper.getParams(resultTypeElement); - - if (!params.isEmpty()) { - resultType.addParams(params); - } - packageContext.addResultTypeConfig(resultType.build()); - - // set the default result type - if (BooleanUtils.toBoolean(def)) { - packageContext.defaultResultType(name); - } - }); - } - - protected Class verifyResultType(String className, Location loc) { - try { - return objectFactory.getClassInstance(className); - } catch (ClassNotFoundException | NoClassDefFoundError e) { - LOG.warn("Result class [{}] doesn't exist ({}) at {}, ignoring", className, e.getClass().getSimpleName(), loc, e); - } - return null; - } - - protected List buildInterceptorList(Element element, PackageConfig.Builder context) throws ConfigurationException { - List interceptorList = new ArrayList<>(); - - iterateChildrenByTagName(element, "interceptor-ref", interceptorRefElement -> { - Node parNode = interceptorRefElement.getParentNode(); - if (!parNode.equals(element) && !parNode.getNodeName().equals(element.getNodeName())) { - return; - } - List interceptors = lookupInterceptorReference(context, interceptorRefElement); - interceptorList.addAll(interceptors); - }); - - return interceptorList; - } - - /** - *

This method builds a package context by looking for the parents of this new package.

- *

If no parents are found, it will return a root package.

- * - * @param packageElement the package element - * @return the package config builder - */ - protected PackageConfig.Builder buildPackageContext(Element packageElement) { - String parent = packageElement.getAttribute("extends"); - String abstractVal = packageElement.getAttribute("abstract"); - boolean isAbstract = parseBoolean(abstractVal); - String name = defaultString(packageElement.getAttribute("name")); - String namespace = defaultString(packageElement.getAttribute("namespace")); - - // Strict DMI is enabled by default, it can be disabled by user - boolean strictDMI = true; - if (packageElement.hasAttribute("strict-method-invocation")) { - strictDMI = parseBoolean(packageElement.getAttribute("strict-method-invocation")); - } - - PackageConfig.Builder cfg = new PackageConfig.Builder(name) - .namespace(namespace) - .isAbstract(isAbstract) - .strictMethodInvocation(strictDMI) - .location(DomHelper.getLocationObject(packageElement)); - - if (parent.isEmpty()) { - return cfg; - } - - // has parents, let's look it up - List parents = new ArrayList<>(); - for (String parentPackageName : ConfigurationUtil.buildParentListFromString(parent)) { - if (configuration.getPackageConfigNames().contains(parentPackageName)) { - parents.add(configuration.getPackageConfig(parentPackageName)); - } else if (declaredPackages.containsKey(parentPackageName)) { - if (configuration.getPackageConfig(parentPackageName) == null) { - addPackage(declaredPackages.get(parentPackageName)); - } - parents.add(configuration.getPackageConfig(parentPackageName)); - } else { - throw new ConfigurationException("Parent package is not defined: " + parentPackageName); - } - - } - - if (parents.isEmpty()) { - cfg.needsRefresh(true); - } else { - cfg.addParents(parents); - } - - return cfg; - } - - /** - * Build a map of ResultConfig objects from below a given XML element. - * - * @param element the given XML element - * @param packageContext the package context - * @return map of result config objects - */ - protected Map buildResults(Element element, PackageConfig.Builder packageContext) { - Map results = new LinkedHashMap<>(); - - iterateChildrenByTagName(element, "result", resultElement -> { - Node parNode = resultElement.getParentNode(); - if (!parNode.equals(element) && !parNode.getNodeName().equals(element.getNodeName())) { - return; - } - - String resultName = resultElement.getAttribute("name"); - String resultType = resultElement.getAttribute("type"); - - // if you don't specify a name on , it defaults to "success" - if (StringUtils.isEmpty(resultName)) { - resultName = Action.SUCCESS; - } - - // there is no result type, so let's inherit from the parent package - if (resultType.isEmpty()) { - resultType = packageContext.getFullDefaultResultType(); - // now check if there is a result type now - if (resultType.isEmpty()) { - throw new ConfigurationException("No result type specified for result named '" - + resultName + "', perhaps the parent package does not specify the result type?", resultElement); - } - } - - ResultTypeConfig config = packageContext.getResultType(resultType); - if (config == null) { - throw new ConfigurationException(format("There is no result type defined for type '%s' mapped with name '%s'. Did you mean '%s'?", resultType, resultName, guessResultType(resultType)), resultElement); - } - - String resultClass = config.getClassName(); - if (resultClass == null) { - throw new ConfigurationException("Result type '" + resultType + "' is invalid"); - } - - Map params = buildResultParams(resultElement, config); - - Set resultNamesSet = commaDelimitedStringToSet(resultName); - if (resultNamesSet.isEmpty()) { - resultNamesSet.add(resultName); - } - - for (String name : resultNamesSet) { - ResultConfig resultConfig = new ResultConfig.Builder(name, resultClass) - .addParams(params) - .location(DomHelper.getLocationObject(element)) - .build(); - results.put(resultConfig.getName(), resultConfig); - } - }); - - return results; - } - - protected Map buildResultParams(Element resultElement, ResultTypeConfig config) { - Map resultParams = XmlHelper.getParams(resultElement); - - // maybe we just have a body - therefore a default parameter - if (resultParams.isEmpty() && resultElement.getChildNodes().getLength() > 0) { - // if something then we add a parameter of 'something' as this is the most used result param - resultParams = new LinkedHashMap<>(); - - String paramName = config.getDefaultResultParam(); - if (paramName != null) { - StringBuilder paramValue = new StringBuilder(); - iterateChildren(resultElement, child -> { - if (child.getNodeType() == Node.TEXT_NODE) { - String val = child.getNodeValue(); - if (val != null) { - paramValue.append(val); - } - } - }); - String val = paramValue.toString().trim(); - if (val.length() > 0) { - resultParams.put(paramName, val); - } - } else { - LOG.debug( - "No default parameter defined for result [{}] of type [{}] ", - config.getName(), - config.getClassName()); - } - } - - // create new param map, so that the result param can override the config param - Map params = new LinkedHashMap<>(); - Map configParams = config.getParams(); - if (configParams != null) { - params.putAll(configParams); - } - params.putAll(resultParams); - return params; - } - - protected static String guessResultType(String type) { - if (type == null) { - return null; - } - StringBuilder sb = new StringBuilder(); - boolean capNext = false; - for (int x = 0; x < type.length(); x++) { - char c = type.charAt(x); - if (c == '-') { - capNext = true; - continue; - } else if (isLowerCase(c) && capNext) { - c = toUpperCase(c); - capNext = false; - } - sb.append(c); - } - return sb.toString(); - } - - /** - * Build a list of exception mapping objects from below a given XML element. - * - * @param element the given XML element - * @param packageContext the package context - * - * @return list of exception mapping config objects - */ - protected List buildExceptionMappings(Element element, PackageConfig.Builder packageContext) { - List exceptionMappings = new ArrayList<>(); - - iterateChildrenByTagName(element, "exception-mapping", ehElement -> { - Node parNode = ehElement.getParentNode(); - if (!parNode.equals(element) && !parNode.getNodeName().equals(element.getNodeName())) { - return; - } - - String emName = ehElement.getAttribute("name"); - String exceptionClassName = ehElement.getAttribute("exception"); - String exceptionResult = ehElement.getAttribute("result"); - Map params = XmlHelper.getParams(ehElement); - if (emName.isEmpty()) { - emName = exceptionResult; - } - - ExceptionMappingConfig ehConfig = new ExceptionMappingConfig.Builder(emName, exceptionClassName, exceptionResult) - .addParams(params) - .location(DomHelper.getLocationObject(ehElement)) - .build(); - exceptionMappings.add(ehConfig); - }); - - return exceptionMappings; - } - - protected Set buildAllowedMethods(Element element, PackageConfig.Builder packageContext) { - NodeList allowedMethodsEls = element.getElementsByTagName("allowed-methods"); - - Set allowedMethods; - if (allowedMethodsEls.getLength() > 0) { - // user defined 'allowed-methods' so used them whatever Strict DMI was enabled or not - allowedMethods = new HashSet<>(packageContext.getGlobalAllowedMethods()); - // Fix for WW-5029 (concatenate all possible text node children) - Node allowedMethodsNode = allowedMethodsEls.item(0); - addAllowedMethodsToSet(allowedMethodsNode, allowedMethods); - } else if (packageContext.isStrictMethodInvocation()) { - // user enabled Strict DMI but didn't define action specific 'allowed-methods' so we use 'global-allowed-methods' only - allowedMethods = new HashSet<>(packageContext.getGlobalAllowedMethods()); - } else { - // Strict DMI is disabled so any method can be called - allowedMethods = new HashSet<>(); - allowedMethods.add(ActionConfig.WILDCARD); - } - - LOG.debug("Collected allowed methods: {}", allowedMethods); - - return Collections.unmodifiableSet(allowedMethods); - } - - protected void loadDefaultInterceptorRef(PackageConfig.Builder packageContext, Element element) { - NodeList resultTypeList = element.getElementsByTagName("default-interceptor-ref"); - - if (resultTypeList.getLength() > 0) { - Element defaultRefElement = (Element) resultTypeList.item(0); - packageContext.defaultInterceptorRef(defaultRefElement.getAttribute("name")); + throw e; + } catch (Exception e) { + throw new ConfigurationException("Error loading configuration file " + configFileName, e); } } - protected void loadDefaultActionRef(PackageConfig.Builder packageContext, Element element) { - NodeList resultTypeList = element.getElementsByTagName("default-action-ref"); - - if (resultTypeList.getLength() > 0) { - Element defaultRefElement = (Element) resultTypeList.item(0); - packageContext.defaultActionRef(defaultRefElement.getAttribute("name")); - } - } - - /** - * Load all the global results for this package from the XML element. - * - * @param packageContext the package context - * @param packageElement the given XML element - */ - protected void loadGlobalResults(PackageConfig.Builder packageContext, Element packageElement) { - NodeList globalResultList = packageElement.getElementsByTagName("global-results"); - - if (globalResultList.getLength() > 0) { - Element globalResultElement = (Element) globalResultList.item(0); - Map results = buildResults(globalResultElement, packageContext); - packageContext.addGlobalResultConfigs(results); - } - } - - protected void loadGlobalAllowedMethods(PackageConfig.Builder packageContext, Element packageElement) { - NodeList globalAllowedMethodsElms = packageElement.getElementsByTagName("global-allowed-methods"); - - if (globalAllowedMethodsElms.getLength() > 0) { - Set globalAllowedMethods = new HashSet<>(); - // Fix for WW-5029 (concatenate all possible text node children) - Node globalAllowedMethodsNode = globalAllowedMethodsElms.item(0); - addAllowedMethodsToSet(globalAllowedMethodsNode, globalAllowedMethods); - packageContext.addGlobalAllowedMethods(globalAllowedMethods); - } - } - - protected static void addAllowedMethodsToSet(Node allowedMethodsNode, Set allowedMethodsSet) { - if (allowedMethodsNode == null) { - return; - } - StringBuilder allowedMethodsSB = new StringBuilder(); - iterateChildren(allowedMethodsNode, allowedMethodsChildNode -> { - if (allowedMethodsChildNode != null && allowedMethodsChildNode.getNodeType() == Node.TEXT_NODE) { - String childNodeValue = allowedMethodsChildNode.getNodeValue(); - childNodeValue = (childNodeValue != null ? childNodeValue.trim() : ""); - if (childNodeValue.length() > 0) { - allowedMethodsSB.append(childNodeValue); - } - } - }); - if (allowedMethodsSB.length() > 0) { - allowedMethodsSet.addAll(commaDelimitedStringToSet(allowedMethodsSB.toString())); - } - } - - protected void loadDefaultClassRef(PackageConfig.Builder packageContext, Element element) { - NodeList defaultClassRefList = element.getElementsByTagName("default-class-ref"); - if (defaultClassRefList.getLength() > 0) { - Element defaultClassRefElement = (Element) defaultClassRefList.item(0); - packageContext.defaultClassRef(defaultClassRefElement.getAttribute("class")); - } - } - - /** - * Load all the global results for this package from the XML element. - * - * @param packageContext the package context - * @param packageElement the given XML element - */ - protected void loadGlobalExceptionMappings(PackageConfig.Builder packageContext, Element packageElement) { - NodeList globalExceptionMappingList = packageElement.getElementsByTagName("global-exception-mappings"); - - if (globalExceptionMappingList.getLength() > 0) { - Element globalExceptionMappingElement = (Element) globalExceptionMappingList.item(0); - List exceptionMappings = buildExceptionMappings(globalExceptionMappingElement, packageContext); - packageContext.addGlobalExceptionMappingConfigs(exceptionMappings); - } - } - - protected InterceptorStackConfig loadInterceptorStack(Element element, PackageConfig.Builder context) throws ConfigurationException { - String name = element.getAttribute("name"); - InterceptorStackConfig.Builder config = new InterceptorStackConfig.Builder(name) - .location(DomHelper.getLocationObject(element)); - - iterateChildrenByTagName(element, "interceptor-ref", interceptorRefElement -> { - List interceptors = lookupInterceptorReference(context, interceptorRefElement); - config.addInterceptors(interceptors); - }); - - return config.build(); - } - - protected void loadInterceptorStacks(Element element, PackageConfig.Builder context) throws ConfigurationException { - iterateChildrenByTagName(element, "interceptor-stack", interceptorStackElement -> { - InterceptorStackConfig config = loadInterceptorStack(interceptorStackElement, context); - context.addInterceptorStackConfig(config); - }); - } - - protected void loadInterceptors(PackageConfig.Builder context, Element element) throws ConfigurationException { - iterateChildrenByTagName(element, "interceptor", interceptorElement -> { - String name = interceptorElement.getAttribute("name"); - String className = interceptorElement.getAttribute("class"); - - Map params = XmlHelper.getParams(interceptorElement); - InterceptorConfig config = new InterceptorConfig.Builder(name, className) - .addParams(params) - .location(DomHelper.getLocationObject(interceptorElement)) - .build(); - - context.addInterceptorConfig(config); - }); - - loadInterceptorStacks(element, context); - } - - private List loadConfigurationFiles(String fileName, Element includeElement) { + protected List loadConfigurationFiles(String fileName, Element includeElement) { if (includedFileNames.contains(fileName)) { return emptyList(); } @@ -1013,7 +149,7 @@ public abstract class XmlConfigurationProvider implements ConfigurationProvider return finalDocs; } - private Iterator getURLs(String fileName) { + protected Iterator getURLs(String fileName) { Iterator urls = null; try { urls = getConfigurationUrls(fileName); @@ -1027,7 +163,11 @@ public abstract class XmlConfigurationProvider implements ConfigurationProvider return urls; } - private List getDocs(Iterator urls, String fileName, Element includeElement) { + protected Iterator getConfigurationUrls(String fileName) throws IOException { + return ClassLoaderUtil.getResources(fileName, XmlConfigurationProvider.class, false); + } + + protected List getDocs(Iterator urls, String fileName, Element includeElement) { List docs = new ArrayList<>(); while (urls.hasNext()) { @@ -1064,7 +204,7 @@ public abstract class XmlConfigurationProvider implements ConfigurationProvider return docs; } - private List getFinalDocs(List docs) { + protected List getFinalDocs(List docs) { List finalDocs = new ArrayList<>(); docs.sort(Comparator.comparing(XmlHelper::getLoadOrder)); for (Document doc : docs) { @@ -1091,38 +231,21 @@ public abstract class XmlConfigurationProvider implements ConfigurationProvider return finalDocs; } - protected Iterator getConfigurationUrls(String fileName) throws IOException { - return ClassLoaderUtil.getResources(fileName, XmlConfigurationProvider.class, false); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof XmlConfigurationProvider)) { + return false; + } + XmlConfigurationProvider xmlConfigurationProvider = (XmlConfigurationProvider) o; + return Objects.equals(configFileName, xmlConfigurationProvider.configFileName); } - /** - * Allows subclasses to load extra information from the document - * - * @param doc The configuration document - */ - protected void loadExtraConfiguration(Document doc) { - // no op - } - - /** - * Looks up the Interceptor Class from the interceptor-ref name and creates an instance, which is added to the - * provided List, or, if this is a ref to a stack, it adds the Interceptor instances from the List to this stack. - * - * @param context The PackageConfig to look up the interceptor from - * @param interceptorRefElement Element to pull interceptor ref data from - * @return A list of Interceptor objects - * @throws ConfigurationException in case of configuration errors - */ - private List lookupInterceptorReference(PackageConfig.Builder context, Element interceptorRefElement) throws ConfigurationException { - String refName = interceptorRefElement.getAttribute("name"); - Map refParams = XmlHelper.getParams(interceptorRefElement); - - Location loc = LocationUtils.getLocation(interceptorRefElement); - return InterceptorBuilder.constructInterceptorReference(context, refName, refParams, loc, objectFactory); - } - - List getDocuments() { - return documents; + @Override + public int hashCode() { + return configFileName != null ? configFileName.hashCode() : 0; } @Override diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java new file mode 100644 index 000000000..0fcdac0ed --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java @@ -0,0 +1,990 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.opensymphony.xwork2.config.providers; + +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.config.BeanSelectionProvider; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.config.ConfigurationProvider; +import com.opensymphony.xwork2.config.ConfigurationUtil; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; +import com.opensymphony.xwork2.config.entities.InterceptorConfig; +import com.opensymphony.xwork2.config.entities.InterceptorMapping; +import com.opensymphony.xwork2.config.entities.InterceptorStackConfig; +import com.opensymphony.xwork2.config.entities.PackageConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.config.entities.ResultTypeConfig; +import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig; +import com.opensymphony.xwork2.config.impl.LocatableFactory; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.ContainerBuilder; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.inject.Scope; +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import com.opensymphony.xwork2.util.DomHelper; +import com.opensymphony.xwork2.util.location.LocatableProperties; +import com.opensymphony.xwork2.util.location.Location; +import com.opensymphony.xwork2.util.location.LocationUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import static com.opensymphony.xwork2.util.TextParseUtil.commaDelimitedStringToSet; +import static java.lang.Boolean.parseBoolean; +import static java.lang.Character.isLowerCase; +import static java.lang.Character.toUpperCase; +import static java.lang.String.format; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isNotEmpty; +import static org.apache.commons.lang3.StringUtils.trimToNull; + +/** + * This is a base XWork2 {@link ConfigurationProvider} for loading configuration from a parsed + * {@link Document XML document}. By extending this class, configuration can be loaded from any source that an XML + * document can be parsed from. Note that this class does not validate the document against any provided DTDs. For + * loading configuration from an XML file with DTD validation, please see + * {@link org.apache.struts2.config.StrutsXmlConfigurationProvider StrutsXmlConfigurationProvider}. + * + * @since 6.2.0 + */ +public abstract class XmlDocConfigurationProvider implements ConfigurationProvider { + + private static final Logger LOG = LogManager.getLogger(XmlConfigurationProvider.class); + + protected final Map declaredPackages = new HashMap<>(); + protected List documents; + protected ObjectFactory objectFactory; + protected Map dtdMappings = new HashMap<>(); + protected Configuration configuration; + protected boolean throwExceptionOnDuplicateBeans = true; + protected ValueSubstitutor valueSubstitutor; + + @Inject + public void setObjectFactory(ObjectFactory objectFactory) { + this.objectFactory = objectFactory; + } + + @Inject(required = false) + public void setValueSubstitutor(ValueSubstitutor valueSubstitutor) { + this.valueSubstitutor = valueSubstitutor; + } + + public XmlDocConfigurationProvider(Document... documents) { + this.documents = Arrays.asList(documents); + } + + public void setThrowExceptionOnDuplicateBeans(boolean val) { + this.throwExceptionOnDuplicateBeans = val; + } + + public void setDtdMappings(Map mappings) { + this.dtdMappings = Collections.unmodifiableMap(mappings); + } + + /** + * Returns an unmodifiable map of DTD mappings + * + * @return map of DTD mappings + */ + public Map getDtdMappings() { + return dtdMappings; + } + + @Override + public void init(Configuration configuration) { + this.configuration = configuration; + } + + @Override + public void destroy() { + } + + protected Class loadClass(String className) throws ClassNotFoundException { + return objectFactory.getClassInstance(className); + } + + public static void iterateElementChildren(Document doc, Consumer function) { + iterateElementChildren(doc.getDocumentElement(), function); + } + + public static void iterateElementChildren(Node node, Consumer function) { + iterateChildren(node, childNode -> { + if (!(childNode instanceof Element)) { + return; + } + function.accept((Element) childNode); + }); + } + + public static void iterateChildren(Node node, Consumer function) { + NodeList children = node.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + function.accept(children.item(i)); + } + } + + public static void iterateChildrenByTagName(Element el, String tagName, Consumer function) { + NodeList childrenByTag = el.getElementsByTagName(tagName); + for (int i = 0; i < childrenByTag.getLength(); i++) { + Element childEl = (Element) childrenByTag.item(i); + function.accept(childEl); + } + } + + @Override + public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { + Map loadedBeans = new HashMap<>(); + for (Document doc : documents) { + iterateElementChildren(doc, child -> { + switch (child.getNodeName()) { + case "bean-selection": { + registerBeanSelection(child, containerBuilder, props); + break; + } + case "bean": { + registerBean(child, loadedBeans, containerBuilder); + break; + } + case "constant": { + registerConstant(child, props); + break; + } + case "unknown-handler-stack": + registerUnknownHandlerStack(child); + break; + } + }); + } + } + + protected void registerBeanSelection(Element child, ContainerBuilder containerBuilder, LocatableProperties props) { + String name = child.getAttribute("name"); + String impl = child.getAttribute("class"); + try { + Class classImpl = loadClass(impl); + if (BeanSelectionProvider.class.isAssignableFrom(classImpl)) { + BeanSelectionProvider provider = (BeanSelectionProvider) classImpl.newInstance(); + provider.register(containerBuilder, props); + } else { + throw new ConfigurationException(format("The bean-provider: name:%s class:%s does not implement %s", name, impl, BeanSelectionProvider.class.getName()), child); + } + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + throw new ConfigurationException(format("Unable to load bean-provider: name:%s class:%s", name, impl), e, child); + } + } + + protected void registerBean(Element child, Map loadedBeans, ContainerBuilder containerBuilder) { + String type = child.getAttribute("type"); + String name = child.getAttribute("name"); + String impl = child.getAttribute("class"); + String onlyStatic = child.getAttribute("static"); + String scopeStr = child.getAttribute("scope"); + boolean optional = "true".equals(child.getAttribute("optional")); + Scope scope = Scope.fromString(scopeStr); + + if (name.isEmpty()) { + name = Container.DEFAULT_NAME; + } + + try { + Class classImpl = ClassLoaderUtil.loadClass(impl, getClass()); + Class classType = classImpl; + if (!type.isEmpty()) { + classType = ClassLoaderUtil.loadClass(type, getClass()); + } + if ("true".equals(onlyStatic)) { + // Force loading of class to detect no class def found exceptions + classImpl.getDeclaredClasses(); + containerBuilder.injectStatics(classImpl); + } else { + if (containerBuilder.contains(classType, name)) { + Location loc = LocationUtils.getLocation(loadedBeans.get(classType.getName() + name)); + if (throwExceptionOnDuplicateBeans) { + throw new ConfigurationException(format("Bean type %s with the name %s has already been loaded by %s", classType, name, loc), child); + } + } + + // Force loading of class to detect no class def found exceptions + classImpl.getDeclaredConstructors(); + + LOG.debug("Loaded type: {} name: {} impl: {}", type, name, impl); + containerBuilder.factory(classType, name, new LocatableFactory<>(name, classType, classImpl, scope, child), scope); + } + loadedBeans.put(classType.getName() + name, child); + } catch (Throwable ex) { + if (!optional) { + throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, child); + } else { + LOG.debug("Unable to load optional class: {}", impl); + } + } + } + + protected void registerConstant(Element child, LocatableProperties props) { + String name = child.getAttribute("name"); + String value = child.getAttribute("value"); + + if (valueSubstitutor != null) { + LOG.debug("Substituting value [{}] using [{}]", value, valueSubstitutor.getClass().getName()); + value = valueSubstitutor.substitute(value); + } + + props.setProperty(name, value, child); + } + + protected void registerUnknownHandlerStack(Element child) { + List unknownHandlerStack = new ArrayList<>(); + + iterateChildrenByTagName(child, "unknown-handler-ref", unknownHandler -> { + Location location = LocationUtils.getLocation(unknownHandler); + unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name"), location)); + }); + + if (!unknownHandlerStack.isEmpty()) { + configuration.setUnknownHandlerStack(unknownHandlerStack); + } + } + + @Override + public boolean needsReload() { + return false; + } + + @Override + public void loadPackages() throws ConfigurationException { + List reloads = new ArrayList<>(); + verifyPackageStructure(); + + for (Document doc : documents) { + iterateElementChildren(doc, child -> { + if ("package".equals(child.getNodeName())) { + PackageConfig cfg = addPackage(child); + if (cfg.isNeedsRefresh()) { + reloads.add(child); + } + } + }); + loadExtraConfiguration(doc); + } + + if (reloads.size() > 0) { + reloadRequiredPackages(reloads); + } + + for (Document doc : documents) { + loadExtraConfiguration(doc); + } + + declaredPackages.clear(); + configuration = null; + } + + private void verifyPackageStructure() { + DirectedGraph graph = new DirectedGraph<>(); + + for (Document doc : documents) { + iterateElementChildren(doc, child -> { + if (!"package".equals(child.getNodeName())) { + return; + } + + String packageName = child.getAttribute("name"); + declaredPackages.put(packageName, child); + graph.addNode(packageName); + + String extendsAttribute = child.getAttribute("extends"); + for (String parent : ConfigurationUtil.buildParentListFromString(extendsAttribute)) { + graph.addNode(parent); + graph.addEdge(packageName, parent); + } + }); + } + + CycleDetector detector = new CycleDetector<>(graph); + if (detector.containsCycle()) { + StringBuilder builder = new StringBuilder("The following packages participate in cycles:"); + for (String packageName : detector.getVerticesInCycles()) { + builder.append(" "); + builder.append(packageName); + } + throw new ConfigurationException(builder.toString()); + } + } + + /** + * Allows subclasses to load extra information from the document + * + * @param doc The configuration document + */ + protected void loadExtraConfiguration(Document doc) { + // no op + } + + private void reloadRequiredPackages(List reloads) { + if (reloads.isEmpty()) { + return; + } + + List result = new ArrayList<>(); + for (Element pkg : reloads) { + PackageConfig cfg = addPackage(pkg); + if (cfg.isNeedsRefresh()) { + result.add(pkg); + } + } + if (!result.isEmpty() && result.size() != reloads.size()) { + reloadRequiredPackages(result); + return; + } + + // Print out error messages for all misconfigured inheritance packages + for (Element rp : result) { + String parent = rp.getAttribute("extends"); + if (!parent.isEmpty() && ConfigurationUtil.buildParentsFromString(configuration, parent).isEmpty()) { + LOG.error("Unable to find parent packages {}", parent); + } + } + } + + /** + * Create a PackageConfig from an XML element representing it. + * + * @param packageElement the given XML element + * @return the package config + * @throws ConfigurationException in case of configuration errors + */ + protected PackageConfig addPackage(Element packageElement) throws ConfigurationException { + String packageName = packageElement.getAttribute("name"); + PackageConfig packageConfig = configuration.getPackageConfig(packageName); + if (packageConfig != null) { + LOG.debug("Package [{}] already loaded, skipping re-loading it and using existing PackageConfig [{}]", packageName, packageConfig); + return packageConfig; + } + + PackageConfig.Builder newPackage = buildPackageContext(packageElement); + + if (newPackage.isNeedsRefresh()) { + return newPackage.build(); + } + + LOG.debug("Loaded {}", newPackage); + + // add result types (and default result) to this package + addResultTypes(newPackage, packageElement); + + // load the interceptors and interceptor stacks for this package + loadInterceptors(newPackage, packageElement); + + // load the default interceptor reference for this package + loadDefaultInterceptorRef(newPackage, packageElement); + + // load the default class ref for this package + loadDefaultClassRef(newPackage, packageElement); + + // load the global result list for this package + loadGlobalResults(newPackage, packageElement); + + loadGlobalAllowedMethods(newPackage, packageElement); + + // load the global exception handler list for this package + loadGlobalExceptionMappings(newPackage, packageElement); + + // get actions + iterateChildrenByTagName(packageElement, "action", actionElement -> addAction(actionElement, newPackage)); + + // load the default action reference for this package + loadDefaultActionRef(newPackage, packageElement); + + PackageConfig cfg = newPackage.build(); + configuration.addPackageConfig(cfg.getName(), cfg); + return cfg; + } + + protected void addAction(Element actionElement, PackageConfig.Builder packageContext) throws ConfigurationException { + String name = actionElement.getAttribute("name"); + String className = actionElement.getAttribute("class"); + + Location location = DomHelper.getLocationObject(actionElement); + + if (location == null) { + LOG.warn("Location null for {}", className); + } + + if (!className.isEmpty() && !verifyAction(className, name, location)) { + LOG.error("Unable to verify action [{}] with class [{}], from [{}]", name, className, location); + return; + } + + Map results; + try { + results = buildResults(actionElement, packageContext); + } catch (ConfigurationException e) { + throw new ConfigurationException( + format("Error building results for action %s in namespace %s", name, packageContext.getNamespace()), + e, + actionElement); + } + + ActionConfig actionConfig = buildActionConfig(actionElement, location, packageContext, results); + packageContext.addActionConfig(actionConfig.getName(), actionConfig); + + LOG.debug("Loaded {}{} in '{}' package: {}", + isNotEmpty(packageContext.getNamespace()) ? (packageContext.getNamespace() + "/") : "", + name, packageContext.getName(), actionConfig); + } + + protected ActionConfig buildActionConfig(Element actionElement, + Location location, + PackageConfig.Builder packageContext, + Map results) { + String actionName = actionElement.getAttribute("name"); + String className = actionElement.getAttribute("class"); + // methodName should be null if it's not set + String methodName = trimToNull(actionElement.getAttribute("method")); + + List interceptorList = buildInterceptorList(actionElement, packageContext); + List exceptionMappings = buildExceptionMappings(actionElement, packageContext); + Set allowedMethods = buildAllowedMethods(actionElement, packageContext); + + return new ActionConfig.Builder(packageContext.getName(), actionName, className) + .methodName(methodName) + .addResultConfigs(results) + .addInterceptors(interceptorList) + .addExceptionMappings(exceptionMappings) + .addParams(XmlHelper.getParams(actionElement)) + .setStrictMethodInvocation(packageContext.isStrictMethodInvocation()) + .addAllowedMethod(allowedMethods) + .location(location) + .build(); + } + + /** + * @deprecated since 6.2.0, use {@link #verifyAction(String, Location)} + */ + @Deprecated + protected boolean verifyAction(String className, String name, Location loc) { + return verifyAction(className, loc); + } + + protected boolean verifyAction(String className, Location loc) { + if (className.contains("{")) { + LOG.debug("Action class [{}] contains a wildcard replacement value, so it can't be verified", className); + return true; + } + try { + if (objectFactory.isNoArgConstructorRequired()) { + Class clazz = loadClass(className); + if (!Modifier.isPublic(clazz.getModifiers())) { + throw new ConfigurationException("Action class [" + className + "] is not public", loc); + } + clazz.getConstructor(); + } + } catch (ClassNotFoundException e) { + LOG.debug("Class not found for action [{}]", className, e); + throw new ConfigurationException("Action class [" + className + "] not found", loc); + } catch (NoSuchMethodException e) { + LOG.debug("No constructor found for action [{}]", className, e); + throw new ConfigurationException("Action class [" + className + "] does not have a public no-arg constructor", e, loc); + } catch (RuntimeException ex) { + // Probably not a big deal, like request or session-scoped Spring beans that need a real request + LOG.info("Unable to verify action class [{}] exists at initialization", className); + LOG.debug("Action verification cause", ex); + } catch (Exception ex) { + // Default to failing fast + LOG.debug("Unable to verify action class [{}]", className, ex); + throw new ConfigurationException(ex, loc); + } + return true; + } + + protected void addResultTypes(PackageConfig.Builder packageContext, Element element) { + iterateChildrenByTagName(element, "result-type", resultTypeElement -> { + String name = resultTypeElement.getAttribute("name"); + String className = resultTypeElement.getAttribute("class"); + String def = resultTypeElement.getAttribute("default"); + + Location loc = DomHelper.getLocationObject(resultTypeElement); + Class clazz = verifyResultType(className, loc); + if (clazz == null) { + return; + } + String paramName = null; + try { + paramName = (String) clazz.getField("DEFAULT_PARAM").get(null); + } catch (Throwable t) { + LOG.debug("The result type [{}] doesn't have a default param [DEFAULT_PARAM] defined!", className, t); + } + packageContext.addResultTypeConfig(buildResultTypeConfig(resultTypeElement, loc, paramName)); + + if (BooleanUtils.toBoolean(def)) { + packageContext.defaultResultType(name); + } + }); + } + + protected ResultTypeConfig buildResultTypeConfig(Element resultTypeElement, Location location, String paramName) { + String name = resultTypeElement.getAttribute("name"); + String className = resultTypeElement.getAttribute("class"); + ResultTypeConfig.Builder resultType = new ResultTypeConfig.Builder(name, + className).defaultResultParam(paramName).location(location); + Map params = XmlHelper.getParams(resultTypeElement); + if (!params.isEmpty()) { + resultType.addParams(params); + } + return resultType.build(); + } + + protected Class verifyResultType(String className, Location loc) { + try { + return loadClass(className); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + LOG.warn("Result class [{}] doesn't exist ({}) at {}, ignoring", className, e.getClass().getSimpleName(), loc, e); + } + return null; + } + + /** + *

This method builds a package context by looking for the parents of this new package.

+ *

If no parents are found, it will return a root package.

+ * + * @param packageElement the package element + * @return the package config builder + */ + protected PackageConfig.Builder buildPackageContext(Element packageElement) { + String parent = packageElement.getAttribute("extends"); + String abstractVal = packageElement.getAttribute("abstract"); + boolean isAbstract = parseBoolean(abstractVal); + String name = defaultString(packageElement.getAttribute("name")); + String namespace = defaultString(packageElement.getAttribute("namespace")); + + // Strict DMI is enabled by default, it can be disabled by user + boolean strictDMI = true; + if (packageElement.hasAttribute("strict-method-invocation")) { + strictDMI = parseBoolean(packageElement.getAttribute("strict-method-invocation")); + } + + PackageConfig.Builder cfg = new PackageConfig.Builder(name) + .namespace(namespace) + .isAbstract(isAbstract) + .strictMethodInvocation(strictDMI) + .location(DomHelper.getLocationObject(packageElement)); + + if (parent.isEmpty()) { + return cfg; + } + + // has parents, let's look it up + List parents = new ArrayList<>(); + for (String parentPackageName : ConfigurationUtil.buildParentListFromString(parent)) { + if (configuration.getPackageConfigNames().contains(parentPackageName)) { + parents.add(configuration.getPackageConfig(parentPackageName)); + } else if (declaredPackages.containsKey(parentPackageName)) { + if (configuration.getPackageConfig(parentPackageName) == null) { + addPackage(declaredPackages.get(parentPackageName)); + } + parents.add(configuration.getPackageConfig(parentPackageName)); + } else { + throw new ConfigurationException("Parent package is not defined: " + parentPackageName); + } + + } + + if (parents.isEmpty()) { + cfg.needsRefresh(true); + } else { + cfg.addParents(parents); + } + + return cfg; + } + + /** + * Build a map of ResultConfig objects from below a given XML element. + * + * @param element the given XML element + * @param packageContext the package context + * @return map of result config objects + */ + protected Map buildResults(Element element, PackageConfig.Builder packageContext) { + Map results = new LinkedHashMap<>(); + + iterateChildrenByTagName(element, "result", resultElement -> { + Node parNode = resultElement.getParentNode(); + if (!parNode.equals(element) && !parNode.getNodeName().equals(element.getNodeName())) { + return; + } + + String resultName = resultElement.getAttribute("name"); + String resultType = resultElement.getAttribute("type"); + + // if you don't specify a name on , it defaults to "success" + if (StringUtils.isEmpty(resultName)) { + resultName = Action.SUCCESS; + } + + // there is no result type, so let's inherit from the parent package + if (resultType.isEmpty()) { + resultType = packageContext.getFullDefaultResultType(); + // now check if there is a result type now + if (resultType.isEmpty()) { + throw new ConfigurationException("No result type specified for result named '" + + resultName + "', perhaps the parent package does not specify the result type?", resultElement); + } + } + + ResultTypeConfig config = packageContext.getResultType(resultType); + if (config == null) { + throw new ConfigurationException(format("There is no result type defined for type '%s' mapped with name '%s'. Did you mean '%s'?", resultType, resultName, guessResultType(resultType)), resultElement); + } + + String resultClass = config.getClassName(); + if (resultClass == null) { + throw new ConfigurationException("Result type '" + resultType + "' is invalid"); + } + + Set resultNamesSet = commaDelimitedStringToSet(resultName); + if (resultNamesSet.isEmpty()) { + resultNamesSet.add(resultName); + } + + Map params = buildResultParams(resultElement, config); + Location location = DomHelper.getLocationObject(element); + + for (String name : resultNamesSet) { + ResultConfig resultConfig = buildResultConfig(name, config, location, params); + results.put(resultConfig.getName(), resultConfig); + } + }); + + return results; + } + + protected ResultConfig buildResultConfig(String name, + ResultTypeConfig config, + Location location, + Map params) { + return new ResultConfig.Builder(name, config.getClassName()).location(location).addParams(params).build(); + } + + protected Map buildResultParams(Element resultElement, ResultTypeConfig config) { + Map resultParams = XmlHelper.getParams(resultElement); + + // maybe we just have a body - therefore a default parameter + if (resultParams.isEmpty() && resultElement.getChildNodes().getLength() > 0) { + // if something then we add a parameter of 'something' as this is the most used result param + resultParams = new LinkedHashMap<>(); + + String paramName = config.getDefaultResultParam(); + if (paramName != null) { + StringBuilder paramValue = new StringBuilder(); + iterateChildren(resultElement, child -> { + if (child.getNodeType() == Node.TEXT_NODE) { + String val = child.getNodeValue(); + if (val != null) { + paramValue.append(val); + } + } + }); + String val = paramValue.toString().trim(); + if (val.length() > 0) { + resultParams.put(paramName, val); + } + } else { + LOG.debug( + "No default parameter defined for result [{}] of type [{}] ", + config.getName(), + config.getClassName()); + } + } + + // create new param map, so that the result param can override the config param + Map params = new LinkedHashMap<>(); + Map configParams = config.getParams(); + if (configParams != null) { + params.putAll(configParams); + } + params.putAll(resultParams); + return params; + } + + protected static String guessResultType(String type) { + if (type == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + boolean capNext = false; + for (int x = 0; x < type.length(); x++) { + char c = type.charAt(x); + if (c == '-') { + capNext = true; + continue; + } else if (isLowerCase(c) && capNext) { + c = toUpperCase(c); + capNext = false; + } + sb.append(c); + } + return sb.toString(); + } + + /** + * @deprecated since 6.2.0, use {@link #buildExceptionMappings(Element)} + */ + @Deprecated + protected List buildExceptionMappings(Element element, PackageConfig.Builder packageContext) { + return buildExceptionMappings(element); + } + + /** + * Build a list of exception mapping objects from below a given XML element. + * + * @param element the given XML element + * @return list of exception mapping config objects + */ + protected List buildExceptionMappings(Element element) { + List exceptionMappings = new ArrayList<>(); + + iterateChildrenByTagName(element, "exception-mapping", ehElement -> { + Node parNode = ehElement.getParentNode(); + if (!parNode.equals(element) && !parNode.getNodeName().equals(element.getNodeName())) { + return; + } + + String emName = ehElement.getAttribute("name"); + String exceptionClassName = ehElement.getAttribute("exception"); + String exceptionResult = ehElement.getAttribute("result"); + Map params = XmlHelper.getParams(ehElement); + if (emName.isEmpty()) { + emName = exceptionResult; + } + + ExceptionMappingConfig ehConfig = new ExceptionMappingConfig.Builder(emName, exceptionClassName, exceptionResult) + .addParams(params) + .location(DomHelper.getLocationObject(ehElement)) + .build(); + exceptionMappings.add(ehConfig); + }); + + return exceptionMappings; + } + + protected Set buildAllowedMethods(Element element, PackageConfig.Builder packageContext) { + NodeList allowedMethodsEls = element.getElementsByTagName("allowed-methods"); + + Set allowedMethods; + if (allowedMethodsEls.getLength() > 0) { + // user defined 'allowed-methods' so used them whatever Strict DMI was enabled or not + allowedMethods = new HashSet<>(packageContext.getGlobalAllowedMethods()); + // Fix for WW-5029 (concatenate all possible text node children) + Node allowedMethodsNode = allowedMethodsEls.item(0); + addAllowedMethodsToSet(allowedMethodsNode, allowedMethods); + } else if (packageContext.isStrictMethodInvocation()) { + // user enabled Strict DMI but didn't define action specific 'allowed-methods' so we use 'global-allowed-methods' only + allowedMethods = new HashSet<>(packageContext.getGlobalAllowedMethods()); + } else { + // Strict DMI is disabled so any method can be called + allowedMethods = new HashSet<>(); + allowedMethods.add(ActionConfig.WILDCARD); + } + + LOG.debug("Collected allowed methods: {}", allowedMethods); + + return Collections.unmodifiableSet(allowedMethods); + } + + protected void loadDefaultActionRef(PackageConfig.Builder packageContext, Element element) { + NodeList resultTypeList = element.getElementsByTagName("default-action-ref"); + + if (resultTypeList.getLength() > 0) { + Element defaultRefElement = (Element) resultTypeList.item(0); + packageContext.defaultActionRef(defaultRefElement.getAttribute("name")); + } + } + + /** + * Load all the global results for this package from the XML element. + * + * @param packageContext the package context + * @param packageElement the given XML element + */ + protected void loadGlobalResults(PackageConfig.Builder packageContext, Element packageElement) { + NodeList globalResultList = packageElement.getElementsByTagName("global-results"); + + if (globalResultList.getLength() > 0) { + Element globalResultElement = (Element) globalResultList.item(0); + Map results = buildResults(globalResultElement, packageContext); + packageContext.addGlobalResultConfigs(results); + } + } + + protected void loadGlobalAllowedMethods(PackageConfig.Builder packageContext, Element packageElement) { + NodeList globalAllowedMethodsElms = packageElement.getElementsByTagName("global-allowed-methods"); + + if (globalAllowedMethodsElms.getLength() > 0) { + Set globalAllowedMethods = new HashSet<>(); + // Fix for WW-5029 (concatenate all possible text node children) + Node globalAllowedMethodsNode = globalAllowedMethodsElms.item(0); + addAllowedMethodsToSet(globalAllowedMethodsNode, globalAllowedMethods); + packageContext.addGlobalAllowedMethods(globalAllowedMethods); + } + } + + protected static void addAllowedMethodsToSet(Node allowedMethodsNode, Set allowedMethodsSet) { + if (allowedMethodsNode == null) { + return; + } + StringBuilder allowedMethodsSB = new StringBuilder(); + iterateChildren(allowedMethodsNode, allowedMethodsChildNode -> { + if (allowedMethodsChildNode != null && allowedMethodsChildNode.getNodeType() == Node.TEXT_NODE) { + String childNodeValue = allowedMethodsChildNode.getNodeValue(); + childNodeValue = (childNodeValue != null ? childNodeValue.trim() : ""); + if (childNodeValue.length() > 0) { + allowedMethodsSB.append(childNodeValue); + } + } + }); + if (allowedMethodsSB.length() > 0) { + allowedMethodsSet.addAll(commaDelimitedStringToSet(allowedMethodsSB.toString())); + } + } + + protected void loadDefaultClassRef(PackageConfig.Builder packageContext, Element element) { + NodeList defaultClassRefList = element.getElementsByTagName("default-class-ref"); + if (defaultClassRefList.getLength() > 0) { + Element defaultClassRefElement = (Element) defaultClassRefList.item(0); + packageContext.defaultClassRef(defaultClassRefElement.getAttribute("class")); + } + } + + /** + * Load all the global results for this package from the XML element. + * + * @param packageContext the package context + * @param packageElement the given XML element + */ + protected void loadGlobalExceptionMappings(PackageConfig.Builder packageContext, Element packageElement) { + NodeList globalExceptionMappingList = packageElement.getElementsByTagName("global-exception-mappings"); + + if (globalExceptionMappingList.getLength() > 0) { + Element globalExceptionMappingElement = (Element) globalExceptionMappingList.item(0); + List exceptionMappings = buildExceptionMappings(globalExceptionMappingElement, packageContext); + packageContext.addGlobalExceptionMappingConfigs(exceptionMappings); + } + } + + protected List buildInterceptorList(Element element, PackageConfig.Builder context) throws ConfigurationException { + List interceptorList = new ArrayList<>(); + + iterateChildrenByTagName(element, "interceptor-ref", interceptorRefElement -> { + Node parNode = interceptorRefElement.getParentNode(); + if (!parNode.equals(element) && !parNode.getNodeName().equals(element.getNodeName())) { + return; + } + List interceptors = lookupInterceptorReference(context, interceptorRefElement); + interceptorList.addAll(interceptors); + }); + + return interceptorList; + } + + protected void loadInterceptors(PackageConfig.Builder context, Element element) throws ConfigurationException { + iterateChildrenByTagName( + element, + "interceptor", + interceptorElement -> context.addInterceptorConfig(buildInterceptorConfig(interceptorElement))); + loadInterceptorStacks(element, context); + } + + protected InterceptorConfig buildInterceptorConfig(Element interceptorElement) { + String interceptorName = interceptorElement.getAttribute("name"); + String className = interceptorElement.getAttribute("class"); + + Map params = XmlHelper.getParams(interceptorElement); + return new InterceptorConfig.Builder(interceptorName, className) + .addParams(params) + .location(DomHelper.getLocationObject(interceptorElement)) + .build(); + } + + protected void loadInterceptorStacks(Element element, PackageConfig.Builder context) throws ConfigurationException { + iterateChildrenByTagName(element, "interceptor-stack", interceptorStackElement -> { + InterceptorStackConfig config = loadInterceptorStack(interceptorStackElement, context); + context.addInterceptorStackConfig(config); + }); + } + + protected InterceptorStackConfig loadInterceptorStack(Element element, PackageConfig.Builder context) throws ConfigurationException { + String name = element.getAttribute("name"); + InterceptorStackConfig.Builder config = new InterceptorStackConfig.Builder(name) + .location(DomHelper.getLocationObject(element)); + + iterateChildrenByTagName(element, "interceptor-ref", interceptorRefElement -> { + List interceptors = lookupInterceptorReference(context, interceptorRefElement); + config.addInterceptors(interceptors); + }); + + return config.build(); + } + + /** + * Looks up the Interceptor Class from the interceptor-ref name and creates an instance, which is added to the + * provided List, or, if this is a ref to a stack, it adds the Interceptor instances from the List to this stack. + * + * @param context The PackageConfig to look up the interceptor from + * @param interceptorRefElement Element to pull interceptor ref data from + * @return A list of Interceptor objects + * @throws ConfigurationException in case of configuration errors + */ + protected List lookupInterceptorReference(PackageConfig.Builder context, Element interceptorRefElement) throws ConfigurationException { + String refName = interceptorRefElement.getAttribute("name"); + Map refParams = XmlHelper.getParams(interceptorRefElement); + + Location loc = LocationUtils.getLocation(interceptorRefElement); + return InterceptorBuilder.constructInterceptorReference(context, refName, refParams, loc, objectFactory); + } + + protected void loadDefaultInterceptorRef(PackageConfig.Builder packageContext, Element element) { + NodeList resultTypeList = element.getElementsByTagName("default-interceptor-ref"); + + if (resultTypeList.getLength() > 0) { + Element defaultRefElement = (Element) resultTypeList.item(0); + packageContext.defaultInterceptorRef(defaultRefElement.getAttribute("name")); + } + } +} diff --git a/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java b/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java index f7f469420..9b62b3d59 100644 --- a/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java +++ b/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java @@ -39,17 +39,34 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import static java.util.Collections.unmodifiableMap; + /** * Override Xwork class so we can use an arbitrary config file */ public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider { private static final Logger LOG = LogManager.getLogger(StrutsXmlConfigurationProvider.class); + private static final Map STRUTS_DTD_MAPPINGS = unmodifiableMap(new HashMap() {{ + put("-//Apache Software Foundation//DTD Struts Configuration 2.0//EN", "struts-2.0.dtd"); + put("-//Apache Software Foundation//DTD Struts Configuration 2.1//EN", "struts-2.1.dtd"); + put("-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN", "struts-2.1.7.dtd"); + put("-//Apache Software Foundation//DTD Struts Configuration 2.3//EN", "struts-2.3.dtd"); + put("-//Apache Software Foundation//DTD Struts Configuration 2.5//EN", "struts-2.5.dtd"); + put("-//Apache Software Foundation//DTD Struts Configuration 6.0//EN", "struts-6.0.dtd"); + }}); private File baseDir = null; private final String filename; private final String reloadKey; private final ServletContext servletContext; + /** + * Constructs the Struts configuration provider using the default struts.xml and no ServletContext + */ + public StrutsXmlConfigurationProvider() { + this("struts.xml", null); + } + /** * Constructs the configuration provider * @@ -57,7 +74,7 @@ public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider { */ @Deprecated public StrutsXmlConfigurationProvider(boolean errorIfMissing) { - this("struts.xml", errorIfMissing, null); + this("struts.xml", null); } /** @@ -66,35 +83,35 @@ public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider { * @param filename file with Struts configuration */ public StrutsXmlConfigurationProvider(String filename) { - this(filename, false, null); + this(filename, null); } /** - * Constructs the configuration provider + * Constructs the Struts configuration provider * * @param filename The filename to look for - * @param errorIfMissing If we should throw an exception if the file can't be found, @deprecated and should be dropped * @param ctx Our ServletContext */ - public StrutsXmlConfigurationProvider(String filename, @Deprecated boolean errorIfMissing, ServletContext ctx) { - super(filename, errorIfMissing); + public StrutsXmlConfigurationProvider(String filename, ServletContext ctx) { + super(filename); this.servletContext = ctx; this.filename = filename; - reloadKey = "configurationReload-" + filename; - Map dtdMappings = new HashMap<>(getDtdMappings()); - dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.0//EN", "struts-2.0.dtd"); - dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.1//EN", "struts-2.1.dtd"); - dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN", "struts-2.1.7.dtd"); - dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.3//EN", "struts-2.3.dtd"); - dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.5//EN", "struts-2.5.dtd"); - dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 6.0//EN", "struts-6.0.dtd"); - setDtdMappings(dtdMappings); + this.reloadKey = "configurationReload-" + filename; + setDtdMappings(STRUTS_DTD_MAPPINGS); File file = new File(filename); if (file.getParent() != null) { this.baseDir = file.getParentFile(); } } + /** + * @deprecated since 6.2.0, use {@link #StrutsXmlConfigurationProvider(String, ServletContext)} + */ + @Deprecated + public StrutsXmlConfigurationProvider(String filename, @Deprecated boolean errorIfMissing, ServletContext ctx) { + this(filename, ctx); + } + /* (non-Javadoc) * @see com.opensymphony.xwork2.config.providers.XmlConfigurationProvider#register(com.opensymphony.xwork2.inject.ContainerBuilder, java.util.Properties) */ diff --git a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java index 43794c1c5..ce6dbe673 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java @@ -112,12 +112,12 @@ public class Dispatcher { /** * Provide a thread local instance. */ - private static ThreadLocal instance = new ThreadLocal<>(); + private static final ThreadLocal instance = new ThreadLocal<>(); /** * Store list of DispatcherListeners. */ - private static List dispatcherListeners = new CopyOnWriteArrayList<>(); + private static final List dispatcherListeners = new CopyOnWriteArrayList<>(); /** * Store state of StrutsConstants.STRUTS_DEVMODE setting. @@ -353,7 +353,7 @@ public class Dispatcher { try { ((ObjectFactoryDestroyable) objectFactory).destroy(); } catch (Exception e) { - // catch any exception that may occurred during destroy() and log it + // catch any exception that may occur during destroy() and log it LOG.error("Exception occurred while destroying ObjectFactory [{}]", objectFactory.toString(), e); } } @@ -437,8 +437,16 @@ public class Dispatcher { } } + protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, ServletContext ctx) { + return new StrutsXmlConfigurationProvider(filename, ctx); + } + + /** + * @deprecated since 6.2.0, use {@link #createStrutsXmlConfigurationProvider(String, ServletContext)} + */ + @Deprecated protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, boolean errorIfMissing, ServletContext ctx) { - return new StrutsXmlConfigurationProvider(filename, errorIfMissing, ctx); + return createStrutsXmlConfigurationProvider(filename, ctx); } private void init_JavaConfigurations() { diff --git a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderTest.java b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderTest.java index 3a8fbb37c..6d228f74d 100644 --- a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderTest.java @@ -75,7 +75,7 @@ public class XmlConfigurationProviderTest extends ConfigurationTestBase { prov.setObjectFactory(container.getInstance(ObjectFactory.class)); prov.setFileManagerFactory(container.getInstance(FileManagerFactory.class)); prov.init(configuration); - List docs = prov.getDocuments(); + List docs = prov.documents; assertEquals(3, docs.size()); assertEquals(1, XmlHelper.getLoadOrder(docs.get(0)).intValue());