Skip to content

WW-5640 Add WebJars support to Struts core#1765

Open
lukaszlenart wants to merge 13 commits into
mainfrom
WW-5640-webjars-support
Open

WW-5640 Add WebJars support to Struts core#1765
lukaszlenart wants to merge 13 commits into
mainfrom
WW-5640-webjars-support

Conversation

@lukaszlenart

Copy link
Copy Markdown
Member

Fixes WW-5640

What

Adds first-class WebJars support to Struts core: client-side libraries packaged as WebJars (org.webjars:*, shipped under META-INF/resources/webjars/<name>/<version>/…) can be referenced by a version-less logical path and are resolved + served through Struts' existing static-content pipeline.

Example: a template references bootstrap/css/bootstrap.min.css → Struts serves META-INF/resources/webjars/bootstrap/5.3.8/css/bootstrap.min.css at <ctx>/static/webjars/bootstrap/5.3.8/css/bootstrap.min.css.

Why

Plugins/apps currently vendor client-side assets on the classpath and re-commit them each release (the struts2-bootstrap plugin commits ~2000 files by hand). WebJars replace this with a dependency bump (auto-updatable via Renovate/Dependabot). First intended consumer: the struts2-bootstrap plugin (separate work).

Changes

  • Dependency: org.webjars:webjars-locator-lite:1.1.3 (MIT; single Apache-2.0 transitive dep jspecify; no Jackson/classpath-scanner — the variant Spring Framework 6.2 adopted).
  • WebJarUrlProvider (container bean, the public seam for plugins) + DefaultWebJarUrlProvider wrapping a singleton WebJarVersionLocator.
  • Serving: a /webjars/** branch in DefaultStaticContentLoader reusing existing caching; content-type map extended for fonts/svg/source-maps/json/ico.
  • <s:webjar> tag + <@s.webjar> macro emitting the resolved URL string (composes with <s:script>/<s:link>; supports var).
  • Config: struts.webjars.enabled (default true), struts.webjars.allowlist (comma-separated; empty = all).

Security

Resolution is hard-constrained to the META-INF/resources/webjars/ root: per-segment ../. rejection and backslash rejection before the locator is called, a containment re-check on the resolved path, locator-backed resolution only (unknown webjar → null), plus the optional allowlist. Unresolved / disabled / traversal / allowlist-blocked all fail closed (404 when serving, empty output when building URLs). This is hardening for the new endpoint, not a fix to existing shipped behavior.

Testing

  • Unit: resolution (known → versioned path/URL; unknown → empty), traversal rejection (both resolveResourcePath and resolveUrl), allowlist, disabled; content-type mappings.
  • Serving: 200 + content-type for a known asset, 404 for unknown, 404 when disabled (servlet-mocked).
  • Tag/macro: renders the resolved URL; var storage; empty on unresolved.
  • Full core suite: 2985 tests, 0 failures. No regression to existing static-content serving.

Backward compatible: enabled=true only activates the new <staticContentPath>/webjars/** namespace; existing /static/** serving is untouched.

lukaszlenart and others added 13 commits July 1, 2026 10:57
Adds first-class WebJars support so client-side libraries can be
referenced by a version-less logical path and served through the
existing static-content pipeline. Grounded against 7.2.x source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
core uses JUnit 4 + AssertJ + Mockito, not JUnit 5 Jupiter (no
Jupiter engine on the classpath). Test tasks translate accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Annotation-processor-generated tag reference (attributes + description),
tracked like every other tag's docs under core/src/site/resources/tags/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…st, javadoc)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- getContentType: replace long if/else chain with a static extension->
  MIME map (S3776 cognitive complexity)
- DefaultWebJarUrlProvider.split: return Optional<String[]> instead of a
  null sentinel (S1168; Optional fits the reject semantics, empty-array
  would not)
- serving tests: rename local 'loader' -> 'webJarLoader' to stop hiding
  the ContentTypeProbe field (S1117)
- WebJarTest: use assertThat(writer).hasToString(...) (S5838)

S110 (WebJarTag inheritance depth) is inherent to the Struts tag base
class hierarchy shared by every tag; left as-is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 1, 2026

Copy link
Copy Markdown

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