spring.osgi.io.OsgiBundleResourcePatternResolver.java Source code

Java tutorial

Introduction

Here is the source code for spring.osgi.io.OsgiBundleResourcePatternResolver.java

Source

/*
 * Copyright 2006-2008 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package spring.osgi.io;

import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ContextResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import spring.osgi.utils.OsgiHeaderUtils;
import spring.osgi.utils.OsgiResourceUtils;

import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * OSGi-aware {@link org.springframework.core.io.support.ResourcePatternResolver}.
 * <p/>
 * Can find resources in the <em>bundle jar</em> and <em>bundle space</em>.
 * See {@link OsgiBundleResource} for more information.
 * <p/>
 * <p/><b>ClassPath support</b>
 * <p/>
 * <p/>As mentioned by {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}, class-path
 * pattern matching needs to resolve the class-path structure to a file-system
 * location (be it an actual folder or a jar). Inside the OSGi environment this
 * is problematic as the bundles can be loaded in memory directly from input
 * streams. To avoid relying on each platform bundle storage structure, this
 * implementation tries to determine the bundles that assemble the given bundle
 * class-path and analyze each of them individually. This involves the bundle
 * archive (including special handling of the <code>Bundle-Classpath</code> as
 * it is computed at runtime), the bundle required packages and its attached
 * fragments.
 * <p/>
 * Depending on the configuration of running environment, this might cause
 * significant IO activity which can affect performance.
 * <p/>
 * <p/><b>Note:</b> Currently, <em>static</em> imports as well as
 * <code>Bundle-Classpath</code> and <code>Required-Bundle</code> entries
 * are supported. Support for <code>DynamicPackage-Import</code> depends on
 * how/when the underlying platform does the wiring between the dynamically
 * imported bundle and the given bundle.
 * <p/>
 * <p/><b>Portability Note:</b> Since it relies only on the OSGi API, this
 * implementation depends heavily on how closely the platform implements the
 * OSGi spec. While significant tests have been made to ensure compatibility,
 * one <em>might</em> experience different behaviour especially when dealing
 * with jars with missing folder entries or boot-path delegation. It is strongly
 * recommended that wildcard resolution be thoroughly tested before switching to
 * a different platform before you rely on it.
 *
 * @author Costin Leau
 * @see org.osgi.framework.Bundle
 * @see OsgiBundleResource
 * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver
 */
public class OsgiBundleResourcePatternResolver extends PathMatchingResourcePatternResolver {

    /**
     * Our own logger to protect against incompatible class changes.
     */
    private static final Logger logger = LoggerFactory.getLogger(OsgiBundleResourcePatternResolver.class);

    /**
     * The bundle on which this resolver works on.
     */
    private final Bundle bundle;

    private static final String FOLDER_SEPARATOR = "/";

    private static final String FOLDER_WILDCARD = "**";

    private static final String JAR_EXTENSION = ".jar";

    private static final String BUNDLE_DEFAULT_CP = ".";

    private static final char SLASH = '/';

    private static final char DOT = '.';

    // use the default package admin version
    //TODO REMOVE
    //    private final DependencyResolver resolver;

    public OsgiBundleResourcePatternResolver(Bundle bundle, ClassLoader classLoader) {
        this(new OsgiBundleResourceLoader(bundle, classLoader));
    }

    protected OsgiBundleResourcePatternResolver(ResourceLoader resourceLoader) {
        super(resourceLoader);
        if (resourceLoader instanceof OsgiBundleResourceLoader) {
            this.bundle = ((OsgiBundleResourceLoader) resourceLoader).getBundle();
        } else {
            this.bundle = null;
        }
    }

    /**
     * Finds existing resources. This method returns the actual resources found
     * w/o adding any extra decoration (such as non-existing resources).
     *
     * @param locationPattern location pattern
     * @return found resources (w/o any decoration)
     * @throws java.io.IOException in case of I/O errors
     */
    protected Resource[] findResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
        int type = OsgiResourceUtils.getSearchType(locationPattern);

