Skip to content

Commit

Permalink
add cache
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesward committed Mar 22, 2024
1 parent 378fa1e commit 74d550c
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 44 deletions.
17 changes: 17 additions & 0 deletions src/main/java/org/webjars/WebJarCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.webjars;


import org.jspecify.annotations.Nullable;

/**
* WebJar Locator Cache Interface
* Since classpath resources are essentially immutable, the WebJarsCache does not have the concept of expiry.
* Cache keys and values are Strings because that is all that is needed.
*/
public interface WebJarCache {

public @Nullable String get(final String key);

public void put(final String key, final String value);

}
25 changes: 25 additions & 0 deletions src/main/java/org/webjars/WebJarCacheDefault.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.webjars;

import org.jspecify.annotations.Nullable;

import java.util.concurrent.ConcurrentHashMap;

public class WebJarCacheDefault implements WebJarCache {

final ConcurrentHashMap<String, String> cache;

public WebJarCacheDefault(ConcurrentHashMap<String, String> cache) {
this.cache = cache;
}

@Override
public @Nullable String get(String key) {
return cache.get(key);
}

@Override
public void put(String key, String value) {
cache.put(key, value);
}

}
121 changes: 82 additions & 39 deletions src/main/java/org/webjars/WebJarVersionLocator.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;


/**
Expand All @@ -17,70 +18,112 @@ public class WebJarVersionLocator {
/**
* The path to where webjar resources live.
*/
public static final String WEBJARS_PATH_PREFIX = "META-INF/resources/webjars";
public final String WEBJARS_PATH_PREFIX = "META-INF/resources/webjars";

private static final String PROPERTIES_ROOT = "META-INF/maven/";
private static final String NPM = "org.webjars.npm/";
private static final String PLAIN = "org.webjars/";
private static final String POM_PROPERTIES = "/pom.properties";
private final String PROPERTIES_ROOT = "META-INF/maven/";
private final String NPM = "org.webjars.npm/";
private final String PLAIN = "org.webjars/";
private final String POM_PROPERTIES = "/pom.properties";

private static final ClassLoader LOADER = WebJarVersionLocator.class.getClassLoader();
private final ClassLoader LOADER = WebJarVersionLocator.class.getClassLoader();

private final WebJarCache cache;

