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.MalformedURLException;
25 import java.net.URISyntaxException;
26 import java.net.URL;
27 import java.net.URLClassLoader;
28 import java.net.URLConnection;
29 import java.util.Collections;
30 import java.util.Enumeration;
31 import java.util.LinkedHashSet;
32 import java.util.Set;
33 import java.util.jar.JarEntry;
34 import java.util.jar.JarFile;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38
39 import org.springframework.core.io.DefaultResourceLoader;
40 import org.springframework.core.io.FileSystemResource;
41 import org.springframework.core.io.Resource;
42 import org.springframework.core.io.ResourceLoader;
43 import org.springframework.core.io.UrlResource;
44 import org.springframework.core.io.VfsResource;
45 import org.springframework.util.AntPathMatcher;
46 import org.springframework.util.Assert;
47 import org.springframework.util.ClassUtils;
48 import org.springframework.util.PathMatcher;
49 import org.springframework.util.ReflectionUtils;
50 import org.springframework.util.ResourceUtils;
51 import org.springframework.util.StringUtils;
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
168
169
170
171
172
173
174 public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
175
176 private static final Log logger = LogFactory.getLog(PathMatchingResourcePatternResolver.class);
177
178 private static Method equinoxResolveMethod;
179
180 static {
181 try {
182
183 Class<?> fileLocatorClass = ClassUtils.forName("org.eclipse.core.runtime.FileLocator",
184 PathMatchingResourcePatternResolver.class.getClassLoader());
185 equinoxResolveMethod = fileLocatorClass.getMethod("resolve", URL.class);
186 logger.debug("Found Equinox FileLocator for OSGi bundle URL resolution");
187 }
188 catch (Throwable ex) {
189 equinoxResolveMethod = null;
190 }
191 }
192
193
194 private final ResourceLoader resourceLoader;
195
196 private PathMatcher pathMatcher = new AntPathMatcher();
197
198
199
200
201
202
203
204 public PathMatchingResourcePatternResolver() {
205 this.resourceLoader = new DefaultResourceLoader();
206 }
207
208
209
210
211
212
213
214 public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
215 Assert.notNull(resourceLoader, "ResourceLoader must not be null");
216 this.resourceLoader = resourceLoader;
217 }
218
219
220
221
222
223
224
225
226 public PathMatchingResourcePatternResolver(ClassLoader classLoader) {
227 this.resourceLoader = new DefaultResourceLoader(classLoader);
228 }
229
230
231
232
233
234 public ResourceLoader getResourceLoader() {
235 return this.resourceLoader;
236 }
237
238 @Override
239 public ClassLoader getClassLoader() {
240 return getResourceLoader().getClassLoader();
241 }
242
243
244
245
246
247
248 public void setPathMatcher(PathMatcher pathMatcher) {
249 Assert.notNull(pathMatcher, "PathMatcher must not be null");
250 this.pathMatcher = pathMatcher;
251 }
252
253
254
255
256 public PathMatcher getPathMatcher() {
257 return this.pathMatcher;
258 }
259
260
261 @Override
262 public Resource getResource(String location) {
263 return getResourceLoader().getResource(location);
264 }
265
266 @Override
267 public Resource[] getResources(String locationPattern) throws IOException {
268 Assert.notNull(locationPattern, "Location pattern must not be null");
269 if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
270
271 if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
272
273 return findPathMatchingResources(locationPattern);
274 }
275 else {
276
277 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
278 }
279 }
280 else {
281
282
283 int prefixEnd = locationPattern.indexOf(":") + 1;
284 if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
285
286 return findPathMatchingResources(locationPattern);
287 }
288 else {
289
290 return new Resource[] {getResourceLoader().getResource(locationPattern)};
291 }
292 }
293 }
294
295
296
297
298
299
300
301
302
303
304 protected Resource[] findAllClassPathResources(String location) throws IOException {
305 String path = location;
306 if (path.startsWith("/")) {
307 path = path.substring(1);
308 }
309 Set<Resource> result = doFindAllClassPathResources(path);
310 return result.toArray(new Resource[result.size()]);
311 }
312
313
314
315
316
317
318
319 protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
320 Set<Resource> result = new LinkedHashSet<Resource>(16);
321 ClassLoader cl = getClassLoader();
322 Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
323 while (resourceUrls.hasMoreElements()) {
324 URL url = resourceUrls.nextElement();
325 result.add(convertClassLoaderURL(url));
326 }
327 if ("".equals(path)) {
328
329
330 addAllClassLoaderJarRoots(cl, result);
331 }
332 return result;
333 }
334
335
336
337
338
339
340
341
342
343 protected Resource convertClassLoaderURL(URL url) {
344 return new UrlResource(url);
345 }
346
347
348
349
350
351
352
353 protected void addAllClassLoaderJarRoots(ClassLoader classLoader, Set<Resource> result) {
354 if (classLoader instanceof URLClassLoader) {
355 try {
356 for (URL url : ((URLClassLoader) classLoader).getURLs()) {
357 if (ResourceUtils.isJarFileURL(url)) {
358 try {
359 UrlResource jarResource = new UrlResource(
360 ResourceUtils.JAR_URL_PREFIX + url.toString() + ResourceUtils.JAR_URL_SEPARATOR);
361 if (jarResource.exists()) {
362 result.add(jarResource);
363 }
364 }
365 catch (MalformedURLException ex) {
366 if (logger.isDebugEnabled()) {
367 logger.debug("Cannot search for matching files underneath [" + url +
368 "] because it cannot be converted to a valid 'jar:' URL: " + ex.getMessage());
369 }
370 }
371 }
372 }
373 }
374 catch (Exception ex) {
375 if (logger.isDebugEnabled()) {
376 logger.debug("Cannot introspect jar files since ClassLoader [" + classLoader +
377 "] does not support 'getURLs()': " + ex);
378 }
379 }
380 }
381 if (classLoader != null) {
382 try {
383 addAllClassLoaderJarRoots(classLoader.getParent(), result);
384 }
385 catch (Exception ex) {
386 if (logger.isDebugEnabled()) {
387 logger.debug("Cannot introspect jar files in parent ClassLoader since [" + classLoader +
388 "] does not support 'getParent()': " + ex);
389 }
390 }
391 }
392 }
393
394
395
396
397
398
399
400
401
402
403
404
405 protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
406 String rootDirPath = determineRootDir(locationPattern);
407 String subPattern = locationPattern.substring(rootDirPath.length());
408 Resource[] rootDirResources = getResources(rootDirPath);
409 Set<Resource> result = new LinkedHashSet<Resource>(16);
410 for (Resource rootDirResource : rootDirResources) {
411 rootDirResource = resolveRootDirResource(rootDirResource);
412 if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
413 result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
414 }
415 else if (isJarResource(rootDirResource)) {
416 result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
417 }
418 else {
419 result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
420 }
421 }
422 if (logger.isDebugEnabled()) {
423 logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
424 }
425 return result.toArray(new Resource[result.size()]);
426 }
427
428
429
430
431
432
433
434
435
436
437
438
439
440 protected String determineRootDir(String location) {
441 int prefixEnd = location.indexOf(":") + 1;
442 int rootDirEnd = location.length();
443 while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
444 rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
445 }
446 if (rootDirEnd == 0) {
447 rootDirEnd = prefixEnd;
448 }
449 return location.substring(0, rootDirEnd);
450 }
451
452
453
454
455
456
457
458
459
460
461 protected Resource resolveRootDirResource(Resource original) throws IOException {
462 if (equinoxResolveMethod != null) {
463 URL url = original.getURL();
464 if (url.getProtocol().startsWith("bundle")) {
465 return new UrlResource((URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, url));
466 }
467 }
468 return original;
469 }
470
471
472
473
474
475
476
477
478
479
480
481
482 protected boolean isJarResource(Resource resource) throws IOException {
483 return ResourceUtils.isJarURL(resource.getURL());
484 }
485
486
487
488
489
490
491
492
493
494
495
496 protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)
497 throws IOException {
498
499 URLConnection con = rootDirResource.getURL().openConnection();
500 JarFile jarFile;
501 String jarFileUrl;
502 String rootEntryPath;
503 boolean newJarFile = false;
504
505 if (con instanceof JarURLConnection) {
506
507 JarURLConnection jarCon = (JarURLConnection) con;
508 ResourceUtils.useCachesIfNecessary(jarCon);
509 jarFile = jarCon.getJarFile();
510 jarFileUrl = jarCon.getJarFileURL().toExternalForm();
511 JarEntry jarEntry = jarCon.getJarEntry();
512 rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
513 }
514 else {
515
516
517
518
519 String urlFile = rootDirResource.getURL().getFile();
520 int separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
521 if (separatorIndex != -1) {
522 jarFileUrl = urlFile.substring(0, separatorIndex);
523 rootEntryPath = urlFile.substring(separatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length());
524 jarFile = getJarFile(jarFileUrl);
525 }
526 else {
527 jarFile = new JarFile(urlFile);
528 jarFileUrl = urlFile;
529 rootEntryPath = "";
530 }
531 newJarFile = true;
532 }
533
534 try {
535 if (logger.isDebugEnabled()) {
536 logger.debug("Looking for matching resources in jar file [" + jarFileUrl + "]");
537 }
538 if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
539
540
541 rootEntryPath = rootEntryPath + "/";
542 }
543 Set<Resource> result = new LinkedHashSet<Resource>(8);
544 for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
545 JarEntry entry = entries.nextElement();
546 String entryPath = entry.getName();
547 if (entryPath.startsWith(rootEntryPath)) {
548 String relativePath = entryPath.substring(rootEntryPath.length());
549 if (getPathMatcher().match(subPattern, relativePath)) {
550 result.add(rootDirResource.createRelative(relativePath));
551 }
552 }
553 }
554 return result;
555 }
556 finally {
557
558
559 if (newJarFile) {
560 jarFile.close();
561 }
562 }
563 }
564
565
566
567
568 protected JarFile getJarFile(String jarFileUrl) throws IOException {
569 if (jarFileUrl.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
570 try {
571 return new JarFile(ResourceUtils.toURI(jarFileUrl).getSchemeSpecificPart());
572 }
573 catch (URISyntaxException ex) {
574
575 return new JarFile(jarFileUrl.substring(ResourceUtils.FILE_URL_PREFIX.length()));
576 }
577 }
578 else {
579 return new JarFile(jarFileUrl);
580 }
581 }
582
583
584
585
586
587
588
589
590
591
592
593 protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
594 throws IOException {
595
596 File rootDir;
597 try {
598 rootDir = rootDirResource.getFile().getAbsoluteFile();
599 }
600 catch (IOException ex) {
601 if (logger.isWarnEnabled()) {
602 logger.warn("Cannot search for matching files underneath " + rootDirResource +
603 " because it does not correspond to a directory in the file system", ex);
604 }
605 return Collections.emptySet();
606 }
607 return doFindMatchingFileSystemResources(rootDir, subPattern);
608 }
609
610
611
612
613
614
615
616
617
618
619
620 protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
621 if (logger.isDebugEnabled()) {
622 logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
623 }
624 Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
625 Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size());
626 for (File file : matchingFiles) {
627 result.add(new FileSystemResource(file));
628 }
629 return result;
630 }
631
632
633
634
635
636
637
638
639
640
641 protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
642 if (!rootDir.exists()) {
643
644 if (logger.isDebugEnabled()) {
645 logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
646 }
647 return Collections.emptySet();
648 }
649 if (!rootDir.isDirectory()) {
650
651 if (logger.isWarnEnabled()) {
652 logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
653 }
654 return Collections.emptySet();
655 }
656 if (!rootDir.canRead()) {
657 if (logger.isWarnEnabled()) {
658 logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +
659 "] because the application is not allowed to read the directory");
660 }
661 return Collections.emptySet();
662 }
663 String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
664 if (!pattern.startsWith("/")) {
665 fullPattern += "/";
666 }
667 fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
668 Set<File> result = new LinkedHashSet<File>(8);
669 doRetrieveMatchingFiles(fullPattern, rootDir, result);
670 return result;
671 }
672
673
674
675
676
677
678
679
680
681
682 protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
683 if (logger.isDebugEnabled()) {
684 logger.debug("Searching directory [" + dir.getAbsolutePath() +
685 "] for files matching pattern [" + fullPattern + "]");
686 }
687 File[] dirContents = dir.listFiles();
688 if (dirContents == null) {
689 if (logger.isWarnEnabled()) {
690 logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
691 }
692 return;
693 }
694 for (File content : dirContents) {
695 String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
696 if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
697 if (!content.canRead()) {
698 if (logger.isDebugEnabled()) {
699 logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
700 "] because the application is not allowed to read the directory");
701 }
702 }
703 else {
704 doRetrieveMatchingFiles(fullPattern, content, result);
705 }
706 }
707 if (getPathMatcher().match(fullPattern, currPath)) {
708 result.add(content);
709 }
710 }
711 }
712
713
714
715
716
717 private static class VfsResourceMatchingDelegate {
718
719 public static Set<Resource> findMatchingResources(
720 Resource rootResource, String locationPattern, PathMatcher pathMatcher) throws IOException {
721 Object root = VfsPatternUtils.findRoot(rootResource.getURL());
722 PatternVirtualFileVisitor visitor =
723 new PatternVirtualFileVisitor(VfsPatternUtils.getPath(root), locationPattern, pathMatcher);
724 VfsPatternUtils.visit(root, visitor);
725 return visitor.getResources();
726 }
727 }
728
729
730
731
732
733 @SuppressWarnings("unused")
734 private static class PatternVirtualFileVisitor implements InvocationHandler {
735
736 private final String subPattern;
737
738 private final PathMatcher pathMatcher;
739
740 private final String rootPath;
741
742 private final Set<Resource> resources = new LinkedHashSet<Resource>();
743
744 public PatternVirtualFileVisitor(String rootPath, String subPattern, PathMatcher pathMatcher) {
745 this.subPattern = subPattern;
746 this.pathMatcher = pathMatcher;
747 this.rootPath = (rootPath.length() == 0 || rootPath.endsWith("/") ? rootPath : rootPath + "/");
748 }
749
750 @Override
751 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
752 String methodName = method.getName();
753 if (Object.class.equals(method.getDeclaringClass())) {
754 if (methodName.equals("equals")) {
755
756 return (proxy == args[0]);
757 }
758 else if (methodName.equals("hashCode")) {
759 return System.identityHashCode(proxy);
760 }
761 }
762 else if ("getAttributes".equals(methodName)) {
763 return getAttributes();
764 }
765 else if ("visit".equals(methodName)) {
766 visit(args[0]);
767 return null;
768 }
769 else if ("toString".equals(methodName)) {
770 return toString();
771 }
772
773 throw new IllegalStateException("Unexpected method invocation: " + method);
774 }
775
776 public void visit(Object vfsResource) {
777 if (this.pathMatcher.match(this.subPattern,
778 VfsPatternUtils.getPath(vfsResource).substring(this.rootPath.length()))) {
779 this.resources.add(new VfsResource(vfsResource));
780 }
781 }
782
783 public Object getAttributes() {
784 return VfsPatternUtils.getVisitorAttribute();
785 }
786
787 public Set<Resource> getResources() {
788 return this.resources;
789 }
790
791 public int size() {
792 return this.resources.size();
793 }
794
795 @Override
796 public String toString() {
797 return "sub-pattern: " + this.subPattern + ", resources: " + this.resources;
798 }
799 }
800
801 }