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"/-->