Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.gemini.web.core/src/main')
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/ConnectorDescriptor.java51
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/InstallationOptions.java202
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebApplication.java62
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebApplicationStartFailedException.java42
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebBundleManifestTransformer.java48
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebContainer.java143
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebContainerProperties.java41
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ContextPathExistsException.java38
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ServletContainer.java50
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ServletContainerException.java40
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/WebApplicationHandle.java49
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/EventManager.java156
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/StandardWebApplication.java160
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/StandardWebContainer.java79
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/SystemBundleExportsResolver.java79
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebApplicationStartFailureRetryController.java104
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebContainerActivator.java130
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebContainerUtils.java207
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceCallback.java21
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceTemplate.java70
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceUnavailableException.java42
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/ChainingWebBundleManifestTransformer.java44
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/DefaultsWebBundleManifestTransformer.java108
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/PackageMergeUtils.java81
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/PackagesInWarScanner.java46
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/Path.java165
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/SpecificationWebBundleManifestTransformer.java218
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/SystemBundleExportsImportingWebBundleManifestTransformer.java55
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleScanner.java298
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleScannerCallback.java24
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleUrl.java130
-rw-r--r--org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleUrlStreamHandlerService.java134
-rw-r--r--org.eclipse.gemini.web.core/src/main/resources/META-INF/MANIFEST.MF25
33 files changed, 3142 insertions, 0 deletions
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/ConnectorDescriptor.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/ConnectorDescriptor.java
new file mode 100644
index 0000000..12cdd82
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/ConnectorDescriptor.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.core;
+
+/**
+ * <p>
+ * ConnectorDescriptor describes a configured connector to the web layer
+ * </p>
+ *
+ * <strong>Concurrent Semantics</strong><br />
+ *
+ * ConnectorDescriptor is thread-safe
+ *
+ */
+public interface ConnectorDescriptor {
+
+ /**
+ * @return The protocol being used, eg. HTTP-1.1
+ */
+ String getProtocol();
+
+ /**
+ * @return The scheme of the connector, eg. http.
+ */
+ String getScheme();
+
+ /**
+ * @return The port the connector is listening on, eg. 8080
+ */
+ int getPort();
+
+ /**
+ * @return true iff ssl is enabled
+ */
+ boolean sslEnabled();
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/InstallationOptions.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/InstallationOptions.java
new file mode 100644
index 0000000..b0b5ff0
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/InstallationOptions.java
@@ -0,0 +1,202 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.core;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.gemini.web.internal.WebContainerUtils;
+import org.osgi.framework.Constants;
+
+
+/**
+ * Simple utility class that parses the user-supplied installation options from a {@link Map}.
+ */
+public final class InstallationOptions {
+
+ private static final class CaseInsensitiveMap extends HashMap<String, String> {
+
+ private static final long serialVersionUID = -514044030419642872L;
+
+ @Override
+ public String get(Object key) {
+ if (key instanceof String) {
+ return super.get(normaliseOption((String) key));
+ }
+ return super.get(key);
+ }
+
+ @Override
+ public String put(String key, String value) {
+ return super.put(normaliseOption(key), value);
+ }
+
+ private String normaliseOption(String key) {
+ return key.toLowerCase(Locale.ENGLISH);
+ }
+
+ }
+
+ private final String bundleSymbolicName;
+
+ private final String bundleVersion;
+
+ private final String bundleManifestVersion;
+
+ private final String bundleClassPath;
+
+ private final String importPackageDeclaration;
+
+ private final String exportPackageDeclaration;
+
+ private final String webContextPath;
+
+ private final String webJSPExtractLocation;
+
+ private volatile boolean defaultWABHeaders;
+
+ /**
+ * Creates a new <code>InstallationOptions</code> from the supplied <code>options</code> {@link Map}.
+ * <p/>
+ * Changes to the <code>options</code> <code>Map</code> are not reflected in the new instance.
+ *
+ * @param options the options
+ */
+ public InstallationOptions(Map<String, String> options) {
+ Map<String, String> normalisedOptions = normalise(options);
+
+ this.bundleSymbolicName = normalisedOptions.get(Constants.BUNDLE_SYMBOLICNAME);
+ this.bundleVersion = normalisedOptions.get(Constants.BUNDLE_VERSION);
+ this.bundleManifestVersion = normalisedOptions.get(Constants.BUNDLE_MANIFESTVERSION);
+ this.bundleClassPath = normalisedOptions.get(Constants.BUNDLE_CLASSPATH);
+
+ this.importPackageDeclaration = normalisedOptions.get(Constants.IMPORT_PACKAGE);
+ this.exportPackageDeclaration = normalisedOptions.get(Constants.EXPORT_PACKAGE);
+
+ this.webContextPath = normalisedOptions.get(WebContainerUtils.HEADER_WEB_CONTEXT_PATH);
+ this.webJSPExtractLocation = normalisedOptions.get(WebContainerUtils.HEADER_WEB_JSP_EXTRACT_LOCATION);
+
+ this.defaultWABHeaders = options.get(WebContainerUtils.HEADER_SPRINGSOURCE_DEFAULT_WAB_HEADERS) != null;
+ }
+
+ private Map<String, String> normalise(Map<String, String> options) {
+ Set<String> keys = options.keySet();
+ Map<String, String> normalisedOptions = new CaseInsensitiveMap();
+ for (String key : keys) {
+ normalisedOptions.put(key, options.get(key));
+ }
+ return normalisedOptions;
+ }
+
+ /**
+ * Gets the symbolic name installation option. This option overrides the <code>Bundle-SymbolicName</code> set in the
+ * web bundle manifest.
+ *
+ * @return the symbolic name installation option.
+ */
+ public String getBundleSymbolicName() {
+ return this.bundleSymbolicName;
+ }
+
+ /**
+ * Get the bundle version installation option. This option overrides the <code>Bundle-Version</code> set in the web
+ * bundle manifest.
+ *
+ * @return the bundle version installation option.
+ */
+ public String getBundleVersion() {
+ return this.bundleVersion;
+ }
+
+ /**
+ * Gets the unvalidated bundle manifest version installation option. This option overrides the
+ * <code>Bundle-ManifestVersion</code> set in the web bundle manifest.
+ * <p/>
+ * The bundle manifest version must not be validated early otherwise the IllegalArgumentException will not be turned
+ * into a BundleException by the OSGi framework.
+ *
+ * @return the unvalidated bundle manifest version installation option.
+ */
+ public String getBundleManifestVersion() {
+ return this.bundleManifestVersion;
+ }
+
+ /**
+ * Gets the bundle class path installation option. This option overrides the <code>Bundle-ClassPath</code> set in
+ * the web bundle manifest.
+ *
+ * @return the bundle class path installation option.
+ */
+ public String getBundleClassPath() {
+ return this.bundleClassPath;
+ }
+
+ /**
+ * Gets the import package installation option.
+ *
+ * @return the import package installation option.
+ */
+ public String getImportPackageDeclaration() {
+ return this.importPackageDeclaration;
+ }
+
+ /**
+ * Gets the export package installation option.
+ *
+ * @return the export package installation option.
+ */
+ public String getExportPackageDeclaration() {
+ return this.exportPackageDeclaration;
+ }
+
+ /**
+ * Gets the web context path installation option. This option overrides the context path specified in the manifest.
+ *
+ * @return the web context path installation option.
+ */
+ public String getWebContextPath() {
+ return this.webContextPath;
+ }
+
+ /**
+ * Gets the JSP extract location installation option. This option controls where the servlet container will extract
+ * application JSPs.
+ *
+ * @return the JSP extract location installation option.
+ */
+ public String getWebJSPExtractLocation() {
+ return this.webJSPExtractLocation;
+ }
+
+ /**
+ * Returns whether Web Application Bundle (WAB) manifest headers should be defaulted or not. This should be
+ * <code>false</code> for strict compliance with the OSGi Web Container specification and <code>true</code> for
+ * compatibility with the behaviour shipped with dm Server 2.0.0.RELEASE.
+ *
+ * @return the default WAB headers installation option.
+ */
+ public boolean getDefaultWABHeaders() {
+ return this.defaultWABHeaders;
+ }
+
+ public void setDefaultWABHeaders(boolean defaultWABHeaders) {
+ this.defaultWABHeaders = defaultWABHeaders;
+ }
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebApplication.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebApplication.java
new file mode 100644
index 0000000..542cf17
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebApplication.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.core;
+
+import javax.servlet.ServletContext;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * Represents a web application managed by a {@link WebContainer}.
+ *
+ * <p/>
+ *
+ * Web applications are created from valid web bundles using {@link WebContainer#createWebApplication(Bundle)}.
+ *
+ *
+ */
+public interface WebApplication {
+
+ /**
+ * Gets the {@link ServletContext} associated with this web application.
+ *
+ * @return the <code>ServletContext</code>, never <code>null</code>.
+ */
+ ServletContext getServletContext();
+
+ /**
+ * Gets the {@link ClassLoader} of this web application.
+ *
+ * @return the web application's <code>ClassLoader</code>.
+ */
+ ClassLoader getClassLoader();
+
+ /**
+ * Starts this web application under the {@link ServletContext#getContextPath() configured context path}.
+ * <p/>
+ * If the application fails to start an {@link EventAdmin} event is emitted to the
+ * <code>org/osgi/services/web/FAILED</code> topic and a {@link WebApplicationStartFailedException} is thrown.
+ * @throws WebApplicationStartFailedException
+ */
+ void start() throws WebApplicationStartFailedException;
+
+ /**
+ * Stops this web application. After stop the web application is no longer available to serve content.
+ */
+ void stop();
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebApplicationStartFailedException.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebApplicationStartFailedException.java
new file mode 100644
index 0000000..2b2cd23
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebApplicationStartFailedException.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.core;
+
+
+/**
+ * Thrown to signal that a {@link WebApplication} has failed to {@link WebApplication#start()}.
+
+ * <p />
+ *
+ * <strong>Concurrent Semantics</strong><br />
+ *
+ * Thread-safe.
+ *
+ */
+public final class WebApplicationStartFailedException extends RuntimeException {
+
+ private static final long serialVersionUID = -1722479683094175136L;
+
+ /**
+ * Creates a new WebApplicationStartFailedException with the supplied cause
+ * @param cause The cause of the failure
+ */
+ public WebApplicationStartFailedException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebBundleManifestTransformer.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebBundleManifestTransformer.java
new file mode 100644
index 0000000..bab96b4
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebBundleManifestTransformer.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.core;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
+
+/**
+ * Strategy for applying transformations to a web bundle manifest.
+ * <p/>
+ * The exact set of transformations performed is implementation-dependent, but it is expected that implementations will
+ * at least support the transformations mandated by the RFC66 specification.
+ * <p/>
+ * Transformations that are not defined by the specification should be disabled by default and enabled using an
+ * {@link InstallationOptions installation option}.
+ *
+ *
+ */
+public interface WebBundleManifestTransformer {
+
+ /**
+ * Transforms the supplied {@link BundleManifest} in place.
+ *
+ * @param manifest the <code>BundleManifest</code> to transform.
+ * @param sourceURL the {@link URL} the bundle was installed from.
+ * @param options the {@link InstallationOptions}. May be <code>null</code>.
+ * @param webBundle whether or not the bundle is deemed to be a bundle as determined by the
+ * {@link org.eclipse.gemini.web.internal.WebContainerUtils#isWebApplicationBundle isWebApplicationBundle} specification.
+ * @throws IOException if transformation fails.
+ */
+ void transform(BundleManifest manifest, URL sourceURL, InstallationOptions options, boolean webBundle) throws IOException;
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebContainer.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebContainer.java
new file mode 100644
index 0000000..1dd67f7
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebContainer.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.core;
+
+import javax.servlet.ServletContext;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.service.event.EventAdmin;
+
+/**
+ * A <code>WebContainer</code> provides a mechanism for creating {@link WebApplication WebApplications} from
+ * user-supplied web bundles.
+ * <p/>
+ * The <code>WebContainer</code> provides programmatic access to the web bundle deployment functionality that is
+ * typically accessed via the extender.
+ *
+ */
+public interface WebContainer {
+
+ /**
+ * The {@link ServletContext} attribute under which the {@link BundleContext} is available.
+ */
+ public static final String ATTRIBUTE_BUNDLE_CONTEXT = "osgi-bundlecontext";
+
+ static final String EVENT_NAME_PREFIX = "org/osgi/service/web/";
+
+ /**
+ * The {@link EventAdmin} topic for web bundle <code>DEPLOYING</code> events.
+ */
+ static final String EVENT_DEPLOYING = EVENT_NAME_PREFIX + "DEPLOYING";
+
+ /**
+ * The {@link EventAdmin} topic for web bundle <code>DEPLOYED</code> events.
+ */
+ static final String EVENT_DEPLOYED = EVENT_NAME_PREFIX + "DEPLOYED";
+
+ /**
+ * The {@link EventAdmin} topic for web bundle <code>UNDEPLOYING</code> events.
+ */
+ static final String EVENT_UNDEPLOYING = EVENT_NAME_PREFIX + "UNDEPLOYING";
+
+ /**
+ * The {@link EventAdmin} topic for web bundle <code>UNDEPLOYED</code> events.
+ */
+ static final String EVENT_UNDEPLOYED = EVENT_NAME_PREFIX + "UNDEPLOYED";
+
+ /**
+ * The {@link EventAdmin} topic for web bundle <code>FAILED</code> events.
+ */
+ static final String EVENT_FAILED = EVENT_NAME_PREFIX + "FAILED";
+
+ /**
+ * The {@link org.osgi.service.event.Event Event} property for the web application bundle's context path.
+ */
+ static final String EVENT_PROPERTY_CONTEXT_PATH = "context.path";
+
+ /**
+ * The {@link org.osgi.service.event.Event Event} property for the web application bundle's version.
+ */
+ static final String EVENT_PROPERTY_BUNDLE_VERSION = "bundle.version";
+
+ /**
+ * The {@link org.osgi.service.event.Event Event} property for the web container extender bundle.
+ */
+ static final String EVENT_PROPERTY_EXTENDER_BUNDLE = "extender.bundle";
+
+ /**
+ * The {@link org.osgi.service.event.Event Event} property for the web container extender bundle's id.
+ */
+ static final String EVENT_PROPERTY_EXTENDER_BUNDLE_ID = "extender.bundle.id";
+
+ /**
+ * The {@link org.osgi.service.event.Event Event} property for the web container extender bundle's symbolic name.
+ */
+ static final String EVENT_PROPERTY_EXTENDER_BUNDLE_SYMBOLICNAME = "extender.bundle.symbolicName";
+
+ /**
+ * The {@link org.osgi.service.event.Event Event} property for the web container extender bundle's version.
+ */
+ static final String EVENT_PROPERTY_EXTENDER_BUNDLE_VERSION = "extender.bundle.version";
+
+ /**
+ * The {@link org.osgi.service.event.Event Event} property containing a Web-ContextPath which is shared by more than one bundle.
+ */
+ static final String EVENT_PROPERTY_COLLISION = "collision";
+
+ /**
+ * The {@link org.osgi.service.event.Event Event} property listing, in the case of a collsion, the bundle ids, as a
+ * <code>Collection&lt;Long&gt;</code>, which share the same Web-ContextPath.
+ */
+ static final String EVENT_PROPERTY_COLLISION_BUNDLES = "collision.bundles";
+
+ /**
+ * Creates a {@link WebApplication} for the supplied web bundle. Equivalent to calling
+ * {@link #createWebApplication(Bundle, Bundle) createWebApplication(bundle, null)}.
+ *
+ * @param bundle the web bundle
+ *
+ * @return the newly created <code>WebApplication</code>.
+ * @throws BundleException if the <code>WebApplication</code> cannot be created.
+ */
+ WebApplication createWebApplication(Bundle bundle) throws BundleException;
+
+ /**
+ * Creates a {@link WebApplication} for the supplied web bundle.
+ *
+ * @param bundle the web bundle
+ * @param extender the extender bundle that has trigger the creation of the web application, or <code>null</code> if
+ * an extender is not involved.
+ * @return the newly created <code>WebApplication</code>.
+ * @throws BundleException if the <code>WebApplication</code> cannot be created.
+ */
+ WebApplication createWebApplication(Bundle bundle, Bundle extender) throws BundleException;
+
+ /**
+ * Checks to see if the supplied {@link Bundle} is a valid web bundle.
+ *
+ * @param bundle the bundle to check.
+ * @return <code>true</code> if the supplied bundle is a valid web bundle; otherwise <code>false</code>.
+ */
+ boolean isWebBundle(Bundle bundle);
+
+ /**
+ * Stops the web container.
+ */
+ public void halt();
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebContainerProperties.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebContainerProperties.java
new file mode 100644
index 0000000..a2a099c
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/WebContainerProperties.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.core;
+
+import java.util.Set;
+
+/**
+ * <p>
+ * WebContainerProperties allows applications running on this RFC66
+ * implementation to obtain the properties used to configure it.
+ * </p>
+ *
+ * <strong>Concurrent Semantics</strong><br />
+ *
+ * thread-safe
+ *
+ */
+public interface WebContainerProperties {
+
+ /**
+ * The port that this webcontainer is listening on.
+ *
+ * @return the port number
+ */
+ Set<ConnectorDescriptor> getConnectorDescriptors();
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ContextPathExistsException.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ContextPathExistsException.java
new file mode 100644
index 0000000..b089f13
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ContextPathExistsException.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.core.spi;
+
+/**
+ * Exception signalling that a context path referred to by a web application is already in use by another web
+ * application.
+ *
+ */
+public class ContextPathExistsException extends ServletContainerException {
+
+ private static final long serialVersionUID = 1846326281773843365L;
+
+ private final String contextPath;
+
+ public ContextPathExistsException(String contextPath) {
+ super("Context path '" + contextPath + "' already exists");
+ this.contextPath = contextPath;
+ }
+
+ public String getContextPath() {
+ return contextPath;
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ServletContainer.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ServletContainer.java
new file mode 100644
index 0000000..4656c5a
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ServletContainer.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.core.spi;
+
+import org.osgi.framework.Bundle;
+
+public interface ServletContainer {
+
+ /**
+ * Creates a web application for the supplied {@link Bundle}.
+ *
+ * @param contextPath the context path the web application should run under.
+ * @param bundle the <code>Bundle</code> containing the web application content.
+ * @return a handle to the web application that can be used to drive lifecycle events.
+ * @throws ServletContainerException if the web application cannot be created.
+ */
+ WebApplicationHandle createWebApplication(String contextPath, Bundle bundle);
+
+ /**
+ * Starts the web application referred to by the supplied {@link WebApplicationHandle}.
+ *
+ * @param handle the handle to the web application to start.
+ * @throws ContextPathExistsException if the context path is already in use.
+ * @throws ServletContainerException if the web application fails to start.
+ */
+ void startWebApplication(WebApplicationHandle handle);
+
+ /**
+ * Stops the web application referred to by the supplied {@link WebApplicationHandle}.
+ *
+ * @param handle the handle to the web application to stop.
+ * @throws ServletContainerException if the web application fails to stop.
+ */
+ void stopWebApplication(WebApplicationHandle handle);
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ServletContainerException.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ServletContainerException.java
new file mode 100644
index 0000000..5cf885e
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/ServletContainerException.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.core.spi;
+
+
+public class ServletContainerException extends RuntimeException {
+
+ private static final long serialVersionUID = -4955082179218908312L;
+
+ public ServletContainerException() {
+ }
+
+ public ServletContainerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ServletContainerException(String message) {
+ super(message);
+ }
+
+ public ServletContainerException(Throwable cause) {
+ super(cause);
+ }
+
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/WebApplicationHandle.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/WebApplicationHandle.java
new file mode 100644
index 0000000..ffd89ec
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/core/spi/WebApplicationHandle.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.core.spi;
+
+import javax.servlet.ServletContext;
+
+/**
+ * Handle to a web application deployed in a {@link ServletContainer}. <code>ServletContainer</code> implementations
+ * will create custom subclasses of this interface and return them from {@link ServletContainer#createWebApplication}.
+ * The <code>ServletContainer</code> can store any state need during {@link ServletContainer#startWebApplication(WebApplicationHandle) start} and
+ * {@link ServletContainer#stopWebApplication(WebApplicationHandle) stop} in this custom implementation.
+ * <p/>
+ * Client code <strong>must</strong> return the correct handle to the <code>ServletContainer</code> when starting or
+ * stopping an application.
+ *
+ */
+public interface WebApplicationHandle {
+
+ /**
+ * Gets the {@link ServletContext} of the deployed web application.
+ *
+ * @return the <code>ServletContext</code>.
+ */
+ ServletContext getServletContext();
+
+ /**
+ * Gets the {@link ClassLoader} of the deployed web application. May be <code>null</code> if the
+ * web application has not yet been {@link ServletContainer#startWebApplication(WebApplicationHandle)
+ * started}.
+ *
+ * @return the <code>ClassLoader</code>.
+ */
+ ClassLoader getClassLoader();
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/EventManager.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/EventManager.java
new file mode 100644
index 0000000..96928fe
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/EventManager.java
@@ -0,0 +1,156 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal;
+
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_DEPLOYED;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_DEPLOYING;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_FAILED;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_PROPERTY_BUNDLE_VERSION;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_PROPERTY_COLLISION;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_PROPERTY_COLLISION_BUNDLES;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_PROPERTY_CONTEXT_PATH;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_PROPERTY_EXTENDER_BUNDLE;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_PROPERTY_EXTENDER_BUNDLE_ID;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_PROPERTY_EXTENDER_BUNDLE_SYMBOLICNAME;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_PROPERTY_EXTENDER_BUNDLE_VERSION;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_UNDEPLOYED;
+import static org.eclipse.gemini.web.core.WebContainer.EVENT_UNDEPLOYING;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.eclipse.gemini.web.internal.template.ServiceCallback;
+import org.eclipse.gemini.web.internal.template.ServiceTemplate;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+
+
+/**
+ * TODO: Need a better implementation of this - abstraction is poor :)
+ *
+ */
+final class EventManager {
+
+ private final ServiceTemplate<EventAdmin> template;
+
+ public EventManager(BundleContext context) {
+ if (isEventAdminAvailable()) {
+ this.template = new ServiceTemplate<EventAdmin>(context, EventAdmin.class);
+ } else {
+ this.template = null;
+ }
+ }
+
+ public void start() {
+ if (this.template != null) {
+ this.template.start();
+ }
+ }
+
+ public void stop() {
+ if (this.template != null) {
+ this.template.stop();
+ }
+ }
+
+ public void sendDeploying(Bundle applicationBundle, Bundle extenderBundle, String contextPath) {
+ sendEvent(EVENT_DEPLOYING, applicationBundle, extenderBundle, contextPath, null, null, null);
+ }
+
+ public void sendDeployed(Bundle applicationBundle, Bundle extenderBundle, String contextPath) {
+ sendEvent(EVENT_DEPLOYED, applicationBundle, extenderBundle, contextPath, null, null, null);
+ }
+
+ public void sendUndeploying(Bundle applicationBundle, Bundle extenderBundle, String contextPath) {
+ sendEvent(EVENT_UNDEPLOYING, applicationBundle, extenderBundle, contextPath, null, null, null);
+ }
+
+ public void sendUndeployed(Bundle applicationBundle, Bundle extenderBundle, String contextPath) {
+ sendEvent(EVENT_UNDEPLOYED, applicationBundle, extenderBundle, contextPath, null, null, null);
+ }
+
+ public void sendFailed(Bundle applicationBundle, Bundle extenderBundle, String contextPath, Exception ex, String collidingWebContextPath,
+ Set<Long> collisionBundles) {
+ sendEvent(EVENT_FAILED, applicationBundle, extenderBundle, contextPath, ex, collidingWebContextPath, collisionBundles);
+ }
+
+ private void sendEvent(final String eventName, final Bundle applicationBundle, final Bundle extenderBundle, final String contextPath,
+ final Throwable ex, final String collidingWebContextPath, final Set<Long> collisionBundles) {
+ if (this.template != null) {
+ this.template.executeWithService(new ServiceCallback<EventAdmin, Void>() {
+
+ public Void doWithService(EventAdmin eventAdmin) {
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ if (applicationBundle.getSymbolicName() != null) {
+ props.put(EventConstants.BUNDLE_SYMBOLICNAME, applicationBundle.getSymbolicName());
+ }
+ props.put(EventConstants.BUNDLE_ID, applicationBundle.getBundleId());
+ props.put(EventConstants.BUNDLE, applicationBundle);
+ props.put(EVENT_PROPERTY_BUNDLE_VERSION, applicationBundle.getVersion());
+ props.put(EventConstants.TIMESTAMP, System.currentTimeMillis());
+ props.put(EVENT_PROPERTY_CONTEXT_PATH, contextPath);
+
+ if (extenderBundle != null) {
+ props.put(EVENT_PROPERTY_EXTENDER_BUNDLE, extenderBundle);
+ props.put(EVENT_PROPERTY_EXTENDER_BUNDLE_ID, extenderBundle.getBundleId());
+ if (extenderBundle.getSymbolicName() != null) {
+ props.put(EVENT_PROPERTY_EXTENDER_BUNDLE_SYMBOLICNAME, extenderBundle.getSymbolicName());
+ }
+ props.put(EVENT_PROPERTY_EXTENDER_BUNDLE_VERSION, extenderBundle.getVersion());
+ }
+
+ if (ex != null) {
+ props.put(EventConstants.EXCEPTION, ex);
+ }
+
+ if (collidingWebContextPath != null) {
+ props.put(EVENT_PROPERTY_COLLISION, collidingWebContextPath);
+
+ /*
+ * Prevent event handlers modifying the set of collision bundles.
+ *
+ * Note: OSGi specs prefer Collection to Set even when there cannot be duplicates.
+ */
+ Collection<Long> immutableCollisionBundles = Collections.unmodifiableCollection(collisionBundles);
+ props.put(EVENT_PROPERTY_COLLISION_BUNDLES, immutableCollisionBundles);
+ }
+
+ eventAdmin.sendEvent(new Event(eventName, props));
+ return null;
+ }
+
+ });
+ }
+ }
+
+ private boolean isEventAdminAvailable() {
+ try {
+ getClass().getClassLoader().loadClass(EventAdmin.class.getName());
+ return true;
+ } catch (NoClassDefFoundError ex) {
+ return false;
+ } catch (ClassNotFoundException ex) {
+ return false;
+ }
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/StandardWebApplication.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/StandardWebApplication.java
new file mode 100644
index 0000000..9f88dd1
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/StandardWebApplication.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal;
+
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+import org.eclipse.gemini.web.core.WebApplication;
+import org.eclipse.gemini.web.core.WebApplicationStartFailedException;
+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.virgo.util.osgi.ServiceRegistrationTracker;
+
+final class StandardWebApplication implements WebApplication {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(StandardWebApplication.class);
+
+ private final BundleContext bundleContext;
+
+ private final Bundle extender;
+
+ private final WebApplicationHandle handle;
+
+ private final ServletContainer container;
+
+ private final ServiceRegistrationTracker tracker = new ServiceRegistrationTracker();
+
+ private final EventManager eventManager;
+
+ private boolean started = false;
+
+ private final Object monitor = new Object();
+
+ private final WebApplicationStartFailureRetryController retryController;
+
+ public StandardWebApplication(BundleContext bundleContext, Bundle extender, WebApplicationHandle handle, ServletContainer container,
+ EventManager eventManager, WebApplicationStartFailureRetryController retryController) {
+ this.bundleContext = bundleContext;
+ this.extender = extender;
+ this.handle = handle;
+ this.container = container;
+ this.eventManager = eventManager;
+ this.retryController = retryController;
+ }
+
+ public ServletContext getServletContext() {
+ return this.handle.getServletContext();
+ }
+
+ public ClassLoader getClassLoader() {
+ return this.handle.getClassLoader();
+ }
+
+ public void start() {
+ boolean localStarted;
+
+ synchronized (this.monitor) {
+ localStarted = this.started;
+ }
+
+ if (!localStarted) {
+ String webContextPath = getContextPath();
+ this.eventManager.sendDeploying(getBundle(), this.extender, webContextPath);
+
+ try {
+ this.container.startWebApplication(this.handle);
+ publishServletContext();
+
+ synchronized (this.monitor) {
+ this.started = true;
+ }
+
+ this.eventManager.sendDeployed(getBundle(), this.extender, webContextPath);
+ } catch (ServletContainerException ex) {
+ if (LOGGER.isErrorEnabled()) {
+ LOGGER.error("Failed to start web application at bundleContext path '" + this.handle.getServletContext().getContextPath() + "'", ex);
+ }
+ this.retryController.recordFailure(this);
+ Set<Long> webContextPathBundleIds = getWebContextPathBundleIds(webContextPath);
+ boolean collision = webContextPathBundleIds.size() > 1;
+ this.eventManager.sendFailed(getBundle(), this.extender, webContextPath, ex, collision ? webContextPath : null,
+ collision ? webContextPathBundleIds : null);
+ throw new WebApplicationStartFailedException(ex);
+ }
+ }
+ }
+
+ private Set<Long> getWebContextPathBundleIds(String webContextPath) {
+ Set<Long> bundleIds = new HashSet<Long>();
+ for (Bundle bundle : this.bundleContext.getBundles()) {
+ if (webContextPath.equals(WebContainerUtils.getContextPath(bundle))) {
+ bundleIds.add(bundle.getBundleId());
+ }
+ }
+ return bundleIds;
+ }
+
+ public void stop() {
+ boolean localStarted;
+
+ synchronized (this.monitor) {
+ localStarted = this.started;
+ this.started = false;
+ }
+
+ if (localStarted) {
+ this.eventManager.sendUndeploying(getBundle(), this.extender, getContextPath());
+ this.container.stopWebApplication(this.handle);
+ this.tracker.unregisterAll();
+ this.eventManager.sendUndeployed(getBundle(), this.extender, getContextPath());
+ }
+ this.retryController.retryFailures(this);
+ }
+
+ private void publishServletContext() {
+ Properties properties = constructServletContextProperties();
+ this.tracker.track(this.bundleContext.registerService(ServletContext.class.getName(), getServletContext(), properties));
+ }
+
+ String getContextPath() {
+ return this.handle.getServletContext().getContextPath();
+ }
+
+ Bundle getBundle() {
+ return this.bundleContext.getBundle();
+ }
+
+ private Properties constructServletContextProperties() {
+ Properties properties = new Properties();
+ Bundle bundle = getBundle();
+ WebContainerUtils.setServletContextBundleProperties(properties, bundle);
+ properties.setProperty("osgi.web.contextpath", getServletContext().getContextPath());
+ return properties;
+ }
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/StandardWebContainer.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/StandardWebContainer.java
new file mode 100644
index 0000000..9662423
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/StandardWebContainer.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal;
+
+import org.eclipse.gemini.web.core.WebApplication;
+import org.eclipse.gemini.web.core.WebContainer;
+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.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Standard implementation of {@link WebContainer}.
+ */
+final class StandardWebContainer implements WebContainer {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(StandardWebContainer.class);
+
+ private final EventManager eventManager;
+
+ private final ServletContainer servletContainer;
+
+ private final WebApplicationStartFailureRetryController retryController = new WebApplicationStartFailureRetryController();
+
+ public StandardWebContainer(ServletContainer servletContainer, EventManager eventManager) {
+ this.servletContainer = servletContainer;
+ this.eventManager = eventManager;
+ }
+
+ public WebApplication createWebApplication(Bundle bundle) throws BundleException {
+ return this.createWebApplication(bundle, null);
+ }
+
+ public WebApplication createWebApplication(Bundle bundle, Bundle extender) throws BundleException {
+ if (!isWebBundle(bundle)) {
+ throw new BundleException("Bundle '" + bundle + "' is not a valid web bundle.");
+ }
+ try {
+ WebApplicationHandle handle = this.servletContainer.createWebApplication(WebContainerUtils.getContextPath(bundle), bundle);
+ handle.getServletContext().setAttribute(ATTRIBUTE_BUNDLE_CONTEXT, bundle.getBundleContext());
+ return new StandardWebApplication(bundle.getBundleContext(), extender, handle, this.servletContainer, this.eventManager, this.retryController);
+ } catch (ServletContainerException ex) {
+ if (LOGGER.isErrorEnabled()) {
+ LOGGER.error("Failed to create web application for bundle '" + bundle + "'", ex);
+ }
+ throw new BundleException("Failed to create web application for bundle '" + bundle + "'", ex);
+ }
+ }
+
+ public boolean isWebBundle(Bundle bundle) {
+ return WebContainerUtils.isWebBundle(bundle);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void halt() {
+ this.retryController.clear();
+ }
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/SystemBundleExportsResolver.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/SystemBundleExportsResolver.java
new file mode 100644
index 0000000..9c0ddf1
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/SystemBundleExportsResolver.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Version;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+import org.eclipse.gemini.web.internal.template.ServiceCallback;
+import org.eclipse.gemini.web.internal.template.ServiceTemplate;
+import org.eclipse.virgo.util.osgi.VersionRange;
+
+final class SystemBundleExportsResolver {
+
+ private final BundleContext bundleContext;
+
+ SystemBundleExportsResolver(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ public Map<String, VersionRange> getSystemBundleExports() {
+ final Bundle systemBundle = bundleContext.getBundle(0);
+ ServiceTemplate<PackageAdmin> packageAdminTemplate = new ServiceTemplate<PackageAdmin>(bundleContext, PackageAdmin.class);
+ packageAdminTemplate.start();
+ ExportedPackage[] systemBundleExports = packageAdminTemplate.executeWithService(new ServiceCallback<PackageAdmin, ExportedPackage[]>() {
+ public ExportedPackage[] doWithService(PackageAdmin packageAdmin) {
+ return packageAdmin.getExportedPackages(systemBundle);
+ }
+ });
+ packageAdminTemplate.stop();
+ return combineDuplicateExports(systemBundleExports);
+ }
+
+ static Map<String, VersionRange> combineDuplicateExports(ExportedPackage[] allExportedPackages) {
+ Map<String, VersionRange> exportedPackages = new HashMap<String, VersionRange>();
+ for (ExportedPackage exportedPackage : allExportedPackages) {
+ VersionRange versionRange = exportedPackages.get(exportedPackage.getName());
+ if (versionRange == null) {
+ versionRange = VersionRange.createExactRange(exportedPackage.getVersion());
+ } else {
+ Version version = exportedPackage.getVersion();
+ if (!versionRange.includes(version)) {
+ versionRange = expandVersionRange(version, versionRange);
+ }
+ }
+ exportedPackages.put(exportedPackage.getName(), versionRange);
+ }
+
+ return exportedPackages;
+ }
+
+ private static VersionRange expandVersionRange(Version version, VersionRange versionRange) {
+ Version ceiling = versionRange.getCeiling();
+ if (version.compareTo(ceiling) > 0) {
+ return new VersionRange("[" + versionRange.getFloor() + "," + version + "]");
+ } else {
+ return new VersionRange("[" + version + "," + versionRange.getCeiling() + "]");
+ }
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebApplicationStartFailureRetryController.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebApplicationStartFailureRetryController.java
new file mode 100644
index 0000000..ae9fb33
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebApplicationStartFailureRetryController.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal;
+
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.gemini.web.core.WebApplication;
+import org.eclipse.gemini.web.core.WebApplicationStartFailedException;
+
+
+
+final class WebApplicationStartFailureRetryController {
+
+ private final Object monitor = new Object();
+
+ private final ConcurrentMap<String, Set<StandardWebApplication>> failures = new ConcurrentHashMap<String, Set<StandardWebApplication>>();
+
+ void recordFailure(StandardWebApplication failedWebApplication) {
+ String contextPath = failedWebApplication.getContextPath();
+ if (contextPath != null) {
+ addFailureForWebContextPath(contextPath, failedWebApplication);
+ }
+ }
+
+ private void addFailureForWebContextPath(String contextPath, StandardWebApplication failedWebApplication) {
+ Set<StandardWebApplication> contextFailures = this.failures.get(contextPath);
+ if (contextFailures == null) {
+ contextFailures = new HashSet<StandardWebApplication>();
+ Set<StandardWebApplication> previousContextFailures = this.failures.putIfAbsent(contextPath, contextFailures);
+ if (previousContextFailures != null) {
+ contextFailures = previousContextFailures;
+ }
+ }
+ synchronized (this.monitor) {
+ contextFailures.add(failedWebApplication);
+ }
+ }
+
+ void retryFailures(StandardWebApplication stoppedWebApplication) {
+ String contextPath = stoppedWebApplication.getContextPath();
+ if (contextPath != null) {
+ Set<StandardWebApplication> contextFailures = removeFailuresForWebContextPath(contextPath);
+ contextFailures.remove(stoppedWebApplication);
+ for (WebApplication failedWebApplication : contextFailures) {
+ try {
+ failedWebApplication.start();
+ } catch (WebApplicationStartFailedException _) {
+ // ignore as the web application will have been added to the new contextFailures set
+ }
+ }
+ }
+ }
+
+ private Set<StandardWebApplication> removeFailuresForWebContextPath(String contextPath) {
+ Set<StandardWebApplication> sortedContextFailures = createSetSortedByBundleId();
+ Set<StandardWebApplication> contextFailures = this.failures.remove(contextPath);
+ if (contextFailures != null) {
+ sortedContextFailures.addAll(contextFailures);
+ }
+ return sortedContextFailures;
+
+ }
+
+ private Set<StandardWebApplication> createSetSortedByBundleId() {
+ return new TreeSet<StandardWebApplication>(new Comparator<StandardWebApplication>() {
+
+ public int compare(StandardWebApplication wa1, StandardWebApplication wa2) {
+ long id1 = wa1.getBundle().getBundleId();
+ long id2 = wa2.getBundle().getBundleId();
+ if (id1 < id2) {
+ return -1;
+ } else if (id1 > id2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ });
+ }
+
+ void clear() {
+ this.failures.clear();
+ }
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebContainerActivator.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebContainerActivator.java
new file mode 100644
index 0000000..f53e524
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebContainerActivator.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.url.URLConstants;
+import org.osgi.service.url.URLStreamHandlerService;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+
+import org.eclipse.gemini.web.core.WebBundleManifestTransformer;
+import org.eclipse.gemini.web.core.WebContainer;
+import org.eclipse.gemini.web.core.spi.ServletContainer;
+import org.eclipse.gemini.web.internal.url.ChainingWebBundleManifestTransformer;
+import org.eclipse.gemini.web.internal.url.DefaultsWebBundleManifestTransformer;
+import org.eclipse.gemini.web.internal.url.SpecificationWebBundleManifestTransformer;
+import org.eclipse.gemini.web.internal.url.SystemBundleExportsImportingWebBundleManifestTransformer;
+import org.eclipse.gemini.web.internal.url.WebBundleUrl;
+import org.eclipse.gemini.web.internal.url.WebBundleUrlStreamHandlerService;
+import org.eclipse.virgo.util.osgi.ServiceRegistrationTracker;
+import org.eclipse.virgo.util.osgi.VersionRange;
+
+public class WebContainerActivator implements BundleActivator {
+
+ private final ServiceRegistrationTracker regTracker = new ServiceRegistrationTracker();
+
+ private volatile EventManager eventManager;
+
+ private ServiceTracker serviceTracker;
+
+ public void start(BundleContext context) throws Exception {
+ WebBundleManifestTransformer transformer = registerWebBundleManifestTransformer(context);
+
+ registerUrlStreamHandler(context, transformer);
+
+ this.eventManager = new EventManager(context);
+ this.eventManager.start();
+
+ this.serviceTracker = new ServiceTracker(context, ServletContainer.class.getName(), new ServletContainerTracker(context, this.eventManager));
+ this.serviceTracker.open();
+ }
+
+ public void stop(BundleContext context) throws Exception {
+ this.serviceTracker.close();
+ this.regTracker.unregisterAll();
+ this.eventManager.stop();
+ }
+
+ private WebBundleManifestTransformer registerWebBundleManifestTransformer(BundleContext context) {
+
+ WebBundleManifestTransformer specTransformer = new SpecificationWebBundleManifestTransformer();
+ WebBundleManifestTransformer defaultsTransformer = new DefaultsWebBundleManifestTransformer();
+ Map<String, VersionRange> systemBundleExports = new SystemBundleExportsResolver(context).getSystemBundleExports();
+ WebBundleManifestTransformer systemBundleExportImportingWebBundleManifestTransformer = new SystemBundleExportsImportingWebBundleManifestTransformer(systemBundleExports);
+
+ WebBundleManifestTransformer chainingTransformer = new ChainingWebBundleManifestTransformer(specTransformer, defaultsTransformer, systemBundleExportImportingWebBundleManifestTransformer);
+
+ ServiceRegistration reg = context.registerService(WebBundleManifestTransformer.class.getName(), chainingTransformer, null);
+ this.regTracker.track(reg);
+
+ return chainingTransformer;
+ }
+
+
+ private void registerUrlStreamHandler(BundleContext context, WebBundleManifestTransformer transformer) {
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(URLConstants.URL_HANDLER_PROTOCOL, WebBundleUrl.SCHEME);
+
+ ServiceRegistration reg = context.registerService(URLStreamHandlerService.class.getName(), new WebBundleUrlStreamHandlerService(transformer), props);
+ this.regTracker.track(reg);
+ }
+
+ private static final class ServletContainerTracker implements ServiceTrackerCustomizer {
+
+ private final ServiceRegistrationTracker regTracker = new ServiceRegistrationTracker();
+
+ private final BundleContext context;
+
+ private final EventManager eventManager;
+
+ public ServletContainerTracker(BundleContext context, EventManager eventManager) {
+ this.context = context;
+ this.eventManager = eventManager;
+ }
+
+ public Object addingService(ServiceReference reference) {
+ ServletContainer container = (ServletContainer) this.context.getService(reference);
+
+ WebContainer webContainer = new StandardWebContainer(container, this.eventManager);
+
+ ServiceRegistration reg = context.registerService(WebContainer.class.getName(), webContainer, null);
+ this.regTracker.track(reg);
+
+ return webContainer;
+ }
+
+ public void modifiedService(ServiceReference reference, Object service) {
+ }
+
+ public void removedService(ServiceReference reference, Object service) {
+ this.regTracker.unregisterAll();
+ if (service instanceof WebContainer) {
+ ((WebContainer)service).halt();
+ }
+ }
+
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebContainerUtils.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebContainerUtils.java
new file mode 100644
index 0000000..f114467
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/WebContainerUtils.java
@@ -0,0 +1,207 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal;
+
+import java.net.URL;
+import java.util.Locale;
+import java.util.Properties;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
+
+public final class WebContainerUtils {
+
+ public static final String WEB_BUNDLE_SCHEME = "webbundle";
+
+ /**
+ * Constant for the <code>Web-ContextPath</code> manifest header.
+ */
+ public static final String HEADER_WEB_CONTEXT_PATH = "Web-ContextPath";
+
+ /**
+ * Constant for the <code>SpringSource-SystemPackages</code> manifest header.
+ */
+ public static final String HEADER_SPRINGSOURCE_DEFAULT_WAB_HEADERS = "SpringSource-DefaultWABHeaders";
+
+ /**
+ * Constant for the <code>Web-JSPExtractLocation</code> manifest header.
+ */
+ public static final String HEADER_WEB_JSP_EXTRACT_LOCATION = "Web-JSPExtractLocation";
+
+ static final String ENTRY_WEB_XML = "/WEB-INF/web.xml";
+
+ static final String WAR_EXTENSION = ".war";
+
+ static final String OSGI_WEB_VERSION = "osgi.web.version";
+
+ static final String OSGI_WEB_SYMBOLICNAME = "osgi.web.symbolicname";
+
+ static final String BUNDLE_VERSION_HEADER = "bundle-version";
+
+ private WebContainerUtils() {
+ }
+
+ public static boolean isWebBundle(Bundle bundle) {
+ return hasWarExtension(bundle) || hasWarScheme(bundle) || hasWebContextPath(bundle) || hasWebXml(bundle);
+ }
+
+ private static boolean hasWarExtension(Bundle bundle) {
+ String lowerCaseLocation = bundle.getLocation().toLowerCase(Locale.ENGLISH);
+ while (lowerCaseLocation.endsWith("/")) {
+ lowerCaseLocation = lowerCaseLocation.substring(0, lowerCaseLocation.length() - 1);
+ }
+ return lowerCaseLocation.endsWith(WAR_EXTENSION);
+ }
+
+ private static boolean hasWarScheme(Bundle bundle) {
+ return bundle.getLocation().startsWith(WEB_BUNDLE_SCHEME);
+ }
+
+ private static boolean hasWebContextPath(Bundle bundle) {
+ return getWebContextPathHeader(bundle) != null;
+ }
+
+ private static String getWebContextPathHeader(Bundle bundle) {
+ return (String) bundle.getHeaders().get(HEADER_WEB_CONTEXT_PATH);
+ }
+
+ private static boolean hasWebXml(Bundle bundle) {
+ return bundle.getEntry(ENTRY_WEB_XML) != null;
+ }
+
+ public static String getContextPath(Bundle bundle) {
+ String contextPath = getWebContextPathHeader(bundle);
+
+ if (contextPath == null) {
+ contextPath = getBaseName(bundle.getLocation());
+ }
+
+ return contextPath;
+ }
+
+ public static String createDefaultBundleSymbolicName(URL source) {
+ return getBaseName(source.getPath());
+ }
+
+ static String getBaseName(String path) {
+ String base = path;
+ base = unifySeparators(base);
+ if (base.endsWith("/")) {
+ base = base.substring(0, base.length() - 1);
+ }
+
+ base = stripQuery(base);
+ base = stripSchemeAndDrive(base);
+ base = stripLeadingPathElements(base);
+ base = stripExtension(base);
+ return base;
+ }
+
+ private static String unifySeparators(String base) {
+ return base.replaceAll("\\\\", "/");
+ }
+
+ private static String stripExtension(String base) {
+ int index;
+ index = base.lastIndexOf(".");
+ if (index > -1) {
+ base = base.substring(0, index);
+ }
+ return base;
+ }
+
+ private static String stripLeadingPathElements(String base) {
+ int index = base.lastIndexOf("/");
+ if (index > -1) {
+ base = base.substring(index + 1);
+ }
+ return base;
+ }
+
+ private static String stripQuery(String path) {
+ String result = path;
+ int index = result.lastIndexOf("?");
+ if (index > -1) {
+ result = result.substring(0, index);
+ }
+ return result;
+ }
+
+ private static String stripSchemeAndDrive(String path) {
+ String result = path;
+ int index = result.indexOf(":");
+ while (index > -1 && index < result.length()) {
+ result = result.substring(index + 1);
+ index = result.indexOf(":");
+ }
+ return result;
+ }
+
+ public static void setServletContextBundleProperties(Properties properties, Bundle bundle) {
+ setServletContextOsgiWebSymbolicNameProperty(properties, bundle);
+ setServletContextOsgiWebVersionProperty(properties, bundle);
+ }
+
+ private static void setServletContextOsgiWebVersionProperty(Properties properties, Bundle bundle) {
+ if (bundle.getHeaders().get(BUNDLE_VERSION_HEADER) != null) {
+ properties.setProperty(OSGI_WEB_VERSION, bundle.getVersion().toString());
+ }
+ }
+
+ private static void setServletContextOsgiWebSymbolicNameProperty(Properties properties, Bundle bundle) {
+ String symbolicName = bundle.getSymbolicName();
+ if (symbolicName != null) {
+ properties.setProperty(OSGI_WEB_SYMBOLICNAME, symbolicName);
+ }
+ }
+
+ /**
+ * Determines whether the given manifest represents a web application bundle. According to the R4.2 Enterprise
+ * Specification, this is true if and only if the manifest contains any of the headers in Table 128.3:
+ * Bundle-SymbolicName, Bundle-Version, Bundle-ManifestVersion, Import-Package, Web-ContextPath. Note: there is no
+ * need to validate the manifest as if it is invalid it will cause an error later.
+ *
+ * @param manifest the bundle manifest
+ * @return <code>true</code> if and only if the given manifest represents a web application bundle
+ */
+ public static boolean isWebApplicationBundle(BundleManifest manifest) {
+ return specifiesBundleSymbolicName(manifest) || specifiesBundleVersion(manifest) || specifiesBundleManifestVersion(manifest)
+ || specifiesImportPackage(manifest) || specifiesWebContextPath(manifest);
+ }
+
+ private static boolean specifiesBundleSymbolicName(BundleManifest manifest) {
+ return manifest.getBundleSymbolicName().getSymbolicName() != null;
+ }
+
+ private static boolean specifiesBundleVersion(BundleManifest manifest) {
+ return manifest.getHeader(Constants.BUNDLE_VERSION) != null;
+ }
+
+ private static boolean specifiesBundleManifestVersion(BundleManifest manifest) {
+ return manifest.getBundleManifestVersion() != 1;
+ }
+
+ private static boolean specifiesImportPackage(BundleManifest manifest) {
+ return !manifest.getImportPackage().getImportedPackages().isEmpty();
+ }
+
+ private static boolean specifiesWebContextPath(BundleManifest manifest) {
+ return manifest.getHeader(WebContainerUtils.HEADER_WEB_CONTEXT_PATH) != null;
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceCallback.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceCallback.java
new file mode 100644
index 0000000..97ff729
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceCallback.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.template;
+
+public interface ServiceCallback<S, T> {
+ T doWithService(S service);
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceTemplate.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceTemplate.java
new file mode 100644
index 0000000..831ae29
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceTemplate.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.template;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+
+public class ServiceTemplate<S> {
+
+ private final ServiceTracker tracker;
+
+ public ServiceTemplate(BundleContext context, Class<S> clazz) {
+ this.tracker = new ServiceTracker(context, clazz.getName(), new ServiceTemplateCustomizer(context));
+ }
+
+ public void start() {
+ this.tracker.open();
+ }
+
+ public void stop() {
+ this.tracker.close();
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T executeWithService(ServiceCallback<S, T> callback) {
+ Object service = this.tracker.getService();
+ if(service != null) {
+ return callback.doWithService((S)service);
+ }
+ return null;
+ }
+
+ private static final class ServiceTemplateCustomizer implements ServiceTrackerCustomizer {
+
+ private final BundleContext context;
+
+ public ServiceTemplateCustomizer(BundleContext context) {
+ this.context = context;
+ }
+
+ public Object addingService(ServiceReference reference) {
+ return this.context.getService(reference);
+ }
+
+ public void modifiedService(ServiceReference reference, Object service) {
+ }
+
+ public void removedService(ServiceReference reference, Object service) {
+ this.context.ungetService(reference);
+ }
+
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceUnavailableException.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceUnavailableException.java
new file mode 100644
index 0000000..394c7b9
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/template/ServiceUnavailableException.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.template;
+
+public class ServiceUnavailableException extends RuntimeException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -4800943191504596869L;
+
+ public ServiceUnavailableException() {
+ super();
+ }
+
+ public ServiceUnavailableException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ServiceUnavailableException(String message) {
+ super(message);
+ }
+
+ public ServiceUnavailableException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/ChainingWebBundleManifestTransformer.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/ChainingWebBundleManifestTransformer.java
new file mode 100644
index 0000000..6d0bc9b
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/ChainingWebBundleManifestTransformer.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.url;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+
+import org.eclipse.gemini.web.core.InstallationOptions;
+import org.eclipse.gemini.web.core.WebBundleManifestTransformer;
+import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
+
+
+public class ChainingWebBundleManifestTransformer implements WebBundleManifestTransformer {
+
+ private final WebBundleManifestTransformer[] manifestTransformers;
+
+ public ChainingWebBundleManifestTransformer(WebBundleManifestTransformer... transformers) {
+ this.manifestTransformers = transformers;
+ }
+
+ public void transform(BundleManifest manifest, URL sourceURL, InstallationOptions options, boolean webBundle) throws IOException {
+ if (options == null) {
+ options = new InstallationOptions(Collections.<String, String> emptyMap());
+ }
+ for (WebBundleManifestTransformer manifestTransformer : this.manifestTransformers) {
+ manifestTransformer.transform(manifest, sourceURL, options, webBundle);
+ }
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/DefaultsWebBundleManifestTransformer.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/DefaultsWebBundleManifestTransformer.java
new file mode 100644
index 0000000..edf06ac
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/DefaultsWebBundleManifestTransformer.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.url;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+
+import org.eclipse.gemini.web.core.InstallationOptions;
+import org.eclipse.gemini.web.core.WebBundleManifestTransformer;
+import org.eclipse.gemini.web.internal.WebContainerUtils;
+import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
+import org.eclipse.virgo.util.osgi.manifest.BundleSymbolicName;
+import org.eclipse.virgo.util.osgi.manifest.ImportedPackage;
+
+public final class DefaultsWebBundleManifestTransformer implements WebBundleManifestTransformer {
+
+ private static final int MINIMUM_BUNDLE_MANIFEST_VERSION = 2;
+
+ private static final String WEB_INF_CLASSES = "WEB-INF/classes";
+
+ public void transform(BundleManifest manifest, URL sourceURL, InstallationOptions options, boolean webBundle) throws IOException {
+ if (!webBundle || options.getDefaultWABHeaders()) {
+ applyDefaultBundleSymbolicName(sourceURL, manifest);
+ applyDefaultBundleManifestVersion(manifest);
+ applyBundleClassPath(sourceURL, manifest);
+ applyImportPackage(manifest);
+ }
+ }
+
+ private void applyImportPackage(BundleManifest manifest) {
+ addImportInNecessary("javax.servlet", new Version("2.5"), manifest);
+ addImportInNecessary("javax.servlet.http", new Version("2.5"), manifest);
+ addImportInNecessary("javax.servlet.jsp", new Version("2.1"), manifest);
+ addImportInNecessary("javax.servlet.jsp.el", new Version("2.1"), manifest);
+ addImportInNecessary("javax.servlet.jsp.tagext", new Version("2.1"), manifest);
+ addImportInNecessary("javax.el", new Version("1.0"), manifest);
+ }
+
+ private void addImportInNecessary(String packageName, Version version, BundleManifest manifest) {
+ List<ImportedPackage> pkgs = manifest.getImportPackage().getImportedPackages();
+ for (ImportedPackage pkg : pkgs) {
+ if (pkg.getPackageName().equals(packageName)) {
+ return;
+ }
+ }
+ ImportedPackage packageImport = manifest.getImportPackage().addImportedPackage(packageName);
+ packageImport.getAttributes().put(Constants.VERSION_ATTRIBUTE, version.toString());
+ }
+
+ private void applyBundleClassPath(URL source, BundleManifest manifest) throws IOException {
+ List<String> bundleClassPath = manifest.getBundleClasspath();
+
+ if (!bundleClassPath.contains(WEB_INF_CLASSES)) {
+ bundleClassPath.add(0, WEB_INF_CLASSES);
+ }
+
+ final List<String> entries = new ArrayList<String>();
+ WebBundleScanner scanner = new WebBundleScanner(source, new WebBundleScannerCallback() {
+
+ public void classFound(String entry) {
+ }
+
+ public void jarFound(String entry) {
+ entries.add(entry);
+ }
+ });
+ scanner.scanWar();
+
+ for (String entry : entries) {
+ if (!bundleClassPath.contains(entry)) {
+ bundleClassPath.add(entry);
+ }
+ }
+ }
+
+ private void applyDefaultBundleManifestVersion(BundleManifest manifest) {
+ if (manifest.getBundleManifestVersion() < MINIMUM_BUNDLE_MANIFEST_VERSION) {
+ manifest.setBundleManifestVersion(MINIMUM_BUNDLE_MANIFEST_VERSION);
+ }
+ }
+
+ private void applyDefaultBundleSymbolicName(URL source, BundleManifest manifest) {
+ BundleSymbolicName bsn = manifest.getBundleSymbolicName();
+ if (bsn.getSymbolicName() == null) {
+ bsn.setSymbolicName(WebContainerUtils.createDefaultBundleSymbolicName(source));
+ }
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/PackageMergeUtils.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/PackageMergeUtils.java
new file mode 100644
index 0000000..6093150
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/PackageMergeUtils.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.url;
+
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
+import org.eclipse.virgo.util.osgi.manifest.ExportedPackage;
+import org.eclipse.virgo.util.osgi.manifest.ImportedPackage;
+
+public class PackageMergeUtils {
+
+ public static void mergeImportPackage(BundleManifest manifest, String name, Map<String, String> attributes, Map<String, String> directives) {
+ ImportedPackage packageImport = findImportedPackage(manifest, name);
+
+ if (packageImport == null) {
+ packageImport = manifest.getImportPackage().addImportedPackage(name);
+ }
+
+ packageImport.getAttributes().clear();
+ packageImport.getAttributes().putAll(attributes);
+
+ packageImport.getDirectives().clear();
+ packageImport.getDirectives().putAll(directives);
+ }
+
+ public static void mergeExportPackage(BundleManifest manifest, String name, Map<String, String> attributes, Map<String, String> directives) {
+ String versionAttribute = attributes.get(Constants.VERSION_ATTRIBUTE);
+ Version version = (versionAttribute == null ? Version.emptyVersion : new Version(versionAttribute));
+
+ ExportedPackage packageExport = findExportedPackage(manifest, name, version);
+
+ if (packageExport == null) {
+ packageExport = manifest.getExportPackage().addExportedPackage(name);
+ }
+
+ packageExport.getAttributes().clear();
+ packageExport.getAttributes().putAll(attributes);
+
+ packageExport.getDirectives().clear();
+ packageExport.getDirectives().putAll(directives);
+ }
+
+ private static final ExportedPackage findExportedPackage(BundleManifest manifest, String packageName, Version version) {
+ List<ExportedPackage> exportedPackages = manifest.getExportPackage().getExportedPackages();
+ for (ExportedPackage exportedPackage : exportedPackages) {
+ if (packageName.equals(exportedPackage.getPackageName()) && version.equals(exportedPackage.getVersion())) {
+ return exportedPackage;
+ }
+ }
+ return null;
+ }
+
+ static final ImportedPackage findImportedPackage(BundleManifest manifest, String packageName) {
+ List<ImportedPackage> importedPackages = manifest.getImportPackage().getImportedPackages();
+ for (ImportedPackage importedPackage : importedPackages) {
+ if (packageName.equals(importedPackage.getPackageName())) {
+ return importedPackage;
+ }
+ }
+ return null;
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/PackagesInWarScanner.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/PackagesInWarScanner.java
new file mode 100644
index 0000000..9bcd17a
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/PackagesInWarScanner.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.url;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+
+class PackagesInWarScanner {
+
+ Set<String> getPackagesContainedInWar(URL warURL) throws IOException {
+ final Set<String> packagesInWar = new HashSet<String>();
+ if (warURL != null) {
+ WebBundleScanner scanner = new WebBundleScanner(warURL, new WebBundleScannerCallback() {
+
+ public void classFound(String entry) {
+ int lastSlashIndex = entry.lastIndexOf('/');
+ if (lastSlashIndex >= 0) {
+ packagesInWar.add(entry.substring(0, lastSlashIndex).replace('/', '.'));
+ }
+ }
+
+ public void jarFound(String entry) {
+ }
+ }, true);
+ scanner.scanWar();
+ }
+
+ return packagesInWar;
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/Path.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/Path.java
new file mode 100644
index 0000000..32f753c
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/Path.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright SpringSource Inc 2010
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eclipse.gemini.web.internal.url;
+
+import java.util.Arrays;
+
+final class Path {
+
+ private static final String BACK_SLASH = "\\";
+
+ private static final String PATH_SEPARATOR = "/";
+
+ private static final String DOUBLE_PATH_SEPARATOR = PATH_SEPARATOR + PATH_SEPARATOR;
+
+ private static final String PATH_HERE = ".";
+
+ private static final String PATH_UP = "..";
+
+ private final String[] baseComponents;
+
+ Path(String basePath) {
+ validatePath(basePath);
+ String[] comps = basePath.split(PATH_SEPARATOR);
+ while (comps.length > 0 && PATH_HERE.equals(comps[0])) {
+ String[] c2 = new String[comps.length - 1];
+ System.arraycopy(comps, 1, c2, 0, c2.length);
+ comps = c2;
+ }
+ this.baseComponents = comps;
+ }
+
+ private Path(String[] comps) {
+ this.baseComponents = comps;
+ }
+
+ private String head() {
+ if (!isEmpty()) {
+ return baseComponents[0];
+ } else {
+ throw new IllegalStateException("head not applicable to an empty path");
+ }
+ }
+
+ private Path tail() {
+ if (!isEmpty()) {
+ String[] c = new String[this.baseComponents.length - 1];
+ System.arraycopy(this.baseComponents, 1, c, 0, c.length);
+ return new Path(c);
+ } else {
+ throw new IllegalStateException("tail not applicable to an empty path");
+ }
+ }
+
+ private Path front() {
+ if (!isEmpty()) {
+ String[] c = new String[this.baseComponents.length - 1];
+ System.arraycopy(this.baseComponents, 0, c, 0, c.length);
+ return new Path(c);
+ } else {
+ throw new IllegalStateException("front not applicable to an empty path");
+ }
+ }
+
+ private static void validatePath(String basePath) {
+ if (basePath == null) {
+ throw new IllegalArgumentException("path must not be null");
+ }
+ if (basePath.contains(BACK_SLASH)) {
+ throw new IllegalArgumentException("path must not contain '" + BACK_SLASH + "'");
+ }
+ if (basePath.endsWith(PATH_SEPARATOR)) {
+ throw new IllegalArgumentException("path must not end in '" + PATH_SEPARATOR + "'");
+ }
+ if (basePath.contains(DOUBLE_PATH_SEPARATOR)) {
+ throw new IllegalArgumentException("path must not contain '" + DOUBLE_PATH_SEPARATOR + "'");
+ }
+ }
+
+ public Path() {
+ this.baseComponents = new String[0];
+ }
+
+ public Path applyRelativePath(Path relativePath) {
+ try {
+ Path b = this;
+ Path r = relativePath;
+ while (r.isUp()) {
+ r = r.tail();
+ b = b.front();
+ }
+ return b.append(r);
+ } catch (IllegalStateException s) {
+ throw new IllegalArgumentException("relative path cannot be applied", s);
+ }
+ }
+
+ private Path append(Path r) {
+ if (isEmpty()) {
+ return r;
+ } else if (r.isEmpty()) {
+ return this;
+ }
+ return new Path(toString() + PATH_SEPARATOR + r.toString());
+ }
+
+ private boolean isUp() {
+ return !isEmpty() && PATH_UP.equals(head());
+ }
+
+ private boolean isEmpty() {
+ return this.baseComponents.length == 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer s = new StringBuffer();
+ boolean first = true;
+ for (String c : baseComponents) {
+ if (!first) {
+ s.append(PATH_SEPARATOR);
+ }
+ first = false;
+ s.append(c);
+ }
+
+ return s.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(baseComponents);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Path other = (Path) obj;
+ if (!Arrays.equals(baseComponents, other.baseComponents))
+ return false;
+ return true;
+ }
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/SpecificationWebBundleManifestTransformer.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/SpecificationWebBundleManifestTransformer.java
new file mode 100644
index 0000000..e0729fc
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/SpecificationWebBundleManifestTransformer.java
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.url;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.List;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+
+import org.eclipse.gemini.web.core.InstallationOptions;
+import org.eclipse.gemini.web.core.WebBundleManifestTransformer;
+import org.eclipse.gemini.web.internal.WebContainerUtils;
+import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
+import org.eclipse.virgo.util.osgi.manifest.parse.HeaderDeclaration;
+import org.eclipse.virgo.util.osgi.manifest.parse.HeaderParser;
+import org.eclipse.virgo.util.osgi.manifest.parse.HeaderParserFactory;
+import org.eclipse.virgo.util.osgi.manifest.parse.ParserLogger;
+
+/**
+ * Applies user installation options onto a {@link BundleManifest}.
+ *
+ *
+ */
+public final class SpecificationWebBundleManifestTransformer implements WebBundleManifestTransformer {
+
+ private static final int MINIMUM_VALID_BUNDLE_MANIFEST_VERSION = 2;
+
+ public void transform(BundleManifest manifest, URL sourceURL, InstallationOptions options, boolean webBundle) throws IOException {
+ if (options == null) {
+ options = new InstallationOptions(Collections.<String, String> emptyMap());
+ }
+
+ transformBundleSymbolicName(manifest, options, webBundle);
+ transformBundleVersion(manifest, options, webBundle);
+ transformBundleManifestVersion(manifest, options, webBundle);
+ transformBundleClassPath(manifest, options, webBundle);
+ transformImportPackage(manifest, options, webBundle);
+ transformExportPackage(manifest, options, webBundle);
+ transformWebContextPath(manifest, options, webBundle);
+ }
+
+ private void transformExportPackage(BundleManifest manifest, InstallationOptions options, boolean isWebApplicationBundle) {
+ String epd = options.getExportPackageDeclaration();
+ if (epd != null) {
+ if (isWebApplicationBundle) {
+ throw new IllegalArgumentException("Export-Package URL parameter cannot modify a Web Application Bundle");
+ }
+ HeaderParser parser = HeaderParserFactory.newHeaderParser(new TransformerParserLogger());
+ List<HeaderDeclaration> packageHeader = parser.parsePackageHeader(epd, Constants.EXPORT_PACKAGE);
+ for (HeaderDeclaration headerDeclaration : packageHeader) {
+ for (String name : headerDeclaration.getNames()) {
+ PackageMergeUtils.mergeExportPackage(manifest, name, headerDeclaration.getAttributes(), headerDeclaration.getDirectives());
+ }
+ }
+ }
+ }
+
+ private void transformImportPackage(BundleManifest manifest, InstallationOptions options, boolean isWebApplicationBundle) {
+ String ipd = options.getImportPackageDeclaration();
+ if (ipd != null) {
+ if (isWebApplicationBundle) {
+ throw new IllegalArgumentException("Import-Package URL parameter cannot modify a Web Application Bundle");
+ }
+ HeaderParser parser = HeaderParserFactory.newHeaderParser(new TransformerParserLogger());
+ List<HeaderDeclaration> packageHeader = parser.parsePackageHeader(ipd, Constants.IMPORT_PACKAGE);
+ for (HeaderDeclaration headerDeclaration : packageHeader) {
+ for (String name : headerDeclaration.getNames()) {
+ PackageMergeUtils.mergeImportPackage(manifest, name, headerDeclaration.getAttributes(), headerDeclaration.getDirectives());
+ }
+ }
+ }
+ }
+
+ private void transformBundleSymbolicName(BundleManifest manifest, InstallationOptions options, boolean isWebApplicationBundle) {
+ if (options.getBundleSymbolicName() != null) {
+ if (isWebApplicationBundle) {
+ throw new IllegalArgumentException("Bundle-SymbolicName URL parameter cannot modify a Web Application Bundle");
+ }
+ manifest.getBundleSymbolicName().setSymbolicName(options.getBundleSymbolicName());
+ }
+ }
+
+ private void transformBundleManifestVersion(BundleManifest manifest, InstallationOptions options, boolean isWebApplicationBundle) {
+ if (options.getBundleManifestVersion() != null) {
+ if (isWebApplicationBundle) {
+ throw new IllegalArgumentException("Bundle-ManifestVersion URL parameter cannot modify a Web Application Bundle");
+ }
+ manifest.setBundleManifestVersion(parseBundleManifestVersion(options.getBundleManifestVersion()));
+ }
+ }
+
+ private int parseBundleManifestVersion(String bundleManifestVersion) {
+ int result = MINIMUM_VALID_BUNDLE_MANIFEST_VERSION;
+ if (bundleManifestVersion != null) {
+ try {
+ result = Integer.parseInt(bundleManifestVersion);
+ if (result < MINIMUM_VALID_BUNDLE_MANIFEST_VERSION) {
+ throw new IllegalArgumentException(Constants.BUNDLE_MANIFESTVERSION + " " + result + " is less than the smallest valid value of "
+ + MINIMUM_VALID_BUNDLE_MANIFEST_VERSION);
+ }
+ } catch (NumberFormatException ex) {
+ throw new IllegalArgumentException(Constants.BUNDLE_MANIFESTVERSION + " is not a valid integer.", ex);
+ }
+ }
+ return result;
+ }
+
+ private void transformBundleVersion(BundleManifest manifest, InstallationOptions options, boolean isWebApplicationBundle) {
+ if (options.getBundleVersion() != null) {
+ if (isWebApplicationBundle) {
+ throw new IllegalArgumentException("Bundle-Version URL parameter cannot modify a Web Application Bundle");
+ }
+ manifest.setBundleVersion(new Version(options.getBundleVersion()));
+ }
+ }
+
+ private void transformBundleClassPath(BundleManifest manifest, InstallationOptions options, boolean isWebApplicationBundle) {
+ List<String> bundleClassPath = manifest.getBundleClasspath();
+ String bundleClassPathOption = options.getBundleClassPath();
+ if (bundleClassPathOption != null) {
+ if (isWebApplicationBundle) {
+ throw new IllegalArgumentException("Bundle-ClassPath URL parameter cannot modify a Web Application Bundle");
+ }
+ for (String entry : parseBundleClassPath(bundleClassPathOption)) {
+ if (!bundleClassPath.contains(entry)) {
+ bundleClassPath.add(entry);
+ }
+ }
+ }
+ }
+
+ private static String[] parseBundleClassPath(String bundleClassPath) {
+ String[] bundleClassPathEntries = bundleClassPath.split(",");
+ minimallyValidateBundleClassPathEntries(bundleClassPathEntries, bundleClassPath);
+ return bundleClassPathEntries;
+ }
+
+ /**
+ * Validates the given bundle class path entries.
+ * <p>
+ * Trailing slashes are tolerated and removed so the resultant class path entries are more likely to conform
+ * strictly to the OSGi specification.
+ */
+ private static void minimallyValidateBundleClassPathEntries(String[] bundleClassPathEntries, String bundleClassPath) {
+ for (int i = 0; i < bundleClassPathEntries.length; i++) {
+ String entry = bundleClassPathEntries[i];
+ if (entry.length() == 0) {
+ diagnoseInvalidEntry(entry, bundleClassPath);
+ }
+ if (entry.endsWith("/")) {
+ if (entry.length() == 1) {
+ diagnoseInvalidEntry(entry, bundleClassPath);
+ }
+ bundleClassPathEntries[i] = entry.substring(0, entry.length() - 1);
+ }
+ }
+ }
+
+ private static void diagnoseInvalidEntry(String entry, String bundleClassPath) {
+ throw new IllegalArgumentException(Constants.BUNDLE_CLASSPATH + "'" + bundleClassPath + "' contains an invalid entry '" + entry + "'");
+ }
+
+ /**
+ * @param isWebApplicationBundle
+ */
+ private void transformWebContextPath(BundleManifest manifest, InstallationOptions options, boolean isWebApplicationBundle) {
+ String webContextPathOption = options.getWebContextPath();
+ if (webContextPathOption != null) {
+ manifest.setHeader(WebContainerUtils.HEADER_WEB_CONTEXT_PATH, validateWebContextPath(webContextPathOption));
+ } else if (!options.getDefaultWABHeaders()) {
+ String webContextPathHeader = manifest.getHeader(WebContainerUtils.HEADER_WEB_CONTEXT_PATH);
+ if (webContextPathHeader == null || webContextPathHeader.trim().length() == 0) {
+ throw new IllegalArgumentException(WebContainerUtils.HEADER_WEB_CONTEXT_PATH + " is missing");
+ }
+ }
+ }
+
+ private String validateWebContextPath(String webContextPathOption) {
+ String trimmedWebContextPathOption = webContextPathOption.trim();
+ if (trimmedWebContextPathOption.length() == 0) {
+ throw new IllegalArgumentException(WebContainerUtils.HEADER_WEB_CONTEXT_PATH + " URL parameter value is missing");
+ }
+ if (trimmedWebContextPathOption.startsWith("/")) {
+ return trimmedWebContextPathOption;
+ } else {
+ return "/" + trimmedWebContextPathOption;
+ }
+ }
+
+ private static class TransformerParserLogger implements ParserLogger {
+
+ public String[] errorReports() {
+ return null;
+ }
+
+ public void outputErrorMsg(Exception re, String item) {
+
+ }
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/SystemBundleExportsImportingWebBundleManifestTransformer.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/SystemBundleExportsImportingWebBundleManifestTransformer.java
new file mode 100644
index 0000000..6a56bfb
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/SystemBundleExportsImportingWebBundleManifestTransformer.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.url;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import org.eclipse.gemini.web.core.InstallationOptions;
+import org.eclipse.gemini.web.core.WebBundleManifestTransformer;
+import org.eclipse.virgo.util.osgi.VersionRange;
+import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
+
+
+public final class SystemBundleExportsImportingWebBundleManifestTransformer implements WebBundleManifestTransformer {
+
+ private final Map<String, VersionRange> systemBundleExports;
+
+ private final PackagesInWarScanner warPackagesScanner = new PackagesInWarScanner();
+
+ public SystemBundleExportsImportingWebBundleManifestTransformer(Map<String, VersionRange> systemBundleExports) {
+ this.systemBundleExports = systemBundleExports;
+ }
+
+ public void transform(BundleManifest manifest, URL sourceURL, InstallationOptions options, boolean webBundle) throws IOException {
+ if (!webBundle || options.getDefaultWABHeaders()) {
+ addImportsForSystemBundleExports(manifest, this.warPackagesScanner.getPackagesContainedInWar(sourceURL));
+ }
+ }
+
+ protected void addImportsForSystemBundleExports(BundleManifest bundleManifest, Set<String> packagesInWar) {
+ for (Entry<String, VersionRange> exportedPackage : this.systemBundleExports.entrySet()) {
+ String packageName = exportedPackage.getKey();
+ if (!packagesInWar.contains(packageName) && PackageMergeUtils.findImportedPackage(bundleManifest, packageName) == null) {
+ bundleManifest.getImportPackage().addImportedPackage(packageName).setVersion(exportedPackage.getValue());
+ }
+ }
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleScanner.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleScanner.java
new file mode 100644
index 0000000..bbdb962
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleScanner.java
@@ -0,0 +1,298 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.url;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.eclipse.virgo.util.io.IOUtils;
+
+final class WebBundleScanner {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(WebBundleScanner.class);
+
+ private static final String LIB_DIR_SUFFIX = File.separator + "WEB-INF" + File.separator + "lib";
+
+ private static final String CLASSES_DIR_SUFFIX = File.separator + "WEB-INF" + File.separator + "classes";
+
+ private static final String FILE_SCHEME = "file";
+
+ private static final String JAR_SUFFIX = ".jar";
+
+ private static final String CLASS_SUFFIX = ".class";
+
+ private static final String LIB_ENTRY_PREFIX = "WEB-INF/lib/";
+
+ private static final String CLASSES_ENTRY_PREFIX = "WEB-INF/classes/";
+
+ private static final String CLASS_PATH_ATTRIBUTE_NAME = "Class-Path";
+
+ private static final String CLASS_PATH_SEPARATOR = " ";
+
+ private final Object monitor = new Object();
+
+ private final URL source;
+
+ private final WebBundleScannerCallback callBack;
+
+ private final boolean findClassesInNestedJars;
+
+ private final Set<String> scannedJars = new HashSet<String>();
+
+ WebBundleScanner(URL source, WebBundleScannerCallback callBack) {
+ this(source, callBack, false);
+ }
+
+ /**
+ * Creates a WebBundleScanner for a given WAR with a given callBack.
+ *
+ * @param source the WAR content
+ * @param callBack The callBack to notify of entries in the WAR
+ * @param findClassesInNestedJars
+ */
+ WebBundleScanner(URL source, WebBundleScannerCallback callBack, boolean findClassesInNestedJars) {
+ this.source = source;
+ this.callBack = callBack;
+ this.findClassesInNestedJars = findClassesInNestedJars;
+ }
+
+ /**
+ * Scans the WAR content from <code>source</code>, notifying the callBack of .class entries in WEB-INF/classes and
+ * its sub-directories, and .jar entries in WEB-INF/lib.
+ *
+ * @throws IOException if the WAR cannot be scanned
+ */
+ void scanWar() throws IOException {
+ synchronized (this.monitor) {
+ this.scannedJars.clear();
+ if (isDirectory()) {
+ scanWarDirectory();
+ } else {
+ scanWarFile();
+ }
+ }
+ }
+
+ private void scanWarDirectory() throws IOException {
+ try {
+ File bundleDir = sourceAsFile();
+ File libDir = new File(bundleDir, LIB_DIR_SUFFIX);
+ if (libDir.isDirectory()) {
+ doScanLibDirectory(libDir);
+ }
+ File classesDir = new File(bundleDir, CLASSES_DIR_SUFFIX);
+ if (classesDir.isDirectory()) {
+ doScanClassesDirectory(classesDir);
+ }
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Unexpected URISyntaxException.", e);
+ }
+ }
+
+ private void doScanLibDirectory(File libDir) throws IOException {
+ File[] files = libDir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isFile() && file.getName().endsWith(JAR_SUFFIX)) {
+ String pathToJar = LIB_ENTRY_PREFIX + file.getName();
+ if (driveCallBackIfNewJarFound(pathToJar)) {
+ doScanNestedJar(file);
+ }
+ }
+ }
+ }
+ }
+
+ private boolean driveCallBackIfNewJarFound(String pathToJar) {
+ // Prevent infinite recursion.
+ if (this.scannedJars.contains(pathToJar)) {
+ return false;
+ }
+ this.scannedJars.add(pathToJar);
+ this.callBack.jarFound(pathToJar);
+ return true;
+ }
+
+ private void doScanNestedJar(File file) throws IOException {
+ JarInputStream jis = null;
+
+ try {
+ jis = new JarInputStream(new FileInputStream(file));
+ doScanNestedJar(file.getAbsolutePath(), jis);
+ } finally {
+ if (jis != null) {
+ IOUtils.closeQuietly(jis);
+ }
+ }
+ }
+
+ private void doScanClassesDirectory(File classesDir) {
+ File[] files = classesDir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ doScanClassesDirectory(file);
+ } else if (file.isFile() && file.getName().endsWith(CLASS_SUFFIX)) {
+ String path = normalizePath(file.getPath());
+ this.callBack.classFound(path.substring(path.lastIndexOf(CLASSES_ENTRY_PREFIX) + CLASSES_ENTRY_PREFIX.length()));
+ }
+ }
+ }
+ }
+
+ private void scanWarFile() throws IOException {
+ JarInputStream jis = new JarInputStream(this.source.openStream());
+ try {
+ JarEntry entry;
+ while ((entry = jis.getNextJarEntry()) != null) {
+ String entryName = entry.getName();
+ if (entryName.startsWith(LIB_ENTRY_PREFIX) && entryName.endsWith(JAR_SUFFIX)) {
+ if (driveCallBackIfNewJarFound(entryName)) {
+ JarInputStream nestedJis = new JarInputStream(jis);
+ doScanNestedJar(entryName, nestedJis);
+ }
+ } else if (entryName.startsWith(CLASSES_ENTRY_PREFIX) && entryName.endsWith(CLASS_SUFFIX)) {
+ this.callBack.classFound(entry.getName().substring(CLASSES_ENTRY_PREFIX.length()));
+ }
+ }
+ } finally {
+ IOUtils.closeQuietly(jis);
+ }
+ }
+
+ private void doScanNestedJar(String jarEntryName, JarInputStream jis) throws IOException {
+ Manifest manifest = jis.getManifest();
+ if (manifest != null) {
+ Attributes mainAttributes = manifest.getMainAttributes();
+ if (mainAttributes != null) {
+ String classPath = mainAttributes.getValue(CLASS_PATH_ATTRIBUTE_NAME);
+ if (classPath != null) {
+ Path jarPathx = getNormalisedDirectoryPath(jarEntryName);
+
+ String[] classPathItems = classPath.split(CLASS_PATH_SEPARATOR);
+ for (String classPathItem : classPathItems) {
+ try {
+ Path entryPath = jarPathx.applyRelativePath(new Path(classPathItem));
+ scanNestedJarInWar(entryPath.toString());
+ } catch (IllegalArgumentException _) {
+ // skip invalid relative paths which try to escape the WAR
+ }
+ }
+ }
+ }
+ }
+
+ JarEntry entry;
+ while ((entry = jis.getNextJarEntry()) != null) {
+ String entryName = entry.getName();
+ if (entryName.endsWith(CLASS_SUFFIX)) {
+ notifyClassFound(entryName);
+ }
+ }
+ }
+
+ private static Path getNormalisedDirectoryPath(String jarEntryName) {
+ String jarPath = normalizePath(jarEntryName);
+ int lastDirectoryIndex = jarPath.lastIndexOf("/");
+ return lastDirectoryIndex == -1 ? new Path() : new Path(jarPath.substring(0, lastDirectoryIndex));
+ }
+
+ private void scanNestedJarInWar(String jarPath) throws IOException {
+ if (isDirectory()) {
+ scanNestedJarInWarDirectory(jarPath);
+ } else {
+ scanNestedJarInWarFile(jarPath);
+ }
+ }
+
+ private void scanNestedJarInWarDirectory(String jarPath) throws IOException {
+ try {
+ File bundleDir = sourceAsFile();
+ File nestedJar = new File(bundleDir, "/" + jarPath);
+ if (nestedJar.isFile()) {
+ String pathToJar = "/" + nestedJar.getName();
+ if (driveCallBackIfNewJarFound(pathToJar)) {
+ doScanNestedJar(nestedJar);
+ }
+ }
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Unexpected URISyntaxException.", e);
+ }
+ }
+
+ private void scanNestedJarInWarFile(String jarPath) throws IOException {
+ JarInputStream jis = new JarInputStream(this.source.openStream());
+ try {
+ JarEntry entry;
+ while ((entry = jis.getNextJarEntry()) != null) {
+ String entryName = entry.getName();
+ if (jarPath.endsWith(entryName)) {
+ if (driveCallBackIfNewJarFound(entryName)) {
+ JarInputStream nestedJis = new JarInputStream(jis);
+ doScanNestedJar(entryName, nestedJis);
+ }
+ }
+ }
+ } finally {
+ IOUtils.closeQuietly(jis);
+ }
+
+ }
+
+ private void notifyClassFound(String entryName) {
+ if (this.findClassesInNestedJars) {
+ this.callBack.classFound(entryName);
+ }
+ }
+
+ private boolean isDirectory() {
+ if (FILE_SCHEME.equals(this.source.getProtocol())) {
+ try {
+ return sourceAsFile().isDirectory();
+ } catch (URISyntaxException e) {
+ LOGGER.warn("Unable to determine if bundle '" + this.source + "'is a directory.", e);
+ }
+ }
+ return false;
+ }
+
+ private File sourceAsFile() throws URISyntaxException {
+ URI uri = this.source.toURI();
+ if (uri.isOpaque()) {
+ return new File(uri.getSchemeSpecificPart());
+ } else {
+ return new File(uri);
+ }
+ }
+
+ private static String normalizePath(String path) {
+ return path.replace('\\', '/');
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleScannerCallback.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleScannerCallback.java
new file mode 100644
index 0000000..d0eb225
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleScannerCallback.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.url;
+
+interface WebBundleScannerCallback {
+
+ void jarFound(String pathToJar);
+
+ void classFound(String className);
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleUrl.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleUrl.java
new file mode 100644
index 0000000..3145c61
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleUrl.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.url;
+
+import static java.util.Collections.unmodifiableMap;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLStreamHandler;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.gemini.web.internal.WebContainerUtils;
+
+
+/**
+ * Encapsulates the state of a <code>war:</code> URL.
+ */
+public class WebBundleUrl {
+
+ public static final String SCHEME = WebContainerUtils.WEB_BUNDLE_SCHEME;
+
+ public final Object monitor = new Object();
+
+ private final URL url;
+
+ private final String location;
+
+ private Map<String, String> options;
+
+ public WebBundleUrl(String location, Map<String, String> options) throws MalformedURLException {
+ this.url = createURL(location, options);
+ this.location = location;
+ this.options = (options == null ? Collections.<String, String> emptyMap() : unmodifiableMap(new HashMap<String, String>(options)));
+ }
+
+ public WebBundleUrl(URL url) {
+ String protocol = url.getProtocol();
+ if (!SCHEME.equals(protocol)) {
+ throw new IllegalArgumentException("URL '" + url + "' is not a valid WAR URL");
+ }
+ this.url = url;
+ this.location = url.getPath();
+ }
+
+ public final String getLocation() {
+ return this.location;
+ }
+
+ /**
+ * Gets the query options from the URL. Note that validation is deferred until this point as doing it earlier does
+ * not produce the necessary BundleException when driven under {@link org.osgi.framework.BundleContext#installBundle(String) installBundle(String)}.
+ * @return the options in a String->String map
+ */
+ public final Map<String, String> getOptions() {
+ synchronized (this.monitor) {
+ if (this.options == null) {
+ this.options = parseQueryString(url.getQuery());
+ }
+ }
+ return this.options;
+ }
+
+ private String toPathString(String location, Map<?, ?> options) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(location);
+ appendQueryStringIfNecessary(options, sb);
+ return sb.toString();
+ }
+
+ public final String toString() {
+ return this.url.toString();
+ }
+
+ public final URL toURL() {
+ return this.url;
+ }
+
+ /**
+ * Hook method to use in unit testing. Allows for a specific {@link URLStreamHandler} to be added to {@link URL}
+ * instances created internally.
+ */
+ protected URLStreamHandler createURLStreamHandler() {
+ return null;
+ }
+
+ private URL createURL(String location, Map<?, ?> options) throws MalformedURLException {
+ return new URL(SCHEME, null, -1, toPathString(location, options), createURLStreamHandler());
+ }
+
+ private static Map<String, String> parseQueryString(String query) {
+ Map<String, String> options = new HashMap<String, String>();
+ if (query != null) {
+ String[] parms = query.split("&");
+ for (String parm : parms) {
+ int equals = parm.indexOf("=");
+ if (equals == -1) {
+ throw new IllegalArgumentException("Missing '=' in URL parameter '" + parm + "'");
+ }
+ options.put(parm.substring(0, equals), parm.substring(equals + 1));
+ }
+ }
+ return unmodifiableMap(options);
+ }
+
+ private static void appendQueryStringIfNecessary(Map<?, ?> options, StringBuilder sb) {
+ if (options != null && !options.isEmpty()) {
+ sb.append("?");
+ for (Map.Entry<?, ?> entry : options.entrySet()) {
+ sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
+ }
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ }
+}
diff --git a/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleUrlStreamHandlerService.java b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleUrlStreamHandlerService.java
new file mode 100644
index 0000000..4743c7e
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/java/org/eclipse/gemini/web/internal/url/WebBundleUrlStreamHandlerService.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.
+ * 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.internal.url;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import org.osgi.service.url.AbstractURLStreamHandlerService;
+import org.osgi.service.url.URLStreamHandlerService;
+
+
+import org.eclipse.gemini.web.core.InstallationOptions;
+import org.eclipse.gemini.web.core.WebBundleManifestTransformer;
+import org.eclipse.gemini.web.internal.WebContainerUtils;
+import org.eclipse.virgo.util.io.JarTransformer;
+import org.eclipse.virgo.util.io.JarTransformingURLConnection;
+import org.eclipse.virgo.util.io.JarTransformer.JarTransformerCallback;
+import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
+import org.eclipse.virgo.util.osgi.manifest.BundleManifestFactory;
+
+/**
+ * {@link URLStreamHandlerService} that transforms bundles installed with the <code>war:</code> protocol.
+ * <p/>
+ * Transformations are applied using the {@link WebBundleManifestTransformer}.
+ *
+ * @see WebBundleManifestTransformer
+ */
+public final class WebBundleUrlStreamHandlerService extends AbstractURLStreamHandlerService {
+
+ private final WebBundleManifestTransformer transformer;
+
+ public WebBundleUrlStreamHandlerService(WebBundleManifestTransformer transformer) {
+ this.transformer = transformer;
+ }
+
+ @Override
+ public URLConnection openConnection(URL u) throws IOException {
+ WebBundleUrl url = new WebBundleUrl(u);
+ URL actualUrl = new URL(url.getLocation());
+
+ JarTransformer jarTransformer = new JarTransformer(new Callback(actualUrl, url, this.transformer));
+ return new JarTransformingURLConnection(actualUrl, jarTransformer, true);
+ }
+
+ private static final class Callback implements JarTransformerCallback {
+
+ private final WebBundleManifestTransformer transformer;
+
+ private final URL sourceURL;
+
+ private final WebBundleUrl webBundleUrl;
+
+ public Callback(URL sourceURL, WebBundleUrl url, WebBundleManifestTransformer transformer) {
+ this.sourceURL = sourceURL;
+ this.webBundleUrl = url;
+ this.transformer = transformer;
+ }
+
+ public boolean transformEntry(String entryName, InputStream is, JarOutputStream jos) throws IOException {
+ if (JarFile.MANIFEST_NAME.equals(entryName)) {
+ jos.putNextEntry(new ZipEntry(entryName));
+ InputStreamReader reader = new InputStreamReader(is);
+ BundleManifest manifest = BundleManifestFactory.createBundleManifest(reader);
+ InstallationOptions options = new InstallationOptions(this.webBundleUrl.getOptions());
+ if (manifest.getHeader(WebContainerUtils.HEADER_SPRINGSOURCE_DEFAULT_WAB_HEADERS) != null) {
+ options.setDefaultWABHeaders(true);
+ }
+
+ boolean webBundle = WebContainerUtils.isWebApplicationBundle(manifest);
+ this.transformer.transform(manifest, sourceURL, options, webBundle);
+
+ toManifest(manifest.toDictionary()).write(jos);
+ jos.closeEntry();
+ return true;
+ }
+
+ // Delete signature files. Should be generalised into another transformer type.
+ return isSignatureFile(entryName);
+ }
+
+ private boolean isSignatureFile(String entryName) {
+ String[] entryNameComponents = entryName.split("/");
+ if (entryNameComponents.length == 2) {
+ if ("META-INF".equals(entryNameComponents[0])) {
+ String entryFileName = entryNameComponents[1];
+ if (entryFileName.endsWith(".SF") || entryFileName.endsWith(".DSA") || entryFileName.endsWith(".RSA")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static Manifest toManifest(Dictionary<String, String> headers) {
+ Manifest manifest = new Manifest();
+ Attributes attributes = manifest.getMainAttributes();
+ Enumeration<String> names = headers.keys();
+
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ String value = headers.get(name);
+
+ attributes.putValue(name, value);
+ }
+ return manifest;
+ }
+
+ }
+
+}
diff --git a/org.eclipse.gemini.web.core/src/main/resources/META-INF/MANIFEST.MF b/org.eclipse.gemini.web.core/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..fb73801
--- /dev/null
+++ b/org.eclipse.gemini.web.core/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,25 @@
+Manifest-Version: 1
+Export-Package: org.eclipse.gemini.web.core;version="1.0";uses:="javax
+ .servlet,org.eclipse.virgo.util.osgi.manifest,org.osgi.framework",org
+ .eclipse.gemini.web.core.spi;version="1.0";uses:="javax.servlet,org.o
+ sgi.framework",org.eclipse.gemini.web.internal.template;version="1.0"
+ ;uses:="org.osgi.framework,org.osgi.util.tracker",org.eclipse.gemini.
+ web.internal.url;version="1.0";uses:="org.eclipse.gemini.web.core,org
+ .eclipse.virgo.util.io,org.eclipse.virgo.util.osgi.manifest,org.eclip
+ se.virgo.util.osgi.manifest.parse,org.osgi.framework,org.osgi.service
+ .url"
+Bundle-Version: 1.0
+Tool: Bundlor 1.0.0.M6
+Bundle-Name: SpringSource OSGi Web Container
+Bundle-ManifestVersion: 2
+Bundle-Activator: org.eclipse.gemini.web.internal.WebContainerActivato
+ r
+Bundle-SymbolicName: org.eclipse.gemini.web.core
+Import-Package: javax.servlet;version="2.5",org.eclipse.virgo.util.io;
+ version="2.0",org.eclipse.virgo.util.osgi;version="2.0",org.eclipse.v
+ irgo.util.osgi.manifest;version="2.0",org.eclipse.virgo.util.osgi.man
+ ifest.parse;version="2.0",org.osgi.framework;version="0",org.osgi.ser
+ vice.event;version="1.1";resolution:="optional",org.osgi.service.pack
+ ageadmin;version="1.2",org.osgi.service.url;version="1.0",org.osgi.ut
+ il.tracker;version="1.4.2",org.slf4j;version="1.5.0"
+

Back to the top