From f27ce69119150aa46b23dc9c2721523b1a949ddb Mon Sep 17 00:00:00 2001 From: Michael Haitz Date: Fri, 7 Feb 2014 08:03:13 +0100 Subject: [PATCH] improve initialization --- .../wicket/webjars/WebJarAssetLocator.java | 259 ------------------ .../wicket/webjars/WicketWebjars.java | 25 +- .../wicket/webjars/collectors/AssetsMap.java | 165 +++++++++++ .../webjars/collectors/IAssetProvider.java | 25 ++ .../collectors/IRecentVersionProvider.java | 15 + .../webjars/settings/IWebjarsSettings.java | 11 + .../webjars/settings/WebjarsSettings.java | 29 +- .../wicket/webjars/util/Helper.java | 56 ++++ .../webjars/util/IFullPathProvider.java | 16 ++ .../webjars/util/RecentVersionCallable.java | 8 +- .../webjars/util/WebJarAssetLocator.java | 76 +++++ .../wicket/webjars/util/WebjarsVersion.java | 22 +- .../util/file/WebjarsResourceFinder.java | 17 +- 13 files changed, 433 insertions(+), 291 deletions(-) delete mode 100644 library/src/main/java/de/agilecoders/wicket/webjars/WebJarAssetLocator.java create mode 100644 library/src/main/java/de/agilecoders/wicket/webjars/collectors/AssetsMap.java create mode 100644 library/src/main/java/de/agilecoders/wicket/webjars/collectors/IAssetProvider.java create mode 100644 library/src/main/java/de/agilecoders/wicket/webjars/collectors/IRecentVersionProvider.java create mode 100644 library/src/main/java/de/agilecoders/wicket/webjars/util/Helper.java create mode 100644 library/src/main/java/de/agilecoders/wicket/webjars/util/IFullPathProvider.java create mode 100644 library/src/main/java/de/agilecoders/wicket/webjars/util/WebJarAssetLocator.java diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/WebJarAssetLocator.java b/library/src/main/java/de/agilecoders/wicket/webjars/WebJarAssetLocator.java deleted file mode 100644 index d01b83c..0000000 --- a/library/src/main/java/de/agilecoders/wicket/webjars/WebJarAssetLocator.java +++ /dev/null @@ -1,259 +0,0 @@ -package de.agilecoders.wicket.webjars; - -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import de.agilecoders.wicket.webjars.collectors.AssetPathCollector; -import de.agilecoders.wicket.webjars.settings.IWebjarsSettings; -import org.apache.wicket.util.lang.Args; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.URL; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Locate WebJar assets. The class is thread safe. - */ -public class WebJarAssetLocator { - private static final Logger LOG = LoggerFactory.getLogger(WicketWebjars.class); - - private final IWebjarsSettings settings; - private final SortedMap fullPathIndex; - private final Set collectors; - - /** - * Convenience constructor that will form a locator for all resources on the current class path. - */ - public WebJarAssetLocator(IWebjarsSettings settings) { - this(settings, null); - } - - public String recentVersionOf(String partialPath) { - partialPath = prependWebjarsPathIfMissing(partialPath); - final Matcher partialPathMatcher = settings.webjarsPathPattern().matcher(partialPath); - - if (partialPathMatcher.find() && "current".equalsIgnoreCase(partialPathMatcher.group(2))) { - final Set assets = listAssets(partialPathMatcher.group(1)); - final String fileName = partialPathMatcher.group(3); - final List versions = Lists.newArrayList(); - - for (String asset : assets) { - if (asset.endsWith(fileName)) { - final Matcher matcher = settings.webjarsPathPattern().matcher(asset); - - if (matcher.find()) { - versions.add(matcher.group(2)); - } - } - } - - if (versions.size() == 1) { - return versions.get(0); - } else if (versions.size() > 1) { - LOG.warn("more than one version of a dependency is not supported till now. webjars resource: {}; available versions: {}; using: {}", - fileName, versions, versions.get(0)); - - return versions.get(0); - } else { - LOG.debug("no version found for webjars resource: {}", partialPath); - } - } else { - LOG.trace("given webjars resource isn't a dynamic versioned one: {}", partialPath); - } - - return null; - } - - /** - * prepends the webjars path if missing - * - * @param path the file name to check - * @return file name that starts with "/webjars/" - */ - public static String prependWebjarsPathIfMissing(final String path) { - final String cleanedName = appendLeadingSlash(Args.notEmpty(path, "path")); - - if (!path.contains("/webjars/")) { - return "/webjars" + cleanedName; - } - - return path; - } - - /** - * prepends a leading slash if there is none. - * - * @param path the path - * @return path with leading slash - */ - private static String appendLeadingSlash(final String path) { - return path.charAt(0) == '/' ? path : '/' + path; - } - - /** - * Establish a locator given an index that it should use. - * - * @param fullPathIndex the index to use. - */ - public WebJarAssetLocator(final IWebjarsSettings settings, final SortedMap fullPathIndex) { - this.settings = Args.notNull(settings, "settings"); - this.collectors = Sets.newHashSet(settings.assetPathCollectors()); - - if (fullPathIndex == null) { - this.fullPathIndex = createFullPathIndex(settings.resourcePattern(), settings.classLoaders()); - } else { - this.fullPathIndex = fullPathIndex; - } - } - - private String throwNotFoundException(final String partialPath) { - throw new IllegalArgumentException(partialPath + " could not be found. Make sure you've added the corresponding WebJar and please check for typos."); - } - - private String throwMultipleMatchesException(final String partialPath) { - throw new IllegalArgumentException("Multiple matches found for " + partialPath - + ". Please provide a more specific path, for example by including a version number."); - } - - /** - * Given a distinct path within the WebJar index passed in return the full path of the resource. - * - * @param partialPath the path to return e.g. "jquery.js" or "abc/someother.js". This must be a distinct path within the index passed in. - * @return a fully qualified path to the resource. - */ - public String getFullPath(String partialPath) { - partialPath = Args.notEmpty(partialPath, "partialPath").contains("/current/") ? - recentVersionOf(partialPath) : - partialPath; - - final String reversePartialPath = reversePath(partialPath); - final SortedMap fullPathTail = fullPathIndex.tailMap(reversePartialPath); - - if (fullPathTail.size() == 0) { - throwNotFoundException(partialPath); - } - - final Iterator> fullPathTailIter = fullPathTail.entrySet().iterator(); - final Entry fullPathEntry = fullPathTailIter.next(); - if (!fullPathEntry.getKey().startsWith(reversePartialPath)) { - throwNotFoundException(partialPath); - } - final String fullPath = fullPathEntry.getValue(); - - if (fullPathTailIter.hasNext() && fullPathTailIter.next().getKey().startsWith(reversePartialPath)) { - throwMultipleMatchesException(reversePartialPath); - } - - return fullPath; - } - - public SortedMap getFullPathIndex() { - return fullPathIndex; - } - - /** - * List assets within a folder. - * - * @param folderPath the root path to the folder. Must begin with '/'. - * @return a set of folder paths that match. - */ - public Set listAssets(final String folderPath) { - final Collection allAssets = fullPathIndex.values(); - final Set assets = new HashSet(); - final String prefix = settings.webjarsPath() + appendLeadingSlash(folderPath); - - for (final String asset : allAssets) { - if (asset.startsWith(prefix)) { - assets.add(asset); - } - } - - return assets; - } - - /* - * Return all {@link URL}s defining {@value VfsAwareWebJarAssetLocator#WEBJARS_PATH_PREFIX} directory, either identifying JAR files or plain - * directories. - */ - private Set listWebjarsParentURLs(final ClassLoader[] classLoaders) { - final Set urls = new HashSet(); - final String webjarsPath = settings.webjarsPath(); - - for (final ClassLoader classLoader : classLoaders) { - try { - final Enumeration enumeration = classLoader.getResources(webjarsPath); - while (enumeration.hasMoreElements()) { - urls.add(enumeration.nextElement()); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - return urls; - } - - /* - * Return all of the resource paths filtered given an expression and a list of class loaders. - */ - private Set getAssetPaths(final Pattern filterExpr, final ClassLoader... classLoaders) { - final Set assetPaths = new HashSet(); - final Set urls = listWebjarsParentURLs(classLoaders); - - for (final URL url : urls) { - for (AssetPathCollector collector : collectors) { - if (collector.accept(url)) { - assetPaths.addAll(collector.collect(url, filterExpr)); - } - } - } - - return assetPaths; - } - - /** - * Return a map that can be used to perform index lookups of partial file paths. This index constitutes a key that is the reverse form of the path - * it relates to. Thus if a partial lookup needs to be performed from the rightmost path components then the key to access can be expressed easily - * e.g. the path "a/b" would be the map tuple "b/a" -> "a/b". If we need to look for an asset named "a" without knowing the full path then we can - * perform a partial lookup on the sorted map. - * - * @param filterExpr the regular expression to be used to filter resources that will be included in the index. - * @param classLoaders the class loaders to be considered for loading the resources from. - * @return the index. - */ - public SortedMap createFullPathIndex(final Pattern filterExpr, final ClassLoader... classLoaders) { - final Set assetPaths = getAssetPaths(filterExpr, classLoaders); - - final SortedMap assetPathIndex = new TreeMap(); - for (final String assetPath : assetPaths) { - assetPathIndex.put(reversePath(assetPath), assetPath); - } - - return assetPathIndex; - } - - /* - * Make paths like aa/bb/cc = cc/bb/aa. - */ - private static String reversePath(String assetPath) { - final String[] assetPathComponents = assetPath.split("/"); - final StringBuilder reversedAssetPath = new StringBuilder(); - for (int i = assetPathComponents.length - 1; i >= 0; --i) { - if (reversedAssetPath.length() > 0) { - reversedAssetPath.append('/'); - } - reversedAssetPath.append(assetPathComponents[i]); - } - return reversedAssetPath.toString(); - } -} diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/WicketWebjars.java b/library/src/main/java/de/agilecoders/wicket/webjars/WicketWebjars.java index b57af95..85f54a4 100644 --- a/library/src/main/java/de/agilecoders/wicket/webjars/WicketWebjars.java +++ b/library/src/main/java/de/agilecoders/wicket/webjars/WicketWebjars.java @@ -15,8 +15,6 @@ * @author miha */ public final class WicketWebjars { - private static final String PATH_SPLITTER = "/"; - private static WebjarsResourceFinder FINDER = null; /** * The {@link org.apache.wicket.MetaDataKey} used to retrieve the {@link IWebjarsSettings} from the Wicket {@link Appendable}. @@ -24,20 +22,13 @@ public final class WicketWebjars { private static final MetaDataKey WEBJARS_SETTINGS_METADATA_KEY = new MetaDataKey() { }; - /** - * @return the webjars resource finder - */ - public static WebjarsResourceFinder finder() { - return FINDER; - } - /** * installs the webjars resource finder and uses a set of default settings. * * @param app the wicket application */ public static void install(final Application app) { - install(app, new WebjarsSettings()); + install(app, null); } /** @@ -50,6 +41,10 @@ public static void install(final Application app, IWebjarsSettings settings) { final IWebjarsSettings existingSettings = settings(app); if (existingSettings == null) { + if (settings == null) { + settings = new WebjarsSettings(); + } + app.setMetaData(WEBJARS_SETTINGS_METADATA_KEY, settings); final List finders = app.getResourceSettings().getResourceFinders(); @@ -57,7 +52,6 @@ public static void install(final Application app, IWebjarsSettings settings) { if (!finders.contains(finder)) { finders.add(finder); - FINDER = finder; } } } @@ -79,7 +73,14 @@ public static IWebjarsSettings settings(final Application app) { */ public static IWebjarsSettings settings() { if (Application.exists()) { - return settings(Application.get()); + IWebjarsSettings settings = Application.get().getMetaData(WEBJARS_SETTINGS_METADATA_KEY); + + if (settings != null) { + return settings; + } else { + throw new IllegalStateException("you have to call WicketWebjars.install() before you can use an " + + "IWebjarsResourceReference or any other component."); + } } throw new IllegalStateException("there is no active application assigned to this thread."); diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/collectors/AssetsMap.java b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/AssetsMap.java new file mode 100644 index 0000000..d2af2a9 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/AssetsMap.java @@ -0,0 +1,165 @@ +package de.agilecoders.wicket.webjars.collectors; + +import com.google.common.collect.Lists; +import de.agilecoders.wicket.webjars.WicketWebjars; +import de.agilecoders.wicket.webjars.settings.IWebjarsSettings; +import de.agilecoders.wicket.webjars.util.Helper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URL; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static de.agilecoders.wicket.webjars.util.Helper.reversePath; + +/** + * asset holder map. + * + * @author miha + */ +public final class AssetsMap implements IAssetProvider, IRecentVersionProvider { + private static final Logger LOG = LoggerFactory.getLogger(WicketWebjars.class); + + private final IWebjarsSettings settings; + private final SortedMap fullPathIndex; + private final AssetPathCollector[] collectors; + private final String recentVersionPlaceHolder; + + /** + * Construct. + * + * @param settings the settings to use. + */ + public AssetsMap(IWebjarsSettings settings) { + this.settings = settings; + this.collectors = settings.assetPathCollectors(); + this.recentVersionPlaceHolder = settings.recentVersionPlaceHolder(); + this.fullPathIndex = createFullPathIndex(settings.resourcePattern(), settings.classLoaders()); + } + + @Override + public String findRecentVersionFor(String path) { + final String partialPath = Helper.prependWebjarsPathIfMissing(path); + final Matcher partialPathMatcher = settings.webjarsPathPattern().matcher(partialPath); + + if (partialPathMatcher.find() && recentVersionPlaceHolder.equalsIgnoreCase(partialPathMatcher.group(2))) { + final Set assets = listAssets(partialPathMatcher.group(1)); + final String fileName = partialPathMatcher.group(3); + final List versions = Lists.newArrayList(); + + for (String asset : assets) { + if (asset.endsWith(fileName)) { + final Matcher matcher = settings.webjarsPathPattern().matcher(asset); + + if (matcher.find()) { + versions.add(matcher.group(2)); + } + } + } + + if (versions.size() == 1) { + return versions.get(0); + } else if (versions.size() > 1) { + LOG.warn("more than one version of a dependency is not supported till now. webjars resource: {}; available versions: {}; using: {}", + fileName, versions, versions.get(0)); + + return versions.get(0); + } else { + LOG.debug("no version found for webjars resource: {}", partialPath); + } + } else { + LOG.trace("given webjars resource isn't a dynamic versioned one: {}", partialPath); + } + + return null; + } + + @Override + public SortedMap getFullPathIndex() { + return fullPathIndex; + } + + @Override + public Set listAssets(final String folderPath) { + final Collection allAssets = getFullPathIndex().values(); + final Set assets = new HashSet(); + final String prefix = settings.webjarsPath() + Helper.appendLeadingSlash(folderPath); + + for (final String asset : allAssets) { + if (asset.startsWith(prefix)) { + assets.add(asset); + } + } + + return assets; + } + + /** + * Return all {@link URL}s found in webjars directory, + * either identifying JAR files or plain directories. + */ + private Set listWebjarsParentURLs(final ClassLoader[] classLoaders) { + final Set urls = new HashSet(); + final String webjarsPath = settings.webjarsPath(); + + for (final ClassLoader classLoader : classLoaders) { + try { + final Enumeration enumeration = classLoader.getResources(webjarsPath); + while (enumeration.hasMoreElements()) { + urls.add(enumeration.nextElement()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return urls; + } + + /** + * Return all of the resource paths filtered given an expression and a list of class loaders. + */ + private Set getAssetPaths(final Pattern filterExpr, final ClassLoader... classLoaders) { + final Set assetPaths = new HashSet(); + final Set urls = listWebjarsParentURLs(classLoaders); + + for (final URL url : urls) { + for (AssetPathCollector collector : collectors) { + if (collector.accept(url)) { + assetPaths.addAll(collector.collect(url, filterExpr)); + } + } + } + + return assetPaths; + } + + /** + * Return a map that can be used to perform index lookups of partial file paths. This index constitutes a key that is the reverse form of the path + * it relates to. Thus if a partial lookup needs to be performed from the rightmost path components then the key to access can be expressed easily + * e.g. the path "a/b" would be the map tuple "b/a" -> "a/b". If we need to look for an asset named "a" without knowing the full path then we can + * perform a partial lookup on the sorted map. + * + * @param filterExpr the regular expression to be used to filter resources that will be included in the index. + * @param classLoaders the class loaders to be considered for loading the resources from. + * @return the index. + */ + private SortedMap createFullPathIndex(final Pattern filterExpr, final ClassLoader... classLoaders) { + final Set assetPaths = getAssetPaths(filterExpr, classLoaders); + + final SortedMap assetPathIndex = new TreeMap(); + for (final String assetPath : assetPaths) { + assetPathIndex.put(reversePath(assetPath), assetPath); + } + + return assetPathIndex; + } +} diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/collectors/IAssetProvider.java b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/IAssetProvider.java new file mode 100644 index 0000000..55f8a54 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/IAssetProvider.java @@ -0,0 +1,25 @@ +package de.agilecoders.wicket.webjars.collectors; + +import java.util.Set; +import java.util.SortedMap; + +/** + * base provider interface + * + * @author miha + */ +public interface IAssetProvider { + + /** + * List assets within a folder. + * + * @param folderPath the root path to the folder. Must begin with '/'. + * @return a set of folder paths that match. + */ + Set listAssets(final String folderPath); + + /** + * @return the full path index map. + */ + SortedMap getFullPathIndex(); +} diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/collectors/IRecentVersionProvider.java b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/IRecentVersionProvider.java new file mode 100644 index 0000000..4206be6 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/IRecentVersionProvider.java @@ -0,0 +1,15 @@ +package de.agilecoders.wicket.webjars.collectors; + +/** + * provides access to recent version of a webjars + * + * @author miha + */ +public interface IRecentVersionProvider { + + /** + * @param path the path to detect version for + * @return recent version + */ + String findRecentVersionFor(String path); +} diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/settings/IWebjarsSettings.java b/library/src/main/java/de/agilecoders/wicket/webjars/settings/IWebjarsSettings.java index f71a567..47e361d 100644 --- a/library/src/main/java/de/agilecoders/wicket/webjars/settings/IWebjarsSettings.java +++ b/library/src/main/java/de/agilecoders/wicket/webjars/settings/IWebjarsSettings.java @@ -1,6 +1,7 @@ package de.agilecoders.wicket.webjars.settings; import de.agilecoders.wicket.webjars.collectors.AssetPathCollector; +import org.apache.wicket.util.time.Duration; import java.util.regex.Pattern; @@ -46,4 +47,14 @@ public interface IWebjarsSettings { * @return the full path pattern. The pattern must contain 3 groups: prefix, version, filename */ Pattern webjarsPathPattern(); + + /** + * @return placeholder for recent version (e.g. current) + */ + String recentVersionPlaceHolder(); + + /** + * @return timeout which is used when reading from cache (Future.get(timeout)) + */ + Duration readFromCacheTimeout(); } \ No newline at end of file diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/settings/WebjarsSettings.java b/library/src/main/java/de/agilecoders/wicket/webjars/settings/WebjarsSettings.java index ebc6420..b55a106 100644 --- a/library/src/main/java/de/agilecoders/wicket/webjars/settings/WebjarsSettings.java +++ b/library/src/main/java/de/agilecoders/wicket/webjars/settings/WebjarsSettings.java @@ -1,10 +1,11 @@ package de.agilecoders.wicket.webjars.settings; -import de.agilecoders.wicket.webjars.WebJarAssetLocator; import de.agilecoders.wicket.webjars.collectors.AssetPathCollector; import de.agilecoders.wicket.webjars.collectors.FileAssetPathCollector; import de.agilecoders.wicket.webjars.collectors.JarAssetPathCollector; +import de.agilecoders.wicket.webjars.util.WebJarAssetLocator; import org.apache.wicket.util.lang.Args; +import org.apache.wicket.util.time.Duration; import java.util.regex.Pattern; @@ -15,8 +16,10 @@ */ public class WebjarsSettings implements IWebjarsSettings { + private Duration readFromCacheTimeout; + private ResourceStreamProvider resourceStreamProvider; + private String recentVersionPlaceHolder; private AssetPathCollector[] assetPathCollectors; - private ResourceStreamProvider resourceStreamProvider = null; private String webjarsPackage; private String webjarsPath; private Pattern resourcePattern; @@ -31,6 +34,8 @@ public WebjarsSettings() { this.webjarsPath = this.webjarsPackage.replaceAll("\\.", "/"); this.resourcePattern = Pattern.compile(".*"); this.webjarsPathPattern = Pattern.compile("/webjars/([^/]*)/([^/]*)/(.*)"); + this.recentVersionPlaceHolder = "current"; + this.readFromCacheTimeout = Duration.seconds(3); this.assetPathCollectors = new AssetPathCollector[] { new FileAssetPathCollector(webjarsPath), @@ -77,6 +82,26 @@ public Pattern webjarsPathPattern() { return webjarsPathPattern; } + @Override + public String recentVersionPlaceHolder() { + return recentVersionPlaceHolder; + } + + @Override + public Duration readFromCacheTimeout() { + return readFromCacheTimeout; + } + + public WebjarsSettings readFromCacheTimeout(Duration readFromCacheTimeout) { + this.readFromCacheTimeout = readFromCacheTimeout; + return this; + } + + public WebjarsSettings recentVersionPlaceHolder(String recentVersionPlaceHolder) { + this.recentVersionPlaceHolder = recentVersionPlaceHolder; + return this; + } + public WebjarsSettings resourcePattern(Pattern resourcePattern) { this.resourcePattern = resourcePattern; return this; diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/util/Helper.java b/library/src/main/java/de/agilecoders/wicket/webjars/util/Helper.java new file mode 100644 index 0000000..98463b8 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/util/Helper.java @@ -0,0 +1,56 @@ +package de.agilecoders.wicket.webjars.util; + +import org.apache.wicket.util.lang.Args; + +/** + * some helper methods + * + * @author miha + */ +public final class Helper { + + /** + * prepends the webjars path if missing + * + * @param path the file name to check + * @return file name that starts with "/webjars/" + */ + public static String prependWebjarsPathIfMissing(final String path) { + final String cleanedName = appendLeadingSlash(Args.notEmpty(path, "path")); + + if (!path.contains("/webjars/")) { + return "/webjars" + cleanedName; + } + + return path; + } + + /** + * prepends a leading slash if there is none. + * + * @param path the path + * @return path with leading slash + */ + public static String appendLeadingSlash(final String path) { + return path.charAt(0) == '/' ? path : '/' + path; + } + + /** + * Make paths like aa/bb/cc = cc/bb/aa. + * + * @param assetPath the path to revert + * @return reverted path + */ + public static String reversePath(String assetPath) { + final String[] assetPathComponents = assetPath.split("/"); + final StringBuilder reversedAssetPath = new StringBuilder(); + for (int i = assetPathComponents.length - 1; i >= 0; --i) { + if (reversedAssetPath.length() > 0) { + reversedAssetPath.append('/'); + } + reversedAssetPath.append(assetPathComponents[i]); + } + return reversedAssetPath.toString(); + } + +} diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/util/IFullPathProvider.java b/library/src/main/java/de/agilecoders/wicket/webjars/util/IFullPathProvider.java new file mode 100644 index 0000000..f146be1 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/util/IFullPathProvider.java @@ -0,0 +1,16 @@ +package de.agilecoders.wicket.webjars.util; + +/** + * full path to a webjars provider + * + * @author miha + */ +public interface IFullPathProvider { + /** + * Given a distinct path within the WebJar index passed in return the full path of the resource. + * + * @param partialPath the path to return e.g. "jquery.js" or "abc/someother.js". This must be a distinct path within the index passed in. + * @return a fully qualified path to the resource. + */ + String getFullPath(String partialPath); +} diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/util/RecentVersionCallable.java b/library/src/main/java/de/agilecoders/wicket/webjars/util/RecentVersionCallable.java index a8531ea..4b471da 100644 --- a/library/src/main/java/de/agilecoders/wicket/webjars/util/RecentVersionCallable.java +++ b/library/src/main/java/de/agilecoders/wicket/webjars/util/RecentVersionCallable.java @@ -1,6 +1,8 @@ package de.agilecoders.wicket.webjars.util; import de.agilecoders.wicket.webjars.WicketWebjars; +import de.agilecoders.wicket.webjars.collectors.AssetsMap; +import de.agilecoders.wicket.webjars.collectors.IRecentVersionProvider; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; @@ -45,6 +47,10 @@ public String call() throws Exception { * @return recent version string */ private static String collectRecentVersionFor(final String partialPath) { - return WicketWebjars.finder().recentVersionOf(partialPath); + return Holder.recentVersionProvider.findRecentVersionFor(partialPath); + } + + static final class Holder { + static final IRecentVersionProvider recentVersionProvider = new AssetsMap(WicketWebjars.settings()); } } \ No newline at end of file diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/util/WebJarAssetLocator.java b/library/src/main/java/de/agilecoders/wicket/webjars/util/WebJarAssetLocator.java new file mode 100644 index 0000000..0586ed0 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/util/WebJarAssetLocator.java @@ -0,0 +1,76 @@ +package de.agilecoders.wicket.webjars.util; + +import de.agilecoders.wicket.webjars.collectors.AssetsMap; +import de.agilecoders.wicket.webjars.collectors.IAssetProvider; +import de.agilecoders.wicket.webjars.settings.IWebjarsSettings; +import org.apache.wicket.util.lang.Args; + +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedMap; + +import static de.agilecoders.wicket.webjars.util.Helper.reversePath; + +/** + * Locate WebJar assets. The class is thread safe. + */ +public class WebJarAssetLocator implements IAssetProvider, IFullPathProvider { + private final AssetsMap assetMap; + private final String recentVersionPlaceHolder; + + /** + * Convenience constructor that will form a locator for all resources on the current class path. + */ + public WebJarAssetLocator(final IWebjarsSettings settings) { + this.assetMap = new AssetsMap(settings); + this.recentVersionPlaceHolder = "/" + settings.recentVersionPlaceHolder() + "/"; + } + + private String throwNotFoundException(final String partialPath) { + throw new IllegalArgumentException(partialPath + " could not be found. Make sure you've added the corresponding WebJar and please check for typos."); + } + + private String throwMultipleMatchesException(final String partialPath) { + throw new IllegalArgumentException("Multiple matches found for " + partialPath + + ". Please provide a more specific path, for example by including a version number."); + } + + @Override + public String getFullPath(String partialPath) { + partialPath = Args.notEmpty(partialPath, "partialPath").contains(recentVersionPlaceHolder) ? + assetMap.findRecentVersionFor(partialPath) : + partialPath; + + final String reversePartialPath = reversePath(partialPath); + final SortedMap fullPathTail = assetMap.getFullPathIndex().tailMap(reversePartialPath); + + if (fullPathTail.size() == 0) { + throwNotFoundException(partialPath); + } + + final Iterator> fullPathTailIter = fullPathTail.entrySet().iterator(); + final Entry fullPathEntry = fullPathTailIter.next(); + if (!fullPathEntry.getKey().startsWith(reversePartialPath)) { + throwNotFoundException(partialPath); + } + final String fullPath = fullPathEntry.getValue(); + + if (fullPathTailIter.hasNext() && fullPathTailIter.next().getKey().startsWith(reversePartialPath)) { + throwMultipleMatchesException(reversePartialPath); + } + + return fullPath; + } + + @Override + public SortedMap getFullPathIndex() { + return assetMap.getFullPathIndex(); + } + + @Override + public Set listAssets(final String folderPath) { + return assetMap.listAssets(folderPath); + } + +} diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/util/WebjarsVersion.java b/library/src/main/java/de/agilecoders/wicket/webjars/util/WebjarsVersion.java index 3f01566..7011d5a 100644 --- a/library/src/main/java/de/agilecoders/wicket/webjars/util/WebjarsVersion.java +++ b/library/src/main/java/de/agilecoders/wicket/webjars/util/WebjarsVersion.java @@ -1,8 +1,9 @@ package de.agilecoders.wicket.webjars.util; -import de.agilecoders.wicket.webjars.WebJarAssetLocator; import de.agilecoders.wicket.webjars.WicketWebjars; +import de.agilecoders.wicket.webjars.settings.IWebjarsSettings; import org.apache.wicket.util.lang.Args; +import org.apache.wicket.util.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,6 +14,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import static de.agilecoders.wicket.webjars.util.Helper.prependWebjarsPathIfMissing; + /** * Collects recent versions of webjars resources. * @@ -22,6 +25,15 @@ public final class WebjarsVersion { private static final Logger LOG = LoggerFactory.getLogger(WicketWebjars.class); private static final ConcurrentMap> VERSIONS_CACHE = new ConcurrentHashMap>(); + private static final class Holder { + private static final IWebjarsSettings settings = WicketWebjars.settings(); + + private static final String recentVersionPattern = "/webjars/[^/]*/" + settings + .recentVersionPlaceHolder() + "/.*"; + private static final String replacePattern = "/" + settings.recentVersionPlaceHolder() + "/"; + private static final Duration timeout = settings.readFromCacheTimeout(); + } + /** * replaces the version string "current" with the recent available version * @@ -30,10 +42,10 @@ public final class WebjarsVersion { public static String useRecent(String path) { Args.notEmpty(path, "path"); - path = WebJarAssetLocator.prependWebjarsPathIfMissing(path); + path = prependWebjarsPathIfMissing(path); - if (path.matches("/webjars/[^/]*/current/.*")) { - return path.replaceFirst("/current/", "/" + recentVersion(path) + "/"); + if (path.matches(Holder.recentVersionPattern)) { + return path.replaceFirst(Holder.replacePattern, "/" + recentVersion(path) + "/"); } return path; @@ -56,7 +68,7 @@ public static String recentVersion(final String partialPath) { } try { - return VERSIONS_CACHE.get(partialPath).get(5000, TimeUnit.MILLISECONDS); + return VERSIONS_CACHE.get(partialPath).get(Holder.timeout.getMilliseconds(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { LOG.error("can't collect recent version of {}; {}", partialPath, e.getMessage()); } catch (ExecutionException e) { diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/util/file/WebjarsResourceFinder.java b/library/src/main/java/de/agilecoders/wicket/webjars/util/file/WebjarsResourceFinder.java index e89e88d..0e92e55 100644 --- a/library/src/main/java/de/agilecoders/wicket/webjars/util/file/WebjarsResourceFinder.java +++ b/library/src/main/java/de/agilecoders/wicket/webjars/util/file/WebjarsResourceFinder.java @@ -1,9 +1,10 @@ package de.agilecoders.wicket.webjars.util.file; -import de.agilecoders.wicket.webjars.WebJarAssetLocator; import de.agilecoders.wicket.webjars.request.resource.IWebjarsResourceReference; import de.agilecoders.wicket.webjars.settings.IWebjarsSettings; +import de.agilecoders.wicket.webjars.util.IFullPathProvider; import de.agilecoders.wicket.webjars.util.IResourceStreamProvider; +import de.agilecoders.wicket.webjars.util.WebJarAssetLocator; import org.apache.wicket.util.file.IResourceFinder; import org.apache.wicket.util.resource.IResourceStream; import org.slf4j.Logger; @@ -17,7 +18,7 @@ public class WebjarsResourceFinder implements IResourceFinder { private static final Logger LOG = LoggerFactory.getLogger("wicket-webjars"); - private final WebJarAssetLocator locator; + private final IFullPathProvider locator; private final IResourceStreamProvider resourceStreamProvider; private final IWebjarsSettings settings; private final int hashCode; @@ -29,7 +30,7 @@ public class WebjarsResourceFinder implements IResourceFinder { */ public WebjarsResourceFinder(IWebjarsSettings settings) { this.settings = settings; - this.locator = newLocator(); + this.locator = newFullPathProvider(); this.resourceStreamProvider = settings.resourceStreamProvider().newInstance(settings.classLoaders()); int result = locator != null ? locator.hashCode() : 0; @@ -40,18 +41,10 @@ public WebjarsResourceFinder(IWebjarsSettings settings) { /** * @return new resource locator instance */ - protected WebJarAssetLocator newLocator() { + protected IFullPathProvider newFullPathProvider() { return new WebJarAssetLocator(settings); } - /** - * @param partialPath the path to detect version for - * @return recent version - */ - public String recentVersionOf(String partialPath) { - return locator.recentVersionOf(partialPath); - } - /** * Looks for a given path name along the webjars root path *