diff --git a/apps/pom.xml b/apps/pom.xml index 791f306ba..54e3b841a 100644 --- a/apps/pom.xml +++ b/apps/pom.xml @@ -24,7 +24,7 @@ org.apache.struts struts2-parent - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT struts2-apps pom diff --git a/apps/rest-showcase/pom.xml b/apps/rest-showcase/pom.xml index 9264dcddd..56533ec53 100644 --- a/apps/rest-showcase/pom.xml +++ b/apps/rest-showcase/pom.xml @@ -24,12 +24,12 @@ org.apache.struts struts2-apps - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT struts2-rest-showcase war - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT Struts 2 Rest Showcase Webapp Struts 2 Rest Showcase Example diff --git a/apps/showcase/pom.xml b/apps/showcase/pom.xml index 78564ac36..9ce5d0b6c 100644 --- a/apps/showcase/pom.xml +++ b/apps/showcase/pom.xml @@ -24,7 +24,7 @@ org.apache.struts struts2-apps - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT struts2-showcase diff --git a/apps/showcase/src/main/java/org/apache/struts2/showcase/validation/BeanValidationExampleAction.java b/apps/showcase/src/main/java/org/apache/struts2/showcase/validation/BeanValidationExampleAction.java index 67190e035..4c07f51e9 100644 --- a/apps/showcase/src/main/java/org/apache/struts2/showcase/validation/BeanValidationExampleAction.java +++ b/apps/showcase/src/main/java/org/apache/struts2/showcase/validation/BeanValidationExampleAction.java @@ -27,22 +27,24 @@ import org.apache.struts2.convention.annotation.Namespace; import org.apache.struts2.convention.annotation.ParentPackage; import org.apache.struts2.convention.annotation.Result; import org.apache.struts2.interceptor.validation.SkipValidation; -import org.hibernate.validator.constraints.Email; -import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.ScriptAssert; import org.hibernate.validator.constraints.URL; -import javax.validation.constraints.*; +import javax.validation.constraints.Email; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.util.Date; -/** - * - */ +// @Namespace("/bean-validation") @ParentPackage("bean-validation") @Action(results = { - @Result(name = "input", location = "bean-validation.jsp"), - @Result(name = "success", location = "/WEB-INF/validation/successFieldValidatorsExample.jsp") + @Result(name = "input", location = "bean-validation.jsp"), + @Result(name = "success", location = "/WEB-INF/validation/successFieldValidatorsExample.jsp") }) @FieldMatch(first = "fieldExpressionValidatorField", second = "requiredValidatorField", message = "requiredValidatorField and fieldExpressionValidatorField are not matching") @ScriptAssert(lang = "javascript", script = "_this.dateValidatorField != null && _this.dateValidatorField.before(new java.util.Date())", message = "Date need to before now") @@ -82,10 +84,10 @@ public class BeanValidationExampleAction extends ActionSupport { private String fieldExpressionValidatorField = null; @Action(value = "bean-validation", results = { - @Result(name = "success", location = "bean-validation.jsp") + @Result(name = "success", location = "bean-validation.jsp") }) @SkipValidation - public String beanValidation(){ + public String beanValidation() { return SUCCESS; } @@ -150,7 +152,7 @@ public class BeanValidationExampleAction extends ActionSupport { } public void setFieldExpressionValidatorField( - String fieldExpressionValidatorField) { + String fieldExpressionValidatorField) { this.fieldExpressionValidatorField = fieldExpressionValidatorField; } @@ -163,8 +165,4 @@ public class BeanValidationExampleAction extends ActionSupport { } } -/** - * - */ - - +// diff --git a/apps/showcase/src/main/webapp/WEB-INF/decorators/main.jsp b/apps/showcase/src/main/webapp/WEB-INF/decorators/main.jsp index 39a3a03ea..0d09eb647 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/decorators/main.jsp +++ b/apps/showcase/src/main/webapp/WEB-INF/decorators/main.jsp @@ -113,7 +113,7 @@ - Download image file. + Download image file.
@@ -47,7 +47,7 @@
- Download ZIP file. + Download ZIP file.
diff --git a/apps/showcase/src/main/webapp/WEB-INF/hangman/hangmanNonAjax.ftl b/apps/showcase/src/main/webapp/WEB-INF/hangman/hangmanNonAjax.ftl index 046ab4bab..3c3251892 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/hangman/hangmanNonAjax.ftl +++ b/apps/showcase/src/main/webapp/WEB-INF/hangman/hangmanNonAjax.ftl @@ -125,7 +125,7 @@ <#else> " width="381" height="44" /> - <@s.a href="%{#startHref}" escapeHtmlBody="false"> + <@s.a href="%{#startHref}"> " width="250" height="43" /> <#else> @@ -139,7 +139,7 @@ <@s.a href="%{#url}" id="%{#currentCharacter}" - escapeHtmlBody="false" + > " width="36" border="0" /> diff --git a/apps/showcase/src/main/webapp/WEB-INF/person/new-person.ftl b/apps/showcase/src/main/webapp/WEB-INF/person/new-person.ftl index 6af4da446..07a66d4a6 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/person/new-person.ftl +++ b/apps/showcase/src/main/webapp/WEB-INF/person/new-person.ftl @@ -59,7 +59,7 @@
- <@s.submit value="Create person" cssClass="btn btn-primary" escapeHtmlBody="false"/> + <@s.submit value="Create person" cssClass="btn btn-primary"/>
diff --git a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/actionPrefix.ftl b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/actionPrefix.ftl index 806883716..6e4432ea5 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/actionPrefix.ftl +++ b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/actionPrefix.ftl @@ -35,7 +35,7 @@

The text you've entered is ${text!''}

- <@s.a href="javascript:history.back();" cssClass="btn btn-info" escapeHtmlBody="false"> Back + <@s.a href="javascript:history.back();" cssClass="btn btn-info"> Back diff --git a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/actionPrefixExample.ftl b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/actionPrefixExample.ftl index 63312c786..98b0a9232 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/actionPrefixExample.ftl +++ b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/actionPrefixExample.ftl @@ -57,13 +57,13 @@ <@s.textfield label="Enter Some Text" name="text" /> - <@s.submit action="actionPrefix" value="%{'action prefix'}" cssClass="btn" escapeHtmlBody="false" /> + <@s.submit action="actionPrefix" value="%{'action prefix'}" cssClass="btn" /> - <@s.submit method="alternateMethod" value="%{'method prefix'}" cssClass="btn" escapeHtmlBody="false" /> + <@s.submit method="alternateMethod" value="%{'method prefix'}" cssClass="btn" /> - <@s.submit value="Normal Submit" cssClass="btn" escapeHtmlBody="false" /> + <@s.submit value="Normal Submit" cssClass="btn" /> - <@s.submit action="redirectActionPrefixAction" value="%{'redirectAction without prefix'}" cssClass="btn" escapeHtmlBody="false" /> + <@s.submit action="redirectActionPrefixAction" value="%{'redirectAction without prefix'}" cssClass="btn" /> diff --git a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/methodPrefix.ftl b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/methodPrefix.ftl index b94bf6d88..e2cf0ee56 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/methodPrefix.ftl +++ b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/methodPrefix.ftl @@ -32,10 +32,10 @@

You have come to this page because you used an method prefix.

- +

The text you've enter is ${text!''}

- <@s.a href="javascript:history.back();" cssClass="btn btn-info" escapeHtmlBody="false"> Back + <@s.a href="javascript:history.back();" cssClass="btn btn-info"> Back

diff --git a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/normalSubmit.ftl b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/normalSubmit.ftl index 3052ff173..7df58271d 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/normalSubmit.ftl +++ b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/normalSubmit.ftl @@ -32,10 +32,10 @@

You have come to this page because you did a normal submit.

- +

The text you've enter is %{text}

- <@s.a href="javascript:history.back();" cssClass="btn btn-info" escapeHtmlBody="false"> Back + <@s.a href="javascript:history.back();" cssClass="btn btn-info"> Back

diff --git a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/redirectActionPrefix.ftl b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/redirectActionPrefix.ftl index 602f6bee7..93fee964c 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/redirectActionPrefix.ftl +++ b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/actionPrefix/redirectActionPrefix.ftl @@ -32,13 +32,13 @@

You have come to this page because you used an 'redirect-action' prefix.

- +

Because this is a redirect-action, the text will be lost, due to a redirection implies a new request being issued from the client.

The text you've enter is ${text!''}

- <@s.a href="javascript:history.back();" cssClass="btn btn-info" escapeHtmlBody="false"> Back + <@s.a href="javascript:history.back();" cssClass="btn btn-info"> Back