@Nullable
public static String fullPath(final String webJarName, final String exactPath) {
String version = webJarVersion(webJarName);
String fullPath = String.format("%s/%s/%s", WEBJARS_PATH_PREFIX, webJarName, exactPath);
if (!isEmpty(version)) {
if (!exactPath.startsWith(version)) {
fullPath = String.format("%s/%s/%s/%s", WEBJARS_PATH_PREFIX, webJarName, version, exactPath);
}
}
public WebJarVersionLocator() {
this.cache = new WebJarCacheDefault(new ConcurrentHashMap<>());
}

public WebJarVersionLocator(WebJarCache cache) {
this.cache = cache;
}

if (LOADER.getResource(fullPath) != null) {
return fullPath;
public static class DEFAULT {
private static final WebJarVersionLocator webJarVersionLocator = new WebJarVersionLocator();
public static final String WEBJARS_PATH_PREFIX = webJarVersionLocator.WEBJARS_PATH_PREFIX;

@Nullable
public static String fullPath(final String webJarName, final String exactPath) {
return webJarVersionLocator.fullPath(webJarName, exactPath);
}

return null;
@Nullable
public static String webJarVersion(final String webJarName) {
return webJarVersionLocator.webJarVersion(webJarName);
}
}

@Nullable
public static String webJarVersion(final String webJarName) {
InputStream resource = LOADER.getResourceAsStream(PROPERTIES_ROOT + NPM + webJarName + POM_PROPERTIES);
if (resource == null) {
resource = LOADER.getResourceAsStream(PROPERTIES_ROOT + PLAIN + webJarName + POM_PROPERTIES);
}
public String fullPath(final String webJarName, final String exactPath) {
final String cacheKey = "fullpath-" + webJarName + "-" + exactPath;
final String maybeCached = cache.get(cacheKey);
if (maybeCached == null) {
final String version = webJarVersion(webJarName);
String fullPath = String.format("%s/%s/%s", WEBJARS_PATH_PREFIX, webJarName, exactPath);
if (!isEmpty(version)) {
if (!exactPath.startsWith(version)) {
fullPath = String.format("%s/%s/%s/%s", WEBJARS_PATH_PREFIX, webJarName, version, exactPath);
}
}

// Webjars also uses org.webjars.bower as a group id, but the resource paths are not as standard (and not so many people use those)
if (resource != null) {
Properties properties = new Properties();
try {
properties.load(resource);
} catch (IOException ignored) {
if (LOADER.getResource(fullPath) != null) {
cache.put(cacheKey, fullPath);
return fullPath;
}

return null;
}
else {
return maybeCached;
}
}

@Nullable
public String webJarVersion(final String webJarName) {
final String cacheKey = "version-" + webJarName;
final String maybeCached = cache.get(cacheKey);
if (maybeCached == null) {
InputStream resource = LOADER.getResourceAsStream(PROPERTIES_ROOT + NPM + webJarName + POM_PROPERTIES);
if (resource == null) {
resource = LOADER.getResourceAsStream(PROPERTIES_ROOT + PLAIN + webJarName + POM_PROPERTIES);
}
String version = properties.getProperty("version");
// Sometimes a webjar version is not the same as the Maven artifact version
if (version != null) {
if (hasResourcePath(webJarName, version)) {
return version;

// Webjars also uses org.webjars.bower as a group id, but the resource paths are not as standard (and not so many people use those)
if (resource != null) {
final Properties properties = new Properties();
try {
properties.load(resource);
} catch (IOException ignored) {

}
if (version.contains("-")) {
version = version.substring(0, version.indexOf("-"));
String version = properties.getProperty("version");
// Sometimes a webjar version is not the same as the Maven artifact version
if (version != null) {
if (hasResourcePath(webJarName, version)) {
cache.put(cacheKey, version);
return version;
}
if (version.contains("-")) {
version = version.substring(0, version.indexOf("-"));
if (hasResourcePath(webJarName, version)) {
cache.put(cacheKey, version);
return version;
}
}
}
}

return null;
}
else {
return maybeCached;
}
return null;
}

private static boolean hasResourcePath(final String webJarName, final String path) {
private boolean hasResourcePath(final String webJarName, final String path) {
return LOADER.getResource(WEBJARS_PATH_PREFIX + "/" + webJarName + "/" + path) != null;
}

private static boolean isEmpty(final String str) {
private boolean isEmpty(final String str) {
return str == null || str.trim().isEmpty();
}

Expand Down
30 changes: 25 additions & 5 deletions src/test/java/org/webjars/WebJarVersionLocatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,50 @@

import org.junit.Test;

import java.util.concurrent.ConcurrentHashMap;

public class WebJarVersionLocatorTest {

@Test
public void invalid_webjar_path_should_return_null() {
assertNull(WebJarVersionLocator.webJarVersion("foo"));
assertNull(WebJarVersionLocator.DEFAULT.webJarVersion("foo"));
}

@Test
public void should_get_a_webjar_version() {
assertEquals("3.1.1", WebJarVersionLocator.webJarVersion("bootswatch-yeti"));
assertEquals("3.1.1", WebJarVersionLocator.DEFAULT.webJarVersion("bootswatch-yeti"));
}

@Test
public void webjar_version_doesnt_match_path() {
assertEquals("3.1.1", WebJarVersionLocator.webJarVersion("bootstrap"));
assertEquals("3.1.1", WebJarVersionLocator.DEFAULT.webJarVersion("bootstrap"));
}

@Test
public void full_path_exists_version_not_supplied() {
assertEquals(WebJarVersionLocator.WEBJARS_PATH_PREFIX + "/bootstrap/3.1.1/js/bootstrap.js", WebJarVersionLocator.fullPath("bootstrap", "js/bootstrap.js"));
assertEquals(WebJarVersionLocator.DEFAULT.WEBJARS_PATH_PREFIX + "/bootstrap/3.1.1/js/bootstrap.js", WebJarVersionLocator.DEFAULT.fullPath("bootstrap", "js/bootstrap.js"));
}

@Test
public void full_path_exists_version_supplied() {
assertEquals(WebJarVersionLocator.WEBJARS_PATH_PREFIX + "/bootstrap/3.1.1/js/bootstrap.js", WebJarVersionLocator.fullPath("bootstrap", "3.1.1/js/bootstrap.js"));
assertEquals(WebJarVersionLocator.DEFAULT.WEBJARS_PATH_PREFIX + "/bootstrap/3.1.1/js/bootstrap.js", WebJarVersionLocator.DEFAULT.fullPath("bootstrap", "3.1.1/js/bootstrap.js"));
}

@Test
public void cache_is_populated_on_lookup() {
final ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
final WebJarVersionLocator webJarVersionLocator = new WebJarVersionLocator(new WebJarCacheDefault(cache));

assertEquals("3.1.1", webJarVersionLocator.webJarVersion("bootstrap"));
assertEquals(1, cache.size());
// should hit the cache and produce the same value
// todo: test that it was actually a cache hit
assertEquals("3.1.1", webJarVersionLocator.webJarVersion("bootstrap"));

assertEquals(webJarVersionLocator.WEBJARS_PATH_PREFIX + "/bootstrap/3.1.1/js/bootstrap.js", webJarVersionLocator.fullPath("bootstrap", "js/bootstrap.js"));
assertEquals(2, cache.size());
// should hit the cache and produce the same value
// todo: test that it was actually a cache hit
assertEquals(webJarVersionLocator.WEBJARS_PATH_PREFIX + "/bootstrap/3.1.1/js/bootstrap.js", webJarVersionLocator.fullPath("bootstrap", "js/bootstrap.js"));
}
}

0 comments on commit 74d550c

Please sign in to comment.