View Javadoc

1   /*
2    * Copyright 2006-2008 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * OSGi-aware {@link ResourcePatternResolver}.
56   * 
57   * Can find resources in the <em>bundle jar</em> and <em>bundle space</em>.
58   * See {@link OsgiBundleResource} for more information.
59   * 
60   * <p/><b>ClassPath support</b>
61   * 
62   * <p/>As mentioned by {@link PathMatchingResourcePatternResolver}, class-path
63   * pattern matching needs to resolve the class-path structure to a file-system
64   * location (be it an actual folder or a jar). Inside the OSGi environment this
65   * is problematic as the bundles can be loaded in memory directly from input
66   * streams. To avoid relying on each platform bundle storage structure, this
67   * implementation tries to determine the bundles that assemble the given bundle
68   * class-path and analyze each of them individually. This involves the bundle
69   * archive (including special handling of the <code>Bundle-Classpath</code> as
70   * it is computed at runtime), the bundle required packages and its attached
71   * fragments.
72   * 
73   * Depending on the configuration of running environment, this might cause
74   * significant IO activity which can affect performance.
75   * 
76   * <p/><b>Note:</b> Currently, <em>static</em> imports as well as
77   * <code>Bundle-Classpath</code> and <code>Required-Bundle</code> entries
78   * are supported. Support for <code>DynamicPackage-Import</code> depends on
79   * how/when the underlying platform does the wiring between the dynamically
80   * imported bundle and the given bundle.
81   * 
82   * <p/><b>Portability Note:</b> Since it relies only on the OSGi API, this
83   * implementation depends heavily on how closely the platform implements the
84   * OSGi spec. While significant tests have been made to ensure compatibility,
85   * one <em>might</em> experience different behaviour especially when dealing
86   * with jars with missing folder entries or boot-path delegation. It is strongly
87   * recommended that wildcard resolution be thoroughly tested before switching to
88   * a different platform before you rely on it.
89   * 
90   * @see Bundle
91   * @see OsgiBundleResource
92   * @see PathMatchingResourcePatternResolver
93   * 
94   * @author Costin Leau
95   * 
96   */
97  public class OsgiBundleResourcePatternResolver extends PathMatchingResourcePatternResolver {
98  
99  	/**
100 	 * Our own logger to protect against incompatible class changes.
101 	 */
102 	private static final Log logger = LogFactory.getLog(OsgiBundleResourcePatternResolver.class);
103 
104 	/**
105 	 * The bundle on which this resolver works on.
106 	 */
107 	private final Bundle bundle;
108 
109 	/**
110 	 * The bundle context associated with this bundle.
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 	// use the default package admin version
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 	 * Finds existing resources. This method returns the actual resources found
149 	 * w/o adding any extra decoration (such as non-existing resources).
150 	 * 
151 	 * @param locationPattern location pattern
152 	 * @return found resources (w/o any decoration)
153 	 * @throws IOException in case of I/O errors
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 		// look for patterns (includes classpath*:)
160 		if (getPathMatcher().isPattern(locationPattern)) {
161 			// treat classpath as a special case
162 			if (OsgiResourceUtils.isClassPathType(type))
163 				return findClassPathMatchingResources(locationPattern, type);
164 
165 			return findPathMatchingResources(locationPattern, type);
166 		}
167 		// even though we have no pattern
168 		// the OSGi space can return multiple entries for the same resource name
169 		// - treat this case below
170 		else {
171 			Resource[] result = null;
172 
173 			OsgiBundleResource resource = new OsgiBundleResource(bundle, locationPattern);
174 
175 			switch (type) {
176 				// same as bundle space
177 				case OsgiResourceUtils.PREFIX_TYPE_NOT_SPECIFIED:
178 					// consider bundle-space which can return multiple URLs
179 				case OsgiResourceUtils.PREFIX_TYPE_BUNDLE_SPACE:
180 					result = resource.getAllUrlsFromBundleSpace(locationPattern);
181 					break;
182 				// for the rest go with the normal resolving
183 				default:
184 					if (!resource.exists())
185 						result = new Resource[] { resource };
186 					break;
187 			}
188 			return result;
189 		}
190 	}
191 
192 	// add a non-existing resource, if none was found and no pattern was specified
193 	public Resource[] getResources(String locationPattern) throws IOException {
194 		Resource[] resources = findResources(locationPattern);
195 
196 		// check whether we found something or we should fall-back to a
197 		// non-existing resource
198 		if (ObjectUtils.isEmpty(resources) && (!getPathMatcher().isPattern(locationPattern))) {
199 			return new Resource[] { getResourceLoader().getResource(locationPattern) };
200 		}
201 		// return the original array
202 		return resources;
203 	}
204 
205 	/**
206 	 * Special classpath method. Will try to detect the imported bundles (which
207 	 * are part of the classpath) and look for resources in all of them. This
208 	 * implementation will try to determine the bundles that compose the current
209 	 * bundle classpath and then it will inspect the bundle space of each of
210 	 * them individually.
211 	 * 
212 	 * <p/> Since the bundle space is considered, runtime classpath entries such
213 	 * as dynamic imports are not supported (yet).
214 	 * 
215 	 * @param locationPattern
216 	 * @param type
217 	 * @return classpath resources
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 		// eliminate classpath path
228 		String path = OsgiResourceUtils.stripPrefix(locationPattern);
229 
230 		Collection foundPaths = new LinkedHashSet();
231 
232 		// 1. search the imported packages
233 
234 		// find folder path matching 
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 		// 2. search the target bundle
245 		findSyntheticClassPathMatchingResource(bundle, path, foundPaths);
246 
247 		// 3. resolve the entries using the official class-path method (as some of them might be hidden)
248 		List resources = new ArrayList(foundPaths.size());
249 
250 		for (Iterator iterator = foundPaths.iterator(); iterator.hasNext();) {
251 			// classpath*: -> getResources()
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 			// classpath -> getResource()
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 	 * Searches for the given pattern inside the imported bundle. This
287 	 * translates to pattern matching on the imported packages.
288 	 * 
289 	 * @param importedBundle imported bundle
290 	 * @param path path used for pattern matching
291 	 * @param foundPaths collection of found results
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 			// transform the package name into a path
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 			// if the imported package matches the path
309 			if (matcher.matchStart(path, pkg)) {
310 				// start the JAR analysis
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 	 * Applies synthetic class-path analysis. That is, search the bundle space
326 	 * and the bundle class-path for entries matching the given path.
327 	 * 
328 	 * @param bundle
329 	 * @param path
330 	 * @param foundPaths
331 	 * @throws IOException
332 	 */
333 	private void findSyntheticClassPathMatchingResource(Bundle bundle, String path, Collection foundPaths)
334 			throws IOException {
335 		// 1. bundle space lookup
336 		OsgiBundleResourcePatternResolver localPatternResolver = new OsgiBundleResourcePatternResolver(bundle);
337 		Resource[] foundResources = localPatternResolver.findResources(path);
338 		for (int j = 0; j < foundResources.length; j++) {
339 			// assemble only the OSGi paths
340 			foundPaths.add(foundResources[j].getURL().getPath());
341 		}
342 		// 2. Bundle-Classpath lookup (on the path stripped of the prefix)
343 		foundPaths.addAll(findBundleClassPathMatchingPaths(bundle, path));
344 
345 		// 3. Required-Bundle is considered already by the dependency resolver
346 	}
347 
348 	/**
349 	 * Searches the bundle classpath (Bundle-Classpath) entries for the given
350 	 * pattern.
351 	 * 
352 	 * @param bundle
353 	 * @param pattern
354 	 * @return
355 	 * @throws IOException
356 	 */
357 	private Collection findBundleClassPathMatchingPaths(Bundle bundle, String pattern) throws IOException {
358 		// list of strings pointing to the matching resources 
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 		// see if there is a bundle class-path defined
366 		String[] entries = OsgiHeaderUtils.getBundleClassPath(bundle);
367 
368 		if (trace)
369 			logger.trace("Found " + Constants.BUNDLE_CLASSPATH + " entries " + ObjectUtils.nullSafeToString(entries));
370 
371 		// 1. if so, look at the entries
372 		for (int i = 0; i < entries.length; i++) {
373 			String entry = entries[i];
374 
375 			// make sure to exclude the default entry
376 			if (!entry.equals(BUNDLE_DEFAULT_CP)) {
377 
378 				// 2. locate resource first from the bundle space (since it might not exist)
379 				OsgiBundleResource entryResource = new OsgiBundleResource(bundle, entry);
380 				// call the internal method to avoid catching an exception
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 				// we've got a valid entry so let's parse it
390 				if (url != null) {
391 					String cpEntryPath = url.getPath();
392 					// is it a jar ?
393 					if (entry.endsWith(JAR_EXTENSION))
394 						findBundleClassPathMatchingJarEntries(list, url, pattern);
395 					// no, so it must be a folder
396 					else
397 						findBundleClassPathMatchingFolders(list, bundle, cpEntryPath, pattern);
398 				}
399 			}
400 		}
401 
402 		return list;
403 	}
404 
405 	/**
406 	 * Checks the jar entries from the Bundle-Classpath for the given pattern.
407 	 * 
408 	 * @param list
409 	 * @param ur
410 	 */
411 	private void findBundleClassPathMatchingJarEntries(List list, URL url, String pattern) throws IOException {
412 		// get the stream to the resource and read it as a jar
413 		JarInputStream jis = new JarInputStream(url.openStream());
414 		Set result = new LinkedHashSet(8);
415 
416 		// parse the jar and do pattern matching
417 		try {
418 			while (jis.available() > 0) {
419 				JarEntry jarEntry = jis.getNextJarEntry();
420 				// if the jar has ended, the entry can be null (on Sun JDK at least)
421 				if (jarEntry != null) {
422 					String entryPath = jarEntry.getName();
423 
424 					// strip leading "/" if it does exist
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 				// ignore it - nothing we can't do about it
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 	 * Checks the folder entries from the Bundle-Classpath for the given
451 	 * pattern.
452 	 * 
453 	 * @param list
454 	 * @param bundle
455 	 * @param cpEntryPath
456 	 * @param pattern
457 	 * @throws IOException
458 	 */
459 	private void findBundleClassPathMatchingFolders(List list, Bundle bundle, String cpEntryPath, String pattern)
460 			throws IOException {
461 		// append path to the pattern and do a normal search
462 		// folder/<pattern> starts being applied
463 
464 		String bundlePathPattern;
465 
466 		boolean entryWithFolderSlash = cpEntryPath.endsWith(FOLDER_SEPARATOR);
467 		boolean patternWithFolderSlash = pattern.startsWith(FOLDER_SEPARATOR);
468 		// concatenate entry + pattern w/o double slashes
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 		// search the bundle space for the detected resource
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 			// skip when dealing with non-existing resources
491 			if (resources.length == 1 && !resources[0].exists()) {
492 				return;
493 			}
494 			else {
495 				int cutStartingIndex = cpEntryPath.length();
496 				// add the resource stripping the cp
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 	 * Replace the super class implementation to pass in the searchType
514 	 * parameter.
515 	 * 
516 	 * @see PathMatchingResourcePatternResolver#findPathMatchingResources(String)
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 	 * {@inheritDoc}
541 	 * 
542 	 * Overrides the default check up since computing the URL can be fairly
543 	 * expensive operation as there is no caching (due to the framework dynamic
544 	 * nature).
545 	 */
546 	protected boolean isJarResource(Resource resource) throws IOException {
547 		if (resource instanceof OsgiBundleResource) {
548 			// check the resource type
549 			OsgiBundleResource bundleResource = (OsgiBundleResource) resource;
550 			// if it's known, then it's not a jar
551 			if (bundleResource.getSearchType() != OsgiResourceUtils.PREFIX_TYPE_UNKNOWN) {
552 				return false;
553 			}
554 			// otherwise the normal parsing occur
555 		}
556 		return super.isJarResource(resource);
557 	}
558 
559 	/**
560 	 * Based on the search type, uses the appropriate searching method.
561 	 * 
562 	 * @see OsgiBundleResource#BUNDLE_URL_PREFIX
563 	 * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver#getResources(java.lang.String)
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 			// sanitize the root folder (since it's possible to not specify the root which fails any further matches)
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 	 * Searches each level inside the bundle for entries based on the search
597 	 * strategy chosen.
598 	 * 
599 	 * @param bundle the bundle to do the lookup
600 	 * @param fullPattern matching pattern
601 	 * @param dir directory inside the bundle
602 	 * @param result set of results (used to concatenate matching sub dirs)
603 	 * @param searchType the search strategy to use
604 	 * @throws IOException
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 				// returns an enumeration of URLs
615 				candidates = bundle.findEntries(dir, null, false);
616 				break;
617 			case OsgiResourceUtils.PREFIX_TYPE_BUNDLE_JAR:
618 				// returns an enumeration of Strings
619 				candidates = bundle.getEntryPaths(dir);
620 				break;
621 			case OsgiResourceUtils.PREFIX_TYPE_CLASS_SPACE:
622 				// returns an enumeration of URLs
623 				throw new IllegalArgumentException("class space does not support pattern matching");
624 			default:
625 				throw new IllegalArgumentException("unknown searchType " + searchType);
626 		}
627 
628 		// entries are relative to the root path - miss the leading /
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 					// Returned resource path does not start with relative
643 					// directory:
644 					// assuming absolute path returned -> strip absolute path.
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 					// Search subdirectories recursively: we manually get the
654 					// folders on only one level
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 	 * Handles candidates returned as URLs.
671 	 * 
672 	 * @param path
673 	 * @return
674 	 */
675 	private String handleURL(URL path) {
676 		return path.getPath();
677 	}
678 
679 	/**
680 	 * Handles candidates returned as Strings.
681 	 * 
682 	 * @param path
683 	 * @return
684 	 */
685 	private String handleString(String path) {
686 		return FOLDER_SEPARATOR.concat(path);
687 	}
688 }