[
https://issues.apache.org/jira/browse/WW-5740?focusedWorklogId=1041317&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1041317
]
ASF GitHub Bot logged work on WW-5740:
--------------------------------------
Author: ASF GitHub Bot
Created on: 14/Sep/26 08:44
Start Date: 14/Sep/26 08:44
Worklog Time Spent: 10m
Work Description: lukaszlenart opened a new pull request, #1937:
URL: https://github.com/apache/struts/pull/1937
Fixes [WW-5740](https://issues.apache.org/jira/browse/WW-5740). Docs:
apache/struts-site PR linked below.
## Problem
A field reached through a `visitor` validator (`user.name`) had its
`data-msg-*` text resolved through `DelegatingValidatorContext(action)` — the
action's bundles only — while `VisitorFieldValidator.validateObject` validates
it with a composite provider over the visited class and the action, and pushes
the visited instance for `${...}`. A `<message key="name.required"/>` living in
`ConstraintUser.properties` rendered as the raw key.
## What changed
- **`Form`** gives each unwrapped visitor validator, at unwrap time, the
context validation gives it: `DelegatingValidatorContext(parent,
CompositeTextProvider([each visited level innermost first, parent]), parent)` —
line for line `VisitorFieldValidator.validateObject`. Providers are class-based
(runtime class when the instance exists), so a form rendered before the visited
object exists still finds the bundle. The wrapper carries the whole visited
chain (classes + OGNL paths, outermost first); visitor validators are cached
per visited path rather than per class, since two visitors over one class must
not share the instances the context is set on.
- **`Form.getValidatedObject(name)`** (new, `@since 7.4.0`) returns the
innermost visited instance when the whole chain exists and exactly one visited
path claims the field; otherwise `null` and the caller uses the action. The
lookup re-pushes the validation-time stack — the action plus, for a
`ModelDriven` action whose interceptor pushed it, the model instance taken from
the stack (identity-checked) — so an `<s:iterator>`/`<s:push>` frame above the
action cannot shadow the object validation used.
- **`UIBean`** pushes the visited chain (popped by the existing
`restoreStackDepth`) and hands the provider that object, so `${...}` and
`messageParams` read the visited bean as during validation.
- **`HtmlConstraintProvider`**: no signature change; the third parameter's
Javadoc now says what object it is.
## Review history
Eight passes (security + seven `/code-review`), each landing as its own
commit; the significant ones: doubly-nested visitors chained through every
level; ambiguous fields (two `appendPrefix="false"` visitors, or visitor-nested
next to direct) fall back to the action; visited objects read off the action
rather than the top of the render stack; partial chains hand over the action;
the model is the instance the interceptor pushed, identity-checked, never
`getModel()` re-invoked — the HIGH found on pass six was `modelDriven` being in
`defaultStack`, so "interceptor configured" alone promoted iterator elements to
models.
## Known limitations, stated on purpose
- The visitor's own `<message>` (which `AppendingValidatorContext` prepends
to submitted errors, "User: Name is required") is not reflected in
`data-msg-*`. It is error-display decoration rather than the field's message;
the deprecated JS validator never applied it either.
- Validators come from the form's *target* action, visited instances from
the *rendering* action's stack. When the two differ (`list` renders `<s:form
action="save">`), messages resolve against the rendering action's objects.
Pre-existing; the previous code resolved against the rendering action too.
- The deprecated JS path shares the cached validator instances, so under the
html5 theme with `validate="true"` a visitor-nested field's JS message now also
resolves through the visited bundle once the field has been rendered. An
improvement for that case, but it is render-order dependent.
## Tests
`mvn test -DskipAssembly -pl core`: 3376 tests, 0 failures. Every production
change went red first (the initial symptom, the raw `name.required`, is the
first RED). Fixtures:
`ConstraintUser.properties`/`AdminConstraintUser.properties`,
`ConstraintAddress`, `ModelDrivenConstraintAction`, and a
`modelDrivenConstraintAction` config in `TestConfigurationProvider`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Issue Time Tracking
-------------------
Worklog Id: (was: 1041317)
Remaining Estimate: 0h
Time Spent: 10m
> Resolve HTML5 constraint messages for visitor-validated fields against the
> visited object
> -----------------------------------------------------------------------------------------
>
> Key: WW-5740
> URL: https://issues.apache.org/jira/browse/WW-5740
> Project: Struts 2
> Issue Type: Improvement
> Components: Core Tags
> Reporter: Lukasz Lenart
> Assignee: Lukasz Lenart
> Priority: Minor
> Fix For: 7.4.0
>
> Time Spent: 10m
> Remaining Estimate: 0h
>
> Follow-up to WW-5702 item 4, found in review of its PR.
> {{Form.getFieldValidators}} now unwraps {{FieldVisitorValidatorWrapper}}, so
> a field such as {{user.name}} behind a {{visitor}} validator on {{user}} gets
> its concrete constraint and a {{data-msg-requiredstring}} message. The
> message text, however, is resolved by
> {{StrutsHtmlConstraintProvider.addMessage}} via
> {{validator.getMessage(action)}}, which builds a
> {{DelegatingValidatorContext}} over the *action's* bundles only.
> Server-side, {{VisitorFieldValidator.validateObject}} validates with
> {{createTextProvider(visited, parent)}} — a {{CompositeTextProvider}} that
> also consults the *visited object's* bundle, and pushes the visited object
> onto the stack first. So for the usual reusable-model pattern:
> * {{ConstraintUser-validation.xml}} declares {{<message
> key="name.required"/>}}
> * the text lives in {{ConstraintUser.properties}}
> validation shows the localized text while the rendered field carries
> {{data-msg-requiredstring="name.required"}} — {{ValidatorSupport.getMessage}}
> falls back to the raw key. A {{${...}}} in the nested message that refers to
> a property of the visited bean resolves against the action for the same
> reason.
> This predates WW-5702 (the wrapper already delegated {{getMessage(action)}}),
> but it used to land under the useless {{data-msg-field-visitor}} name; now it
> lands under the real validator type and looks correct.
> Fixing it means the provider needs the object each validator ran against, not
> only the action:
> * {{Form}} carries the visitor prefix out of {{findFieldValidators}} (or
> returns validator + prefix pairs)
> * {{UIBean.addConstraintAttributes}} resolves the visited bean from the stack
> by that prefix
> * {{HtmlConstraintProvider.constraintsFor}} receives it — an interface
> change, acceptable while 7.4.0 is unreleased
> * {{addMessage}} resolves through the same composite text provider validation
> uses, with the action as fallback
> Test: a {{<message key=.../>}} in the visited class's own validation file,
> with the text in the visited class's {{.properties}}; assert the rendered
> {{data-msg-*}} carries the text, not the key.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)