Resolve URL path for versioned webjar directories

Prior to this commit, the `resolveUrlPath` implementation for the
`LiteWebJarsResourceResolver` would always delegate to the resource
chain once the versioned webjar folder has been resolved.

While this aligns with the `ResourceResolver` contract and the fact that
the resource chain does not resolve directories, here the WebJar locator
does support such use cases and we shouldn't get in the way here.

This commit falls back to the resolved versioned WebJar path if no
resource could be resolved and the path ends with "/".

Fixes gh-36726
This commit is contained in:
Brian Clozel
2026-04-30 15:17:45 +02:00
committed by rstoyanchev
parent 08c5280843
commit 3a91d90c28
7 changed files with 52 additions and 17 deletions
@@ -138,7 +138,6 @@ dependencies {
api("org.seleniumhq.selenium:selenium-java:4.41.0")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.testng:testng:7.12.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-lite:1.1.0")
api("org.xmlunit:xmlunit-assertj:2.10.4")
api("org.xmlunit:xmlunit-matchers:2.10.4")
+1 -1
View File
@@ -61,7 +61,7 @@ dependencies {
testRuntimeOnly("org.glassfish:jakarta.el")
testRuntimeOnly("org.jruby:jruby")
testRuntimeOnly("org.python:jython-standalone")
testRuntimeOnly("org.webjars:underscorejs")
testRuntimeOnly("org.webjars:momentjs:2.29.4")
}
test {
@@ -90,7 +90,8 @@ public class LiteWebJarsResourceResolver extends AbstractResourceResolver {
.switchIfEmpty(Mono.defer(() -> {
String webJarResourcePath = findWebJarResourcePath(resourceUrlPath);
if (webJarResourcePath != null) {
return chain.resolveUrlPath(webJarResourcePath, locations);
Mono<String> fallback = (webJarResourcePath.endsWith("/")) ? Mono.just(webJarResourcePath) : Mono.empty();
return chain.resolveUrlPath(webJarResourcePath, locations).switchIfEmpty(fallback);
}
else {
return Mono.empty();
@@ -29,6 +29,8 @@ import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRe
import org.springframework.web.testfixture.server.MockServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -80,8 +82,8 @@ class LiteWebJarsResourceResolverTests {
@Test
void resolveUrlWebJarResource() {
String file = "underscorejs/underscore.js";
String expected = "underscorejs/1.8.3/underscore.js";
String file = "momentjs/momentjs.js";
String expected = "momentjs/2.29.4/momentjs.js";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.empty());
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(Mono.just(expected));
@@ -92,10 +94,24 @@ class LiteWebJarsResourceResolverTests {
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
}
@Test
void resolveUrlWebJarDirectory() {
String folder = "momentjs/locale/";
String expected = "momentjs/2.29.4/locale/";
given(this.chain.resolveUrlPath(folder, this.locations)).willReturn(Mono.empty());
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(Mono.empty());
String actual = this.resolver.resolveUrlPath(folder, this.locations, this.chain).block(TIMEOUT);
assertThat(actual).isEqualTo(expected);
verify(this.chain, times(1)).resolveUrlPath(folder, this.locations);
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
}
@Test
void resolveUrlWebJarResourceNotFound() {
String file = "something/something.js";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.empty());
String file = "momentjs/locale/unknown.js";
given(this.chain.resolveUrlPath(anyString(), eq(this.locations))).willReturn(Mono.empty());
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain).block(TIMEOUT);
@@ -134,11 +150,11 @@ class LiteWebJarsResourceResolverTests {
@Test
void resolveResourceWebJar() {
String file = "underscorejs/underscore.js";
String file = "momentjs/momentjs.js";
given(this.chain.resolveResource(this.exchange, file, this.locations)).willReturn(Mono.empty());
Resource expected = mock();
String expectedPath = "underscorejs/1.8.3/underscore.js";
String expectedPath = "momentjs/2.29.4/momentjs.js";
given(this.chain.resolveResource(this.exchange, expectedPath, this.locations))
.willReturn(Mono.just(expected));
+1 -1
View File
@@ -80,5 +80,5 @@ dependencies {
testRuntimeOnly("org.glassfish:jakarta.el")
testRuntimeOnly("org.jruby:jruby")
testRuntimeOnly("org.python:jython-standalone")
testRuntimeOnly("org.webjars:underscorejs")
testRuntimeOnly("org.webjars:momentjs:2.29.4")
}
@@ -88,7 +88,10 @@ public class LiteWebJarsResourceResolver extends AbstractResourceResolver {
if (path == null) {
String webJarResourcePath = findWebJarResourcePath(resourceUrlPath);
if (webJarResourcePath != null) {
return chain.resolveUrlPath(webJarResourcePath, locations);
path = chain.resolveUrlPath(webJarResourcePath, locations);
if (path == null && webJarResourcePath.endsWith("/")) {
path = webJarResourcePath;
}
}
}
return path;
@@ -26,6 +26,8 @@ import org.springframework.core.io.Resource;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -74,8 +76,8 @@ class LiteWebJarsResourceResolverTests {
@Test
void resolveUrlWebJarResource() {
String file = "underscorejs/underscore.js";
String expected = "underscorejs/1.8.3/underscore.js";
String file = "momentjs/momentjs.js";
String expected = "momentjs/2.29.4/momentjs.js";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(expected);
@@ -86,10 +88,24 @@ class LiteWebJarsResourceResolverTests {
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
}
@Test
void resolveUrlWebJarDirectory() {
String folder = "momentjs/locale/";
String expected = "momentjs/2.29.4/locale/";
given(this.chain.resolveUrlPath(folder, this.locations)).willReturn(null);
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(null);
String actual = this.resolver.resolveUrlPath(folder, this.locations, this.chain);
assertThat(actual).isEqualTo(expected);
verify(this.chain, times(1)).resolveUrlPath(folder, this.locations);
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
}
@Test
void resolveUrlWebJarResourceNotFound() {
String file = "something/something.js";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
String file = "momentjs/locale/unknown.js";
given(this.chain.resolveUrlPath(anyString(), eq(this.locations))).willReturn(null);
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain);
@@ -125,8 +141,8 @@ class LiteWebJarsResourceResolverTests {
@Test
void resolveResourceWebJar() {
Resource expected = mock();
String file = "underscorejs/underscore.js";
String expectedPath = "underscorejs/1.8.3/underscore.js";
String file = "momentjs/momentjs.js";
String expectedPath = "momentjs/2.29.4/momentjs.js";
given(this.chain.resolveResource(this.request, expectedPath, this.locations)).willReturn(expected);
Resource actual = this.resolver.resolveResource(this.request, file, this.locations, this.chain);