bug 448004: Implement bundle resources as web resources.
diff --git a/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/Activator.java b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/Activator.java
index d0a7799..7838925 100644
--- a/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/Activator.java
+++ b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/Activator.java
@@ -3,12 +3,12 @@
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
- * and Apache License v2.0 which accompanies this distribution. 
+ * and Apache License v2.0 which accompanies this distribution.
  * The Eclipse Public License is available at
  *   http://www.eclipse.org/legal/epl-v10.html
- * and the Apache License v2.0 is available at 
+ * and the Apache License v2.0 is available at
  *   http://www.opensource.org/licenses/apache2.0.php.
- * You may elect to redistribute this code under either of these licenses.  
+ * You may elect to redistribute this code under either of these licenses.
  *
  * Contributors:
  *   VMware Inc. - initial contribution
@@ -23,7 +23,7 @@
 
 import org.eclipse.gemini.web.core.WebContainerProperties;
 import org.eclipse.gemini.web.core.spi.ServletContainer;
-import org.eclipse.gemini.web.tomcat.internal.loading.DirContextURLStreamHandlerService;
+import org.eclipse.gemini.web.tomcat.internal.bundleresources.BundleURLStreamHandlerService;
 import org.eclipse.virgo.util.osgi.ServiceRegistrationTracker;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
