diff --git a/core/src/main/java/org/apache/struts2/components/CheckboxList.java b/core/src/main/java/org/apache/struts2/components/CheckboxList.java index e83091888..601ce5bdf 100644 --- a/core/src/main/java/org/apache/struts2/components/CheckboxList.java +++ b/core/src/main/java/org/apache/struts2/components/CheckboxList.java @@ -48,7 +48,7 @@ import com.opensymphony.xwork2.util.ValueStack; allowDynamicAttributes = true) public class CheckboxList extends ListUIBean { final public static String TEMPLATE = "checkboxlist"; - + public CheckboxList(ValueStack stack, HttpServletRequest request, HttpServletResponse response) { super(stack, request, response); } @@ -56,9 +56,19 @@ public class CheckboxList extends ListUIBean { protected String getDefaultTemplate() { return TEMPLATE; } - + public void evaluateExtraParams() { super.evaluateExtraParams(); } -} \ No newline at end of file + /** + * Checkboxlist tag requires lazy evaluation as list of tags is dynamically generated using + * + * @return boolean true by default + */ + @Override + protected boolean lazyEvaluation() { + return true; + } + +} diff --git a/core/src/main/java/org/apache/struts2/components/ListUIBean.java b/core/src/main/java/org/apache/struts2/components/ListUIBean.java index 26484f250..bfaffe6f1 100644 --- a/core/src/main/java/org/apache/struts2/components/ListUIBean.java +++ b/core/src/main/java/org/apache/struts2/components/ListUIBean.java @@ -195,7 +195,6 @@ public abstract class ListUIBean extends UIBean { this.listTitle = listTitle; } - public void setThrowExceptionOnNullValueAttribute(boolean throwExceptionOnNullValueAttribute) { this.throwExceptionOnNullValueAttribute = throwExceptionOnNullValueAttribute; } diff --git a/core/src/main/java/org/apache/struts2/components/Radio.java b/core/src/main/java/org/apache/struts2/components/Radio.java index 0315cb65b..ba5eb471f 100644 --- a/core/src/main/java/org/apache/struts2/components/Radio.java +++ b/core/src/main/java/org/apache/struts2/components/Radio.java @@ -57,7 +57,7 @@ import javax.servlet.http.HttpServletResponse; allowDynamicAttributes = true) public class Radio extends ListUIBean { final public static String TEMPLATE = "radiomap"; - + public Radio(ValueStack stack, HttpServletRequest request, HttpServletResponse response) { super(stack, request, response); } @@ -65,8 +65,19 @@ public class Radio extends ListUIBean { protected String getDefaultTemplate() { return TEMPLATE; } - + public void evaluateExtraParams() { super.evaluateExtraParams(); } -} \ No newline at end of file + + /** + * Radio tag requires lazy evaluation as list of tags is dynamically generated using + * + * @return boolean true by default + */ + @Override + protected boolean lazyEvaluation() { + return true; + } + +} diff --git a/core/src/main/java/org/apache/struts2/components/UIBean.java b/core/src/main/java/org/apache/struts2/components/UIBean.java index 24b47fa14..85b3d1bb9 100644 --- a/core/src/main/java/org/apache/struts2/components/UIBean.java +++ b/core/src/main/java/org/apache/struts2/components/UIBean.java @@ -20,7 +20,9 @@ package org.apache.struts2.components; import com.opensymphony.xwork2.config.ConfigurationException; import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.TextParseUtil; import com.opensymphony.xwork2.util.ValueStack; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -31,6 +33,7 @@ import org.apache.struts2.components.template.TemplateEngine; import org.apache.struts2.components.template.TemplateEngineManager; import org.apache.struts2.components.template.TemplateRenderingContext; import org.apache.struts2.dispatcher.StaticContentLoader; +import org.apache.struts2.util.ComponentUtils; import org.apache.struts2.util.TextProviderHelper; import org.apache.struts2.views.annotations.StrutsTagAttribute; import org.apache.struts2.views.util.ContextUtil; @@ -1272,10 +1275,16 @@ public abstract class UIBean extends Component { public void setDynamicAttributes(Map tagDynamicAttributes) { for (Map.Entry entry : tagDynamicAttributes.entrySet()) { - String entryKey = entry.getKey(); + String attrName = entry.getKey(); + String attrValue = entry.getValue(); - if (!isValidTagAttribute(entryKey)) { - dynamicAttributes.put(entryKey, entry.getValue()); + if (!isValidTagAttribute(attrName)) { + if (ComponentUtils.containsExpression(attrValue) && !lazyEvaluation()) { + String translated = TextParseUtil.translateVariables('%', attrValue, stack); + dynamicAttributes.put(attrName, ObjectUtils.defaultIfNull(translated, attrValue)); + } else { + dynamicAttributes.put(attrName, attrValue); + } } } } @@ -1296,4 +1305,14 @@ public abstract class UIBean extends Component { } } + /** + * Used to avoid evaluating attributes in {@link #evaluateParams()} or {@link #evaluateExtraParams()} + * as evaluation will happen in tag's template + * + * @return boolean false if evaluation should be performed in ftl + */ + protected boolean lazyEvaluation() { + return false; + } + } diff --git a/core/src/main/java/org/apache/struts2/components/template/FreemarkerTemplateEngine.java b/core/src/main/java/org/apache/struts2/components/template/FreemarkerTemplateEngine.java index 3bbd2196b..91e155867 100644 --- a/core/src/main/java/org/apache/struts2/components/template/FreemarkerTemplateEngine.java +++ b/core/src/main/java/org/apache/struts2/components/template/FreemarkerTemplateEngine.java @@ -148,16 +148,13 @@ public class FreemarkerTemplateEngine extends BaseTemplateEngine { } }; - LOG.debug("Puts action on the top of ValueStack, just before the tag"); - action = stack.pop(); + LOG.debug("Push tag on top of the stack"); stack.push(templateContext.getTag()); - stack.push(action); try { template.process(model, writer); } finally { - stack.pop(); // removes action - stack.pop(); // removes tag - stack.push(action); // puts back action + LOG.debug("Removes tag from top of the stack"); + stack.pop(); } } diff --git a/core/src/main/resources/template/simple/checkboxlist.ftl b/core/src/main/resources/template/simple/checkboxlist.ftl index 3fa27f189..87bef6651 100644 --- a/core/src/main/resources/template/simple/checkboxlist.ftl +++ b/core/src/main/resources/template/simple/checkboxlist.ftl @@ -30,7 +30,7 @@ <#assign itemKeyStr = stack.findString('top')> <#if parameters.listLabelKey??> - <#-- checks the valueStack for the 'valueKey.' The valueKey is then looked-up in the locale + <#-- checks the valueStack for the 'valueKey.' The valueKey is then looked-up in the locale file for it's localized value. This is then used as a label --> <#assign itemValue = struts.getText(stack.findString(parameters.listLabelKey))/> <#elseif parameters.listValue??> @@ -95,9 +95,10 @@ <#include "/${parameters.templateDir}/${parameters.expandTheme}/css.ftl" /> <#include "/${parameters.templateDir}/${parameters.expandTheme}/scripting-events.ftl" /> <#include "/${parameters.templateDir}/${parameters.expandTheme}/common-attributes.ftl" /> + <#global evaluate_dynamic_attributes = true/> <#include "/${parameters.templateDir}/${parameters.expandTheme}/dynamic-attributes.ftl" /> /> - + <#if parameters.id?has_content> for="${parameters.id}-${itemCount}"<#rt/> <#else> @@ -106,11 +107,10 @@ class="checkboxLabel">${itemValue} <#else> -   <#if parameters.disabled!false> disabled="disabled"<#rt/> - /> \ No newline at end of file + /> diff --git a/core/src/main/resources/template/simple/dynamic-attributes.ftl b/core/src/main/resources/template/simple/dynamic-attributes.ftl index 47a91139f..7f15aa48f 100644 --- a/core/src/main/resources/template/simple/dynamic-attributes.ftl +++ b/core/src/main/resources/template/simple/dynamic-attributes.ftl @@ -30,7 +30,11 @@ <#list aKeys?filter(acceptKey) as aKey><#rt/> <#assign keyValue = parameters.dynamicAttributes.get(aKey)/> <#if keyValue?is_string> - <#assign value = struts.translateVariables(keyValue)!keyValue/> + <#if evaluate_dynamic_attributes!false == true> + <#assign value = struts.translateVariables(keyValue)!keyValue/><#rt/> + <#else> + <#assign value = keyValue/><#rt/> + <#else> <#assign value = keyValue?string/> diff --git a/core/src/main/resources/template/simple/radiomap.ftl b/core/src/main/resources/template/simple/radiomap.ftl index 5c37a4b4b..597300e95 100644 --- a/core/src/main/resources/template/simple/radiomap.ftl +++ b/core/src/main/resources/template/simple/radiomap.ftl @@ -27,7 +27,7 @@ <#assign itemKeyStr = stack.findString('top')> <#if parameters.listValueKey??> - <#-- checks the valueStack for the 'valueKey.' The valueKey is then looked-up in the locale + <#-- checks the valueStack for the 'valueKey.' The valueKey is then looked-up in the locale file for it's localized value. This is then used as a label --> <#assign valueKey = stack.findString(parameters.listValueKey)!''/> <#if valueKey?has_content> @@ -94,9 +94,10 @@ <#include "/${parameters.templateDir}/${parameters.expandTheme}/css.ftl" /> <#include "/${parameters.templateDir}/${parameters.expandTheme}/scripting-events.ftl" /> <#include "/${parameters.templateDir}/${parameters.expandTheme}/common-attributes.ftl" /> +<#global evaluate_dynamic_attributes = true/> <#include "/${parameters.templateDir}/${parameters.expandTheme}/dynamic-attributes.ftl" /> /><#rt/> - \ No newline at end of file + diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/TextfieldTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/TextfieldTest.java index cf300e806..f42fdbccf 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/TextfieldTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/TextfieldTest.java @@ -347,6 +347,7 @@ public class TextfieldTest extends AbstractUITagTest { tag.setValue("%{foo}"); tag.setSize("10"); tag.setDynamicAttribute(null, "anotherAttr", "%{foo}"); + tag.setDynamicAttribute(null, "secondAttr", "second_%{foo}"); tag.doStartTag(); tag.doEndTag(); @@ -373,6 +374,7 @@ public class TextfieldTest extends AbstractUITagTest { tag.setValue("%{foo}"); tag.setSize("10"); tag.setDynamicAttribute(null, "anotherAttr", "%{foo}"); + tag.setDynamicAttribute(null, "secondAttr", "second_%{foo}"); tag.doStartTag(); setComponentTagClearTagState(tag, true); // Ensure component tag state clearing is set true (to match tag). diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Textfield-5.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Textfield-5.txt index 281847627..5dc125c18 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Textfield-5.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Textfield-5.txt @@ -1,4 +1,4 @@ - +