1 package org.webjars;
2
3 import io.github.classgraph.ClassGraph;
4 import io.github.classgraph.Resource;
5 import io.github.classgraph.ResourceList;
6 import io.github.classgraph.ScanResult;
7
8 import java.io.IOException;
9 import java.net.URI;
10 import java.util.*;
11 import java.util.Map.Entry;
12 import java.util.regex.Matcher;
13 import java.util.regex.Pattern;
14
15
18 public class WebJarAssetLocator {
19
20
23 public static final String WEBJARS_PACKAGE = "META-INF.resources.webjars";
24
25
28 public static final String WEBJARS_PATH_PREFIX = "META-INF/resources/webjars";
29
30 private static Pattern WEBJAR_EXTRACTOR_PATTERN = Pattern.compile(WEBJARS_PATH_PREFIX + "/([^/]*)/([^/]*)/(.*)$");
31
32 static class WebJarInfo {
33 final String version;
34 final String groupId;
35 final URI uri;
36 final List<String> contents;
37
38 public WebJarInfo(final String version, final String groupId, final URI uri, final List<String> contents) {
39 this.version = version;
40 this.groupId = groupId;
41 this.uri = uri;
42 this.contents = contents;
43 }
44 }
45
46 protected final Map<String, WebJarInfo> allWebJars;
47
48 protected static ResourceList webJarResources(final String webJarName, final ResourceList resources) {
49 return resources.filter(resource -> resource.getPath().startsWith(WEBJARS_PATH_PREFIX + "/" + webJarName + "/"));
50 }
51
52 protected static String webJarVersion(final String webJarName, final ResourceList resources) {
53 if (resources.isEmpty()) {
54 return null;
55 }
56 else {
57 final String aPath = resources.get(0).getPath();
58 final String prefix = WEBJARS_PATH_PREFIX + "/" + webJarName + "/";
59 if (aPath.startsWith(prefix)) {
60 final String withoutName = aPath.substring(prefix.length());
61 try {
62 final String maybeVersion = withoutName.substring(0, withoutName.indexOf("/"));
63 ResourceList withMaybeVersion = resources.filter(resource -> resource.getPath().startsWith(prefix + maybeVersion + "/"));
64
65 if (withMaybeVersion.size() == resources.size()) {
66 return maybeVersion;
67 } else {
68 return null;
69 }
70 }
71 catch (Exception e) {
72 return null;
73 }
74 }
75 else {
76 return null;
77 }
78 }
79 }
80
81 private static String groupId(final URI classpathElementURI) {
82 final ClassGraph classGraph = new ClassGraph().overrideClasspath(classpathElementURI).ignoreParentClassLoaders().whitelistPaths("META-INF/maven");
83 try (ScanResult scanResult = classGraph.scan()) {
84 final ResourceList maybePomProperties = scanResult.getResourcesWithLeafName("pom.properties");
85
86 String groupId = null;
87 if (maybePomProperties.size() == 1) {
88 try {
89 final Properties properties = new Properties();
90 properties.load(maybePomProperties.get(0).open());
91 maybePomProperties.get(0).close();
92 groupId = properties.getProperty("groupId");
93 } catch (IOException e) {
94
95 }
96 }
97
98 return groupId;
99 }
100 }
101
102 protected static Map<String, WebJarInfo> findWebJars(ScanResult scanResult) {
103 Map<String, WebJarInfo> allWebJars = new HashMap<>();
104
105 for (Resource resource : scanResult.getAllResources()) {
106 final String noPrefix = resource.getPath().substring(WEBJARS_PATH_PREFIX.length() + 1);
107 final String webJarName = noPrefix.substring(0, noPrefix.indexOf("/"));
108 WebJarInfo webJarInfo = allWebJars.get(webJarName);
109 if (webJarInfo == null) {
110 final ResourceList webJarResources = webJarResources(webJarName, scanResult.getAllResources());
111 final String maybeWebJarVersion = webJarVersion(webJarName, webJarResources);
112 final String maybeGroupId = groupId(resource.getClasspathElementURI());
113
114
115
116
117 final List<String> paths = new ArrayList<>(new HashSet<>(webJarResources.getPaths()));
118 webJarInfo = new WebJarInfo(maybeWebJarVersion, maybeGroupId, resource.getClasspathElementURI(), paths);
119 allWebJars.put(webJarName, webJarInfo);
120 }
121 }
122
123 return allWebJars;
124 }
125
126
130 public static Entry<String, String> getWebJar(String path) {
131
132 Matcher matcher = WEBJAR_EXTRACTOR_PATTERN.matcher(path);
133 if (matcher.find()) {
134 String id = matcher.group(1);
135 String version = matcher.group(2);
136 return new AbstractMap.SimpleEntry<>(id, version);
137 } else {
138
139 return null;
140 }
141 }
142
143 private Map<String, WebJarInfo> scanForWebJars(ClassGraph classGraph) {
144 try(ScanResult scanResult = classGraph.whitelistPaths(WEBJARS_PATH_PREFIX).scan()) {
145 return findWebJars(scanResult);
146 }
147 }
148
149 public WebJarAssetLocator() {
150 allWebJars = scanForWebJars(new ClassGraph());
151 }
152
153 public WebJarAssetLocator(final ClassLoader classLoader) {
154 allWebJars = scanForWebJars(new ClassGraph().overrideClassLoaders(classLoader).ignoreParentClassLoaders());
155 }
156
157 public WebJarAssetLocator(final String... whitelistPaths) {
158 allWebJars = scanForWebJars(new ClassGraph().whitelistPaths(whitelistPaths));
159 }
160
161 public WebJarAssetLocator(final ClassLoader classLoader, final String... whitelistPaths) {
162 allWebJars = scanForWebJars(new ClassGraph().overrideClassLoaders(classLoader).ignoreParentClassLoaders().whitelistPaths(whitelistPaths));
163 }
164
165 public WebJarAssetLocator(final Map<String, WebJarInfo> allWebJars) {
166 this.allWebJars = allWebJars;
167 }
168
169 private String throwNotFoundException(final String partialPath) {
170 throw new NotFoundException(
171 partialPath
172 + " could not be found. Make sure you've added the corresponding WebJar and please check for typos."
173 );
174 }
175
176 private String throwMultipleMatchesException(final String partialPath, final List<String> matches) {
177 throw new MultipleMatchesException(
178 "Multiple matches found for "
179 + partialPath
180 + ". Please provide a more specific path, for example by including a version number.", matches);
181 }
182
183
191 public String getFullPath(final String partialPath) {
192 List<String> paths = new ArrayList<>();
193
194 for(String webJarName : allWebJars.keySet()) {
195 try {
196 paths.add(getFullPath(webJarName, partialPath));
197 }
198 catch (NotFoundException e) {
199
200 }
201 }
202
203 if (paths.size() == 0) {
204 throwNotFoundException(partialPath);
205 }
206 else if (paths.size() > 1) {
207 throwMultipleMatchesException(partialPath, paths);
208 }
209
210 return paths.get(0);
211 }
212
213
220 public String getFullPath(final String webjar, final String partialPath) {
221 List<String> paths = new ArrayList<>();
222
223 for (String path : allWebJars.get(webjar).contents) {
224 if (path.endsWith(partialPath)) {
225 paths.add(path);
226 }
227 }
228
229 if (paths.size() == 0) {
230 throwNotFoundException(partialPath);
231 }
232 else if (paths.size() > 1) {
233 throwMultipleMatchesException(partialPath, paths);
234 }
235
236 return paths.get(0);
237 }
238
239
246 public String getFullPathExact(final String webJarName, final String exactPath) {
247 final String maybeVersion = getWebJars().get(webJarName);
248
249 String fullPath;
250 if (maybeVersion != null) {
251 fullPath = WEBJARS_PATH_PREFIX + "/" + webJarName + "/" + maybeVersion + "/" + exactPath;
252 }
253 else {
254 fullPath = WEBJARS_PATH_PREFIX + "/" + webJarName + "/" + exactPath;
255 }
256
257 WebJarInfo webJarInfo = allWebJars.get(webJarName);
258 if ((webJarInfo != null) && (webJarInfo.contents.contains(fullPath))) {
259 return fullPath;
260 }
261
262 return null;
263 }
264
265 public Set<String> listAssets() {
266 return listAssets("");
267 }
268
269
275 public Set<String> listAssets(final String folderPath) {
276 Set<String> assets = new HashSet<>();
277
278 final String prefix = WEBJARS_PATH_PREFIX + (!folderPath.startsWith("/") ? "/" : "") + folderPath;
279 for (final WebJarInfo webJarInfo : allWebJars.values()) {
280 for (final String path : webJarInfo.contents) {
281 if (path.startsWith(folderPath) || path.startsWith(prefix)) {
282 assets.add(path);
283 }
284 }
285 }
286
287 return assets;
288 }
289
290
293 public Map<String, String> getWebJars() {
294 Map<String, String> webJars = new HashMap<>();
295 for (String webJarName : allWebJars.keySet()) {
296 webJars.put(webJarName, allWebJars.get(webJarName).version);
297 }
298
299 return webJars;
300 }
301
302
303
309 public String groupId(final String fullPath) {
310 String groupId = null;
311
312 for (String webJarName : allWebJars.keySet()) {
313 WebJarInfo webJarInfo = allWebJars.get(webJarName);
314 if (webJarInfo.contents.contains(fullPath)) {
315 groupId = webJarInfo.groupId;
316 }
317 }
318
319 return groupId;
320 }
321
322 }
323