You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aries.apache.org by jb...@apache.org on 2019/11/29 08:40:31 UTC

[aries-cdi] branch rotty3000/cdi-spi updated: adding ContainerListener spi to enable to listen for container lifecycle portably

This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch rotty3000/cdi-spi
in repository https://gitbox.apache.org/repos/asf/aries-cdi.git


The following commit(s) were added to refs/heads/rotty3000/cdi-spi by this push:
     new 5de485b  adding ContainerListener spi to enable to listen for container lifecycle portably
     new b1247ac  Merge pull request #4 from rmannibucau/rmannibucau/container-listener
5de485b is described below

commit 5de485b6233c3079371bbc619857b4024d9d9251
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Thu Nov 28 10:17:15 2019 +0100

    adding ContainerListener spi to enable to listen for container lifecycle portably
---
 cdi-executable/weld-executable.bndrun              |   1 -
 .../aries/cdi/container/internal/Activator.java    |  12 +-
 .../internal/container/ContainerBootstrap.java     |  85 +++++---
 .../container/internal/spi/ContainerListener.java  |  21 ++
 .../internal/phase/ContainerBootstrapTest.java     |   3 +-
 .../cdi/container/test/ContainerListenerTest.java  | 217 +++++++++++++++++++++
 6 files changed, 313 insertions(+), 26 deletions(-)

diff --git a/cdi-executable/weld-executable.bndrun b/cdi-executable/weld-executable.bndrun
index 1c46fa3..242c842 100644
--- a/cdi-executable/weld-executable.bndrun
+++ b/cdi-executable/weld-executable.bndrun
@@ -50,7 +50,6 @@
 
 -runbundles: \
 	javax.ejb-api;version='[3.2.0,3.2.1)',\
-	javax.enterprise.cdi-api;version='[2.0.0,2.0.1)',\
 	javax.transaction-api;version='[1.2.0,1.2.1)',\
 	jboss-classfilewriter;version='[1.2.3,1.2.4)',\
 	org.apache.aries.cdi.extender;version='[1.1.0,1.1.1)',\
diff --git a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/Activator.java b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/Activator.java
index 2f78330..02cd5c0 100644
--- a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/Activator.java
+++ b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/Activator.java
@@ -41,6 +41,7 @@ import org.apache.aries.cdi.container.internal.model.FactoryActivator;
 import org.apache.aries.cdi.container.internal.model.FactoryComponent;
 import org.apache.aries.cdi.container.internal.model.SingleActivator;
 import org.apache.aries.cdi.container.internal.model.SingleComponent;
+import org.apache.aries.cdi.container.internal.spi.ContainerListener;
 import org.apache.aries.cdi.container.internal.util.Logs;
 import org.apache.aries.cdi.spi.CDIContainerInitializer;
 import org.apache.felix.utils.extender.AbstractExtender;
@@ -78,6 +79,7 @@ public class Activator extends AbstractExtender {
 	private volatile Logs _logs;
 	private volatile PromiseFactory _promiseFactory;
 	private volatile ServiceTracker<CDIContainerInitializer, ServiceObjects<CDIContainerInitializer>> _containerTracker;
+	private volatile ServiceTracker<ContainerListener, ContainerListener> _containerListeners;
 
 	public Activator() {
 		setSynchronous(true);
@@ -110,6 +112,10 @@ public class Activator extends AbstractExtender {
 			}
 		);
 		_containerTracker.open();
+
+		_containerListeners = new ServiceTracker<>(bundleContext, ContainerListener.class, null);
+		_containerListeners.open();
+
 		_executorService = Executors.newSingleThreadExecutor(worker -> {
 			Thread t = new Thread(new ThreadGroup("Apache Aries CCR - CDI"), worker, "Aries CCR Thread (" + hashCode() + ")");
 			t.setDaemon(false);
@@ -175,6 +181,9 @@ public class Activator extends AbstractExtender {
 		}
 		_executorService.shutdownNow();
 		_executorService.awaitTermination(2, TimeUnit.SECONDS); // not important but just to avoid to quit too fast
+
+		_containerTracker.close();
+		_containerListeners.close();
 	}
 
 	@Override
@@ -215,7 +224,8 @@ public class Activator extends AbstractExtender {
 								new SingleComponent.Builder(containerState,
 									new SingleActivator.Builder(containerState)),
 								new FactoryComponent.Builder(containerState,
-									new FactoryActivator.Builder(containerState))
+									new FactoryActivator.Builder(containerState)),
+								_containerListeners
 							)
 						)
 					).build()
