Author: pderop
Date: Wed Nov 9 22:14:45 2016
New Revision: 1769022
URL: http://svn.apache.org/viewvc?rev=1769022&view=rev
Log:
FELIX-5403: Improve the Javadoc for org.apache.felix.dm.ComponentStateListener.
Re added old starting/started/stopping/stopped method callbacks from dm3.
The callbacks are declared as java8 empty default methods, in order to keep
compatibility with current dm4 listeners.
Added:
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ComponentStateListenerTest.java
Modified:
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentStateListener.java
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
Added:
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ComponentStateListenerTest.java
URL:
http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ComponentStateListenerTest.java?rev=1769022&view=auto
==============================================================================
---
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ComponentStateListenerTest.java
(added)
+++
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ComponentStateListenerTest.java
Wed Nov 9 22:14:45 2016
@@ -0,0 +1,340 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.felix.dm.itest.api;
+
+import org.junit.Assert;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentState;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+
+/**
+ * @author <a href="mailto:[email protected]">Felix Project Team</a>
+ */
+public class ComponentStateListenerTest extends TestBase {
+
+ public void testComponentLifeCycleCallbacks() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a simple service component
+ Component s = m.createComponent().setImplementation(new
ComponentInstance(e));
+ // add it, and since it has no dependencies, it should be activated
immediately
+ m.add(s);
+ // remove it so it gets destroyed
+ m.remove(s);
+ // ensure we executed all steps inside the component instance
+ e.step(6);
+ }
+
+ static class ComponentInstance {
+ private final Ensure m_ensure;
+ public ComponentInstance(Ensure e) {
+ m_ensure = e;
+ m_ensure.step(1);
+ }
+ public void init() {
+ m_ensure.step(2);
+ }
+ public void start() {
+ m_ensure.step(3);
+ }
+ public void stop() {
+ m_ensure.step(4);
+ }
+ public void destroy() {
+ m_ensure.step(5);
+ }
+ }
+
+ public void testCustomComponentLifeCycleCallbacks() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a simple service component
+ Component s = m.createComponent() .setImplementation(new
CustomComponentInstance(e)).setCallbacks("a", "b", "c", "d");
+ // add it, and since it has no dependencies, it should be activated
immediately
+ m.add(s);
+ // remove it so it gets destroyed
+ m.remove(s);
+ // ensure we executed all steps inside the component instance
+ e.step(6);
+ }
+
+ static class CustomComponentInstance {
+ private final Ensure m_ensure;
+ public CustomComponentInstance(Ensure e) {
+ m_ensure = e;
+ m_ensure.step(1);
+ }
+ public void a() {
+ m_ensure.step(2);
+ }
+ public void b() {
+ m_ensure.step(3);
+ }
+ public void c() {
+ m_ensure.step(4);
+ }
+ public void d() {
+ m_ensure.step(5);
+ }
+ }
+
+ public void testComponentStateListingLifeCycle() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a simple service component
+ ComponentStateListeningInstance implementation = new
ComponentStateListeningInstance(e);
+ Component s =
m.createComponent().setInterface(MyInterface.class.getName(),
null).setImplementation(implementation);
+ // add the state listener
+ s.add(implementation);
+ // add it, and since it has no dependencies, it should be activated
immediately
+ m.add(s);
+ // remove it so it gets destroyed
+ m.remove(s);
+ // remove the state listener
+ s.remove(implementation);
+ // ensure we executed all steps inside the component instance
+ e.step(10);
+ }
+
+ public static interface MyInterface {}
+
+ static class ComponentStateListeningInstance implements MyInterface,
ComponentStateListener {
+ volatile ServiceRegistration m_registration;
+ private final Ensure m_ensure;
+
+ public ComponentStateListeningInstance(Ensure e) {
+ m_ensure = e;
+ m_ensure.step(1);
+ }
+
+ private void debug() {
+ StackTraceElement[] stackTrace =
Thread.currentThread().getStackTrace();
+ System.out.println("AT: " + stackTrace[2].getClassName() + "." +
stackTrace[2].getMethodName() + "():" + stackTrace[2].getLineNumber());
+ }
+
+ public void init(Component c) {
+ debug();
+ m_ensure.step(2);
+ }
+
+ public void start(Component c) {
+ debug();
+ m_ensure.step(4);
+ }
+ public void stop(Component c) {
+ debug();
+ m_ensure.step(7);
+ }
+
+ public void destroy(Component c) {
+ debug();
+ m_ensure.step(9);
+ }
+
+ public void starting(Component component) {
+ debug();
+ m_ensure.step(3);
+ }
+
+ public void started(Component component) {
+ debug();
+ m_ensure.step(5);
+ ServiceReference reference = m_registration.getReference();
+ Assert.assertNotNull("Service not yet registered.", reference);
+ }
+
+ public void stopping(Component component) {
+ debug();
+ m_ensure.step(6);
+ }
+
+ public void stopped(Component component) {
+ debug();
+ m_ensure.step(8);
+ }
+
+ @Override
+ public void changed(Component c, ComponentState state) {
+ }
+ }
+
+ public void testDynamicComponentStateListingLifeCycle() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a simple service component
+ Component s =
m.createComponent().setInterface(MyInterface.class.getName(),
null).setImplementation(new DynamicComponentStateListeningInstance(e));
+ // add it, and since it has no dependencies, it should be activated
immediately
+ m.add(s);
+ // remove it so it gets destroyed
+ m.remove(s);
+ // ensure we executed all steps inside the component instance
+ e.step(10);
+ }
+
+ static class DynamicComponentStateListeningInstance implements
MyInterface, ComponentStateListener {
+ volatile ServiceRegistration m_registration;
+ private final Ensure m_ensure;
+
+ public DynamicComponentStateListeningInstance(Ensure e) {
+ m_ensure = e;
+ m_ensure.step(1);
+ }
+
+ private void debug() {
+ StackTraceElement[] stackTrace =
Thread.currentThread().getStackTrace();
+ System.out.println("AT: " + stackTrace[2].getClassName() + "." +
stackTrace[2].getMethodName() + "():" + stackTrace[2].getLineNumber());
+ }
+
+ public void init(Component c) {
+ debug();
+ m_ensure.step(2);
+ c.add(this);
+ }
+
+ public void start(Component c) {
+ debug();
+ m_ensure.step(4);
+ }
+ public void stop(Component c) {
+ debug();
+ m_ensure.step(7);
+ }
+
+ public void destroy(Component c) {
+ debug();
+ m_ensure.step(9);
+ c.remove(this);
+ }
+
+ public void starting(Component component) {
+ debug();
+ m_ensure.step(3);
+ }
+
+ public void started(Component component) {
+ debug();
+ m_ensure.step(5);
+ ServiceReference reference = m_registration.getReference();
+ Assert.assertNotNull("Service not yet registered.", reference);
+ }
+
+ public void stopping(Component component) {
+ debug();
+ m_ensure.step(6);
+ }
+
+ public void stopped(Component component) {
+ debug();
+ m_ensure.step(8);
+ }
+
+ @Override
+ public void changed(Component c, ComponentState state) {
+ // TODO Auto-generated method stub
+
+ }
+ }
+
+ public void testDynamicComponentStateListingLifeCycle2() {
+ DependencyManager m = getDM();
+ // helper class that ensures certain steps get executed in sequence
+ Ensure e = new Ensure();
+ // create a simple service component
+ Component s =
m.createComponent().setInterface(MyInterface.class.getName(),
null).setImplementation(new DynamicComponentStateListeningInstance2(e));
+ // add it, and since it has no dependencies, it should be activated
immediately
+ m.add(s);
+ // remove it so it gets destroyed
+ m.remove(s);
+ // ensure we executed all steps inside the component instance
+ e.step(10);
+ }
+
+ static class DynamicComponentStateListeningInstance2 implements
MyInterface, ComponentStateListener {
+ volatile ServiceRegistration m_registration;
+ private final Ensure m_ensure;
+
+ public DynamicComponentStateListeningInstance2(Ensure e) {
+ m_ensure = e;
+ m_ensure.step(1);
+ }
+
+ private void debug() {
+ StackTraceElement[] stackTrace =
Thread.currentThread().getStackTrace();
+ System.out.println("AT: " + stackTrace[2].getClassName() + "." +
stackTrace[2].getMethodName() + "():" + stackTrace[2].getLineNumber());
+ }
+
+ public void init(Component c) {
+ debug();
+ m_ensure.step(2);
+ }
+
+ public void start(Component c) {
+ debug();
+ m_ensure.step(3);
+ c.add(this);
+ }
+ public void stop(Component c) {
+ debug();
+ m_ensure.step(7);
+ c.remove(this);
+ }
+
+ public void destroy(Component c) {
+ debug();
+ m_ensure.step(9);
+ }
+
+ public void starting(Component component) {
+ debug();
+ m_ensure.step(4);
+ }
+
+ public void started(Component component) {
+ debug();
+ m_ensure.step(5);
+ ServiceReference reference = m_registration.getReference();
+ Assert.assertNotNull("Service not yet registered.", reference);
+ }
+
+ public void stopping(Component component) {
+ debug();
+ m_ensure.step(6);
+ }
+
+ public void stopped(Component component) {
+ debug();
+ m_ensure.step(8);
+ }
+
+ @Override
+ public void changed(Component c, ComponentState state) {
+ // TODO Auto-generated method stub
+
+ }
+ }
+}
\ No newline at end of file
Modified:
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentStateListener.java
URL:
http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentStateListener.java?rev=1769022&r1=1769021&r2=1769022&view=diff
==============================================================================
---
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentStateListener.java
(original)
+++
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentStateListener.java
Wed Nov 9 22:14:45 2016
@@ -28,4 +28,37 @@ package org.apache.felix.dm;
*/
public interface ComponentStateListener {
public void changed(Component c, ComponentState state);
+
+ /**
+ * Called when the component is starting. At this point, the required
+ * dependencies have been injected, but the service has not been registered
+ * yet.
+ *
+ * @param component the component
+ */
+ public default void starting(Component component) {}
+
+ /**
+ * Called when the component is started. At this point, the component has
been
+ * registered.
+ *
+ * @param component the component
+ */
+ public default void started(Component component) {}
+
+ /**
+ * Called when the component is stopping. At this point, the component is
still
+ * registered.
+ *
+ * @param component the component
+ */
+ public default void stopping(Component component) {}
+
+ /**
+ * Called when the component is stopped. At this point, the component has
been
+ * unregistered.
+ *
+ * @param component the component
+ */
+ public default void stopped(Component component) {}
}
Modified:
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
URL:
http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java?rev=1769022&r1=1769021&r2=1769022&view=diff
==============================================================================
---
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
(original)
+++
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
Wed Nov 9 22:14:45 2016
@@ -43,6 +43,7 @@ import java.util.concurrent.CopyOnWriteA
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiConsumer;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.ComponentDeclaration;
@@ -276,6 +277,24 @@ public class ComponentImpl implements Co
*/
private boolean m_startCalled;
+ // Used to track what state listener callback we have already called
+ private int m_stateListener = LISTENER_IDLE;
+
+ // We have not yet called any state listener callbacks
+ private final static int LISTENER_IDLE = 0;
+
+ // We have already called the "starting" state listener callback.
+ private final static int LISTENER_STARTING = 1;
+
+ // We have already called the "started" state listener callback.
+ private final static int LISTENER_STARTED = 2;
+
+ // We have already called the "stopping" state listener callback.
+ private final static int LISTENER_STOPPING = 3;
+
+ // We have already called the "stopped" state listener callback.
+ private final static int LISTENER_STOPPED = 4;
+
/**
* Default component declaration implementation.
*/
@@ -577,14 +596,51 @@ public class ComponentImpl implements Co
}
@Override
- public Component add(final ComponentStateListener l) {
- m_listeners.add(l);
+ public Component add(ComponentStateListener listener) {
+ getExecutor().execute(() -> {
+ m_listeners.add(listener);
+ switch (m_stateListener) {
+ case LISTENER_STARTING:
+ // this new listener missed the starting cb
+ listener.starting(ComponentImpl.this);
+ break;
+ case LISTENER_STARTED:
+ // this new listener missed the starting/started cb
+ listener.starting(ComponentImpl.this);
+ listener.started(ComponentImpl.this);
+ break;
+ case LISTENER_STOPPING:
+ // this new listener missed the starting/started/stopping cb
+ listener.starting(ComponentImpl.this);
+ listener.started(ComponentImpl.this);
+ listener.stopping(ComponentImpl.this);
+ break;
+ case LISTENER_STOPPED:
+ // no need to call missed listener callbacks
+ break;
+ }
+ });
return this;
}
@Override
- public Component remove(ComponentStateListener l) {
- m_listeners.remove(l);
+ public Component remove(ComponentStateListener listener) {
+ getExecutor().execute(() -> {
+ switch (m_stateListener) {
+ case LISTENER_STARTING:
+ // The listener has been previously called in starting cb;
+ // so we should call the listener started cb, before
unregistering it.
+ listener.started(ComponentImpl.this);
+ break;
+
+ case LISTENER_STOPPING:
+ // The listener has been previously called in stopping cb;
+ // so we should call the listener stopped cb, before
unregistering it.
+ listener.stopped(ComponentImpl.this);
+ break;
+ }
+ m_listeners.remove(listener);
+ });
return this;
}
@@ -993,18 +1049,20 @@ public class ComponentImpl implements Co
if (oldState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED
&& newState == ComponentState.TRACKING_OPTIONAL) {
invokeAutoConfigInstanceBoundDependencies();
invokeAddRequiredInstanceBoundDependencies();
+ notifyListeners(ComponentStateListener::starting,
LISTENER_STARTING);
invokeStart();
invokeAddOptionalDependencies();
registerService();
- notifyListeners(newState);
+ notifyListeners(newState, ComponentStateListener::started,
LISTENER_STARTED);
return true;
}
if (oldState == ComponentState.TRACKING_OPTIONAL && newState ==
ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
- unregisterService();
+ notifyListeners(ComponentStateListener::stopping,
LISTENER_STOPPING);
+ unregisterService();
invokeRemoveOptionalDependencies();
invokeStop();
invokeRemoveInstanceBoundDependencies();
- notifyListeners(newState);
+ notifyListeners(newState, ComponentStateListener::stopped,
LISTENER_STOPPED);
return true;
}
if (oldState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED
&& newState == ComponentState.WAITING_FOR_REQUIRED) {
@@ -1512,7 +1570,38 @@ public class ComponentImpl implements Co
private void notifyListeners(ComponentState state) {
for (ComponentStateListener l : m_listeners) {
- l.changed(this, state);
+ try {
+ l.changed(this, state);
+ } catch (Exception e) {
+ m_logger.log(Logger.LOG_ERROR, "Exception caught while
invoking component state listener", e);
+ }
+ }
+ }
+
+ private void notifyListeners(ComponentState state,
BiConsumer<ComponentStateListener, Component> listenerCallback, int
stateListener) {
+ m_stateListener = stateListener;
+ for (ComponentStateListener l : m_listeners) {
+ try {
+ l.changed(this, state);
+ } catch (Exception e) {
+ m_logger.log(Logger.LOG_ERROR, "Exception caught while
invoking component state listener", e);
+ }
+ try {
+ listenerCallback.accept(l, this);
+ } catch (Exception e) {
+ m_logger.log(Logger.LOG_ERROR, "Exception caught while
invoking component state listener", e);
+ }
+ }
+ }
+
+ private void notifyListeners(BiConsumer<ComponentStateListener,
Component> listenerCallback, int stateListener) {
+ m_stateListener = stateListener;
+ for (ComponentStateListener l : m_listeners) {
+ try {
+ listenerCallback.accept(l, this);
+ } catch (Exception e) {
+ m_logger.log(Logger.LOG_ERROR, "Exception caught while
invoking component state listener", e);
+ }
}
}