Bug 454256 - [http] add customizer to provide default context selection
and context path prefix
diff --git a/bundles/org.eclipse.equinox.http.servlet.tests/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.http.servlet.tests/META-INF/MANIFEST.MF
index 37c5897..cb3b051 100644
--- a/bundles/org.eclipse.equinox.http.servlet.tests/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.http.servlet.tests/META-INF/MANIFEST.MF
@@ -9,6 +9,7 @@
javax.servlet.http;version="2.6.0",
junit.framework;version="4.8.2",
org.eclipse.equinox.http.servlet;version="1.1.0",
+ org.eclipse.equinox.http.servlet.context; version="1.0.0",
org.eclipse.osgi.service.urlconversion;version="1.0.0",
org.junit;version="4.11.0",
org.osgi.framework;version="1.6.0",
diff --git a/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java b/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java
index accbc54..1dd7dd4 100644
--- a/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java
+++ b/bundles/org.eclipse.equinox.http.servlet.tests/src/org/eclipse/equinox/http/servlet/tests/ServletTest.java
@@ -17,7 +17,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
-import java.util.EventListener;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
@@ -39,6 +38,7 @@
import junit.framework.TestCase;
import org.eclipse.equinox.http.servlet.ExtendedHttpService;
+import org.eclipse.equinox.http.servlet.context.ContextPathCustomizer;
import org.eclipse.equinox.http.servlet.tests.bundle.Activator;
import org.eclipse.equinox.http.servlet.tests.bundle.BundleAdvisor;
import org.eclipse.equinox.http.servlet.tests.bundle.BundleInstaller;
@@ -56,6 +56,7 @@
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.http.HttpService;
@@ -1200,6 +1201,7 @@
private static final String UNREGISTER = "unregister";
private static final String STATUS_PARAM = "servlet.init.status";
private static final String TEST_PROTOTYPE_NAME = "test.prototype.name";
+ private static final String TEST_PATH_CUSTOMIZER_NAME = "test.path.customizer.name";
public void testWBServletChangeInitParams() throws Exception{
String actual;
@@ -1277,6 +1279,84 @@
Assert.assertEquals(getName() + 2, actual);
}
+ public void testWBServletDefaultContextAdaptor1() throws Exception{
+ Dictionary<String, String> helperProps = new Hashtable<String, String>();
+ helperProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME, "testContext" + getName());
+ helperProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH, "/testContext");
+ helperProps.put(TEST_PATH_CUSTOMIZER_NAME, getName());
+ ServiceRegistration<ServletContextHelper> helperReg = getBundleContext().registerService(ServletContextHelper.class, new TestServletContextHelperFactory(), helperProps);
+
+ ServiceRegistration<ContextPathCustomizer> pathAdaptorReg = null;
+ try {
+ Map<String, String> params = new HashMap<String, String>();
+ params.put(TEST_PROTOTYPE_NAME, getName());
+ params.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, '/' + getName());
+ params.put(STATUS_PARAM, getName());
+ params.put("servlet.init." + TEST_PATH_CUSTOMIZER_NAME, getName());
+ String actual = doRequest(CONFIGURE, params);
+ Assert.assertEquals(getName(), actual);
+
+ actual = requestAdvisor.request(getName());
+ Assert.assertEquals(getName(), actual);
+
+ ContextPathCustomizer pathAdaptor = new TestContextPathAdaptor("(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=" + "testContext" + getName() + ")", null, getName());
+ pathAdaptorReg = getBundleContext().registerService(ContextPathCustomizer.class, pathAdaptor, null);
+
+ actual = requestAdvisor.request("testContext/" + getName());
+ Assert.assertEquals(getName(), actual);
+
+ pathAdaptorReg.unregister();
+ pathAdaptorReg = null;
+
+ actual = requestAdvisor.request(getName());
+ Assert.assertEquals(getName(), actual);
+ } finally {
+ helperReg.unregister();
+ if (pathAdaptorReg != null) {
+ pathAdaptorReg.unregister();
+ }
+ }
+ }
+
+ public void testWBServletDefaultContextAdaptor2() throws Exception{
+ Dictionary<String, String> helperProps = new Hashtable<String, String>();
+ helperProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME, "testContext" + getName());
+ helperProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH, "/testContext");
+ helperProps.put(TEST_PATH_CUSTOMIZER_NAME, getName());
+ ServiceRegistration<ServletContextHelper> helperReg = getBundleContext().registerService(ServletContextHelper.class, new TestServletContextHelperFactory(), helperProps);
+
+ ServiceRegistration<ContextPathCustomizer> pathAdaptorReg = null;
+ try {
+ Map<String, String> params = new HashMap<String, String>();
+ params.put(TEST_PROTOTYPE_NAME, getName());
+ params.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, '/' + getName());
+ params.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=" + "testContext" + getName() + ")");
+ params.put(STATUS_PARAM, getName());
+ params.put("servlet.init." + TEST_PATH_CUSTOMIZER_NAME, getName());
+ String actual = doRequest(CONFIGURE, params);
+ Assert.assertEquals(getName(), actual);
+
+ actual = requestAdvisor.request("testContext/" + getName());
+ Assert.assertEquals(getName(), actual);
+
+ ContextPathCustomizer pathAdaptor = new TestContextPathAdaptor(null, "testPrefix", getName());
+ pathAdaptorReg = getBundleContext().registerService(ContextPathCustomizer.class, pathAdaptor, null);
+
+ actual = requestAdvisor.request("testPrefix/testContext/" + getName());
+ Assert.assertEquals(getName(), actual);
+
+ pathAdaptorReg.unregister();
+ pathAdaptorReg = null;
+
+ actual = requestAdvisor.request("testContext/" + getName());
+ Assert.assertEquals(getName(), actual);
+ } finally {
+ helperReg.unregister();
+ if (pathAdaptorReg != null) {
+ pathAdaptorReg.unregister();
+ }
+ }
+ }
private String doRequest(String action, Map<String, String> params) throws IOException {
StringBuilder requestInfo = new StringBuilder(PROTOTYPE);
requestInfo.append(action);
@@ -1387,4 +1467,55 @@
public void init(FilterConfig arg0) throws ServletException {/**/}
}
+ static class TestServletContextHelperFactory implements ServiceFactory<ServletContextHelper> {
+ static class TestServletContextHelper extends ServletContextHelper {
+ public TestServletContextHelper(Bundle bundle) {
+ super(bundle);
+ }};
+ @Override
+ public ServletContextHelper getService(Bundle bundle, ServiceRegistration<ServletContextHelper> registration) {
+ return new TestServletContextHelper(bundle);
+ }
+
+ @Override
+ public void ungetService(Bundle bundle, ServiceRegistration<ServletContextHelper> registration,
+ ServletContextHelper service) {
+ // nothing
+ }
+
+ }
+
+ static class TestContextPathAdaptor extends ContextPathCustomizer {
+ private final String defaultFilter;
+ private final String contextPrefix;
+ private final String testName;
+
+ /**
+ * @param defaultFilter
+ * @param contextPrefix
+ */
+ public TestContextPathAdaptor(String defaultFilter, String contextPrefix, String testName) {
+ super();
+ this.defaultFilter = defaultFilter;
+ this.contextPrefix = contextPrefix;
+ this.testName = testName;
+ }
+
+ @Override
+ public String getDefaultContextSelectFilter(ServiceReference<?> httpWhiteBoardService) {
+ if (testName.equals(httpWhiteBoardService.getProperty("servlet.init." + TEST_PATH_CUSTOMIZER_NAME))) {
+ return defaultFilter;
+ }
+ return null;
+ }
+
+ @Override
+ public String getContextPathPrefix(ServiceReference<ServletContextHelper> helper) {
+ if (testName.equals(helper.getProperty(TEST_PATH_CUSTOMIZER_NAME))) {
+ return contextPrefix;
+ }
+ return null;
+ }
+
+ }
}
diff --git a/bundles/org.eclipse.equinox.http.servlet/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.http.servlet/META-INF/MANIFEST.MF
index c79c4e1..4d7b121 100644
--- a/bundles/org.eclipse.equinox.http.servlet/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.http.servlet/META-INF/MANIFEST.MF
@@ -7,7 +7,8 @@
Bundle-Activator: org.eclipse.equinox.http.servlet.internal.Activator
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
-Export-Package: org.eclipse.equinox.http.servlet;version="1.1.0"
+Export-Package: org.eclipse.equinox.http.servlet;version="1.1.0",
+ org.eclipse.equinox.http.servlet.context; x-internal:=true;version="1.0.0"
Import-Package: javax.servlet;version="[2.3.0,4.0.0)",
javax.servlet.annotation;version="2.6.0";resolution:=optional,
javax.servlet.descriptor;version="2.6.0";resolution:=optional,
diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/context/ContextPathCustomizer.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/context/ContextPathCustomizer.java
new file mode 100644
index 0000000..c899fe9
--- /dev/null
+++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/context/ContextPathCustomizer.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) Dec 5, 2014 Liferay, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Liferay, Inc. - initial API and implementation and/or initial
+ * documentation
+ ******************************************************************************/
+
+package org.eclipse.equinox.http.servlet.context;
+
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.http.context.ServletContextHelper;
+
+/**
+ * A customizer that is called by the Http Whiteboard runtime in order to allow
+ * customization of context path used for servlets, resources and filters.
+ * There are two types of customizations that are allowed.
+ * <ol>
+ * <li>Control the default selection filter used when no "osgi.http.whiteboard.context.select"
+ * is specified.</li>
+ * <li>Provide a prefix to the context path "osgi.http.whiteboard.context.path"
+ * specified by ServletContextHelper registrations.</li>
+ * </ol>
+ * <p>
+ * Registering a customizer results in re-initializing all existing ServletContextHelper registrations.
+ * This should not be done often. Only the highest ranked customizer is used the runtime.
+ * </p>
+ * <p>
+ * <b>Note:</b> This class is part of an interim SPI that is still under
+ * development and expected to change significantly before reaching stability.
+ * It is being made available at this early stage to solicit feedback from pioneering
+ * adopters on the understanding that any code that uses this SPI will almost certainly
+ * be broken (repeatedly) as the SPI evolves.
+ * </p>
+ * @since 1.2
+ */
+public abstract class ContextPathCustomizer {
+ /**
+ * Returns a service filter that is used to select the default ServletContextHelper when no
+ * selection filter is specified by the whiteboard service. This method is only
+ * called if the supplied whiteboard service does not provide the
+ * "osgi.http.whiteboard.context.select" service property.
+ * @param httpWhiteBoardService
+ * @return a service filter that is used to select the default SErvletContextHelper for the
+ * specified whiteboard service.
+ */
+ public String getDefaultContextSelectFilter(ServiceReference<?> httpWhiteBoardService) {
+ return null;
+ }
+
+ /**
+ * Returns a prefix that is prepended to the context path value
+ * specified by the supplied helper's "osgi.http.whiteboard.context.path"
+ * service property.
+ * @param helper the helper for which the context path will be prepended to
+ * @return the prefix to prepend to the context path
+ */
+ public String getContextPathPrefix(ServiceReference<ServletContextHelper> helper) {
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/HttpServiceRuntimeImpl.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/HttpServiceRuntimeImpl.java
index 0a4f158..128e18b 100644
--- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/HttpServiceRuntimeImpl.java
+++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/HttpServiceRuntimeImpl.java
@@ -20,6 +20,7 @@
import javax.servlet.*;
import javax.servlet.Filter;
import javax.servlet.http.*;
+import org.eclipse.equinox.http.servlet.context.ContextPathCustomizer;
import org.eclipse.equinox.http.servlet.internal.context.*;
import org.eclipse.equinox.http.servlet.internal.error.*;
import org.eclipse.equinox.http.servlet.internal.servlet.*;
@@ -64,6 +65,11 @@
new ServiceTracker<ServletContextHelper, AtomicReference<ContextController>>(
trackingContext, ServletContextHelper.class, this);
+ contextPathCustomizerHolder = new ContextPathCustomizerHolder(consumingContext, contextServiceTracker);
+ contextPathAdaptorTracker = new ServiceTracker<ContextPathCustomizer, ContextPathCustomizer>(
+ consumingContext, ContextPathCustomizer.class, contextPathCustomizerHolder);
+ contextPathAdaptorTracker.open();
+
contextServiceTracker.open();
Hashtable<String, Object> defaultContextProps = new Hashtable<String, Object>();
@@ -106,6 +112,7 @@
if (contextPath == null || contextPath.equals(Const.SLASH)) {
contextPath = Const.BLANK;
}
+ contextPath = adaptContextPath(contextPath, serviceReference);
long serviceId = (Long)serviceReference.getProperty(
Constants.SERVICE_ID);
@@ -124,6 +131,28 @@
return result;
}
+ private String adaptContextPath(String contextPath, ServiceReference<ServletContextHelper> helper) {
+ ContextPathCustomizer pathAdaptor = contextPathCustomizerHolder.getHighestRanked();
+ if (pathAdaptor != null) {
+ String contextPrefix = pathAdaptor.getContextPathPrefix(helper);
+ if (contextPrefix != null && !contextPrefix.isEmpty() && !contextPrefix.equals(Const.SLASH)) {
+ if (!contextPrefix.startsWith(Const.SLASH)) {
+ contextPrefix = Const.SLASH + contextPrefix;
+ }
+ return contextPrefix + contextPath;
+ }
+ }
+ return contextPath;
+ }
+
+ public String getDefaultContextSelectFilter(ServiceReference<?> httpWhiteBoardService) {
+ ContextPathCustomizer pathAdaptor = contextPathCustomizerHolder.getHighestRanked();
+ if (pathAdaptor != null) {
+ return pathAdaptor.getDefaultContextSelectFilter(httpWhiteBoardService);
+ }
+ return null;
+ }
+
public ContextController addServletContextHelper(
ServiceReference<ServletContextHelper> servletContextHelperRef,
String contextName, String contextPath, long serviceId,
@@ -172,7 +201,10 @@
public void destroy() {
defaultContextReg.unregister();
+
contextServiceTracker.close();
+ contextPathAdaptorTracker.close();
+
controllerMap.clear();
contextPathMap.clear();
registeredObjects.clear();
@@ -185,6 +217,7 @@
parentServletContext = null;
registeredObjects = null;
contextServiceTracker = null;
+ contextPathCustomizerHolder = null;
}
public boolean doDispatch(
@@ -210,18 +243,6 @@
return initParameters;
}
- public ContextController getContextController(
- org.osgi.framework.Filter targetFilter) {
-
- for (ContextController contextController : controllerMap.keySet()) {
- if (contextController.matches(targetFilter)) {
- return contextController;
- }
- }
-
- return null;
- }
-
public Set<Object> getRegisteredObjects() {
return registeredObjects;
}
@@ -981,6 +1002,8 @@
private Set<String> registeredContextNames = new ConcurrentSkipListSet<String>();
private ServiceTracker<ServletContextHelper, AtomicReference<ContextController>> contextServiceTracker;
+ private ServiceTracker<ContextPathCustomizer, ContextPathCustomizer> contextPathAdaptorTracker;
+ private ContextPathCustomizerHolder contextPathCustomizerHolder;
static class DefaultServletContextHelperFactory implements ServiceFactory<ServletContextHelper> {
@Override
@@ -1045,4 +1068,65 @@
}
+ static class ContextPathCustomizerHolder implements ServiceTrackerCustomizer<ContextPathCustomizer, ContextPathCustomizer> {
+ private final BundleContext context;
+ private final ServiceTracker<ServletContextHelper, AtomicReference<ContextController>> contextServiceTracker;
+ private final NavigableMap<ServiceReference<ContextPathCustomizer>, ContextPathCustomizer> pathCustomizers =
+ new TreeMap<ServiceReference<ContextPathCustomizer>, ContextPathCustomizer>(Collections.reverseOrder());
+
+ public ContextPathCustomizerHolder(
+ BundleContext context,
+ ServiceTracker<ServletContextHelper, AtomicReference<ContextController>> contextServiceTracker) {
+ super();
+ this.context = context;
+ this.contextServiceTracker = contextServiceTracker;
+ }
+
+ @Override
+ public ContextPathCustomizer addingService(
+ ServiceReference<ContextPathCustomizer> reference) {
+ ContextPathCustomizer service = context.getService(reference);
+ boolean reset = false;
+ synchronized (pathCustomizers) {
+ pathCustomizers.put(reference, service);
+ reset = pathCustomizers.firstKey().equals(reference);
+ }
+ if (reset) {
+ contextServiceTracker.close();
+ contextServiceTracker.open();
+ }
+ return service;
+ }
+
+ @Override
+ public void modifiedService(
+ ServiceReference<ContextPathCustomizer> reference,
+ ContextPathCustomizer service) {
+ removedService(reference, service);
+ addingService(reference);
+ }
+ @Override
+ public void removedService(
+ ServiceReference<ContextPathCustomizer> reference,
+ ContextPathCustomizer service) {
+ boolean reset = false;
+ synchronized (pathCustomizers) {
+ ServiceReference<ContextPathCustomizer> currentFirst = pathCustomizers.firstKey();
+ pathCustomizers.remove(reference);
+ reset = currentFirst.equals(reference);
+ }
+ if (reset) {
+ contextServiceTracker.close();
+ contextServiceTracker.open();
+ }
+ context.ungetService(reference);
+ }
+
+ ContextPathCustomizer getHighestRanked() {
+ synchronized (pathCustomizers) {
+ Map.Entry<ServiceReference<ContextPathCustomizer>, ContextPathCustomizer> firstEntry = pathCustomizers.firstEntry();
+ return firstEntry == null ? null : firstEntry.getValue();
+ }
+ }
+ }
}
diff --git a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/ContextController.java b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/ContextController.java
index 871ce6b..8d92431 100644
--- a/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/ContextController.java
+++ b/bundles/org.eclipse.equinox.http.servlet/src/org/eclipse/equinox/http/servlet/internal/context/ContextController.java
@@ -760,9 +760,12 @@
String contextSelector = (String) whiteBoardService.getProperty(
HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT);
if (contextSelector == null) {
- contextSelector = "(" + //$NON-NLS-1$
- HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=" //$NON-NLS-1$
- + HttpWhiteboardConstants.HTTP_WHITEBOARD_DEFAULT_CONTEXT_NAME + ")"; //$NON-NLS-1$
+ contextSelector = httpServiceRuntime.getDefaultContextSelectFilter(whiteBoardService);
+ if (contextSelector == null) {
+ contextSelector = "(" + //$NON-NLS-1$
+ HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=" //$NON-NLS-1$
+ + HttpWhiteboardConstants.HTTP_WHITEBOARD_DEFAULT_CONTEXT_NAME + ")"; //$NON-NLS-1$
+ }
}
if (!contextSelector.startsWith(Const.OPEN_PAREN)) {