This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/struts.git
commit 8be5c47e546a7eec9a74bd13812bb106d11c38ac Author: Lukasz Lenart <[email protected]> AuthorDate: Mon Nov 2 08:06:20 2026 +0100 WW-4164 feat(core): build action URLs with the target action's extension Co-Authored-By: Claude Opus 5.5 <[email protected]> --- .../org/apache/struts2/components/Component.java | 12 +++ .../struts2/components/ServletUrlRenderer.java | 1 + .../dispatcher/mapper/ActionExtensionResolver.java | 92 ++++++++++++++++ .../org/apache/struts2/result/PostbackResult.java | 15 ++- .../result/ServletActionRedirectResult.java | 17 ++- core/src/main/resources/struts-beans.xml | 1 + .../mapper/ActionExtensionResolverTest.java | 116 +++++++++++++++++++++ .../apache/struts2/result/PostbackResultTest.java | 33 ++++++ .../result/ServletActionRedirectResultTest.java | 33 ++++++ .../org/apache/struts2/views/jsp/URLTagTest.java | 18 ++++ .../apache/struts2/views/jsp/ui/FormTagTest.java | 18 ++++ .../struts2/json/JSONActionRedirectResultTest.java | 4 + 12 files changed, 358 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/components/Component.java b/core/src/main/java/org/apache/struts2/components/Component.java index 10e6373cc..d1e9f7d2c 100644 --- a/core/src/main/java/org/apache/struts2/components/Component.java +++ b/core/src/main/java/org/apache/struts2/components/Component.java @@ -31,6 +31,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.StrutsConstants; import org.apache.struts2.StrutsException; +import org.apache.struts2.dispatcher.mapper.ActionExtensionResolver; import org.apache.struts2.dispatcher.mapper.ActionMapper; import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.util.ComponentUtils; @@ -81,6 +82,7 @@ public class Component { protected ValueStack stack; protected Map<String, Object> attributes; protected ActionMapper actionMapper; + protected ActionExtensionResolver actionExtensionResolver; protected boolean throwExceptionOnELFailure; protected boolean performClearTagStateForTagPoolingServers = false; private UrlHelper urlHelper; @@ -121,6 +123,15 @@ public class Component { this.actionMapper = mapper; } + /** + * @param actionExtensionResolver chooses the extension of generated action URLs + * @since 7.5.0 + */ + @Inject + public void setActionExtensionResolver(ActionExtensionResolver actionExtensionResolver) { + this.actionExtensionResolver = actionExtensionResolver; + } + @Inject(StrutsConstants.STRUTS_EL_THROW_EXCEPTION) public void setThrowExceptionsOnELFailure(String throwException) { this.throwExceptionOnELFailure = BooleanUtils.toBoolean(throwException); @@ -422,6 +433,7 @@ public class Component { String finalMethod = method != null ? findString(method) : null; String finalNamespace = determineNamespace(namespace, getStack(), req); ActionMapping mapping = new ActionMapping(finalAction, finalNamespace, finalMethod, parameters); + actionExtensionResolver.applyTo(mapping); String uri = actionMapper.getUriFromActionMapping(mapping); return urlHelper.buildUrl(uri, req, res, parameters, scheme, includeContext, encodeResult, forceAddSchemeHostAndPort, escapeAmp); } diff --git a/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java b/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java index c6bfec306..438fe72ae 100644 --- a/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java +++ b/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java @@ -176,6 +176,7 @@ public class ServletUrlRenderer implements UrlRenderer { if (actionConfig != null) { ActionMapping mapping = new ActionMapping(actionName, namespace, actionMethod, formComponent.attributes); + formComponent.actionExtensionResolver.applyTo(mapping); String result = urlHelper.buildUrl(formComponent.actionMapper.getUriFromActionMapping(mapping), formComponent.request, formComponent.response, queryStringResult.getQueryParams(), scheme, formComponent.includeContext, true, false, false); formComponent.addParameter("action", result); diff --git a/core/src/main/java/org/apache/struts2/dispatcher/mapper/ActionExtensionResolver.java b/core/src/main/java/org/apache/struts2/dispatcher/mapper/ActionExtensionResolver.java new file mode 100644 index 000000000..b4d450cd8 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/dispatcher/mapper/ActionExtensionResolver.java @@ -0,0 +1,92 @@ +/* + * 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 org.apache.struts2.dispatcher.mapper; + +import org.apache.commons.lang3.StringUtils; +import org.apache.struts2.ActionContext; +import org.apache.struts2.StrutsConstants; +import org.apache.struts2.config.Configuration; +import org.apache.struts2.config.entities.ActionConfig; +import org.apache.struts2.inject.Inject; + +import java.util.List; + +/** + * Chooses the extension of a generated action URL from the extensions the target action accepts, so links, + * forms and redirects point at an extension the target can be reached by. + * + * @since 7.5.0 + */ +public class ActionExtensionResolver { + + private Configuration configuration; + private List<String> extensions; + + /** + * @param configuration the configuration used to look up the target action + * @since 7.5.0 + */ + @Inject + public void setConfiguration(Configuration configuration) { + this.configuration = configuration; + } + + /** + * @param extensions the global {@code struts.action.extension} list + * @since 7.5.0 + */ + @Inject(StrutsConstants.STRUTS_ACTION_EXTENSION) + public void setExtensions(String extensions) { + this.extensions = StringUtils.isNotEmpty(extensions) ? ActionExtensions.parse(extensions) : null; + } + + /** + * Sets the extension of {@code target} to the current request's extension when the target accepts it, otherwise + * to the target's first accepted extension, which is also used when the current request has no extension, as in + * {@link DefaultActionMapper#lookupExtension(String)}. Leaves {@code target} unchanged when it already has an extension, + * when the target action is unknown, or when neither the target nor the global constant declares extensions. + * + * @param target the mapping of the action the URL points at + * @since 7.5.0 + */ + public void applyTo(ActionMapping target) { + if (target.getExtension() != null) { + return; + } + ActionConfig actionConfig = configuration.getRuntimeConfiguration() + .getActionConfig(target.getNamespace(), StringUtils.substringBefore(target.getName(), "!")); + if (actionConfig == null) { + return; + } + List<String> effective = actionConfig.getExtensions() != null ? actionConfig.getExtensions() : extensions; + if (effective == null) { + return; + } + String current = currentExtension(); + target.setExtension(current == null ? effective.get(0) : ActionExtensions.select(effective, current)); + } + + private String currentExtension() { + ActionContext context = ActionContext.getContext(); + if (context == null || context.getActionMapping() == null) { + return null; + } + return context.getActionMapping().getExtension(); + } +} diff --git a/core/src/main/java/org/apache/struts2/result/PostbackResult.java b/core/src/main/java/org/apache/struts2/result/PostbackResult.java index 519feee9d..0ac987adb 100644 --- a/core/src/main/java/org/apache/struts2/result/PostbackResult.java +++ b/core/src/main/java/org/apache/struts2/result/PostbackResult.java @@ -24,6 +24,7 @@ import org.apache.struts2.inject.Inject; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.text.StringEscapeUtils; +import org.apache.struts2.dispatcher.mapper.ActionExtensionResolver; import org.apache.struts2.dispatcher.mapper.ActionMapper; import org.apache.struts2.dispatcher.mapper.ActionMapping; @@ -86,6 +87,7 @@ public class PostbackResult extends StrutsResultSupport { private boolean cache = true; protected ActionMapper actionMapper; + private transient ActionExtensionResolver actionExtensionResolver; @Override protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { @@ -152,7 +154,9 @@ public class PostbackResult extends StrutsResultSupport { } else { method = conditionalParse(method, invocation); } - postbackUri = request.getContextPath() + actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null)); + ActionMapping mapping = new ActionMapping(actionName, namespace, method, null); + actionExtensionResolver.applyTo(mapping); + postbackUri = request.getContextPath() + actionMapper.getUriFromActionMapping(mapping); } else { String location = getLocation(); // Do not prepend if the URL is a FQN @@ -173,6 +177,15 @@ public class PostbackResult extends StrutsResultSupport { this.actionMapper = mapper; } + /** + * @param actionExtensionResolver chooses the extension of the target action URL + * @since 7.5.0 + */ + @Inject + public void setActionExtensionResolver(ActionExtensionResolver actionExtensionResolver) { + this.actionExtensionResolver = actionExtensionResolver; + } + /** * Sets the name of the destination action. * diff --git a/core/src/main/java/org/apache/struts2/result/ServletActionRedirectResult.java b/core/src/main/java/org/apache/struts2/result/ServletActionRedirectResult.java index fe78231ae..b4cfd356d 100644 --- a/core/src/main/java/org/apache/struts2/result/ServletActionRedirectResult.java +++ b/core/src/main/java/org/apache/struts2/result/ServletActionRedirectResult.java @@ -19,8 +19,10 @@ package org.apache.struts2.result; import org.apache.struts2.ActionInvocation; +import org.apache.struts2.dispatcher.mapper.ActionExtensionResolver; import org.apache.struts2.dispatcher.mapper.ActionMapper; import org.apache.struts2.dispatcher.mapper.ActionMapping; +import org.apache.struts2.inject.Inject; import org.apache.struts2.util.reflection.ReflectionExceptionHandler; import java.io.Serial; @@ -132,6 +134,8 @@ public class ServletActionRedirectResult extends ServletRedirectResult implement protected String namespace; protected String method; + private transient ActionExtensionResolver actionExtensionResolver; + public ServletActionRedirectResult() { super(); } @@ -176,13 +180,24 @@ public class ServletActionRedirectResult extends ServletRedirectResult implement method = conditionalParse(method, invocation); } - String tmpLocation = actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null)); + ActionMapping mapping = new ActionMapping(actionName, namespace, method, null); + actionExtensionResolver.applyTo(mapping); + String tmpLocation = actionMapper.getUriFromActionMapping(mapping); setLocation(tmpLocation); super.execute(invocation); } + /** + * @param actionExtensionResolver chooses the extension of the target action URL + * @since 7.5.0 + */ + @Inject + public void setActionExtensionResolver(ActionExtensionResolver actionExtensionResolver) { + this.actionExtensionResolver = actionExtensionResolver; + } + /** * Sets the action name * diff --git a/core/src/main/resources/struts-beans.xml b/core/src/main/resources/struts-beans.xml index 922dc91d7..067bba743 100644 --- a/core/src/main/resources/struts-beans.xml +++ b/core/src/main/resources/struts-beans.xml @@ -119,6 +119,7 @@ <bean type="org.apache.struts2.conversion.UserConversionPropertiesProvider" name="struts" class="org.apache.struts2.conversion.StrutsConversionPropertiesProcessor"/> <bean class="org.apache.struts2.conversion.UserConversionPropertiesProcessor" scope="singleton"/> + <bean class="org.apache.struts2.dispatcher.mapper.ActionExtensionResolver" scope="singleton"/> <bean class="org.apache.struts2.conversion.impl.XWorkBasicConverter"/> diff --git a/core/src/test/java/org/apache/struts2/dispatcher/mapper/ActionExtensionResolverTest.java b/core/src/test/java/org/apache/struts2/dispatcher/mapper/ActionExtensionResolverTest.java new file mode 100644 index 000000000..061302865 --- /dev/null +++ b/core/src/test/java/org/apache/struts2/dispatcher/mapper/ActionExtensionResolverTest.java @@ -0,0 +1,116 @@ +/* + * 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 org.apache.struts2.dispatcher.mapper; + +import org.apache.struts2.ActionContext; +import org.apache.struts2.StrutsInternalTestCase; +import org.apache.struts2.config.StrutsXmlConfigurationProvider; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ActionExtensionResolverTest extends StrutsInternalTestCase { + + private static final String CONFIG = "org/apache/struts2/config/providers/struts-extension.xml"; + + private ActionExtensionResolver resolver; + + @Override + protected void setUp() throws Exception { + super.setUp(); + loadConfigurationProviders(new StrutsXmlConfigurationProvider(CONFIG)); + resolver = new ActionExtensionResolver(); + resolver.setConfiguration(configuration); + resolver.setExtensions("action,"); + } + + public void testDeclaredTargetUsesFirstWhenCurrentNotAllowed() { + withCurrentExtension("action"); + + assertThat(resolve("/child", "inherits")).isEqualTo("html"); + } + + public void testDeclaredTargetKeepsAllowedCurrent() { + withCurrentExtension("json"); + + assertThat(resolve("/child", "own")).isEqualTo("json"); + } + + public void testUndeclaredTargetFromDeclaredOnlyPage() { + withCurrentExtension("html"); + + assertThat(resolve("/plain", "undeclared")).isEqualTo("action"); + } + + public void testUndeclaredTargetKeepsGlobalCurrent() { + withCurrentExtension(""); + + assertThat(resolve("/plain", "undeclared")).isEmpty(); + } + + public void testExplicitExtensionUntouched() { + withCurrentExtension("action"); + ActionMapping target = new ActionMapping("inherits", "/child", null, null); + target.setExtension(""); + + resolver.applyTo(target); + + assertThat(target.getExtension()).isEmpty(); + } + + public void testUnknownActionUntouched() { + withCurrentExtension("action"); + + assertThat(resolve("/nowhere", "x")).isNull(); + } + + public void testDmiNameResolvesActionConfig() { + withCurrentExtension("action"); + + assertThat(resolve("/child", "inherits!foo")).isEqualTo("html"); + } + + public void testWithoutCurrentMappingUsesFirstAllowed() { + assertThat(resolve("/child", "inherits")).isEqualTo("html"); + } + + public void testRequestWithoutExtensionUsesGlobalDefault() { + withCurrentExtension(null); + + assertThat(resolve("/plain", "undeclared")).isEqualTo("action"); + } + + public void testNoDeclaredAndNoGlobalListUntouched() { + resolver.setExtensions(""); + withCurrentExtension("action"); + + assertThat(resolve("/plain", "undeclared")).isNull(); + } + + private String resolve(String namespace, String name) { + ActionMapping target = new ActionMapping(name, namespace, null, null); + resolver.applyTo(target); + return target.getExtension(); + } + + private void withCurrentExtension(String extension) { + ActionMapping current = new ActionMapping("current", "/", null, null); + current.setExtension(extension); + ActionContext.getContext().withActionMapping(current); + } +} diff --git a/core/src/test/java/org/apache/struts2/result/PostbackResultTest.java b/core/src/test/java/org/apache/struts2/result/PostbackResultTest.java index c17bfdbd3..1d1e44d27 100644 --- a/core/src/test/java/org/apache/struts2/result/PostbackResultTest.java +++ b/core/src/test/java/org/apache/struts2/result/PostbackResultTest.java @@ -23,7 +23,9 @@ import org.apache.struts2.ActionInvocation; import org.apache.struts2.ActionProxy; import org.apache.struts2.ServletActionContext; import org.apache.struts2.StrutsInternalTestCase; +import org.apache.struts2.dispatcher.mapper.ActionExtensionResolver; import org.apache.struts2.dispatcher.mapper.ActionMapper; +import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.dispatcher.mapper.DefaultActionMapper; import org.apache.struts2.util.ValueStack; import org.easymock.IMocksControl; @@ -58,6 +60,7 @@ public class PostbackResultTest extends StrutsInternalTestCase { control.replay(); result.setActionMapper(container.getInstance(ActionMapper.class)); + result.setActionExtensionResolver(container.getInstance(ActionExtensionResolver.class)); result.execute(mockInvocation); assertEquals("<!DOCTYPE html><html><body><form action=\"${1-1}/myAction0.action\" method=\"POST\">" + "<script>setTimeout(function(){document.forms[0].submit();},0);</script></html>", res.getContentAsString()); @@ -86,6 +89,7 @@ public class PostbackResultTest extends StrutsInternalTestCase { control.replay(); result.setActionMapper(container.getInstance(ActionMapper.class)); + result.setActionExtensionResolver(container.getInstance(ActionExtensionResolver.class)); result.execute(mockInvocation); assertEquals("<!DOCTYPE html><html><body><form action=\"myNamespace0/myAction0.action\" method=\"POST\">" + "<script>setTimeout(function(){document.forms[0].submit();},0);</script></html>", res.getContentAsString()); @@ -120,6 +124,7 @@ public class PostbackResultTest extends StrutsInternalTestCase { DefaultActionMapper mapper = (DefaultActionMapper) container.getInstance(ActionMapper.class); mapper.setAllowDynamicMethodCalls("true"); result.setActionMapper(mapper); + result.setActionExtensionResolver(container.getInstance(ActionExtensionResolver.class)); result.execute(mockInvocation); assertEquals("<!DOCTYPE html><html><body><form action=\"/myNamespace${1-1}/myAction${1-1}!myMethod${1-1}.action\" method=\"POST\">" + "<script>setTimeout(function(){document.forms[0].submit();},0);</script></html>", res.getContentAsString()); @@ -249,4 +254,32 @@ public class PostbackResultTest extends StrutsInternalTestCase { control.verify(); } + public void testPostbackUsesDeclaredExtension() throws Exception { + initDispatcherWithConfigs("struts-default.xml,org/apache/struts2/config/providers/struts-extension.xml"); + ActionContext context = ActionContext.getContext(); + MockHttpServletRequest req = new MockHttpServletRequest(); + MockHttpServletResponse res = new MockHttpServletResponse(); + context.put(ServletActionContext.HTTP_REQUEST, req); + context.put(ServletActionContext.HTTP_RESPONSE, res); + ActionMapping current = new ActionMapping("current", "/", null, null); + current.setExtension("action"); + context.withActionMapping(current); + + PostbackResult result = new PostbackResult(); + result.setActionName("inherits"); + result.setNamespace("/child"); + result.setPrependServletContext(false); + + IMocksControl control = createControl(); + ActionInvocation mockInvocation = control.createMock(ActionInvocation.class); + expect(mockInvocation.getInvocationContext()).andReturn(context).anyTimes(); + expect(mockInvocation.getStack()).andReturn(context.getValueStack()).anyTimes(); + + control.replay(); + container.inject(result); + result.execute(mockInvocation); + + assertTrue(res.getContentAsString(), res.getContentAsString().contains("<form action=\"/child/inherits.html\"")); + } + } diff --git a/core/src/test/java/org/apache/struts2/result/ServletActionRedirectResultTest.java b/core/src/test/java/org/apache/struts2/result/ServletActionRedirectResultTest.java index 9436422d5..337ce3188 100644 --- a/core/src/test/java/org/apache/struts2/result/ServletActionRedirectResultTest.java +++ b/core/src/test/java/org/apache/struts2/result/ServletActionRedirectResultTest.java @@ -28,6 +28,7 @@ import org.apache.struts2.util.ValueStack; import org.apache.struts2.ServletActionContext; import org.apache.struts2.StrutsInternalTestCase; import org.apache.struts2.dispatcher.mapper.ActionMapper; +import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.dispatcher.mapper.DefaultActionMapper; import org.apache.struts2.url.StrutsQueryStringBuilder; import org.apache.struts2.url.StrutsUrlEncoder; @@ -303,4 +304,36 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase { assertNotNull(result); } + public void testRedirectUsesDeclaredExtension() throws Exception { + initDispatcherWithConfigs("struts-default.xml,org/apache/struts2/config/providers/struts-extension.xml"); + ActionContext context = ActionContext.getContext(); + MockHttpServletRequest req = new MockHttpServletRequest(); + MockHttpServletResponse res = new MockHttpServletResponse(); + context.put(ServletActionContext.HTTP_REQUEST, req); + context.put(ServletActionContext.HTTP_RESPONSE, res); + ActionMapping current = new ActionMapping("current", "/", null, null); + current.setExtension("action"); + context.withActionMapping(current); + + ServletActionRedirectResult result = new ServletActionRedirectResult(); + result.setActionName("inherits"); + result.setNamespace("/child"); + result.setPrependServletContext(false); + + IMocksControl control = createControl(); + ActionProxy mockActionProxy = control.createMock(ActionProxy.class); + ActionInvocation mockInvocation = control.createMock(ActionInvocation.class); + expect(mockInvocation.getProxy()).andReturn(mockActionProxy).anyTimes(); + expect(mockInvocation.getResultCode()).andReturn("myResult").anyTimes(); + expect(mockActionProxy.getConfig()).andReturn(new ActionConfig.Builder("", "", "").build()).anyTimes(); + expect(mockInvocation.getInvocationContext()).andReturn(context).anyTimes(); + expect(mockInvocation.getStack()).andReturn(context.getValueStack()).anyTimes(); + + control.replay(); + container.inject(result); + result.execute(mockInvocation); + + assertEquals("/child/inherits.html", res.getRedirectedUrl()); + } + } diff --git a/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java index 276d9163c..588134809 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java @@ -2104,6 +2104,24 @@ public class URLTagTest extends AbstractUITagTest { assertEquals("/books#v=twopage&q&f=false", writer.toString()); } + public void testActionUrlUsesDeclaredExtension() throws Exception { + initDispatcherWithConfigs("struts-default.xml,org/apache/struts2/config/providers/struts-extension.xml"); + createMocks(); + ActionMapping current = new ActionMapping("current", "/", null, null); + current.setExtension("action"); + ActionContext.getContext().withActionMapping(current); + + URLTag urlTag = new URLTag(); + urlTag.setPageContext(pageContext); + urlTag.setAction("inherits"); + urlTag.setNamespace("/child"); + + urlTag.doStartTag(); + urlTag.doEndTag(); + + assertEquals("/child/inherits.html", writer.toString()); + } + @Override protected void setUp() throws Exception { super.setUp(); diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/FormTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/FormTagTest.java index b8a89e712..80444b45a 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/FormTagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/FormTagTest.java @@ -27,6 +27,7 @@ import org.apache.struts2.StrutsConstants; import org.apache.struts2.TestAction; import org.apache.struts2.TestConfigurationProvider; import org.apache.struts2.dispatcher.mapper.ActionMapper; +import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.dispatcher.mapper.DefaultActionMapper; import org.apache.struts2.views.jsp.AbstractUITagTest; import org.apache.struts2.views.jsp.ActionTag; @@ -40,6 +41,23 @@ import java.util.HashMap; */ public class FormTagTest extends AbstractUITagTest { + public void testFormActionUsesDeclaredExtension() throws Exception { + initDispatcherWithConfigs("struts-default.xml,org/apache/struts2/config/providers/struts-extension.xml"); + createMocks(); + ActionMapping current = new ActionMapping("current", "/", null, null); + current.setExtension("action"); + ActionContext.getContext().withActionMapping(current); + + FormTag tag = new FormTag(); + tag.setPageContext(pageContext); + tag.setNamespace("/child"); + tag.setAction("inherits"); + + tag.doStartTag(); + tag.doEndTag(); + + assertTrue(writer.toString(), writer.toString().contains("action=\"/child/inherits.html\"")); + } public void testFormWithActionAttributeContainingQueryString() throws Exception { FormTag tag = new FormTag(); diff --git a/plugins/json/src/test/java/org/apache/struts2/json/JSONActionRedirectResultTest.java b/plugins/json/src/test/java/org/apache/struts2/json/JSONActionRedirectResultTest.java index a1c7c44fa..3204ae5e6 100644 --- a/plugins/json/src/test/java/org/apache/struts2/json/JSONActionRedirectResultTest.java +++ b/plugins/json/src/test/java/org/apache/struts2/json/JSONActionRedirectResultTest.java @@ -24,6 +24,7 @@ import org.apache.struts2.mock.MockActionInvocation; import org.apache.struts2.mock.MockActionProxy; import org.apache.struts2.util.ValueStack; import org.apache.struts2.StrutsStatics; +import org.apache.struts2.dispatcher.mapper.ActionExtensionResolver; import org.apache.struts2.dispatcher.mapper.DefaultActionMapper; import org.apache.struts2.junit.StrutsTestCase; import org.apache.struts2.url.QueryStringBuilder; @@ -49,6 +50,7 @@ public class JSONActionRedirectResultTest extends StrutsTestCase { JSONActionRedirectResult result = new JSONActionRedirectResult(); result.setActionName("targetAction"); result.setActionMapper(actionMapper); + result.setActionExtensionResolver(container.getInstance(ActionExtensionResolver.class)); result.setQueryStringBuilder(queryStringBuilder); Object action = new Object(); @@ -68,6 +70,7 @@ public class JSONActionRedirectResultTest extends StrutsTestCase { JSONActionRedirectResult result = new JSONActionRedirectResult(); result.setActionName("targetAction"); result.setActionMapper(actionMapper); + result.setActionExtensionResolver(container.getInstance(ActionExtensionResolver.class)); result.setQueryStringBuilder(queryStringBuilder); request.setParameter("struts.enableJSONValidation", "true"); @@ -88,6 +91,7 @@ public class JSONActionRedirectResultTest extends StrutsTestCase { JSONActionRedirectResult result = new JSONActionRedirectResult(); result.setActionName("targetAction"); result.setActionMapper(actionMapper); + result.setActionExtensionResolver(container.getInstance(ActionExtensionResolver.class)); result.setQueryStringBuilder(queryStringBuilder); request.setParameter("struts.enableJSONValidation", "true");