@@ -34,7 +34,7 @@
 
 public class Activator implements BundleActivator {
 
-    private static final String JNDI_SCHEME = "jndi";
+    private static final String WAR_PROTOCOL = "war";
 
     private static final String EXPRESSION_FACTORY = "javax.el.ExpressionFactory";
 
@@ -75,9 +75,9 @@
 
     private void registerURLStreamHandler(BundleContext context) {
         Dictionary<String, Object> properties = new Hashtable<>();
-        properties.put(URLConstants.URL_HANDLER_PROTOCOL, new String[] { JNDI_SCHEME });
+        properties.put(URLConstants.URL_HANDLER_PROTOCOL, new String[] { WAR_PROTOCOL });
 
-        DirContextURLStreamHandlerService handler = new DirContextURLStreamHandlerService();
+        BundleURLStreamHandlerService handler = new BundleURLStreamHandlerService();
         ServiceRegistration<URLStreamHandlerService> reg = context.registerService(URLStreamHandlerService.class, handler, properties);
         this.tracker.track(reg);
     }
diff --git a/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/TomcatServletContainer.java b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/TomcatServletContainer.java
index 3ae614b..72d714c 100644
--- a/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/TomcatServletContainer.java
+++ b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/TomcatServletContainer.java
@@ -1,14 +1,14 @@
 /*******************************************************************************
- * Copyright (c) 2009, 2012 VMware Inc.
+ * Copyright (c) 2009, 2015 VMware Inc.
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
- * and Apache License v2.0 which accompanies this distribution. 
+ * and Apache License v2.0 which accompanies this distribution.
  * The Eclipse Public License is available at
  *   http://www.eclipse.org/legal/epl-v10.html
- * and the Apache License v2.0 is available at 
+ * and the Apache License v2.0 is available at
  *   http://www.opensource.org/licenses/apache2.0.php.
- * You may elect to redistribute this code under either of these licenses.  
+ * You may elect to redistribute this code under either of these licenses.
  *
  * Contributors:
  *   VMware Inc. - initial contribution
@@ -29,10 +29,10 @@
 import org.eclipse.gemini.web.core.spi.ServletContainer;
 import org.eclipse.gemini.web.core.spi.ServletContainerException;
 import org.eclipse.gemini.web.core.spi.WebApplicationHandle;
-import org.eclipse.gemini.web.tomcat.internal.loading.BundleDirContext;
-import org.eclipse.gemini.web.tomcat.internal.loading.BundleWebappLoader;
-import org.eclipse.gemini.web.tomcat.internal.loading.ChainedClassLoader;
-import org.eclipse.gemini.web.tomcat.internal.loading.StandardWebBundleClassLoaderFactory;
+import org.eclipse.gemini.web.tomcat.internal.bundleresources.BundleWebResourceRoot;
+import org.eclipse.gemini.web.tomcat.internal.loader.BundleWebappLoader;
+import org.eclipse.gemini.web.tomcat.internal.loader.ChainedClassLoader;
+import org.eclipse.gemini.web.tomcat.internal.loader.StandardWebBundleClassLoaderFactory;
 import org.eclipse.gemini.web.tomcat.internal.support.BundleFileResolver;
 import org.eclipse.gemini.web.tomcat.internal.support.BundleFileResolverFactory;
 import org.eclipse.gemini.web.tomcat.spi.WebBundleClassLoaderFactory;
@@ -102,13 +102,13 @@
 
             BundleWebappLoader loader = new BundleWebappLoader(bundle, this.classLoaderCustomizer);
             context.setLoader(loader);
-            context.setResources(new BundleDirContext(bundle));
+            context.setResources(new BundleWebResourceRoot(bundle, docBase));
 
             ServletContext servletContext = context.getServletContext();
 
             return new TomcatWebApplicationHandle(servletContext, context, loader);
         } catch (Exception ex) {
-            throw new ServletContainerException("Unablo te create web application for context path '" + contextPath + "'", ex);
+            throw new ServletContainerException("Unablo te create web application for context path [" + contextPath + "].", ex);
         }
     }
 
@@ -125,12 +125,12 @@
             host.addChild(context);
         } catch (IllegalStateException e) {
             host.removeChild(context);
-            throw new ServletContainerException("Web application at '" + contextPath + "' cannot be added to the host.", e);
+            throw new ServletContainerException("Web application at [" + contextPath + "] cannot be added to the host.", e);
         }
 
         if (!context.getState().isAvailable()) {
             host.removeChild(context);
-            throw new ServletContainerException("Web application at '" + contextPath + "' failed to start. Check the logs for more details.");
+            throw new ServletContainerException("Web application at [" + contextPath + "] failed to start. Check the logs for more details.");
         }
     }
 
@@ -180,7 +180,7 @@
             Host host = this.tomcat.getHost();
             host.removeChild(context);
         } catch (Exception e) {
-            throw new ServletContainerException("Unable to remove web application with context path '" + context.getName() + "'", e);
+            throw new ServletContainerException("Unable to remove web application with context path [" + context.getName() + "].", e);
         }
     }
 
@@ -190,7 +190,7 @@
                 context.stop();
             }
         } catch (Exception e) {
-            throw new ServletContainerException("Error stopping web application with context path '" + context.getName() + "'", e);
+            throw new ServletContainerException("Error stopping web application with context path [" + context.getName() + "].", e);
         }
     }
 
@@ -200,14 +200,14 @@
                 context.destroy();
             }
         } catch (Exception e) {
-            throw new ServletContainerException("Error destroying web application with context path '" + context.getName() + "'", e);
+            throw new ServletContainerException("Error destroying web application with context path [" + context.getName() + "].", e);
         }
     }
 
     /**
      * A context path can only be bound to one application. This method checks to see if a given context path is free,
      * throwing {@link ContextPathExistsException} if not.
-     * 
+     *
      * @param contextPath the context path
      * @param host the {@link Host} to check for duplicate context paths.
      * @throws ContextPathExistsException if the context path is already used.
@@ -227,7 +227,7 @@
 
     private StandardContext extractTomcatContext(WebApplicationHandle handle) {
         if (!(handle instanceof TomcatWebApplicationHandle)) {
-            throw new IllegalStateException("Unrecognized handle type '" + handle.getClass() + "'.");
+            throw new IllegalStateException("Unrecognized handle type [" + handle.getClass() + "].");
         }
         return ((TomcatWebApplicationHandle) handle).getContext();
     }
diff --git a/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleJarResource.java b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleJarResource.java
new file mode 100644
index 0000000..05c7904
--- /dev/null
+++ b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleJarResource.java
@@ -0,0 +1,95 @@
+/*******************************************************************************

+ * Copyright (c) 2015 SAP SE

+ *

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * and Apache License v2.0 which accompanies this distribution.

+ * The Eclipse Public License is available at

+ *   http://www.eclipse.org/legal/epl-v10.html

+ * and the Apache License v2.0 is available at

+ *   http://www.opensource.org/licenses/apache2.0.php.

+ * You may elect to redistribute this code under either of these licenses.

+ *

+ * Contributors:

+ *   Violeta Georgieva - initial contribution

+ *******************************************************************************/

+

+package org.eclipse.gemini.web.tomcat.internal.bundleresources;

+

+import java.io.IOException;

+import java.io.InputStream;

+import java.net.URL;

+import java.net.URLConnection;

+import java.util.jar.JarEntry;

+import java.util.jar.JarFile;

+import java.util.jar.JarInputStream;

+import java.util.jar.Manifest;

+

+import org.apache.catalina.WebResourceRoot;

+import org.apache.catalina.webresources.JarResource;

+

+final class BundleJarResource extends JarResource {

+

+    BundleJarResource(WebResourceRoot root, String webAppPath, String base, String baseUrl, JarEntry jarEntry, String internalPath, Manifest manifest) {

+        super(root, webAppPath, base, baseUrl, jarEntry, internalPath, manifest);

+    }

+

+    @Override

+    protected JarInputStreamWrapper getJarInputStreamWrapper() {

+        URLConnection conn = null;

+        try {

+            conn = new URL(getBase()).openConnection();

+        } catch (IOException e) {

+            return null;

+        }

+

+        JarInputStream jarIs = null;

+        JarEntry entry = null;

+        try {

+            // Need to create a new JarEntry so the certificates can be read

+            jarIs = new JarInputStream(conn.getInputStream());

+            entry = jarIs.getNextJarEntry();

+            while (entry != null && !entry.getName().equals(getResource().getName())) {

+                entry = jarIs.getNextJarEntry();

+            }

+

+            if (entry != null) {

+                return new ExtendedJarInputStreamWrapper(null, entry, jarIs);

+            } else {

+                return null;

+            }

+        } catch (IOException e) {

+            if (getLog().isDebugEnabled()) {

+                getLog().debug(

+                    "Unable to obtain an InputStream for the resource [" + getResource().getName() + "] located in the JAR [" + getBaseUrl() + "]", e);

+            }

+            return null;

+        } finally {

+            if (entry == null) {

+                try {

+                    jarIs.close();

+                } catch (IOException ioe) {

+                    // Ignore

+                }

+            }

+        }

+    }

+

+    private class ExtendedJarInputStreamWrapper extends JarInputStreamWrapper {

+

+        private final InputStream is;

+

+        public ExtendedJarInputStreamWrapper(JarFile jarFile, JarEntry jarEntry, InputStream is) {

+            super(jarFile, jarEntry, is);

+            this.is = is;

+        }

+

+        @Override

+        public void close() throws IOException {

+            // Closing the JarInputStream releases the file lock on the JAR and also

+            // closes input stream created from the URLConnection.

+            this.is.close();

+        }

+    }

+

+}

diff --git a/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleJarResourceSet.java b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleJarResourceSet.java
new file mode 100644
index 0000000..db7b7d7
--- /dev/null
+++ b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleJarResourceSet.java
@@ -0,0 +1,66 @@
+/*******************************************************************************

+ * Copyright (c) 2015 SAP SE

+ *

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * and Apache License v2.0 which accompanies this distribution.

+ * The Eclipse Public License is available at

+ *   http://www.eclipse.org/legal/epl-v10.html

+ * and the Apache License v2.0 is available at

+ *   http://www.opensource.org/licenses/apache2.0.php.

+ * You may elect to redistribute this code under either of these licenses.

+ *

+ * Contributors:

+ *   Violeta Georgieva - initial contribution

+ *******************************************************************************/

+

+package org.eclipse.gemini.web.tomcat.internal.bundleresources;

+

+import java.io.IOException;

+import java.net.URL;

+import java.net.URLConnection;

+import java.util.jar.JarEntry;

+import java.util.jar.JarInputStream;

+import java.util.jar.Manifest;

+

+import org.apache.catalina.LifecycleException;

+import org.apache.catalina.WebResource;

+import org.apache.catalina.WebResourceRoot;

+import org.apache.catalina.webresources.JarResourceSet;

+

+final class BundleJarResourceSet extends JarResourceSet {

+

+    BundleJarResourceSet(WebResourceRoot root, String webAppMount, String base, String internalPath) throws IllegalArgumentException {

+        super(root, webAppMount, base, internalPath);

+    }

+

+    @Override

+    protected WebResource createArchiveResource(JarEntry jarEntry, String webAppPath, Manifest manifest) {

+        return new BundleJarResource(getRoot(), webAppPath, getBase(), getBaseUrlString(), jarEntry, getInternalPath(), manifest);

+    }

+

+    @Override

+    protected void initInternal() throws LifecycleException {

+        URLConnection conn = null;

+        URL baseUrl = null;

+        try {

+            baseUrl = new URL(getBase());

+            conn = baseUrl.openConnection();

+        } catch (IOException e) {

+            throw new IllegalArgumentException(e);

+        }

+

+        try (JarInputStream jarIs = new JarInputStream(conn.getInputStream())) {

+            JarEntry entry = jarIs.getNextJarEntry();

+            while (entry != null) {

+                getJarFileEntries().put(entry.getName(), entry);

+                entry = jarIs.getNextJarEntry();

+            }

+            setManifest(jarIs.getManifest());

+        } catch (IOException ioe) {

+            throw new IllegalArgumentException(ioe);

+        }

+

+        setBaseUrl(baseUrl);

+    }

+}

diff --git a/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleURLStreamHandlerService.java b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleURLStreamHandlerService.java
new file mode 100644
index 0000000..1b71082
--- /dev/null
+++ b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleURLStreamHandlerService.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2015 SAP SE
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * and the Apache License v2.0 is available at
+ *   http://www.opensource.org/licenses/apache2.0.php.
+ * You may elect to redistribute this code under either of these licenses.
+ *
+ * Contributors:
+ *   Violeta Georgieva - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.gemini.web.tomcat.internal.bundleresources;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.apache.catalina.webresources.WarURLStreamHandler;
+import org.osgi.service.url.AbstractURLStreamHandlerService;
+
+public class BundleURLStreamHandlerService extends AbstractURLStreamHandlerService {
+
+    private static final String WAR_BUNDLE_ENTRY_SCHEMA = "war:bundleentry";
+
+    private static final String WAR_TO_ENTRY_SEPARATOR = "\\^/";
+
+    private final ExtendedWarURLStreamHandler handler = new ExtendedWarURLStreamHandler();
+
+    @Override
+    public URLConnection openConnection(URL u) throws IOException {
+        return new URL(null, u.toExternalForm(), this.handler).openConnection();
+    }
+
+    private static class ExtendedWarURLStreamHandler extends WarURLStreamHandler {
+
+        @Override
+        protected void parseURL(URL u, String spec, int start, int limit) {
+            // Only the path needs to be changed
+            if (spec.startsWith(WAR_BUNDLE_ENTRY_SCHEMA)) {
+                String path = spec.substring(4);
+                path = path.replaceFirst(WAR_TO_ENTRY_SEPARATOR, "");
+                setURL(u, u.getProtocol(), "", -1, null, null, path, null, null);
+            } else {
+                super.parseURL(u, spec, start, limit);
+            }
+        }
+
+    }
+}
diff --git a/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResource.java b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResource.java
new file mode 100644
index 0000000..59e2a5e
--- /dev/null
+++ b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResource.java
@@ -0,0 +1,523 @@
+/*******************************************************************************
+ * Copyright (c) 2015 SAP SE
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * and the Apache License v2.0 is available at
+ *   http://www.opensource.org/licenses/apache2.0.php.
+ * You may elect to redistribute this code under either of these licenses.
+ *
+ * Contributors:
+ *   Violeta Georgieva - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.gemini.web.tomcat.internal.bundleresources;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.jar.Manifest;
+
+import org.apache.catalina.WebResourceRoot;
+import org.apache.catalina.webresources.AbstractResource;
+import org.apache.juli.logging.Log;
+import org.eclipse.gemini.web.tomcat.internal.support.BundleFileResolver;
+import org.eclipse.gemini.web.tomcat.internal.support.BundleFileResolverFactory;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+
+final class BundleWebResource extends AbstractResource {
+
+    private static final String WEB_INF_DOT = "WEB-INF.";
+
+    private static final String META_INF_DOT = "META-INF.";
+
+    private static final String META_INF = "META-INF";
+
+    private static final String OSGI_INF_DOT = "OSGI-INF.";
+
+    private static final String OSGI_OPT_DOT = "OSGI-OPT.";
+
+    private static final String PATH_SEPARATOR = "/";
+
+    private static final String DOT = ".";
+
+    private static final long TIME_NOT_SET = -1L;
+
+    private static final int CREATION_DATE_UNKNOWN = 0;
+
+    private static final long CONTENT_LENGTH_NOT_SET = -1;
+
+    private final WebResourceRoot root;
+
+    private final String path;
+
+    private final Bundle bundle;
+
+    private final List<Bundle> fragments;
+
+    private final BundleFileResolver bundleFileResolver = BundleFileResolverFactory.createBundleFileResolver();
+
+    private final boolean checkEntryPath;
+
+    private String bundleLocationCanonicalPath;
+
+    private boolean isBundleLocationDirectory;
+
+    private long lastModified = TIME_NOT_SET;
+
+    private long creation = TIME_NOT_SET;
+
+    private long contentLength = CONTENT_LENGTH_NOT_SET;
+
+    private URL url;
+
+    BundleWebResource(Bundle bundle, WebResourceRoot root) {
+        super(root, "");
+        this.root = root;
+        this.path = "";
+        this.bundle = bundle;
+        this.fragments = getFragments(bundle);
+        this.checkEntryPath = checkEntryPath();
+        File bundleLocation = this.bundleFileResolver.resolve(bundle);
+        if (bundleLocation != null) {
+            try {
+                this.bundleLocationCanonicalPath = bundleLocation.getCanonicalPath();
+            } catch (IOException e) {
+            }
+            if (bundleLocation.isDirectory()) {
+                this.isBundleLocationDirectory = true;
+            }
+        }
+    }
+
+    private BundleWebResource(Bundle bundle, WebResourceRoot root, List<Bundle> fragments, String path, boolean checkEntryPath,
+        String bundleLocationCanonicalPath, boolean isBundleLocationDirectory) {
+        super(root, path);
+        this.root = root;
+        this.path = path;
+        this.bundle = bundle;
+        this.fragments = fragments;
+        this.checkEntryPath = checkEntryPath;
+        this.bundleLocationCanonicalPath = bundleLocationCanonicalPath;
+        this.isBundleLocationDirectory = isBundleLocationDirectory;
+    }
+
+    Bundle getBundle() {
+        return this.bundle;
+    }
+
+    List<BundleWebResource> list() {
+        List<BundleWebResource> entries = new ArrayList<>();
+        Set<String> paths = getEntryPathsFromBundle();
+        if (paths != null) {
+            Iterator<String> iterator = paths.iterator();
+            while (iterator.hasNext()) {
+                String subPath = iterator.next();
+                entries.add(createBundleEntry(subPath));
+            }
+        }
+        return entries;
+    }
+
+    private BundleWebResource createBundleEntry(String path) {
+        return new BundleWebResource(this.bundle, this.root, this.fragments, path, this.checkEntryPath, this.bundleLocationCanonicalPath,
+            this.isBundleLocationDirectory);
+    }
+
+    private Set<String> getEntryPathsFromBundle() {
+        Set<String> paths = getEntryPathsFromBundle(this.bundle);
+
+        for (int i = 0; i < this.fragments.size(); i++) {
+            paths.addAll(getEntryPathsFromBundle(this.fragments.get(i)));
+        }
+
+        if (paths.isEmpty()) {
+            return null;
+        }
+
+        return paths;
+    }
+
+    private Set<String> getEntryPathsFromBundle(Bundle bundle) {
+        final Enumeration<String> ep = bundle.getEntryPaths(this.path);
+
+        Set<String> paths = new HashSet<String>();
+        if (ep != null) {
+            while (ep.hasMoreElements()) {
+                paths.add(ep.nextElement());
+            }
+        }
+
+        return paths;
+    }
+
+    Entry<BundleWebResource, URL> getEntry(String subPath) {
+        String finalPath = this.path + subPath;
+        URL entryURL = getEntryFromBundle(finalPath);
+        if (entryURL != null) {
+            Map<BundleWebResource, URL> result = new HashMap<>();
+            result.put(createBundleEntry(finalPath), entryURL);
+            return result.entrySet().iterator().next();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * This method has been generalized from this.bundle.getEntry(path) to allow entries to be supplied by a fragment.
+     */
+    private URL getEntryFromBundle(String path) {
+        if (this.checkEntryPath
+            && (checkNotAttemptingToAccess(path, META_INF_DOT) || checkNotAttemptingToAccess(path, WEB_INF_DOT)
+                || checkNotAttemptingToAccess(path, OSGI_INF_DOT) || checkNotAttemptingToAccess(path, OSGI_OPT_DOT))) {
+            return null;
+        }
+
+        if (path.endsWith(PATH_SEPARATOR) || path.length() == 0) {
+            return this.bundle.getEntry(path);
+        }
+
+        String searchPath;
+        String searchFile;
+        int lastSlashIndex = path.lastIndexOf(PATH_SEPARATOR);
+        if (lastSlashIndex == -1) {
+            searchPath = PATH_SEPARATOR;
+            searchFile = path;
+        } else {
+            searchPath = path.substring(0, lastSlashIndex);
+            searchFile = path.substring(lastSlashIndex + 1);
+        }
+
+        if (searchFile.equals(DOT)) {
+            return this.bundle.getEntry(path.substring(0, path.length() - 1));
+        }
+
+        Enumeration<URL> entries = this.bundle.findEntries(searchPath, searchFile, false);
+
+        if (entries != null) {
+            if (entries.hasMoreElements()) {
+                return entries.nextElement();
+            }
+        }
+
+        return null;
+    }
+
+    private boolean checkNotAttemptingToAccess(String path, String prefix) {
+        return path.startsWith(prefix + PATH_SEPARATOR) || path.startsWith(PATH_SEPARATOR + prefix + PATH_SEPARATOR)
+            || path.startsWith(DOT + PATH_SEPARATOR + prefix + PATH_SEPARATOR);
+    }
+
+    @Override
+    public String getName() {
+        String name = this.path;
+
+        if (name.endsWith(PATH_SEPARATOR)) {
+            name = name.substring(0, this.path.length() - 1);
+        }
+
+        int index = name.lastIndexOf(PATH_SEPARATOR);
+        if (index > -1) {
+            name = name.substring(index + 1);
+        }
+
+        if (name.length() == 0) {
+            return PATH_SEPARATOR;
+        } else {
+            return name;
+        }
+    }
+
+    @Override
+    public URL getURL() {
+        if (this.url == null) {
+            this.url = getEntryFromBundle(this.path);
+        }
+        return this.url;
+    }
+
+    void setURL(URL url) {
+        this.url = url;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("BundleWebResource [bundle=%s,path=%s]", this.bundle, this.path);
+    }
+
+    /**
+     * Returns the bundle entry size. If the BundleFileResolver is EquinoxBundleFileResolver then we will use equinox
+     * specific functionality to get BundleEntry and its size. If the BundleFileResolver is NoOpBundleFileResolver we
+     * will use URLConnection.getContentLength(). Note: URLConnection.getContentLength() returns "int", if the bundle
+     * entry size exceeds max "int", then the content length will not be correct.
+     *
+     * @return the bundle entry size
+     */
+    private long determineContentLength(URLConnection urlConnection) {
+        long size = this.bundleFileResolver.resolveBundleEntrySize(this.bundle, this.path);
+        if (size == -1 && urlConnection != null) {
+            size = urlConnection.getContentLength();
+        }
+        return size;
+    }
+
+    private List<Bundle> getFragments(Bundle bundle) {
+        List<Bundle> fragments = new ArrayList<Bundle>();
+        BundleRevision bundleRevision = bundle.adapt(BundleRevision.class);
+        if (bundleRevision != null) {
+            BundleWiring bundleWiring = bundleRevision.getWiring();
+            List<BundleWire> bundleWires = bundleWiring.getProvidedWires(BundleRevision.HOST_NAMESPACE);
+            for (int i = 0; bundleWires != null && i < bundleWires.size(); i++) {
+                fragments.add(bundleWires.get(i).getRequirerWiring().getRevision().getBundle());
+            }
+        }
+        return fragments;
+    }
+
+    private boolean checkEntryPath() {
+        try {
+            return Paths.get(META_INF).toRealPath().equals(Paths.get(META_INF_DOT).toRealPath());
+        } catch (IOException e) {
+            return true;
+        }
+    }
+
+    @Override
+    public boolean canRead() {
+        return true;
+    }
+
+    @Override
+    public boolean delete() {
+        return false;
+    }
+
+    @Override
+    public boolean exists() {
+        return true;
+    }
+
+    @Override
+    public String getCanonicalPath() {
+        if (isBundleLocationDirectory()) {
+            boolean checkInBundleLocation = this.path != null && this.path.indexOf("..") >= 0;
+            String bundleLocationCanonicalPath = getBundleLocationCanonicalPath();
+            Path entry = Paths.get(bundleLocationCanonicalPath, this.path);
+            if (checkInBundleLocation) {
+                try {
+                    if (!entry.toRealPath().startsWith(bundleLocationCanonicalPath)) {
+                        return null;
+                    }
+                } catch (IOException e) {
+                    return null;
+                }
+            }
+            return entry.toAbsolutePath().toString();
+        }
+        return null;
+    }
+
+    private String getBundleLocationCanonicalPath() {
+        return this.bundleLocationCanonicalPath;
+    }
+
+    private boolean isBundleLocationDirectory() {
+        return this.isBundleLocationDirectory;
+    }
+
+    @Override
+    public Certificate[] getCertificates() {
+        return null;
+    }
+
+    @Override
+    public URL getCodeBase() {
+        return getURL();
+    }
+
+    @Override
+    public byte[] getContent() {
+        long len = getContentLength();
+
+        if (len > Integer.MAX_VALUE) {
+            // Can't create an array that big
+            throw new ArrayIndexOutOfBoundsException("Unable to return [" + getWebappPath() + "] as a byte array since the resource is ["
+                + Long.valueOf(len) + "] bytes in size which is larger than the maximum size of a byte array.");
+        }
+
+        int size = (int) len;
+        byte[] result = new byte[size];
+
+        int pos = 0;
+        try (InputStream is = getURL().openStream()) {
+            while (pos < size) {
+                int n = is.read(result, pos, size - pos);
+                if (n < 0) {
+                    break;
+                }
+                pos += n;
+            }
+        } catch (IOException ioe) {
+            if (getLog().isDebugEnabled()) {
+                getLog().debug("Unable to return [" + getWebappPath() + "] as a byte array", ioe);
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public long getContentLength() {
+        return getContentLength(null);
+    }
+
+    private long getContentLength(URLConnection urlConnection) {
+        if (this.contentLength == CONTENT_LENGTH_NOT_SET) {
+            if (urlConnection == null) {
+                urlConnection = getURLConnection();
+            }
+
+            if (urlConnection != null) {
+                this.contentLength = determineContentLength(urlConnection);
+            }
+        }
+
+        return this.contentLength;
+    }
+
+    private URLConnection getURLConnection() {
+        try {
+            URL url = getURL();
+            if (url != null) {
+                return url.openConnection();
+            } else {
+                return null;
+            }
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public long getCreation() {
+        return getCreation(null, TIME_NOT_SET);
+    }
+
+    private long getCreation(URLConnection urlConnection, long lastModified) {
+        if (this.creation == TIME_NOT_SET) {
+            if (urlConnection == null) {
+                urlConnection = getURLConnection();
+            }
+
+            if (urlConnection != null) {
+                this.creation = urlConnection.getDate();
+
+                if (this.creation == CREATION_DATE_UNKNOWN) {
+                    if (lastModified == TIME_NOT_SET) {
+                        lastModified = urlConnection.getLastModified();
+                    }
+
+                    this.creation = lastModified;
+                }
+            }
+        }
+
+        return this.creation;
+    }
+
+    @Override
+    public long getLastModified() {
+        return getLastModified(null);
+    }
+
+    private long getLastModified(URLConnection urlConnection) {
+        if (this.lastModified == TIME_NOT_SET) {
+            if (urlConnection == null) {
+                urlConnection = getURLConnection();
+            }
+
+            if (urlConnection != null) {
+                this.lastModified = urlConnection.getLastModified();
+            }
+        }
+
+        return this.lastModified;
+    }
+
+    @Override
+    public Manifest getManifest() {
+        return null;
+    }
+
+    @Override
+    public boolean isDirectory() {
+        return getURL().getFile().endsWith(PATH_SEPARATOR);
+    }
+
+    @Override
+    public boolean isFile() {
+        return !getURL().getFile().endsWith(PATH_SEPARATOR);
+    }
+
+    @Override
+    public boolean isVirtual() {
+        return false;
+    }
+
+    @Override
+    protected InputStream doGetInputStream() {
+        try {
+            return getURL().openStream();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    @Override
+    protected Log getLog() {
+        return null;
+    }
+
+    Entry<BundleWebResource, URL> getNamedEntry(String name) {
+        checkCanLookup(name);
+        return getEntry(name);
+    }
+
+    private void checkCanLookup(String name) {
+        if (getBundle().getState() == Bundle.UNINSTALLED) {
+            throw new IllegalArgumentException("Resource not found [" + name + "].");
+        }
+        checkNotAttemptingToLookupFromProtectedLocation(name);
+    }
+
+    private void checkNotAttemptingToLookupFromProtectedLocation(String name) {
+        checkNotAttemptingToLookupFrom(name, "/OSGI-INF/");
+        checkNotAttemptingToLookupFrom(name, "/OSGI-OPT/");
+    }
+
+    private void checkNotAttemptingToLookupFrom(String name, String prefix) {
+        if (name.startsWith(prefix)) {
+            throw new IllegalArgumentException("Resource cannot be obtained from [" + prefix + "].");
+        }
+    }
+}
diff --git a/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceRoot.java b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceRoot.java
new file mode 100644
index 0000000..8c25a8d
--- /dev/null
+++ b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceRoot.java
@@ -0,0 +1,174 @@
+/*******************************************************************************

+ * Copyright (c) 2015 SAP SE

+ *

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * and Apache License v2.0 which accompanies this distribution.

+ * The Eclipse Public License is available at

+ *   http://www.eclipse.org/legal/epl-v10.html

+ * and the Apache License v2.0 is available at

+ *   http://www.opensource.org/licenses/apache2.0.php.

+ * You may elect to redistribute this code under either of these licenses.

+ *

+ * Contributors:

+ *   Violeta Georgieva - initial contribution

+ *******************************************************************************/

+

+package org.eclipse.gemini.web.tomcat.internal.bundleresources;

+

+import java.net.MalformedURLException;

+import java.net.URL;

+import java.nio.file.Path;

+import java.nio.file.Paths;

+import java.util.Locale;

+

+import org.apache.catalina.Host;

+import org.apache.catalina.WebResource;

+import org.apache.catalina.WebResourceSet;

+import org.apache.catalina.webresources.EmptyResourceSet;

+import org.apache.catalina.webresources.StandardRoot;

+import org.osgi.framework.Bundle;

+

+public class BundleWebResourceRoot extends StandardRoot {

+

+    private final Bundle bundle;

+

+    private final WebResource main;

+

+    private Path docBase;

+

+    public BundleWebResourceRoot(Bundle bundle, String docBaseStr) {

+        this.bundle = bundle;

+        this.main = new BundleWebResource(this.bundle, this);

+

+        if (docBaseStr != null) {

+            this.docBase = Paths.get(docBaseStr);

+            if (!this.docBase.isAbsolute()) {

+                this.docBase = Paths.get(((Host) getContext().getParent()).getAppBaseFile().getPath()).resolve(this.docBase);

+            }

+        }

+    }

+

+    @Override

+    public void createWebResourceSet(ResourceSetType type, String webAppMount, URL url, String internalPath) {

+        BaseLocation baseLocation = new BaseLocation(url);

+        createWebResourceSet(type, webAppMount, baseLocation.getBasePath(), baseLocation.getArchivePath(), internalPath);

+    }

+

+    @Override

+    public void createWebResourceSet(ResourceSetType type, String webAppMount, String base, String archivePath, String internalPath) {

+        WebResourceSet resourceSet = null;

+

+        if (base.startsWith("bundleentry")) {

+            if (archivePath != null) {

+                if (archivePath.toLowerCase(Locale.ENGLISH).endsWith(".jar")) {

+                    resourceSet = new BundleJarResourceSet(this, webAppMount, base + archivePath, internalPath);

+                } else {

+                    WebResource entry = ((BundleWebResource) this.main).getNamedEntry(archivePath).getKey();

+                    if (entry != null) {

+                        resourceSet = new BundleWebResourceSet(entry, this, webAppMount, this.docBase.toAbsolutePath().toString() + archivePath,

+                            internalPath);

+                    }

+                }

+

+                if (type.equals(ResourceSetType.CLASSES_JAR)) {

+                    resourceSet.setClassLoaderOnly(true);

+                }

+            }

+        } else {

+            // TODO need additional testing

+            WebResource entry = ((BundleWebResource) this.main).getNamedEntry(base).getKey();

+            if (entry != null) {

+                resourceSet = new BundleWebResourceSet(entry, this, webAppMount, base, internalPath);

+

+                if (type.equals(ResourceSetType.CLASSES_JAR)) {

+                    resourceSet.setClassLoaderOnly(true);

+                }

+            }

+        }

+

+        switch (type) {

+            case PRE:

+                addPreResources(resourceSet);

+                break;

+            case CLASSES_JAR:

+                addClassResources(resourceSet);

+                break;

+            case RESOURCE_JAR:

+                addJarResources(resourceSet);

+                break;

+            case POST:

+                addPostResources(resourceSet);

+                break;

+            default:

+                throw new IllegalArgumentException("Unable to create WebResourceSet of unknown type [" + type + "].");

+        }

+    }

+

+    @Override

+    protected String getObjectNameKeyProperties() {

+        StringBuilder keyProperties = new StringBuilder("type=BundleWebResourceRoot");

+        keyProperties.append(getContext().getMBeanKeyProperties());

+

+        return keyProperties.toString();

+    }

+

+    @Override

+    protected void registerURLStreamHandlerFactory() {

+        // no-op

+    }

+

+    @Override

+    protected WebResourceSet createMainResourceSet() {

+        WebResourceSet mainResourceSet;

+

+        if (this.docBase == null) {

+            // TODO When we are in not Equinox case the docBase will be null

+            // we need to handle this case also

+            mainResourceSet = new EmptyResourceSet(this);

+        } else {

+            mainResourceSet = new BundleWebResourceSet(this.main, this, "/", this.docBase.toAbsolutePath().toString(), "/");

+        }

+        return mainResourceSet;

+    }

+

+    private static class BaseLocation {

+

+        private String basePath = "";

+

+        private String archivePath = "";

+

+        BaseLocation(URL url) {

+            if ("jar".equals(url.getProtocol())) {

+                String jarUrl = url.toString();

+                int endOfFileUrl = jarUrl.indexOf("!/");

+                String fileUrl = jarUrl.substring(4, endOfFileUrl);

+                if (fileUrl.startsWith("bundleentry")) {

+                    URL file = null;

+                    try {

+                        file = new URL(fileUrl);

+                    } catch (MalformedURLException e) {

+                        throw new IllegalArgumentException(e);

+                    }

+                    this.archivePath = file.getFile();

+                    this.basePath = fileUrl.substring(0, fileUrl.indexOf(this.archivePath));

+                }

+            } else if ("bundleentry".equals(url.getProtocol())) {

+                this.archivePath = url.getFile();

+                String fileUrl = url.toString();

+                this.basePath = fileUrl.substring(0, fileUrl.indexOf(this.archivePath));

+            } else {

+                throw new IllegalArgumentException("The URL protocol [" + url.getProtocol()

+                    + "] is not supported by this web resources implementation");

+            }

+        }

+

+        String getBasePath() {

+            return this.basePath;

+        }

+

+        String getArchivePath() {

+            return this.archivePath;

+        }

+    }

+}

diff --git a/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceSet.java b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceSet.java
new file mode 100644
index 0000000..55235ac
--- /dev/null
+++ b/org.eclipse.gemini.web.tomcat/src/main/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceSet.java
@@ -0,0 +1,163 @@
+/*******************************************************************************

+ * Copyright (c) 2015 SAP SE

+ *

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * and Apache License v2.0 which accompanies this distribution.

+ * The Eclipse Public License is available at

+ *   http://www.eclipse.org/legal/epl-v10.html

+ * and the Apache License v2.0 is available at

+ *   http://www.opensource.org/licenses/apache2.0.php.

+ * You may elect to redistribute this code under either of these licenses.

+ *

+ * Contributors:

+ *   Violeta Georgieva - initial contribution

+ *******************************************************************************/

+

+package org.eclipse.gemini.web.tomcat.internal.bundleresources;

+

+import java.io.File;

+import java.net.URL;

+import java.util.ArrayList;

+import java.util.List;

+import java.util.Map.Entry;

+import java.util.Set;

+

+import org.apache.catalina.WebResource;

+import org.apache.catalina.WebResourceRoot;

+import org.apache.catalina.util.ResourceSet;

+import org.apache.catalina.webresources.DirResourceSet;

+import org.apache.catalina.webresources.EmptyResource;

+

+final class BundleWebResourceSet extends DirResourceSet {

+

+    private final WebResource bundleEntry;

+

+    BundleWebResourceSet(WebResource bundleEntry, WebResourceRoot root, String webAppMount, String base, String internalPath) {

+        super(root, webAppMount, base, internalPath);

+        this.bundleEntry = bundleEntry;

+    }

+

+    @Override

+    public WebResource getResource(String path) {

+        checkPath(path);

+        String webAppMount = getWebAppMount();

+        WebResourceRoot root = getRoot();

+        if (path.startsWith(webAppMount)) {

+            Entry<BundleWebResource, URL> entry = getNamedEntry(path.substring(webAppMount.length()));

+            if (entry != null) {

+                WebResource bundleEntry = entry.getKey();

+                if (bundleEntry == null) {

+                    return new EmptyResource(root, path);

+                }

+

+                return bundleEntry;

+            } else {

+                return new EmptyResource(root, path);

+            }

+        } else {

+            return new EmptyResource(root, path);

+        }

+    }

+

+    @Override

+    public String[] list(String path) {

+        checkPath(path);

+        String webAppMount = getWebAppMount();

+        if (path.startsWith(webAppMount)) {

+            Entry<BundleWebResource, URL> entry = getNamedEntry(path.substring(webAppMount.length()));

+            if (entry != null) {

+                BundleWebResource bundleEntry = entry.getKey();

+                if (bundleEntry == null) {

+                    return EMPTY_STRING_ARRAY;

+                }

+                List<BundleWebResource> list = bundleEntry.list();

+                String[] result = null;

+                if (list != null) {

+                    List<String> resources = new ArrayList<String>();

+                    for (BundleWebResource resource : list) {

+                        resources.add(resource.getName());

+                    }

+                    result = resources.toArray(new String[resources.size()]);

+                }

+                if (result == null) {

+                    return EMPTY_STRING_ARRAY;

+                } else {

+                    return result;

+                }

+            } else {

+                return EMPTY_STRING_ARRAY;

+            }

+        } else {

+            if (!path.endsWith("/")) {

+                path = path + "/";

+            }

+            if (webAppMount.startsWith(path)) {

+                int i = webAppMount.indexOf('/', path.length());

+                if (i == -1) {

+                    return new String[] { webAppMount.substring(path.length()) };

+                } else {

+                    return new String[] { webAppMount.substring(path.length(), i) };

+                }

+            }

+            return EMPTY_STRING_ARRAY;

+        }

+    }

+

+    @Override

+    public Set<String> listWebAppPaths(String path) {

+        checkPath(path);

+        String webAppMount = getWebAppMount();

+        ResourceSet<String> result = new ResourceSet<>();

+        if (path.startsWith(webAppMount)) {

+            Entry<BundleWebResource, URL> entry = getNamedEntry(path.substring(webAppMount.length()));

+            if (entry != null) {

+                BundleWebResource bundleEntry = entry.getKey();

+                if (bundleEntry != null) {

+                    List<BundleWebResource> list = bundleEntry.list();

+                    if (list != null) {

+                        for (BundleWebResource bEntry : list) {

+                            StringBuilder sb = new StringBuilder(path);

+                            if (path.charAt(path.length() - 1) != '/') {

+                                sb.append('/');

+                            }

+                            sb.append(bEntry.getName());

+                            if (bEntry.isDirectory()) {

+                                sb.append('/');

+                            }

+                            result.add(sb.toString());

+                        }

+                    }

+                }

+            }

+        } else {

+            if (!path.endsWith("/")) {

+                path = path + "/";

+            }

+            if (webAppMount.startsWith(path)) {

+                int i = webAppMount.indexOf('/', path.length());

+                if (i == -1) {

+                    result.add(webAppMount + "/");

+                } else {

+                    result.add(webAppMount.substring(0, i + 1));

+                }

+            }

+        }

+        result.setLocked(true);

+        return result;

+    }

+

+    @Override

+    public boolean isReadOnly() {

+        return true;

+    }

+

+    @Override

+    protected void checkType(File file) {

+        // no-op

+    }

+

+    Entry<BundleWebResource, URL> getNamedEntry(String name) {

+        return ((BundleWebResource) this.bundleEntry).getNamedEntry(name);

+    }

+}

diff --git a/org.eclipse.gemini.web.tomcat/src/test/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceSetTests.java b/org.eclipse.gemini.web.tomcat/src/test/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceSetTests.java
new file mode 100644
index 0000000..31c6110
--- /dev/null
+++ b/org.eclipse.gemini.web.tomcat/src/test/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceSetTests.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (c) 2015 SAP SE
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * and the Apache License v2.0 is available at
+ *   http://www.opensource.org/licenses/apache2.0.php.
+ * You may elect to redistribute this code under either of these licenses.
+ *
+ * Contributors:
+ *   Violeta Georgieva - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.gemini.web.tomcat.internal.bundleresources;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+
+import javax.naming.NamingException;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.WebResource;
+import org.apache.catalina.WebResourceRoot;
+import org.eclipse.gemini.web.tomcat.internal.loader.FindEntriesDelegateImpl;
+import org.eclipse.virgo.test.stubs.framework.StubBundle;
+import org.junit.Before;
+import org.junit.Test;
+
+public class BundleWebResourceSetTests {
+
+    private static final String FILE_NAME = "/sub/one.txt";
+
+    private static final String DIRECTORY_NAME = "/sub/";
+
+    private final StubBundle testBundle = new StubBundle();
+
+    private BundleWebResourceSet bundleWebResourceSet;
+
+    private WebResourceRoot root;
+
+    private Context context;
+
+    @Before
+    public void setUp() throws Exception {
+        this.testBundle.addEntry("", new File("src/test/resources/").toURI().toURL());
+        this.testBundle.addEntry(DIRECTORY_NAME, new File("src/test/resources/sub/").toURI().toURL());
+        this.testBundle.addEntry(FILE_NAME, new File("src/test/resources/sub/one.txt").toURI().toURL());
+        this.testBundle.setFindEntriesDelegate(new FindEntriesDelegateImpl(this.testBundle));
+
+        this.root = createMock(WebResourceRoot.class);
+        this.context = createMock(Context.class);
+        expect(this.root.getContext()).andReturn(this.context);
+        expect(this.context.getAddWebinfClassesResources()).andReturn(false);
+        expect(this.root.getState()).andReturn(LifecycleState.STARTED);
+    }
+
+    @Test
+    public void testGetAttributesOfDirectory() throws NamingException {
+        replay(this.root, this.context);
+
+        this.bundleWebResourceSet = new BundleWebResourceSet(new BundleWebResource(this.testBundle, this.root), this.root, "/", null, "/");
+        WebResource webResource = this.bundleWebResourceSet.getResource(DIRECTORY_NAME);
+
+        assertEquals(webResource.getName(), DIRECTORY_NAME.substring(1, DIRECTORY_NAME.length() - 1));
+
+        assertTrue(webResource.isDirectory());
+
+        assertTrue(webResource.getCreation() != -1);
+        assertTrue(webResource.getLastModified() != -1);
+
+        assertTrue(webResource.getContentLength() != -1);
+
+        verify(this.root, this.context);
+    }
+
+    @Test
+    public void testGetAttributesOfFile() throws NamingException {
+        replay(this.root, this.context);
+
+        this.bundleWebResourceSet = new BundleWebResourceSet(new BundleWebResource(this.testBundle, this.root), this.root, "/", null, "/");
+        WebResource webResource = this.bundleWebResourceSet.getResource(FILE_NAME);
+
+        assertEquals(webResource.getName(), FILE_NAME.split("/")[2]);
+
+        assertTrue(webResource.isFile());
+
+        assertTrue(webResource.getCreation() != -1);
+        assertTrue(webResource.getLastModified() != -1);
+
+        assertTrue(webResource.getContentLength() != -1);
+
+        verify(this.root, this.context);
+    }
+
+}
diff --git a/org.eclipse.gemini.web.tomcat/src/test/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceTests.java b/org.eclipse.gemini.web.tomcat/src/test/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceTests.java
new file mode 100644
index 0000000..5fc8c33
--- /dev/null
+++ b/org.eclipse.gemini.web.tomcat/src/test/java/org/eclipse/gemini/web/tomcat/internal/bundleresources/BundleWebResourceTests.java
@@ -0,0 +1,212 @@
+/*******************************************************************************
+ * Copyright (c) 2015 SAP SE
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * and the Apache License v2.0 is available at
+ *   http://www.opensource.org/licenses/apache2.0.php.
+ * You may elect to redistribute this code under either of these licenses.
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.gemini.web.tomcat.internal.bundleresources;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Vector;
+
+import org.apache.catalina.WebResourceRoot;
+import org.eclipse.gemini.web.tomcat.internal.loader.FindEntriesDelegateImpl;
+import org.eclipse.virgo.test.stubs.framework.StubBundle;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+
+public class BundleWebResourceTests {
+
+    private final StubBundle testBundle = new StubBundle();
+
+    @Before
+    public void createEntries() throws MalformedURLException {
+        this.testBundle.addEntryPaths("", createPathsEnumeration("sub/"));
+        this.testBundle.addEntryPaths("sub/", createPathsEnumeration("sub/one.txt", "sub/another.sub/"));
+        this.testBundle.addEntryPaths("sub/another.sub/", createPathsEnumeration("sub/another.sub/two.txt"));
+
+        this.testBundle.addEntry("", new File("src/test/resources/").toURI().toURL());
+        this.testBundle.addEntry("sub/", new File("src/test/resources/sub/").toURI().toURL());
+        this.testBundle.addEntry("sub/one.txt", new File("src/test/resources/sub/one.txt").toURI().toURL());
+        this.testBundle.addEntry("sub/another.sub/", new File("src/test/resources/sub/another.sub/").toURI().toURL());
+        this.testBundle.addEntry("sub/another.sub/two.txt", new File("src/test/resources/sub/another.sub/two.txt").toURI().toURL());
+        this.testBundle.addEntry("a/", new File("src/test/resources/a/").toURI().toURL());
+        this.testBundle.addEntry("a/b/", new File("src/test/resources/a/b/").toURI().toURL());
+        this.testBundle.addEntry("a/b/c.txt", new File("src/test/resources/a/b/c.txt").toURI().toURL());
+
+        this.testBundle.addEntry("/", new File("src/test/resources/").toURI().toURL());
+        this.testBundle.addEntry("/sub/", new File("src/test/resources/sub/").toURI().toURL());
+        this.testBundle.addEntry("/sub/one.txt", new File("src/test/resources/sub/one.txt").toURI().toURL());
+        this.testBundle.addEntry("/sub/another.sub/", new File("src/test/resources/sub/another.sub/").toURI().toURL());
+        this.testBundle.addEntry("/sub/another.sub/two.txt", new File("src/test/resources/sub/another.sub/two.txt").toURI().toURL());
+        this.testBundle.addEntry("/a/", new File("src/test/resources/a/").toURI().toURL());
+        this.testBundle.addEntry("/a/b/", new File("src/test/resources/a/b/").toURI().toURL());
+        this.testBundle.addEntry("/a/b/c.txt", new File("src/test/resources/a/b/c.txt").toURI().toURL());
+        this.testBundle.setFindEntriesDelegate(new FindEntriesDelegateImpl(this.testBundle));
+    }
+
+    @Test
+    public void testList() {
+        testList(this.testBundle);
+    }
+
+    @Test
+    public void testListBundleWithFragment() throws Exception {
+        Bundle bundle = createMock(Bundle.class);
+        BundleRevision bundleRevision = createMock(BundleRevision.class);
+        BundleWiring bundleWiring = createMock(BundleWiring.class);
+        BundleWire bundleWire = createMock(BundleWire.class);
+        Bundle fbundle = createMock(Bundle.class);
+        BundleRevision fbundleRevision = createMock(BundleRevision.class);
+        BundleWiring fbundleWiring = createMock(BundleWiring.class);
+        expect(bundle.getEntry("")).andReturn(Paths.get("src/test/resources/sub/").toUri().toURL()).anyTimes();
+        expect(bundle.getEntry("sub/")).andReturn(Paths.get("src/test/resources/sub/").toUri().toURL()).anyTimes();
+        expect(bundle.getEntryPaths("")).andReturn(createPathsEnumeration("sub/"));
+        expect(bundle.getEntryPaths("sub/")).andReturn(createPathsEnumeration("sub/one.txt"));
+        List<URL> entries = new ArrayList<>();
+        entries.add(Paths.get("src/test/resources/sub/one.txt").toUri().toURL());
+        expect(bundle.findEntries("sub", "one.txt", false)).andReturn(Collections.enumeration(entries)).anyTimes();
+        expect(bundle.adapt(BundleRevision.class)).andReturn(bundleRevision);
+        expect(bundleRevision.getWiring()).andReturn(bundleWiring);
+        expect(bundleWiring.getProvidedWires(BundleRevision.HOST_NAMESPACE)).andReturn(Arrays.asList(new BundleWire[] { bundleWire }));
+        expect(bundleWire.getRequirerWiring()).andReturn(fbundleWiring);
+        expect(fbundleWiring.getRevision()).andReturn(fbundleRevision);
+        expect(fbundleRevision.getBundle()).andReturn(fbundle);
+        expect(bundle.getEntry("sub/another.sub/")).andReturn(Paths.get("src/test/resources/sub/another.sub/").toUri().toURL()).anyTimes();
+        expect(fbundle.getEntryPaths("")).andReturn(createPathsEnumeration("sub/"));
+        expect(fbundle.getEntryPaths("sub/")).andReturn(createPathsEnumeration("sub/another.sub/"));
+
+        replay(bundle, bundleRevision, bundleWiring, bundleWire, fbundle, fbundleRevision, fbundleWiring);
+
+        testList(bundle);
+
+        verify(bundle, bundleRevision, bundleWiring, bundleWire, fbundle, fbundleRevision, fbundleWiring);
+    }
+
+    @Test
+    public void testGetEntry() {
+        WebResourceRoot root = createMock(WebResourceRoot.class);
+        BundleWebResource entry = new BundleWebResource(this.testBundle, root);
+
+        assertNotNull(entry.getEntry("sub/"));
+        assertNotNull(entry.getEntry("sub/one.txt"));
+        assertNotNull(entry.getEntry("sub/another.sub/"));
+        assertNotNull(entry.getEntry("sub/another.sub/two.txt"));
+        assertNotNull(entry.getEntry("."));
+        assertNotNull(entry.getEntry("sub/."));
+        assertNotNull(entry.getEntry(""));
+        assertNotNull(entry.getEntry("/"));
+
+        assertTrue(entry.getEntry("sub/").getKey().isDirectory());
+        assertTrue(entry.getEntry("sub/another.sub/").getKey().isDirectory());
+        assertTrue(entry.getEntry(".").getKey().isDirectory());
+        assertTrue(entry.getEntry("sub/.").getKey().isDirectory());
+        assertTrue(entry.getEntry("").getKey().isDirectory());
+        assertTrue(entry.getEntry("/").getKey().isDirectory());
+    }
+
+    @Test
+    public void testNames() {
+        WebResourceRoot root = createMock(WebResourceRoot.class);
+        BundleWebResource entry = new BundleWebResource(this.testBundle, root);
+
+        Entry<BundleWebResource, URL> e = entry.getEntry("/");
+        assertEquals("/", e.getKey().getName());
+
+        e = entry.getEntry("/sub/");
+        assertEquals("sub", e.getKey().getName());
+
+        e = entry.getEntry("/sub/one.txt");
+        assertEquals("one.txt", e.getKey().getName());
+
+        e = entry.getEntry("");
+        assertEquals("/", e.getKey().getName());
+
+        e = entry.getEntry("sub/");
+        assertEquals("sub", e.getKey().getName());
+
+        e = entry.getEntry("sub/one.txt");
+        assertEquals("one.txt", e.getKey().getName());
+
+        e = entry.getEntry("/a/");
+        assertEquals("a", e.getKey().getName());
+
+        e = entry.getEntry("/a/b/");
+        assertEquals("b", e.getKey().getName());
+
+        e = entry.getEntry("/a/b/c.txt");
+        assertEquals("c.txt", e.getKey().getName());
+
+        e = entry.getEntry("a/");
+        assertEquals("a", e.getKey().getName());
+
+        e = entry.getEntry("a/b/");
+        assertEquals("b", e.getKey().getName());
+
+        e = entry.getEntry("a/b/c.txt");
+        assertEquals("c.txt", e.getKey().getName());
+    }
+
+    private BundleWebResource findByPath(List<BundleWebResource> entries, String entry) {
+        for (BundleWebResource bundleEntry : entries) {
+            if (bundleEntry.getName().equals(entry)) {
+                return bundleEntry;
+            }
+        }
+        return null;
+    }
+
+    private Enumeration<String> createPathsEnumeration(String... paths) {
+        Vector<String> vector = new Vector<>();
+
+        for (String path : paths) {
+            vector.add(path);
+        }
+
+        return vector.elements();
+    }
+
+    private void testList(Bundle bundle) {
+        WebResourceRoot root = createMock(WebResourceRoot.class);
+        BundleWebResource entry = new BundleWebResource(bundle, root);
+        List<BundleWebResource> list = entry.list();
+
+        BundleWebResource subEntry = findByPath(list, "sub");
+        assertNotNull(subEntry);
+
+        list = subEntry.list();
+        assertNotNull(findByPath(list, "one.txt"));
+        assertNotNull(findByPath(list, "another.sub"));
+    }
+}
diff --git a/test-bundles/war-with-context-xml-custom-classloader/src/main/resources/classes/META-INF/resources/test.jsp b/test-bundles/war-with-context-xml-custom-classloader/src/main/resources/classes/META-INF/resources/test.jsp
new file mode 100644
index 0000000..3b12464
--- /dev/null
+++ b/test-bundles/war-with-context-xml-custom-classloader/src/main/resources/classes/META-INF/resources/test.jsp
@@ -0,0 +1 @@
+TEST
\ No newline at end of file
diff --git a/test-bundles/war-with-context-xml-custom-classloader/src/main/webapp/META-INF/context.xml b/test-bundles/war-with-context-xml-custom-classloader/src/main/webapp/META-INF/context.xml
index e6ed0c8..ebeeb67 100755
--- a/test-bundles/war-with-context-xml-custom-classloader/src/main/webapp/META-INF/context.xml
+++ b/test-bundles/war-with-context-xml-custom-classloader/src/main/webapp/META-INF/context.xml
@@ -1,4 +1,4 @@
-<Context>
+<Context addWebinfClassesResources="true">
 
    <!--Loader className="org.apache.catalina.loader.WebappLoader"/-->