From 789dbf3cd253d528af230272805b3ce392005fb1 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Mon, 6 Jul 2026 15:03:05 +0200 Subject: [PATCH] WW-5640 Add WebJars support to Struts core (#1765) * WW-5640 docs: design for WebJars support in Struts core 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 * WW-5640 docs: implementation plan for WebJars support Co-Authored-By: Claude Opus 4.8 * WW-5640 build: add webjars-locator-lite dependency Co-Authored-By: Claude Opus 4.8 * WW-5640 feat: add webjars config constants and defaults Co-Authored-By: Claude Opus 4.8 * WW-5640 docs: correct plan test framework to JUnit 4 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 * WW-5640 feat: add WebJarUrlProvider resolution seam Co-Authored-By: Claude Opus 4.8 * WW-5640 feat: register WebJarUrlProvider bean Co-Authored-By: Claude Opus 4.8 * WW-5640 feat: extend static content-type map for webjar assets Co-Authored-By: Claude Opus 4.8 * WW-5640 feat: serve webjar assets via static content loader Co-Authored-By: Claude Opus 4.8 * WW-5640 feat: add tag and <@s.webjar> macro Co-Authored-By: Claude Opus 4.8 * WW-5640 docs: add generated tag reference for 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 * WW-5640 fix: address final review (log level, resolveUrl traversal test, javadoc) Co-Authored-By: Claude Opus 4.8 * WW-5640 refactor: address SonarCloud code smells - getContentType: replace long if/else chain with a static extension-> MIME map (S3776 cognitive complexity) - DefaultWebJarUrlProvider.split: return Optional 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 --------- Co-authored-by: Claude Opus 4.8 --- core/pom.xml | 11 + .../org/apache/struts2/StrutsConstants.java | 15 + .../org/apache/struts2/components/WebJar.java | 95 ++ .../config/StrutsBeanSelectionProvider.java | 2 + .../DefaultStaticContentLoader.java | 93 +- .../views/freemarker/tags/StrutsModels.java | 8 + .../views/freemarker/tags/WebJarModel.java | 37 + .../apache/struts2/views/jsp/WebJarTag.java | 48 + .../webjars/DefaultWebJarUrlProvider.java | 151 ++ .../struts2/webjars/WebJarUrlProvider.java | 51 + .../org/apache/struts2/default.properties | 6 + core/src/main/resources/struts-beans.xml | 2 + .../resources/tags/webjar-attributes.html | 40 + .../resources/tags/webjar-description.html | 1 + .../apache/struts2/components/WebJarTest.java | 81 + .../DefaultStaticContentLoaderWebJarTest.java | 119 ++ .../WebJarTestServletOutputStream.java | 50 + .../webjars/DefaultWebJarUrlProviderTest.java | 110 ++ .../2026-07-01-WW-5640-webjars-support.md | 1396 +++++++++++++++++ .../2026-07-01-webjars-support-design.md | 146 ++ parent/pom.xml | 12 + pom.xml | 2 + 22 files changed, 2458 insertions(+), 18 deletions(-) create mode 100644 core/src/main/java/org/apache/struts2/components/WebJar.java create mode 100644 core/src/main/java/org/apache/struts2/views/freemarker/tags/WebJarModel.java create mode 100644 core/src/main/java/org/apache/struts2/views/jsp/WebJarTag.java create mode 100644 core/src/main/java/org/apache/struts2/webjars/DefaultWebJarUrlProvider.java create mode 100644 core/src/main/java/org/apache/struts2/webjars/WebJarUrlProvider.java create mode 100644 core/src/site/resources/tags/webjar-attributes.html create mode 100644 core/src/site/resources/tags/webjar-description.html create mode 100644 core/src/test/java/org/apache/struts2/components/WebJarTest.java create mode 100644 core/src/test/java/org/apache/struts2/dispatcher/DefaultStaticContentLoaderWebJarTest.java create mode 100644 core/src/test/java/org/apache/struts2/dispatcher/WebJarTestServletOutputStream.java create mode 100644 core/src/test/java/org/apache/struts2/webjars/DefaultWebJarUrlProviderTest.java create mode 100644 docs/superpowers/plans/2026-07-01-WW-5640-webjars-support.md create mode 100644 docs/superpowers/specs/2026-07-01-webjars-support-design.md diff --git a/core/pom.xml b/core/pom.xml index 784a9053d..b5303545f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -153,6 +153,17 @@ caffeine + + org.webjars + webjars-locator-lite + + + + org.webjars + jquery + test + + jakarta.servlet jakarta.servlet-api diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java index 0e9ca4fc3..e76fac4e5 100644 --- a/core/src/main/java/org/apache/struts2/StrutsConstants.java +++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java @@ -195,6 +195,16 @@ public final class StrutsConstants { */ public static final String STRUTS_UI_STATIC_CONTENT_PATH = "struts.ui.staticContentPath"; + /** + * Whether WebJars support is enabled (serving and URL building) + */ + public static final String STRUTS_WEBJARS_ENABLED = "struts.webjars.enabled"; + + /** + * Optional comma-separated allowlist of WebJar names permitted to be served (empty = all) + */ + public static final String STRUTS_WEBJARS_ALLOWLIST = "struts.webjars.allowlist"; + /** * A global flag to enable/disable html body escaping in tags, can be overwritten per tag */ @@ -434,6 +444,11 @@ public final class StrutsConstants { */ public static final String STRUTS_STATIC_CONTENT_LOADER = "struts.staticContentLoader"; + /** + * The {@link org.apache.struts2.webjars.WebJarUrlProvider} implementation class + */ + public static final String STRUTS_WEBJARS_URL_PROVIDER = "struts.webjars.urlProvider"; + /** * The {@link org.apache.struts2.UnknownHandlerManager} implementation class */ diff --git a/core/src/main/java/org/apache/struts2/components/WebJar.java b/core/src/main/java/org/apache/struts2/components/WebJar.java new file mode 100644 index 000000000..c18538a42 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/components/WebJar.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.components; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.inject.Inject; +import org.apache.struts2.util.ValueStack; +import org.apache.struts2.views.annotations.StrutsTag; +import org.apache.struts2.views.annotations.StrutsTagAttribute; +import org.apache.struts2.webjars.WebJarUrlProvider; + +import java.io.IOException; +import java.io.Writer; +import java.util.Optional; + +/** + *

Resolves a version-less WebJar resource path to a servable URL and writes it to the output + * (or stores it in a variable when {@code var} is set). Compose it with {@code }/{@code } + * or a raw {@code }/{@code