diff --git a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/container/ContainerBootstrap.java b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/container/ContainerBootstrap.java
index 677ea78..e0dc3c5 100644
--- a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/container/ContainerBootstrap.java
+++ b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/container/ContainerBootstrap.java
@@ -19,6 +19,8 @@ import static java.util.Objects.requireNonNull;
 import java.net.URL;
 import java.util.ServiceLoader;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
 
 import javax.enterprise.inject.spi.Extension;
 
@@ -29,12 +31,15 @@ import org.apache.aries.cdi.container.internal.model.ExtendedExtensionDTO;
 import org.apache.aries.cdi.container.internal.model.FactoryComponent;
 import org.apache.aries.cdi.container.internal.model.OSGiBean;
 import org.apache.aries.cdi.container.internal.model.SingleComponent;
+import org.apache.aries.cdi.container.internal.spi.ContainerListener;
 import org.apache.aries.cdi.container.internal.util.Maps;
 import org.apache.aries.cdi.container.internal.util.Syncro;
 import org.apache.aries.cdi.spi.CDIContainerInitializer;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceObjects;
+import org.osgi.framework.ServiceReference;
 import org.osgi.service.cdi.runtime.dto.ExtensionDTO;
 import org.osgi.service.log.Logger;
 import org.osgi.util.tracker.ServiceTracker;
