fix: Review 2.1245#84
Conversation
…explicitly Drop the unused RenderContext argument from FormAction.execute and pass validated uploads as List<SubmittedFile> instead of reading them from HttpServletRequest attributes. Update actions, tests, and docs accordingly. Merge custom form action doc with how to create form action
📝 Documentation GuidelinesThank you for contributing to our documentation! To ensure your contributions meet our standards, please review these resources:
This comment is posted automatically when changes are detected in the |
There was a problem hiding this comment.
Pull request overview
This PR updates the Formidable submission pipeline and action SPI to pass validated uploaded files explicitly, while also simplifying field metadata into a single per-field model and consolidating action-authoring documentation.
Changes:
- Replaces the request-attribute file handoff with
List<SubmittedFile>onFormAction.execute(...). - Collapses parser field metadata into
FieldInfo/FieldMetadataand updates validation/collection logic. - Updates in-tree actions, tests, and documentation for the new SPI.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
formidable-engine/src/main/java/org/jahia/modules/formidable/engine/actions/FormAction.java |
Updates the action SPI signature to include uploaded files and remove RenderContext. |
formidable-engine/src/main/java/org/jahia/modules/formidable/engine/actions/SubmittedFile.java |
Adds the SPI-level uploaded-file representation. |
formidable-engine/src/main/java/org/jahia/modules/formidable/engine/actions/FormDataParser.java |
Introduces consolidated field metadata accessors and updates parser validation usage. |
formidable-engine/src/main/java/org/jahia/modules/formidable/engine/servlet/FormSubmissionPipeline.java |
Converts parsed files to submitted files and passes them to actions. |
formidable-engine/src/main/java/org/jahia/modules/formidable/engine/servlet/FormFieldMetadataCollector.java |
Builds the new FieldInfo metadata map. |
formidable-engine/src/main/java/org/jahia/modules/formidable/engine/actions/storage/SaveToJcrFormAction.java |
Uses the new files argument for persisted uploads. |
formidable-engine/src/main/java/org/jahia/modules/formidable/engine/actions/forward/ForwardSubmissionFormAction.java |
Uses the new files argument when rebuilding forwarded multipart requests. |
formidable-engine/src/main/java/org/jahia/modules/formidable/engine/actions/email/SendEmailContentFormAction.java |
Uses the new files argument for email attachments. |
formidable-engine/src/main/java/org/jahia/modules/formidable/engine/actions/email/SendEmailNotificationFormAction.java |
Updates the method signature for the new SPI. |
formidable-engine/src/test/java/org/jahia/modules/formidable/engine/actions/FormDataParserFieldMetadataTest.java |
Adds tests for the new field metadata model. |
formidable-engine/src/test/java/org/jahia/modules/formidable/engine/actions/email/EmailActionRecipientTest.java |
Updates test calls to the new action signature. |
docs/how-to-create-form-action.md |
Consolidates action-authoring guidance and documents the new file argument. |
docs/custom-form-action-from-external-module.md |
Removes the separate external-module action guide after consolidation. |
docs/save-to-jcr.md |
Updates runtime steps to reference the new files argument. |
docs/form-submission-flow.md |
Updates submission-flow docs for the new uploaded-file handoff. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Use explicit 0.2.0-SNAPSHOT versions in the root, module, and test-module POMs, and remove the root flatten-maven-plugin now that CI-friendly version placeholders are no longer used.
jkevan
left a comment
There was a problem hiding this comment.
Scope
This PR targets items 2.1, 2.2, and 2.5 from the master review. Items 2.3 and 2.4 are explicitly deferred or disputed in the PR description. An unrelated pom.xml cleanup (drop ${revision} / flatten-maven-plugin) is included as a separate commit.
Commits reviewed
| SHA | Message |
|---|---|
a8f3615 |
fix: review 2.5 FieldMetadata would be cleaner as a single map |
552068e |
fix: review 2.1-2 remove null RenderContext and pass submitted files explicitly |
f7025be |
Potential fix for pull request finding |
01f7c82 |
Potential fix for pull request finding |
c597d54 |
Potential fix for pull request finding |
2705982 |
update lessons-learned.md to clarify |
0a0ba41 |
refactor: drop ${revision} and remove flatten-maven-plugin (#85) |
Status summary
| Item | Title | Status |
|---|---|---|
| 2.1 | FormAction.execute — RenderContext always null |
✅ Addressed |
| 2.2 | HttpServletRequest as backdoor for files |
✅ Addressed — req now unused in SPI (see below) |
| 2.3 | No pre/post pipeline extension points | ℹ️ Deferred — explicitly acknowledged |
| 2.4 | ConditionalLogicEvaluator O(n²) + monolithic design |
ℹ️ Disputed — accepted (see below) |
| 2.5 | FieldMetadata as parallel maps |
✅ Addressed |
Item-by-item analysis
2.1 — RenderContext removed from FormAction SPI ✅
RenderContext renderContext is removed from FormAction.execute(...) across the interface and all four implementations (SendEmailNotificationFormAction, SendEmailContentFormAction, ForwardSubmissionFormAction, SaveToJcrFormAction).
docs/custom-form-action-from-external-module.md (which advertised the old signature) is deleted. Its content is merged into the revised docs/how-to-create-form-action.md, which now correctly describes the SPI without RenderContext and covers both internal and external module authors.
EmailActionRecipientTest call sites updated accordingly.
2.2 — SubmittedFile SPI type; PARSED_FILES_ATTR removed ✅
What was done:
- New
SubmittedFilepublic record introduced as the SPI boundary type for uploaded files. FormAction.executenow receivesList<SubmittedFile> filesas an explicit parameter.FormDataParser.PARSED_FILES_ATTRconstant removed.req.setAttribute(PARSED_FILES_ATTR, ...)removed fromFormSubmissionPipeline.run.- All three actions that previously did an unchecked
req.getAttributecast now receivefilesdirectly. FormSubmissionPipeline.toSubmittedFiles()converts the internalList<FormFile>toList<SubmittedFile>before dispatching actions.
The @SuppressWarnings("unchecked") casts are gone. The magic string attribute is gone. The separation between internal FormFile (parser output) and SubmittedFile (SPI boundary) is correct — it avoids leaking internal parser types into the public API.
Issue 1 — SubmittedFile is a record with a byte[] field — broken equals()/hashCode() 🔴
Java records generate equals() and hashCode() using the component values. For array components, the compiler uses == (identity) for equals() and System.identityHashCode() for hashCode(), not Arrays.equals/Arrays.hashCode. This means:
byte[] content = {1, 2, 3};
new SubmittedFile("f", "f.txt", "image/png", content)
.equals(new SubmittedFile("f", "f.txt", "image/png", content.clone()))
// → false (even though data is identical)This is the well-known Java SonarQube rule java:S6218 ("Mutable members should not be used in equals()/hashCode() without proper overriding in records"). This is almost certainly the root cause of the E Reliability Rating and 9 new reliability issues reported by Sonar on this PR (3/12 CI checks failing).
Fix options:
- Override
equals()andhashCode()manually usingArrays.equals/Arrays.deepHashCode. - Replace the
byte[] datacomponent with anObjectwrapper (e.g.java.nio.ByteBuffer.wrap(data).asReadOnlyBuffer()) — but this changes the accessor API. - Drop the record syntax and use a plain final class. This is the most explicit option for a stable SPI type with a mutable array field.
This must be resolved before merge — Sonar CI gate is failing.
Issue 2 — HttpServletRequest req is now unused in the SPI 🟠
After removing the req.getAttribute(PARSED_FILES_ATTR) calls, req is no longer used by any of the four action implementations. Verified: ForwardSubmissionFormAction, SaveToJcrFormAction, SendEmailContentFormAction, SendEmailNotificationFormAction — none call any method on req.
The original review proposed keeping req in the signature (as a potential hook for future needs). But with 0 current usages, this is dead weight in a public SPI. Removing it now costs nothing; removing it later is a breaking change. Recommend removing req from the signature while the surface is still young.
2.3 — Pre/post pipeline extension points ℹ️ Deferred
Explicitly deferred in the PR description. No action needed.
2.4 — ConditionalLogicEvaluator O(n²) dispute ℹ️
The PR author disputes the finding: logicIdToFieldName is already precomputed in FormFieldMetadataCollector and the evaluator performs an O(1) lookup. The review's O(n²) claim was based on the original code pattern; if the reverse map was added in an earlier PR, the performance concern no longer applies.
The registry-based operator architecture remains a subjective design suggestion, not a blocking concern. The author's conclusion is accepted.
2.5 — FieldMetadata unified model ✅
What was done:
- New
FormDataParser.FieldInforecord consolidatesnodeType,allowedChoices,acceptTypes,constraintsper field. FormDataParser.FieldMetadatais refactored to wrapMap<String, FieldInfo>with helper accessors (field(),fieldType(),allowedChoices(),acceptTypes(),constraints(),allowedNames()).FormFieldMetadataCollector.CollectorContextloses 4 parallel maps; now holds a singleMap<String, FieldInfo>built bottom-up inregisterField.FormFieldMetadataCollector.Resultsimplified tofieldInfos + logic-related maps.FormFieldMetadataCollector.collectChoices()and newcollectAcceptTypes()now return values instead of mutating context — a cleaner functional approach.FormSubmissionPipeline.validateRequired()updated to use the new API.FormDataParserFieldMetadataTestadded with 3 test cases covering the happy path, absent-field defaults, and null-collection normalization.
Both the constructor defensive copies (Set.copyOf, Map.copyOf) and the null normalization are correctly implemented. The refactor is clean.
Docs and tooling changes
how-to-create-form-action.md — example code uses System.out.println 🟠
Summary of new issues surfaced
| Severity | Location | Description |
|---|---|---|
| 🔴 Blocking | SubmittedFile.java |
Record with byte[] data — auto-generated equals()/hashCode() use array identity, not content; root cause of Sonar E Reliability rating and 9 new issues — must fix before merge |
| 🟠 Medium | FormAction.java |
req parameter is now unused across all 4 implementations — dead SPI weight; remove while still cheap |
| 🟠 Medium | docs/how-to-create-form-action.md |
Example code uses System.out.println — should use SLF4J logger; teaches bad practice to external module authors |
Override SubmittedFile.equals() and hashCode() so uploaded file value semantics are based on byte-array content instead of array identity, while keeping defensive copies on construction and access. Add focused unit tests for SubmittedFile content equality and immutability. Update the custom FormAction documentation to use SLF4J logger examples instead of System.out.println, so external action authors follow the same logging practices as the runtime code. Note : We should keep HttpServletRequest req in the FormAction SPI for now. The review assumes it is dead across all implementations, but that is not true in the current code: SaveToJcrFormAction still uses it to persist request-derived metadata (lang and Referer) with the submission. So removing it would not be a no-op cleanup; it would require either changing existing behavior or introducing a replacement request-context object. More broadly, keeping req in the SPI remains defensible because some action types may legitimately need request-level context that is not part of the validated form payload itself, such as headers, locale/routing information, or other submission metadata. As long as there is at least one real consumer today, and plausible future consumers tomorrow, I would not change the SPI just to make the unused implementations look cleaner.
|


Description
2.1 FormAction.execute — RenderContext is always null : done
2.2 HttpServletRequest as a back door for parsed files : done
2.3 No pre/post pipeline extension points : keep for later
2.4 ConditionalLogicEvaluator — O(n²) lookups and monolithic design :
I don’t think this review is valid as written.
The performance concern appears stale relative to the current code. ConditionalLogicEvaluator already performs an O(1) lookup through logicIdToFieldName.get(logicId), and that map is precomputed in FormFieldMetadataCollector. So the claimed O(n²) reverse-lookup cost does not match the implementation that is currently in the branch.
The broader design comment is more subjective. Yes, the evaluator centralizes operator semantics in one switch, and a registry-based operator model could be a reasonable future refactor if this area grows. But at the current scale (about 10 simple operators), that is an architectural preference, not a correctness issue, and not something I would block on in this PR.
My conclusion:
2.5 FieldMetadata would be cleaner as a single map : done
Checklist
Source code
Tests
Tip
Documentation to guide the reviews: How to do a code review