Skip to content

Commit fbd03f8

Browse files
fix: use ResourceProvider to search for frontend resources (#22627) (#22651)
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 Co-authored-by: Marco Collovati <marco@vaadin.com>
1 parent 4c30ba1 commit fbd03f8

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;
@@ -62,6 +62,7 @@
6262
import com.vaadin.base.devserver.viteproxy.ViteWebsocketEndpoint;
6363
import com.vaadin.experimental.FeatureFlags;
6464
import com.vaadin.flow.di.Lookup;
65+
import com.vaadin.flow.di.ResourceProvider;
6566
import com.vaadin.flow.internal.DevModeHandler;
6667
import com.vaadin.flow.server.Constants;
6768
import com.vaadin.flow.server.ExecutionFailedException;
@@ -273,8 +274,10 @@ public static DevModeHandler initDevModeHandler(Set<Class<?>> classes,
273274
options.createMissingPackageJson(true);
274275
}
275276

276-
Set<File> frontendLocations = getFrontendLocationsFromClassloader(
277-
DevModeStartupListener.class.getClassLoader());
277+
ResourceProvider resourceProvider = lookup
278+
.lookup(ResourceProvider.class);
279+
Set<File> frontendLocations = getFrontendLocationsFromResourceProvider(
280+
resourceProvider);
278281

279282
boolean useByteCodeScanner = config.getBooleanProperty(
280283
SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE,
@@ -387,15 +390,17 @@ private static Logger log() {
387390
* META-INF/resources/frontend and META-INF/resources/themes folder. We
388391
* don't use URLClassLoader because will fail in Java 9+
389392
*/
390-
static Set<File> getFrontendLocationsFromClassloader(
391-
ClassLoader classLoader) throws VaadinInitializerException {
393+
static Set<File> getFrontendLocationsFromResourceProvider(
394+
ResourceProvider resourceProvider)
395+
throws VaadinInitializerException {
392396
Set<File> frontendFiles = new HashSet<>();
393-
frontendFiles.addAll(getFrontendLocationsFromClassloader(classLoader,
394-
Constants.RESOURCES_FRONTEND_DEFAULT));
395-
frontendFiles.addAll(getFrontendLocationsFromClassloader(classLoader,
396-
Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT));
397-
frontendFiles.addAll(getFrontendLocationsFromClassloader(classLoader,
398-
Constants.RESOURCES_THEME_JAR_DEFAULT));
397+
frontendFiles.addAll(getFrontendLocationsFromResourceProvider(
398+
resourceProvider, Constants.RESOURCES_FRONTEND_DEFAULT));
399+
frontendFiles.addAll(
400+
getFrontendLocationsFromResourceProvider(resourceProvider,
401+
Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT));
402+
frontendFiles.addAll(getFrontendLocationsFromResourceProvider(
403+
resourceProvider, Constants.RESOURCES_THEME_JAR_DEFAULT));
399404
return frontendFiles;
400405
}
401406

@@ -410,22 +415,22 @@ private static void runNodeTasks(NodeTasks tasks) {
410415
}
411416
}
412417

413-
private static Set<File> getFrontendLocationsFromClassloader(
414-
ClassLoader classLoader, String resourcesFolder)
418+
private static Set<File> getFrontendLocationsFromResourceProvider(
419+
ResourceProvider resourceProvider, String resourcesFolder)
415420
throws VaadinInitializerException {
416421
Set<File> frontendFiles = new HashSet<>();
417422
try {
418-
Enumeration<URL> en = classLoader.getResources(resourcesFolder);
423+
List<URL> en = resourceProvider
424+
.getApplicationResources(resourcesFolder);
419425
if (en == null) {
420426
return frontendFiles;
421427
}
422428
Set<String> vfsJars = new HashSet<>();
423-
while (en.hasMoreElements()) {
424-
URL url = en.nextElement();
429+
for (URL url : en) {
425430
String urlString = url.toString();
426431

427432
String path = URLDecoder.decode(url.getPath(),
428-
StandardCharsets.UTF_8.name());
433+
StandardCharsets.UTF_8);
429434
Matcher jarMatcher = JAR_FILE_REGEX.matcher(path);
430435
Matcher zipProtocolJarMatcher = ZIP_PROTOCOL_JAR_FILE_REGEX
431436
.matcher(path);
@@ -439,12 +444,14 @@ private static Set<File> getFrontendLocationsFromClassloader(
439444
if (jarVfsMatcher.find()) {
440445
String vfsJar = jarVfsMatcher.group(1);
441446
if (vfsJars.add(vfsJar)) { // NOSONAR
442-
frontendFiles.add(
443-
getPhysicalFileOfJBossVfsJar(new URL(vfsJar)));
447+
frontendFiles.add(getPhysicalFileOfJBossVfsJar(
448+
URI.create(vfsJar).toURL()));
444449
}
445450
} else if (dirVfsMatcher.find()) {
446-
URL vfsDirUrl = new URL(urlString.substring(0,
447-
urlString.lastIndexOf(resourcesFolder)));
451+
URL vfsDirUrl = URI
452+
.create(urlString.substring(0,
453+
urlString.lastIndexOf(resourcesFolder)))
454+
.toURL();
448455
frontendFiles
449456
.add(getPhysicalFileOfJBossVfsDirectory(vfsDirUrl));
450457
} else if (jarMatcher.find()) {
@@ -476,7 +483,7 @@ private static File getPhysicalFileOfJBossVfsDirectory(URL url)
476483
throws IOException, VaadinInitializerException {
477484
try {
478485
Object virtualFile = url.openConnection().getContent();
479-
Class virtualFileClass = virtualFile.getClass();
486+
Class<?> virtualFileClass = virtualFile.getClass();
480487

481488
// Reflection as we cannot afford a dependency to
482489
// WildFly or JBoss
@@ -492,7 +499,7 @@ private static File getPhysicalFileOfJBossVfsDirectory(URL url)
492499
// are created. Later, these physical files are scanned
493500
// to collect
494501
// their resources.
495-
List virtualFiles = (List) getChildrenRecursivelyMethod
502+
List<?> virtualFiles = (List<?>) getChildrenRecursivelyMethod
496503
.invoke(virtualFile);
497504
File rootDirectory = (File) getPhysicalFileMethod
498505
.invoke(virtualFile);
@@ -539,15 +546,15 @@ private static void generateJarFromJBossVfsFolder(Object jarVirtualFile,
539546
// We should use reflection to use JBoss VFS API as we cannot
540547
// afford a
541548
// dependency to WildFly or JBoss
542-
Class virtualFileClass = jarVirtualFile.getClass();
549+
Class<?> virtualFileClass = jarVirtualFile.getClass();
543550
Method getChildrenRecursivelyMethod = virtualFileClass
544551
.getMethod("getChildrenRecursively");
545552
Method openStreamMethod = virtualFileClass.getMethod("openStream");
546553
Method isFileMethod = virtualFileClass.getMethod("isFile");
547554
Method getPathNameRelativeToMethod = virtualFileClass
548555
.getMethod("getPathNameRelativeTo", virtualFileClass);
549556

550-
List jarVirtualChildren = (List) getChildrenRecursivelyMethod
557+
List<?> jarVirtualChildren = (List<?>) getChildrenRecursivelyMethod
551558
.invoke(jarVirtualFile);
552559
try (ZipOutputStream zipOutputStream = new ZipOutputStream(
553560
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
@@ -36,6 +36,7 @@
3636
import com.vaadin.flow.component.dependency.JsModule;
3737
import com.vaadin.flow.component.page.AppShellConfigurator;
3838
import com.vaadin.flow.di.Lookup;
39+
import com.vaadin.flow.di.ResourceProvider;
3940
import com.vaadin.flow.router.Route;
4041
import com.vaadin.flow.server.InitParameters;
4142
import com.vaadin.flow.server.LoadDependenciesOnStartup;
@@ -612,14 +613,17 @@ private void loadingJarsWithProtocol_allFilesExist(String resourcesFolder,
612613
List<URL> urls = new ArrayList<>();
613614
urls.add(jar);
614615

615-
// Create mock loader with the single jar to be found
616-
ClassLoader classLoader = Mockito.mock(ClassLoader.class);
617-
Mockito.when(classLoader.getResources(resourcesFolder))
618-
.thenReturn(Collections.enumeration(urls));
616+
// Create mock resource provider with the single jar to be found
617+
ResourceProvider resourceProvider = Mockito
618+
.mock(ResourceProvider.class);
619+
Mockito.when(lookup.lookup(ResourceProvider.class))
620+
.thenReturn(resourceProvider);
621+
Mockito.when(resourceProvider.getApplicationResources(resourcesFolder))
622+
.thenReturn(urls);
619623

620624
// load jars from classloader
621625
List<File> jarFilesFromClassloader = new ArrayList<>(DevModeInitializer
622-
.getFrontendLocationsFromClassloader(classLoader));
626+
.getFrontendLocationsFromResourceProvider(resourceProvider));
623627

624628
// Assert that jar was found and accepted
625629
assertEquals("One jar should have been found and added as a File", 1,
@@ -641,14 +645,18 @@ private void loadingFsResources_allFilesExist(String resourcesRoot,
641645
private void loadingFsResources_allFilesExist(Collection<URL> urls,
642646
String resourcesFolder)
643647
throws IOException, VaadinInitializerException {
644-
// Create mock loader with the single jar to be found
645-
ClassLoader classLoader = Mockito.mock(ClassLoader.class);
646-
Mockito.when(classLoader.getResources(resourcesFolder))
647-
.thenReturn(Collections.enumeration(urls));
648+
649+
// Create mock resource provider with the single jar to be found
650+
ResourceProvider resourceProvider = Mockito
651+
.mock(ResourceProvider.class);
652+
Mockito.when(lookup.lookup(ResourceProvider.class))
653+
.thenReturn(resourceProvider);
654+
Mockito.when(resourceProvider.getApplicationResources(resourcesFolder))
655+
.thenReturn(List.copyOf(urls));
648656

649657
// load jars from classloader
650658
List<File> locations = new ArrayList<>(DevModeInitializer
651-
.getFrontendLocationsFromClassloader(classLoader));
659+
.getFrontendLocationsFromResourceProvider(resourceProvider));
652660

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

0 commit comments

Comments
 (0)