This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch feat/struts-730-modernization
in repository https://gitbox.apache.org/repos/asf/struts-examples.git

commit 1258a5317b0e022c7f41461a8cf7b355eb6d85e0
Author: Lukasz Lenart <[email protected]>
AuthorDate: Fri Aug 14 11:57:32 2026 +0200

    docs: implementation plan for Struts 7.3.0 modernization
    
    Six tasks covering the version bump, DOCTYPE and web.xml schema sweeps,
    eight verified @StrutsParameter fixes, the Apache Struts renaming, and
    final verification.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 .../plans/2026-08-14-struts-730-modernization.md   | 767 +++++++++++++++++++++
 1 file changed, 767 insertions(+)

diff --git a/docs/superpowers/plans/2026-08-14-struts-730-modernization.md 
b/docs/superpowers/plans/2026-08-14-struts-730-modernization.md
new file mode 100644
index 0000000..2478f47
--- /dev/null
+++ b/docs/superpowers/plans/2026-08-14-struts-730-modernization.md
@@ -0,0 +1,767 @@
+# Struts 7.3.0 Modernization Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use 
superpowers:subagent-driven-development (recommended) or 
superpowers:executing-plans to implement this plan task-by-task. Steps use 
checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Bring all 47 example modules to the Struts 7.3.0 standard — 
framework version, XML DOCTYPEs and schemas, `@StrutsParameter` annotations, 
and "Apache Struts" naming.
+
+**Architecture:** Four independent sweeps over an existing Maven multi-module 
repo. Three are mechanical text transformations verified by exact match counts; 
one (the annotation fixes) is eight hand-verified source edits. No new modules, 
no new dependencies, no new test infrastructure.
+
+**Tech Stack:** Java 17/21, Maven, Apache Struts 7.3.0, Jakarta Servlet 6.1, 
JSP, Log4j2.
+
+**Spec:** 
`docs/superpowers/specs/2026-08-14-struts-730-modernization-design.md`
+
+## Global Constraints
+
+- Target Struts version: **7.3.0** (`struts2.version` in the parent `pom.xml`).
+- Struts config DTD: public ID `-//Apache Software Foundation//DTD Struts 
Configuration 6.5//EN`, system ID 
`https://struts.apache.org/dtds/struts-6.5.dtd`.
+- Validator DTD: public ID `-//Apache Struts//XWork Validator 1.0.3//EN`, 
system ID `https://struts.apache.org/dtds/xwork-validator-1.0.3.dtd`.
+- Tiles DTD: public ID `-//Apache Software Foundation//DTD Tiles Configuration 
3.0//EN`, system ID `https://tiles.apache.org/dtds/tiles-config_3_0.dtd`.
+- Maven POM schema: `https://maven.apache.org/xsd/maven-4.0.0.xsd`.
+- `web.xml` schema: `https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd`, 
`version="6.0"`.
+- **Do not modify any `struts.allowlist.*` constant.** Spec decision: removing 
working security config is out of scope.
+- **Do not change `struts.chaining.requireAnnotations`.** Leave at its default 
`false`.
+- Do not add test dependencies or new test files. Spec decision.
+- Branch: `feat/struts-730-modernization` (already checked out).
+- Commit messages carry no `WW-` prefix.
+- Every commit ends with the trailer `Co-Authored-By: Claude Opus 5 
<[email protected]>`.
+
+**Note on TDD:** This plan does not follow a red-green cycle. The spec 
explicitly declined new test infrastructure, and the repo has only 6 test 
files, none covering the touched code. Each task therefore substitutes a 
**deterministic verification command with an expected exact count** for the 
failing-test step. Do not skip these — they are the only automated check that a 
sweep hit its intended targets and nothing else.
+
+---
+
+### Task 1: Bump Struts to 7.3.0
+
+**Files:**
+- Modify: `pom.xml:56`
+
+**Interfaces:**
+- Consumes: nothing
+- Produces: `struts2.version` = `7.3.0`, inherited by all 47 modules
+
+- [ ] **Step 1: Confirm the current version before changing it**
+
+Run:
+```bash
+grep -n '<struts2.version>' pom.xml
+```
+Expected: exactly one line — `<struts2.version>7.2.1</struts2.version>`
+
+- [ ] **Step 2: Apply the bump**
+
+In `pom.xml`, replace:
+```xml
+<struts2.version>7.2.1</struts2.version>
+```
+with:
+```xml
+<struts2.version>7.3.0</struts2.version>
+```
+
+- [ ] **Step 3: Verify the whole reactor resolves and compiles against 7.3.0**
+
+Run:
+```bash
+./mvnw -B -q clean test
+```
+Expected: BUILD SUCCESS. All 6 existing tests pass. If any module fails to 
resolve `7.3.0`, stop — do not proceed to Task 2.
+
+- [ ] **Step 4: Confirm no other file pins a Struts version**
+
+Run:
+```bash
+grep -rn '7\.2\.1' --include='pom.xml' . | grep -v target
+```
+Expected: no output. Module POMs all use `${struts2.version}`.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add pom.xml
+git commit -m "Bump Struts to 7.3.0
+
+Co-Authored-By: Claude Opus 5 <[email protected]>"
+```
+
+---
+
+### Task 2: Normalize Struts, validator, tiles, and Maven DOCTYPEs
+
+Pure find-and-replace across 79 files. The public ID and system ID are 
replaced independently so the transformation works regardless of how the 
DOCTYPE is wrapped across lines.
+
+**Files:**
+- Modify: 54 files containing `DOCTYPE struts`
+- Modify: 13 files containing `DOCTYPE validators`
+- Modify: 7 files containing `DOCTYPE tiles`
+- Modify: 5 `pom.xml` files referencing `maven-v4_0_0.xsd`
+
+**Interfaces:**
+- Consumes: Task 1's version bump (7.3.0 ships `struts-6.5.dtd`)
+- Produces: nothing consumed by later tasks
+
+- [ ] **Step 1: Record the "before" counts**
+
+Run:
+```bash
+grep -rlE '\-//Apache Software Foundation//DTD Struts Configuration 
[0-9.]+//EN' --include='*.xml' . | grep -v target | wc -l
+grep -rlE '\-//Apache Struts//XWork Validator 1\.0\.[23]//EN' 
--include='*.xml' . | grep -v target | wc -l
+grep -rlE '\-//Apache Software Foundation//DTD Tiles Configuration 
[0-9.]+//EN' --include='*.xml' . | grep -v target | wc -l
+grep -rl 'maven-v4_0_0.xsd' --include='pom.xml' . | grep -v target | wc -l
+```
+Expected, in order: `54`, `13`, `7`, `5`
+
+- [ ] **Step 2: Rewrite the Struts config DOCTYPEs**
+
+```bash
+grep -rlE '\-//Apache Software Foundation//DTD Struts Configuration 
[0-9.]+//EN' --include='*.xml' . | grep -v target | \
+  xargs perl -pi -e '
+    s{-//Apache Software Foundation//DTD Struts Configuration 
[0-9.]+//EN}{-//Apache Software Foundation//DTD Struts Configuration 6.5//EN}g;
+    
s{https?://struts\.apache\.org/dtds/struts-[0-9.]+\.dtd}{https://struts.apache.org/dtds/struts-6.5.dtd}g;
+  '
+```
+
+- [ ] **Step 3: Rewrite the validator DOCTYPEs**
+
+```bash
+grep -rlE '\-//Apache Struts//XWork Validator 1\.0\.[23]//EN' 
--include='*.xml' . | grep -v target | \
+  xargs perl -pi -e '
+    s{-//Apache Struts//XWork Validator 1\.0\.[23]//EN}{-//Apache 
Struts//XWork Validator 1.0.3//EN}g;
+    
s{https?://struts\.apache\.org/dtds/xwork-validator-1\.0\.[23]\.dtd}{https://struts.apache.org/dtds/xwork-validator-1.0.3.dtd}g;
+  '
+```
+
+- [ ] **Step 4: Rewrite the tiles DOCTYPEs**
+
+```bash
+grep -rlE '\-//Apache Software Foundation//DTD Tiles Configuration 
[0-9.]+//EN' --include='*.xml' . | grep -v target | \
+  xargs perl -pi -e '
+    s{-//Apache Software Foundation//DTD Tiles Configuration 
[0-9.]+//EN}{-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN}g;
+    
s{https?://tiles\.apache\.org/dtds/tiles-config_[0-9_]+\.dtd}{https://tiles.apache.org/dtds/tiles-config_3_0.dtd}g;
+  '
+```
+
+- [ ] **Step 5: Rewrite the Maven POM schema URLs**
+
+```bash
+grep -rl 'maven-v4_0_0.xsd' --include='pom.xml' . | grep -v target | \
+  xargs perl -pi -e 
's{http://maven\.apache\.org/maven-v4_0_0\.xsd}{https://maven.apache.org/xsd/maven-4.0.0.xsd}g'
+```
+
+- [ ] **Step 6: Verify every target was hit and nothing stale remains**
+
+Do **not** use a `grep -P` negative lookahead here — combining `-E` and `-P` 
silently returns wrong counts rather than erroring, which would make this check 
lie. Match the stale versions explicitly instead:
+
+```bash
+echo "stale struts:    $(grep -rlE 
'struts-(2\.0|2\.1|2\.1\.7|2\.3|2\.5|6\.0)\.dtd' --include='*.xml' . | grep -v 
target | wc -l | tr -d ' ')"
+echo "new struts:      $(grep -rl 'struts-6.5.dtd' --include='*.xml' . | grep 
-v target | wc -l | tr -d ' ')"
+echo "stale validator: $(grep -rl 'xwork-validator-1.0.2' --include='*.xml' . 
| grep -v target | wc -l | tr -d ' ')"
+echo "new validator:   $(grep -rl 'xwork-validator-1.0.3.dtd' 
--include='*.xml' . | grep -v target | wc -l | tr -d ' ')"
+echo "stale tiles:     $(grep -rl 'tiles-config_2_0' --include='*.xml' . | 
grep -v target | wc -l | tr -d ' ')"
+echo "http dtds left:  $(grep -rlE 'http://(struts|tiles)\.apache\.org/dtds' 
--include='*.xml' . | grep -v target | wc -l | tr -d ' ')"
+echo "stale maven:     $(grep -rl 'maven-v4_0_0.xsd' --include='pom.xml' . | 
grep -v target | wc -l | tr -d ' ')"
+```
+Expected after the rewrite: `stale struts: 0`, `new struts: 54`, `stale 
validator: 0`, `new validator: 13`, `stale tiles: 0`, `http dtds left: 0`, 
`stale maven: 0`
+
+For reference, the same commands **before** the rewrite print `54`, `0`, `7`, 
`6`, `1`, `28`, `5`.
+
+- [ ] **Step 7: Confirm every XML file is still well-formed**
+
+```bash
+find . -name '*.xml' -not -path '*/target/*' -print0 | xargs -0 -n1 xmllint 
--noout --nonet
+```
+Expected: no output. Any parse error means a replacement corrupted a file — 
fix before committing.
+
+- [ ] **Step 8: Build**
+
+```bash
+./mvnw -B -q clean test
+```
+Expected: BUILD SUCCESS.
+
+- [ ] **Step 9: Commit**
+
+```bash
+git add -A
+git commit -m "Modernize XML DOCTYPEs to current Struts standards
+
+Struts config DTDs to 6.5, XWork validator DTDs to 1.0.3, Tiles to 3.0,
+and Maven POM schema URLs to the current location. All system IDs now
+use https.
+
+Co-Authored-By: Claude Opus 5 <[email protected]>"
+```
+
+---
+
+### Task 3: Migrate `web.xml` to the Jakarta EE 6.0 schema
+
+29 of 47 `web.xml` files are stranded on `web-app_2_3.dtd` (1), `2_4` (19), 
`2_5` (3), `3_0` (1), and `xmlns.jcp.org/…3_1` (5). The other 18 are already 
correct and must not be touched. This needs a script rather than a regex 
because the root element's attributes must be rewritten as a unit, and the 2.3 
file carries a DOCTYPE that has to be removed.
+
+**Files:**
+- Create: `/tmp/migrate_webxml.py` (throwaway; not committed)
+- Modify: 29 `web.xml` files
+
+**Interfaces:**
+- Consumes: nothing
+- Produces: nothing consumed by later tasks
+
+- [ ] **Step 1: Record the "before" count**
+
+```bash
+grep -rlE 'web-app_(2_3\.dtd|2_4|2_5|3_0|3_1)' --include='web.xml' . | grep -v 
target | wc -l
+```
+Expected: `29`
+
+- [ ] **Step 2: Write the migration script**
+
+Create `/tmp/migrate_webxml.py`:
+
+```python
+import re, subprocess, sys
+
+TARGET = (
+    '<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"\n'
+    '         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n'
+    '         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee '
+    'https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"\n'
+    '         version="6.0">'
+)
+
+files = subprocess.run(
+    ["grep", "-rlE", r"web-app_(2_3\.dtd|2_4|2_5|3_0|3_1)", 
"--include=web.xml", "."],
+    capture_output=True, text=True, check=True,
+).stdout.split()
+files = [f for f in files if "/target/" not in f]
+
+changed = 0
+for path in files:
+    src = open(path, encoding="utf-8").read()
+    orig = src
+    # Drop any legacy DOCTYPE (the 2.3-era file).
+    src = re.sub(r'<!DOCTYPE\s+web-app\b.*?>\s*', '', src, flags=re.S)
+    # Replace the whole opening <web-app ...> tag, however its attributes wrap.
+    src, n = re.subn(r'<web-app\b[^>]*?>', TARGET, src, count=1, flags=re.S)
+    if n != 1:
+        sys.exit(f"FAILED: no <web-app> opening tag found in {path}")
+    # Preserve an ISO-8859-1 declaration by normalising to UTF-8 (content is 
ASCII).
+    src = src.replace('<?xml version="1.0" encoding="ISO-8859-1"?>',
+                      '<?xml version="1.0" encoding="UTF-8"?>')
+    if src != orig:
+        open(path, "w", encoding="utf-8").write(src)
+        changed += 1
+
+print(f"migrated {changed} files")
+```
+
+- [ ] **Step 3: Run it**
+
+```bash
+python3 /tmp/migrate_webxml.py
+```
+Expected: `migrated 29 files`
+
+- [ ] **Step 4: Verify all 47 are now on the 6.0 schema and none was missed**
+
+```bash
+echo "on 6.0:     $(grep -rl 'web-app_6_0.xsd' --include='web.xml' . | grep -v 
target | wc -l)"
+echo "stale left: $(grep -rlE 
'java\.sun\.com|xmlns\.jcp\.org|web-app_(2_3|2_4|2_5|3_0|3_1)' 
--include='web.xml' . | grep -v target | wc -l)"
+echo "total:      $(find . -name web.xml -not -path '*/target/*' | wc -l)"
+```
+Expected: `on 6.0: 47`, `stale left: 0`, `total: 47`
+
+- [ ] **Step 5: Confirm well-formedness**
+
+```bash
+find . -name 'web.xml' -not -path '*/target/*' -print0 | xargs -0 -n1 xmllint 
--noout --nonet
+```
+Expected: no output.
+
+- [ ] **Step 6: Confirm no filter/servlet declarations were lost**
+
+The root-element rewrite must not have eaten body content. Compare against 
`HEAD`:
+
+```bash
+for f in $(git diff --name-only -- '*web.xml'); do
+  a=$(git show HEAD:$f | grep -c 
'<filter-class>\|<servlet-class>\|<listener-class>')
+  b=$(grep -c '<filter-class>\|<servlet-class>\|<listener-class>' $f)
+  [ "$a" != "$b" ] && echo "MISMATCH $f: was $a now $b"
+done; echo "comparison done"
+```
+Expected: `comparison done` with no `MISMATCH` lines.
+
+- [ ] **Step 7: Build**
+
+```bash
+./mvnw -B -q clean test
+```
+Expected: BUILD SUCCESS.
+
+- [ ] **Step 8: Commit**
+
+```bash
+git add -A
+git commit -m "Migrate web.xml descriptors to Jakarta EE 6.0 schema
+
+Brings the 29 descriptors still on Servlet 2.3-3.1 era schemas in line
+with the 18 already using web-app_6_0.xsd.
+
+Co-Authored-By: Claude Opus 5 <[email protected]>"
+```
+
+---
+
+### Task 4: Fix missing `@StrutsParameter` annotations
+
+Eight fixes across five classes in four modules. Each was hand-verified 
against the submitting JSP form. **Do not add annotations beyond these eight** 
— the audit confirmed every other action is either correct, ModelDriven-exempt, 
or has a property that is never submitted (annotating those would wrongly widen 
the injection surface).
+
+Rules being applied, from 
`ParametersInterceptor.hasValidAnnotatedPropertyDescriptor`:
+- depth 0 → annotation goes on the **setter**
+- depth ≥ 1 → annotation goes on the **getter**
+
+**Files:**
+- Modify: `crud/src/main/java/org/apache/struts/crud/action/PersonAction.java`
+- Modify: 
`shiro-basic/src/main/java/org/apache/struts2/shiro/example/action/LoginAction.java`
+- Modify: 
`shiro-basic/src/main/java/org/apache/struts2/shiro/example/action/WelcomeAction.java`
+- Modify: 
`validation-messages/src/main/java/org/apache/struts/validation_messages/Login.java`
+- Modify: 
`unknown-handler/src/main/java/org/apache/strutsexamples/actions/Login.java`
+
+**Interfaces:**
+- Consumes: Task 1's 7.3.0 bump
+- Produces: nothing consumed by later tasks
+
+- [ ] **Step 1: `crud` — annotate the nested `person` getter**
+
+Evidence: `person.personId` and `person.country.countryId` are submitted, so 
depth is 2. `PersonAction` is `Preparable`, **not** `ModelDriven`, so it is not 
exempt.
+
+Add the import alongside the existing `org.apache.struts2` imports:
+```java
+import org.apache.struts2.interceptor.parameter.StrutsParameter;
+```
+
+Replace:
+```java
+    public Person getPerson() {
+        return person;
+    }
+```
+with:
+```java
+    @StrutsParameter(depth = 2)
+    public Person getPerson() {
+        return person;
+    }
+```
+
+Leave `setPerson` unannotated — at depth 2 the setter's annotation is never 
consulted.
+
+- [ ] **Step 2: `shiro-basic/LoginAction` — annotate both setters**
+
+Evidence: the login form submits `username` and `password` at depth 0. Note 
this file uses Allman braces; match that style.
+
+Add the import:
+```java
+import org.apache.struts2.interceptor.parameter.StrutsParameter;
+```
+
+Replace:
+```java
+    public void setUsername(String username)
+    {
+        this.username = username;
+    }
+```
+with:
+```java
+    @StrutsParameter
+    public void setUsername(String username)
+    {
+        this.username = username;
+    }
+```
+
+Replace:
+```java
+    public void setPassword(String password)
+    {
+        this.password = password;
+    }
+```
+with:
+```java
+    @StrutsParameter
+    public void setPassword(String password)
+    {
+        this.password = password;
+    }
+```
+
+- [ ] **Step 3: `shiro-basic/WelcomeAction` — annotate the `username` setter**
+
+Evidence: `execute()` compares the submitted `username` against the session 
attribute, so it genuinely expects the parameter. This file currently has no 
`org.apache.struts2` import.
+
+Add after the existing imports:
+```java
+import org.apache.struts2.interceptor.parameter.StrutsParameter;
+```
+
+Replace:
+```java
+    public void setUsername(String username) 
+    {
+        this.username = username;
+    }
+```
+with:
+```java
+    @StrutsParameter
+    public void setUsername(String username) 
+    {
+        this.username = username;
+    }
+```
+
+- [ ] **Step 4: `validation-messages/Login` — annotate both setters**
+
+Evidence: `username` and `password` submitted at depth 0. The class extends 
`ExampleSupport`, which is a bare `ActionSupport` subclass declaring no members 
— nothing is inherited. The file currently has no imports at all.
+
+Add after the `package` declaration:
+```java
+import org.apache.struts2.interceptor.parameter.StrutsParameter;
+```
+
+Replace:
+```java
+    public void setUsername(String username) {
+        this.username = username;
+    }
+```
+with:
+```java
+    @StrutsParameter
+    public void setUsername(String username) {
+        this.username = username;
+    }
+```
+
+Replace:
+```java
+    public void setPassword(String password) {
+        this.password = password;
+    }
+```
+with:
+```java
+    @StrutsParameter
+    public void setPassword(String password) {
+        this.password = password;
+    }
+```
+
+- [ ] **Step 5: `unknown-handler/Login` — annotate both setters**
+
+Evidence: `WEB-INF/tiles/login.jsp` submits `<s:textfield name="email"/>` and 
`<s:password name="password"/>` to the `login-submit` action. This module is 
convention-plugin driven, which is why it is not in `struts.xml`.
+
+Add alongside the existing `org.apache.struts2` imports:
+```java
+import org.apache.struts2.interceptor.parameter.StrutsParameter;
+```
+
+Replace:
+```java
+    public void setEmail(String email) {
+        this.email = email;
+    }
+```
+with:
+```java
+    @StrutsParameter
+    public void setEmail(String email) {
+        this.email = email;
+    }
+```
+
+Replace:
+```java
+    public void setPassword(String password) {
+        this.password = password;
+    }
+```
+with:
+```java
+    @StrutsParameter
+    public void setPassword(String password) {
+        this.password = password;
+    }
+```
+
+- [ ] **Step 6: Verify exactly 8 annotations were added across 5 files**
+
+```bash
+git diff --stat -- '*.java'
+echo "added @StrutsParameter lines: $(git diff -U0 -- '*.java' | grep -c 
'^+.*@StrutsParameter')"
+echo "added import lines:           $(git diff -U0 -- '*.java' | grep -c 
'^+import.*StrutsParameter')"
+```
+Expected: 5 files changed, `added @StrutsParameter lines: 8`, `added import 
lines: 5`
+
+- [ ] **Step 7: Build**
+
+```bash
+./mvnw -B -q clean test
+```
+Expected: BUILD SUCCESS.
+
+- [ ] **Step 8a: Runtime spot-check — `crud` (the module changed here)**
+
+`PersonAction.prepare()` loads the person only when `person.getPersonId()` is 
non-null:
+
+```java
+if (person != null && person.getPersonId() != null) {
+    person = personService.getPerson(person.getPersonId());
+}
+```
+
+So the annotation is observable directly: with it, `person.personId=1` binds 
and the edit form renders the seeded record **Bruce Phillips** 
(`MemoryPersonDao` line 23); without it, `person` stays null, the load is 
skipped, and the form renders empty. The context path is `/crud` 
(`<contextPath>/${project.artifactId}</contextPath>`).
+
+```bash
+(cd crud && ../mvnw -q jetty:run) &
+sleep 45
+curl -s 'http://localhost:8080/crud/editPerson.action?person.personId=1' \
+     -o /tmp/crud_edit.html -w 'HTTP %{http_code}\n'
+echo "Bruce occurrences: $(grep -c 'Bruce' /tmp/crud_edit.html)"
+kill %1
+```
+Expected: `HTTP 200` and `Bruce occurrences:` ≥ 1. **A count of 0 means the 
annotation is not taking effect — stop and investigate before committing.**
+
+- [ ] **Step 8b: Runtime spot-check — `form-tags` (untouched control)**
+
+`form-tags` was already correct (`@StrutsParameter(depth = 1)` on 
`getPersonBean`). This confirms Tasks 1–3 did not regress a module that 
previously worked. `thankyou.jsp` renders `<s:property value="personBean"/>`, 
and `Person.toString()` emits `First Name: <value>`.
+
+```bash
+(cd form-tags && ../mvnw -q jetty:run) &
+sleep 45
+curl -s -X POST 'http://localhost:8080/form-tags/save.action' \
+     -d 'personBean.firstName=Ada&personBean.lastName=Lovelace' \
+     -o /tmp/formtags_post.html -w 'HTTP %{http_code}\n'
+echo "Ada occurrences: $(grep -c 'First Name: Ada' /tmp/formtags_post.html)"
+kill %1
+```
+Expected: `HTTP 200` and `Ada occurrences:` ≥ 1.
+
+- [ ] **Step 9: Commit**
+
+```bash
+git add -A
+git commit -m "Add missing @StrutsParameter annotations to action classes
+
+Eight parameters across crud, shiro-basic, unknown-handler and
+validation-messages were silently dropped: struts.parameters.requireAnnotations
+has defaulted to true since before 7.2.1, and these setters and getters
+were never annotated.
+
+Depth 0 parameters are annotated on the setter and nested parameters on
+the getter, matching what ParametersInterceptor actually inspects.
+
+Co-Authored-By: Claude Opus 5 <[email protected]>"
+```
+
+---
+
+### Task 5: Rename to Apache Struts and fix README claims
+
+45 occurrences of "Struts 2" across 34 files (28 POMs, 6 READMEs). Blind 
substitution produces awkward phrasing, so the bulk replacement is followed by 
a manual read-through.
+
+**Files:**
+- Modify: 28 `pom.xml` files, 6 module `README.md` files, root `README.md`
+
+**Interfaces:**
+- Consumes: nothing
+- Produces: nothing
+
+- [ ] **Step 1: Record the "before" count**
+
+```bash
+grep -rn 'Struts 2' --include='pom.xml' --include='README.md' . | grep -v 
target | wc -l
+```
+Expected: `45`
+
+- [ ] **Step 2: Bulk replace the plain occurrences**
+
+```bash
+grep -rl 'Struts 2' --include='pom.xml' --include='README.md' . | grep -v 
target | \
+  xargs perl -pi -e 's{\bStruts 2\b(?! *\.)}{Apache Struts}g'
+```
+
+- [ ] **Step 3: Read every changed line and fix awkward phrasing**
+
+```bash
+git diff -U0 -- '*.xml' '*.md' | grep '^+' | grep -i 'apache struts'
+```
+
+A dry run of Step 2 produces exactly four lines that read badly. Fix these by 
hand; everything else the substitution produces is correct as-is.
+
+**These are the strings as they exist *after* Step 2 has run — match them, not 
the original "Struts 2" text.**
+
+1. Root `README.md` — `"Getting Started Apache Struts tutorials"` reads 
backwards. Replace:
+```markdown
+This Maven multi-module project contains all the Apache Struts example 
applications that are part of the Getting Started Apache Struts tutorials at 
http://struts.apache.org.
+```
+with:
+```markdown
+This Maven multi-module project contains all the Apache Struts example 
applications that are part of the Apache Struts Getting Started tutorials at 
https://struts.apache.org.
+```
+
+2. `basic-struts/README.md` — `"How To Create A Apache Struts Web 
Application"`. Replace `A Apache Struts` with `An Apache Struts`:
+```markdown
+This is the example project referred to in the Apache Struts documentation, 
[How To Create An Apache Struts Web 
Application](https://struts.apache.org/getting-started/how-to-create-a-struts2-web-application.html)
 tutorial.
+```
+Leave the URL slug (`how-to-create-a-struts2-web-application`) alone — it is a 
real upstream path, not prose.
+
+3. `using-tags/pom.xml` — the description repeats the name. Replace:
+```xml
+    <description>Apache Struts example application for the Using Apache Struts 
Tags getting started tutorial</description>
+```
+with:
+```xml
+    <description>Example application for the Using Struts Tags getting started 
tutorial</description>
+```
+
+4. `using-tags/pom.xml` `<name>` — `Using Apache Struts Tags` is acceptable, 
but prefer the shorter `Using Struts Tags` for consistency with the description 
above.
+
+Then confirm no duplication slipped through:
+```bash
+grep -rn 'Apache Struts Apache Struts\|A Apache' --include='pom.xml' 
--include='README.md' . | grep -v target
+```
+Expected: no output.
+
+- [ ] **Step 4: Update the parent POM name and description**
+
+In `pom.xml`, confirm the result reads:
+```xml
+    <name>Apache Struts Examples</name>
+```
+and that `<description>` reads naturally, e.g.:
+```xml
+    <description>
+        This is the parent pom for the Apache Struts examples that
+        go with the Struts Getting Started series of tutorials.
+    </description>
+```
+
+- [ ] **Step 5: Fix the root README's false claim and broken link**
+
+In `README.md`, replace:
+```markdown
+There is a README file in each module with instructions and the URL to view 
that application.
+```
+with:
+```markdown
+Some modules have a README file with instructions and the URL to view that 
application.
+```
+
+Replace:
+```markdown
+The examples are using the latest Struts version, if you are looking for older 
versions please take a look on the [Releases](releases) page.
+```
+with:
+```markdown
+The examples use the latest Struts version. For older versions, see the 
[Releases](https://github.com/apache/struts-examples/releases) page.
+```
+
+- [ ] **Step 6: Switch remaining `http://` links to `https://` in READMEs**
+
+```bash
+grep -rl 'http://' --include='README.md' . | grep -v target | \
+  xargs perl -pi -e 
's{http://(www\.apache\.org|img\.shields\.io|struts\.apache\.org)}{https://$1}g'
+```
+
+Verify:
+```bash
+grep -rn 'http://' --include='README.md' . | grep -v target | grep -v localhost
+```
+Expected: no output. (`http://localhost` references are correct and must stay.)
+
+- [ ] **Step 7: Verify no "Struts 2" remains and POMs are still valid**
+
+```bash
+echo "Struts 2 left: $(grep -rn 'Struts 2' --include='pom.xml' 
--include='README.md' . | grep -v target | wc -l)"
+find . -name 'pom.xml' -not -path '*/target/*' -print0 | xargs -0 -n1 xmllint 
--noout --nonet
+```
+Expected: `Struts 2 left: 0`, and no `xmllint` output.
+
+- [ ] **Step 8: Build**
+
+```bash
+./mvnw -B -q clean test
+```
+Expected: BUILD SUCCESS.
+
+- [ ] **Step 9: Commit**
+
+```bash
+git add -A
+git commit -m "Rename to Apache Struts and refresh README links
+
+The project has been Struts 7 for some time; the Struts 2 naming in POM
+names and descriptions was stale. Also corrects the root README's claim
+that every module has a README (6 of 47 do), fixes the broken Releases
+link, and moves remaining http:// links to https://.
+
+Co-Authored-By: Claude Opus 5 <[email protected]>"
+```
+
+---
+
+### Task 6: Full verification
+
+**Files:** none modified.
+
+**Interfaces:**
+- Consumes: Tasks 1–5
+- Produces: the verification record for the PR description
+
+- [ ] **Step 1: Clean build on Java 17**
+
+```bash
+JAVA_HOME=$(/usr/libexec/java_home -v 17) ./mvnw -B -V clean test
+```
+Expected: BUILD SUCCESS, 47 modules.
+
+- [ ] **Step 2: Clean build on Java 21**
+
+```bash
+JAVA_HOME=$(/usr/libexec/java_home -v 21) ./mvnw -B -V clean test
+```
+Expected: BUILD SUCCESS, 47 modules. These two mirror the CI matrix in 
`.github/workflows/maven.yml`.
+
+- [ ] **Step 3: Confirm 7.3.0 introduces no new CVE**
+
+```bash
+./mvnw -B -q -P dependency-check verify
+```
+Expected: no failure. The profile is configured with `failBuildOnCVSS=7`, so a 
CVSS ≥ 7 finding fails the build. If it fails, record the CVE and raise it — do 
not suppress it without discussion.
+
+- [ ] **Step 4: Confirm the commit series is exactly as planned**
+
+```bash
+git log --oneline origin/main..HEAD
+```
+Expected: 6 commits — the spec doc, then Tasks 1–5.
+
+- [ ] **Step 5: Confirm nothing unintended was touched**
+
+```bash
+git diff --stat origin/main..HEAD | tail -1
+```
+Expected: roughly 140–150 files. The per-task counts are the authoritative 
check (1 parent POM, 79 DOCTYPE files, 29 `web.xml`, 5 Java, 34 naming files, 
with some POM overlap between the Maven-schema and naming sweeps). Review the 
file list for anything outside those four sweeps — in particular, no file under 
any `target/` directory and no `.java` file beyond the five in Task 4.
+
+---
+
+## Notes for the reviewer
+
+- **Commit 2 of the spec became two commits** (Tasks 2 and 3). The DOCTYPE 
sweep is a regex over 79 files; the `web.xml` migration rewrites a root element 
in 29 files via a script. Different mechanisms and different risk, so they are 
reviewed separately.
+- **The annotation audit found 8 fixes, not the 8–12 the spec estimated** — 
within the predicted range. Modules deliberately left alone, with reasons:
+  - `rest-angular` — `OrderController` is `ModelDriven`, and 
`ParameterAuthorizer` documents the model as exempt from annotation 
requirements. `IndexController.useMinifiedResources` is only read by a JSP 
`<s:if>`, never submitted.
+  - `annotations` — `HelloAction.message` is assigned inside `execute()` and 
only rendered; it is not an input.
+  - `text-provider` — `SystemAction.setTextProvider` is `@Inject`-driven, not 
a request parameter.
+  - `file-upload` and `sitemesh3` — `UploadAction implements 
UploadedFilesAware` and receives files via 
`withUploadedFiles(List<UploadedFile>)`. There is no `setUpload`; `<s:file>` is 
consumed by the file-upload interceptor, not `ParametersInterceptor`.
+  - `struts-parameter` — `users[%{#status.index}].id` evaluates to 
`users[0].id`, depth 2. The existing `@StrutsParameter(depth = 2)` is already 
correct.
+- **No `unverified` rows.** Every action's parameters were traceable to a JSP 
form, a validation descriptor, or an assignment in `execute()`.

Reply via email to