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;
+ }
+ }
+}