diff --git a/library/pom.xml b/library/pom.xml index 351301b..096911b 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -1,42 +1,47 @@ - - 4.0.0 + + 4.0.0 - - de.agilecoders.wicket.webjars - wicket-webjars-parent - 0.3.4-SNAPSHOT - ../ - + + de.agilecoders.wicket.webjars + wicket-webjars-parent + 0.3.4-SNAPSHOT + ../pom.xml + - wicket-webjars - jar - library + wicket-webjars + jar + library - + + + + org.apache.wicket + wicket-core + + + + + org.slf4j + slf4j-api + + + + + org.reflections + reflections + + - junit - junit - 4.11 + org.jboss + jboss-vfs - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - ${mvn.build.java.version} - ${mvn.build.java.version} - ${mvn.build.java.version} - ${project.build.sourceEncoding} - true - true - true - - - - + + + junit + junit + + diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/collectors/AssetPathCollector.java b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/AssetPathCollector.java new file mode 100644 index 0000000..e36f316 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/AssetPathCollector.java @@ -0,0 +1,17 @@ +package de.agilecoders.wicket.webjars.collectors; + +import java.net.URL; +import java.util.Collection; +import java.util.regex.Pattern; + +/** + * TODO miha: document class purpose + * + * @author miha + */ +public interface AssetPathCollector { + + boolean accept(URL url); + + Collection collect(URL url, Pattern filterExpr); +} diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/collectors/FileAssetPathCollector.java b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/FileAssetPathCollector.java new file mode 100644 index 0000000..2716e15 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/FileAssetPathCollector.java @@ -0,0 +1,72 @@ +package de.agilecoders.wicket.webjars.collectors; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class FileAssetPathCollector extends ProtocolAwareAssetPathCollector { + private static final int MAX_DIRECTORY_DEPTH = 5; + + private final String pathPrefix; + + public FileAssetPathCollector(final String pathPrefix) { + super("file"); + + this.pathPrefix = pathPrefix; + } + + @Override + public Collection collect(URL url, Pattern filterExpr) { + final File file; + try { + file = new File(url.toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + + return listFiles(file, filterExpr); + } + + /** + * Recursively search all directories for relative file paths matching `filterExpr`. + * + * @param file + * @param filterExpr + * @return + */ + private Set listFiles(final File file, final Pattern filterExpr) { + final Set aggregatedChildren = new HashSet(); + aggregateChildren(file, file, aggregatedChildren, filterExpr, 0); + return aggregatedChildren; + } + + private void aggregateChildren(final File rootDirectory, final File file, final Set aggregatedChildren, final Pattern filterExpr, final int level) { + if (file != null && file.isDirectory()) { + if (level > MAX_DIRECTORY_DEPTH) { + throw new IllegalStateException("Got deeper than " + MAX_DIRECTORY_DEPTH + " levels while searching " + rootDirectory); + } + + File[] files = file.listFiles(); + + if (files != null) { + for (final File child : files) { + aggregateChildren(rootDirectory, child, aggregatedChildren, filterExpr, level + 1); + } + } + } else if (file != null) { + aggregateFile(file, aggregatedChildren, filterExpr); + } + } + + private void aggregateFile(final File file, final Set aggregatedChildren, final Pattern filterExpr) { + final String path = file.getPath().replace('\\', '/'); + final String relativePath = path.substring(path.indexOf(pathPrefix)); + if (filterExpr.matcher(relativePath).matches()) { + aggregatedChildren.add(relativePath); + } + } +} \ No newline at end of file diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/collectors/JarAssetPathCollector.java b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/JarAssetPathCollector.java new file mode 100644 index 0000000..61f9ed5 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/JarAssetPathCollector.java @@ -0,0 +1,56 @@ +package de.agilecoders.wicket.webjars.collectors; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Pattern; + +public class JarAssetPathCollector extends ProtocolAwareAssetPathCollector { + + public JarAssetPathCollector() { + super("jar"); + } + + protected JarAssetPathCollector(final String protocol) { + super(protocol); + } + + protected JarAssetPathCollector(final String... protocols) { + super(protocols); + } + + @Override + public Collection collect(URL url, Pattern filterExpr) { + final JarFile jarFile = newJarFile(url); + final Set assetPaths = new HashSet(); + + final Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + final JarEntry entry = entries.nextElement(); + final String assetPathCandidate = entry.getName(); + if (!entry.isDirectory() && filterExpr.matcher(assetPathCandidate).matches()) { + assetPaths.add(assetPathCandidate); + } + } + + return assetPaths; + } + + protected JarFile newJarFile(final URL url) { + try { + final String path = url.getPath(); + final File file = new File(URI.create(path.substring(0, path.indexOf("!")))); + + return new JarFile(file); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/collectors/ProtocolAwareAssetPathCollector.java b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/ProtocolAwareAssetPathCollector.java new file mode 100644 index 0000000..f55ac78 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/ProtocolAwareAssetPathCollector.java @@ -0,0 +1,31 @@ +package de.agilecoders.wicket.webjars.collectors; + +import java.net.URL; +import java.util.Arrays; +import java.util.List; + +public abstract class ProtocolAwareAssetPathCollector implements AssetPathCollector { + + private final List protocols; + + protected ProtocolAwareAssetPathCollector(final String protocol) { + this.protocols = Arrays.asList(protocol); + } + + protected ProtocolAwareAssetPathCollector(final String... protocols) { + this.protocols = Arrays.asList(protocols); + } + + @Override + public boolean accept(URL url) { + if (url == null) { + return false; + } + for (String protocol : protocols) { + if (protocol.equalsIgnoreCase(url.getProtocol())) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/collectors/VfsJarAssetPathCollector.java b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/VfsJarAssetPathCollector.java new file mode 100644 index 0000000..4f84ad2 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/collectors/VfsJarAssetPathCollector.java @@ -0,0 +1,69 @@ +package de.agilecoders.wicket.webjars.collectors; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.jar.JarFile; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jboss.vfs.VirtualFile; +import org.reflections.vfs.Vfs; + +import de.agilecoders.wicket.webjars.vfs.ExtendedUrlTypeVFS; + +/** + * Adds support of vfs protocol to webjars. + * + * @author miha + */ +public class VfsJarAssetPathCollector extends JarAssetPathCollector { + private static final Pattern PATTERN = Pattern.compile("/([^/]*\\.jar)"); + + /** + * Construct. + */ + public VfsJarAssetPathCollector() { + super("vfs", "vfszip", "vfsfile"); + addDefaultUrlTypes(); + } + + @Override + protected JarFile newJarFile(URL url) { + try { + URLConnection conn = url.openConnection(); + VirtualFile vf = (VirtualFile) conn.getContent(); + File contentsFile = vf.getPhysicalFile(); + String c = contentsFile.getPath().replace('\\', '/'); + + final String jarName = toJarName(url); + final String pathToJar = c.substring(0, c.indexOf("/contents/")); + + return new JarFile(new File(pathToJar, jarName)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static String toJarName(URL url) throws FileNotFoundException { + final String path = url.getPath(); + + Matcher m = PATTERN.matcher(path); + if (m.find()) { + return m.group(1); + } + + throw new FileNotFoundException(url.getPath()); + } + + private static AtomicBoolean added = new AtomicBoolean(false); + + private static void addDefaultUrlTypes() { + if (!added.getAndSet(true)) { + Vfs.addDefaultURLTypes(new ExtendedUrlTypeVFS()); + } + } +} \ No newline at end of file diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/util/WicketWebjars.java b/library/src/main/java/de/agilecoders/wicket/webjars/util/WicketWebjars.java index 768fd48..8b78d9b 100644 --- a/library/src/main/java/de/agilecoders/wicket/webjars/util/WicketWebjars.java +++ b/library/src/main/java/de/agilecoders/wicket/webjars/util/WicketWebjars.java @@ -1,11 +1,12 @@ package de.agilecoders.wicket.webjars.util; +import de.agilecoders.wicket.webjars.collectors.AssetPathCollector; import de.agilecoders.wicket.webjars.util.file.WebjarsResourceFinder; + import org.apache.wicket.Application; import org.apache.wicket.util.file.IResourceFinder; import org.apache.wicket.util.lang.Args; -import org.webjars.AssetPathCollector; -import org.webjars.VfsAwareWebJarAssetLocator; +import org.webjars.WebJarAssetLocator; import java.util.List; @@ -23,7 +24,7 @@ public final class WicketWebjars { * @param collectorArr the collectors to register */ public static void registerCollector(AssetPathCollector... collectorArr) { - VfsAwareWebJarAssetLocator.registerCollector(collectorArr); + WebJarAssetLocator.registerCollector(collectorArr); } /** 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 5c28d7a..d5e7739 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 @@ -7,7 +7,6 @@ import org.apache.wicket.util.resource.IResourceStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.webjars.VfsAwareWebJarAssetLocator; import org.webjars.WebJarAssetLocator; import java.net.MalformedURLException; @@ -36,7 +35,7 @@ public static WebjarsResourceFinder instance() { return Holder.instance; } - private final VfsAwareWebJarAssetLocator locator; + private final WebJarAssetLocator locator; private final ClasspathUrlStreamHandler urlHandler; /** @@ -55,7 +54,7 @@ protected WebjarsResourceFinder() { protected ClassLoader[] classLoaders() { return new ClassLoader[] { Thread.currentThread().getContextClassLoader(), - VfsAwareWebJarAssetLocator.class.getClassLoader(), + WebJarAssetLocator.class.getClassLoader(), WebJarAssetLocator.class.getClassLoader(), getClass().getClassLoader() }; @@ -65,16 +64,16 @@ protected ClassLoader[] classLoaders() { * @param classLoaders the classloaders to use to load resources * @return new resource locator instance */ - protected VfsAwareWebJarAssetLocator newLocator(ClassLoader[] classLoaders) { - return new VfsAwareWebJarAssetLocator( - VfsAwareWebJarAssetLocator.getFullPathIndex(Pattern.compile(".*"), classLoaders) + protected WebJarAssetLocator newLocator(ClassLoader[] classLoaders) { + return new WebJarAssetLocator( + WebJarAssetLocator.getFullPathIndex(Pattern.compile(".*"), classLoaders) ); } /** * @return new resource locator instance */ - protected VfsAwareWebJarAssetLocator newLocator() { + protected WebJarAssetLocator newLocator() { return newLocator(classLoaders()); } diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/vfs/ExtendedUrlTypeVFS.java b/library/src/main/java/de/agilecoders/wicket/webjars/vfs/ExtendedUrlTypeVFS.java new file mode 100644 index 0000000..5005161 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/vfs/ExtendedUrlTypeVFS.java @@ -0,0 +1,61 @@ +package de.agilecoders.wicket.webjars.vfs; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import org.jboss.vfs.VirtualFile; +import org.reflections.vfs.UrlTypeVFS; +import org.reflections.vfs.Vfs.Dir; + +/** + * handles vfs urls. + * + * @author miha + */ +public class ExtendedUrlTypeVFS extends UrlTypeVFS { + + final static boolean jbossAS; + + final String VFS = "vfs"; + + static { + boolean jbossFound; + try { + Class.forName("org.jboss.vfs.VirtualFile"); + jbossFound = true; + } catch (ClassNotFoundException e) { + jbossFound = false; + } + + jbossAS = jbossFound; + } + + @Override + public boolean matches(URL url) { + return super.matches(url) || VFS.equals(url.getProtocol()); + } + + @Override + public Dir createDir(URL url) { + if (jbossAS && VFS.equals(url.getProtocol())) { + try { + if (url.getContent() instanceof VirtualFile) { + return new VfsDir(url); + } + } catch (IOException e) { + throw new RuntimeException("error reading from VFS", e); + } + } + return super.createDir(url); + } + + @Override + public URL adaptURL(URL url) throws MalformedURLException { + if (VFS.equals(url.getProtocol())) { + return new URL(url.toString().replace(VFS, "file")); + } else { + return super.adaptURL(url); + } + } +} diff --git a/library/src/main/java/de/agilecoders/wicket/webjars/vfs/VfsDir.java b/library/src/main/java/de/agilecoders/wicket/webjars/vfs/VfsDir.java new file mode 100644 index 0000000..76b8538 --- /dev/null +++ b/library/src/main/java/de/agilecoders/wicket/webjars/vfs/VfsDir.java @@ -0,0 +1,92 @@ +package de.agilecoders.wicket.webjars.vfs; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.jboss.vfs.VirtualFile; +import org.reflections.vfs.Vfs; + +import com.google.common.collect.AbstractIterator; + +/** + * Implementation of {@link Vfs.Dir} to support JBoss VFS for proper classpath scanning on JBoss AS. + * + * @author johnjcool + */ +public class VfsDir implements Vfs.Dir { + + private VirtualFile virtualFile; + + public VfsDir(URL url) { + try { + Object content = url.getContent(); + if (content instanceof VirtualFile) { + virtualFile = (VirtualFile) content; + } else { + throw new IllegalArgumentException("URL content is not a JBoss VFS VirtualFile. Type is: " + + (content == null ? "null" : content.getClass().getName())); + } + } catch (IOException e) { + throw new RuntimeException("could not instantiate VFS directory", e); + } + } + + @Override + public String getPath() { + return virtualFile.getPathName(); + } + + @Override + public Iterable getFiles() { + return new Iterable() { + @Override + public Iterator iterator() { + final List toVisit = new ArrayList(virtualFile.getChildren()); + + return new AbstractIterator() { + + @Override + protected Vfs.File computeNext() { + while (!toVisit.isEmpty()) { + final VirtualFile nextFile = toVisit.remove(toVisit.size() - 1); + if (nextFile.isDirectory()) { + toVisit.addAll(nextFile.getChildren()); + continue; + } + return new Vfs.File() { + @Override + public String getName() { + return nextFile.getName(); + } + + @Override + public String getRelativePath() { + return nextFile.getPathNameRelativeTo(virtualFile); + } + + @Override + public InputStream openInputStream() throws IOException { + return nextFile.openStream(); + } + }; + } + return endOfData(); + } + }; + } + }; + } + + @Override + public void close() { + } + + @Override + public String toString() { + return virtualFile.getName(); + } +} diff --git a/library/src/main/java/org/webjars/AssetPathCollector.java b/library/src/main/java/org/webjars/AssetPathCollector.java deleted file mode 100644 index 8753d39..0000000 --- a/library/src/main/java/org/webjars/AssetPathCollector.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.webjars; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.regex.Pattern; - -/** - * TODO miha: document class purpose - * - * @author miha - */ -public interface AssetPathCollector { - - boolean accept(URL url); - - Collection collect(URL url, Pattern filterExpr); - - public static abstract class ProtocolAwareAssetPathCollector implements AssetPathCollector { - - private final String protocol; - - protected ProtocolAwareAssetPathCollector(final String protocol) { - this.protocol = protocol; - } - - @Override - public boolean accept(URL url) { - return url != null && protocol.equalsIgnoreCase(url.getProtocol()); - } - } - - public static class FileAssetPathCollector extends ProtocolAwareAssetPathCollector { - private static final int MAX_DIRECTORY_DEPTH = 5; - - private final String pathPrefix; - - public FileAssetPathCollector(final String pathPrefix) { - super("file"); - - this.pathPrefix = pathPrefix; - } - - @Override - public Collection collect(URL url, Pattern filterExpr) { - final File file; - try { - file = new File(url.toURI()); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - - return listFiles(file, filterExpr); - } - - /** - * Recursively search all directories for relative file paths matching `filterExpr`. - * - * @param file - * @param filterExpr - * @return - */ - private Set listFiles(final File file, final Pattern filterExpr) { - final Set aggregatedChildren = new HashSet(); - aggregateChildren(file, file, aggregatedChildren, filterExpr, 0); - return aggregatedChildren; - } - - private void aggregateChildren(final File rootDirectory, final File file, final Set aggregatedChildren, final Pattern filterExpr, final int level) { - if (file != null && file.isDirectory()) { - if (level > MAX_DIRECTORY_DEPTH) { - throw new IllegalStateException("Got deeper than " + MAX_DIRECTORY_DEPTH + " levels while searching " + rootDirectory); - } - - File[] files = file.listFiles(); - - if (files != null) { - for (final File child : files) { - aggregateChildren(rootDirectory, child, aggregatedChildren, filterExpr, level + 1); - } - } - } else if (file != null) { - aggregateFile(file, aggregatedChildren, filterExpr); - } - } - - private void aggregateFile(final File file, final Set aggregatedChildren, final Pattern filterExpr) { - final String path = file.getPath(); - final String relativePath = path.substring(path.indexOf(pathPrefix)); - if (filterExpr.matcher(relativePath).matches()) { - aggregatedChildren.add(relativePath); - } - } - } - - public static class JarAssetPathCollector extends ProtocolAwareAssetPathCollector { - - public JarAssetPathCollector() { - super("jar"); - } - - protected JarAssetPathCollector(final String protocol) { - super(protocol); - } - - @Override - public Collection collect(URL url, Pattern filterExpr) { - final JarFile jarFile = newJarFile(url); - final Set assetPaths = new HashSet(); - - final Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - final JarEntry entry = entries.nextElement(); - final String assetPathCandidate = entry.getName(); - if (!entry.isDirectory() && filterExpr.matcher(assetPathCandidate).matches()) { - assetPaths.add(assetPathCandidate); - } - } - - return assetPaths; - } - - protected JarFile newJarFile(final URL url) { - try { - final String path = url.getPath(); - final File file = new File(URI.create(path.substring(0, path.indexOf("!")))); - - return new JarFile(file); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - -} diff --git a/library/src/main/java/org/webjars/VfsAwareWebJarAssetLocator.java b/library/src/main/java/org/webjars/VfsAwareWebJarAssetLocator.java deleted file mode 100644 index 76a691c..0000000 --- a/library/src/main/java/org/webjars/VfsAwareWebJarAssetLocator.java +++ /dev/null @@ -1,217 +0,0 @@ -package org.webjars; - -import com.google.common.collect.Lists; - -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.Map.Entry; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.regex.Pattern; - -/** - * Locate WebJar assets. The class is thread safe. - */ -public class VfsAwareWebJarAssetLocator { - - /** - * The webjar package name. - */ - public static final String WEBJARS_PACKAGE = "META-INF.resources.webjars"; - - /** - * The path to where webjar resources live. - */ - public static final String WEBJARS_PATH_PREFIX = "META-INF/resources/webjars"; - - private static final Set collectors = new HashSet(); - - static { - registerCollector(new AssetPathCollector.FileAssetPathCollector(WebJarAssetLocator.WEBJARS_PATH_PREFIX), - new AssetPathCollector.JarAssetPathCollector()); - } - - /** - * registers an additional collector - * - * @param collectorArr the collectors to register - */ - public static void registerCollector(AssetPathCollector... collectorArr) { - synchronized (collectors) { - collectors.addAll(Lists.newArrayList(collectorArr)); - } - } - - /* - * Return all {@link URL}s defining {@value VfsAwareWebJarAssetLocator#WEBJARS_PATH_PREFIX} directory, either identifying JAR files or plain directories. - */ - private static Set listWebjarsParentURLs(final ClassLoader[] classLoaders) { - final Set urls = new HashSet(); - for (final ClassLoader classLoader : classLoaders) { - try { - final Enumeration enumeration = classLoader.getResources(WebJarAssetLocator.WEBJARS_PATH_PREFIX); - 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 static 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 static SortedMap getFullPathIndex( - 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(); - } - - final SortedMap fullPathIndex; - - /** - * Convenience constructor that will form a locator for all resources on the - * current class path. - */ - public VfsAwareWebJarAssetLocator() { - this(getFullPathIndex(Pattern.compile(".*"), - VfsAwareWebJarAssetLocator.class.getClassLoader())); - } - - /** - * Establish a locator given an index that it should use. - * - * @param fullPathIndex the index to use. - */ - public VfsAwareWebJarAssetLocator(final SortedMap fullPathIndex) { - 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."); - } - - /** - * 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(final String 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)) { - throw new MultipleMatchesException( - "Multiple matches found for " - + partialPath - + ". Please provide a more specific path, for example by including a version number."); - } - - 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 = WEBJARS_PATH_PREFIX + folderPath; - for (final String asset : allAssets) { - if (asset.startsWith(prefix)) { - assets.add(asset); - } - } - return assets; - } -} diff --git a/library/src/main/java/org/webjars/WebJarAssetLocator.java b/library/src/main/java/org/webjars/WebJarAssetLocator.java new file mode 100644 index 0000000..1cc341b --- /dev/null +++ b/library/src/main/java/org/webjars/WebJarAssetLocator.java @@ -0,0 +1,211 @@ +package org.webjars; + +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.Map.Entry; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.regex.Pattern; + +import com.google.common.collect.Lists; + +import de.agilecoders.wicket.webjars.collectors.AssetPathCollector; +import de.agilecoders.wicket.webjars.collectors.FileAssetPathCollector; +import de.agilecoders.wicket.webjars.collectors.JarAssetPathCollector; + +/** + * Locate WebJar assets. The class is thread safe. + */ +public class WebJarAssetLocator { + + /** + * The webjar package name. + */ + public static final String WEBJARS_PACKAGE = "META-INF.resources.webjars"; + + /** + * The path to where webjar resources live. + */ + public static final String WEBJARS_PATH_PREFIX = "META-INF/resources/webjars"; + + private static final Set collectors = new HashSet(); + + static { + registerCollector(new FileAssetPathCollector(WEBJARS_PATH_PREFIX), new JarAssetPathCollector()); + } + + /** + * registers an additional collector + * + * @param collectorArr + * the collectors to register + */ + public static void registerCollector(AssetPathCollector... collectorArr) { + synchronized (collectors) { + collectors.addAll(Lists.newArrayList(collectorArr)); + } + } + + /* + * Return all {@link URL}s defining {@value VfsAwareWebJarAssetLocator#WEBJARS_PATH_PREFIX} directory, either identifying JAR files or plain + * directories. + */ + private static Set listWebjarsParentURLs(final ClassLoader[] classLoaders) { + final Set urls = new HashSet(); + for (final ClassLoader classLoader : classLoaders) { + try { + final Enumeration enumeration = classLoader.getResources(WEBJARS_PATH_PREFIX); + 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 static 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 static SortedMap getFullPathIndex(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(); + } + + final SortedMap fullPathIndex; + + /** + * Convenience constructor that will form a locator for all resources on the current class path. + */ + public WebJarAssetLocator() { + this(getFullPathIndex(Pattern.compile(".*"), WebJarAssetLocator.class.getClassLoader())); + } + + /** + * Establish a locator given an index that it should use. + * + * @param fullPathIndex + * the index to use. + */ + public WebJarAssetLocator(final SortedMap fullPathIndex) { + 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(final String 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 = WEBJARS_PATH_PREFIX + folderPath; + for (final String asset : allAssets) { + if (asset.startsWith(prefix)) { + assets.add(asset); + } + } + return assets; + } +} diff --git a/pom.xml b/pom.xml index 6f179bc..6c47688 100644 --- a/pom.xml +++ b/pom.xml @@ -1,208 +1,210 @@ - - 4.0.0 - - - org.sonatype.oss - oss-parent - 7 - - - de.agilecoders.wicket.webjars - wicket-webjars-parent - pom - 0.3.4-SNAPSHOT - wicket-webjars-parent - - https://github.com/l0rdn1kk0n/wicket-webjars - - - git@github.com:l0rdn1kk0n/wicket-webjars.git - scm:git:git@github.com:l0rdn1kk0n/wicket-webjars.git - scm:git:git@github.com:l0rdn1kk0n/wicket-webjars.git - - - - github - https://github.com/l0rdn1kk0n/wicket-webjars/issues - - - - agilecoders.de - http://agilecoders.de - - - - ${mvn.version} - - - - library - samples - vfs-support - - - - - - - org.apache.wicket - wicket-core - ${wicket.version} - - - - de.agilecoders.wicket.webjars - wicket-webjars - ${project.version} - - - - org.webjars - webjars-locator - ${org.webjars.locator.version} - - - - - - - - org.apache.wicket - wicket-core - - - - org.webjars - webjars-locator - - - - - org.slf4j - slf4j-api - 1.7.5 - - - - org.reflections - reflections - 0.9.9-RC1 - - - - - - - - false - src/main/resources - - - false - src/main/java - - ** - - - **/*.java - - - - - - false - src/test/java - - ** - - - **/*.java - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - ${mvn.build.java.version} - ${mvn.build.java.version} - ${mvn.build.java.version} - ${project.build.sourceEncoding} - true - true - true - - - - org.codehaus.mojo - versions-maven-plugin - ${versions-maven-plugin.version} - true - - - org.apache.maven.plugins - maven-source-plugin - 2.2 - - - - jar - test-jar - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - test-jar - - - - - - - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - - miha - Michael Haitz - michael.haitz@agilecoders.de - agilecoders.de - - Owner - Comitter - - - - - - github - - UTF-8 - ${project.build.sourceEncoding} - 1.7 - 3.0.0 - - 1.3.1 - 0.6 - 6.12.0 - + + 4.0.0 + + + org.sonatype.oss + oss-parent + 7 + + + de.agilecoders.wicket.webjars + wicket-webjars-parent + pom + 0.3.4-SNAPSHOT + wicket-webjars-parent + + https://github.com/l0rdn1kk0n/wicket-webjars + + + git@github.com:l0rdn1kk0n/wicket-webjars.git + scm:git:git@github.com:l0rdn1kk0n/wicket-webjars.git + scm:git:git@github.com:l0rdn1kk0n/wicket-webjars.git + + + + github + https://github.com/l0rdn1kk0n/wicket-webjars/issues + + + + agilecoders.de + http://agilecoders.de + + + + ${mvn.version} + + + + library + samples + + + + + + + de.agilecoders.wicket.webjars + wicket-webjars + ${project.version} + + + + + org.apache.wicket + wicket-core + ${wicket.version} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + + org.reflections + reflections + ${reflections.version} + + + + org.jboss + jboss-vfs + ${jboss.vfs.version} + provided + + + + + junit + junit + ${junit.version} + test + + + + + + + + + + + false + src/main/resources + + + false + src/main/java + + ** + + + **/*.java + + + + + + false + src/test/java + + ** + + + **/*.java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5.1 + + ${mvn.build.java.version} + ${mvn.build.java.version} + ${mvn.build.java.version} + ${project.build.sourceEncoding} + true + true + true + + + + org.codehaus.mojo + versions-maven-plugin + 1.3.1 + true + + + org.apache.maven.plugins + maven-source-plugin + 2.2 + + + + jar + test-jar + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + test-jar + + + + + + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + miha + Michael Haitz + michael.haitz@agilecoders.de + agilecoders.de + + Owner + Comitter + + + + + + github + + UTF-8 + ${project.build.sourceEncoding} + 1.6 + 3.0.0 + + 6.12.0 + 1.7.5 + 0.9.9-rc1 + 4.11 + 3.2.2.Final + \ No newline at end of file diff --git a/samples/src/main/java/de/agilecoders/wicket/HomePage.java b/samples/src/main/java/de/agilecoders/wicket/HomePage.java index 5ad0aeb..1207201 100644 --- a/samples/src/main/java/de/agilecoders/wicket/HomePage.java +++ b/samples/src/main/java/de/agilecoders/wicket/HomePage.java @@ -20,7 +20,7 @@ public void renderHead(IHeaderResponse response) { super.renderHead(response); response.render(JavaScriptHeaderItem.forReference(new WebjarsJavaScriptResourceReference("jquery/current/jquery.min.js"))); - response.render(CssHeaderItem.forReference(new WebjarsCssResourceReference("bootstrap/current/css/bootstrap.min.css"))); - response.render(CssHeaderItem.forReference(new WebjarsCssResourceReference("bootstrap/current/css/bootstrap-theme.min.css"))); + response.render(CssHeaderItem.forReference(new WebjarsCssResourceReference("bootstrap/current/css/bootstrap.css"))); + response.render(CssHeaderItem.forReference(new WebjarsCssResourceReference("bootstrap/current/css/bootstrap-theme.css"))); } } diff --git a/samples/src/main/java/de/agilecoders/wicket/WicketApplication.java b/samples/src/main/java/de/agilecoders/wicket/WicketApplication.java index c7f60f5..295ebf0 100644 --- a/samples/src/main/java/de/agilecoders/wicket/WicketApplication.java +++ b/samples/src/main/java/de/agilecoders/wicket/WicketApplication.java @@ -1,28 +1,31 @@ package de.agilecoders.wicket; -import de.agilecoders.wicket.webjars.util.WicketWebjars; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.protocol.http.WebApplication; +import de.agilecoders.wicket.webjars.collectors.VfsJarAssetPathCollector; +import de.agilecoders.wicket.webjars.util.WicketWebjars; + /** * Application object for your web application. If you want to run this application without deploying, run the Start class. */ public class WicketApplication extends WebApplication { - /** - * @see org.apache.wicket.Application#getHomePage() - */ - @Override - public Class getHomePage() { - return HomePage.class; - } + /** + * @see org.apache.wicket.Application#getHomePage() + */ + @Override + public Class getHomePage() { + return HomePage.class; + } - /** - * @see org.apache.wicket.Application#init() - */ - @Override - public void init() { - super.init(); + /** + * @see org.apache.wicket.Application#init() + */ + @Override + public void init() { + super.init(); - WicketWebjars.install(this); - } + WicketWebjars.install(this); + WicketWebjars.registerCollector(new VfsJarAssetPathCollector()); + } } diff --git a/vfs-support/src/main/java/de/agilecoders/wicket/webjars/collectors/VfsJarAssetPathCollector.java b/vfs-support/src/main/java/de/agilecoders/wicket/webjars/collectors/VfsJarAssetPathCollector.java index c7853e4..e4374a2 100644 --- a/vfs-support/src/main/java/de/agilecoders/wicket/webjars/collectors/VfsJarAssetPathCollector.java +++ b/vfs-support/src/main/java/de/agilecoders/wicket/webjars/collectors/VfsJarAssetPathCollector.java @@ -37,7 +37,7 @@ protected JarFile newJarFile(URL url) { URLConnection conn = url.openConnection(); VirtualFile vf = (VirtualFile) conn.getContent(); File contentsFile = vf.getPhysicalFile(); - String c = contentsFile.getPath(); + String c = contentsFile.getPath().replaceAll("\\", "/"); final String jarName = toJarName(url); final String pathToJar = c.substring(0, c.indexOf("/contents/"));