This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-5782-anchor-href-params in repository https://gitbox.apache.org/repos/asf/struts.git
commit c58dfad3914c201665b9f2425a56d709c37adf2d Author: Lukasz Lenart <[email protected]> AuthorDate: Fri Oct 16 11:27:54 2026 +0200 WW-5782 fix(tags): document and warn that s:a href ignores nested params With an explicit href the anchor tag never applies nested <s:param> tags; they are only used when the URL is built from value or action. The javadoc example showed exactly that combination. The example now uses value, the javadoc and the href attribute description say nested params are not applied to href, and in devMode the tag logs a warning naming the ignored params. Rendered output is unchanged; applying params to href is WW-5783 (8.0.0). Co-Authored-By: Claude Opus 5.5 <[email protected]> --- .../java/org/apache/struts2/components/Anchor.java | 11 +++-- core/src/site/resources/tags/a-attributes.html | 2 +- .../apache/struts2/views/jsp/AnchorTagTest.java | 50 ++++++++++++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/apache/struts2/components/Anchor.java b/core/src/main/java/org/apache/struts2/components/Anchor.java index 6bda43f20..9e7e09fc7 100644 --- a/core/src/main/java/org/apache/struts2/components/Anchor.java +++ b/core/src/main/java/org/apache/struts2/components/Anchor.java @@ -38,7 +38,8 @@ import java.util.Map; * <!-- START SNIPPET: javadoc --> * <p> * A tag that creates a HTML <a >.This tag supports the same attributes as the "url" tag, - * including nested parameters using the "param" tag. + * including nested parameters using the "param" tag. Nested parameters are applied only when the URL is built + * from the "value" or "action" attributes; they are ignored when "href" is set. * </p> * <!-- END SNIPPET: javadoc --> * @@ -46,7 +47,7 @@ import java.util.Map; * * <pre> * <!-- START SNIPPET: example1 --> - * <s:a id="link1" href="/do-it.action"> + * <s:a id="link1" value="/do-it.action"> * <img border="none" src="<%=request.getContextPath()%>/images/delete.gif"/> * <s:param name="id" value="1"/> * </s:a> @@ -101,6 +102,10 @@ public class Anchor extends ClosingUIBean { if (href != null) { addParameter("href", ensureAttributeSafelyNotEscaped(findString(href))); + if (devMode && !urlParameters.isEmpty()) { + LOG.warn("Nested params {} are ignored because <s:a> has href [{}], use value or action to build a URL with params", + urlParameters.keySet(), href); + } } else { //no href, build it from URL attributes StringWriter sw = new StringWriter(); @@ -189,7 +194,7 @@ public class Anchor extends ClosingUIBean { return urlProvider; } - @StrutsTagAttribute(description = "The URL.") + @StrutsTagAttribute(description = "The URL. Nested param tags are not applied, use value or action to build a URL with params") public void setHref(String href) { this.href = href; } diff --git a/core/src/site/resources/tags/a-attributes.html b/core/src/site/resources/tags/a-attributes.html index 48ba23f96..738d0bc36 100644 --- a/core/src/site/resources/tags/a-attributes.html +++ b/core/src/site/resources/tags/a-attributes.html @@ -123,7 +123,7 @@ <td class="tag-attribute"></td> <td class="tag-attribute">false</td> <td class="tag-attribute">String</td> - <td class="tag-attribute">The URL.</td> + <td class="tag-attribute">The URL. Nested param tags are not applied, use value or action to build a URL with params</td> </tr> <tr> <td class="tag-attribute">id</td> diff --git a/core/src/test/java/org/apache/struts2/views/jsp/AnchorTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/AnchorTagTest.java index 7bf07bd68..1b9047a2b 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/AnchorTagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/AnchorTagTest.java @@ -18,6 +18,8 @@ */ package org.apache.struts2.views.jsp; +import org.apache.struts2.components.Anchor; +import org.apache.struts2.test.LogCapture; import org.apache.struts2.views.jsp.ui.AnchorTag; import org.apache.struts2.views.jsp.ui.StrutsBodyContent; @@ -1646,4 +1648,52 @@ public class AnchorTagTest extends AbstractUITagTest { pageContext.setJspWriter(jspWriter); } + public void testHrefIgnoresNestedParams() throws Exception { + tag.setHref("/do-it.action"); + + try (LogCapture logs = new LogCapture(Anchor.class)) { + renderWithParam("id", "%{'1'}"); + + assertEquals(wrapWithAnchor("/do-it.action"), writer.toString()); + assertTrue(logs.messages().toString(), logs.messages().isEmpty()); + } + } + + public void testHrefWithNestedParamsWarnsInDevMode() throws Exception { + setDevMode(true); + tag.setHref("/do-it.action"); + + try (LogCapture logs = new LogCapture(Anchor.class)) { + renderWithParam("id", "%{'1'}"); + + assertEquals(wrapWithAnchor("/do-it.action"), writer.toString()); + assertEquals(1, logs.messages().size()); + assertTrue(logs.messages().get(0), logs.messages().get(0).contains("[id]")); + } + } + + public void testValueWithNestedParamsDoesNotWarnInDevMode() throws Exception { + setDevMode(true); + tag.setValue("/do-it.action"); + + try (LogCapture logs = new LogCapture(Anchor.class)) { + renderWithParam("id", "%{'1'}"); + + assertEquals(wrapWithAnchor("/do-it.action?id=1"), writer.toString()); + assertTrue(logs.messages().toString(), logs.messages().isEmpty()); + } + } + + private void renderWithParam(String name, String value) throws JspException { + ParamTag param = new ParamTag(); + param.setPageContext(pageContext); + param.setName(name); + param.setValue(value); + + tag.doStartTag(); + param.doStartTag(); + param.doEndTag(); + tag.doEndTag(); + } + }
