diff --git a/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java b/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java index 88790fc6f7..8a4695e576 100644 --- a/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java +++ b/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java @@ -36,6 +36,8 @@ import java.util.Locale; import java.util.Map; +import static java.util.Collections.singletonMap; + /** * Base JUnit TestCase to extend for XWork specific JUnit tests. Uses * the generic test setup for logic. @@ -56,9 +58,7 @@ public XWorkTestCase() { @Override protected void setUp() throws Exception { configurationManager = XWorkTestCaseHelper.setUp(); - configuration = configurationManager.getConfiguration(); - container = configuration.getContainer(); - actionProxyFactory = container.getInstance(ActionProxyFactory.class); + reloadConfiguration(configurationManager); } @Override @@ -66,13 +66,17 @@ protected void tearDown() throws Exception { XWorkTestCaseHelper.tearDown(configurationManager); } - protected void loadConfigurationProviders(ConfigurationProvider... providers) { - configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers); + private void reloadConfiguration(ConfigurationManager configurationManager) { configuration = configurationManager.getConfiguration(); container = configuration.getContainer(); actionProxyFactory = container.getInstance(ActionProxyFactory.class); } + protected void loadConfigurationProviders(ConfigurationProvider... providers) { + configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers); + reloadConfiguration(configurationManager); + } + protected void loadButSet(Map properties) { loadConfigurationProviders(new StubConfigurationProvider() { @Override @@ -115,4 +119,25 @@ protected Map createContextWithLocale(Locale locale) { .getContextMap(); } + protected void setStrutsConstant(String constant, String value) { + setStrutsConstant(singletonMap(constant, value)); + } + + protected void setStrutsConstant(final Map overwritePropeties) { + configurationManager.addContainerProvider(new StubConfigurationProvider() { + @Override + public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { + for (Map.Entry stringStringEntry : overwritePropeties.entrySet()) { + props.setProperty(stringStringEntry.getKey(), stringStringEntry.getValue(), null); + } + } + + @Override + public void destroy() { + } + }); + + configurationManager.reload(); + reloadConfiguration(configurationManager); + } } diff --git a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java index 7bf0e7c779..7c725e15b0 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java @@ -120,6 +120,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; @@ -459,9 +460,12 @@ protected synchronized RuntimeConfiguration buildRuntimeConfiguration() throws C boolean appendNamedParameters = Boolean.parseBoolean( container.getInstance(String.class, StrutsConstants.STRUTS_MATCHER_APPEND_NAMED_PARAMETERS) ); + boolean fallbackToEmptyNamespace = Boolean.parseBoolean( + Optional.ofNullable(container.getInstance(String.class, StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE)).orElse("true") + ); return new RuntimeConfigurationImpl(Collections.unmodifiableMap(namespaceActionConfigs), - Collections.unmodifiableMap(namespaceConfigs), matcher, appendNamedParameters); + Collections.unmodifiableMap(namespaceConfigs), matcher, appendNamedParameters, fallbackToEmptyNamespace); } private void setDefaultResults(Map results, PackageConfig packageContext) { @@ -536,14 +540,17 @@ private static class RuntimeConfigurationImpl implements RuntimeConfiguration { private final Map namespaceActionConfigMatchers; private final NamespaceMatcher namespaceMatcher; private final Map namespaceConfigs; + private final boolean fallbackToEmptyNamespace; public RuntimeConfigurationImpl(Map> namespaceActionConfigs, Map namespaceConfigs, PatternMatcher matcher, - boolean appendNamedParameters) + boolean appendNamedParameters, + boolean fallbackToEmptyNamespace) { this.namespaceActionConfigs = namespaceActionConfigs; this.namespaceConfigs = namespaceConfigs; + this.fallbackToEmptyNamespace = fallbackToEmptyNamespace; this.namespaceActionConfigMatchers = new LinkedHashMap<>(); this.namespaceMatcher = new NamespaceMatcher(matcher, namespaceActionConfigs.keySet(), appendNamedParameters); @@ -583,14 +590,17 @@ public ActionConfig getActionConfig(String namespace, String name) { } // fail over to empty namespace - if (config == null && StringUtils.isNotBlank(namespace)) { + if (config == null && shouldFallbackToEmptyNamespace(namespace)) { config = findActionConfigInNamespace("", name); } - return config; } + private boolean shouldFallbackToEmptyNamespace(String namespace) { + return StringUtils.isNotBlank(namespace) && ("/".equals(namespace) || fallbackToEmptyNamespace); + } + private ActionConfig findActionConfigInNamespace(String namespace, String name) { ActionConfig config = null; if (namespace == null) { diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java index 3d0d1a00dc..7a7f4b2478 100644 --- a/core/src/main/java/org/apache/struts2/StrutsConstants.java +++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java @@ -230,6 +230,8 @@ public final class StrutsConstants { public static final String STRUTS_XWORKCONVERTER = "struts.xworkConverter"; public static final String STRUTS_ALWAYS_SELECT_FULL_NAMESPACE = "struts.mapper.alwaysSelectFullNamespace"; + /** Fallback to empty namespace when request namespace didn't match any in action configuration */ + public static final String STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE = "struts.actionConfig.fallbackToEmptyNamespace"; /** The {@link com.opensymphony.xwork2.LocaleProviderFactory} implementation class */ public static final String STRUTS_LOCALE_PROVIDER_FACTORY = "struts.localeProviderFactory"; diff --git a/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java b/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java index 2b854243dc..3f7484f16c 100644 --- a/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java +++ b/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java @@ -90,6 +90,7 @@ public class ConstantConfig { private Boolean freemarkerWrapperAltMap; private BeanConfig xworkConverter; private Boolean mapperAlwaysSelectFullNamespace; + private Boolean actionConfigFallbackToEmptyNamespace; private BeanConfig localeProviderFactory; private String mapperIdParameterName; private Boolean ognlAllowStaticFieldAccess; @@ -225,6 +226,7 @@ public Map getAllAsStringsMap() { map.put(StrutsConstants.STRUTS_FREEMARKER_WRAPPER_ALT_MAP, Objects.toString(freemarkerWrapperAltMap, null)); map.put(StrutsConstants.STRUTS_XWORKCONVERTER, beanConfToString(xworkConverter)); map.put(StrutsConstants.STRUTS_ALWAYS_SELECT_FULL_NAMESPACE, Objects.toString(mapperAlwaysSelectFullNamespace, null)); + map.put(StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE, Objects.toString(actionConfigFallbackToEmptyNamespace, null)); map.put(StrutsConstants.STRUTS_LOCALE_PROVIDER_FACTORY, beanConfToString(localeProviderFactory)); map.put(StrutsConstants.STRUTS_ID_PARAMETER_NAME, mapperIdParameterName); map.put(StrutsConstants.STRUTS_ALLOW_STATIC_FIELD_ACCESS, Objects.toString(ognlAllowStaticFieldAccess, null)); @@ -812,6 +814,14 @@ public void setMapperAlwaysSelectFullNamespace(Boolean mapperAlwaysSelectFullNam this.mapperAlwaysSelectFullNamespace = mapperAlwaysSelectFullNamespace; } + public Boolean getActionConfigFallbackToEmptyNamespace() { + return actionConfigFallbackToEmptyNamespace; + } + + public void setActionConfigFallbackToEmptyNamespace(Boolean actionConfigFallbackToEmptyNamespace) { + this.actionConfigFallbackToEmptyNamespace = actionConfigFallbackToEmptyNamespace; + } + public BeanConfig getLocaleProviderFactory() { return localeProviderFactory; } diff --git a/core/src/main/resources/org/apache/struts2/default.properties b/core/src/main/resources/org/apache/struts2/default.properties index 96c5459fe6..8b7226a89b 100644 --- a/core/src/main/resources/org/apache/struts2/default.properties +++ b/core/src/main/resources/org/apache/struts2/default.properties @@ -215,6 +215,9 @@ struts.xslt.nocache=false ### Whether to always select the namespace to be everything before the last slash or not struts.mapper.alwaysSelectFullNamespace=false +### Whether to fallback to empty namespace when request namespace does not match any in configuration +struts.actionConfig.fallbackToEmptyNamespace=true + ### Whether to allow static field access in OGNL expressions or not struts.ognl.allowStaticFieldAccess=true diff --git a/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java b/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java index b52b9d48ca..520f8c2409 100644 --- a/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java @@ -31,6 +31,7 @@ import com.opensymphony.xwork2.mock.MockInterceptor; import com.opensymphony.xwork2.test.StubConfigurationProvider; import com.opensymphony.xwork2.util.location.LocatableProperties; +import org.apache.struts2.StrutsConstants; import org.apache.struts2.config.StrutsXmlConfigurationProvider; import org.apache.struts2.dispatcher.HttpParameters; @@ -239,6 +240,41 @@ public void testMultipleContainerProviders() { mockContainerProvider.verify(); } + public void testGetActionConfigFallbackToEmptyNamespaceWhenNamespaceDontMatchAndEmptyNamespaceFallbackIsEnabled() { + // struts.actionConfig.fallbackToEmptyNamespace default to true, so it is enabled + RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration(); + + // check namespace that doesn't match fallback to empty namespace + ActionConfig actionConfig = configuration.getActionConfig("/something/that/is/not/in/the/namespace/config", "LazyFoo"); + assertEquals("default", actionConfig.getPackageName()); // fallback to empty namespace (package name is default) + assertEquals("LazyFoo", actionConfig.getName()); + + // check non-empty namespace and name in config still matches + assertNotNull(configuration.getActionConfig("includeTest", "Foo")); + + // check root namespace and name in config still matches + actionConfig = configuration.getActionConfig("/", "LazyFoo"); + assertEquals("default", actionConfig.getPackageName()); + assertEquals("LazyFoo", actionConfig.getName()); + } + + public void testGetActionConfigReturnNullWhenNamespaceDontMatchAndEmptyNamespaceFallbackIsDisabled() { + // set the struts.actionConfig.fallbackToEmptyNamespace to false and reload the configuration + setStrutsConstant(StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE, "false"); + RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration(); + + // check namespace that doesn't match NOT fallback to empty namespace and return null + assertNull(configuration.getActionConfig("/something/that/is/not/in/the/namespace/config", "LazyFoo")); + + // check non-empty namespace and name in config still matches + assertNotNull(configuration.getActionConfig("includeTest", "Foo")); + + // check root namespace and name in config still matches + ActionConfig actionConfig = configuration.getActionConfig("/", "LazyFoo"); + assertEquals("default", actionConfig.getPackageName()); + assertEquals("LazyFoo", actionConfig.getName()); + } + public void testInitForPackageProviders() { loadConfigurationProviders(new StubConfigurationProvider() { diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java index 7f4b545819..b7db51513b 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java @@ -217,23 +217,9 @@ private void setDevMode(final boolean devMode) { /** * Overwrite the Struts Constant and reload container */ - private void setStrutsConstant(final Map overwritePropeties) { - configurationManager.addContainerProvider(new StubConfigurationProvider() { - @Override - public boolean needsReload() { - return true; - } - - @Override - public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { - for (Map.Entry stringStringEntry : overwritePropeties.entrySet()) { - props.setProperty(stringStringEntry.getKey(), stringStringEntry.getValue(), null); - } - } - }); - - configurationManager.reload(); - container = configurationManager.getConfiguration().getContainer(); + @Override + protected void setStrutsConstant(final Map overwritePropeties) { + super.setStrutsConstant(overwritePropeties); stack.getActionContext().withContainer(container); } -} \ No newline at end of file +}