@@ -42,16 +47,18 @@ import org.osgi.util.tracker.ServiceTracker;
 public class ContainerBootstrap extends Phase {
 
 	public ContainerBootstrap(
-		ContainerState containerState,
-		ServiceTracker<CDIContainerInitializer, ServiceObjects<CDIContainerInitializer>> containerTracker,
-		ConfigurationListener.Builder configurationBuilder,
-		SingleComponent.Builder singleBuilder,
-		FactoryComponent.Builder factoryBuilder) {
+			ContainerState containerState,
+			ServiceTracker<CDIContainerInitializer, ServiceObjects<CDIContainerInitializer>> containerTracker,
+			ConfigurationListener.Builder configurationBuilder,
+			SingleComponent.Builder singleBuilder,
+			FactoryComponent.Builder factoryBuilder,
+			ServiceTracker<ContainerListener, ContainerListener> listeners) {
 
 		super(containerState, null);
 
 		_configurationBuilder = configurationBuilder;
 		_containerTracker = containerTracker;
+		_listeners = listeners;
 		_singleBuilder = singleBuilder;
 		_factoryBuilder = factoryBuilder;
 		_log = containerState.containerLogs().getLogger(getClass());
@@ -66,14 +73,21 @@ public class ContainerBootstrap extends Phase {
 		try (Syncro syncro = _lock.open()) {
 			if (_containerInstance != null) {
 				_log.debug(l -> l.debug("CCR container shutdown for {}", bundle()));
-				_containerInstance.close();
-				_containerInstance = null;
 				try {
-					_serviceObjects.ungetService(_initializer);
-					_initializer = null;
-				}
-				catch (Throwable t) {
-					_log.trace(l -> l.trace("CCR Failure in returning initializer instance on {}", bundle(), t));
+					_containerInstance.close();
+					withListeners(ContainerListener::onStopSuccess);
+				} catch (final RuntimeException re) {
+					withListeners(listener -> listener.onStopError(re));
+					throw re;
+				} finally {
+					_containerInstance = null;
+					try {
+						_serviceObjects.ungetService(_initializer);
+						_initializer = null;
+					}
+					catch (Throwable t) {
+						_log.trace(l -> l.trace("CCR Failure in returning initializer instance on {}", bundle(), t));
+					}
 				}
 			}
 
@@ -109,21 +123,28 @@ public class ContainerBootstrap extends Phase {
 
 			_log.debug(log -> log.debug("CCR container startup for {}", bundle()));
 
-			// always use a new class loader
-			BundleClassLoader loader = new BundleClassLoader(containerState.bundle(), containerState.extenderBundle());
+			try {
+				// always use a new class loader
+				BundleClassLoader loader = new BundleClassLoader(containerState.bundle(), containerState.extenderBundle());
+
+				_initializer = _serviceObjects.getService();
 
-			_initializer = _serviceObjects.getService();
+				processExtensions(loader, _initializer);
 
-			processExtensions(loader, _initializer);
+				containerState.containerComponentTemplateDTO().properties.forEach(_initializer::addProperty);
 
-			containerState.containerComponentTemplateDTO().properties.forEach(_initializer::addProperty);
+				_containerInstance = _initializer
+						.addBeanClasses(containerState.beansModel().getOSGiBeans().stream().map(OSGiBean::getBeanClass).toArray(Class<?>[]::new))
+						.addBeanXmls(containerState.beansModel().getBeansXml().toArray(new URL[0]))
+						.setBundleContext(bundle().getBundleContext())
+						.setClassLoader(loader)
+						.initialize();
 
-			_containerInstance = _initializer
-				.addBeanClasses(containerState.beansModel().getOSGiBeans().stream().map(OSGiBean::getBeanClass).toArray(Class<?>[]::new))
-				.addBeanXmls(containerState.beansModel().getBeansXml().toArray(new URL[0]))
-				.setBundleContext(bundle().getBundleContext())
-				.setClassLoader(loader)
-				.initialize();
+				withListeners(ContainerListener::onStartSuccess);
+			} catch (final RuntimeException re) {
+				withListeners(listener -> listener.onStartError(re));
+				throw re;
+			}
 
 			return true;
 		}
@@ -175,6 +196,23 @@ public class ContainerBootstrap extends Phase {
 		}
 	}
 
+	private void withListeners(final Consumer<ContainerListener> action) {
+		final ServiceReference<ContainerListener>[] refs = _listeners.getServiceReferences();
+		if (refs != null && refs.length > 0) {
+			final BundleContext bundleContext = bundle().getBundleContext();
+			Stream.of(refs).forEach(ref -> {
+				final ContainerListener service = bundleContext.getService(ref);
+				if (service != null) {
+					try {
+						action.accept(service);
+					} finally {
+						bundleContext.ungetService(ref);
+					}
+				}
+			});
+		}
+	}
+
 	private volatile AutoCloseable _containerInstance;
 	private final ServiceTracker<CDIContainerInitializer, ServiceObjects<CDIContainerInitializer>> _containerTracker;
 	private final ConfigurationListener.Builder _configurationBuilder;
@@ -184,5 +222,6 @@ public class ContainerBootstrap extends Phase {
 	private final SingleComponent.Builder _singleBuilder;
 	private final Syncro _lock = new Syncro(true);
 	private final Logger _log;
+	private final ServiceTracker<ContainerListener, ContainerListener> _listeners;
 
 }
\ No newline at end of file
diff --git a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/spi/ContainerListener.java b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/spi/ContainerListener.java
new file mode 100644
index 0000000..c77f82e
--- /dev/null
+++ b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/spi/ContainerListener.java
@@ -0,0 +1,21 @@
+/**
+ * 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.apache.aries.cdi.container.internal.spi;
+
+public interface ContainerListener {
+    void onStartSuccess();
+    void onStartError(Throwable error);
+    void onStopSuccess();
+    void onStopError(Throwable error);
+}
diff --git a/cdi-extender/src/test/java/org/apache/aries/cdi/container/internal/phase/ContainerBootstrapTest.java b/cdi-extender/src/test/java/org/apache/aries/cdi/container/internal/phase/ContainerBootstrapTest.java
index fe71a79..be14d4a 100644
--- a/cdi-extender/src/test/java/org/apache/aries/cdi/container/internal/phase/ContainerBootstrapTest.java
+++ b/cdi-extender/src/test/java/org/apache/aries/cdi/container/internal/phase/ContainerBootstrapTest.java
@@ -87,7 +87,8 @@ public class ContainerBootstrapTest extends BaseCDIBundleTest {
 			containerState, serviceTracker,
 			new ConfigurationListener.Builder(containerState),
 			new SingleComponent.Builder(containerState, null),
-			new FactoryComponent.Builder(containerState, null));
+			new FactoryComponent.Builder(containerState, null),
+			mock(ServiceTracker.class));
 
 		ExtendedComponentInstanceDTO componentInstanceDTO = new ExtendedComponentInstanceDTO(containerState, new ContainerActivator.Builder(containerState, containerBootstrap));
 		componentInstanceDTO.activations = new CopyOnWriteArrayList<>();
diff --git a/cdi-extender/src/test/java/org/apache/aries/cdi/container/test/ContainerListenerTest.java b/cdi-extender/src/test/java/org/apache/aries/cdi/container/test/ContainerListenerTest.java
new file mode 100644
index 0000000..20ba3af
--- /dev/null
+++ b/cdi-extender/src/test/java/org/apache/aries/cdi/container/test/ContainerListenerTest.java
@@ -0,0 +1,217 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.apache.aries.cdi.container.test;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+
+import javax.enterprise.inject.spi.Extension;
+
+import org.apache.aries.cdi.container.internal.container.ConfigurationListener;
+import org.apache.aries.cdi.container.internal.container.ContainerBootstrap;
+import org.apache.aries.cdi.container.internal.container.ContainerState;
+import org.apache.aries.cdi.container.internal.model.FactoryComponent;
+import org.apache.aries.cdi.container.internal.model.SingleComponent;
+import org.apache.aries.cdi.container.internal.spi.ContainerListener;
+import org.apache.aries.cdi.container.internal.util.Logs;
+import org.apache.aries.cdi.spi.CDIContainerInitializer;
+import org.apache.aries.cdi.spi.loader.SpiLoader;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceObjects;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cdi.runtime.dto.ComponentDTO;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class ContainerListenerTest extends BaseCDIBundleTest {
+    private enum State {
+        BEFORE_START, STARTED, STOPPED
+    }
+
+    @Test
+    public void ensureListenerIsCalledForSuccesses() throws Exception {
+        final EmptyCdiContainer cdiContainer = new EmptyCdiContainer();
+        doRun(cdiContainer, (containerBootstrap, listenerCalls) -> {
+            assertTrue(listenerCalls.isEmpty());
+            assertEquals(State.BEFORE_START, cdiContainer.state);
+            containerBootstrap.open();
+            assertEquals(singletonList("onStartSuccess"), listenerCalls);
+            assertEquals(State.STARTED, cdiContainer.state);
+            assertTrue(containerBootstrap.close());
+            assertEquals(asList("onStartSuccess", "onStopSuccess"), listenerCalls);
+            assertEquals(State.STOPPED, cdiContainer.state);
+        });
+    }
+
+    @Test
+    public void ensureListenerIsCalledForFailedStop() throws Exception {
+        final EmptyCdiContainer cdiContainer = new EmptyCdiContainer();
+        cdiContainer.failAtState = State.STOPPED;
+        doRun(cdiContainer, (containerBootstrap, listenerCalls) -> {
+            assertTrue(listenerCalls.isEmpty());
+            assertEquals(State.BEFORE_START, cdiContainer.state);
+            containerBootstrap.open();
+            assertEquals(singletonList("onStartSuccess"), listenerCalls);
+            assertEquals(State.STARTED, cdiContainer.state);
+            assertFalse(containerBootstrap.close());
+            assertEquals(asList("onStartSuccess", "onStopError"), listenerCalls);
+        });
+    }
+
+    @Test
+    public void ensureListenerIsCalledForFailedStartup() throws Exception {
+        final EmptyCdiContainer cdiContainer = new EmptyCdiContainer();
+        cdiContainer.failAtState = State.STARTED;
+        doRun(cdiContainer, (containerBootstrap, listenerCalls) -> {
+            assertTrue(listenerCalls.isEmpty());
+            assertEquals(State.BEFORE_START, cdiContainer.state);
+            try {
+                containerBootstrap.open();
+                fail();
+            } catch (final RuntimeException re) {
+                // expected
+            }
+            assertEquals(singletonList("onStartError"), listenerCalls);
+        });
+    }
+
+    private void doRun(EmptyCdiContainer cdiContainer, final BiConsumer<ContainerBootstrap, List<String>> test) throws Exception {
+        ContainerState containerState = new ContainerState(
+                bundle, ccrBundle, ccrChangeCount, promiseFactory, TestUtil.mockCaSt(bundle),
+                new Logs.Builder(bundle.getBundleContext()).build());
+
+        // ensure it starts, this is not really used in this test but required due to current open() impl
+        ComponentDTO componentDTO = new ComponentDTO();
+        componentDTO.enabled = false;
+        containerState.containerDTO().components.add(componentDTO);
+
+        List<String> listenerCalls = new ArrayList<>();
+        ContainerListener listenerSpy = ContainerListener.class.cast(Proxy.newProxyInstance(
+                Thread.currentThread().getContextClassLoader(),
+                new Class<?>[]{ContainerListener.class},
+                (proxy, method, args) -> {
+                    listenerCalls.add(method.getName());
+                    return null; // all methods return void
+                }));
+
+        final MockServiceRegistration<ContainerListener> initializer = new MockServiceRegistration<>(
+                new MockServiceReference<>(bundle, listenerSpy, new String[]{CDIContainerInitializer.class.getName()}),
+                TestUtil.serviceRegistrations,
+                TestUtil.serviceListeners);
+        final MockServiceRegistration<ContainerListener> listener = new MockServiceRegistration<>(
+                new MockServiceReference<>(bundle, listenerSpy, new String[]{ContainerListener.class.getName()}),
+                TestUtil.serviceRegistrations,
+                TestUtil.serviceListeners);
+        ContainerBootstrap containerBootstrap = new ContainerBootstrap(
+                containerState,
+                new ServiceTracker<CDIContainerInitializer, ServiceObjects<CDIContainerInitializer>>(
+                        bundle.getBundleContext(), CDIContainerInitializer.class, null) {
+                    @Override
+                    public ServiceObjects<CDIContainerInitializer> getService() {
+                        return cdiContainer;
+                    }
+                },
+                new ConfigurationListener.Builder(containerState),
+                new SingleComponent.Builder(containerState, null),
+                new FactoryComponent.Builder(containerState, null),
+                new ServiceTracker<ContainerListener, ContainerListener>(bundle.getBundleContext(), ContainerListener.class, null) {
+                    @Override
+                    public ServiceReference<ContainerListener>[] getServiceReferences() {
+                        return new ServiceReference[] { listener.getReference() };
+                    }
+                });
+
+        try {
+            test.accept(containerBootstrap, listenerCalls);
+        } finally {
+            Stream.of(initializer, listener).forEach(TestUtil.serviceRegistrations::remove);
+        }
+    }
+
+    private static class EmptyCdiContainer implements ServiceObjects<CDIContainerInitializer> {
+        private State state = State.BEFORE_START;
+        private State failAtState;
+
+        @Override
+        public CDIContainerInitializer getService() {
+            return new CDIContainerInitializer() {
+                @Override
+                public CDIContainerInitializer addBeanClasses(Class<?>... classes) {
+                    return this;
+                }
+
+                @Override
+                public CDIContainerInitializer addBeanXmls(URL... beanXmls) {
+                    return this;
+                }
+
+                @Override
+                public CDIContainerInitializer addExtension(Extension extension, Map<String, Object> properties) {
+                    return this;
+                }
+
+                @Override
+                public CDIContainerInitializer addProperty(String key, Object value) {
+                    return this;
+                }
+
+                @Override
+                public CDIContainerInitializer setClassLoader(SpiLoader spiLoader) {
+                    return this;
+                }
+
+                @Override
+                public CDIContainerInitializer setBundleContext(BundleContext bundleContext) {
+                    return this;
+                }
+
+                @Override
+                public AutoCloseable initialize() {
+                    state = State.STARTED;
+                    if (failAtState != null && failAtState == State.STARTED) {
+                        throw new RuntimeException("failed for test");
+                    }
+                    return () -> {
+                        if (failAtState != null && failAtState == State.STOPPED) {
+                            throw new RuntimeException("failed for test");
+                        }
+                        state = State.STOPPED;
+                    };
+                }
+            };
+        }
+
+        @Override
+        public void ungetService(final CDIContainerInitializer service) {
+            // no-op
+        }
+
+        @Override
+        public ServiceReference<CDIContainerInitializer> getServiceReference() {
+            return null;
+        }
+    }
+}