diff --git a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/appendIteratorTagDemoResult.jsp b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/appendIteratorTagDemoResult.jsp index c95de872d..c2c7ff3dd 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/appendIteratorTagDemoResult.jsp +++ b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/appendIteratorTagDemoResult.jsp @@ -46,7 +46,7 @@ - Back To Input + Back To Input diff --git a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/iteratorGeneratorTagDemoResult.jsp b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/iteratorGeneratorTagDemoResult.jsp index fefb0933e..f4a31d897 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/iteratorGeneratorTagDemoResult.jsp +++ b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/iteratorGeneratorTagDemoResult.jsp @@ -41,7 +41,7 @@ - Back To Input + Back To Input diff --git a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/mergeIteratorTagDemoResult.jsp b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/mergeIteratorTagDemoResult.jsp index 3ae67a590..1798da445 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/mergeIteratorTagDemoResult.jsp +++ b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/mergeIteratorTagDemoResult.jsp @@ -45,7 +45,7 @@ - Back To Input + Back To Input diff --git a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/subsetIteratorTagDemoResult.jsp b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/subsetIteratorTagDemoResult.jsp index fc89d5e28..e704e577d 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/subsetIteratorTagDemoResult.jsp +++ b/apps/showcase/src/main/webapp/WEB-INF/tags/non-ui/iteratorTag/subsetIteratorTagDemoResult.jsp @@ -42,7 +42,7 @@ - Back To Input + Back To Input diff --git a/apps/showcase/src/main/webapp/WEB-INF/token/example4.ftl b/apps/showcase/src/main/webapp/WEB-INF/token/example4.ftl index e97fa0820..19dac0114 100644 --- a/apps/showcase/src/main/webapp/WEB-INF/token/example4.ftl +++ b/apps/showcase/src/main/webapp/WEB-INF/token/example4.ftl @@ -51,7 +51,7 @@ <@s.form action="transfer4"> <@s.token/> <@s.textfield label="Amount" name="amount" required=true value="400"/> - <@s.submit value="Transfer money" cssClass="btn btn-primary" escapeHtmlBody="false"/> + <@s.submit value="Transfer money" cssClass="btn btn-primary"/> diff --git a/assembly/pom.xml b/assembly/pom.xml index 4ad736216..5abd8547c 100644 --- a/assembly/pom.xml +++ b/assembly/pom.xml @@ -24,7 +24,7 @@ org.apache.struts struts2-parent - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT struts2-assembly diff --git a/bom/pom.xml b/bom/pom.xml index 9d5be00f8..021a767b2 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -29,7 +29,7 @@ struts2-bom - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT pom Struts 2 Bill of Materials @@ -44,7 +44,7 @@ - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT true true diff --git a/bundles/admin/pom.xml b/bundles/admin/pom.xml index c6cd0e652..4f5ce6cc2 100644 --- a/bundles/admin/pom.xml +++ b/bundles/admin/pom.xml @@ -24,7 +24,7 @@ org.apache.struts struts2-osgi-bundles - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT struts2-osgi-admin-bundle diff --git a/bundles/demo/pom.xml b/bundles/demo/pom.xml index a2f639bd7..df1cd3231 100644 --- a/bundles/demo/pom.xml +++ b/bundles/demo/pom.xml @@ -24,7 +24,7 @@ org.apache.struts struts2-osgi-bundles - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT struts2-osgi-demo-bundle diff --git a/bundles/pom.xml b/bundles/pom.xml index c6d4a9de8..606f2358f 100755 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -24,7 +24,7 @@ org.apache.struts struts2-parent - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT struts2-osgi-bundles diff --git a/core/pom.xml b/core/pom.xml index 980714243..4ae6fa027 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -24,7 +24,7 @@ org.apache.struts struts2-parent - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT struts2-core jar @@ -342,7 +342,7 @@ org.slf4j slf4j-simple - true + test diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java b/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java deleted file mode 100644 index 74c6d6e6d..000000000 --- a/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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.conversion.metadata; - -import com.opensymphony.xwork2.conversion.annotations.ConversionRule; -import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -import java.io.PrintWriter; -import java.io.StringWriter; - -/** - * ConversionDescription - * - * @author Rainer Hermanns - * @version $Id$ - * - * @deprecated class will be removed - */ -@Deprecated -public class ConversionDescription { - - /** - * Jakarta commons-logging reference. - */ - protected static Logger log = null; - - - public static final String KEY_PREFIX = "Key_"; - public static final String ELEMENT_PREFIX = "Element_"; - public static final String KEY_PROPERTY_PREFIX = "KeyProperty_"; - public static final String DEPRECATED_ELEMENT_PREFIX = "Collection_"; - - /** - * Key used for type conversion of maps. - */ - String MAP_PREFIX = "Map_"; - - public String property; - public String typeConverter = ""; - public String rule = ""; - public String value = ""; - public String fullQualifiedClassName; - public String type = null; - - public ConversionDescription() { - log = LogManager.getLogger(this.getClass()); - } - - /** - * Creates an ConversionDescription with the specified property name. - * - * @param property property - */ - public ConversionDescription(String property) { - this.property = property; - log = LogManager.getLogger(this.getClass()); - } - - /** - *

- * Sets the property name to be inserted into the related conversion.properties file.
- * Note: Do not add COLLECTION_PREFIX or MAP_PREFIX keys to property names. - *

- * - * @param property The property to be converted. - */ - public void setProperty(String property) { - this.property = property; - } - - /** - * Sets the class name of the type converter to be used. - * - * @param typeConverter The class name of the type converter. - */ - public void setTypeConverter(String typeConverter) { - this.typeConverter = typeConverter; - } - - /** - * @param rule the rule prefix for COLLECTION_PREFIX or MAP_PREFIX key. Defaults to en empty String. - */ - public void setRule(String rule) { - if (rule != null && rule.length() > 0) { - if (rule.equals(ConversionRule.COLLECTION.toString())) { - this.rule = DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX; - } else if (rule.equals(ConversionRule.ELEMENT.toString())) { - this.rule = DefaultObjectTypeDeterminer.ELEMENT_PREFIX; - } else if (rule.equals(ConversionRule.KEY.toString())) { - this.rule = DefaultObjectTypeDeterminer.KEY_PREFIX; - } else if (rule.equals(ConversionRule.KEY_PROPERTY.toString())) { - this.rule = DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX; - } else if (rule.equals(ConversionRule.MAP.toString())) { - this.rule = MAP_PREFIX; - } - } - } - - - public void setType(String type) { - this.type = type; - } - - public String getType() { - return type; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - /** - * Returns the conversion description as property entry. - *

- * Example:
- * property.name = converter.className
- * Collection_property.name = converter.className
- * Map_property.name = converter.className - * KeyProperty_name = id - *

- * - * @return the conversion description as property entry. - */ - public String asProperty() { - StringWriter sw = new StringWriter(); - PrintWriter writer = null; - try { - writer = new PrintWriter(sw); - writer.print(rule); - writer.print(property); - writer.print("="); - if ( rule.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX) && value != null && value.length() > 0 ) { - writer.print(value); - } else { - writer.print(typeConverter); - } - } finally { - if (writer != null) { - writer.flush(); - writer.close(); - } - } - - return sw.toString(); - - } - - /** - * Returns the fullQualifiedClassName attribute is used to create the special conversion.properties file name. - * - * @return full qualified class name - */ - public String getFullQualifiedClassName() { - return fullQualifiedClassName; - } - - /** - * The fullQualifiedClassName attribute is used to create the special conversion.properties file name. - * - * @param fullQualifiedClassName a full qualified class name - */ - public void setFullQualifiedClassName(String fullQualifiedClassName) { - this.fullQualifiedClassName = fullQualifiedClassName; - } -} diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java index a8f717636..69b717c6c 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java @@ -40,6 +40,7 @@ import java.util.Collection; import java.util.Comparator; import java.util.Map; import java.util.TreeMap; +import java.util.regex.Pattern; /** * This interceptor sets all parameters on the value stack. @@ -50,8 +51,11 @@ public class ParametersInterceptor extends MethodFilterInterceptor { protected static final int PARAM_NAME_MAX_LENGTH = 100; + private static final Pattern DMI_IGNORED_PATTERN = Pattern.compile("^(action|method):.*", Pattern.CASE_INSENSITIVE); + private int paramNameMaxLength = PARAM_NAME_MAX_LENGTH; private boolean devMode = false; + private boolean dmiEnabled = false; protected boolean ordered = false; @@ -79,6 +83,11 @@ public class ParametersInterceptor extends MethodFilterInterceptor { this.acceptedPatterns = acceptedPatterns; } + @Inject(value = StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, required = false) + protected void setDynamicMethodInvocation(String dmiEnabled) { + this.dmiEnabled = Boolean.parseBoolean(dmiEnabled); + } + /** * If the param name exceeds the configured maximum length it will not be * accepted. @@ -101,13 +110,10 @@ public class ParametersInterceptor extends MethodFilterInterceptor { /** * Compares based on number of '.' and '[' characters (fewer is higher) */ - static final Comparator rbCollator = new Comparator() { - public int compare(String s1, String s2) { - int l1 = countOGNLCharacters(s1); - int l2 = countOGNLCharacters(s2); - return l1 < l2 ? -1 : (l2 < l1 ? 1 : s1.compareTo(s2)); - } - + static final Comparator rbCollator = (s1, s2) -> { + int l1 = countOGNLCharacters(s1); + int l2 = countOGNLCharacters(s2); + return l1 < l2 ? -1 : (l2 < l1 ? 1 : s1.compareTo(s2)); }; @Override @@ -286,6 +292,10 @@ public class ParametersInterceptor extends MethodFilterInterceptor { } protected boolean acceptableName(String name) { + if (isIgnoredDMI(name)) { + LOG.trace("DMI is enabled, ignoring DMI method: {}", name); + return false; + } boolean accepted = isWithinLengthLimit(name) && !isExcluded(name) && isAccepted(name); if (devMode && accepted) { // notify only when in devMode LOG.debug("Parameter [{}] was accepted and will be appended to action!", name); @@ -293,6 +303,14 @@ public class ParametersInterceptor extends MethodFilterInterceptor { return accepted; } + private boolean isIgnoredDMI(String name) { + if (dmiEnabled) { + return DMI_IGNORED_PATTERN.matcher(name).matches(); + } else { + return false; + } + } + protected boolean isWithinLengthLimit(String name) { boolean matchLength = name.length() <= paramNameMaxLength; if (!matchLength) { diff --git a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java index fabb89c16..5685d5e30 100644 --- a/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java +++ b/core/src/main/java/com/opensymphony/xwork2/util/AbstractLocalizedTextProvider.java @@ -306,13 +306,13 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { /** * A helper method for {@link ResourceBundle} bundle reload logic. - * - * Uses standard {@link ResourceBundle} methods to clear the bundle caches for the + * + * Uses standard {@link ResourceBundle} methods to clear the bundle caches for the * {@link ClassLoader} instances that this class is aware of at the time of the call. - * - * The clearCache() methods have been available since Java 1.6, so + * + * The clearCache() methods have been available since Java 1.6, so * it is anticipated the logic will work on any subsequent JVM versions. - * + * * @since 2.6 */ private void clearResourceBundleClassloaderCaches() { @@ -326,14 +326,14 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { /** * "Hacky" helper method that attempts to clear the Tomcat ResourceEntry * {@link Map} using knowledge of the Tomcat source code. - * - * It relies on the {@link #TOMCAT_RESOURCE_ENTRIES_FIELD} field name, base class name + * + * It relies on the {@link #TOMCAT_RESOURCE_ENTRIES_FIELD} field name, base class name * {@link #TOMCAT_WEBAPP_CLASSLOADER_BASE}. and descendant class names {@link #TOMCAT_WEBAPP_CLASSLOADER}, * {@link #TOMCAT_PARALLEL_WEBAPP_CLASSLOADER}, to keep the values identified in the constants. * It appears to be valid for Tomcat versions 7-10 so far, but could become invalid at any time in the future * when the resource handling logic in Tomcat changes. - * - * Note: With Java 9+, calling this method may result in "Illegal reflective access" warnings. Be aware + * + * Note: With Java 9+, calling this method may result in "Illegal reflective access" warnings. Be aware * its logic may fail in a future version of Java that blocks the reflection calls needed for this method. */ private void clearTomcatCache() { @@ -367,10 +367,10 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { /** * Helper method that is intended to clear a {@link Map} instance by name. - * + * * This method relies on reflection to perform its operations, and may be blocked in Java 9 and later, * depending on the accessibility of the field. - * + * * @param cl The {@link Class} of the obj parameter. * @param obj The {@link Object} from which the named field is to be extracted (may be null for a static field). * @param name The name of the field containing a {@link Map} reference. @@ -433,9 +433,9 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { * Set the {@link #searchDefaultBundlesFirst} flag state. This flag may be used by descendant TextProvider * implementations to determine if default bundles should be searched for messages first (before the standard * flow of the {@link LocalizedTextProvider} implementation the descendant provides). - * + * * @param searchDefaultBundlesFirst provide {@link String} "true" or "false" to set the flag state accordingly. - * + * * @since 2.6 */ @Inject(value = StrutsConstants.STRUTS_I18N_SEARCH_DEFAULTBUNDLES_FIRST, required = false) @@ -490,7 +490,7 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { } return bundle; } - + /** * Clears all the internal lists. * @@ -566,17 +566,17 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { * against the default resource bundles. The default resource bundles are searched for a value using key first, then * alternateKey when the first search fails, then utilizing defaultMessage (which may be null) if both * key lookup operations fail. - * + * *

* A known use case is when a key indexes a collection (e.g. user.phone[0]) for which some specific keys may exist, but not all, * along with a general key (e.g. user.phone[*]). In such cases the specific key would be passed in the key parameter and the * general key would be passed in the alternateKey parameter. *

- * + * * @param key the initial key to search for a value within the default resource bundles. * @param alternateKey the alternate (fall-back) key to search for a value within the default resource bundles, if the initial key lookup fails. * @param locale the {@link Locale} to be used for the default resource bundle lookup. - * @param valueStack the {@link ValueStack} associated with the operation. + * @param valueStack the {@link ValueStack} associated with the operation. * @param args the argument array for parameterized messages (may be null). * @param defaultMessage the default message {@link String} to use if both key lookup operations fail. * @return the {@link GetDefaultMessageReturnArg} result containing the processed message lookup (by key first, then alternateKey if key's lookup fails). @@ -616,11 +616,7 @@ abstract class AbstractLocalizedTextProvider implements LocalizedTextProvider { MessageFormat mf = buildMessageFormat(message, locale); return formatWithNullDetection(mf, args); } catch (MissingResourceException e) { - if (devMode) { - LOG.warn("Missing key [{}] in bundle [{}]!", key, bundleName); - } else { - LOG.debug("Missing key [{}] in bundle [{}]!", key, bundleName); - } + LOG.debug("Missing key [{}] in bundle [{}]!", key, bundleName); return null; } } diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java index 84b608077..a5cf7e0f6 100644 --- a/core/src/main/java/org/apache/struts2/StrutsConstants.java +++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java @@ -18,6 +18,7 @@ */ package org.apache.struts2; +import org.apache.struts2.components.date.DateFormatter; import org.apache.struts2.dispatcher.mapper.CompositeActionMapper; /** @@ -132,6 +133,9 @@ public final class StrutsConstants { /** A path to static content, by default and from historical point of view it's /static. */ public static final String STRUTS_UI_STATIC_CONTENT_PATH = "struts.ui.staticContentPath"; + /** A global flag to enable/disable html body escaping in tags, can be overwritten per tag */ + public static final String STRUTS_UI_ESCAPE_HTML_BODY = "struts.ui.escapeHtmlBody"; + /** The maximize size of a multipart request (file upload) */ public static final String STRUTS_MULTIPART_MAXSIZE = "struts.multipart.maxSize"; @@ -442,4 +446,7 @@ public final class StrutsConstants { public static final String STRUTS_CHAINING_COPY_FIELD_ERRORS = "struts.chaining.copyFieldErrors"; public static final String STRUTS_CHAINING_COPY_MESSAGES = "struts.chaining.copyMessages"; public static final String STRUTS_OBJECT_FACTORY_CLASSLOADER = "struts.objectFactory.classloader"; + + /** See {@link org.apache.struts2.components.Date#setDateFormatter(DateFormatter)} */ + public static final String STRUTS_DATE_FORMATTER = "struts.date.formatter"; } 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 153fdf8e8..ba9d84ac9 100644 --- a/core/src/main/java/org/apache/struts2/components/Anchor.java +++ b/core/src/main/java/org/apache/struts2/components/Anchor.java @@ -20,6 +20,7 @@ package org.apache.struts2.components; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.ValueStack; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -69,8 +70,7 @@ public class Anchor extends ClosingUIBean { protected UrlProvider urlProvider; protected UrlRenderer urlRenderer; protected boolean processingTagBody = false; - protected boolean escapeHtmlBody = true; - + //these params are passed by the Param tag protected Map urlParameters = new LinkedHashMap(); @@ -96,18 +96,6 @@ public class Anchor extends ClosingUIBean { return true; } - /** - * Override to set if body content should be HTML-escaped. - * - * @return true if body should be HTML-escaped, false otherwise. - * - * @since 2.6 - */ - @Override - public boolean escapeHtmlBody() { - return escapeHtmlBody; - } - @Override protected void evaluateExtraParams() { super.evaluateExtraParams(); @@ -124,6 +112,8 @@ public class Anchor extends ClosingUIBean { addParameter("href", ensureAttributeSafelyNotEscaped(builtHref)); } } + + addParameter("escapeHtmlBody", escapeHtmlBody); } @Inject(StrutsConstants.STRUTS_URL_INCLUDEPARAMS) @@ -276,7 +266,7 @@ public class Anchor extends ClosingUIBean { urlProvider.setForceAddSchemeHostAndPort(forceAddSchemeHostAndPort); } - @StrutsTagAttribute(required = false, description = "Specifies whether to HTML-escape the tag body or not", type = "Boolean", defaultValue = "true") + @StrutsTagAttribute(description = "Specifies whether to HTML-escape the tag body or not", type = "Boolean", defaultValue = "false") public void setEscapeHtmlBody(boolean escapeHtmlBody) { this.escapeHtmlBody = escapeHtmlBody; } diff --git a/core/src/main/java/org/apache/struts2/components/Checkbox.java b/core/src/main/java/org/apache/struts2/components/Checkbox.java index 591f425ac..dd853caba 100644 --- a/core/src/main/java/org/apache/struts2/components/Checkbox.java +++ b/core/src/main/java/org/apache/struts2/components/Checkbox.java @@ -75,7 +75,8 @@ public class Checkbox extends UIBean { } if (submitUnchecked != null) { - addParameter("submitUnchecked", findValue(submitUnchecked, Boolean.class)); + Object parsedValue = findValue(submitUnchecked, Boolean.class); + addParameter("submitUnchecked", parsedValue == null ? Boolean.valueOf(submitUnchecked) : parsedValue); } else { addParameter("submitUnchecked", false); } 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/Component.java b/core/src/main/java/org/apache/struts2/components/Component.java index 4d5c84560..f45dac07d 100644 --- a/core/src/main/java/org/apache/struts2/components/Component.java +++ b/core/src/main/java/org/apache/struts2/components/Component.java @@ -68,6 +68,7 @@ public class Component { protected static ConcurrentMap, Collection> standardAttributesMap = new ConcurrentHashMap<>(); protected boolean devMode = false; + protected boolean escapeHtmlBody = false; protected ValueStack stack; protected Map parameters; protected ActionMapper actionMapper; @@ -116,6 +117,11 @@ public class Component { this.throwExceptionOnELFailure = BooleanUtils.toBoolean(throwException); } + @Inject(value = StrutsConstants.STRUTS_UI_ESCAPE_HTML_BODY, required = false) + public void setEscapeHtmlBody(String escapeHtmlBody) { + this.escapeHtmlBody = BooleanUtils.toBoolean(escapeHtmlBody); + } + @Inject public void setUrlHelper(UrlHelper urlHelper) { this.urlHelper = urlHelper; @@ -213,13 +219,13 @@ public class Component { * @return the component if found, null if not. */ protected Component findAncestor(Class clazz) { - Stack componentStack = getComponentStack(); + Stack componentStack = getComponentStack(); int currPosition = componentStack.search(this); if (currPosition >= 0) { int start = componentStack.size() - currPosition - 1; for (int i = start; i >= 0; i--) { - Component component = (Component) componentStack.get(i); + Component component = componentStack.get(i); if (clazz.isAssignableFrom(component.getClass()) && component != this) { return component; } @@ -378,9 +384,8 @@ public class Component { return expression; } } else { - expression = stripExpression(expression); - - return getStack().findValue(expression, toType, throwExceptionOnELFailure); + String strippedExpression = stripExpression(expression); + return getStack().findValue(strippedExpression, toType, throwExceptionOnELFailure); } } @@ -536,7 +541,7 @@ public class Component { * @since 2.6 */ public boolean escapeHtmlBody() { - return true; + return escapeHtmlBody; } /** @@ -572,16 +577,16 @@ public class Component { /** * Request that the tag state be cleared during {@link org.apache.struts2.views.jsp.StrutsBodyTagSupport#doEndTag()} processing, * which may help with certain edge cases with tag logic running on servers that implement JSP Tag Pooling. - * - * Note: All Tag classes that extend {@link org.apache.struts2.views.jsp.StrutsBodyTagSupport} must implement a setter for + * + * Note: All Tag classes that extend {@link org.apache.struts2.views.jsp.StrutsBodyTagSupport} must implement a setter for * this attribute (same name), and it must be defined at the Tag class level. * Defining a setter in the superclass alone is insufficient (results in "Cannot find a setter method for the attribute"). - * + * * See {@link org.apache.struts2.views.jsp.StrutsBodyTagSupport#clearTagStateForTagPoolingServers() for additional details. - * + * * @param performClearTagStateForTagPoolingServers true if tag state should be cleared, false otherwise. */ - @StrutsTagAttribute(description="Whether to clear all tag state during doEndTag() processing (if applicable)", type="Boolean", defaultValue="false", required = false) + @StrutsTagAttribute(description="Whether to clear all tag state during doEndTag() processing (if applicable)", type="Boolean", defaultValue="false") public void setPerformClearTagStateForTagPoolingServers(boolean performClearTagStateForTagPoolingServers) { this.performClearTagStateForTagPoolingServers = performClearTagStateForTagPoolingServers; } diff --git a/core/src/main/java/org/apache/struts2/components/Date.java b/core/src/main/java/org/apache/struts2/components/Date.java index 5f65d9c7b..ad174fd11 100644 --- a/core/src/main/java/org/apache/struts2/components/Date.java +++ b/core/src/main/java/org/apache/struts2/components/Date.java @@ -18,29 +18,29 @@ */ package org.apache.struts2.components; -import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.TextProvider; +import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.ValueStack; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.components.date.DateFormatter; import org.apache.struts2.views.annotations.StrutsTag; import org.apache.struts2.views.annotations.StrutsTagAttribute; import java.io.IOException; import java.io.Writer; import java.time.Instant; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; import java.util.ArrayList; import java.util.Calendar; import java.util.List; /** * - * + *

* Format Date object in different ways. *

* The date tag will allow you to format a Date in a quick and easy way. @@ -59,6 +59,12 @@ import java.util.List; *

* *

+ * Note: Since Struts 2.6 a new Java 8 API has been used to format the Date, it's based on + * DateTimeFormatter + * which uses a bit different patterns. + *

+ * + *

* Configurable attributes are: *

* @@ -130,8 +136,8 @@ import java.util.List; * if one is not found DateFormat.MEDIUM format will be used * * - * - * + *

+ *

* * *

Examples

@@ -145,12 +151,12 @@ import java.util.List; * * * Date - * */ -@StrutsTag(name="date", tldBodyContent="empty", tldTagClass="org.apache.struts2.views.jsp.DateTag", description="Render a formatted date.") +@StrutsTag(name = "date", tldBodyContent = "empty", tldTagClass = "org.apache.struts2.views.jsp.DateTag", description = "Render a formatted date.") public class Date extends ContextBean { private static final Logger LOG = LogManager.getLogger(Date.class); + /** * Property name to fall back when no format is specified */ @@ -202,17 +208,18 @@ public class Date extends ContextBean { private String timezone; + private DateFormatter dateFormatter; + public Date(ValueStack stack) { super(stack); } - private TextProvider findProviderInStack() { - for (Object o : getStack().getRoot()) { - if (o instanceof TextProvider) { - return (TextProvider) o; - } - } - return null; + /** + * An instance of {@link DateFormatter} + */ + @Inject + public void setDateFormatter(DateFormatter dateFormatter) { + this.dateFormatter = dateFormatter; } /** @@ -280,6 +287,8 @@ public class Date extends ContextBean { @Override public boolean end(Writer writer, String body) { + TextProvider textProvider = findProviderInStack(); + ZonedDateTime date = null; final ZoneId tz = getTimeZone(); // find the name on the valueStack @@ -292,26 +301,27 @@ public class Date extends ContextBean { date = Instant.ofEpochMilli((long) dateObject).atZone(tz); } else if (dateObject instanceof LocalDateTime) { date = ((LocalDateTime) dateObject).atZone(tz); + } else if (dateObject instanceof LocalDate) { + date = ((LocalDate) dateObject).atStartOfDay(tz); } else if (dateObject instanceof Instant) { date = ((Instant) dateObject).atZone(tz); } else { if (devMode) { - TextProvider tp = findProviderInStack(); String developerNotification = ""; - if (tp != null) { - developerNotification = findProviderInStack().getText( - "devmode.notification", - "Developer Notification:\n{0}", - new String[]{ - "Expression [" + name + "] passed to tag which was evaluated to [" + dateObject + "](" - + (dateObject != null ? dateObject.getClass() : "null") + ") isn't supported!" - } + if (textProvider != null) { + developerNotification = textProvider.getText( + "devmode.notification", + "Developer Notification:\n{0}", + new String[]{ + "Expression [" + name + "] passed to tag which was evaluated to [" + dateObject + "](" + + (dateObject != null ? dateObject.getClass() : "null") + ") isn't supported!" + } ); } LOG.warn(developerNotification); } else { LOG.debug("Expression [{}] passed to tag which was evaluated to [{}]({}) isn't supported!", - name, dateObject, (dateObject != null ? dateObject.getClass() : "null")); + name, dateObject, (dateObject != null ? dateObject.getClass() : "null")); } } @@ -321,33 +331,11 @@ public class Date extends ContextBean { } String msg; if (date != null) { - TextProvider tp = findProviderInStack(); - if (tp != null) { + if (textProvider != null) { if (nice) { - msg = formatTime(tp, date); + msg = formatTime(textProvider, date); } else { - DateTimeFormatter dtf; - if (format == null) { - String globalFormat = null; - - // if the format is not specified, fall back using the - // defined property DATETAG_PROPERTY - globalFormat = tp.getText(DATETAG_PROPERTY); - - // if tp.getText can not find the property then the - // returned string is the same as input = - // DATETAG_PROPERTY - if (globalFormat != null - && !DATETAG_PROPERTY.equals(globalFormat)) { - dtf = DateTimeFormatter.ofPattern(globalFormat, ActionContext.getContext().getLocale()); - } else { - dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) - .withLocale(ActionContext.getContext().getLocale()); - } - } else { - dtf = DateTimeFormatter.ofPattern(format, ActionContext.getContext().getLocale()); - } - msg = dtf.format(date); + msg = formatDate(textProvider, date); } if (msg != null) { try { @@ -365,6 +353,20 @@ public class Date extends ContextBean { return super.end(writer, ""); } + private String formatDate(TextProvider textProvider, ZonedDateTime date) { + String useFormat = format; + if (useFormat == null) { + // if the format is not specified, fall back using the defined property DATETAG_PROPERTY + useFormat = textProvider.getText(DATETAG_PROPERTY); + if (DATETAG_PROPERTY.equals(useFormat)) { + // if tp.getText can not find the property then the + // returned string is the same as input = DATETAG_PROPERTY + useFormat = null; + } + } + return dateFormatter.format(date, useFormat); + } + private ZoneId getTimeZone() { ZoneId tz = ZoneId.systemDefault(); if (timezone != null) { @@ -378,17 +380,26 @@ public class Date extends ContextBean { return tz; } - @StrutsTagAttribute(description="Date or DateTime format pattern", rtexprvalue=false) + private TextProvider findProviderInStack() { + for (Object o : getStack().getRoot()) { + if (o instanceof TextProvider) { + return (TextProvider) o; + } + } + return null; + } + + @StrutsTagAttribute(description = "Date or DateTime format pattern") public void setFormat(String format) { this.format = format; } - @StrutsTagAttribute(description="Whether to print out the date nicely", type="Boolean", defaultValue="false") + @StrutsTagAttribute(description = "Whether to print out the date nicely", type = "Boolean", defaultValue = "false") public void setNice(boolean nice) { this.nice = nice; } - @StrutsTagAttribute(description = "The specific timezone in which to format the date", required = false) + @StrutsTagAttribute(description = "The specific timezone in which to format the date") public void setTimezone(String timezone) { this.timezone = timezone; } @@ -400,7 +411,7 @@ public class Date extends ContextBean { return name; } - @StrutsTagAttribute(description="The date value to format", required=true) + @StrutsTagAttribute(description = "The date value to format", required = true) public void setName(String name) { this.name = name; } 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/Submit.java b/core/src/main/java/org/apache/struts2/components/Submit.java index 124526a8e..7d13d3b85 100644 --- a/core/src/main/java/org/apache/struts2/components/Submit.java +++ b/core/src/main/java/org/apache/struts2/components/Submit.java @@ -23,6 +23,9 @@ import java.io.Writer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.opensymphony.xwork2.inject.Inject; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.struts2.StrutsConstants; import org.apache.struts2.views.annotations.StrutsTag; import org.apache.struts2.views.annotations.StrutsTagAttribute; @@ -54,7 +57,6 @@ public class Submit extends FormButton { final public static String OPEN_TEMPLATE = "submit"; final public static String TEMPLATE = "submit-close"; protected String src; - protected boolean escapeHtmlBody = true; public Submit(ValueStack stack, HttpServletRequest request, HttpServletResponse response) { super(stack, request, response); @@ -73,7 +75,7 @@ public class Submit extends FormButton { value = "Submit"; } - if (((key != null)) && (value == null)) { + if ((key != null) && (value == null)) { this.value = "%{getText('"+key +"')}"; } @@ -83,8 +85,11 @@ public class Submit extends FormButton { public void evaluateExtraParams() { super.evaluateExtraParams(); - if (src != null) + if (src != null) { addParameter("src", findString(src)); + } + + addParameter("escapeHtmlBody", escapeHtmlBody); } /** @@ -101,7 +106,7 @@ public class Submit extends FormButton { this.src = src; } - @StrutsTagAttribute(required = false, description = "Specifies whether to HTML-escape the tag body or not", type = "Boolean", defaultValue = "true") + @StrutsTagAttribute(description = "Specifies whether to HTML-escape the tag body or not", type = "Boolean", defaultValue = "false") public void setEscapeHtmlBody(boolean escapeHtmlBody) { this.escapeHtmlBody = escapeHtmlBody; } @@ -111,18 +116,6 @@ public class Submit extends FormButton { return true; } - /** - * Override to set if body content should be HTML-escaped. - * - * @return true if body should be HTML-escaped, false otherwise. - * - * @since 2.6 - */ - @Override - public boolean escapeHtmlBody() { - return escapeHtmlBody; - } - /** * Overrides to be able to render body in a template rather than always before the template */ 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..4a4b5eb2b 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; @@ -699,11 +702,13 @@ public abstract class UIBean extends Component { } if (requiredLabel != null) { - addParameter("required", findValue(requiredLabel, Boolean.class)); + Object parsedValue = findValue(requiredLabel, Boolean.class); + addParameter("required", parsedValue == null ? Boolean.valueOf(requiredLabel) : parsedValue); } if (disabled != null) { - addParameter("disabled", findValue(disabled, Boolean.class)); + Object parsedValue = findValue(disabled, Boolean.class); + addParameter("disabled", parsedValue == null ? Boolean.valueOf(disabled) : parsedValue); } if (tabindex != null) { @@ -883,9 +888,9 @@ public abstract class UIBean extends Component { this.addParameter("tooltipDelay", findString(this.tooltipDelay)); if (this.javascriptTooltip != null) { - Boolean jsTooltips = (Boolean) findValue(this.javascriptTooltip, Boolean.class); + Object jsTooltips = findValue(this.javascriptTooltip, Boolean.class); //TODO use a Boolean model when tooltipConfig is dropped - this.addParameter("jsTooltipEnabled", jsTooltips.toString()); + this.addParameter("jsTooltipEnabled", jsTooltips == null ? this.javascriptTooltip : jsTooltips.toString()); if (form != null) form.addParameter("hasTooltip", jsTooltips); @@ -965,7 +970,7 @@ public abstract class UIBean extends Component { // 1] UI component's tooltipConfig attribute OR // 2] param tag value attribute - result = new LinkedHashMap<>((Map) tooltipConfigObj); + result = new LinkedHashMap((Map) tooltipConfigObj); } else if (tooltipConfigObj instanceof String) { // we get this if its configured using @@ -1272,10 +1277,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 +1307,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/date/DateFormatter.java b/core/src/main/java/org/apache/struts2/components/date/DateFormatter.java new file mode 100644 index 000000000..282daaa06 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/components/date/DateFormatter.java @@ -0,0 +1,40 @@ +/* + * 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.components.date; + +import java.time.temporal.TemporalAccessor; + +/** + * Allows defines a wrapper around different formatting APIs, like old SimpleDateFormat + * and new DateTimeFormatter introduced in Java 8 Date/Time API + *

+ * New instance will be injected using {@link org.apache.struts2.StrutsConstants#STRUTS_DATE_FORMATTER} + */ +public interface DateFormatter { + + /** + * Formats provided temporal with the given format + * + * @param temporal Java 8 {@link TemporalAccessor} + * @param format implementation specific format + * @return a string representation of the formatted `temporal` + */ + String format(TemporalAccessor temporal, String format); + +} diff --git a/core/src/main/java/org/apache/struts2/components/date/DateTimeFormatterAdapter.java b/core/src/main/java/org/apache/struts2/components/date/DateTimeFormatterAdapter.java new file mode 100644 index 000000000..05767ab1b --- /dev/null +++ b/core/src/main/java/org/apache/struts2/components/date/DateTimeFormatterAdapter.java @@ -0,0 +1,43 @@ +/* + * 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.components.date; + +import com.opensymphony.xwork2.ActionContext; + +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.time.temporal.TemporalAccessor; +import java.util.Locale; + +public class DateTimeFormatterAdapter implements DateFormatter { + + @Override + public String format(TemporalAccessor temporal, String format) { + DateTimeFormatter dtf; + Locale locale = ActionContext.getContext().getLocale(); + if (format == null) { + dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) + .withLocale(locale); + } else { + dtf = DateTimeFormatter.ofPattern(format, locale); + } + return dtf.format(temporal); + } + +} diff --git a/core/src/main/java/org/apache/struts2/components/date/SimpleDateFormatAdapter.java b/core/src/main/java/org/apache/struts2/components/date/SimpleDateFormatAdapter.java new file mode 100644 index 000000000..38f3c5296 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/components/date/SimpleDateFormatAdapter.java @@ -0,0 +1,44 @@ +/* + * 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.components.date; + +import com.opensymphony.xwork2.ActionContext; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.temporal.TemporalAccessor; +import java.util.Date; +import java.util.Locale; + +public class SimpleDateFormatAdapter implements DateFormatter { + + @Override + public String format(TemporalAccessor temporal, String format) { + DateFormat df; + Locale locale = ActionContext.getContext().getLocale(); + if (format == null) { + df = SimpleDateFormat.getDateInstance(DateFormat.MEDIUM, locale); + } else { + df = new SimpleDateFormat(format, locale); + } + return df.format(new Date(Instant.from(temporal).toEpochMilli())); + } + +} 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/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java index ade6aa0b0..69aa9258e 100644 --- a/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java +++ b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java @@ -54,13 +54,12 @@ import com.opensymphony.xwork2.util.PatternMatcher; import com.opensymphony.xwork2.util.TextParser; import com.opensymphony.xwork2.util.ValueStackFactory; import com.opensymphony.xwork2.util.location.LocatableProperties; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; import com.opensymphony.xwork2.util.reflection.ReflectionContextFactory; import com.opensymphony.xwork2.util.reflection.ReflectionProvider; import com.opensymphony.xwork2.validator.ActionValidatorManager; import org.apache.struts2.StrutsConstants; import org.apache.struts2.components.UrlRenderer; +import org.apache.struts2.components.date.DateFormatter; import org.apache.struts2.dispatcher.DispatcherErrorHandler; import org.apache.struts2.dispatcher.StaticContentLoader; import org.apache.struts2.dispatcher.mapper.ActionMapper; @@ -422,6 +421,8 @@ public class StrutsBeanSelectionProvider extends AbstractBeanSelectionProvider { alias(NotExcludedAcceptedPatternsChecker.class, StrutsConstants.STRUTS_NOT_EXCLUDED_ACCEPTED_PATTERNS_CHECKER , builder, props, Scope.SINGLETON); + alias(DateFormatter.class, StrutsConstants.STRUTS_DATE_FORMATTER, builder, props, Scope.SINGLETON); + switchDevMode(props); } diff --git a/core/src/main/java/org/apache/struts2/interceptor/ServletConfigInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ServletConfigInterceptor.java index 0ef0f3786..634240085 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/ServletConfigInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/ServletConfigInterceptor.java @@ -45,25 +45,23 @@ import com.opensymphony.xwork2.interceptor.AbstractInterceptor; *

* *
    - * *
  • {@link ServletContextAware}
  • - * + *
  • {@link org.apache.struts2.action.ServletContextAware}
  • *
  • {@link ServletRequestAware}
  • - * + *
  • {@link org.apache.struts2.action.ServletRequestAware}
  • *
  • {@link ServletResponseAware}
  • - * + *
  • {@link org.apache.struts2.action.ServletResponseAware}
  • *
  • {@link ParameterAware} - deprecated since 2.5.4, please use {@link HttpParametersAware}
  • - * *
  • {@link HttpParametersAware}
  • - * + *
  • {@link org.apache.struts2.action.ParametersAware}
  • *
  • {@link RequestAware}
  • - * + *
  • {@link org.apache.struts2.action.ServletRequestAware}
  • *
  • {@link SessionAware}
  • - * + *
  • {@link org.apache.struts2.action.SessionAware}
  • *
  • {@link ApplicationAware}
  • - * + *
  • {@link org.apache.struts2.action.ApplicationAware}
  • *
  • {@link PrincipalAware}
  • - * + *
  • {@link org.apache.struts2.action.PrincipalAware}
  • *
* * @@ -101,13 +99,19 @@ import com.opensymphony.xwork2.interceptor.AbstractInterceptor; * * * @see ServletContextAware + * @see org.apache.struts2.action.ServletContextAware * @see ServletRequestAware + * @see org.apache.struts2.action.ServletRequestAware * @see ServletResponseAware + * @see org.apache.struts2.action.ServletResponseAware * @see ParameterAware - * @see ParametersAware + * @see org.apache.struts2.action.ParametersAware * @see SessionAware + * @see org.apache.struts2.action.SessionAware * @see ApplicationAware + * @see org.apache.struts2.action.ApplicationAware * @see PrincipalAware + * @see org.apache.struts2.action.PrincipalAware */ public class ServletConfigInterceptor extends AbstractInterceptor implements StrutsStatics { @@ -159,7 +163,7 @@ public class ServletConfigInterceptor extends AbstractInterceptor implements Str if (action instanceof ApplicationAware) { ((ApplicationAware) action).setApplication(context.getApplication()); } - + if (action instanceof org.apache.struts2.action.ApplicationAware) { ((org.apache.struts2.action.ApplicationAware) action).withApplication(context.getApplication()); } @@ -167,7 +171,7 @@ public class ServletConfigInterceptor extends AbstractInterceptor implements Str if (action instanceof SessionAware) { ((SessionAware) action).setSession(context.getSession()); } - + if (action instanceof org.apache.struts2.action.SessionAware) { ((org.apache.struts2.action.SessionAware) action).withSession(context.getSession()); } @@ -179,7 +183,7 @@ public class ServletConfigInterceptor extends AbstractInterceptor implements Str if (action instanceof PrincipalAware) { HttpServletRequest request = context.getServletRequest(); if(request != null) { - // We are in servtlet environment, so principal information resides in HttpServletRequest + // We are in servlet environment, so principal information resides in HttpServletRequest ((PrincipalAware) action).setPrincipalProxy(new ServletPrincipalProxy(request)); } } diff --git a/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java b/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java index 1fa411080..d7ad70681 100644 --- a/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java +++ b/core/src/main/java/org/apache/struts2/util/TextProviderHelper.java @@ -71,11 +71,6 @@ public class TextProviderHelper { } else { LOG.warn("Could not locate the message resource '{}' as there is no TextProvider in the ValueStack.", key); } - if (defaultMessage.equals(msg)) { - LOG.warn("The default value expression '{}' was evaluated and did not match a property. The literal value '{}' will be used.", defaultMessage, defaultMessage); - } else { - LOG.warn("The default value expression '{}' evaluated to '{}'", defaultMessage, msg); - } } } return msg; diff --git a/core/src/main/java/org/apache/struts2/views/jsp/ui/AnchorTag.java b/core/src/main/java/org/apache/struts2/views/jsp/ui/AnchorTag.java index 4f334ada0..0e181d5c6 100644 --- a/core/src/main/java/org/apache/struts2/views/jsp/ui/AnchorTag.java +++ b/core/src/main/java/org/apache/struts2/views/jsp/ui/AnchorTag.java @@ -47,13 +47,13 @@ public class AnchorTag extends AbstractClosingTag { protected String portletUrlType; protected String anchor; protected String forceAddSchemeHostAndPort; - protected boolean escapeHtmlBody = true; // Default - escape HTML body + protected String escapeHtmlBody; @Override public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { return new Anchor(stack, req, res); } - + @Override protected void populateParams() { super.populateParams(); @@ -80,10 +80,12 @@ public class AnchorTag extends AbstractClosingTag { if (escapeAmp != null) { tag.setEscapeAmp(BooleanUtils.toBoolean(escapeAmp)); } - if (forceAddSchemeHostAndPort != null) { + if (forceAddSchemeHostAndPort != null) { tag.setForceAddSchemeHostAndPort(BooleanUtils.toBoolean(forceAddSchemeHostAndPort)); } - tag.setEscapeHtmlBody(escapeHtmlBody); + if (escapeHtmlBody != null) { + tag.setEscapeHtmlBody(escapeHtmlBody); + } } public void setHref(String href) { @@ -149,12 +151,12 @@ public class AnchorTag extends AbstractClosingTag { /** * Set via parameter to control if body content should be HTML-escaped. - * - * @param escapeHtmlBody - * + * + * @param escapeHtmlBody + * * @since 2.6 */ - public void setEscapeHtmlBody(boolean escapeHtmlBody) { + public void setEscapeHtmlBody(String escapeHtmlBody) { this.escapeHtmlBody = escapeHtmlBody; } diff --git a/core/src/main/java/org/apache/struts2/views/jsp/ui/SubmitTag.java b/core/src/main/java/org/apache/struts2/views/jsp/ui/SubmitTag.java index 5250cbe62..2bfd6a91e 100644 --- a/core/src/main/java/org/apache/struts2/views/jsp/ui/SubmitTag.java +++ b/core/src/main/java/org/apache/struts2/views/jsp/ui/SubmitTag.java @@ -37,7 +37,7 @@ public class SubmitTag extends AbstractClosingTag { protected String method; protected String type; protected String src; - protected boolean escapeHtmlBody = true; // Default - escape HTML body + protected boolean escapeHtmlBody = false; @Override public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { @@ -78,9 +78,9 @@ public class SubmitTag extends AbstractClosingTag { /** * Set via parameter to control if body content should be HTML-escaped. - * - * @param escapeHtmlBody - * + * + * @param escapeHtmlBody + * * @since 2.6 */ public void setEscapeHtmlBody(boolean escapeHtmlBody) { diff --git a/core/src/main/resources/org/apache/struts2/default.properties b/core/src/main/resources/org/apache/struts2/default.properties index eef441ebc..74a85f137 100644 --- a/core/src/main/resources/org/apache/struts2/default.properties +++ b/core/src/main/resources/org/apache/struts2/default.properties @@ -151,6 +151,11 @@ struts.ui.theme.expansion.token=~~~ ### Sets the default template type. Either ftl, vm, or jsp struts.ui.templateSuffix=ftl +### Sets a global flag which will escape html body of Anchor, Submit and Component tag +### You can control this flag per tag, e.g.: ... +### and this take precedence over the global flag +# struts.ui.escapeHtmlBody=true + ### Configuration reloading ### This will cause the configuration to reload struts.xml when it is changed # struts.configuration.xml.reload=false @@ -256,7 +261,7 @@ struts.handle.exception=true ### Applies maximum length allowed on OGNL expressions for security enhancement (optional) ### -### **WARNING**: If developers enable this option (by configuration) they should make sure that they understand the implications of setting +### **WARNING**: If developers enable this option (by configuration) they should make sure that they understand the implications of setting ### struts.ognl.expressionMaxLength. They must choose a value large enough to permit ALL valid OGNL expressions used within the application. ### Values larger than the 200-400 range have diminishing security value (at which point it is really only a "style guard" for long OGNL ### expressions in an application. Setting a value of null or "" will also disable the feature. @@ -264,4 +269,10 @@ struts.handle.exception=true ### NOTE: The sample line below is *INTENTIONALLY* commented out, as this feature is disabled by default. # struts.ognl.expressionMaxLength=256 +### Defines which named instance of DateFormatter to use, there are two instances: +### - simpleDateFormatter (based on SimpleDateFormat) +### - dateTimeFormatter (based on Java 8 Date/Time API) +### These formatters are using a slightly different patterns, please check JavaDocs of both and more details is in WW-5016 +struts.date.formatter=dateTimeFormatter + ### END SNIPPET: complete_file diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml index 65b89f169..775b3fbbf 100644 --- a/core/src/main/resources/struts-default.xml +++ b/core/src/main/resources/struts-default.xml @@ -216,7 +216,7 @@ - + @@ -228,6 +228,9 @@ + + + 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/site/resources/tags/a-attributes.html b/core/src/site/resources/tags/a-attributes.html index e9cbb587f..e9fd9089c 100644 --- a/core/src/site/resources/tags/a-attributes.html +++ b/core/src/site/resources/tags/a-attributes.html @@ -112,7 +112,7 @@ escapeHtmlBody false - true + false false Boolean Specifies whether to HTML-escape the tag body or not diff --git a/core/src/site/resources/tags/submit-attributes.html b/core/src/site/resources/tags/submit-attributes.html index 9f790b235..52ed55ad0 100644 --- a/core/src/site/resources/tags/submit-attributes.html +++ b/core/src/site/resources/tags/submit-attributes.html @@ -88,7 +88,7 @@ escapeHtmlBody false - true + false false Boolean Specifies whether to HTML-escape the tag body or not diff --git a/core/src/test/java/com/opensymphony/xwork2/interceptor/ParametersInterceptorTest.java b/core/src/test/java/com/opensymphony/xwork2/interceptor/ParametersInterceptorTest.java index 381f909d3..515b7cffe 100644 --- a/core/src/test/java/com/opensymphony/xwork2/interceptor/ParametersInterceptorTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/interceptor/ParametersInterceptorTest.java @@ -730,6 +730,33 @@ public class ParametersInterceptorTest extends XWorkTestCase { assertEquals(expected, actual); } + public void testDMIMethodsAreIgnored() throws Exception { + // given + ParametersInterceptor interceptor = createParametersInterceptor(); + final Map actual = injectValueStackFactory(interceptor); + ValueStack stack = injectValueStack(actual); + + final Map expected = new HashMap() { + { + put("ordinary.bean", "value"); + } + }; + + Map parameters = new HashMap() { + { + put("ordinary.bean", "value"); + put("action:", "myAction"); + put("method:", "doExecute"); + } + }; + + // when + interceptor.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build()); + + // then + assertEquals(expected, actual); + } + public void testBeanListSingleValue() throws Exception { Map params = new HashMap<>(); params.put("beanList.name", new String[]{"Superman"}); diff --git a/core/src/test/java/org/apache/struts2/components/ComponentTest.java b/core/src/test/java/org/apache/struts2/components/ComponentTest.java index 0bc098265..762e37ae3 100644 --- a/core/src/test/java/org/apache/struts2/components/ComponentTest.java +++ b/core/src/test/java/org/apache/struts2/components/ComponentTest.java @@ -18,14 +18,11 @@ */ package org.apache.struts2.components; -import java.util.Iterator; -import java.util.Locale; -import java.util.Stack; - -import javax.servlet.jsp.tagext.TagSupport; - import com.opensymphony.xwork2.LocalizedTextProvider; +import org.apache.struts2.StrutsException; +import org.apache.struts2.TestConfigurationProvider; import org.apache.struts2.views.jsp.AbstractTagTest; +import org.apache.struts2.views.jsp.ActionTag; import org.apache.struts2.views.jsp.BeanTag; import org.apache.struts2.views.jsp.ElseIfTag; import org.apache.struts2.views.jsp.ElseTag; @@ -42,17 +39,19 @@ import org.apache.struts2.views.jsp.iterator.MergeIteratorTag; import org.apache.struts2.views.jsp.ui.TextFieldTag; import org.apache.struts2.views.jsp.ui.UpDownSelectTag; +import javax.servlet.jsp.tagext.TagSupport; import java.util.HashMap; -import org.apache.struts2.StrutsException; +import java.util.Iterator; +import java.util.Locale; +import java.util.Stack; /** * Test case for method findAncestor(Class) in Component and some commons * test cases for Component in general. - * */ public class ComponentTest extends AbstractTagTest { - public void testFindAncestorTest() throws Exception { + public void testFindAncestorTest() { Property property = new Property(stack); Form form = new Form(stack, request, response); ActionComponent actionComponent = new ActionComponent(stack, request, response); @@ -61,8 +60,8 @@ public class ComponentTest extends AbstractTagTest { TextField textField = new TextField(stack, request, response); - Stack stack = property.getComponentStack(); - Iterator i = stack.iterator(); + Stack stack = property.getComponentStack(); + Iterator i = stack.iterator(); try { @@ -107,8 +106,7 @@ public class ComponentTest extends AbstractTagTest { assertEquals(textField.findAncestor(Anchor.class), anchor); assertEquals(textField.findAncestor(ActionComponent.class), actionComponent); assertEquals(textField.findAncestor(Property.class), property); - } - finally { + } finally { property.getComponentStack().pop(); property.getComponentStack().pop(); property.getComponentStack().pop(); @@ -118,16 +116,7 @@ public class ComponentTest extends AbstractTagTest { } // Action Component - /* - public void testActionComponentDisposeItselfFromComponentStack() throws Exception { - ConfigurationManager.clearConfigurationProviders(); - ConfigurationManager.addConfigurationProvider(new TestConfigurationProvider()); - ConfigurationManager.getConfiguration().reload(); - - ActionContext actionContext = new ActionContext(context); - actionContext.setValueStack(stack); - ActionContext.setContext(actionContext); - + public void testActionComponentDisposeItselfFromComponentStack() { request.setupGetServletPath(TestConfigurationProvider.TEST_NAMESPACE + "/" + "foo.action"); try { TextFieldTag t = new TextFieldTag(); @@ -145,17 +134,14 @@ public class ComponentTest extends AbstractTagTest { assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } } - */ - // AppendInterator - public void testAppendIteratorDisposeItselfFromComponentStack() throws Exception { + public void testAppendIteratorDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -170,8 +156,7 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } @@ -179,7 +164,7 @@ public class ComponentTest extends AbstractTagTest { // Bean - public void testBeanComponentDisposeItselfFromComponentStack() throws Exception { + public void testBeanComponentDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -195,8 +180,7 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(); } @@ -204,7 +188,7 @@ public class ComponentTest extends AbstractTagTest { // ElseIf - public void testElseIfComponentDisposeItselfFromComponentStack() throws Exception { + public void testElseIfComponentDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -219,8 +203,7 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } @@ -228,7 +211,7 @@ public class ComponentTest extends AbstractTagTest { // Else - public void testElseComponentDisposeItselfFromComponentStack() throws Exception { + public void testElseComponentDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -243,8 +226,7 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } @@ -252,7 +234,7 @@ public class ComponentTest extends AbstractTagTest { // If - public void testIfComponentDisposeItselfFromComponentStack() throws Exception { + public void testIfComponentDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -268,8 +250,7 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } @@ -277,7 +258,7 @@ public class ComponentTest extends AbstractTagTest { // Iterator - public void testIteratorComponentDisposeItselfFromComponentStack() throws Exception { + public void testIteratorComponentDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -291,15 +272,14 @@ public class ComponentTest extends AbstractTagTest { tag.doStartTag(); assertEquals(tag.getComponent().getComponentStack().peek(), tag.getComponent()); int endIt = tag.doAfterBody(); - while(TagSupport.EVAL_BODY_AGAIN == endIt) { + while (TagSupport.EVAL_BODY_AGAIN == endIt) { assertEquals(tag.getComponent().getComponentStack().peek(), tag.getComponent()); endIt = tag.doAfterBody(); } tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } @@ -307,7 +287,7 @@ public class ComponentTest extends AbstractTagTest { // MergeIterator - public void testMergeIteratorComponentDisposeItselfFromComponentStack() throws Exception { + public void testMergeIteratorComponentDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -322,8 +302,7 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } @@ -331,7 +310,7 @@ public class ComponentTest extends AbstractTagTest { // Property - public void testPropertyComponentDisposeItselfFromComponentStack() throws Exception { + public void testPropertyComponentDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -346,8 +325,7 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } @@ -355,7 +333,7 @@ public class ComponentTest extends AbstractTagTest { // Push - public void testPushComponentDisposeItselfFromComponentStack() throws Exception { + public void testPushComponentDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -371,8 +349,7 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } @@ -380,7 +357,7 @@ public class ComponentTest extends AbstractTagTest { // Set - public void testSetComponentDisposeItselfFromComponentStack() throws Exception { + public void testSetComponentDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -397,8 +374,7 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } @@ -406,7 +382,7 @@ public class ComponentTest extends AbstractTagTest { // Text - public void testTextComponentDisposeItselfFromComponentStack() throws Exception { + public void testTextComponentDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -422,15 +398,14 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } } - public void testI18nComponentDisposeItselfFromComponentStack() throws Exception { + public void testI18nComponentDisposeItselfFromComponentStack() { stack.getActionContext().withLocale(Locale.getDefault()); TextFieldTag t = new TextFieldTag(); @@ -450,15 +425,14 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } } // URL - public void testURLComponentDisposeItselfFromComponentStack() throws Exception { + public void testURLComponentDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -473,15 +447,14 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } } // updownselect - public void testUpDownSelectDisposeItselfFromComponentStack() throws Exception { + public void testUpDownSelectDisposeItselfFromComponentStack() { TextFieldTag t = new TextFieldTag(); t.setPageContext(pageContext); t.setName("textFieldName"); @@ -499,8 +472,7 @@ public class ComponentTest extends AbstractTagTest { tag.doEndTag(); assertEquals(t.getComponent().getComponentStack().peek(), t.getComponent()); t.doEndTag(); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } @@ -508,22 +480,22 @@ public class ComponentTest extends AbstractTagTest { /** * Test the expected default behaviour for component body state methods. - */ + */ public void testComponentDefaultBodyStates() { Component component = new Component(stack); // Test expected default results for: usesBody(), escapeHtmlBody(). assertFalse("Component default usesBody not false ?", component.usesBody()); - assertTrue("Component default htmlEscapeBody not true ?", component.escapeHtmlBody()); + assertFalse("Component default htmlEscapeBody not false ?", component.escapeHtmlBody()); } /** * Test the behaviour for Anchor body state methods. - */ + */ public void testAnchorBodyStates() { Anchor anchor = new Anchor(stack, request, response); // Test expected default results for: usesBody(), escapeHtmlBody(). assertTrue("Anchor default usesBody not true ?", anchor.usesBody()); - assertTrue("Anchor default htmlEscapeBody not true ?", anchor.escapeHtmlBody()); + assertFalse("Anchor default htmlEscapeBody not false ?", anchor.escapeHtmlBody()); anchor.setEscapeHtmlBody(false); assertFalse("Anchor htmlEscapeBody not false after set false ?", anchor.escapeHtmlBody()); anchor.setEscapeHtmlBody(true); @@ -532,12 +504,12 @@ public class ComponentTest extends AbstractTagTest { /** * Test the behaviour for Submit body state methods. - */ - public void testAnchortBodyStates() { + */ + public void testSubmitBodyStates() { Submit submit = new Submit(stack, request, response); // Test expected default results for: usesBody(), escapeHtmlBody(). assertTrue("Submit default usesBody not true ?", submit.usesBody()); - assertTrue("Submit default htmlEscapeBody not true ?", submit.escapeHtmlBody()); + assertFalse("Submit default htmlEscapeBody not false ?", submit.escapeHtmlBody()); submit.setEscapeHtmlBody(false); assertFalse("Submit htmlEscapeBody not false after set false ?", submit.escapeHtmlBody()); submit.setEscapeHtmlBody(true); @@ -547,10 +519,8 @@ public class ComponentTest extends AbstractTagTest { /** * Attempt some code coverage tests for {@link Component} that can be achieved without * too much difficulty. - * - * @throws Exception */ - public void testComponent_coverageTest() throws Exception { + public void testComponent_coverageTest() { HashMap propertyMap = new HashMap<>(); Exception exception = new Exception("Generic exception"); Property property = new Property(stack); @@ -586,8 +556,7 @@ public class ComponentTest extends AbstractTagTest { assertFalse("Initial performClearTagStateForTagPoolingServers not false ?", actionComponent.getPerformClearTagStateForTagPoolingServers()); actionComponent.setPerformClearTagStateForTagPoolingServers(true); assertTrue("performClearTagStateForTagPoolingServers false after setting to true ?", actionComponent.getPerformClearTagStateForTagPoolingServers()); - } - finally { + } finally { property.getComponentStack().pop(); } } diff --git a/core/src/test/java/org/apache/struts2/components/DateTest.java b/core/src/test/java/org/apache/struts2/components/DateTest.java new file mode 100644 index 000000000..2b9ca8bd7 --- /dev/null +++ b/core/src/test/java/org/apache/struts2/components/DateTest.java @@ -0,0 +1,90 @@ +/* + * 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.components; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import org.apache.struts2.StrutsInternalTestCase; +import org.apache.struts2.components.date.SimpleDateFormatAdapter; + +import java.io.StringWriter; +import java.io.Writer; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Map; + +public class DateTest extends StrutsInternalTestCase { + + private Map context; + private ValueStack stack; + + public void testSupportSimpleDateTimeFormat() { + // given + Date date = new Date(stack); + date.setDateFormatter(new SimpleDateFormatAdapter()); + + String format = "EEEE MMMM dd, hh:mm aa"; + java.util.Date now = new java.util.Date(); + + String expected = new SimpleDateFormat(format, ActionContext.getContext().getLocale()).format(now); + context.put("myDate", now); + + Writer writer = new StringWriter(); + + // when + date.setFormat(format); + date.setName("myDate"); + date.setNice(false); + date.start(writer); + date.end(writer, ""); + + // then + assertEquals(expected, writer.toString()); + } + + public void testDefaultFormat() { + // given + Date date = new Date(stack); + date.setDateFormatter(new SimpleDateFormatAdapter()); + + java.util.Date now = new java.util.Date(); + + String expected = SimpleDateFormat.getDateInstance(DateFormat.MEDIUM, ActionContext.getContext().getLocale()).format(now); + context.put("myDate", now); + + Writer writer = new StringWriter(); + + // when + date.setName("myDate"); + date.setNice(false); + date.start(writer); + date.end(writer, ""); + + // then + assertEquals(expected, writer.toString()); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + stack = container.getInstance(ValueStackFactory.class).createValueStack(); + context = stack.getContext(); + } +} diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/AnchorTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/AnchorTest.java index 8101cc8a1..7c3f80d71 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/AnchorTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/AnchorTest.java @@ -18,17 +18,15 @@ */ package org.apache.struts2.views.jsp.ui; +import org.apache.struts2.StrutsConstants; import org.apache.struts2.TestAction; +import org.apache.struts2.components.Anchor; import org.apache.struts2.views.jsp.AbstractUITagTest; -import javax.servlet.jsp.JspException; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; - -/** - */ public class AnchorTest extends AbstractUITagTest { public void testBeanInfo() throws Exception { @@ -72,7 +70,7 @@ public class AnchorTest extends AbstractUITagTest { AnchorTag freshTag = new AnchorTag(); freshTag.setPerformClearTagStateForTagPoolingServers(true); freshTag.setPageContext(pageContext); - assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + + assertTrue("Tag state after doEndTag() and explicit tag state clearing is unequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", strutsBodyTagsAreReflectionEqual(tag, freshTag)); } @@ -111,7 +109,7 @@ public class AnchorTest extends AbstractUITagTest { AnchorTag freshTag = new AnchorTag(); freshTag.setPerformClearTagStateForTagPoolingServers(true); freshTag.setPageContext(pageContext); - assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + + assertTrue("Tag state after doEndTag() and explicit tag state clearing is unequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", strutsBodyTagsAreReflectionEqual(tag, freshTag)); } @@ -156,7 +154,7 @@ public class AnchorTest extends AbstractUITagTest { AnchorTag freshTag = new AnchorTag(); freshTag.setPerformClearTagStateForTagPoolingServers(true); freshTag.setPageContext(pageContext); - assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + + assertTrue("Tag state after doEndTag() and explicit tag state clearing is unequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", strutsBodyTagsAreReflectionEqual(tag, freshTag)); } @@ -201,7 +199,7 @@ public class AnchorTest extends AbstractUITagTest { AnchorTag freshTag = new AnchorTag(); freshTag.setPerformClearTagStateForTagPoolingServers(true); freshTag.setPageContext(pageContext); - assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + + assertTrue("Tag state after doEndTag() and explicit tag state clearing is unequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", strutsBodyTagsAreReflectionEqual(tag, freshTag)); } @@ -211,7 +209,7 @@ public class AnchorTest extends AbstractUITagTest { testAction.setFoo("bar"); } - private AnchorTag createTag() throws JspException { + private AnchorTag createTag() { AnchorTag tag = new AnchorTag(); tag.setPageContext(pageContext); @@ -221,8 +219,6 @@ public class AnchorTest extends AbstractUITagTest { /** * Test anchor tag body supported - * - * @throws Exception */ public void testSimpleWithBody() throws Exception { createAction(); @@ -242,8 +238,6 @@ public class AnchorTest extends AbstractUITagTest { /** * Test that by default anchor tag body is HTML-escaped. - * - * @throws Exception */ public void testSimpleWithBodyHTMLEscaped() throws Exception { createAction(); @@ -254,6 +248,7 @@ public class AnchorTest extends AbstractUITagTest { StrutsBodyContent body = new StrutsBodyContent(null); body.print("should HTML escape: < & >"); tag.setBodyContent(body); + tag.setEscapeHtmlBody("true"); tag.doStartTag(); tag.doEndTag(); @@ -263,15 +258,13 @@ public class AnchorTest extends AbstractUITagTest { /** * Test that with htmlEscapeBody false anchor tag body is not HTML-escaped. - * - * @throws Exception */ public void testSimpleWithBodyNotHTMLEscaped() throws Exception { createAction(); AnchorTag tag = createTag(); tag.setHref("a"); - tag.setEscapeHtmlBody(false); + tag.setEscapeHtmlBody("false"); StrutsBodyContent body = new StrutsBodyContent(null); body.print("should not HTML escape: < & >"); @@ -282,4 +275,50 @@ public class AnchorTest extends AbstractUITagTest { verifyResource("href-5.txt"); } + + public void testInjectEscapeHtmlBodyFlag() throws Exception { + // given + initDispatcherWithConfigs("struts-default.xml, struts-escape-body.xml"); + String escapeHtmlBody = container.getInstance(String.class, StrutsConstants.STRUTS_UI_ESCAPE_HTML_BODY); + assertEquals("true", escapeHtmlBody); + + createMocks(); + + createAction(); + + AnchorTag tag = createTag(); + + // when + tag.doStartTag(); + + // then + Anchor component = (Anchor) tag.getComponent(); + assertTrue(component.escapeHtmlBody()); + + tag.doEndTag(); + } + + public void testTagAttributeTakesPrecedenceOverInjectEscapeHtmlBodyFlag() throws Exception { + // given + initDispatcherWithConfigs("struts-default.xml, struts-escape-body.xml"); + String escapeHtmlBody = container.getInstance(String.class, StrutsConstants.STRUTS_UI_ESCAPE_HTML_BODY); + assertEquals("true", escapeHtmlBody); + + createMocks(); + + createAction(); + + AnchorTag tag = createTag(); + tag.setEscapeHtmlBody("false"); + + // when + tag.doStartTag(); + + // then + Anchor component = (Anchor) tag.getComponent(); + assertFalse(component.escapeHtmlBody()); + + tag.doEndTag(); + } + } diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/CheckboxTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/CheckboxTest.java index 778fb19f4..783d5aefd 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/CheckboxTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/CheckboxTest.java @@ -40,8 +40,8 @@ public class CheckboxTest extends AbstractUITagTest { * as key. */ @Override - protected Map initializedGenericTagTestProperties() { - Map result = super.initializedGenericTagTestProperties(); + protected Map initializedGenericTagTestProperties() { + Map result = super.initializedGenericTagTestProperties(); new PropertyHolder("value", "true").addToMap(result); return result; } diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/DateTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/DateTagTest.java index 6f6e31ecc..05e267e05 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/DateTagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/DateTagTest.java @@ -19,35 +19,33 @@ package org.apache.struts2.views.jsp.ui; import com.opensymphony.xwork2.ActionContext; - import org.apache.struts2.TestAction; +import org.apache.struts2.components.Component; +import org.apache.struts2.components.DateTextField; import org.apache.struts2.views.jsp.AbstractTagTest; import org.apache.struts2.views.jsp.DateTag; +import javax.servlet.jsp.JspException; import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.time.Instant; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Calendar; import java.util.Date; -import java.util.TimeZone; -import org.apache.struts2.components.Component; -import org.apache.struts2.components.DateTextField; /** * Unit test for {@link org.apache.struts2.components.Date}. - * */ public class DateTagTest extends AbstractTagTest { private DateTag tag; - public void testCustomFormat() throws Exception { + public void testCustomFormatForDateTime() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; - Date now = new Date(); - String formatted = new SimpleDateFormat(format).format(now); + LocalDateTime now = LocalDateTime.now(); + String formatted = DateTimeFormatter.ofPattern(format).format(now); context.put("myDate", now); tag.setName("myDate"); @@ -62,13 +60,55 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); + } + + public void testCustomFormatForLong() throws Exception { + String format = "yyyy/MM/dd"; + long now = new Date().getTime(); + String formatted = DateTimeFormatter.ofPattern(format).format(Instant.ofEpochMilli(now).atZone(ZoneId.systemDefault())); + context.put("myDate", now); + + tag.setName("myDate"); + tag.setNice(false); + tag.setFormat(format); + tag.doStartTag(); + tag.doEndTag(); + assertEquals(formatted, writer.toString()); + + // Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag(). + DateTag freshTag = new DateTag(); + freshTag.setPageContext(pageContext); + assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", + strutsBodyTagsAreReflectionEqual(tag, freshTag)); + } + + public void testCustomFormatForDate() throws Exception { + String format = "yyyy/MM/dd"; + LocalDate now = LocalDate.now(); + String formatted = DateTimeFormatter.ofPattern(format).format(now); + context.put("myDate", now); + + tag.setName("myDate"); + tag.setNice(false); + tag.setFormat(format); + tag.doStartTag(); + tag.doEndTag(); + assertEquals(formatted, writer.toString()); + + // Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag(). + DateTag freshTag = new DateTag(); + freshTag.setPageContext(pageContext); + assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomFormat_clearTagStateSet() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; - Date now = new Date(); - String formatted = new SimpleDateFormat(format).format(now); + LocalDateTime now = LocalDateTime.now(); + String formatted = DateTimeFormatter.ofPattern(format).format(now); context.put("myDate", now); tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing. @@ -86,13 +126,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomGlobalFormatFormat() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; - Date now = new Date(); - String formatted = new SimpleDateFormat(format).format(now); + LocalDateTime now = LocalDateTime.now(); + String formatted = DateTimeFormatter.ofPattern(format).format(now); context.put("myDate", now); ((TestAction) action).setText(org.apache.struts2.components.Date.DATETAG_PROPERTY, format); @@ -106,10 +146,8 @@ public class DateTagTest extends AbstractTagTest { public void testCustomFormatWithTimezone() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; - Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+1")).getTime(); - SimpleDateFormat sdf = new SimpleDateFormat(format); - sdf.setTimeZone(TimeZone.getTimeZone("GMT+1")); - String formatted = sdf.format(now); + LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+1")); + String formatted = DateTimeFormatter.ofPattern(format).format(now); context.put("myDate", now); tag.setName("myDate"); @@ -126,15 +164,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomFormatWithTimezone_clearTagStateSet() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; - Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+1")).getTime(); - SimpleDateFormat sdf = new SimpleDateFormat(format); - sdf.setTimeZone(TimeZone.getTimeZone("GMT+1")); - String formatted = sdf.format(now); + LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+1")); + String formatted = DateTimeFormatter.ofPattern(format).format(now); context.put("myDate", now); tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing. @@ -153,15 +189,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomFormatWithTimezoneAsExpression() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; - Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+2")).getTime(); - SimpleDateFormat sdf = new SimpleDateFormat(format); - sdf.setTimeZone(TimeZone.getTimeZone("GMT+2")); - String formatted = sdf.format(now); + LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+2")); + String formatted = DateTimeFormatter.ofPattern(format).format(now); context.put("myDate", now); context.put("myTimezone", "GMT+2"); @@ -178,15 +212,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomFormatWithTimezoneAsExpression_clearTagStateSet() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; - Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+2")).getTime(); - SimpleDateFormat sdf = new SimpleDateFormat(format); - sdf.setTimeZone(TimeZone.getTimeZone("GMT+2")); - String formatted = sdf.format(now); + LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+2")); + String formatted = DateTimeFormatter.ofPattern(format).format(now); context.put("myDate", now); context.put("myTimezone", "GMT+2"); @@ -206,13 +238,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomFormatCalendar() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; Calendar calendar = Calendar.getInstance(); - String formatted = new SimpleDateFormat(format).format(calendar.getTime()); + String formatted = DateTimeFormatter.ofPattern(format).format(calendar.toInstant().atZone(ZoneId.systemDefault())); context.put("myDate", calendar); tag.setName("myDate"); @@ -227,13 +259,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomFormatCalendar_clearTagStateSet() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; Calendar calendar = Calendar.getInstance(); - String formatted = new SimpleDateFormat(format).format(calendar.getTime()); + String formatted = DateTimeFormatter.ofPattern(format).format(calendar.toInstant().atZone(ZoneId.systemDefault())); context.put("myDate", calendar); tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing. @@ -251,13 +283,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomFormatLong() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; Date date = new Date(); - String formatted = new SimpleDateFormat(format).format(date); + String formatted = DateTimeFormatter.ofPattern(format).format(date.toInstant().atZone(ZoneId.systemDefault())); // long context.put("myDate", date.getTime()); @@ -273,13 +305,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomFormatLong_clearTagStateSet() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; Date date = new Date(); - String formatted = new SimpleDateFormat(format).format(date); + String formatted = DateTimeFormatter.ofPattern(format).format(date.toInstant().atZone(ZoneId.systemDefault())); // long context.put("myDate", date.getTime()); @@ -298,7 +330,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomFormatLocalDateTime() throws Exception { @@ -332,7 +364,7 @@ public class DateTagTest extends AbstractTagTest { public void testDefaultFormat() throws Exception { Date now = new Date(); String formatted = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, - ActionContext.getContext().getLocale()).format(now); + ActionContext.getContext().getLocale()).format(now); context.put("myDate", now); tag.setName("myDate"); @@ -346,13 +378,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testDefaultFormat_clearTagStateSet() throws Exception { Date now = new Date(); String formatted = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, - ActionContext.getContext().getLocale()).format(now); + ActionContext.getContext().getLocale()).format(now); context.put("myDate", now); tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing. @@ -369,13 +401,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomFormatAndComponent() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; - Date now = new Date(); - String formatted = new SimpleDateFormat(format).format(now); + LocalDateTime now = LocalDateTime.now(); + String formatted = DateTimeFormatter.ofPattern(format).format(now); context.put("myDate", now); tag.setName("myDate"); @@ -388,7 +420,7 @@ public class DateTagTest extends AbstractTagTest { org.apache.struts2.components.Date component = (org.apache.struts2.components.Date) tag.getComponent(); assertEquals("myDate", component.getName()); assertEquals(format, component.getFormat()); - assertEquals(false, component.isNice()); + assertFalse(component.isNice()); tag.doEndTag(); @@ -399,13 +431,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testCustomFormatAndComponent_clearTagStateSet() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; - Date now = new Date(); - String formatted = new SimpleDateFormat(format).format(now); + LocalDateTime now = LocalDateTime.now(); + String formatted = DateTimeFormatter.ofPattern(format).format(now); context.put("myDate", now); tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing. @@ -420,7 +452,7 @@ public class DateTagTest extends AbstractTagTest { org.apache.struts2.components.Date component = (org.apache.struts2.components.Date) tag.getComponent(); assertEquals("myDate", component.getName()); assertEquals(format, component.getFormat()); - assertEquals(false, component.isNice()); + assertFalse(component.isNice()); tag.doEndTag(); @@ -432,13 +464,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testSetId() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; - Date now = new Date(); - String formatted = new SimpleDateFormat(format).format(now); + LocalDateTime now = LocalDateTime.now(); + String formatted = DateTimeFormatter.ofPattern(format).format(now); context.put("myDate", now); tag.setName("myDate"); @@ -454,13 +486,13 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testSetId_clearTagStateSet() throws Exception { String format = "yyyy/MM/dd hh:mm:ss"; - Date now = new Date(); - String formatted = new SimpleDateFormat(format).format(now); + LocalDateTime now = LocalDateTime.now(); + String formatted = DateTimeFormatter.ofPattern(format).format(now); context.put("myDate", now); tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing. @@ -479,7 +511,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureNiceHour() throws Exception { @@ -501,7 +533,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureNiceHour_clearTagStateSet() throws Exception { @@ -526,7 +558,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testPastNiceHour() throws Exception { @@ -548,7 +580,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testPastNiceHour_clearTagStateSet() throws Exception { @@ -573,7 +605,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureNiceHourMinSec() throws Exception { @@ -596,7 +628,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureNiceHourMinSec_clearTagStateSet() throws Exception { @@ -622,7 +654,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testPastNiceHourMin() throws Exception { @@ -645,7 +677,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testPastNiceHourMin_clearTagStateSet() throws Exception { @@ -671,7 +703,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureLessOneMin() throws Exception { @@ -693,7 +725,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureLessOneMin_clearTagStateSet() throws Exception { @@ -718,7 +750,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureLessOneHour() throws Exception { @@ -740,7 +772,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureLessOneHour_clearTagStateSet() throws Exception { @@ -765,7 +797,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureLessOneYear() throws Exception { @@ -787,7 +819,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureLessOneYear_clearTagStateSet() throws Exception { @@ -812,7 +844,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureTwoYears() throws Exception { @@ -838,7 +870,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testFutureTwoYears_clearTagStateSet() throws Exception { @@ -867,7 +899,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testNoDateObjectInContext() throws Exception { @@ -884,7 +916,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } public void testNoDateObjectInContext_clearTagStateSet() throws Exception { @@ -904,7 +936,7 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(tag, freshTag)); + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } /** @@ -912,10 +944,8 @@ public class DateTagTest extends AbstractTagTest { * since that tag does not have its own unit tests, and it also appears to be * a broken tag. The code coverage tests can be moved if the tag is fixed, or * removed if the tag is dropped. - * - * @throws Exception */ - public void testDateTextFieldTag_artificialCoverageTest() throws Exception { + public void testDateTextFieldTag_artificialCoverageTest() throws JspException { final String format = "yyyy/MM/dd hh:mm:ss"; DateTextFieldTag dateTextFieldTag = createDateTextFieldTag(); dateTextFieldTag.setFormat(format); @@ -939,20 +969,40 @@ public class DateTagTest extends AbstractTagTest { freshTag.setPageContext(pageContext); assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", - strutsBodyTagsAreReflectionEqual(dateTextFieldTag, freshTag)); + strutsBodyTagsAreReflectionEqual(dateTextFieldTag, freshTag)); + } + + public void testNewJava8Format() throws Exception { + String format = "EEEE MMMM dd, hh:mm a"; + LocalDateTime now = LocalDateTime.now(); + String formatted = DateTimeFormatter.ofPattern(format, ActionContext.getContext().getLocale()).format(now); + context.put("myDate", now); + + tag.setName("myDate"); + tag.setNice(false); + tag.setFormat(format); + tag.doStartTag(); + tag.doEndTag(); + assertEquals(formatted, writer.toString()); + + // Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag(). + DateTag freshTag = new DateTag(); + freshTag.setPageContext(pageContext); + assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " + + "May indicate that clearTagStateForTagPoolingServers() calls are not working properly.", + strutsBodyTagsAreReflectionEqual(tag, freshTag)); } /** * Utility method to create a new {@link DateTextFieldTag} instance for code coverage tests. - * + *

* Note: There is no datetextfield.ftl template for the tag, so it does not appear that it can - * actually be used in practice. We can perform basic coverage tests from within this - * unit test class until the {@link DateTextFieldTag} is fixed or removed. - * + * actually be used in practice. We can perform basic coverage tests from within this + * unit test class until the {@link DateTextFieldTag} is fixed or removed. + * * @return a basic {@link DateTextFieldTag} instance - * @throws Exception */ - private DateTextFieldTag createDateTextFieldTag() throws Exception { + private DateTextFieldTag createDateTextFieldTag() { DateTextFieldTag tag = new DateTextFieldTag(); tag.setPageContext(pageContext); tag.setName("myDate"); diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/FileTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/FileTest.java index 32b932229..d5a296c7f 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/FileTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/FileTest.java @@ -96,8 +96,8 @@ public class FileTest extends AbstractUITagTest { * as key. */ @Override - protected Map initializedGenericTagTestProperties() { - Map result = super.initializedGenericTagTestProperties(); + protected Map initializedGenericTagTestProperties() { + Map result = super.initializedGenericTagTestProperties(); new PropertyHolder("accept", "someAccepted").addToMap(result); new PropertyHolder("size", "101").addToMap(result); new PropertyHolder("value", "", "").addToMap(result); diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/SubmitTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/SubmitTest.java index 6f573ae07..99eff80d5 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/SubmitTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/SubmitTest.java @@ -630,8 +630,8 @@ public class SubmitTest extends AbstractUITagTest { * as key. */ @Override - protected Map initializedGenericTagTestProperties() { - Map result = new HashMap(); + protected Map initializedGenericTagTestProperties() { + Map result = new HashMap<>(); new PropertyHolder("title", "someTitle").addToMap(result); new PropertyHolder("cssClass", "cssClass1", "class=\"cssClass1\"").addToMap(result); new PropertyHolder("cssStyle", "cssStyle1", "style=\"cssStyle1\"").addToMap(result); @@ -652,8 +652,6 @@ public class SubmitTest extends AbstractUITagTest { /** * Test that by default submit tag body is HTML-escaped. - * - * @throws Exception */ public void testSubmitWithBodyHTMLEscaped() throws Exception { TestAction testAction = (TestAction) action; @@ -670,6 +668,7 @@ public class SubmitTest extends AbstractUITagTest { StrutsBodyContent body = new StrutsBodyContent(null); body.print("should HTML escape: < & >"); tag.setBodyContent(body); + tag.setEscapeHtmlBody(true); tag.doStartTag(); tag.doEndTag(); @@ -678,8 +677,6 @@ public class SubmitTest extends AbstractUITagTest { /** * Test that with htmlEscapeBody false submit tag body is not HTML-escaped. - * - * @throws Exception */ public void testSubmitWithBodyNotHTMLEscaped() throws Exception { TestAction testAction = (TestAction) action; diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/TextareaTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/TextareaTest.java index 54fa4787a..d94bbd2c5 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/TextareaTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/TextareaTest.java @@ -130,8 +130,8 @@ public class TextareaTest extends AbstractUITagTest { * as key. */ @Override - protected Map initializedGenericTagTestProperties() { - Map result = super.initializedGenericTagTestProperties(); + protected Map initializedGenericTagTestProperties() { + Map result = super.initializedGenericTagTestProperties(); new PropertyHolder("cols", "10").addToMap(result); new PropertyHolder("rows", "11").addToMap(result); new PropertyHolder("readonly", "true", "readonly=\"readonly\"").addToMap(result); 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 @@ - + diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html b/core/src/test/resources/struts-escape-body.xml similarity index 70% rename from core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html rename to core/src/test/resources/struts-escape-body.xml index e48517d45..d3f708469 100644 --- a/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html +++ b/core/src/test/resources/struts-escape-body.xml @@ -1,3 +1,4 @@ + -Type conversion meta data classes. + + + + + + + + diff --git a/plugins/async/pom.xml b/plugins/async/pom.xml index b92e8ad60..8512e1986 100644 --- a/plugins/async/pom.xml +++ b/plugins/async/pom.xml @@ -24,7 +24,7 @@ org.apache.struts struts2-plugins - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT struts2-async-plugin diff --git a/plugins/bean-validation/pom.xml b/plugins/bean-validation/pom.xml index fc616fa53..aed8f05f5 100644 --- a/plugins/bean-validation/pom.xml +++ b/plugins/bean-validation/pom.xml @@ -24,7 +24,7 @@ org.apache.struts struts2-plugins - 2.6-SNAPSHOT + 6.0.0-SNAPSHOT 4.0.0 @@ -44,11 +44,6 @@ 2.0.1.Final - - commons-beanutils - commons-beanutils - - org.hibernate hibernate-validator @@ -60,6 +55,17 @@ javax.el test + + org.mockito + mockito-core + test + + + + org.objenesis + objenesis + 3.2 + 9.2 2.10.5 2.10.5.1 - 2.17.1 - 3.2.20 + 2.17.2 + 3.3.2 1.7.32 4.3.30.RELEASE 3.0.8 @@ -933,12 +933,6 @@ commons-collections4 4.4 - - - commons-collections - commons-collections - 3.2.2 - commons-fileupload commons-fileupload @@ -1081,7 +1075,7 @@ org.mockito mockito-core - 3.3.3 + 4.3.1 @@ -1134,7 +1128,7 @@ com.thoughtworks.xstream xstream - 1.4.18 + 1.4.19 io.github.x-stream