From dc49efbb3f17266ef200bc4a6fcad70bacafe1b7 Mon Sep 17 00:00:00 2001 From: Tomi Virtanen Date: Tue, 14 May 2024 18:11:59 +0300 Subject: [PATCH 1/7] feat: speedup startup time by excluding jars Optimize class scanning of VaadinServletContextInitializer by excluding some commonly used jars and add support for vaadin.blocked-jar boolean property in META-INF/VAADIN/package.properties to exclude whole jar content from the class scanner. Fixes: #19112 --- .../allowed-ui/pom.xml | 141 ++++++++++++++++++ .../flow/spring/test/allowed/AllowedView.java | 24 +++ .../flow/spring/test/blocked/BlockedView.java | 27 ++++ .../test/filtering/ClassScannerView.java | 40 +++++ .../filtering/TestServletInitializer.java | 58 +++++++ .../src/main/resources/application.properties | 5 + .../spring/test/filtering/ClassScannerIT.java | 96 ++++++++++++ .../ScannedAllowedRoute.java | 2 +- .../spring/test/exclude/ExcludedRoute.java | 26 ++++ .../META-INF/VAADIN/package.properties | 1 + .../test-spring-filter-packages/pom.xml | 1 + .../test-spring-filter-packages/ui/pom.xml | 6 + .../spring/test/filtering/ClassScannerIT.java | 9 +- .../VaadinServletContextInitializer.java | 28 +++- .../spring/io/FilterableResourceResolver.java | 102 +++++++++---- 15 files changed, 531 insertions(+), 35 deletions(-) create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/pom.xml create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/allowed/AllowedView.java create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/blocked/BlockedView.java create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/filtering/ClassScannerView.java create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/filtering/TestServletInitializer.java create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/resources/application.properties create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java rename flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked/src/main/java/com/vaadin/flow/spring/test/{blocked => allowed}/ScannedAllowedRoute.java (94%) create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/src/main/java/com/vaadin/flow/spring/test/exclude/ExcludedRoute.java create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/src/main/resources/META-INF/VAADIN/package.properties diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/pom.xml new file mode 100644 index 00000000000..05073878f71 --- /dev/null +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/pom.xml @@ -0,0 +1,141 @@ + + + + + 4.0.0 + + vaadin-test-spring-filter-packages + com.vaadin + 24.5-SNAPSHOT + + vaadin-test-spring-filter-packages-allowed-ui + Test UI with allowed-packages properties + jar + + + true + + + + + com.vaadin + vaadin-spring + ${project.version} + + + + com.vaadin + vaadin-dev-server + ${project.version} + + + + org.springframework.boot + spring-boot-autoconfigure + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.apache.logging.log4j + log4j-api + + + + + + org.springframework.boot + spring-boot-starter-jetty + + + + com.vaadin + vaadin-test-spring-filter-packages-lib-allowed + ${project.version} + + + + com.vaadin + vaadin-test-spring-filter-packages-lib-blocked + ${project.version} + + + + com.vaadin + vaadin-test-spring-filter-packages-lib-exclude + ${project.version} + + + + + + + + com.vaadin + flow-maven-plugin + ${project.version} + + + + prepare-frontend + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + 100 + 2500 + 9009 + + -Xdebug + -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=18888 + + + + + + pre-integration-test + + start + + + + post-integration-test + + stop + + + + + + + + + diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/allowed/AllowedView.java b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/allowed/AllowedView.java new file mode 100644 index 00000000000..6cbd430b53c --- /dev/null +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/allowed/AllowedView.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.spring.test.allowed; + +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.router.Route; + +@Route("allowed") +public class AllowedView extends Div { +} diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/blocked/BlockedView.java b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/blocked/BlockedView.java new file mode 100644 index 00000000000..22608e9e0ce --- /dev/null +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/blocked/BlockedView.java @@ -0,0 +1,27 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.spring.test.blocked; + +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.router.Route; + +@Route("blocked") +public class BlockedView extends Div { + + public BlockedView() { + } +} diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/filtering/ClassScannerView.java b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/filtering/ClassScannerView.java new file mode 100644 index 00000000000..15930edffee --- /dev/null +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/filtering/ClassScannerView.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.spring.test.filtering; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.router.Route; + +@Route("") +public class ClassScannerView extends Div { + + public static Set> classes = Collections.emptySet(); + public static final String SCANNED_CLASSES = "scanned-classes"; + + public ClassScannerView() { + Span scannedClasses = new Span(classes.stream() + .map(Class::getSimpleName).collect(Collectors.joining(","))); + scannedClasses.setId(SCANNED_CLASSES); + add(scannedClasses); + } + +} diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/filtering/TestServletInitializer.java b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/filtering/TestServletInitializer.java new file mode 100644 index 00000000000..4639a0371a7 --- /dev/null +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/java/com/vaadin/flow/spring/test/filtering/TestServletInitializer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.spring.test.filtering; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Set; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.vaadin.flow.spring.VaadinServletContextInitializer; + +/** + * The entry point of the Spring Boot application. + */ +@SpringBootApplication +@Configuration +public class TestServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(TestServletInitializer.class, args); + } + + @Bean + public VaadinServletContextInitializer vaadinServletContextInitializer( + ApplicationContext context) { + + return new VaadinServletContextInitializer(context) { + @Override + protected Set> findClassesForDevMode( + Set basePackages, + List> annotations, + List> superTypes) { + ClassScannerView.classes = super.findClassesForDevMode( + basePackages, annotations, superTypes); + return ClassScannerView.classes; + } + }; + } +} diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/resources/application.properties b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/resources/application.properties new file mode 100644 index 00000000000..84cdb307a2f --- /dev/null +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/main/resources/application.properties @@ -0,0 +1,5 @@ +server.port=8888 + +# By default, also following package is scanned in allowed-ui module: +# - com/vaadin/flow/spring/test/filtering +vaadin.allowed-packages=com/vaadin/flow/spring/test/allowed,com/vaadin/flow/spring/test/exclude diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java new file mode 100644 index 00000000000..b94f5060cca --- /dev/null +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java @@ -0,0 +1,96 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.spring.test.filtering; + +import java.util.List; +import java.util.stream.Stream; + +import com.vaadin.flow.spring.test.exclude.ExcludedRoute; +import com.vaadin.flow.spring.test.allowed.AllowedView; +import com.vaadin.flow.spring.test.allowed.startup.CustomVaadinServiceInitListener; +import com.vaadin.flow.spring.test.allowed.startup.vaadin.AllowedRoute; +import com.vaadin.flow.spring.test.allowed.BlockedRoute; +import com.vaadin.flow.spring.test.allowed.ScannedAllowedRoute; +import com.vaadin.flow.spring.test.blocked.startup.BlockedCustomVaadinServiceInitListener; +import com.vaadin.flow.spring.test.blocked.startup.vaadin.ScannedBlockedRoute; +import com.vaadin.flow.spring.test.blocked.BlockedView; +import com.vaadin.flow.testutil.ChromeBrowserTest; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; + +/** + * Primary target of this IT is class scanning of DevModeServletContextListener + * in {@link com.vaadin.flow.spring.VaadinServletContextInitializer} and + * especially usage of {@code vaadin.blocked-packages} and + * {@code vaadin.allowed-packages} in a multi-module Maven project with jar + * packaged dependencies. + */ +public class ClassScannerIT extends ChromeBrowserTest { + + @Test + public void allowedUiModule_withAllowedPackages() { + open(); + assertClassAllowed(ClassScannerView.class.getSimpleName()); + assertClassAllowed(AllowedView.class.getSimpleName()); + assertClassBlocked(BlockedView.class.getSimpleName()); + } + + @Test + public void libAllowedModule_withAllowedPackagesJar() { + open(); + assertClassAllowed(AllowedRoute.class.getSimpleName()); + assertClassAllowed( + CustomVaadinServiceInitListener.class.getSimpleName()); + assertClassBlocked(BlockedRoute.class.getSimpleName()); + } + + @Test + public void libBlockedModule_withBlockedPackagesJar() { + open(); + assertClassBlocked(ScannedBlockedRoute.class.getSimpleName()); + assertClassBlocked( + BlockedCustomVaadinServiceInitListener.class.getSimpleName()); + assertClassAllowed(ScannedAllowedRoute.class.getSimpleName()); + } + + @Test + public void libExcludedModule_withExcludedJar() { + open(); + assertClassBlocked(ExcludedRoute.class.getSimpleName()); + } + + private void assertClassAllowed(String className) { + Assert.assertTrue(className + " should be allowed.", + getScannedClasses().contains(className)); + } + + private void assertClassBlocked(String className) { + Assert.assertFalse(className + " should be blocked.", + getScannedClasses().contains(className)); + } + + private List getScannedClasses() { + return Stream.of(findElement(By.id(ClassScannerView.SCANNED_CLASSES)) + .getText().split(",")).map(String::trim).toList(); + } + + @Override + protected String getTestPath() { + return "/"; + } +} diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked/src/main/java/com/vaadin/flow/spring/test/blocked/ScannedAllowedRoute.java b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked/src/main/java/com/vaadin/flow/spring/test/allowed/ScannedAllowedRoute.java similarity index 94% rename from flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked/src/main/java/com/vaadin/flow/spring/test/blocked/ScannedAllowedRoute.java rename to flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked/src/main/java/com/vaadin/flow/spring/test/allowed/ScannedAllowedRoute.java index 10d49a1a14c..e40b4067723 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked/src/main/java/com/vaadin/flow/spring/test/blocked/ScannedAllowedRoute.java +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked/src/main/java/com/vaadin/flow/spring/test/allowed/ScannedAllowedRoute.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.vaadin.flow.spring.test.blocked; +package com.vaadin.flow.spring.test.allowed; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.router.Route; diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/src/main/java/com/vaadin/flow/spring/test/exclude/ExcludedRoute.java b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/src/main/java/com/vaadin/flow/spring/test/exclude/ExcludedRoute.java new file mode 100644 index 00000000000..a73c026f72c --- /dev/null +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/src/main/java/com/vaadin/flow/spring/test/exclude/ExcludedRoute.java @@ -0,0 +1,26 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.spring.test.exclude; + +import com.vaadin.flow.router.Route; + +/** + * Test class in a jar that is blocked from scanning via vaadin.blocked-jar=true + * in package.properties. + */ +@Route("excluded-route") +public class ExcludedRoute { +} diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/src/main/resources/META-INF/VAADIN/package.properties b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/src/main/resources/META-INF/VAADIN/package.properties new file mode 100644 index 00000000000..31b5bcc3068 --- /dev/null +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/src/main/resources/META-INF/VAADIN/package.properties @@ -0,0 +1 @@ +vaadin.blocked-jar=true diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/pom.xml index 0a1b762ffbc..5337212328b 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/pom.xml @@ -37,6 +37,7 @@ lib-blocked lib-exclude ui + allowed-ui diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml index cf88cab2bc7..5b19461f1da 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml @@ -80,6 +80,12 @@ ${project.version} + + com.vaadin + vaadin-test-spring-filter-packages-lib-exclude + ${project.version} + + diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java index 7ba72c0a4f6..3d662d008a1 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java @@ -19,10 +19,11 @@ import java.util.List; import java.util.stream.Stream; +import com.vaadin.flow.spring.test.exclude.ExcludedRoute; import com.vaadin.flow.spring.test.allowed.startup.CustomVaadinServiceInitListener; import com.vaadin.flow.spring.test.allowed.startup.vaadin.AllowedRoute; import com.vaadin.flow.spring.test.allowed.BlockedRoute; -import com.vaadin.flow.spring.test.blocked.ScannedAllowedRoute; +import com.vaadin.flow.spring.test.allowed.ScannedAllowedRoute; import com.vaadin.flow.spring.test.blocked.startup.BlockedCustomVaadinServiceInitListener; import com.vaadin.flow.spring.test.blocked.startup.vaadin.ScannedBlockedRoute; import com.vaadin.flow.spring.test.filtering.blocked.BlockedView; @@ -66,6 +67,12 @@ public void libBlockedModule_withBlockedPackagesJar() { assertClassAllowed(ScannedAllowedRoute.class.getSimpleName()); } + @Test + public void libExcludedModule_withExcludedJar() { + open(); + assertClassBlocked(ExcludedRoute.class.getSimpleName()); + } + private void assertClassAllowed(String className) { Assert.assertTrue(className + " should be allowed.", getScannedClasses().contains(className)); diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java index 7b6380aae31..51c573be579 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java @@ -708,7 +708,7 @@ public VaadinServletContextInitializer(ApplicationContext context) { customScanOnly = Arrays.stream(onlyScanProperty.split(",")) .map(onlyPackage -> onlyPackage.replace('/', '.').trim()) .collect(Collectors.toList()); - customLoader = appContext; + customLoader = new CustomResourceLoader(appContext); } if (!customScanOnly.isEmpty() && !neverScan.isEmpty()) { @@ -937,6 +937,12 @@ private static class CustomResourceLoader .stream().map(packageName -> packageName.replace('.', '/')) .collect(Collectors.toList())); + /** + * If true, only filter based on the package.properties in jar/module. + * false by default. + */ + private boolean filterOnlyByPackageProperties = false; + public CustomResourceLoader(ResourceLoader resourceLoader, List addedScanNever) { super(resourceLoader); @@ -947,6 +953,11 @@ public CustomResourceLoader(ResourceLoader resourceLoader, addedScanNever.forEach(scanNever::addPrefix); } + public CustomResourceLoader(ResourceLoader resourceLoader) { + super(resourceLoader); + filterOnlyByPackageProperties = true; + } + /** * Lock used to ensure there's only one update going on at once. *

@@ -992,11 +1003,11 @@ private Resource[] collectResources(String locationPattern) path = originalPath; } - if (devModeCachingEnabled && skipped.contains(originalPath)) { + if (isDevModeCacheUsed() && skipped.contains(originalPath)) { continue; } - if (devModeCachingEnabled && valid.contains(originalPath)) { + if (isDevModeCacheUsed() && valid.contains(originalPath)) { resourcesList.add(resource); // Restore root paths to ensure new resources are correctly // validate and cached after a reload @@ -1013,7 +1024,7 @@ private Resource[] collectResources(String locationPattern) int index = path.lastIndexOf(".jar!/"); if (index >= 0) { String relativePath = path.substring(index + 6); - if (devModeCachingEnabled + if (isDevModeCacheUsed() && relativePath.endsWith(".class")) { // Stores names of all classes from JARs String className = relativePath @@ -1046,7 +1057,7 @@ private Resource[] collectResources(String locationPattern) } } - if (devModeCachingEnabled) { + if (isDevModeCacheUsed()) { if (resourcesList.contains(resource)) { valid.add(originalPath); } else { @@ -1058,6 +1069,10 @@ private Resource[] collectResources(String locationPattern) return resourcesList.toArray(new Resource[0]); } + private boolean isDevModeCacheUsed() { + return !filterOnlyByPackageProperties && devModeCachingEnabled; + } + /** * Checks if the given path should be scanned. * @@ -1067,7 +1082,8 @@ private Resource[] collectResources(String locationPattern) * otherwise */ private boolean shouldPathBeScanned(String path) { - return scanAlways.hasPrefix(path) || !scanNever.hasPrefix(path); + return filterOnlyByPackageProperties || scanAlways.hasPrefix(path) + || !scanNever.hasPrefix(path); } /** diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java index 10b143860a8..a7d00ed46c9 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -68,11 +69,32 @@ public class FilterableResourceResolver * The property key for blocked packages. */ public static final String BLOCKED_PACKAGES_PROPERTY = "vaadin.blocked-packages"; + /** + * The property key for blocked JAR file. + */ + public static final String BLOCKED_JAR_PROPERTY = "vaadin.blocked-jar"; + + private static final List DEFAULT_SCAN_NEVER_JAR = Stream.of( + "antlr", "logback-classic", "logback-classic-core", + "commons-codec-", "commons-fileupload", "commons-io", + "commons-logging", "commons-exec", "commons-lang", "jackson-", + "atmosphere-runtime", "byte-buddy", "commons-compress", + "aspectjweaver", "hibernate-core", "hibernate-commons", + "hibernate-validator", "jboss-logging", "/selenium-", "slf4j", + "/spring-", "org/webjars/bowergithub", "snakeyaml", + + "javax.", "jakarta.", "kotlin-", + + "gwt-elemental", "javassist", "javaparser-core", + "javaparser-symbol", "oshi-core", "micrometer-", "nimbus-jose-jwt", + + "/hilla-engine-core-", "/hilla-engine-runtime-", + "/hilla-parser-jvm-", "/hilla-runtime-plugin-").toList(); private final Map propertiesCache = new HashMap<>(); private record PackageInfo(Set allowedPackages, - Set blockedPackages) implements Serializable { + Set blockedPackages, boolean blockedJar) implements Serializable { } /** @@ -116,6 +138,9 @@ protected boolean isJar(String path) { private Resource doResolveRootDirResource(Resource original) throws IOException { String rootDirPath = original.getURI().getPath(); + if (rootDirPath == null) { + rootDirPath = original.getURI().toString(); + } if (rootDirPath != null) { int index = rootDirPath.lastIndexOf(JAR_KEY); if (index != -1) { @@ -147,9 +172,12 @@ protected Set doFindPathMatchingJarResources( Resource rootDirResource, URL rootDirUrl, String subPattern) throws IOException { String path = rootDirResource.getURI().toString(); - cachePackageProperties(path, rootDirResource, rootDirUrl); + if (DEFAULT_SCAN_NEVER_JAR.stream().anyMatch(path::contains)) { + return Set.of(); + } + String key = cachePackageProperties(path, rootDirResource, rootDirUrl); - if (isBlockedJar(rootDirResource)) { + if (isBlockedJar(rootDirResource, key)) { return Collections.emptySet(); } return super.doFindPathMatchingJarResources(rootDirResource, rootDirUrl, @@ -174,42 +202,55 @@ protected Set doFindAllClassPathResources(String path) throws IOException { var result = super.doFindAllClassPathResources(path); result.removeIf(res -> { - cachePackageProperties(res); - return isBlockedJar(res); + try { + String resourcePath = res.getURI().getPath(); + if (resourcePath != null && DEFAULT_SCAN_NEVER_JAR.stream() + .anyMatch(resourcePath::contains)) { + return false; + } + } catch (IOException e) { + getLogger().warn("Failed to resolve path for resource {}", res, + e); + } + String key = cachePackageProperties(res); + return isBlockedJar(res, key); }); return result; } - private void cachePackageProperties(String path, Resource rootDirResource, + private String cachePackageProperties(String path, Resource rootDirResource, URL rootDirUrl) throws IOException { - if (!propertiesCache.containsKey(path)) { - if (isJar(path)) { - String jarPath = pathToKey(path); - propertiesCache.put(jarPath, readPackageProperties(rootDirUrl, - path, doResolveRootDirResource(rootDirResource))); + String key = path; + if (isJar(path)) { + key = pathToKey(path); + if (!propertiesCache.containsKey(key)) { + propertiesCache.put(key, readPackageProperties(rootDirUrl, path, + doResolveRootDirResource(rootDirResource))); getLogger().trace("Caching package.properties of JAR {}", path); - } else { - Resource resource = doFindPathMatchingFileResources( - rootDirResource, PACKAGE_PROPERTIES_PATH).stream() - .findFirst().orElse(null); - Properties properties = resource != null - ? PropertiesLoaderUtils.loadProperties(resource) - : null; - propertiesCache.put(path, createPackageInfo(properties)); - getLogger().trace("Caching package.properties of directory {}", - path); } + } else if (!propertiesCache.containsKey(path)) { + Resource resource = doFindPathMatchingFileResources(rootDirResource, + PACKAGE_PROPERTIES_PATH).stream().findFirst().orElse(null); + Properties properties = resource != null + ? PropertiesLoaderUtils.loadProperties(resource) + : null; + propertiesCache.put(path, createPackageInfo(properties)); + getLogger().trace("Caching package.properties of directory {}", + path); } + return key; } - private void cachePackageProperties(Resource res) { + private String cachePackageProperties(Resource res) { + String key = null; try { Resource rootDirResource = convertClassLoaderURL(res.getURL()); String rootDirPath = rootDirResource.getURI().toString(); String rootPath = rootDirResource.getURI().getPath(); + key = rootPath; if (rootPath != null && isJar(rootDirPath)) { String jarPath = toJarPath(rootDirPath); - String key = pathToKey(rootPath); + key = pathToKey(rootPath); if (!propertiesCache.containsKey(key)) { propertiesCache.put(key, readPackageProperties(null, jarPath, rootDirResource)); @@ -232,6 +273,7 @@ private void cachePackageProperties(Resource res) { getLogger().warn("Failed to find {} for path {}", PACKAGE_PROPERTIES_PATH, res, e); } + return key; } /** @@ -240,12 +282,16 @@ private void cachePackageProperties(Resource res) { * * @param resource * the resource to check + * @param key + * the key for the package info * @return {@code true} if the resource is a blocked jar, {@code false} * otherwise */ - protected boolean isBlockedJar(Resource resource) { - // placeholder to handle case of package.properties with - // vaadin.blocked-jar=true + protected boolean isBlockedJar(Resource resource, String key) { + if (resource != null && key != null) { + PackageInfo pkgInfo = propertiesCache.get(key); + return pkgInfo != null && pkgInfo.blockedJar(); + } return false; } @@ -383,6 +429,8 @@ private PackageInfo createPackageInfo(Properties properties) { .split(",")) .filter(pkg -> !pkg.isBlank()).map(String::trim) .map(pkg -> pkg.replace(".", "/")).collect(Collectors.toSet()); - return new PackageInfo(allowedPackages, blockedPackages); + boolean blockedJar = Boolean.parseBoolean( + properties.getProperty(BLOCKED_JAR_PROPERTY, "false")); + return new PackageInfo(allowedPackages, blockedPackages, blockedJar); } } From 6ac4ed26726707d2f3a53c630db489ae2ffb147d Mon Sep 17 00:00:00 2001 From: Tomi Virtanen Date: Thu, 16 May 2024 12:05:49 +0300 Subject: [PATCH 2/7] test: added missing module to computeMatrix.js --- scripts/computeMatrix.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/computeMatrix.js b/scripts/computeMatrix.js index a713f6ed112..e81a61d5a83 100755 --- a/scripts/computeMatrix.js +++ b/scripts/computeMatrix.js @@ -91,6 +91,7 @@ const moduleWeights = { 'flow-tests/vaadin-spring-tests/test-spring-boot-contextpath': { pos: 4, weight: 4 }, 'flow-tests/vaadin-spring-tests/test-spring-boot-reverseproxy': { pos: 4, weight: 4 }, 'flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui': { pos: 4, weight: 4 }, + 'flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui': { pos: 4, weight: 4 }, 'flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-allowed': { pos: 4, weight: 4 }, 'flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked': { pos: 4, weight: 4 }, 'flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude': { pos: 4, weight: 4 }, From c9b02ebb94daed2fb6f17aff55d122ae80bdb4e2 Mon Sep 17 00:00:00 2001 From: Tomi Virtanen Date: Wed, 22 May 2024 15:57:48 +0300 Subject: [PATCH 3/7] chore: fixes and improvement Fixed null jar path. Checking jar name only instead of full paths. Added more default excluded jar name patterns. Added IT for a jar excluded by default. --- .../1.0/spring-test-excluded-1.0.jar | Bin 0 -> 3211 bytes .../1.0/spring-test-excluded-1.0.pom | 8 ++++ .../spring-test-excluded/maven-metadata.xml | 12 ++++++ .../test-spring-filter-packages/ui/pom.xml | 12 ++++++ .../spring/test/filtering/ClassScannerIT.java | 9 +++++ .../VaadinServletContextInitializer.java | 7 ++++ .../spring/io/FilterableResourceResolver.java | 37 ++++++++++++++---- 7 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/local-lib/com/vaadin/test/spring-test-excluded/1.0/spring-test-excluded-1.0.jar create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/local-lib/com/vaadin/test/spring-test-excluded/1.0/spring-test-excluded-1.0.pom create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/local-lib/com/vaadin/test/spring-test-excluded/maven-metadata.xml diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/local-lib/com/vaadin/test/spring-test-excluded/1.0/spring-test-excluded-1.0.jar b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/local-lib/com/vaadin/test/spring-test-excluded/1.0/spring-test-excluded-1.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..7c41f0d168d1dcedaef1897f46a2a664aa41a59c GIT binary patch literal 3211 zcmWIWW@h1HVBlb2;H}&i!GHuffoxyb5Jz24KR5jVh%%s1N7c57@Lp5hFd!R*MUa*G zI{JCKxdw;m`MUWg=4Ga(7MJLTr4|)u=I2={80s1Dayb{JCYGe8=sHzeDHs{(8R!`* zX!sW;Cg-FoIOi7?{7o;YG6!UWNazWjJ#knj%cO>WM z!d!|)1vgYhSz=;JW*#1e0;mepa`MaZs20bfy11YyGcO&FCK=qCN`QfeN2dw_ov9Vc zIi)G7cue32ngEa3+{Cg}d|@GlsTdf_dZ=LtRD+&+^zrJ0gpV%BtGZAJr+{4yOidgd zV8eYpR5_5v`OG{Gql5-M^ zi{}QM_F^^^vCUjAJ2xzP^@C)^9l;CJbVD7Ntm|@`dULsPVDKduqnR7`KlLhj$o?bn z(cH|~m#13hO#Xdp=g#@@@8ucJZ4_PDR@Jefmu(i$-IW?F*ALlzx_vT4ZQjDlWj&w7 zdA=TAY;lC^aAs2a9s#A$Hv5d4#UvcR4nPN--l6hWsF$Gq0s$ce8NS6^A9Bn<;%{`}0fDnd+KXvQtdCGeP zW!%hoIUWTl&s$(H!$~vfO2Tvw=c7Wuvi)}zT<>YAyU@Iy@4;60d%OELOjH%gVRnkT zzRtwMc1I)YdBK9Y3pP0j3rDV)V?0qI z>DE^&JgZl6MfjRcu;TvAmUbwxrp|I++%K0Mx;sQIrI@AmPmj5r`dm7}E0A%v@Yy+` z3Z@%;l;=jiO)uxD$u2WZ^RUmAncJ)0UVNO!ukcchY0pEEM=ROqw_UEd z)%v^QTV;`T@!5Cn=H`b#>g=0y=n307^~c>6V*bZOVhA5*iv?6o~BNU^GOY#A5H2drW2>?IG<3lbidH+uuyMDp|q)sLF*jxJ4UY` zJqxNo;pg|U_LHckcjnKIPFABu2Fv4iw>7N|*b{T)`pMD?nJ*_6@P10kwOCm0z53pD zi%(Ajf4r?SS$e4HXN<&anMb93ae+rWS6)`0dF9E!WSKwQ8_hB&f60z<*l4~$Gf*-3 zOmsc}tIZlUGgmwMMDI)J?7iF=I7{V=uGH(vJ3kv8HQ_36kkXH6Y!;lKz0CZJQ^3J* zdsk1hiL{z;vb9%m@|=rFzK2?(`%f>~k&v3FsJpRjv8~C5xs2Fg#w|TO?MmWv zh8NXg-etabe&w{0S!dghIlkFpX!&Rm&Dnt1*?~I_cJ1D#v)*@xo7$0rVlW;fUqrjxu&2fzaX`! zBr~-*5lE&bCYN}o*kUU%dAZVy@=FVVvWOCym#Yj?-P#(N=$YyU`#A;#d-#WdDo#cw z5oX*qJEg5Q_AU;0Ff$DV#06A_wkO`^faeD_;&qDx6$3-9$sqVmM6{y~a0FcIC zKqit#Xe$7>wV-wY1b}pF0IMx@-SAccZbLxr0)!#4xDA1}5^$RYiyKI!PA6aztOmsw zJfL<1!fiK!Cc@(z){4OG98fy~p_vU>v7l)N1{R`K0W}Ax0b9EQVU8VMbFgI^U@HS; zIPP`^)RhcN8oPmpZBY?Uf3iWGo$as$&j?hwK16KEBSoWah0=zQ)1 z%uq1;vC&sSSO$Y-W6WX-*&UATWVr)TXdydCz&NTK + + 4.0.0 + com.vaadin.test + spring-test-excluded + 1.0 + diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/local-lib/com/vaadin/test/spring-test-excluded/maven-metadata.xml b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/local-lib/com/vaadin/test/spring-test-excluded/maven-metadata.xml new file mode 100644 index 00000000000..1fb1f9782c2 --- /dev/null +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/local-lib/com/vaadin/test/spring-test-excluded/maven-metadata.xml @@ -0,0 +1,12 @@ + + + com.vaadin.test + spring-test-excluded + + 1.0 + + 1.0 + + 20240522123432 + + diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml index 5b19461f1da..9d62193152c 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml @@ -30,6 +30,13 @@ true + + + my-local-repo + file://${project.basedir}/local-lib/ + + + com.vaadin @@ -86,6 +93,11 @@ ${project.version} + + com.vaadin.test + spring-test-excluded + 1.0 + diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java index 3d662d008a1..61d898b228a 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.stream.Stream; +import com.vaadin.flow.spring.test.exclude.ExcludedExternalRoute; import com.vaadin.flow.spring.test.exclude.ExcludedRoute; import com.vaadin.flow.spring.test.allowed.startup.CustomVaadinServiceInitListener; import com.vaadin.flow.spring.test.allowed.startup.vaadin.AllowedRoute; @@ -73,6 +74,14 @@ public void libExcludedModule_withExcludedJar() { assertClassBlocked(ExcludedRoute.class.getSimpleName()); } + @Test + public void defaultJarExclusion_externalJarExcluded() { + open(); + // External Jar "spring-test-excluded.jar" is excluded by default by + // FilterableResourceProvider#DEFAULT_SCAN_NEVER_JAR. + assertClassBlocked(ExcludedExternalRoute.class.getSimpleName()); + } + private void assertClassAllowed(String className) { Assert.assertTrue(className + " should be allowed.", getScannedClasses().contains(className)); diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java index 51c573be579..29271a3e0ee 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java @@ -953,6 +953,13 @@ public CustomResourceLoader(ResourceLoader resourceLoader, addedScanNever.forEach(scanNever::addPrefix); } + /** + * Constructor sets filterOnlyByPackageProperties to true. Only filter + * based on the package.properties in jar/module. + * + * @param resourceLoader + * Resource loader + */ public CustomResourceLoader(ResourceLoader resourceLoader) { super(resourceLoader); filterOnlyByPackageProperties = true; diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java index a7d00ed46c9..1eb0c68f3e3 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.Serializable; import java.net.JarURLConnection; +import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.util.Collections; @@ -59,6 +60,7 @@ public class FilterableResourceResolver private static final String JAR_PROTOCOL = "jar:"; private static final String JAR_KEY = ".jar!/"; + private static final String JAR_EXTENSION = ".jar"; private static final String PACKAGE_PROPERTIES_PATH = "META-INF/VAADIN/package.properties"; /** @@ -80,13 +82,15 @@ public class FilterableResourceResolver "commons-logging", "commons-exec", "commons-lang", "jackson-", "atmosphere-runtime", "byte-buddy", "commons-compress", "aspectjweaver", "hibernate-core", "hibernate-commons", - "hibernate-validator", "jboss-logging", "/selenium-", "slf4j", - "/spring-", "org/webjars/bowergithub", "snakeyaml", + "hibernate-validator", "jboss-logging", "selenium-", "slf4j", + "/spring-", "snakeyaml", "javax.", "jakarta.", "kotlin-", "gwt-elemental", "javassist", "javaparser-core", "javaparser-symbol", "oshi-core", "micrometer-", "nimbus-jose-jwt", + "jooq.", "jooq-", "directory-watcher", "classgraph.", "jsoup.", + "throw-if-servlet3", "ph-css", "ph-commons", "gentyref.", "/hilla-engine-core-", "/hilla-engine-runtime-", "/hilla-parser-jvm-", "/hilla-runtime-plugin-").toList(); @@ -172,7 +176,8 @@ protected Set doFindPathMatchingJarResources( Resource rootDirResource, URL rootDirUrl, String subPattern) throws IOException { String path = rootDirResource.getURI().toString(); - if (DEFAULT_SCAN_NEVER_JAR.stream().anyMatch(path::contains)) { + String jarName = resolveJarName(rootDirResource.getURI()); + if (DEFAULT_SCAN_NEVER_JAR.stream().anyMatch(jarName::contains)) { return Set.of(); } String key = cachePackageProperties(path, rootDirResource, rootDirUrl); @@ -203,10 +208,10 @@ protected Set doFindAllClassPathResources(String path) var result = super.doFindAllClassPathResources(path); result.removeIf(res -> { try { - String resourcePath = res.getURI().getPath(); - if (resourcePath != null && DEFAULT_SCAN_NEVER_JAR.stream() - .anyMatch(resourcePath::contains)) { - return false; + String jarName = resolveJarName(res.getURI()); + if (DEFAULT_SCAN_NEVER_JAR.stream() + .anyMatch(jarName::contains)) { + return true; } } catch (IOException e) { getLogger().warn("Failed to resolve path for resource {}", res, @@ -218,6 +223,24 @@ protected Set doFindAllClassPathResources(String path) return result; } + private String resolveJarName(URI resourceURI) { + String resourcePath = resourceURI.getPath(); + if (resourcePath == null) { + resourcePath = resourceURI.toString(); + } + int index = resourcePath.lastIndexOf(JAR_EXTENSION); + if (index > -1) { + String jarName = resourcePath.substring(0, + index + JAR_EXTENSION.length()); + index = jarName.lastIndexOf("/"); + if (index > -1) { + return "/" + jarName.substring(index + 1); + } + return "/" + jarName; + } + return resourcePath; + } + private String cachePackageProperties(String path, Resource rootDirResource, URL rootDirUrl) throws IOException { String key = path; From a3d7afbcbb5d19ac42c778adc3d2cfe561341f0c Mon Sep 17 00:00:00 2001 From: Tomi Virtanen Date: Thu, 23 May 2024 09:57:33 +0300 Subject: [PATCH 4/7] chore: apply DEFAULT_SCAN_NEVER_JAR only for jar --- .../vaadin/flow/spring/io/FilterableResourceResolver.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java index 1eb0c68f3e3..a5fc0c1267a 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java @@ -177,7 +177,8 @@ protected Set doFindPathMatchingJarResources( throws IOException { String path = rootDirResource.getURI().toString(); String jarName = resolveJarName(rootDirResource.getURI()); - if (DEFAULT_SCAN_NEVER_JAR.stream().anyMatch(jarName::contains)) { + if (jarName != null && DEFAULT_SCAN_NEVER_JAR.stream() + .anyMatch(jarName::contains)) { return Set.of(); } String key = cachePackageProperties(path, rootDirResource, rootDirUrl); @@ -209,7 +210,7 @@ protected Set doFindAllClassPathResources(String path) result.removeIf(res -> { try { String jarName = resolveJarName(res.getURI()); - if (DEFAULT_SCAN_NEVER_JAR.stream() + if (jarName != null && DEFAULT_SCAN_NEVER_JAR.stream() .anyMatch(jarName::contains)) { return true; } @@ -238,7 +239,7 @@ private String resolveJarName(URI resourceURI) { } return "/" + jarName; } - return resourcePath; + return null; } private String cachePackageProperties(String path, Resource rootDirResource, From b29e42c9079a2c7420644261a9bcc741acd8b5f8 Mon Sep 17 00:00:00 2001 From: Tomi Virtanen Date: Thu, 23 May 2024 16:32:17 +0300 Subject: [PATCH 5/7] chore: improved jar name pattern matching Added more default blocked patterns. Added support for /META-INF/VAADIN/blocked-jars.list to override default blocked jar list. --- .../META-INF/VAADIN/blocked-jars.list | 1 + .../spring/test/filtering/ClassScannerIT.java | 2 +- .../spring/io/FilterableResourceResolver.java | 143 +++++++++++++++--- 3 files changed, 124 insertions(+), 22 deletions(-) create mode 100644 flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/main/resources/META-INF/VAADIN/blocked-jars.list diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/main/resources/META-INF/VAADIN/blocked-jars.list b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/main/resources/META-INF/VAADIN/blocked-jars.list new file mode 100644 index 00000000000..6b5ddfba041 --- /dev/null +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/main/resources/META-INF/VAADIN/blocked-jars.list @@ -0,0 +1 @@ +spring-test-excluded-*.*.jar \ No newline at end of file diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java index 61d898b228a..d4eb4772ec0 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/src/test/java/com/vaadin/flow/spring/test/filtering/ClassScannerIT.java @@ -78,7 +78,7 @@ public void libExcludedModule_withExcludedJar() { public void defaultJarExclusion_externalJarExcluded() { open(); // External Jar "spring-test-excluded.jar" is excluded by default by - // FilterableResourceProvider#DEFAULT_SCAN_NEVER_JAR. + // custom META-INF/VAADIN/blocked.jars.list. assertClassBlocked(ExcludedExternalRoute.class.getSimpleName()); } diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java index a5fc0c1267a..c3cf7150895 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java @@ -22,6 +22,8 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; @@ -35,6 +37,7 @@ import java.util.stream.Stream; import java.util.zip.ZipException; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; @@ -62,6 +65,7 @@ public class FilterableResourceResolver private static final String JAR_KEY = ".jar!/"; private static final String JAR_EXTENSION = ".jar"; private static final String PACKAGE_PROPERTIES_PATH = "META-INF/VAADIN/package.properties"; + private static final String BLOCKED_JARS_LIST_PATH = "/META-INF/VAADIN/blocked-jars.list"; /** * The property key for allowed packages. @@ -76,27 +80,44 @@ public class FilterableResourceResolver */ public static final String BLOCKED_JAR_PROPERTY = "vaadin.blocked-jar"; + /** + * Jar filename patterns for excluded jars. + */ private static final List DEFAULT_SCAN_NEVER_JAR = Stream.of( "antlr", "logback-classic", "logback-classic-core", - "commons-codec-", "commons-fileupload", "commons-io", - "commons-logging", "commons-exec", "commons-lang", "jackson-", - "atmosphere-runtime", "byte-buddy", "commons-compress", - "aspectjweaver", "hibernate-core", "hibernate-commons", - "hibernate-validator", "jboss-logging", "selenium-", "slf4j", - "/spring-", "snakeyaml", - - "javax.", "jakarta.", "kotlin-", - - "gwt-elemental", "javassist", "javaparser-core", - "javaparser-symbol", "oshi-core", "micrometer-", "nimbus-jose-jwt", - "jooq.", "jooq-", "directory-watcher", "classgraph.", "jsoup.", - "throw-if-servlet3", "ph-css", "ph-commons", "gentyref.", + "commons-codec-*.*.*.jar", "commons-fileupload", + "commons-io-*.*.*.jar", "commons-logging", "commons-exec", + "commons-lang*-*.*.*.jar", "jackson-databind-", "jackson-core-", + "jackson-datatype-", "jackson-annotations-", "jackson-module-", + "jackson-datatype-", "atmosphere-runtime", "byte-buddy", + "commons-compress", "aspectjweaver", "hibernate-core", + "hibernate-commons", "hibernate-validator", "jboss-logging", + "selenium-", "slf4j-simple-", "slf4j-api-", "spring-*.*.*.jar", + "spring-webmvc-*.*.*.jar", "spring-aop-*.*.*.jar", + "spring-beans-*.*.*.jar", "spring-context-*.*.*.jar", + "spring-core-*.*.*.jar", "spring-jcl-*.*.*.jar", + "spring-expression-*.*.*.jar", "spring-websocket-*.*.*.jar", + "spring-web-*.*.*.jar", "snakeyaml-*.*.jar", "javax.", "jakarta.", + "kotlin-reflect-", "kotlin-stdlib-", "gwt-elemental", + "javassist-*.*.*-*.jar", "javaparser-core-*.*.*.jar", + "javaparser-symbol", "oshi-core-*.*.*.jar", + "micrometer-observation-*.*.*.jar", "micrometer-commons-*.*.*.jar", + "nimbus-jose-jwt", "jooq-*.*.*.jar", "jooq-*-*.*.*.jar", + "directory-watcher-*.*.*.jar", "classgraph", "jsoup-*.*.*.jar", + "throw-if-servlet3", "ph-css-*.*.*.jar", "ph-commons-*.*.*.jar", + "gentyref-*.*.*.vaadin1.jar", "asm-*.*.jar", "asm-commons-*.*.jar", + "asm-tree-*.*.jar", "jetty-", "tomcat-", "classmate-*.*.*.jar", + "reflections-*.*.*.jar", "jna-*.*.*.jar", "jna-platform-*.*.*.jar", + "jcip-annotations-*.*.*.jar", "activation-*.*.*.jar", + "httpcore5-*.*.*.jar", "httpcore5-h2-*.*.*.jar", - "/hilla-engine-core-", "/hilla-engine-runtime-", - "/hilla-parser-jvm-", "/hilla-runtime-plugin-").toList(); + "hilla-engine-core-", "hilla-engine-runtime-", "hilla-parser-jvm-", + "hilla-runtime-plugin-").toList(); private final Map propertiesCache = new HashMap<>(); + private List customBlockedJarsList; + private record PackageInfo(Set allowedPackages, Set blockedPackages, boolean blockedJar) implements Serializable { } @@ -109,6 +130,7 @@ private record PackageInfo(Set allowedPackages, */ public FilterableResourceResolver(ResourceLoader resourceLoader) { super(resourceLoader); + initCustomBlockedJars(); } private static Logger getLogger() { @@ -139,6 +161,11 @@ protected boolean isJar(String path) { return path.lastIndexOf(JAR_KEY) != -1; } + private List getDefaultBlockedJarList() { + return customBlockedJarsList != null ? customBlockedJarsList + : DEFAULT_SCAN_NEVER_JAR; + } + private Resource doResolveRootDirResource(Resource original) throws IOException { String rootDirPath = original.getURI().getPath(); @@ -177,8 +204,8 @@ protected Set doFindPathMatchingJarResources( throws IOException { String path = rootDirResource.getURI().toString(); String jarName = resolveJarName(rootDirResource.getURI()); - if (jarName != null && DEFAULT_SCAN_NEVER_JAR.stream() - .anyMatch(jarName::contains)) { + if (jarName != null && getDefaultBlockedJarList().stream() + .anyMatch(pattern -> patternMatch(jarName, pattern))) { return Set.of(); } String key = cachePackageProperties(path, rootDirResource, rootDirUrl); @@ -210,8 +237,8 @@ protected Set doFindAllClassPathResources(String path) result.removeIf(res -> { try { String jarName = resolveJarName(res.getURI()); - if (jarName != null && DEFAULT_SCAN_NEVER_JAR.stream() - .anyMatch(jarName::contains)) { + if (jarName != null && getDefaultBlockedJarList().stream() + .anyMatch(pattern -> patternMatch(jarName, pattern))) { return true; } } catch (IOException e) { @@ -224,6 +251,58 @@ protected Set doFindAllClassPathResources(String path) return result; } + /** + * Matches given jarName with the pattern. if pattern doesn't contain '*', + * then match is based on startsWith(pattern). If pattern has one or more + * '*', pattern is split into array and each part is matched with startsWith + * for each part in the given jarName. '*' match any character 0-n times + * except '-' or content of the part following `*`.
+ *
+ * For example, "spring-*.*.*.jar" pattern matches to "spring-1.0.0.jar", + * "spring-abc.1.0.jar" but NOT "spring-abc-1.0.0.jar" or + * "spring-abc.1.0.0.jar".
+ *
+ * String operations are handled from left to right, where content of `*` is + * substring starting from end of the previous String to beginning of the + * first occurrence of the next part in the parts array.
+ *
+ * "spring-*-*.*.*.jar" pattern matches to "spring-foo-1.0.0.jar", + * "spring-foo-bar.1.0.jar" but NOT "spring-foo-bar-1.0.0.jar" or + * "spring-1.0.0.jar".
+ *
+ * "spring-*_*.*.jar" match "spring-abc.1_0.0.jar" due to the order '*' is a + * substring part by part from left to right.
+ *
+ * Method is not using much regex to get optimal performance. + */ + private boolean patternMatch(String jarName, String pattern) { + if (pattern.contains("*")) { + var parts = pattern.split("\\*"); + String remainingName = jarName; + int nextPartIndex = 0; + int partIndex = 0; + for (String part : parts) { + if (!remainingName.startsWith(part)) { + return false; + } + if ((partIndex + 1) >= parts.length) { + return true; + } + remainingName = remainingName.substring(part.length()); + nextPartIndex = remainingName.indexOf(parts[partIndex + 1]); + if (nextPartIndex == -1) { + return false; + } + if (remainingName.substring(0, nextPartIndex).contains("-")) { + return false; + } + remainingName = remainingName.substring(nextPartIndex); + partIndex++; + } + } + return jarName.startsWith(pattern); + } + private String resolveJarName(URI resourceURI) { String resourcePath = resourceURI.getPath(); if (resourcePath == null) { @@ -235,9 +314,9 @@ private String resolveJarName(URI resourceURI) { index + JAR_EXTENSION.length()); index = jarName.lastIndexOf("/"); if (index > -1) { - return "/" + jarName.substring(index + 1); + return jarName.substring(index + 1); } - return "/" + jarName; + return jarName; } return null; } @@ -457,4 +536,26 @@ private PackageInfo createPackageInfo(Properties properties) { properties.getProperty(BLOCKED_JAR_PROPERTY, "false")); return new PackageInfo(allowedPackages, blockedPackages, blockedJar); } + + private void initCustomBlockedJars() { + customBlockedJarsList = null; + URL url = getClass().getResource(BLOCKED_JARS_LIST_PATH); + if (url == null) { + return; + } + try { + String content = IOUtils.toString(url, StandardCharsets.UTF_8); + if (content != null) { + if (content.isBlank()) { + customBlockedJarsList = Collections.emptyList(); + } else { + customBlockedJarsList = Arrays.asList(content.split("\\R")); + } + } + } catch (IOException e) { + getLogger().error( + "Failed to read {}. Falling back to default list of blocked jars.", + BLOCKED_JARS_LIST_PATH, e); + } + } } From 1d2b180a41d697e0a35c5fd29f67a70b37189fb4 Mon Sep 17 00:00:00 2001 From: Tomi Virtanen Date: Fri, 24 May 2024 10:21:58 +0300 Subject: [PATCH 6/7] chore: refactored blocked jars list --- .../spring/io/FilterableResourceResolver.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java index c3cf7150895..1531768a582 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java @@ -116,7 +116,7 @@ public class FilterableResourceResolver private final Map propertiesCache = new HashMap<>(); - private List customBlockedJarsList; + private List blockedJarsList; private record PackageInfo(Set allowedPackages, Set blockedPackages, boolean blockedJar) implements Serializable { @@ -130,7 +130,7 @@ private record PackageInfo(Set allowedPackages, */ public FilterableResourceResolver(ResourceLoader resourceLoader) { super(resourceLoader); - initCustomBlockedJars(); + initBlockedJars(); } private static Logger getLogger() { @@ -161,11 +161,6 @@ protected boolean isJar(String path) { return path.lastIndexOf(JAR_KEY) != -1; } - private List getDefaultBlockedJarList() { - return customBlockedJarsList != null ? customBlockedJarsList - : DEFAULT_SCAN_NEVER_JAR; - } - private Resource doResolveRootDirResource(Resource original) throws IOException { String rootDirPath = original.getURI().getPath(); @@ -204,7 +199,7 @@ protected Set doFindPathMatchingJarResources( throws IOException { String path = rootDirResource.getURI().toString(); String jarName = resolveJarName(rootDirResource.getURI()); - if (jarName != null && getDefaultBlockedJarList().stream() + if (jarName != null && blockedJarsList.stream() .anyMatch(pattern -> patternMatch(jarName, pattern))) { return Set.of(); } @@ -237,7 +232,7 @@ protected Set doFindAllClassPathResources(String path) result.removeIf(res -> { try { String jarName = resolveJarName(res.getURI()); - if (jarName != null && getDefaultBlockedJarList().stream() + if (jarName != null && blockedJarsList.stream() .anyMatch(pattern -> patternMatch(jarName, pattern))) { return true; } @@ -537,8 +532,8 @@ private PackageInfo createPackageInfo(Properties properties) { return new PackageInfo(allowedPackages, blockedPackages, blockedJar); } - private void initCustomBlockedJars() { - customBlockedJarsList = null; + private void initBlockedJars() { + blockedJarsList = DEFAULT_SCAN_NEVER_JAR; URL url = getClass().getResource(BLOCKED_JARS_LIST_PATH); if (url == null) { return; @@ -547,9 +542,9 @@ private void initCustomBlockedJars() { String content = IOUtils.toString(url, StandardCharsets.UTF_8); if (content != null) { if (content.isBlank()) { - customBlockedJarsList = Collections.emptyList(); + blockedJarsList = Collections.emptyList(); } else { - customBlockedJarsList = Arrays.asList(content.split("\\R")); + blockedJarsList = Arrays.asList(content.split("\\R")); } } } catch (IOException e) { From 9b893389659c142828e9399667ddb578b7baf147 Mon Sep 17 00:00:00 2001 From: Tomi Virtanen Date: Fri, 24 May 2024 11:48:19 +0300 Subject: [PATCH 7/7] test: add unit tests in FilterableResourceResolver --- .../spring/io/FilterableResourceResolver.java | 15 ++-- .../io/FilterableResourceResolverTest.java | 89 +++++++++++++++++++ 2 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 vaadin-spring/src/test/java/com/vaadin/flow/spring/io/FilterableResourceResolverTest.java diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java index 1531768a582..55e1f9c9689 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/io/FilterableResourceResolver.java @@ -200,7 +200,7 @@ protected Set doFindPathMatchingJarResources( String path = rootDirResource.getURI().toString(); String jarName = resolveJarName(rootDirResource.getURI()); if (jarName != null && blockedJarsList.stream() - .anyMatch(pattern -> patternMatch(jarName, pattern))) { + .anyMatch(pattern -> jarNamePatternMatch(jarName, pattern))) { return Set.of(); } String key = cachePackageProperties(path, rootDirResource, rootDirUrl); @@ -232,8 +232,8 @@ protected Set doFindAllClassPathResources(String path) result.removeIf(res -> { try { String jarName = resolveJarName(res.getURI()); - if (jarName != null && blockedJarsList.stream() - .anyMatch(pattern -> patternMatch(jarName, pattern))) { + if (jarName != null && blockedJarsList.stream().anyMatch( + pattern -> jarNamePatternMatch(jarName, pattern))) { return true; } } catch (IOException e) { @@ -254,8 +254,8 @@ protected Set doFindAllClassPathResources(String path) * except '-' or content of the part following `*`.
*
* For example, "spring-*.*.*.jar" pattern matches to "spring-1.0.0.jar", - * "spring-abc.1.0.jar" but NOT "spring-abc-1.0.0.jar" or - * "spring-abc.1.0.0.jar".
+ * "spring-abc.1.0.jar", "spring-abc.1.0.0.jar" but NOT + * "spring-abc-1.0.0.jar" or "spring-1.0.jar".
*
* String operations are handled from left to right, where content of `*` is * substring starting from end of the previous String to beginning of the @@ -270,8 +270,11 @@ protected Set doFindAllClassPathResources(String path) *
* Method is not using much regex to get optimal performance. */ - private boolean patternMatch(String jarName, String pattern) { + boolean jarNamePatternMatch(String jarName, String pattern) { if (pattern.contains("*")) { + if (pattern.equals("*")) { + return true; + } var parts = pattern.split("\\*"); String remainingName = jarName; int nextPartIndex = 0; diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/io/FilterableResourceResolverTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/io/FilterableResourceResolverTest.java new file mode 100644 index 00000000000..1e42044d95c --- /dev/null +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/io/FilterableResourceResolverTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed 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 com.vaadin.flow.spring.io; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +public class FilterableResourceResolverTest { + + private FilterableResourceResolver filterableResourceResolver; + + @Before + public void setup() { + filterableResourceResolver = new FilterableResourceResolver( + Mockito.mock(FilterableResourceResolver.class)); + } + + @Test + public void jarNamePatternMatch_test() { + assertTrue(filterableResourceResolver + .jarNamePatternMatch("spring-1.0.0.jar", "")); + assertTrue(filterableResourceResolver + .jarNamePatternMatch("spring-1.0.0.jar", "*")); + assertTrue(filterableResourceResolver + .jarNamePatternMatch("spring-1.0.0.jar", "*-*")); + assertFalse( + "'*.*' is not matching 'spring-1.0.0.jar' due to Part[0] 'spring-1' contains '-'.", + filterableResourceResolver + .jarNamePatternMatch("spring-1.0.0.jar", "*.*")); + assertTrue(filterableResourceResolver + .jarNamePatternMatch("spring-1.0.0.jar", "spring")); + assertFalse(filterableResourceResolver + .jarNamePatternMatch("spring-1.0.0.jar", "foo")); + assertFalse(filterableResourceResolver + .jarNamePatternMatch("foo-1.0.0.jar", "spring")); + assertFalse(filterableResourceResolver + .jarNamePatternMatch("vaadin-spring-1.0.0.jar", "spring")); + + assertTrue(filterableResourceResolver + .jarNamePatternMatch("spring-1.0.0.jar", "spring-*")); + assertFalse(filterableResourceResolver + .jarNamePatternMatch("spring.1.0.0.jar", "spring-*")); + assertFalse(filterableResourceResolver + .jarNamePatternMatch("spring-1.0.0.jar", "spring-*-*")); + assertFalse(filterableResourceResolver + .jarNamePatternMatch("foo-1.0.0.jar", "spring-*")); + + assertTrue(filterableResourceResolver + .jarNamePatternMatch("spring-1.0.0.jar", "spring-*.*.*.jar")); + assertTrue(filterableResourceResolver + .jarNamePatternMatch("spring-abc.1.0.jar", "spring-*.*.*.jar")); + assertTrue(filterableResourceResolver.jarNamePatternMatch( + "spring-abc.1.0.0.jar", "spring-*.*.*.jar")); + assertFalse(filterableResourceResolver.jarNamePatternMatch( + "spring-abc-1.0.0.jar", "spring-*.*.*.jar")); + assertFalse(filterableResourceResolver.jarNamePatternMatch( + "spring.abc.1.0.0.jar", "spring-*.*.*.jar")); + assertFalse(filterableResourceResolver + .jarNamePatternMatch("spring-1.0.jar", "spring-*.*.*.jar")); + + assertTrue(filterableResourceResolver.jarNamePatternMatch( + "spring-foo-1.0.0.jar", "spring-*-*.*.*.jar")); + assertTrue(filterableResourceResolver.jarNamePatternMatch( + "spring-foo-bar.1.0.jar", "spring-*-*.*.*.jar")); + assertFalse(filterableResourceResolver.jarNamePatternMatch( + "spring-foo-bar-1.0.0.jar", "spring-*-*.*.*.jar")); + assertFalse(filterableResourceResolver + .jarNamePatternMatch("spring-1.0.0.jar", "spring-*-*.*.*.jar")); + } + +}