        // look for patterns (includes classpath*:)
        if (getPathMatcher().isPattern(locationPattern)) {
            // treat classpath as a special case
            if (OsgiResourceUtils.isClassPathType(type))
                return findClassPathMatchingResources(locationPattern, type);

            return findPathMatchingResources(locationPattern, type);
        }
        // even though we have no pattern
        // the OSGi space can return multiple entries for the same resource name
        // - treat this case below
        else {
            Resource[] result = null;

            OsgiBundleResource resource = new OsgiBundleResource(bundle, locationPattern);

            switch (type) {
            // same as bundle space
            case OsgiResourceUtils.PREFIX_TYPE_NOT_SPECIFIED:
                // consider bundle-space which can return multiple URLs
            case OsgiResourceUtils.PREFIX_TYPE_BUNDLE_SPACE:
                result = resource.getAllUrlsFromBundleSpace(locationPattern);
                break;
            // for the rest go with the normal resolving
            default:
                if (!resource.exists())
                    result = new Resource[] { resource };
                break;
            }
            return result;
        }
    }

    // add a non-existing resource, if none was found and no pattern was specified
    public Resource[] getResources(final String locationPattern) throws IOException {

        Resource[] resources = findResources(locationPattern);

        // check whether we found something or we should fall-back to a
        // non-existing resource
        if (ObjectUtils.isEmpty(resources) && (!getPathMatcher().isPattern(locationPattern))) {
            return new Resource[] { getResourceLoader().getResource(locationPattern) };
        }
        // return the original array
        return resources;

    }

    /**
     * Special classpath method. Will try to detect the imported bundles (which
     * are part of the classpath) and look for resources in all of them. This
     * implementation will try to determine the bundles that compose the current
     * bundle classpath and then it will inspect the bundle space of each of
     * them individually.
     * <p/>
     * <p/> Since the bundle space is considered, runtime classpath entries such
     * as dynamic imports are not supported (yet).
     *
     * @param locationPattern locationPattern
     * @param type            type
     * @return classpath resources
     */
    private Resource[] findClassPathMatchingResources(String locationPattern, int type) throws IOException {

        //TODO REMOVE
        //        if (resolver == null)
        //            throw new IllegalArgumentException(
        //                    "PackageAdmin service/a started bundle is required for classpath matching");
        //
        //        final ImportedBundle[] importedBundles = resolver.getImportedBundles(bundle);
        //
        //        // eliminate classpath path
        //        final String path = OsgiResourceUtils.stripPrefix(locationPattern);
        //
        //        final Collection<String> foundPaths = new LinkedHashSet<>();
        //
        //        // 1. search the imported packages
        //
        //        // find folder path matching
        //        final String rootDirPath = determineFolderPattern(path);
        //
        //        if (System.getSecurityManager() != null) {
        //            try {
        //                AccessController.doPrivileged(new PrivilegedExceptionAction() {
        //
        //                    public Object run() throws IOException {
        //                        for (final ImportedBundle importedBundle : importedBundles) {
        //                            if (!bundle.equals(importedBundle.getBundle())) {
        //                                findImportedBundleMatchingResource(importedBundle, rootDirPath, path, foundPaths);
        //                            }
        //                        }
        //                        return null;
        //                    }
        //                });
        //            } catch (PrivilegedActionException pe) {
        //                throw (IOException) pe.getException();
        //            }
        //        } else {
        //            for (final ImportedBundle importedBundle : importedBundles) {
        //                if (!bundle.equals(importedBundle.getBundle())) {
        //                    findImportedBundleMatchingResource(importedBundle, rootDirPath, path, foundPaths);
        //                }
        //            }
        //        }

        // eliminate classpath path
        final String path = OsgiResourceUtils.stripPrefix(locationPattern);

        final Collection<String> foundPaths = new LinkedHashSet<>();
        // 2. search the target bundle
        findSyntheticClassPathMatchingResource(bundle, path, foundPaths);

        // 3. resolve the entries using the official class-path method (as some of them might be hidden)
        List<Resource> resources = new ArrayList<>(foundPaths.size());

        for (String resourcePath : foundPaths) {
            // classpath*: -> getResources()
            if (OsgiResourceUtils.PREFIX_TYPE_CLASS_ALL_SPACE == type) {
                CollectionUtils.mergeArrayIntoCollection(
                        convertURLEnumerationToResourceArray(bundle.getResources(resourcePath), resourcePath),
                        resources);
            }
            // classpath -> getResource()
            else {
                URL url = bundle.getResource(resourcePath);
                if (url != null)
                    resources.add(new UrlContextResource(url, resourcePath));
            }
        }

        if (logger.isTraceEnabled()) {
            logger.trace("Fitered " + foundPaths + " to " + resources);
        }

        return resources.toArray(new Resource[resources.size()]);
    }

    //TODO REMOVE
    //    private String determineFolderPattern(String path) {
    //        int index = path.lastIndexOf(FOLDER_SEPARATOR);
    //        return (index > 0 ? path.substring(0, index + 1) : "");
    //    }

    private ContextResource[] convertURLEnumerationToResourceArray(Enumeration enm, String path) {
        Set<ContextResource> resources = new LinkedHashSet<>(4);
        while (enm != null && enm.hasMoreElements()) {
            resources.add(new UrlContextResource((URL) enm.nextElement(), path));
        }
        return resources.toArray(new ContextResource[resources.size()]);
    }

    /**
     * Searches for the given pattern inside the imported bundle. This
     * translates to pattern matching on the imported packages.
     *
     * @param importedBundle imported bundle
     * @param path           path used for pattern matching
     * @param foundPaths     collection of found results
     */
    //TODO REMOVE
    //    private void findImportedBundleMatchingResource(final ImportedBundle importedBundle, String rootPath, String path,
    //                                                    final Collection<String> foundPaths) throws IOException {
    //
    //        final boolean trace = logger.isTraceEnabled();
    //
    //        String[] packages = importedBundle.getImportedPackages();
    //
    //        if (trace)
    //            logger.trace("Searching path [" + path + "] on imported pkgs " + ObjectUtils.nullSafeToString(packages)
    //                    + "...");
    //
    //        final boolean startsWithSlash = rootPath.startsWith(FOLDER_SEPARATOR);
    //
    //        for (String aPackage : packages) {
    //            // transform the package name into a path
    //            String pkg = aPackage.replace(DOT, SLASH) + SLASH;
    //
    //            if (startsWithSlash) {
    //                pkg = FOLDER_SEPARATOR + pkg;
    //            }
    //
    //            final PathMatcher matcher = getPathMatcher();
    //            // if the imported package matches the path
    //            if (matcher.matchStart(path, pkg)) {
    //                Bundle bundle = importedBundle.getBundle();
    //                // 1. look at the Bundle jar root
    //                Enumeration entries = bundle.getEntryPaths(pkg);
    //                while (entries != null && entries.hasMoreElements()) {
    //                    String entry = (String) entries.nextElement();
    //                    if (startsWithSlash)
    //                        entry = FOLDER_SEPARATOR + entry;
    //
    //                    if (matcher.match(path, entry)) {
    //                        if (trace)
    //                            logger.trace("Found entry [" + entry + "]");
    //                        foundPaths.add(entry);
    //                    }
    //                }
    //                // 2. Do a Bundle-Classpath lookup (since the jar might use a different classpath)
    //                Collection<String> cpMatchingPaths = findBundleClassPathMatchingPaths(bundle, path);
    //                foundPaths.addAll(cpMatchingPaths);
    //            }
    //        }
    //    }

    /**
     * Applies synthetic class-path analysis. That is, search the bundle space
     * and the bundle class-path for entries matching the given path.
     *
     * @param bundle     bundle
     * @param path       path
     * @param foundPaths foundPaths
     * @throws java.io.IOException
     */
    private void findSyntheticClassPathMatchingResource(Bundle bundle, String path, Collection<String> foundPaths)
            throws IOException {
        // 1. bundle space lookup
        OsgiBundleResourcePatternResolver localPatternResolver = new OsgiBundleResourcePatternResolver(bundle,
                getClassLoader());
        Resource[] foundResources = localPatternResolver.findResources(path);

        boolean trace = logger.isTraceEnabled();

        if (trace)
            logger.trace("Found synthetic cp resources " + ObjectUtils.nullSafeToString(foundResources));

        for (Resource foundResource : foundResources) {
            // assemble only the OSGi paths
            foundPaths.add(foundResource.getURL().getPath());
        }
        // 2. Bundle-Classpath lookup (on the path stripped of the prefix)
        Collection<String> cpMatchingPaths = findBundleClassPathMatchingPaths(bundle, path);

        if (trace)
            logger.trace("Found Bundle-ClassPath matches " + cpMatchingPaths);

        foundPaths.addAll(cpMatchingPaths);

        // 3. Required-Bundle is considered already by the dependency resolver
    }

    /**
     * Searches the bundle classpath (Bundle-Classpath) entries for the given
     * pattern.
     *
     * @param bundle  bundle
     * @param pattern pattern
     * @return paths
     * @throws java.io.IOException
     */
    private Collection<String> findBundleClassPathMatchingPaths(Bundle bundle, String pattern) throws IOException {
        // list of strings pointing to the matching resources
        List<String> list = new ArrayList<>(4);

        boolean trace = logger.isTraceEnabled();
        if (trace)
            logger.trace("Analyzing " + Constants.BUNDLE_CLASSPATH + " entries for bundle [" + bundle.getBundleId()
                    + "|" + bundle.getSymbolicName() + "]");
        // see if there is a bundle class-path defined
        String[] entries = OsgiHeaderUtils.getBundleClassPath(bundle);

        if (trace)
            logger.trace(
                    "Found " + Constants.BUNDLE_CLASSPATH + " entries " + ObjectUtils.nullSafeToString(entries));

        // 1. if so, look at the entries
        for (String entry : entries) {
            // make sure to exclude the default entry
            if (!entry.equals(BUNDLE_DEFAULT_CP)) {

                // 2. locate resource first from the bundle space (since it might not exist)
                OsgiBundleResource entryResource = new OsgiBundleResource(bundle, entry);
                // call the internal method to avoid catching an exception
                URL url = null;
                ContextResource res = entryResource.getResourceFromBundleSpace(entry);
                if (res != null) {
                    url = res.getURL();
                }

                if (trace)
                    logger.trace("Classpath entry [" + entry + "] resolves to [" + url + "]");
                // we've got a valid entry so let's parse it
                if (url != null) {
                    String cpEntryPath = url.getPath();
                    // is it a jar ?
                    if (entry.endsWith(JAR_EXTENSION))
                        findBundleClassPathMatchingJarEntries(list, url, pattern);
                    // no, so it must be a folder
                    else
                        findBundleClassPathMatchingFolders(list, bundle, cpEntryPath, pattern);
                }
            }
        }

        return list;
    }

    /**
     * Checks the jar entries from the Bundle-Classpath for the given pattern.
     *
     * @param list    paths
     * @param url     url
     * @param pattern pattern
     */
    private void findBundleClassPathMatchingJarEntries(List<String> list, URL url, String pattern)
            throws IOException {
        // get the stream to the resource and read it as a jar
        JarInputStream jis = new JarInputStream(url.openStream());
        Set<String> result = new LinkedHashSet<>(8);

        boolean patternWithFolderSlash = pattern.startsWith(FOLDER_SEPARATOR);

        // parse the jar and do pattern matching
        try {
            while (jis.available() > 0) {
                JarEntry jarEntry = jis.getNextJarEntry();
                // if the jar has ended, the entry can be null (on Sun JDK at least)
                if (jarEntry != null) {
                    String entryPath = jarEntry.getName();

                    // check if leading "/" is needed or not (it depends how the jar was created)
                    if (entryPath.startsWith(FOLDER_SEPARATOR)) {
                        if (!patternWithFolderSlash) {
                            entryPath = entryPath.substring(FOLDER_SEPARATOR.length());
                        }
                    } else {
                        if (patternWithFolderSlash) {
                            entryPath = FOLDER_SEPARATOR.concat(entryPath);
                        }
                    }
                    if (getPathMatcher().match(pattern, entryPath)) {
                        result.add(entryPath);
                    }
                }
            }
        } finally {
            try {
                jis.close();
            } catch (IOException io) {
                // ignore it - nothing we can't do about it
            }
        }

        if (logger.isTraceEnabled())
            logger.trace("Found in nested jar [" + url + "] matching entries " + result);

        list.addAll(result);
    }

    /**
     * Checks the folder entries from the Bundle-Classpath for the given
     * pattern.
     *
     * @param list        list
     * @param bundle      bundle
     * @param cpEntryPath cpEntryPath
     * @param pattern     pattern
     * @throws java.io.IOException
     */
    private void findBundleClassPathMatchingFolders(List<String> list, Bundle bundle, String cpEntryPath,
            String pattern) throws IOException {
        // append path to the pattern and do a normal search
        // folder/<pattern> starts being applied

        String bundlePathPattern;

        boolean entryWithFolderSlash = cpEntryPath.endsWith(FOLDER_SEPARATOR);
        boolean patternWithFolderSlash = pattern.startsWith(FOLDER_SEPARATOR);
        // concatenate entry + pattern w/o double slashes
        if (entryWithFolderSlash) {
            if (patternWithFolderSlash)
                bundlePathPattern = cpEntryPath + pattern.substring(1, pattern.length());
            else
                bundlePathPattern = cpEntryPath + pattern;
        } else {
            if (patternWithFolderSlash)
                bundlePathPattern = cpEntryPath + pattern;
            else
                bundlePathPattern = cpEntryPath + FOLDER_SEPARATOR + pattern;
        }

        // search the bundle space for the detected resource
        OsgiBundleResourcePatternResolver localResolver = new OsgiBundleResourcePatternResolver(bundle,
                getClassLoader());
        Resource[] resources = localResolver.getResources(bundlePathPattern);

        boolean trace = logger.isTraceEnabled();
        List<String> foundResources = (trace ? new ArrayList<String>(resources.length) : null);

        try {
            // skip when dealing with non-existing resources
            if (resources.length == 1 && !resources[0].exists()) {
                return;
            }
            int cutStartingIndex = cpEntryPath.length();
            // add the resource stripping the cp
            for (Resource resource : resources) {
                String path = resource.getURL().getPath().substring(cutStartingIndex);
                list.add(path);
                if (trace)
                    foundResources.add(path);
            }
        } finally {
            if (trace)
                logger.trace(
                        "Searching for [" + bundlePathPattern + "] revealed resources (relative to the cp entry ["
                                + cpEntryPath + "]): " + foundResources);
        }
    }

    /**
     * Replace the super class implementation to pass in the searchType
     * parameter.
     *
     * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver#findPathMatchingResources(String)
     */
    private Resource[] findPathMatchingResources(String locationPattern, int searchType) throws IOException {
        String rootDirPath = determineRootDir(locationPattern);
        String subPattern = locationPattern.substring(rootDirPath.length());
        Resource[] rootDirResources = getResources(rootDirPath);

        boolean trace = logger.isTraceEnabled();

        if (trace)
            logger.trace("Found root resources for [" + rootDirPath + "] :"
                    + ObjectUtils.nullSafeToString(rootDirResources));

        Set<Resource> result = new LinkedHashSet<>();
        for (Resource rootDirResource : rootDirResources) {
            if (isJarResource(rootDirResource)) {
                result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
            } else {
                result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern, searchType));
            }
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
        }
        return result.toArray(new Resource[result.size()]);
    }

    /**
     * {@inheritDoc}
     * <p/>
     * Overrides the default check up since computing the URL can be fairly
     * expensive operation as there is no caching (due to the framework dynamic
     * nature).
     */
    protected boolean isJarResource(Resource resource) throws IOException {
        if (resource instanceof OsgiBundleResource) {
            // check the resource type
            OsgiBundleResource bundleResource = (OsgiBundleResource) resource;
            // if it's known, then it's not a jar
            if (bundleResource.getSearchType() != OsgiResourceUtils.PREFIX_TYPE_UNKNOWN) {
                return false;
            }
            // otherwise the normal parsing occur
        }
        return super.isJarResource(resource);
    }

    /**
     * Based on the search type, uses the appropriate searching method.
     *
     * @see OsgiBundleResource#BUNDLE_URL_PREFIX
     * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver#getResources(String)
     */
    private Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern,
            int searchType) throws IOException {

        String rootPath = null;

        if (rootDirResource instanceof OsgiBundleResource) {
            OsgiBundleResource bundleResource = (OsgiBundleResource) rootDirResource;
            rootPath = bundleResource.getPath();
            searchType = bundleResource.getSearchType();
        } else if (rootDirResource instanceof UrlResource) {
            rootPath = rootDirResource.getURL().getPath();
        }

        if (rootPath != null) {
            String cleanPath = OsgiResourceUtils.stripPrefix(rootPath);
            // sanitize the root folder (since it's possible to not specify the root which fails any further matches)
            if (!cleanPath.endsWith(FOLDER_SEPARATOR)) {
                cleanPath = cleanPath + FOLDER_SEPARATOR;
            }
            String fullPattern = cleanPath + subPattern;
            Set<Resource> result = new LinkedHashSet<>();
            doRetrieveMatchingBundleEntries(bundle, fullPattern, cleanPath, result, searchType);
            return result;
        } else {
            return super.doFindPathMatchingFileResources(rootDirResource, subPattern);
        }
    }

    /**
     * Searches each level inside the bundle for entries based on the search
     * strategy chosen.
     *
     * @param bundle      the bundle to do the lookup
     * @param fullPattern matching pattern
     * @param dir         directory inside the bundle
     * @param result      set of results (used to concatenate matching sub dirs)
     * @param searchType  the search strategy to use
     */
    @SuppressWarnings("unchecked")
    private void doRetrieveMatchingBundleEntries(Bundle bundle, String fullPattern, String dir,
            Set<Resource> result, int searchType) {

        Enumeration<?> candidates;

        switch (searchType) {
        case OsgiResourceUtils.PREFIX_TYPE_NOT_SPECIFIED:
        case OsgiResourceUtils.PREFIX_TYPE_BUNDLE_SPACE:
            // returns an enumeration of URLs
            candidates = bundle.findEntries(dir, null, false);
            break;
        case OsgiResourceUtils.PREFIX_TYPE_BUNDLE_JAR:
            // returns an enumeration of Strings
            candidates = bundle.getEntryPaths(dir);
            break;
        case OsgiResourceUtils.PREFIX_TYPE_CLASS_SPACE:
            // returns an enumeration of URLs
            throw new IllegalArgumentException("class space does not support pattern matching");
        default:
            throw new IllegalArgumentException("unknown searchType " + searchType);
        }

        // entries are relative to the root path - miss the leading /
        if (candidates != null) {
            boolean dirDepthNotFixed = (fullPattern.contains(FOLDER_WILDCARD));
            while (candidates.hasMoreElements()) {
                Object path = candidates.nextElement();
                String currPath;

                if (path instanceof String)
                    currPath = handleString((String) path);
                else
                    currPath = handleURL((URL) path);

                if (!currPath.startsWith(dir)) {
                    // Returned resource path does not start with relative
                    // directory:
                    // assuming absolute path returned -> strip absolute path.
                    int dirIndex = currPath.indexOf(dir);
                    if (dirIndex != -1) {
                        currPath = currPath.substring(dirIndex);
                    }
                }

                if (currPath.endsWith(FOLDER_SEPARATOR) && (dirDepthNotFixed
                        || StringUtils.countOccurrencesOf(currPath, FOLDER_SEPARATOR) < StringUtils
                                .countOccurrencesOf(fullPattern, FOLDER_SEPARATOR))) {
                    // Search subdirectories recursively: we manually get the
                    // folders on only one level

                    doRetrieveMatchingBundleEntries(bundle, fullPattern, currPath, result, searchType);
                }
                if (getPathMatcher().match(fullPattern, currPath)) {
                    if (path instanceof URL)
                        result.add(new UrlContextResource((URL) path, currPath));
                    else
                        result.add(new OsgiBundleResource(bundle, currPath));

                }
            }
        }
    }

    /**
     * Handles candidates returned as URLs.
     *
     * @param path path
     * @return path
     */
    private String handleURL(URL path) {
        return path.getPath();
    }

    /**
     * Handles candidates returned as Strings.
     *
     * @param path path
     * @return path
     */
    private String handleString(String path) {
        return FOLDER_SEPARATOR.concat(path);
    }
}