Skip to content

TL3: expose self-closing model-variable processors' values to sibling markup#13

Open
devin-ai-integration[bot] wants to merge 2 commits into
migration/java-21-migrationfrom
devin/p2-core-tl3-localvar-fix
Open

TL3: expose self-closing model-variable processors' values to sibling markup#13
devin-ai-integration[bot] wants to merge 2 commits into
migration/java-21-migrationfrom
devin/p2-core-tl3-localvar-fix

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 17, 2026

Copy link
Copy Markdown

Summary

Follow-up to the Java 21 / Thymeleaf 3 migration. The merged base-class fix in AbstractModelVariableModifierProcessor switched removeElement()removeTags() so a contributed setLocalVariable stays visible to the host element's body. That correctly handles processors used as wrappers, but two processors are documented as self-closing elements whose value is read by a following sibling, where a body-scoped local variable is never visible:

<!-- NamedOrderProcessor -->
<blc:named_order orderVar="wishlist" orderName="wishlist" />
<span th:text="${wishlist.customer.name}" />   <!-- ${wishlist} was null -->

<!-- GoogleAnalyticsProcessor (@Deprecated) -->
<blc:googleanalytics th:attr="orderNumber=${...}" />
<script th:utext="${analytics}" />             <!-- ${analytics} was null -->

In TL3 a local variable is scoped to the element body, so for a self-closing element it can't reach a sibling. The page-wide equivalent of TL2's old "add to model" is a request attribute: Thymeleaf's WebEngineContext.getVariable(name) resolves live from webExchange.getAttributeValue(name), so a request attribute is readable as ${name} anywhere later in the page.

Change

New base-class helper (pure addition; does not change addToModel/removeTags behavior used by the wrapper-style processors):

// AbstractModelVariableModifierProcessor
protected void addToRequest(ITemplateContext context, String key, Object value) {
    if (context instanceof IWebContext) {
        ((IWebContext) context).getExchange().setAttributeValue(key, value);
    }
}

NamedOrderProcessor and GoogleAnalyticsProcessor now call addToRequest(context, ...) instead of addToModel(structureHandler, ...), restoring their documented sibling-visible behavior. The other subclasses (CategoriesProcessor, ProductOptionsProcessor, RatingsProcessor, RelatedProductProcessor) are unchanged and keep the merged body-scoped approach.

Verification

  • export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 && mvn -q -pl common,core/broadleaf-framework-web -am clean install -Pblc-development -DskipTests → BUILD SUCCESS.
  • common unit tests pass. broadleaf-framework-web introduces no new test failures; the only failing tests are the 3 pre-existing *.taglib.* tests (CategoryBreadcrumbTagTest, CategoryTagTest, GetProductsByCategoryIdTagTest) that fail under JPMS because EasyMock classextension/cglib can't setAccessible on ClassLoader.defineClass (InaccessibleObjectException) — identical to the already-merged broadleaf-profile BatchRetrieveDaoTest failure and unrelated to this change.

No tests, license-header, jacoco, or animal-sniffer gates were weakened.

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/b9de3eef877542ac889ca59866907d66
Requested by: @mbatchelor81


Open in Devin Review

…ling markup

The TL3 base-class fix (removeTags + setLocalVariable) only makes contributed
values visible to the host element's body. NamedOrderProcessor and the deprecated
GoogleAnalyticsProcessor are documented as self-closing elements whose value is
read by a following sibling (<span>/<script>), so a body-scoped local variable is
never visible. Add an addToRequest() helper that sets a request attribute (read
live by Thymeleaf's WebEngineContext as ${var}) and use it in those two
processors to restore page-wide visibility.
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Open in Devin Review

Comment on lines 76 to 77
structureHandler.setLocalVariable(key, value);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Other self-closing processors may also need the addToRequest pattern

Several other subclasses of AbstractModelVariableModifierProcessor still use addToModel (local variable scoping) and may also be used as self-closing elements whose values are consumed by sibling markup. For example:

  • ConfigVariableProcessor.java:60 sets resultVar via addToModel
  • BreadcrumbProcessor.java:66 sets resultVar via addToModel
  • DataDrivenEnumerationProcessor.java:91 sets enumValues via addToModel
  • RatingsProcessor.java:66,75 sets rating data via addToModel
  • RelatedProductProcessor.java:85-88 sets product data via addToModel

If any of these are used as self-closing elements (e.g., <blc:config ... />), the local variable will have no body to scope into and the value will be invisible to siblings. This is the same problem this PR aims to fix for NamedOrderProcessor and GoogleAnalyticsProcessor. Each processor's usage pattern in templates would need to be audited.

(Refers to lines 74-77)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid follow-up. This PR is deliberately scoped to the two processors whose javadoc explicitly documents self-closing + sibling usage:

  • NamedOrderProcessor: <blc:named_order orderVar="wishlist" .../> then <span th:text="${wishlist...}"/>
  • GoogleAnalyticsProcessor: <blc:googleanalytics/> then <script th:utext="${analytics}"/>

The base-class addToRequest() helper is now available for any others confirmed to need it. The remaining candidates split two ways:

  • ConfigVariableProcessor, BreadcrumbProcessor, DataDrivenEnumerationProcessor live in the common module, outside this task's broadleaf-framework-web scope.
  • RatingsProcessor, RelatedProductProcessor are in broadleaf-framework-web, but their javadocs don't document a self-closing+sibling pattern, and the prior merged fix intentionally kept them on the body-scoped removeTags() approach.

Confirming which are actually used as self-closing elements requires the demo-site templates, which aren't in this repo, so I'd recommend a dedicated template-usage audit as the follow-up rather than blindly switching them. Note addToRequest is strictly broader than a body-local (a request attribute is visible both inside the body and to siblings), but it persists for the whole request, so local-variable scoping remains preferable wherever a body actually consumes the value.

Use the codebase's established BroadleafRequestContext.getRequest() pattern
(as in ContentProcessor/ResourceBundleProcessor after TL3.1 removed
IWebContext#getHttpServletRequest) to set the request attribute, instead of
casting the Thymeleaf context. WebEngineContext resolves ${var} from request
attributes, so siblings see the value.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant