diff --git a/pom.xml b/pom.xml index a90aa22..cd39af1 100644 --- a/pom.xml +++ b/pom.xml @@ -97,6 +97,12 @@ 3.1.1 test + + org.webjars.bower + js-base64 + 2.3.2 + test + diff --git a/src/main/java/org/webjars/WebJarVersionLocator.java b/src/main/java/org/webjars/WebJarVersionLocator.java index 6cd6803..dcc4e3c 100644 --- a/src/main/java/org/webjars/WebJarVersionLocator.java +++ b/src/main/java/org/webjars/WebJarVersionLocator.java @@ -5,6 +5,9 @@ import java.io.IOException; import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; @@ -25,6 +28,9 @@ public class WebJarVersionLocator { private static final String NPM = "org.webjars.npm/"; private static final String PLAIN = "org.webjars/"; private static final String POM_PROPERTIES = "/pom.properties"; + private static final String LOCATOR_PROPERTIES = "META-INF/resources/webjars-locator.properties"; + + private static final String CACHE_KEY_PREFIX = "version-"; private static final ClassLoader LOADER = WebJarVersionLocator.class.getClassLoader(); @@ -32,10 +38,12 @@ public class WebJarVersionLocator { public WebJarVersionLocator() { this.cache = new WebJarCacheDefault(new ConcurrentHashMap<>()); + readLocatorProperties(); } WebJarVersionLocator(WebJarCache cache) { this.cache = cache; + readLocatorProperties(); } /** @@ -83,7 +91,7 @@ public String path(final String webJarName, final String exactPath) { */ @Nullable public String version(final String webJarName) { - final String cacheKey = "version-" + webJarName; + final String cacheKey = CACHE_KEY_PREFIX + webJarName; final Optional optionalVersion = cache.computeIfAbsent(cacheKey, (key) -> { InputStream resource = LOADER.getResourceAsStream(PROPERTIES_ROOT + NPM + webJarName + POM_PROPERTIES); if (resource == null) { @@ -126,6 +134,36 @@ public String version(final String webJarName) { return optionalVersion.orElse(null); } + private void readLocatorProperties() { + try { + Enumeration resources = LOADER.getResources(LOCATOR_PROPERTIES); + while (resources.hasMoreElements()) { + URL resourceUrl = resources.nextElement(); + try (InputStream resource = resourceUrl.openStream()) { + Properties properties = new Properties(); + properties.load(resource); + for (Map.Entry entry : properties.entrySet()) { + String webJarName = entry.getKey().toString(); + if (!webJarName.endsWith(".version")) { + // ".version" suffix is required + continue; + } + + webJarName = webJarName.substring(0, webJarName.lastIndexOf(".version")); + + String version = entry.getValue().toString(); + if (hasResourcePath(webJarName, version)) { + // Only add configured versions if their path exists + cache.computeIfAbsent(CACHE_KEY_PREFIX + webJarName, x -> Optional.of(version)); + } + } + } + } + } catch (IOException e) { + throw new RuntimeException("unable to load locator properties", e); + } + } + private boolean hasResourcePath(final String webJarName, final String path) { return LOADER.getResource(WEBJARS_PATH_PREFIX + "/" + webJarName + "/" + path) != null; } diff --git a/src/main/resources/META-INF/native-image/org.webjars/webjars-locator-lite/resource-config.json b/src/main/resources/META-INF/native-image/org.webjars/webjars-locator-lite/resource-config.json new file mode 100644 index 0000000..0b6bf0a --- /dev/null +++ b/src/main/resources/META-INF/native-image/org.webjars/webjars-locator-lite/resource-config.json @@ -0,0 +1,30 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\QMETA-INF/resources/webjars-locator.properties\\E", + "condition": { + "typeReachable": "org.webjars.WebJarVersionLocator" + } + }, + { + "pattern": "\\QMETA-INF\/resources\/webjars\/\\E.*", + "condition": { + "typeReachable": "org.webjars.WebJarVersionLocator" + } + }, + { + "pattern": "\\QMETA-INF/maven/org.webjars/\\E.*\\Q/pom.properties\\E", + "condition": { + "typeReachable": "org.webjars.WebJarVersionLocator" + } + }, + { + "pattern": "\\QMETA-INF/maven/org.webjars.npm/\\E.*\\Q/pom.properties\\E", + "condition": { + "typeReachable": "org.webjars.WebJarVersionLocator" + } + } + ] + } +} diff --git a/src/test/java/org/webjars/WebJarVersionLocatorTest.java b/src/test/java/org/webjars/WebJarVersionLocatorTest.java index 0c8c3ea..eadc65c 100644 --- a/src/test/java/org/webjars/WebJarVersionLocatorTest.java +++ b/src/test/java/org/webjars/WebJarVersionLocatorTest.java @@ -8,6 +8,7 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -23,6 +24,21 @@ void should_get_a_webjar_version() { assertEquals("3.1.1", new WebJarVersionLocator().version("bootswatch-yeti")); } + @Test + void should_find_good_custom_webjar_version() { + assertEquals("3.2.1", new WebJarVersionLocator().version("goodwebjar")); + } + + @Test + void should_not_find_bad_custom_webjar_version() { + assertNull(new WebJarVersionLocator().version("badwebjar")); + } + + @Test + void should_find_bower_webjar_version() { + assertEquals("2.3.2", new WebJarVersionLocator().version("js-base64")); + } + @Test void webjar_version_doesnt_match_path() { assertEquals("3.1.1", new WebJarVersionLocator().version("bootstrap")); @@ -45,6 +61,7 @@ void full_path_exists_version_supplied() { @Test void cache_is_populated_on_lookup() { + AtomicBoolean shouldInspect = new AtomicBoolean(false); AtomicInteger numLookups = new AtomicInteger(0); @NullMarked @@ -54,7 +71,9 @@ class InspectableCache implements WebJarCache { @Override public Optional computeIfAbsent(String key, Function> function) { Function> inspectableFunction = function.andThen((value) -> { - numLookups.incrementAndGet(); + if(shouldInspect.get()) { + numLookups.incrementAndGet(); + } return value; }); return cache.computeIfAbsent(key, inspectableFunction); @@ -62,6 +81,8 @@ public Optional computeIfAbsent(String key, Function