Skip to content

Commit 02f8ee3

Browse files
authored
fix: use ResourceProvider to search for frontend resources (#22627)
Using resource provider to search for frontend resources prevents potential issues with environment that use multiple classloaders, like Quarkus. Every environment can provide its own resource provider implementation that uses the best approach for loading resources from classpath. Fixes #22617
1 parent 4eb80df commit 02f8ee3

File tree

2 files changed

+50
-35
lines changed

2 files changed

+50
-35
lines changed

vaadin-dev-server/src/main/java/com/vaadin/base/devserver/startup/DevModeInitializer.java

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.lang.annotation.Annotation;
2626
import java.lang.reflect.InvocationTargetException;
2727
import java.lang.reflect.Method;
28+
import java.net.URI;
2829
import java.net.URL;
2930
import java.net.URLDecoder;
3031
import java.nio.charset.StandardCharsets;
@@ -33,7 +34,6 @@
3334
import java.nio.file.Paths;
3435
import java.util.Arrays;
3536
import java.util.Collections;
36-
import java.util.Enumeration;
3737
import java.util.HashSet;
3838
import java.util.List;
3939
import java.util.Locale;
@@ -61,6 +61,7 @@
6161
import com.vaadin.base.devserver.viteproxy.ViteWebsocketEndpoint;
6262
import com.vaadin.experimental.FeatureFlags;
6363
import com.vaadin.flow.di.Lookup;
64+
import com.vaadin.flow.di.ResourceProvider;
6465
import com.vaadin.flow.internal.DevModeHandler;
6566
import com.vaadin.flow.server.Constants;
6667
import com.vaadin.flow.server.ExecutionFailedException;
@@ -244,8 +245,10 @@ public static DevModeHandler initDevModeHandler(Set<Class<?>> classes,
244245
options.createMissingPackageJson(true);
245246
}
246247

247-
Set<File> frontendLocations = getFrontendLocationsFromClassloader(
248-
DevModeStartupListener.class.getClassLoader());
248+
ResourceProvider resourceProvider = lookup
249+
.lookup(ResourceProvider.class);
250+
Set<File> frontendLocations = getFrontendLocationsFromResourceProvider(
251+
resourceProvider);
249252

250253
boolean useByteCodeScanner = config.getBooleanProperty(
251254
SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE,
@@ -358,15 +361,17 @@ private static Logger log() {
358361
* META-INF/resources/frontend and META-INF/resources/themes folder. We
359362
* don't use URLClassLoader because will fail in Java 9+
360363
*/
361-
static Set<File> getFrontendLocationsFromClassloader(
362-
ClassLoader classLoader) throws VaadinInitializerException {
364+
static Set<File> getFrontendLocationsFromResourceProvider(
365+
ResourceProvider resourceProvider)
366+
throws VaadinInitializerException {
363367
Set<File> frontendFiles = new HashSet<>();
364-
frontendFiles.addAll(getFrontendLocationsFromClassloader(classLoader,
365-
Constants.RESOURCES_FRONTEND_DEFAULT));
366-
frontendFiles.addAll(getFrontendLocationsFromClassloader(classLoader,
367-
Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT));
368-
frontendFiles.addAll(getFrontendLocationsFromClassloader(classLoader,
369-
Constants.RESOURCES_THEME_JAR_DEFAULT));
368+
frontendFiles.addAll(getFrontendLocationsFromResourceProvider(
369+
resourceProvider, Constants.RESOURCES_FRONTEND_DEFAULT));
370+
frontendFiles.addAll(
371+
getFrontendLocationsFromResourceProvider(resourceProvider,
372+
Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT));
373+
frontendFiles.addAll(getFrontendLocationsFromResourceProvider(
374+
resourceProvider, Constants.RESOURCES_THEME_JAR_DEFAULT));
370375
return frontendFiles;
371376
}
372377

@@ -381,22 +386,22 @@ private static void runNodeTasks(NodeTasks tasks) {
381386
}
382387
}
383388

384-
private static Set<File> getFrontendLocationsFromClassloader(
385-
ClassLoader classLoader, String resourcesFolder)
389+
private static Set<File> getFrontendLocationsFromResourceProvider(
390+
ResourceProvider resourceProvider, String resourcesFolder)
386391
throws VaadinInitializerException {
387392
Set<File> frontendFiles = new HashSet<>();
388393
try {
389-
Enumeration<URL> en = classLoader.getResources(resourcesFolder);
394+
List<URL> en = resourceProvider
395+
.getApplicationResources(resourcesFolder);
390396
if (en == null) {
391397
return frontendFiles;
392398
}
393399
Set<String> vfsJars = new HashSet<>();
394-
while (en.hasMoreElements()) {
395-
URL url = en.nextElement();
400+
for (URL url : en) {
396401
String urlString = url.toString();
397402

398403
String path = URLDecoder.decode(url.getPath(),
399-
StandardCharsets.UTF_8.name());
404+
StandardCharsets.UTF_8);
400405
Matcher jarMatcher = JAR_FILE_REGEX.matcher(path);
401406
Matcher zipProtocolJarMatcher = ZIP_PROTOCOL_JAR_FILE_REGEX
402407
.matcher(path);
@@ -410,12 +415,14 @@ private static Set<File> getFrontendLocationsFromClassloader(
410415
if (jarVfsMatcher.find()) {
411416
String vfsJar = jarVfsMatcher.group(1);
412417
if (vfsJars.add(vfsJar)) { // NOSONAR
413-
frontendFiles.add(
414-
getPhysicalFileOfJBossVfsJar(new URL(vfsJar)));
418+
frontendFiles.add(getPhysicalFileOfJBossVfsJar(
419+
URI.create(vfsJar).toURL()));
415420
}
416421
} else if (dirVfsMatcher.find()) {
417-
URL vfsDirUrl = new URL(urlString.substring(0,
418-
urlString.lastIndexOf(resourcesFolder)));
422+
URL vfsDirUrl = URI
423+
.create(urlString.substring(0,
424+
urlString.lastIndexOf(resourcesFolder)))
425+
.toURL();
419426
frontendFiles
420427
.add(getPhysicalFileOfJBossVfsDirectory(vfsDirUrl));
421428
} else if (jarMatcher.find()) {
@@ -447,7 +454,7 @@ private static File getPhysicalFileOfJBossVfsDirectory(URL url)
447454
throws IOException, VaadinInitializerException {
448455
try {
449456
Object virtualFile = url.openConnection().getContent();
450-
Class virtualFileClass = virtualFile.getClass();
457+
Class<?> virtualFileClass = virtualFile.getClass();
451458

452459
// Reflection as we cannot afford a dependency to
453460
// WildFly or JBoss
@@ -463,7 +470,7 @@ private static File getPhysicalFileOfJBossVfsDirectory(URL url)
463470
// are created. Later, these physical files are scanned
464471
// to collect
465472
// their resources.
466-
List virtualFiles = (List) getChildrenRecursivelyMethod
473+
List<?> virtualFiles = (List<?>) getChildrenRecursivelyMethod
467474
.invoke(virtualFile);
468475
File rootDirectory = (File) getPhysicalFileMethod
469476
.invoke(virtualFile);
@@ -510,15 +517,15 @@ private static void generateJarFromJBossVfsFolder(Object jarVirtualFile,
510517
// We should use reflection to use JBoss VFS API as we cannot
511518
// afford a
512519
// dependency to WildFly or JBoss
513-
Class virtualFileClass = jarVirtualFile.getClass();
520+
Class<?> virtualFileClass = jarVirtualFile.getClass();
514521
Method getChildrenRecursivelyMethod = virtualFileClass
515522
.getMethod("getChildrenRecursively");
516523
Method openStreamMethod = virtualFileClass.getMethod("openStream");
517524
Method isFileMethod = virtualFileClass.getMethod("isFile");
518525
Method getPathNameRelativeToMethod = virtualFileClass
519526
.getMethod("getPathNameRelativeTo", virtualFileClass);
520527

521-
List jarVirtualChildren = (List) getChildrenRecursivelyMethod
528+
List<?> jarVirtualChildren = (List<?>) getChildrenRecursivelyMethod
522529
.invoke(jarVirtualFile);
523530
try (ZipOutputStream zipOutputStream = new ZipOutputStream(
524531
Files.newOutputStream(tempJar))) {

vaadin-dev-server/src/test/java/com/vaadin/base/devserver/startup/DevModeInitializerTest.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import com.vaadin.flow.component.dependency.JsModule;
5353
import com.vaadin.flow.component.page.AppShellConfigurator;
5454
import com.vaadin.flow.di.Lookup;
55+
import com.vaadin.flow.di.ResourceProvider;
5556
import com.vaadin.flow.router.Route;
5657
import com.vaadin.flow.server.InitParameters;
5758
import com.vaadin.flow.server.LoadDependenciesOnStartup;
@@ -626,14 +627,17 @@ private void loadingJarsWithProtocol_allFilesExist(String resourcesFolder,
626627
List<URL> urls = new ArrayList<>();
627628
urls.add(jar);
628629

629-
// Create mock loader with the single jar to be found
630-
ClassLoader classLoader = Mockito.mock(ClassLoader.class);
631-
Mockito.when(classLoader.getResources(resourcesFolder))
632-
.thenReturn(Collections.enumeration(urls));
630+
// Create mock resource provider with the single jar to be found
631+
ResourceProvider resourceProvider = Mockito
632+
.mock(ResourceProvider.class);
633+
Mockito.when(lookup.lookup(ResourceProvider.class))
634+
.thenReturn(resourceProvider);
635+
Mockito.when(resourceProvider.getApplicationResources(resourcesFolder))
636+
.thenReturn(urls);
633637

634638
// load jars from classloader
635639
List<File> jarFilesFromClassloader = new ArrayList<>(DevModeInitializer
636-
.getFrontendLocationsFromClassloader(classLoader));
640+
.getFrontendLocationsFromResourceProvider(resourceProvider));
637641

638642
// Assert that jar was found and accepted
639643
assertEquals("One jar should have been found and added as a File", 1,
@@ -655,14 +659,18 @@ private void loadingFsResources_allFilesExist(String resourcesRoot,
655659
private void loadingFsResources_allFilesExist(Collection<URL> urls,
656660
String resourcesFolder)
657661
throws IOException, VaadinInitializerException {
658-
// Create mock loader with the single jar to be found
659-
ClassLoader classLoader = Mockito.mock(ClassLoader.class);
660-
Mockito.when(classLoader.getResources(resourcesFolder))
661-
.thenReturn(Collections.enumeration(urls));
662+
663+
// Create mock resource provider with the single jar to be found
664+
ResourceProvider resourceProvider = Mockito
665+
.mock(ResourceProvider.class);
666+
Mockito.when(lookup.lookup(ResourceProvider.class))
667+
.thenReturn(resourceProvider);
668+
Mockito.when(resourceProvider.getApplicationResources(resourcesFolder))
669+
.thenReturn(List.copyOf(urls));
662670

663671
// load jars from classloader
664672
List<File> locations = new ArrayList<>(DevModeInitializer
665-
.getFrontendLocationsFromClassloader(classLoader));
673+
.getFrontendLocationsFromResourceProvider(resourceProvider));
666674

667675
// Assert that resource was found and accepted
668676
assertEquals("One resource should have been found and added as a File",

0 commit comments

Comments
 (0)