[
https://issues.apache.org/jira/browse/WW-5811?focusedWorklogId=1050044&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1050044
]
ASF GitHub Bot logged work on WW-5811:
--------------------------------------
Author: ASF GitHub Bot
Created on: 25/Oct/26 15:32
Start Date: 25/Oct/26 15:32
Worklog Time Spent: 10m
Work Description: Copilot commented on code in PR #2061:
URL: https://github.com/apache/struts/pull/2061#discussion_r4371355839
##########
core/src/main/java/org/apache/struts2/components/StrutsHtmlConstraintProvider.java:
##########
@@ -119,7 +137,11 @@ protected void addConstraints(Map<String, String>
attributes, Validator validato
} else if (validator instanceof DoubleRangeFieldValidator
doubleValidator) {
addDoubleRange(attributes, doubleValidator, control);
} else if (validator instanceof RangeValidatorSupport<?>
rangeValidator) {
- addRange(attributes, rangeValidator, control);
+ if (control == HtmlControlType.DATE) {
+ addDateRange(attributes, rangeValidator);
+ } else {
+ addRange(attributes, rangeValidator, control);
Review Comment:
The new `DATE` branch bypasses existing protected `addRange` overrides,
silently disabling subclasses' date-range customizations despite the stated
protected-hook compatibility guarantee. Keep dispatching through `addRange`,
move the default date handling into that method, and add a regression test for
an existing override.
##########
core/src/main/java/org/apache/struts2/components/Form.java:
##########
@@ -428,6 +438,51 @@ private List<Object> resolveChain(String path) {
return chain;
}
+ /**
+ * The declared type of the property a field binds to, found on the object
parameter binding would
+ * write to and then walked through declared types rather than values, so
a null intermediate object
+ * (which binding creates) does not hide it. Null for an indexed or
unknown path.
+ */
+ Class<?> getPropertyType(String name) {
+ if (name == null || name.isEmpty() || StringUtils.containsAny(name,
'[', '(')) {
+ return null;
+ }
+ String[] segments = name.split("\\.");
+ for (Object root : bindingRoots()) {
+ Class<?> type = declaredType(root.getClass(), segments[0]);
+ if (type != null) {
+ for (int i = 1; i < segments.length && type != null; i++) {
+ type = declaredType(type, segments[i]);
+ }
+ return type;
+ }
+ }
+ return null;
+ }
+
+ private List<Object> bindingRoots() {
+ Object invoked = currentAction();
Review Comment:
Validators come from the form's submission action (`actionClass`), but this
lookup uses the action rendering the page. If the rendering action has a `Date
birthday` and the target has a `String birthday` with a `required` validator,
the browser incorrectly blocks empty input that the target accepts. Resolve the
submission action's binding type, or return an unknown type when the rendering
action cannot establish it.
##########
core/src/main/java/org/apache/struts2/components/StrutsHtmlConstraintProvider.java:
##########
@@ -155,6 +178,63 @@ protected void addRequiredField(Map<String, String>
attributes, RequiredFieldVal
}
}
+ /**
+ * An empty temporal input submits {@code ""}, which the built-in
converters turn into null for the
+ * types {@link #convertsBlankToNull(Class)} accepts, so {@code required}
then fails on both sides.
+ *
+ * @since 7.5.0
+ */
+ protected void addTemporalRequired(Map<String, String> attributes,
HtmlControlType control, Class<?> propertyType) {
+ if (control.isTemporal() && convertsBlankToNull(propertyType)) {
+ attributes.put(REQUIRED_ATTRIBUTE, REQUIRED_ATTRIBUTE);
+ }
+ }
+
+ /**
+ * A property with its own converter that maps {@code ""} to a value needs
a provider overriding this.
+ *
+ * @since 7.5.0
+ */
+ protected boolean convertsBlankToNull(Class<?> propertyType) {
+ if (propertyType == null) {
+ return false;
+ }
+ return Date.class.isAssignableFrom(propertyType)
+ || Calendar.class.isAssignableFrom(propertyType)
+ || LocalDate.class.isAssignableFrom(propertyType)
+ || LocalDateTime.class.isAssignableFrom(propertyType)
+ || LocalTime.class.isAssignableFrom(propertyType)
+ || OffsetDateTime.class.isAssignableFrom(propertyType);
+ }
+
+ /**
+ * A {@code java.util.Date} bound becomes its calendar date in the default
time zone, the zone the
+ * submitted {@code yyyy-MM-dd} is parsed in. Dropping the time leaves the
browser no stricter than the
+ * server: a min of 10:00 on the 1st still lets the 1st through
client-side.
+ *
+ * @since 7.5.0
+ */
+ protected void addDateRange(Map<String, String> attributes,
RangeValidatorSupport<?> validator) {
+ putIfPresent(attributes, "min", toHtmlDate(validator.getMin()));
+ putIfPresent(attributes, "max", toHtmlDate(validator.getMax()));
+ }
+
+ private String toHtmlDate(Object bound) {
+ LocalDate date;
+ if (bound instanceof LocalDate localDate) {
+ date = localDate;
+ } else if (bound instanceof Date utilDate) {
+ date =
Instant.ofEpochMilli(utilDate.getTime()).atZone(ZoneId.systemDefault()).toLocalDate();
Review Comment:
`DateConverter` parses `yyyy-MM-dd` with a hybrid Julian/Gregorian calendar,
but this conversion uses the proleptic Gregorian calendar. In UTC, a bound
parsed from `1500-01-01` emits `min="1500-01-10"`, blocking a date that
satisfies the server's inclusive minimum. Match the converter's calendar
semantics, or omit bounds that cannot round-trip, and add a pre-cutover
regression test.
##########
core/src/main/java/org/apache/struts2/components/StrutsHtmlConstraintProvider.java:
##########
@@ -155,6 +178,63 @@ protected void addRequiredField(Map<String, String>
attributes, RequiredFieldVal
}
}
+ /**
+ * An empty temporal input submits {@code ""}, which the built-in
converters turn into null for the
+ * types {@link #convertsBlankToNull(Class)} accepts, so {@code required}
then fails on both sides.
+ *
+ * @since 7.5.0
+ */
+ protected void addTemporalRequired(Map<String, String> attributes,
HtmlControlType control, Class<?> propertyType) {
+ if (control.isTemporal() && convertsBlankToNull(propertyType)) {
+ attributes.put(REQUIRED_ATTRIBUTE, REQUIRED_ATTRIBUTE);
+ }
+ }
+
+ /**
+ * A property with its own converter that maps {@code ""} to a value needs
a provider overriding this.
+ *
+ * @since 7.5.0
+ */
+ protected boolean convertsBlankToNull(Class<?> propertyType) {
+ if (propertyType == null) {
+ return false;
+ }
+ return Date.class.isAssignableFrom(propertyType)
+ || Calendar.class.isAssignableFrom(propertyType)
+ || LocalDate.class.isAssignableFrom(propertyType)
+ || LocalDateTime.class.isAssignableFrom(propertyType)
+ || LocalTime.class.isAssignableFrom(propertyType)
+ || OffsetDateTime.class.isAssignableFrom(propertyType);
+ }
+
+ /**
+ * A {@code java.util.Date} bound becomes its calendar date in the default
time zone, the zone the
+ * submitted {@code yyyy-MM-dd} is parsed in. Dropping the time leaves the
browser no stricter than the
+ * server: a min of 10:00 on the 1st still lets the 1st through
client-side.
+ *
+ * @since 7.5.0
+ */
+ protected void addDateRange(Map<String, String> attributes,
RangeValidatorSupport<?> validator) {
+ putIfPresent(attributes, "min", toHtmlDate(validator.getMin()));
Review Comment:
Emitting min also changes a date input's HTML step base. For example, a
control with step="7" and value="2026-01-05" accepts that value before
derivation, but a validator minimum of 2026-01-01 changes the permitted grid to
Thursdays and makes the existing Monday value fail step validation, although
the server accepts it. Preserve the developer's step grid by suppressing
derived min when it would shift that grid; this needs the tag's step/value
context.
Issue Time Tracking
-------------------
Worklog Id: (was: 1050044)
Time Spent: 50m (was: 40m)
> Derive HTML5 min/max and required constraints for temporal controls
> -------------------------------------------------------------------
>
> Key: WW-5811
> URL: https://issues.apache.org/jira/browse/WW-5811
> Project: Struts 2
> Issue Type: Improvement
> Components: Core Tags
> Reporter: Lukasz Lenart
> Assignee: Lukasz Lenart
> Priority: Major
> Fix For: 7.5.0
>
> Time Spent: 50m
> Remaining Estimate: 0h
>
> h2. Problem
> HTML5 constraint derivation ({{struts.ui.html5.constraints}}) maps range
> validators to {{min}}/{{max}} only for {{number}} and {{range}} controls.
> {{StrutsHtmlConstraintProvider}} deliberately skips the temporal controls, so
> {{DateRangeFieldValidator}} (and the new {{localdate}} validator, WW-5816)
> emit nothing.
> The {{required}} validator has a related gap. {{addRequiredField}} emits
> {{required}} only for {{radio}} and {{file}}, because other controls submit
> an empty string, which the {{required}} validator accepts. A temporal control
> bound to a {{java.util.Date}} (or {{java.time}}) property is different:
> {{DateConverter}} turns {{""}} into null, so the server rejects the empty
> value just as the browser would. These controls currently get only
> {{data-msg-required}}.
> h2. Scope
> * {{min}}/{{max}} on *{{date}} controls only*, from {{LocalDate}} bounds
> ({{localdate}}) and {{java.util.Date}} bounds ({{date}}), written as
> {{yyyy-MM-dd}}. A {{java.util.Date}} bound becomes its calendar date in the
> server default time zone (the zone the submitted value is parsed in);
> dropping the time never makes the browser stricter than the server. Years
> outside 0001-9999 are skipped.
> * {{required}} on *all temporal controls* ({{date}}, {{month}}, {{week}},
> {{time}}, {{datetime-local}}) from a {{required}} validator, when the bound
> property's declared type converts {{""}} to null: {{java.util.Date}} and
> subclasses, {{Calendar}}, {{LocalDate}}, {{LocalDateTime}}, {{LocalTime}},
> {{OffsetDateTime}}. A {{String}} property gets no {{required}}.
> * The provider needs the property type, so {{HtmlConstraintProvider}} gains a
> {{default}} {{constraintsFor}} overload carrying it, which delegates to the
> 7.4.0 method.
> h2. Out of scope
> {{min}}/{{max}} on {{month}}, {{week}}, {{time}} and {{datetime-local}}: no
> built-in converter and range validator pair reads their submitted values
> ({{datetime-local}} submits without seconds, there is no
> {{LocalTime}}/{{LocalDateTime}} range validator, and {{month}}/{{week}} have
> no converter). A separate ticket if needed.
> Related: WW-5778 (datetextfield in the html5 theme), WW-5810 (java.time
> support in datetextfield), WW-5816 ({{localdate}} validator)
> PR: [apache/struts#2061|https://github.com/apache/struts/pull/2061], docs:
> [apache/struts-site#367|https://github.com/apache/struts-site/pull/367]
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)