cleaner fix to initialize the servlet context for OWB
diff --git a/cdi-extension-servlet-owb/src/main/java/org/apache/aries/cdi/extension/servlet/owb/OWBServletExtension.java b/cdi-extension-servlet-owb/src/main/java/org/apache/aries/cdi/extension/servlet/owb/OWBServletExtension.java
index 1e25872..2e01dde 100644
--- a/cdi-extension-servlet-owb/src/main/java/org/apache/aries/cdi/extension/servlet/owb/OWBServletExtension.java
+++ b/cdi-extension-servlet-owb/src/main/java/org/apache/aries/cdi/extension/servlet/owb/OWBServletExtension.java
@@ -16,22 +16,24 @@
import static java.util.Collections.list;
import static java.util.Optional.ofNullable;
-import static javax.interceptor.Interceptor.Priority.LIBRARY_AFTER;
import static org.osgi.framework.Constants.SERVICE_DESCRIPTION;
import static org.osgi.framework.Constants.SERVICE_RANKING;
import static org.osgi.framework.Constants.SERVICE_VENDOR;
import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT;
import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_LISTENER;
+import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Dictionary;
import java.util.Hashtable;
+import java.util.Objects;
import javax.annotation.Priority;
import javax.enterprise.event.Observes;
-import javax.enterprise.inject.spi.AfterDeploymentValidation;
-import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.ObserverMethod;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
@@ -47,7 +49,7 @@
import org.osgi.framework.BundleContext;
@SuppressWarnings("serial")
-public class OWBServletExtension extends BaseServletExtension implements StartObjectSupplier {
+public class OWBServletExtension extends BaseServletExtension implements StartObjectSupplier<ServletContextEvent> {
private final BundleContext bundleContext;
private final ServletContext proxyContext;
@@ -60,7 +62,7 @@
startEvent = null;
}
- public OWBServletExtension(Bundle bundle) {
+ public OWBServletExtension(final Bundle bundle) {
this.startEvent = new ServletContextEvent(new MockServletContext()) {
@Override
public ServletContext getServletContext() {
@@ -69,33 +71,23 @@
};
this.bundleContext = bundle.getBundleContext();
- this.delegateContext = new ForwardingContext();
+ this.delegateContext = new SimpleContext(); // ensure we don't loop over the proxy with a specific instance
// ensure we can switch the impl and keep ServletContextBean working with an updated context
+ final InvocationHandler contextHandler = new ServletContextHandler();
this.proxyContext = ServletContext.class.cast(Proxy.newProxyInstance(ServletContext.class.getClassLoader(),
- new Class<?>[]{ServletContext.class},
- (proxy, method, args) -> {
- try {
- final ServletContext ctx = ofNullable(delegateContext).orElseGet(startEvent::getServletContext);
- return method.invoke(ctx, args);
- }
- catch (final InvocationTargetException ite) {
- throw ite.getTargetException();
- }
- }));
+ new Class<?>[]{ServletContext.class}, contextHandler));
}
public void setDelegate(final ServletContext delegateContext) {
- final ServletContext oldCtx = this.delegateContext;
this.delegateContext = delegateContext;
- list(oldCtx.getAttributeNames()).forEach(attr -> this.delegateContext.setAttribute(attr, oldCtx.getAttribute(attr)));
}
- void afterDeploymentValidation(
- @Observes @Priority(LIBRARY_AFTER + 800)
- AfterDeploymentValidation adv, BeanManager beanManager) {
-
- Dictionary<String, Object> properties = new Hashtable<>();
+ // execute that as early as possible, it already has a currentInstance() so we'll find the right one
+ // and it will setDelegate so the context will be initialized and "materialized" for the full cdi lifecycle
+ void eagerInitOfServletContext(@Observes @Priority(ObserverMethod.DEFAULT_PRIORITY - 100)
+ final BeforeBeanDiscovery bbd) {
+ final Dictionary<String, Object> properties = new Hashtable<>();
properties.put(SERVICE_DESCRIPTION, "Aries CDI - HTTP Portable Extension for OpenWebBeans");
properties.put(SERVICE_VENDOR, "Apache Software Foundation");
properties.put(HTTP_WHITEBOARD_CONTEXT_SELECT, configuration.get(HTTP_WHITEBOARD_CONTEXT_SELECT));
@@ -103,7 +95,7 @@
properties.put(SERVICE_RANKING, Integer.MAX_VALUE - 100);
_listenerRegistration = bundleContext.registerService(
- LISTENER_CLASSES, new CdiListener(WebBeansContext.currentInstance()), properties);
+ LISTENER_CLASSES, new CdiListener(WebBeansContext.currentInstance()), properties);
}
private static final String[] LISTENER_CLASSES = new String[]{
@@ -113,7 +105,7 @@
};
@Override
- public Object getStartObject() {
+ public ServletContextEvent getStartObject() {
return startEvent;
}
@@ -156,11 +148,54 @@
}
}
- private static class ForwardingContext extends MockServletContext {
+ private static class SimpleContext extends MockServletContext {
@Override
public String getVirtualServerName() {
return "http";
}
}
+
+ private class ServletContextHandler implements InvocationHandler {
+ private volatile Integer hashCode; // ensure it is stable but try to use the right instance
+
+ @Override
+ public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+ if (method.getDeclaringClass() == Object.class) {
+ switch (method.getName()) {
+ case "equals":
+ return args[0] != null &&
+ (proxy == args[0] || equalsAsProxy(args[0]) || Objects.equals(getContext(), args[0]));
+ case "hashCode":
+ return getOrCreateHashCode();
+ default:
+ // let delegate
+ }
+ }
+ try {
+ return method.invoke(getContext(), args);
+ } catch (final InvocationTargetException ite) {
+ throw ite.getTargetException();
+ }
+ }
+
+ private int getOrCreateHashCode() {
+ if (hashCode == null) {
+ synchronized (this) {
+ if (hashCode == null) {
+ hashCode = delegateContext != null ? delegateContext.hashCode() : super.hashCode();
+ }
+ }
+ }
+ return hashCode;
+ }
+
+ private boolean equalsAsProxy(final Object arg) {
+ return Proxy.isProxyClass(arg.getClass()) && Objects.equals(Proxy.getInvocationHandler(arg), this);
+ }
+
+ private ServletContext getContext() {
+ return ofNullable(delegateContext).orElseGet(startEvent::getServletContext);
+ }
+ }
}
diff --git a/cdi-owb/pom.xml b/cdi-owb/pom.xml
index 8a9970c..ddeffbb 100644
--- a/cdi-owb/pom.xml
+++ b/cdi-owb/pom.xml
@@ -60,6 +60,12 @@
<dependencies>
<dependency>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>biz.aQute.bnd.annotation</artifactId>
+ <version>${bnd.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.apache.aries.cdi</groupId>
<artifactId>org.apache.aries.cdi.extra</artifactId>
<version>${project.version}</version>
diff --git a/cdi-owb/src/main/java/org/apache/aries/cdi/owb/core/OWBCDIContainerInitializer.java b/cdi-owb/src/main/java/org/apache/aries/cdi/owb/core/OWBCDIContainerInitializer.java
index 1b34f7b..e8a9dd7 100644
--- a/cdi-owb/src/main/java/org/apache/aries/cdi/owb/core/OWBCDIContainerInitializer.java
+++ b/cdi-owb/src/main/java/org/apache/aries/cdi/owb/core/OWBCDIContainerInitializer.java
@@ -162,7 +162,9 @@
properties.putAll(entry.getValue());
// Extract the start instance to ensure it works with the configured services (properties)
- startObject = StartObjectSupplier.class.cast(entry.getKey()).getStartObject();
+ final StartObjectSupplier<?> objectSupplier = StartObjectSupplier.class.cast(entry.getKey());
+ properties.putAll(objectSupplier.properties());
+ startObject = objectSupplier.getStartObject();
});
bootstrap = new WebBeansContext(services, properties) {
diff --git a/cdi-owb/src/main/java/org/apache/aries/cdi/owb/spi/StartObjectSupplier.java b/cdi-owb/src/main/java/org/apache/aries/cdi/owb/spi/StartObjectSupplier.java
index eeebcfb..76f4a42 100644
--- a/cdi-owb/src/main/java/org/apache/aries/cdi/owb/spi/StartObjectSupplier.java
+++ b/cdi-owb/src/main/java/org/apache/aries/cdi/owb/spi/StartObjectSupplier.java
@@ -13,10 +13,35 @@
*/
package org.apache.aries.cdi.owb.spi;
+import static java.util.Collections.emptyMap;
+
+import java.util.Map;
+import aQute.bnd.annotation.baseline.BaselineIgnore;
+
+/**
+ * Enables to customize the OWB context creation.
+ * @param <T> the type of start object provided.
+ */
public interface StartObjectSupplier<T> {
+ /**
+ * @return cdi start instance (instance which can be observed when appscope is initialized).
+ */
T getStartObject();
+ /**
+ * @return enable to select the supplier to use when ambiguous (like ranking a bit), higher is selected.
+ */
default int ordinal() {
return 0;
}
+
+ /**
+ * TIP: this enables to customize the context from a bundle extension and not a service.
+ *
+ * @return a set of OWB properties.
+ */
+ @BaselineIgnore("1.1.1") // does not break backward compat but baseline does not see "default"
+ default Map<String, String> properties() {
+ return emptyMap();
+ }
}