Skip to content

Commit 755c836

Browse files
fix(spring): handle Unicode classpath resource paths (#24220)(CP:25.0) (#24250)
Cherry pick of #24220 to 25.0 Co-authored-by: François Martin <f.martin@fastmail.com>
1 parent addcba0 commit 755c836

2 files changed

Lines changed: 72 additions & 13 deletions

File tree

vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.io.IOException;
2727
import java.io.Serializable;
2828
import java.lang.annotation.Annotation;
29+
import java.net.URISyntaxException;
2930
import java.util.ArrayList;
3031
import java.util.Arrays;
3132
import java.util.Collection;
@@ -975,8 +976,7 @@ private static void collectHandleTypes(Class<?>[] handleTypes,
975976
* For npm we scan all packages. For performance reasons and due to problems
976977
* with atmosphere we skip known packaged from our resources collection.
977978
*/
978-
private static class CustomResourceLoader
979-
extends FilterableResourceResolver {
979+
static class CustomResourceLoader extends FilterableResourceResolver {
980980

981981
private final PrefixTree scanNever = new PrefixTree(DEFAULT_SCAN_NEVER);
982982

@@ -1047,15 +1047,8 @@ private Resource[] collectResources(String locationPattern)
10471047

10481048
for (Resource resource : super.getResources(locationPattern)) {
10491049
String originalPath = resource.getURL().getPath();
1050-
String path;
1051-
if (originalPath.startsWith("file:///resources!")) {
1052-
// It's a resource from a native build, remove the
1053-
// prefix from URL path
1054-
path = originalPath
1055-
.substring("file:///resources!".length());
1056-
} else {
1057-
path = originalPath;
1058-
}
1050+
final String path = getComparableResourcePath(resource,
1051+
originalPath);
10591052

10601053
if (isDevModeCacheUsed() && skipped.contains(originalPath)) {
10611054
continue;
@@ -1065,8 +1058,8 @@ private Resource[] collectResources(String locationPattern)
10651058
resources.add(resource);
10661059
// Restore root paths to ensure new resources are correctly
10671060
// validate and cached after a reload
1068-
if (originalPath.endsWith("/")) {
1069-
rootPaths.add(originalPath);
1061+
if (path.endsWith("/")) {
1062+
rootPaths.add(path);
10701063
}
10711064
} else {
10721065
if (path.endsWith(".jar!/")) {
@@ -1123,6 +1116,26 @@ private Resource[] collectResources(String locationPattern)
11231116
return resources.toArray(new Resource[0]);
11241117
}
11251118

1119+
private String getComparableResourcePath(Resource resource,
1120+
String fallbackPath) throws IOException {
1121+
try {
1122+
String path = resource.getURL().toURI().getPath();
1123+
return stripNativeImageResourcePrefix(
1124+
path == null ? fallbackPath : path);
1125+
} catch (IllegalArgumentException | URISyntaxException exception) {
1126+
return stripNativeImageResourcePrefix(fallbackPath);
1127+
}
1128+
}
1129+
1130+
private String stripNativeImageResourcePrefix(String path) {
1131+
if (path.startsWith("file:///resources!")) {
1132+
// It's a resource from a native build, remove the prefix from
1133+
// URL path.
1134+
return path.substring("file:///resources!".length());
1135+
}
1136+
return path;
1137+
}
1138+
11261139
private boolean isDevModeCacheUsed() {
11271140
return !filterOnlyByPackageProperties && devModeCachingEnabled;
11281141
}

vaadin-spring/src/test/java/com/vaadin/flow/spring/VaadinServletContextInitializerTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
import jakarta.servlet.ServletContextEvent;
2020
import jakarta.servlet.ServletContextListener;
2121

22+
import java.io.IOException;
23+
import java.net.URL;
24+
import java.net.URLClassLoader;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.util.Arrays;
2228
import java.util.Collections;
2329
import java.util.HashMap;
2430
import java.util.Map;
@@ -29,14 +35,18 @@
2935
import org.junit.After;
3036
import org.junit.Assert;
3137
import org.junit.Before;
38+
import org.junit.Rule;
3239
import org.junit.Test;
40+
import org.junit.rules.TemporaryFolder;
3341
import org.mockito.Mock;
3442
import org.mockito.MockedStatic;
3543
import org.mockito.Mockito;
3644
import org.mockito.MockitoAnnotations;
3745
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
3846
import org.springframework.context.ApplicationContext;
3947
import org.springframework.core.env.Environment;
48+
import org.springframework.core.io.DefaultResourceLoader;
49+
import org.springframework.core.io.Resource;
4050

4151
import com.vaadin.flow.component.Component;
4252
import com.vaadin.flow.di.Lookup;
@@ -54,8 +64,13 @@
5464
import com.vaadin.flow.server.startup.ApplicationRouteRegistry;
5565
import com.vaadin.flow.server.startup.ServletDeployer;
5666

67+
import static org.assertj.core.api.Assertions.assertThat;
68+
5769
public class VaadinServletContextInitializerTest {
5870

71+
@Rule
72+
public TemporaryFolder temporaryFolder = new TemporaryFolder();
73+
5974
@Mock
6075
private ApplicationContext applicationContext;
6176

@@ -216,6 +231,37 @@ public int setErrorParameter(BeforeEnterEvent event,
216231
Assert.assertEquals(TestErrorView.class, navigationTarget);
217232
}
218233

234+
@Test
235+
public void customResourceLoader_classpathRootContainsUnicodeCombiningCharacter_resourcesAreMatched()
236+
throws Exception {
237+
Path tempDir = temporaryFolder.getRoot().toPath();
238+
Path classesRoot = tempDir.resolve("François")
239+
.resolve("vaadin-c\u0327-repro").resolve("target/classes");
240+
Path applicationClass = classesRoot
241+
.resolve("com/example/application/Application.class");
242+
Files.createDirectories(applicationClass.getParent());
243+
Files.write(applicationClass, new byte[] { 0 });
244+
245+
try (URLClassLoader classLoader = new URLClassLoader(
246+
new URL[] { classesRoot.toUri().toURL() }, null)) {
247+
Resource[] resources = new VaadinServletContextInitializer.CustomResourceLoader(
248+
new DefaultResourceLoader(classLoader)).getResources(
249+
"classpath*:com/example/application/**/*.class");
250+
251+
assertThat(resources).as(Arrays.toString(resources)).anySatisfy(
252+
resource -> assertThat(getResourcePath(resource)).endsWith(
253+
"/com/example/application/Application.class"));
254+
}
255+
}
256+
257+
private static String getResourcePath(Resource resource) {
258+
try {
259+
return resource.getURI().getPath();
260+
} catch (IOException exception) {
261+
throw new IllegalStateException(exception);
262+
}
263+
}
264+
219265
private Runnable initRouteNotFoundMocksAndGetContextInitializedMockCall(
220266
VaadinServletContextInitializer vaadinServletContextInitializer)
221267
throws Exception {

0 commit comments

Comments
 (0)