1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.core.io.support;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.lang.reflect.InvocationHandler;
22 import java.lang.reflect.Method;
23 import java.net.JarURLConnection;
24 import java.net.URISyntaxException;
25 import java.net.URL;
26 import java.net.URLConnection;
27 import java.util.Collections;
28 import java.util.Enumeration;
29 import java.util.LinkedHashSet;
30 import java.util.Set;
31 import java.util.jar.JarEntry;
32 import java.util.jar.JarFile;
33
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37 import org.springframework.core.io.DefaultResourceLoader;
38 import org.springframework.core.io.FileSystemResource;
39 import org.springframework.core.io.Resource;
40 import org.springframework.core.io.ResourceLoader;
41 import org.springframework.core.io.UrlResource;
42 import org.springframework.core.io.VfsResource;
43 import org.springframework.util.AntPathMatcher;
44 import org.springframework.util.Assert;
45 import org.springframework.util.PathMatcher;
46 import org.springframework.util.ReflectionUtils;
47 import org.springframework.util.ResourceUtils;
48 import org.springframework.util.StringUtils;
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167 public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
168
169 private static final Log logger = LogFactory.getLog(PathMatchingResourcePatternResolver.class);
170
171 private static Method equinoxResolveMethod;
172
173 static {
174
175 try {
176 Class<?> fileLocatorClass = PathMatchingResourcePatternResolver.class.getClassLoader().loadClass(
177 "org.eclipse.core.runtime.FileLocator");
178 equinoxResolveMethod = fileLocatorClass.getMethod("resolve", URL.class);
179 logger.debug("Found Equinox FileLocator for OSGi bundle URL resolution");
180 }
181 catch (Throwable ex) {
182 equinoxResolveMethod = null;
183 }
184 }
185
186
187 private final ResourceLoader resourceLoader;
188
189 private PathMatcher pathMatcher = new AntPathMatcher();
190
191
192
193
194
195
196
197 public PathMatchingResourcePatternResolver() {
198 this.resourceLoader = new DefaultResourceLoader();
199 }
200
201
202
203
204
205
206
207
208 public PathMatchingResourcePatternResolver(ClassLoader classLoader) {
209 this.resourceLoader = new DefaultResourceLoader(classLoader);
210 }
211
212
213
214
215
216
217
218 public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
219 Assert.notNull(resourceLoader, "ResourceLoader must not be null");
220 this.resourceLoader = resourceLoader;
221 }
222
223
224
225
226 public ResourceLoader getResourceLoader() {
227 return this.resourceLoader;
228 }
229
230
231
232
233
234 public ClassLoader getClassLoader() {
235 return getResourceLoader().getClassLoader();
236 }
237
238
239
240
241
242
243 public void setPathMatcher(PathMatcher pathMatcher) {
244 Assert.notNull(pathMatcher, "PathMatcher must not be null");
245 this.pathMatcher = pathMatcher;
246 }
247
248
249
250
251 public PathMatcher getPathMatcher() {
252 return this.pathMatcher;
253 }
254
255
256 public Resource getResource(String location) {
257 return getResourceLoader().getResource(location);
258 }
259
260 public Resource[] getResources(String locationPattern) throws IOException {
261 Assert.notNull(locationPattern, "Location pattern must not be null");
262 if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
263
264 if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
265
266 return findPathMatchingResources(locationPattern);
267 }
268 else {
269
270 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
271 }
272 }
273 else {
274
275
276 int prefixEnd = locationPattern.indexOf(":") + 1;
277 if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
278
279 return findPathMatchingResources(locationPattern);
280 }
281 else {
282
283 return new Resource[] {getResourceLoader().getResource(locationPattern)};
284 }
285 }
286 }
287
288
289
290
291
292
293
294
295
296 protected Resource[] findAllClassPathResources(String location) throws IOException {
297 String path = location;
298 if (path.startsWith("/")) {
299 path = path.substring(1);
300 }
301 Enumeration<URL> resourceUrls = getClassLoader().getResources(path);
302 Set<Resource> result = new LinkedHashSet<Resource>(16);
303 while (resourceUrls.hasMoreElements()) {
304 URL url = resourceUrls.nextElement();
305 result.add(convertClassLoaderURL(url));
306 }
307 return result.toArray(new Resource[result.size()]);
308 }
309
310
311
312
313
314
315
316
317
318 protected Resource convertClassLoaderURL(URL url) {
319 return new UrlResource(url);
320 }
321
322
323
324
325
326
327
328
329
330
331
332
333 protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
334 String rootDirPath = determineRootDir(locationPattern);
335 String subPattern = locationPattern.substring(rootDirPath.length());
336 Resource[] rootDirResources = getResources(rootDirPath);
337 Set<Resource> result = new LinkedHashSet<Resource>(16);
338 for (Resource rootDirResource : rootDirResources) {
339 rootDirResource = resolveRootDirResource(rootDirResource);
340 if (isJarResource(rootDirResource)) {
341 result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
342 }
343 else if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
344 result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
345 }
346 else {
347 result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
348 }
349 }
350 if (logger.isDebugEnabled()) {
351 logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
352 }
353 return result.toArray(new Resource[result.size()]);
354 }
355
356
357
358
359
360
361
362
363
364
365
366
367
368 protected String determineRootDir(String location) {
369 int prefixEnd = location.indexOf(":") + 1;
370 int rootDirEnd = location.length();
371 while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
372 rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
373 }
374 if (rootDirEnd == 0) {
375 rootDirEnd = prefixEnd;
376 }
377 return location.substring(0, rootDirEnd);
378 }
379
380
381
382
383
384
385
386
387
388
389 protected Resource resolveRootDirResource(Resource original) throws IOException {
390 if (equinoxResolveMethod != null) {
391 URL url = original.getURL();
392 if (url.getProtocol().startsWith("bundle")) {
393 return new UrlResource((URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, url));
394 }
395 }
396 return original;
397 }
398
399
400
401
402
403
404
405
406
407
408
409
410 protected boolean isJarResource(Resource resource) throws IOException {
411 return ResourceUtils.isJarURL(resource.getURL());
412 }
413
414
415
416
417
418
419
420
421
422
423
424 protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)
425 throws IOException {
426
427 URLConnection con = rootDirResource.getURL().openConnection();
428 JarFile jarFile;
429 String jarFileUrl;
430 String rootEntryPath;
431 boolean newJarFile = false;
432
433 if (con instanceof JarURLConnection) {
434
435 JarURLConnection jarCon = (JarURLConnection) con;
436 ResourceUtils.useCachesIfNecessary(jarCon);
437 jarFile = jarCon.getJarFile();
438 jarFileUrl = jarCon.getJarFileURL().toExternalForm();
439 JarEntry jarEntry = jarCon.getJarEntry();
440 rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
441 }
442 else {
443
444
445
446
447 String urlFile = rootDirResource.getURL().getFile();
448 int separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
449 if (separatorIndex != -1) {
450 jarFileUrl = urlFile.substring(0, separatorIndex);
451 rootEntryPath = urlFile.substring(separatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length());
452 jarFile = getJarFile(jarFileUrl);
453 }
454 else {
455 jarFile = new JarFile(urlFile);
456 jarFileUrl = urlFile;
457 rootEntryPath = "";
458 }
459 newJarFile = true;
460 }
461
462 try {
463 if (logger.isDebugEnabled()) {
464 logger.debug("Looking for matching resources in jar file [" + jarFileUrl + "]");
465 }
466 if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
467
468
469 rootEntryPath = rootEntryPath + "/";
470 }
471 Set<Resource> result = new LinkedHashSet<Resource>(8);
472 for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
473 JarEntry entry = entries.nextElement();
474 String entryPath = entry.getName();
475 if (entryPath.startsWith(rootEntryPath)) {
476 String relativePath = entryPath.substring(rootEntryPath.length());
477 if (getPathMatcher().match(subPattern, relativePath)) {
478 result.add(rootDirResource.createRelative(relativePath));
479 }
480 }
481 }
482 return result;
483 }
484 finally {
485
486
487 if (newJarFile) {
488 jarFile.close();
489 }
490 }
491 }
492
493
494
495
496 protected JarFile getJarFile(String jarFileUrl) throws IOException {
497 if (jarFileUrl.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
498 try {
499 return new JarFile(ResourceUtils.toURI(jarFileUrl).getSchemeSpecificPart());
500 }
501 catch (URISyntaxException ex) {
502
503 return new JarFile(jarFileUrl.substring(ResourceUtils.FILE_URL_PREFIX.length()));
504 }
505 }
506 else {
507 return new JarFile(jarFileUrl);
508 }
509 }
510
511
512
513
514
515
516
517
518
519
520
521 protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
522 throws IOException {
523
524 File rootDir;
525 try {
526 rootDir = rootDirResource.getFile().getAbsoluteFile();
527 }
528 catch (IOException ex) {
529 if (logger.isWarnEnabled()) {
530 logger.warn("Cannot search for matching files underneath " + rootDirResource +
531 " because it does not correspond to a directory in the file system", ex);
532 }
533 return Collections.emptySet();
534 }
535 return doFindMatchingFileSystemResources(rootDir, subPattern);
536 }
537
538
539
540
541
542
543
544
545
546
547
548 protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
549 if (logger.isDebugEnabled()) {
550 logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
551 }
552 Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
553 Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size());
554 for (File file : matchingFiles) {
555 result.add(new FileSystemResource(file));
556 }
557 return result;
558 }
559
560
561
562
563
564
565
566
567
568
569 protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
570 if (!rootDir.exists()) {
571
572 if (logger.isDebugEnabled()) {
573 logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
574 }
575 return Collections.emptySet();
576 }
577 if (!rootDir.isDirectory()) {
578
579 if (logger.isWarnEnabled()) {
580 logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
581 }
582 return Collections.emptySet();
583 }
584 if (!rootDir.canRead()) {
585 if (logger.isWarnEnabled()) {
586 logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +
587 "] because the application is not allowed to read the directory");
588 }
589 return Collections.emptySet();
590 }
591 String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
592 if (!pattern.startsWith("/")) {
593 fullPattern += "/";
594 }
595 fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
596 Set<File> result = new LinkedHashSet<File>(8);
597 doRetrieveMatchingFiles(fullPattern, rootDir, result);
598 return result;
599 }
600
601
602
603
604
605
606
607
608
609
610 protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
611 if (logger.isDebugEnabled()) {
612 logger.debug("Searching directory [" + dir.getAbsolutePath() +
613 "] for files matching pattern [" + fullPattern + "]");
614 }
615 File[] dirContents = dir.listFiles();
616 if (dirContents == null) {
617 if (logger.isWarnEnabled()) {
618 logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
619 }
620 return;
621 }
622 for (File content : dirContents) {
623 String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
624 if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
625 if (!content.canRead()) {
626 if (logger.isDebugEnabled()) {
627 logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
628 "] because the application is not allowed to read the directory");
629 }
630 }
631 else {
632 doRetrieveMatchingFiles(fullPattern, content, result);
633 }
634 }
635 if (getPathMatcher().match(fullPattern, currPath)) {
636 result.add(content);
637 }
638 }
639 }
640
641
642
643
644
645 private static class VfsResourceMatchingDelegate {
646
647 public static Set<Resource> findMatchingResources(
648 Resource rootResource, String locationPattern, PathMatcher pathMatcher) throws IOException {
649 Object root = VfsPatternUtils.findRoot(rootResource.getURL());
650 PatternVirtualFileVisitor visitor =
651 new PatternVirtualFileVisitor(VfsPatternUtils.getPath(root), locationPattern, pathMatcher);
652 VfsPatternUtils.visit(root, visitor);
653 return visitor.getResources();
654 }
655 }
656
657
658
659
660
661 private static class PatternVirtualFileVisitor implements InvocationHandler {
662
663 private final String subPattern;
664
665 private final PathMatcher pathMatcher;
666
667 private final String rootPath;
668
669 private final Set<Resource> resources = new LinkedHashSet<Resource>();
670
671 public PatternVirtualFileVisitor(String rootPath, String subPattern, PathMatcher pathMatcher) {
672 this.subPattern = subPattern;
673 this.pathMatcher = pathMatcher;
674 this.rootPath = (rootPath.length() == 0 || rootPath.endsWith("/") ? rootPath : rootPath + "/");
675 }
676
677 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
678 String methodName = method.getName();
679 if (Object.class.equals(method.getDeclaringClass())) {
680 if (methodName.equals("equals")) {
681
682 return (proxy == args[0]);
683 }
684 else if (methodName.equals("hashCode")) {
685 return System.identityHashCode(proxy);
686 }
687 }
688 else if ("getAttributes".equals(methodName)) {
689 return getAttributes();
690 }
691 else if ("visit".equals(methodName)) {
692 visit(args[0]);
693 return null;
694 }
695 else if ("toString".equals(methodName)) {
696 return toString();
697 }
698
699 throw new IllegalStateException("Unexpected method invocation: " + method);
700 }
701
702 public void visit(Object vfsResource) {
703 if (this.pathMatcher.match(this.subPattern,
704 VfsPatternUtils.getPath(vfsResource).substring(this.rootPath.length()))) {
705 this.resources.add(new VfsResource(vfsResource));
706 }
707 }
708
709 public Object getAttributes() {
710 return VfsPatternUtils.getVisitorAttribute();
711 }
712
713 public Set<Resource> getResources() {
714 return this.resources;
715 }
716
717 @SuppressWarnings("unused")
718 public int size() {
719 return this.resources.size();
720 }
721
722 public String toString() {
723 StringBuilder sb = new StringBuilder();
724 sb.append("sub-pattern: ").append(this.subPattern);
725 sb.append(", resources: ").append(this.resources);
726 return sb.toString();
727 }
728 }
729
730 }