1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.osgi.io;
18
19 import java.io.IOException;
20 import java.net.URL;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Enumeration;
24 import java.util.Iterator;
25 import java.util.LinkedHashSet;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.jar.JarEntry;
29 import java.util.jar.JarInputStream;
30
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.osgi.framework.Bundle;
34 import org.osgi.framework.BundleContext;
35 import org.osgi.framework.Constants;
36 import org.springframework.core.io.ContextResource;
37 import org.springframework.core.io.Resource;
38 import org.springframework.core.io.ResourceLoader;
39 import org.springframework.core.io.UrlResource;
40 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
41 import org.springframework.core.io.support.ResourcePatternResolver;
42 import org.springframework.osgi.io.internal.OsgiHeaderUtils;
43 import org.springframework.osgi.io.internal.OsgiResourceUtils;
44 import org.springframework.osgi.io.internal.OsgiUtils;
45 import org.springframework.osgi.io.internal.resolver.DependencyResolver;
46 import org.springframework.osgi.io.internal.resolver.ImportedBundle;
47 import org.springframework.osgi.io.internal.resolver.PackageAdminResolver;
48 import org.springframework.util.Assert;
49 import org.springframework.util.CollectionUtils;
50 import org.springframework.util.ObjectUtils;
51 import org.springframework.util.PathMatcher;
52 import org.springframework.util.StringUtils;
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 public class OsgiBundleResourcePatternResolver extends PathMatchingResourcePatternResolver {
98
99
100
101
102 private static final Log logger = LogFactory.getLog(OsgiBundleResourcePatternResolver.class);
103
104
105
106
107 private final Bundle bundle;
108
109
110
111
112 private final BundleContext bundleContext;
113
114 private static final String FOLDER_SEPARATOR = "/";
115
116 private static final String FOLDER_WILDCARD = "**";
117
118 private static final String JAR_EXTENSION = ".jar";
119
120 private static final String BUNDLE_DEFAULT_CP = ".";
121
122 private static final char SLASH = '/';
123
124 private static final char DOT = '.';
125
126
127 private final DependencyResolver resolver;
128
129
130 public OsgiBundleResourcePatternResolver(Bundle bundle) {
131 this(new OsgiBundleResourceLoader(bundle));
132 }
133
134 public OsgiBundleResourcePatternResolver(ResourceLoader resourceLoader) {
135 super(resourceLoader);
136 if (resourceLoader instanceof OsgiBundleResourceLoader) {
137 this.bundle = ((OsgiBundleResourceLoader) resourceLoader).getBundle();
138 }
139 else {
140 this.bundle = null;
141 }
142
143 this.bundleContext = (bundle != null ? OsgiUtils.getBundleContext(this.bundle) : null);
144 this.resolver = (bundleContext != null ? new PackageAdminResolver(bundleContext) : null);
145 }
146
147
148
149
150
151
152
153
154
155 protected Resource[] findResources(String locationPattern) throws IOException {
156 Assert.notNull(locationPattern, "Location pattern must not be null");
157 int type = OsgiResourceUtils.getSearchType(locationPattern);
158
159
160 if (getPathMatcher().isPattern(locationPattern)) {
161
162 if (OsgiResourceUtils.isClassPathType(type))
163 return findClassPathMatchingResources(locationPattern, type);
164
165 return findPathMatchingResources(locationPattern, type);
166 }
167
168
169
170 else {
171 Resource[] result = null;
172
173 OsgiBundleResource resource = new OsgiBundleResource(bundle, locationPattern);
174
175 switch (type) {
176
177 case OsgiResourceUtils.PREFIX_TYPE_NOT_SPECIFIED:
178
179 case OsgiResourceUtils.PREFIX_TYPE_BUNDLE_SPACE:
180 result = resource.getAllUrlsFromBundleSpace(locationPattern);
181 break;
182
183 default:
184 if (!resource.exists())
185 result = new Resource[] { resource };
186 break;
187 }
188 return result;
189 }
190 }
191
192
193 public Resource[] getResources(String locationPattern) throws IOException {
194 Resource[] resources = findResources(locationPattern);
195
196
197
198 if (ObjectUtils.isEmpty(resources) && (!getPathMatcher().isPattern(locationPattern))) {
199 return new Resource[] { getResourceLoader().getResource(locationPattern) };
200 }
201
202 return resources;
203 }
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219 private Resource[] findClassPathMatchingResources(String locationPattern, int type) throws IOException {
220
221 if (resolver == null)
222 throw new IllegalArgumentException(
223 "PackageAdmin service/a started bundle is required for classpath matching");
224
225 ImportedBundle[] importedBundles = resolver.getImportedBundles(bundle);
226
227
228 String path = OsgiResourceUtils.stripPrefix(locationPattern);
229
230 Collection foundPaths = new LinkedHashSet();
231
232
233
234
235 String rootDirPath = determineFolderPattern(path);
236
237 for (int i = 0; i < importedBundles.length; i++) {
238 ImportedBundle importedBundle = importedBundles[i];
239 if (!bundle.equals(importedBundle.getBundle())) {
240 findImportedBundleMatchingResource(importedBundle, rootDirPath, path, foundPaths);
241 }
242 }
243
244
245 findSyntheticClassPathMatchingResource(bundle, path, foundPaths);
246
247
248 List resources = new ArrayList(foundPaths.size());
249
250 for (Iterator iterator = foundPaths.iterator(); iterator.hasNext();) {
251
252 String resourcePath = (String) iterator.next();
253 if (OsgiResourceUtils.PREFIX_TYPE_CLASS_ALL_SPACE == type) {
254 CollectionUtils.mergeArrayIntoCollection(convertURLEnumerationToResourceArray(
255 bundle.getResources(resourcePath), resourcePath), resources);
256 }
257
258 else {
259 URL url = bundle.getResource(resourcePath);
260 if (url != null)
261 resources.add(new UrlContextResource(url, resourcePath));
262 }
263 }
264
265 if (logger.isTraceEnabled()) {
266 logger.trace("Fitered " + foundPaths + " to " + resources);
267 }
268
269 return (Resource[]) resources.toArray(new Resource[resources.size()]);
270 }
271
272 private String determineFolderPattern(String path) {
273 int index = path.lastIndexOf(FOLDER_SEPARATOR);
274 return (index > 0 ? path.substring(0, index + 1) : "");
275 }
276
277 private ContextResource[] convertURLEnumerationToResourceArray(Enumeration enm, String path) {
278 Set resources = new LinkedHashSet(4);
279 while (enm != null && enm.hasMoreElements()) {
280 resources.add(new UrlContextResource((URL) enm.nextElement(), path));
281 }
282 return (ContextResource[]) resources.toArray(new ContextResource[resources.size()]);
283 }
284
285
286
287
288
289
290
291
292
293 private void findImportedBundleMatchingResource(ImportedBundle importedBundle, String rootPath, String path,
294 Collection foundPaths) {
295 String[] packages = importedBundle.getImportedPackages();
296
297 boolean startsWithSlash = rootPath.startsWith(FOLDER_SEPARATOR);
298
299 for (int i = 0; i < packages.length; i++) {
300
301 String pkg = packages[i].replace(DOT, SLASH) + SLASH;
302
303 if (startsWithSlash) {
304 pkg = FOLDER_SEPARATOR + pkg;
305 }
306
307 PathMatcher matcher = getPathMatcher();
308
309 if (matcher.matchStart(path, pkg)) {
310
311 Enumeration entries = importedBundle.getBundle().getEntryPaths(pkg);
312 while (entries != null && entries.hasMoreElements()) {
313 String entry = (String) entries.nextElement();
314 if (startsWithSlash)
315 entry = FOLDER_SEPARATOR + entry;
316
317 if (matcher.match(path, entry))
318 foundPaths.add(entry);
319 }
320 }
321 }
322 }
323
324
325
326
327
328
329
330
331
332
333 private void findSyntheticClassPathMatchingResource(Bundle bundle, String path, Collection foundPaths)
334 throws IOException {
335
336 OsgiBundleResourcePatternResolver localPatternResolver = new OsgiBundleResourcePatternResolver(bundle);
337 Resource[] foundResources = localPatternResolver.findResources(path);
338 for (int j = 0; j < foundResources.length; j++) {
339
340 foundPaths.add(foundResources[j].getURL().getPath());
341 }
342
343 foundPaths.addAll(findBundleClassPathMatchingPaths(bundle, path));
344
345
346 }
347
348
349
350
351
352
353
354
355
356
357 private Collection findBundleClassPathMatchingPaths(Bundle bundle, String pattern) throws IOException {
358
359 List list = new ArrayList(4);
360
361 boolean trace = logger.isTraceEnabled();
362 if (trace)
363 logger.trace("Analyzing " + Constants.BUNDLE_CLASSPATH + " entries for bundle [" + bundle.getBundleId()
364 + "|" + bundle.getSymbolicName() + "]");
365
366 String[] entries = OsgiHeaderUtils.getBundleClassPath(bundle);
367
368 if (trace)
369 logger.trace("Found " + Constants.BUNDLE_CLASSPATH + " entries " + ObjectUtils.nullSafeToString(entries));
370
371
372 for (int i = 0; i < entries.length; i++) {
373 String entry = entries[i];
374
375
376 if (!entry.equals(BUNDLE_DEFAULT_CP)) {
377
378
379 OsgiBundleResource entryResource = new OsgiBundleResource(bundle, entry);
380
381 URL url = null;
382 ContextResource res = entryResource.getResourceFromBundleSpace(entry);
383 if (res != null) {
384 url = res.getURL();
385 }
386
387 if (trace)
388 logger.trace("Classpath entry [" + entry + "] resolves to [" + url + "]");
389
390 if (url != null) {
391 String cpEntryPath = url.getPath();
392
393 if (entry.endsWith(JAR_EXTENSION))
394 findBundleClassPathMatchingJarEntries(list, url, pattern);
395
396 else
397 findBundleClassPathMatchingFolders(list, bundle, cpEntryPath, pattern);
398 }
399 }
400 }
401
402 return list;
403 }
404
405
406
407
408
409
410
411 private void findBundleClassPathMatchingJarEntries(List list, URL url, String pattern) throws IOException {
412
413 JarInputStream jis = new JarInputStream(url.openStream());
414 Set result = new LinkedHashSet(8);
415
416
417 try {
418 while (jis.available() > 0) {
419 JarEntry jarEntry = jis.getNextJarEntry();
420
421 if (jarEntry != null) {
422 String entryPath = jarEntry.getName();
423
424
425 if (entryPath.startsWith(FOLDER_SEPARATOR)) {
426 entryPath = entryPath.substring(FOLDER_SEPARATOR.length());
427 }
428 if (getPathMatcher().match(pattern, entryPath)) {
429 result.add(entryPath);
430 }
431 }
432 }
433 }
434 finally {
435 try {
436 jis.close();
437 }
438 catch (IOException io) {
439
440 }
441 }
442
443 if (logger.isTraceEnabled())
444 logger.trace("Found in nested jar [" + url + "] matching entries " + result);
445
446 list.addAll(result);
447 }
448
449
450
451
452
453
454
455
456
457
458
459 private void findBundleClassPathMatchingFolders(List list, Bundle bundle, String cpEntryPath, String pattern)
460 throws IOException {
461
462
463
464 String bundlePathPattern;
465
466 boolean entryWithFolderSlash = cpEntryPath.endsWith(FOLDER_SEPARATOR);
467 boolean patternWithFolderSlash = pattern.startsWith(FOLDER_SEPARATOR);
468
469 if (entryWithFolderSlash) {
470 if (patternWithFolderSlash)
471 bundlePathPattern = cpEntryPath + pattern.substring(1, pattern.length());
472 else
473 bundlePathPattern = cpEntryPath + pattern;
474 }
475 else {
476 if (patternWithFolderSlash)
477 bundlePathPattern = cpEntryPath + pattern;
478 else
479 bundlePathPattern = cpEntryPath + FOLDER_SEPARATOR + pattern;
480 }
481
482
483 OsgiBundleResourcePatternResolver localResolver = new OsgiBundleResourcePatternResolver(bundle);
484 Resource[] resources = localResolver.getResources(bundlePathPattern);
485
486 boolean trace = logger.isTraceEnabled();
487 List foundResources = (trace ? new ArrayList(resources.length) : null);
488
489 try {
490
491 if (resources.length == 1 && !resources[0].exists()) {
492 return;
493 }
494 else {
495 int cutStartingIndex = cpEntryPath.length();
496
497 for (int i = 0; i < resources.length; i++) {
498 String path = resources[i].getURL().getPath().substring(cutStartingIndex);
499 list.add(path);
500 if (trace)
501 foundResources.add(path);
502 }
503 }
504 }
505 finally {
506 if (trace)
507 logger.trace("Searching for [" + bundlePathPattern + "] revealed resources (relative to the cp entry ["
508 + cpEntryPath + "]): " + foundResources);
509 }
510 }
511
512
513
514
515
516
517
518 private Resource[] findPathMatchingResources(String locationPattern, int searchType) throws IOException {
519 String rootDirPath = determineRootDir(locationPattern);
520 String subPattern = locationPattern.substring(rootDirPath.length());
521 Resource[] rootDirResources = getResources(rootDirPath);
522
523 Set result = new LinkedHashSet(16);
524 for (int i = 0; i < rootDirResources.length; i++) {
525 Resource rootDirResource = rootDirResources[i];
526 if (isJarResource(rootDirResource)) {
527 result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
528 }
529 else {
530 result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern, searchType));
531 }
532 }
533 if (logger.isTraceEnabled()) {
534 logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
535 }
536 return (Resource[]) result.toArray(new Resource[result.size()]);
537 }
538
539
540
541
542
543
544
545
546 protected boolean isJarResource(Resource resource) throws IOException {
547 if (resource instanceof OsgiBundleResource) {
548
549 OsgiBundleResource bundleResource = (OsgiBundleResource) resource;
550
551 if (bundleResource.getSearchType() != OsgiResourceUtils.PREFIX_TYPE_UNKNOWN) {
552 return false;
553 }
554
555 }
556 return super.isJarResource(resource);
557 }
558
559
560
561
562
563
564
565 private Set doFindPathMatchingFileResources(Resource rootDirResource, String subPattern, int searchType)
566 throws IOException {
567
568 String rootPath = null;
569
570 if (rootDirResource instanceof OsgiBundleResource) {
571 OsgiBundleResource bundleResource = (OsgiBundleResource) rootDirResource;
572 rootPath = bundleResource.getPath();
573 searchType = bundleResource.getSearchType();
574 }
575 else if (rootDirResource instanceof UrlResource) {
576 rootPath = rootDirResource.getURL().getPath();
577 }
578
579 if (rootPath != null) {
580 String cleanPath = OsgiResourceUtils.stripPrefix(rootPath);
581
582 if (!cleanPath.endsWith(FOLDER_SEPARATOR)) {
583 cleanPath = cleanPath + FOLDER_SEPARATOR;
584 }
585 String fullPattern = cleanPath + subPattern;
586 Set result = new LinkedHashSet(16);
587 doRetrieveMatchingBundleEntries(bundle, fullPattern, cleanPath, result, searchType);
588 return result;
589 }
590 else {
591 return super.doFindPathMatchingFileResources(rootDirResource, subPattern);
592 }
593 }
594
595
596
597
598
599
600
601
602
603
604
605
606 private void doRetrieveMatchingBundleEntries(Bundle bundle, String fullPattern, String dir, Set result,
607 int searchType) throws IOException {
608
609 Enumeration candidates;
610
611 switch (searchType) {
612 case OsgiResourceUtils.PREFIX_TYPE_NOT_SPECIFIED:
613 case OsgiResourceUtils.PREFIX_TYPE_BUNDLE_SPACE:
614
615 candidates = bundle.findEntries(dir, null, false);
616 break;
617 case OsgiResourceUtils.PREFIX_TYPE_BUNDLE_JAR:
618
619 candidates = bundle.getEntryPaths(dir);
620 break;
621 case OsgiResourceUtils.PREFIX_TYPE_CLASS_SPACE:
622
623 throw new IllegalArgumentException("class space does not support pattern matching");
624 default:
625 throw new IllegalArgumentException("unknown searchType " + searchType);
626 }
627
628
629 if (candidates != null) {
630 boolean dirDepthNotFixed = (fullPattern.indexOf(FOLDER_WILDCARD) != -1);
631 while (candidates.hasMoreElements()) {
632
633 Object path = candidates.nextElement();
634 String currPath;
635
636 if (path instanceof String)
637 currPath = handleString((String) path);
638 else
639 currPath = handleURL((URL) path);
640
641 if (!currPath.startsWith(dir)) {
642
643
644
645 int dirIndex = currPath.indexOf(dir);
646 if (dirIndex != -1) {
647 currPath = currPath.substring(dirIndex);
648 }
649 }
650 if (currPath.endsWith(FOLDER_SEPARATOR)
651 && (dirDepthNotFixed || StringUtils.countOccurrencesOf(currPath, FOLDER_SEPARATOR) < StringUtils.countOccurrencesOf(
652 fullPattern, FOLDER_SEPARATOR))) {
653
654
655
656 doRetrieveMatchingBundleEntries(bundle, fullPattern, currPath, result, searchType);
657 }
658 if (getPathMatcher().match(fullPattern, currPath)) {
659 if (path instanceof URL)
660 result.add(new UrlContextResource((URL) path, currPath));
661 else
662 result.add(new OsgiBundleResource(bundle, currPath));
663
664 }
665 }
666 }
667 }
668
669
670
671
672
673
674
675 private String handleURL(URL path) {
676 return path.getPath();
677 }
678
679
680
681
682
683
684
685 private String handleString(String path) {
686 return FOLDER_SEPARATOR.concat(path);
687 }
688 }