You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by pd...@apache.org on 2015/06/04 12:15:36 UTC
svn commit: r1683504 - in /felix/trunk/dependencymanager:
org.apache.felix.dependencymanager.annotation/
org.apache.felix.dependencymanager.itest/
org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/
org.apache.felix.dependencyma...
Author: pderop
Date: Thu Jun 4 10:15:35 2015
New Revision: 1683504
URL: http://svn.apache.org/r1683504
Log:
FELIX-4907: ConfigurationDependency calls updated(null) when component is stopped.
FELIX-4910: ComponentExecutorFactory does not allow to return null from getExecutorFor method.
FELIX-4913: DM Optional callbacks may sometimes be invoked twice.
FELIX-4876: DM Annotations bnd plugin compatibility with Bndtools 2.4.1 / 3.0.0 versions.
[FELIX-4877: DM Annotations should detect service type using more method signatures.
Modified:
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.annotation/bnd.bnd
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/bnd.bnd
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.samples/bnd.bnd
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentScheduler.java
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/ServiceRaceTest.java
Modified: felix/trunk/dependencymanager/org.apache.felix.dependencymanager.annotation/bnd.bnd
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager.annotation/bnd.bnd?rev=1683504&r1=1683503&r2=1683504&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager.annotation/bnd.bnd (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager.annotation/bnd.bnd Thu Jun 4 10:15:35 2015
@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-Bundle-Version: 4.0.1
+Bundle-Version: 4.0.2
-buildpath: \
osgi.core;version=4.2,\
de.twentyeleven.skysail.org.json-osgi;version=20080701.0,\
Modified: felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/bnd.bnd
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/bnd.bnd?rev=1683504&r1=1683503&r2=1683504&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/bnd.bnd (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/bnd.bnd Thu Jun 4 10:15:35 2015
@@ -18,7 +18,7 @@
org.apache.felix.metatype;version=1.0.4,\
org.apache.felix.gogo.runtime;version=0.10.0,\
org.apache.felix.log;version=1.0.1,\
- org.apache.felix.configadmin;version=1.8.4,\
+ org.apache.felix.configadmin;version=1.8.6,\
org.apache.felix.dependencymanager;version=latest,\
org.apache.felix.dependencymanager.shell;version=latest
-runee: JavaSE-1.7
Modified: felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java?rev=1683504&r1=1683503&r2=1683504&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java Thu Jun 4 10:15:35 2015
@@ -221,11 +221,9 @@ public class ServiceRaceTest extends Tes
// Ensure that client has been stopped, then destroyed, then unbound from all dependencies
expectedStep += 2; // stop/destroy
expectedStep += DEPENDENCIES; // removed all dependencies
- expectedStep += 1; // removed configuration
step.waitForStep(expectedStep, STEP_WAIT);
step.ensure();
Assert.assertEquals(0, clientImpl.getDependencies());
- Assert.assertNull(clientImpl.getConfiguration());
if (super.errorsLogged()) {
throw new IllegalStateException("Race test interrupted (some error occured, see previous logs)");
@@ -258,29 +256,27 @@ public class ServiceRaceTest extends Tes
}
public void updated(Dictionary conf) throws ConfigurationException {
- m_conf = conf;
- try {
- if (conf != null) {
+ if (conf != null) {
+ try {
Assert.assertEquals("bar", conf.get("foo"));
+ m_conf = conf;
m_step.step(1);
- } else {
- m_step.step();
+ } catch (Throwable t) {
+ m_step.throwable(t);
}
- } catch (Throwable t) {
- m_step.throwable(t);
}
}
void add(Dep d) {
Assert.assertNotNull(d);
- m_step.step();
m_dependencies ++;
+ m_step.step();
}
void remove(Dep d) {
Assert.assertNotNull(d);
- m_step.step();
m_dependencies --;
+ m_step.step();
}
void start() {
Modified: felix/trunk/dependencymanager/org.apache.felix.dependencymanager.samples/bnd.bnd
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager.samples/bnd.bnd?rev=1683504&r1=1683503&r2=1683504&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager.samples/bnd.bnd (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager.samples/bnd.bnd Thu Jun 4 10:15:35 2015
@@ -34,7 +34,7 @@ Bundle-Version: 1.0.0
org.apache.felix.gogo.shell;version=0.10.0,\
org.apache.felix.dependencymanager.shell;version=latest,\
org.apache.felix.dependencymanager.runtime;version=latest,\
- org.apache.felix.configadmin;version=1.8.0,\
+ org.apache.felix.configadmin;version=1.8.6,\
org.apache.felix.eventadmin;version=1.4.3,\
biz.aQute.bndlib;version=2.3.0,\
org.apache.felix.webconsole;version=4.2.2,\
Modified: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd?rev=1683504&r1=1683503&r2=1683504&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd Thu Jun 4 10:15:35 2015
@@ -35,7 +35,7 @@ Include-Resource: META-INF/=resources/LI
META-INF/=resources/changelog.txt
Import-Package: !org.junit,!org.mockito.*,*
Bundle-Activator: org.apache.felix.dm.impl.Activator
-Bundle-Version: 4.1.0
+Bundle-Version: 4.1.1
Bundle-Name: Apache Felix Dependency Manager
Bundle-Description: Provides dynamic service and component dependency management
Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt
Modified: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java?rev=1683504&r1=1683503&r2=1683504&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java Thu Jun 4 10:15:35 2015
@@ -23,15 +23,24 @@ package org.apache.felix.dm;
* it, specify a PID for the configuration. The dependency is always required, because if it is
* not, it does not make sense to use the dependency manager. In that scenario, simply register
* your component as a <code>ManagedService(Factory)</code> and handle everything yourself. Also,
- * only managed services are supported, not factories. There are a couple of things you need to
- * be aware of when implementing the <code>updated(Dictionary)</code> method:
+ * only managed services are supported, not factories. If you need support for factories, then
+ * you can use
+ * {@link DependencyManager#createFactoryConfigurationAdapterService(String, String, boolean)}.
+ * There are a couple of things you need to be aware of when implementing the
+ * <code>updated(Dictionary)</code> method:
* <ul>
- * <li>Make sure it throws a <code>ConfigurationException</code> when you get a configuration
- * that is invalid. In this case, the dependency will not change: if it was not available, it
- * will still not be. If it was available, it will remain available and implicitly assume you
- * keep working with your old configuration.</li>
+ * <li>Make sure it throws a <code>ConfigurationException</code> or any other exception when you
+ * get a configuration that is invalid. In this case, the dependency will not change:
+ * if it was not available, it will still not be. If it was available, it will remain available
+ * and implicitly assume you keep working with your old configuration.</li>
* <li>This method will be called before all required dependencies are available. Make sure you
* do not depend on these to parse your settings.</li>
+ * <li>unlike all other DM dependency callbacks, the update method is called from the CM configuration
+ * update thread, and is not serialized with the internal queue maintained by the DM component.
+ * So, take care to concurrent calls between updated callback and your other lifecycle callbacks.
+ * <li>When the configuration is lost, updated callback is invoked with a null dictionary parameter,
+ * and then the component stop lifecycle callback is invoked.
+ * <li>When the DM component is stopped, then updated(null) is not invoked.
* </ul>
*
* The callback invoked when a configuration dependency is updated can supports the following signatures:<p>
@@ -46,6 +55,7 @@ public interface ConfigurationDependency
* Sets the name of the callback method that should be invoked when a configuration
* is available. The contract for this method is identical to that of
* <code>ManagedService.updated(Dictionary) throws ConfigurationException</code>.
+ * By default, if this method is not called, the callback name is "updated".
*
* @param callback the name of the callback method
*/
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=1683504&r1=1683503&r2=1683504&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 Thu Jun 4 10:15:35 2015
@@ -33,6 +33,7 @@ import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -46,6 +47,7 @@ import java.util.concurrent.atomic.Atomi
import org.apache.felix.dm.Component;
import org.apache.felix.dm.ComponentDeclaration;
import org.apache.felix.dm.ComponentDependencyDeclaration;
+import org.apache.felix.dm.ComponentExecutorFactory;
import org.apache.felix.dm.ComponentState;
import org.apache.felix.dm.ComponentStateListener;
import org.apache.felix.dm.Dependency;
@@ -53,8 +55,8 @@ import org.apache.felix.dm.DependencyMan
import org.apache.felix.dm.Logger;
import org.apache.felix.dm.context.ComponentContext;
import org.apache.felix.dm.context.DependencyContext;
-import org.apache.felix.dm.context.EventType;
import org.apache.felix.dm.context.Event;
+import org.apache.felix.dm.context.EventType;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
@@ -66,57 +68,211 @@ import org.osgi.service.log.LogService;
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class ComponentImpl implements Component, ComponentContext, ComponentDeclaration {
+ /**
+ * NullObject ServiceRegistration that is injected in components that don't provide any services.
+ */
private static final ServiceRegistration NULL_REGISTRATION = (ServiceRegistration) Proxy
.newProxyInstance(ComponentImpl.class.getClassLoader(),
new Class[] { ServiceRegistration.class },
new DefaultNullObject());
+
+ /**
+ * Constant Used to get empty constructor by reflection.
+ */
private static final Class<?>[] VOID = new Class[] {};
+
+ /**
+ * Default Component Executor, which is by default single threaded. The first thread which schedules a task
+ * is the master thread and will execute all tasks that are scheduled by other threads at the time the master
+ * thread is executing. Internal tasks scheduled by the master thread are executed immediately (inline execution).
+ *
+ * If a ComponentExecutorFactory is provided in the OSGI registry, then this executor will be replaced by the
+ * executor returned by the ComponentExecutorFactory (however, the same semantic of the default executor is used:
+ * all tasks are serially executed).
+ *
+ * @see @link {@link ComponentExecutorFactory}
+ */
private volatile Executor m_executor = new SerialExecutor(new Logger(null));
+
+ /**
+ * The current state of the component state machine.
+ */
private ComponentState m_state = ComponentState.INACTIVE;
+
+ /**
+ * Indicates that the handleChange method is currently being executed.
+ */
+ private boolean m_handlingChange;
+
+ /**
+ * List of dependencies. We use a COW list in order to avoid ConcurrentModificationException while iterating on the
+ * list and while a component synchronously add more dependencies from one of its callback method.
+ */
private final CopyOnWriteArrayList<DependencyContext> m_dependencies = new CopyOnWriteArrayList<>();
+
+ /**
+ * List of Component state listeners. We use a COW list in order to avoid ConcurrentModificationException while iterating on the
+ * list and while a component synchronously add more listeners from one of its callback method.
+ */
private final List<ComponentStateListener> m_listeners = new CopyOnWriteArrayList<>();
+
+ /**
+ * Is the component active ?
+ */
private boolean m_isStarted;
+
+ /**
+ * The Component logger.
+ */
private final Logger m_logger;
+
+ /**
+ * The Component bundle context.
+ */
private final BundleContext m_context;
+
+ /**
+ * The DependencyManager object that has created this component.
+ */
private final DependencyManager m_manager;
+
+ /**
+ * The object used to create the component. Can be a class name, or the component implementation instance.
+ */
private Object m_componentDefinition;
+
+ /**
+ * The component instance.
+ */
private Object m_componentInstance;
+
+ /**
+ * The service(s) provided by this component. Can be a String, or a String array.
+ */
private volatile Object m_serviceName;
+
+ /**
+ * The service properties, if this component is providing a service.
+ */
private volatile Dictionary<Object, Object> m_serviceProperties;
+
+ /**
+ * The component service registration. Can be a NullObject in case the component does not provide a service.
+ */
private volatile ServiceRegistration m_registration;
+
+ /**
+ * Map of auto configured fields (BundleContext, ServiceRegistration, DependencyManager, or Component).
+ * By default, all fields mentioned above are auto configured (injected in class fields having the same type).
+ */
private final Map<Class<?>, Boolean> m_autoConfig = new ConcurrentHashMap<>();
+
+ /**
+ * Map of auto configured instance fields that will be used when injected auto configured fields.
+ * @see #m_autoConfig
+ */
private final Map<Class<?>, String> m_autoConfigInstance = new ConcurrentHashMap<>();
+
+ /**
+ * Data structure used to record the elapsed time used by component lifecycle callbacks.
+ * Key = callback name ("init", "start", "stop", "destroy").
+ * Value = elapsed time in nanos.
+ */
private final Map<String, Long> m_stopwatch = new ConcurrentHashMap<>();
+
+ /**
+ * Unique component id.
+ */
private final long m_id;
- private static AtomicLong m_idGenerator = new AtomicLong();
- // Holds all the services of a given dependency context. Caution: the last entry in the skiplist is the highest ranked service.
+
+ /**
+ * Unique ID generator.
+ */
+ private final static AtomicLong m_idGenerator = new AtomicLong();
+
+ /**
+ * Holds all the services of a given dependency context. Caution: the last entry in the skiplist is the highest
+ * ranked service.
+ */
private final Map<DependencyContext, ConcurrentSkipListSet<Event>> m_dependencyEvents = new HashMap<>();
+
+ /**
+ * Flag used to check if this component has been added in a DependencyManager object.
+ */
private final AtomicBoolean m_active = new AtomicBoolean(false);
- public Component setDebug(String debugKey) {
- // Force debug level in our logger
- m_logger.setEnabledLevel(LogService.LOG_DEBUG);
- m_logger.setDebugKey(debugKey);
- return this;
- }
-
- // configuration (static)
+ /**
+ * Init lifecycle callback. From that method, component are expected to add more extra dependencies.
+ * When this callback is invoked, all required dependencies have been injected.
+ */
private volatile String m_callbackInit;
+
+ /**
+ * Start lifecycle callback. When this method is called, all required + all extra required dependencies defined in the
+ * init callback have been injected. The component may then perform its initialization.
+ */
private volatile String m_callbackStart;
+
+ /**
+ * Stop callback. When this method is called, the component has been unregistered (if it provides any services),
+ * and all optional dependencies have been unbound.
+ */
private volatile String m_callbackStop;
+
+ /**
+ * Destroy callback. When this method is called, all required dependencies defined in the init method have been unbound.
+ * After this method is called, then all required dependencies defined in the Activator will be unbound.
+ */
private volatile String m_callbackDestroy;
+
+ /**
+ * By default, the init/start/stop/destroy callbacks are invoked on the component instance(s).
+ * But you can specify a separate callback instance.
+ */
private volatile Object m_callbackInstance;
- // instance factory
+ /**
+ * Component Factory instance object, that can be used to instantiate the component instance.
+ */
private volatile Object m_instanceFactory;
+
+ /**
+ * Name of the Factory method to call.
+ */
private volatile String m_instanceFactoryCreateMethod;
- // composition manager
+ /**
+ * Composition Manager that can be used to create a graph of objects that are used to implement the component.
+ */
private volatile Object m_compositionManager;
+
+ /**
+ * Name of the method used to invoke in order to get the list of component instance objects.
+ */
private volatile String m_compositionManagerGetMethod;
+
+ /**
+ * The composition manager instance object, if specified.
+ */
private volatile Object m_compositionManagerInstance;
+
+ /**
+ * The Component bundle.
+ */
private final Bundle m_bundle;
+
+ /**
+ * Cache of callback invocation used to avoid calling the same callback twice.
+ * This situation may sometimes happen when the state machine triggers a lifecycle callback ("bind" call), and
+ * when the bind method registers a service which is tracked by another optional component dependency.
+ *
+ * @see org.apache.felix.dm.itest.api.FELIX4913_OptionalCallbackInvokedTwiceTest which reproduces the use case.
+ */
+ private final Map<Event, Event> m_invokeCallbackCache = new IdentityHashMap<>();
+ /**
+ * Default component declaration implementation.
+ */
static class SCDImpl implements ComponentDependencyDeclaration {
private final String m_name;
private final int m_state;
@@ -149,10 +305,19 @@ public class ComponentImpl implements Co
}
}
+ /**
+ * Constructor. Only used for tests.
+ */
public ComponentImpl() {
this(null, null, new Logger(null));
}
+ /**
+ * Constructor
+ * @param context the component bundle context
+ * @param manager the manager used to create the component
+ * @param logger the logger to use
+ */
public ComponentImpl(BundleContext context, DependencyManager manager, Logger logger) {
m_context = context;
m_bundle = context != null ? context.getBundle() : null;
@@ -169,6 +334,14 @@ public class ComponentImpl implements Co
m_id = m_idGenerator.getAndIncrement();
}
+ @Override
+ public Component setDebug(String debugKey) {
+ // Force debug level in our logger
+ m_logger.setEnabledLevel(LogService.LOG_DEBUG);
+ m_logger.setDebugKey(debugKey);
+ return this;
+ }
+
@Override
public Component add(final Dependency ... dependencies) {
getExecutor().execute(new Runnable() {
@@ -218,6 +391,7 @@ public class ComponentImpl implements Co
return this;
}
+ @Override
public void start() {
if (m_active.compareAndSet(false, true)) {
getExecutor().execute(new Runnable() {
@@ -230,6 +404,7 @@ public class ComponentImpl implements Co
}
}
+ @Override
public void stop() {
if (m_active.compareAndSet(true, false)) {
Executor executor = getExecutor();
@@ -284,19 +459,25 @@ public class ComponentImpl implements Co
getExecutor().execute(new Runnable() {
@Override
public void run() {
- switch (type) {
- case ADDED:
- handleAdded(dc, event[0]);
- break;
- case CHANGED:
- handleChanged(dc, event[0]);
- break;
- case REMOVED:
- handleRemoved(dc, event[0]);
- break;
- case SWAPPED:
- handleSwapped(dc, event[0], event[1]);
- break;
+ try {
+ switch (type) {
+ case ADDED:
+ handleAdded(dc, event[0]);
+ break;
+ case CHANGED:
+ handleChanged(dc, event[0]);
+ break;
+ case REMOVED:
+ handleRemoved(dc, event[0]);
+ break;
+ case SWAPPED:
+ handleSwapped(dc, event[0], event[1]);
+ break;
+ }
+ } finally {
+ // Clear cache of component callbacks invocation, except if we are currently called from handleChange().
+ // (See FELIX-4913).
+ clearInvokeCallbackCache();
}
}
});
@@ -313,275 +494,682 @@ public class ComponentImpl implements Co
return m_dependencyEvents.get(dc);
}
+ @Override
public Component setAutoConfig(Class<?> clazz, boolean autoConfig) {
m_autoConfig.put(clazz, Boolean.valueOf(autoConfig));
return this;
}
+ @Override
public Component setAutoConfig(Class<?> clazz, String instanceName) {
m_autoConfig.put(clazz, Boolean.valueOf(instanceName != null));
m_autoConfigInstance.put(clazz, instanceName);
return this;
}
+ @Override
public boolean getAutoConfig(Class<?> clazz) {
Boolean result = (Boolean) m_autoConfig.get(clazz);
return (result != null && result.booleanValue());
}
+ @Override
public String getAutoConfigInstance(Class<?> clazz) {
return (String) m_autoConfigInstance.get(clazz);
}
- private void handleAdded(DependencyContext dc, Event e) {
- if (! m_isStarted) {
- return;
+ @SuppressWarnings("unchecked")
+ public <T> T getInstance() {
+ Object[] instances = getCompositionInstances();
+ return instances.length == 0 ? null : (T) instances[0];
+ }
+
+ public Object[] getInstances() {
+ return getCompositionInstances();
+ }
+
+ public void invokeCallbackMethod(Object[] instances, String methodName, Class<?>[][] signatures, Object[][] parameters) {
+ invokeCallbackMethod(instances, methodName, signatures, parameters, true);
+ }
+
+ public void invokeCallbackMethod(Object[] instances, String methodName, Class<?>[][] signatures,
+ Object[][] parameters, boolean logIfNotFound) {
+ boolean callbackFound = false;
+ for (int i = 0; i < instances.length; i++) {
+ try {
+ InvocationUtil.invokeCallbackMethod(instances[i], methodName, signatures, parameters);
+ callbackFound |= true;
+ }
+ catch (NoSuchMethodException e) {
+ // if the method does not exist, ignore it
+ }
+ catch (InvocationTargetException e) {
+ // the method itself threw an exception, log that
+ m_logger.log(Logger.LOG_ERROR, "Invocation of '" + methodName + "' failed.", e.getCause());
+ }
+ catch (Throwable e) {
+ m_logger.log(Logger.LOG_ERROR, "Could not invoke '" + methodName + "'.", e);
+ }
}
- m_logger.debug("handleAdded %s", e);
- Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
- dependencyEvents.add(e);
- dc.setAvailable(true);
-
- // In the following switch block, we sometimes only recalculate state changes
- // if the dependency is fully started. If the dependency is not started,
- // it means it is actually starting (the service tracker is executing the open method).
- // And in this case, depending on the state, we don't recalculate state changes now. We'll do it
- // once all currently available services are found, and then after,
- // we'll recalculate state change (see the startDependencies method).
- //
- // All this is done for two reasons:
- // 1- optimization: it is preferable to recalculate state changes once we know about all currently
- // available dependency services (after the tracker has returned from its open method).
- // 2- This also allows to determine the list of currently available dependency services from within
- // the component start method callback (this will be extremely useful when porting the Felix SCR
- // on top of DM4).
-
- switch (m_state) {
- case WAITING_FOR_REQUIRED:
- if (dc.isStarted() && dc.isRequired()) {
- // if dependency is starting, we'll handle change after the tracker has returned (see startDependencies method).
- handleChange();
- }
- break;
- case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
- if (!dc.isInstanceBound()) {
- if (dc.isRequired()) {
- dc.invokeCallback(EventType.ADDED, e);
- }
- updateInstance(dc, e, false, true);
+ // If the callback is not found, we don't log if the method is on an AbstractDecorator.
+ // (Aspect or Adapter are not interested in user dependency callbacks)
+ if (logIfNotFound && ! callbackFound && ! (getInstance() instanceof AbstractDecorator)) {
+ if (m_logger == null) {
+ System.out.println("Callback \"" + methodName + "\" not found on componnent instances "
+ + Arrays.toString(getInstances()));
} else {
- if (dc.isStarted() && dc.isRequired()) {
- // if dependency is starting, we'll handle change after the tracker has returned (see startDependencies method).
- handleChange();
- }
+ m_logger.log(LogService.LOG_ERROR, "Callback \"" + methodName + "\" callback not found on componnent instances "
+ + Arrays.toString(getInstances()));
}
- break;
- case TRACKING_OPTIONAL:
- dc.invokeCallback(EventType.ADDED, e);
- updateInstance(dc, e, false, true);
- break;
- default:
+
}
}
-
- private void handleChanged(final DependencyContext dc, final Event e) {
- if (! m_isStarted) {
- return;
- }
- Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
- dependencyEvents.remove(e);
- dependencyEvents.add(e);
-
- switch (m_state) {
- case TRACKING_OPTIONAL:
- dc.invokeCallback(EventType.CHANGED, e);
- updateInstance(dc, e, true, false);
- break;
- case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
- if (!dc.isInstanceBound()) {
- dc.invokeCallback(EventType.CHANGED, e);
- updateInstance(dc, e, true, false);
- }
- break;
- default:
- // noop
- }
+ @Override
+ public boolean isAvailable() {
+ return m_state == TRACKING_OPTIONAL;
}
- private void handleRemoved(DependencyContext dc, Event e) {
- if (! m_isStarted) {
- return;
- }
- // Check if the dependency is still available.
- Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
- int size = dependencyEvents.size();
- if (dependencyEvents.contains(e)) {
- size--; // the dependency is currently registered and is about to be removed.
- }
- dc.setAvailable(size > 0);
-
- // If the dependency is now unavailable, we have to recalculate state change. This will result in invoking the
- // "removed" callback with the removed dependency (which we have not yet removed from our dependency events list.).
- // But we don't recalculate the state if the dependency is not started (if not started, it means that it is currently starting,
- // and the tracker is detecting a removed service).
- if (size == 0 && dc.isStarted()) {
- handleChange();
- }
-
- // Now, really remove the dependency event.
- dependencyEvents.remove(e);
-
- // Depending on the state, we possible have to invoke the callbacks and update the component instance.
- switch (m_state) {
- case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
- if (!dc.isInstanceBound()) {
- if (dc.isRequired()) {
- dc.invokeCallback(EventType.REMOVED, e);
- }
- updateInstance(dc, e, false, false);
- }
- break;
- case TRACKING_OPTIONAL:
- dc.invokeCallback(EventType.REMOVED, e);
- updateInstance(dc, e, false, false);
- break;
- default:
- }
+ @Override
+ public boolean isActive() {
+ return m_active.get();
}
- private void handleSwapped(DependencyContext dc, Event oldEvent, Event newEvent) {
- if (! m_isStarted) {
- return;
- }
- Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
- dependencyEvents.remove(oldEvent);
- dependencyEvents.add(newEvent);
-
- // Depending on the state, we possible have to invoke the callbacks and update the component instance.
- switch (m_state) {
- case WAITING_FOR_REQUIRED:
- // No need to swap, we don't have yet injected anything
- break;
- case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
- // Only swap *non* instance-bound dependencies
- if (!dc.isInstanceBound()) {
- if (dc.isRequired()) {
- dc.invokeCallback(EventType.SWAPPED, oldEvent, newEvent);
+ @Override
+ public Component add(final ComponentStateListener l) {
+ m_listeners.add(l);
+ return this;
+ }
+
+ @Override
+ public Component remove(ComponentStateListener l) {
+ m_listeners.remove(l);
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public List<DependencyContext> getDependencies() {
+ return (List<DependencyContext>) m_dependencies.clone();
+ }
+
+ @Override
+ public Component setImplementation(Object implementation) {
+ m_componentDefinition = implementation;
+ return this;
+ }
+
+ @Override
+ public ServiceRegistration getServiceRegistration() {
+ return m_registration;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <K,V> Dictionary<K, V> getServiceProperties() {
+ if (m_serviceProperties != null) {
+ // Applied patch from FELIX-4304
+ Hashtable<Object, Object> serviceProperties = new Hashtable<>();
+ addTo(serviceProperties, m_serviceProperties);
+ return (Dictionary<K, V>) serviceProperties;
+ }
+ return null;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Component setServiceProperties(final Dictionary<?, ?> serviceProperties) {
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ Dictionary<Object, Object> properties = null;
+ m_serviceProperties = (Dictionary<Object, Object>) serviceProperties;
+ if ((m_registration != null) && (m_serviceName != null)) {
+ properties = calculateServiceProperties();
+ m_registration.setProperties(properties);
}
}
- break;
- case TRACKING_OPTIONAL:
- dc.invokeCallback(EventType.SWAPPED, oldEvent, newEvent);
- break;
- default:
+ });
+ return this;
+ }
+
+ public Component setCallbacks(String init, String start, String stop, String destroy) {
+ ensureNotActive();
+ m_callbackInit = init;
+ m_callbackStart = start;
+ m_callbackStop = stop;
+ m_callbackDestroy = destroy;
+ return this;
+ }
+
+ public Component setCallbacks(Object instance, String init, String start, String stop, String destroy) {
+ ensureNotActive();
+ m_callbackInstance = instance;
+ m_callbackInit = init;
+ m_callbackStart = start;
+ m_callbackStop = stop;
+ m_callbackDestroy = destroy;
+ return this;
+ }
+
+ @Override
+ public Component setFactory(Object factory, String createMethod) {
+ ensureNotActive();
+ m_instanceFactory = factory;
+ m_instanceFactoryCreateMethod = createMethod;
+ return this;
+ }
+
+ @Override
+ public Component setFactory(String createMethod) {
+ return setFactory(null, createMethod);
+ }
+
+ @Override
+ public Component setComposition(Object instance, String getMethod) {
+ ensureNotActive();
+ m_compositionManager = instance;
+ m_compositionManagerGetMethod = getMethod;
+ return this;
+ }
+
+ @Override
+ public Component setComposition(String getMethod) {
+ return setComposition(null, getMethod);
+ }
+
+ @Override
+ public DependencyManager getDependencyManager() {
+ return m_manager;
+ }
+
+ public ComponentDependencyDeclaration[] getComponentDependencies() {
+ List<DependencyContext> deps = getDependencies();
+ if (deps != null) {
+ ComponentDependencyDeclaration[] result = new ComponentDependencyDeclaration[deps.size()];
+ for (int i = 0; i < result.length; i++) {
+ DependencyContext dep = (DependencyContext) deps.get(i);
+ if (dep instanceof ComponentDependencyDeclaration) {
+ result[i] = (ComponentDependencyDeclaration) dep;
+ }
+ else {
+ result[i] = new SCDImpl(dep.toString(), (dep.isAvailable() ? 1 : 0) + (dep.isRequired() ? 2 : 0), dep.getClass().getName());
+ }
+ }
+ return result;
}
+ return null;
}
- private void handleChange() {
- m_logger.debug("handleChanged");
- try {
- ComponentState oldState;
- ComponentState newState;
- do {
- oldState = m_state;
- newState = calculateNewState(oldState);
- m_logger.debug("%s -> %s", oldState, newState);
- m_state = newState;
- } while (performTransition(oldState, newState));
- } finally {
- m_logger.debug("end handling change.");
- }
- }
+ public String getName() {
+ StringBuffer sb = new StringBuffer();
+ Object serviceName = m_serviceName;
+ if (serviceName instanceof String[]) {
+ String[] names = (String[]) serviceName;
+ for (int i = 0; i < names.length; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(names[i]);
+ }
+ appendProperties(sb);
+ } else if (serviceName instanceof String) {
+ sb.append(serviceName.toString());
+ appendProperties(sb);
+ } else {
+ Object implementation = m_componentDefinition;
+ if (implementation != null) {
+ if (implementation instanceof Class) {
+ sb.append(((Class<?>) implementation).getName());
+ } else {
+ // If the implementation instance does not override "toString", just display
+ // the class name, else display the component using its toString method
+ try {
+ Method m = implementation.getClass().getMethod("toString", new Class[0]);
+ if (m.getDeclaringClass().equals(Object.class)) {
+ sb.append(implementation.getClass().getName());
+ } else {
+ sb.append(implementation.toString());
+ }
+ } catch (java.lang.NoSuchMethodException e) {
+ // Just display the class name
+ sb.append(implementation.getClass().getName());
+ }
+ }
+ } else {
+ sb.append(super.toString());
+ }
+ }
+ return sb.toString();
+ }
- /** Based on the current state, calculate the new state. */
- private ComponentState calculateNewState(ComponentState currentState) {
- if (currentState == INACTIVE) {
- if (m_isStarted) {
- return WAITING_FOR_REQUIRED;
- }
- }
- if (currentState == WAITING_FOR_REQUIRED) {
- if (!m_isStarted) {
- return INACTIVE;
- }
- if (allRequiredAvailable()) {
- return INSTANTIATED_AND_WAITING_FOR_REQUIRED;
- }
- }
- if (currentState == INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
- if (m_isStarted && allRequiredAvailable()) {
- if (allInstanceBoundAvailable()) {
- return TRACKING_OPTIONAL;
- }
- return currentState;
- }
- return WAITING_FOR_REQUIRED;
- }
- if (currentState == TRACKING_OPTIONAL) {
- if (m_isStarted && allRequiredAvailable() && allInstanceBoundAvailable()) {
- return currentState;
- }
- return INSTANTIATED_AND_WAITING_FOR_REQUIRED;
- }
- return currentState;
- }
+ @Override
+ public BundleContext getBundleContext() {
+ return m_context;
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return m_bundle;
+ }
- /** Perform all the actions associated with state transitions. Returns true if a transition was performed. */
- private boolean performTransition(ComponentState oldState, ComponentState newState) {
-// System.out.println("transition from " + oldState + " to " + newState);
- if (oldState == ComponentState.INACTIVE && newState == ComponentState.WAITING_FOR_REQUIRED) {
- startDependencies(m_dependencies);
- notifyListeners(newState);
- return true;
- }
- if (oldState == ComponentState.WAITING_FOR_REQUIRED && newState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
- instantiateComponent();
+ public long getId() {
+ return m_id;
+ }
+
+ public String getClassName() {
+ Object serviceInstance = m_componentInstance;
+ if (serviceInstance != null) {
+ return serviceInstance.getClass().getName();
+ }
+
+ Object implementation = m_componentDefinition;
+ if (implementation != null) {
+ if (implementation instanceof Class) {
+ return ((Class<?>) implementation).getName();
+ }
+ return implementation.getClass().getName();
+ }
+
+ Object instanceFactory = m_instanceFactory;
+ if (instanceFactory != null) {
+ return instanceFactory.getClass().getName();
+ } else {
+ // unexpected.
+ return ComponentImpl.class.getName();
+ }
+ }
+
+ public String[] getServices() {
+ if (m_serviceName instanceof String[]) {
+ return (String[]) m_serviceName;
+ } else if (m_serviceName instanceof String) {
+ return new String[] { (String) m_serviceName };
+ } else {
+ return null;
+ }
+ }
+
+ public int getState() {
+ return (isAvailable() ? ComponentDeclaration.STATE_REGISTERED : ComponentDeclaration.STATE_UNREGISTERED);
+ }
+
+ public void ensureNotActive() {
+ if (m_active.get()) {
+ throw new IllegalStateException("Can't modify an already started component.");
+ }
+ }
+
+ public ComponentDeclaration getComponentDeclaration() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ if (m_logger.getDebugKey() != null) {
+ return m_logger.getDebugKey();
+ }
+ return getClassName();
+ }
+
+ @Override
+ public void setThreadPool(Executor threadPool) {
+ ensureNotActive();
+ m_executor = new DispatchExecutor(threadPool, m_logger);
+ }
+
+ @Override
+ public Logger getLogger() {
+ return m_logger;
+ }
+
+ @Override
+ public Map<String, Long> getCallbacksTime() {
+ return m_stopwatch;
+ }
+
+ // ---------------------- Package/Private methods ---------------------------
+
+ void instantiateComponent() {
+ m_logger.debug("instantiating component.");
+
+ // TODO add more complex factory instantiations of one or more components in a composition here
+ if (m_componentInstance == null) {
+ if (m_componentDefinition instanceof Class) {
+ try {
+ m_componentInstance = createInstance((Class<?>) m_componentDefinition);
+ }
+ catch (Exception e) {
+ m_logger.log(Logger.LOG_ERROR, "Could not instantiate class " + m_componentDefinition, e);
+ }
+ }
+ else {
+ if (m_instanceFactoryCreateMethod != null) {
+ Object factory = null;
+ if (m_instanceFactory != null) {
+ if (m_instanceFactory instanceof Class) {
+ try {
+ factory = createInstance((Class<?>) m_instanceFactory);
+ }
+ catch (Exception e) {
+ m_logger.log(Logger.LOG_ERROR, "Could not create factory instance of class " + m_instanceFactory + ".", e);
+ }
+ }
+ else {
+ factory = m_instanceFactory;
+ }
+ }
+ else {
+ // TODO review if we want to try to default to something if not specified
+ // for now the JavaDoc of setFactory(method) reflects the fact that we need
+ // to review it
+ }
+ if (factory == null) {
+ m_logger.log(Logger.LOG_ERROR, "Factory cannot be null.");
+ }
+ else {
+ try {
+ m_componentInstance = InvocationUtil.invokeMethod(factory,
+ factory.getClass(), m_instanceFactoryCreateMethod,
+ new Class[][] {{}, {Component.class}}, new Object[][] {{}, {this}}, false);
+ }
+ catch (Exception e) {
+ m_logger.log(Logger.LOG_ERROR, "Could not create service instance using factory " + factory + " method " + m_instanceFactoryCreateMethod + ".", e);
+ }
+ }
+ }
+ }
+
+ if (m_componentInstance == null) {
+ m_componentInstance = m_componentDefinition;
+ }
+
+ // configure the bundle context
+ autoConfigureImplementation(BundleContext.class, m_context);
+ autoConfigureImplementation(ServiceRegistration.class, NULL_REGISTRATION);
+ autoConfigureImplementation(DependencyManager.class, m_manager);
+ autoConfigureImplementation(Component.class, this);
+ }
+ }
+
+ /**
+ * Runs the state machine, to see if a change event has to trigger some component state transition.
+ */
+ private void handleChange() {
+ m_logger.debug("handleChanged");
+ handlingChange(true);
+ try {
+ ComponentState oldState;
+ ComponentState newState;
+ do {
+ oldState = m_state;
+ newState = calculateNewState(oldState);
+ m_logger.debug("%s -> %s", oldState, newState);
+ m_state = newState;
+ } while (performTransition(oldState, newState));
+ } finally {
+ handlingChange(false);
+ clearInvokeCallbackCache();
+ m_logger.debug("end handling change.");
+ }
+ }
+
+ /**
+ * Based on the current state, calculate the new state.
+ */
+ private ComponentState calculateNewState(ComponentState currentState) {
+ if (currentState == INACTIVE) {
+ if (m_isStarted) {
+ return WAITING_FOR_REQUIRED;
+ }
+ }
+ if (currentState == WAITING_FOR_REQUIRED) {
+ if (!m_isStarted) {
+ return INACTIVE;
+ }
+ if (allRequiredAvailable()) {
+ return INSTANTIATED_AND_WAITING_FOR_REQUIRED;
+ }
+ }
+ if (currentState == INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
+ if (m_isStarted && allRequiredAvailable()) {
+ if (allInstanceBoundAvailable()) {
+ return TRACKING_OPTIONAL;
+ }
+ return currentState;
+ }
+ return WAITING_FOR_REQUIRED;
+ }
+ if (currentState == TRACKING_OPTIONAL) {
+ if (m_isStarted && allRequiredAvailable() && allInstanceBoundAvailable()) {
+ return currentState;
+ }
+ return INSTANTIATED_AND_WAITING_FOR_REQUIRED;
+ }
+ return currentState;
+ }
+
+ /**
+ * Perform all the actions associated with state transitions.
+ * @returns true if a transition was performed.
+ **/
+ private boolean performTransition(ComponentState oldState, ComponentState newState) {
+ if (oldState == ComponentState.INACTIVE && newState == ComponentState.WAITING_FOR_REQUIRED) {
+ startDependencies(m_dependencies);
+ notifyListeners(newState);
+ return true;
+ }
+ if (oldState == ComponentState.WAITING_FOR_REQUIRED && newState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
+ instantiateComponent();
invokeAutoConfigDependencies();
- invokeAddRequiredDependencies();
+ invokeAddRequiredDependencies();
ComponentState stateBeforeCallingInit = m_state;
- invoke(m_callbackInit);
+ invoke(m_callbackInit);
if (stateBeforeCallingInit == m_state) {
notifyListeners(newState); // init did not change current state, we can notify about this new state
}
- return true;
- }
- if (oldState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED && newState == ComponentState.TRACKING_OPTIONAL) {
+ return true;
+ }
+ if (oldState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED && newState == ComponentState.TRACKING_OPTIONAL) {
invokeAutoConfigInstanceBoundDependencies();
- invokeAddRequiredInstanceBoundDependencies();
- invoke(m_callbackStart);
- invokeAddOptionalDependencies();
- registerService();
- notifyListeners(newState);
- return true;
- }
- if (oldState == ComponentState.TRACKING_OPTIONAL && newState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
- unregisterService();
- invokeRemoveOptionalDependencies();
- invoke(m_callbackStop);
- invokeRemoveInstanceBoundDependencies();
- notifyListeners(newState);
- return true;
- }
- if (oldState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED && newState == ComponentState.WAITING_FOR_REQUIRED) {
- invoke(m_callbackDestroy);
- invokeRemoveRequiredDependencies();
- notifyListeners(newState);
- if (! someDependenciesNeedInstance()) {
+ invokeAddRequiredInstanceBoundDependencies();
+ invoke(m_callbackStart);
+ invokeAddOptionalDependencies();
+ registerService();
+ notifyListeners(newState);
+ return true;
+ }
+ if (oldState == ComponentState.TRACKING_OPTIONAL && newState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
+ unregisterService();
+ invokeRemoveOptionalDependencies();
+ invoke(m_callbackStop);
+ invokeRemoveInstanceBoundDependencies();
+ notifyListeners(newState);
+ return true;
+ }
+ if (oldState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED && newState == ComponentState.WAITING_FOR_REQUIRED) {
+ invoke(m_callbackDestroy);
+ invokeRemoveRequiredDependencies();
+ notifyListeners(newState);
+ if (! someDependenciesNeedInstance()) {
destroyComponent();
}
- return true;
- }
- if (oldState == ComponentState.WAITING_FOR_REQUIRED && newState == ComponentState.INACTIVE) {
- stopDependencies();
+ return true;
+ }
+ if (oldState == ComponentState.WAITING_FOR_REQUIRED && newState == ComponentState.INACTIVE) {
+ stopDependencies();
destroyComponent();
- notifyListeners(newState);
- return true;
- }
- return false;
- }
-
+ notifyListeners(newState);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Sets the m_handlingChange flag that indicates if the state machine is currently running the handleChange method.
+ */
+ private void handlingChange(boolean transiting) {
+ m_handlingChange = transiting;
+ }
+
+ /**
+ * Are we currently running the handleChange method ?
+ */
+ private boolean isHandlingChange() {
+ return m_handlingChange;
+ }
+
+ /**
+ * Then handleEvent calls this method when a dependency service is being added.
+ */
+ private void handleAdded(DependencyContext dc, Event e) {
+ if (! m_isStarted) {
+ return;
+ }
+ m_logger.debug("handleAdded %s", e);
+
+ Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
+ dependencyEvents.add(e);
+ dc.setAvailable(true);
+
+ // In the following switch block, we sometimes only recalculate state changes
+ // if the dependency is fully started. If the dependency is not started,
+ // it means it is actually starting (the service tracker is executing the open method).
+ // And in this case, depending on the state, we don't recalculate state changes now.
+ //
+ // All this is done for two reasons:
+ // 1- optimization: it is preferable to recalculate state changes once we know about all currently
+ // available dependency services (after the tracker has returned from its open method).
+ // 2- This also allows to determine the list of currently available dependency services before calling
+ // the component start() callback.
+
+ switch (m_state) {
+ case WAITING_FOR_REQUIRED:
+ if (dc.isStarted() && dc.isRequired()) {
+ handleChange();
+ }
+ break;
+ case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
+ if (!dc.isInstanceBound()) {
+ if (dc.isRequired()) {
+ invokeCallbackSafe(dc, EventType.ADDED, e);
+ }
+ updateInstance(dc, e, false, true);
+ } else {
+ if (dc.isStarted() && dc.isRequired()) {
+ handleChange();
+ }
+ }
+ break;
+ case TRACKING_OPTIONAL:
+ invokeCallbackSafe(dc, EventType.ADDED, e);
+ updateInstance(dc, e, false, true);
+ break;
+ default:
+ }
+ }
+
+ /**
+ * Then handleEvent calls this method when a dependency service is being changed.
+ */
+ private void handleChanged(final DependencyContext dc, final Event e) {
+ if (! m_isStarted) {
+ return;
+ }
+ Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
+ dependencyEvents.remove(e);
+ dependencyEvents.add(e);
+
+ switch (m_state) {
+ case TRACKING_OPTIONAL:
+ invokeCallbackSafe(dc, EventType.CHANGED, e);
+ updateInstance(dc, e, true, false);
+ break;
+
+ case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
+ if (!dc.isInstanceBound()) {
+ invokeCallbackSafe(dc, EventType.CHANGED, e);
+ updateInstance(dc, e, true, false);
+ }
+ break;
+ default:
+ // noop
+ }
+ }
+
+ /**
+ * Then handleEvent calls this method when a dependency service is being removed.
+ */
+ private void handleRemoved(DependencyContext dc, Event e) {
+ if (! m_isStarted) {
+ return;
+ }
+ // Check if the dependency is still available.
+ Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
+ int size = dependencyEvents.size();
+ if (dependencyEvents.contains(e)) {
+ size--; // the dependency is currently registered and is about to be removed.
+ }
+ dc.setAvailable(size > 0);
+
+ // If the dependency is now unavailable, we have to recalculate state change. This will result in invoking the
+ // "removed" callback with the removed dependency (which we have not yet removed from our dependency events list.).
+ // But we don't recalculate the state if the dependency is not started (if not started, it means that it is currently starting,
+ // and the tracker is detecting a removed service).
+ if (size == 0 && dc.isStarted()) {
+ handleChange();
+ }
+
+ // Now, really remove the dependency event.
+ dependencyEvents.remove(e);
+
+ // Depending on the state, we possible have to invoke the callbacks and update the component instance.
+ switch (m_state) {
+ case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
+ if (!dc.isInstanceBound()) {
+ if (dc.isRequired()) {
+ invokeCallbackSafe(dc, EventType.REMOVED, e);
+ }
+ updateInstance(dc, e, false, false);
+ }
+ break;
+ case TRACKING_OPTIONAL:
+ invokeCallbackSafe(dc, EventType.REMOVED, e);
+ updateInstance(dc, e, false, false);
+ break;
+ default:
+ }
+ }
+
+ private void handleSwapped(DependencyContext dc, Event oldEvent, Event newEvent) {
+ if (! m_isStarted) {
+ return;
+ }
+ Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
+ dependencyEvents.remove(oldEvent);
+ dependencyEvents.add(newEvent);
+
+ // Depending on the state, we possible have to invoke the callbacks and update the component instance.
+ switch (m_state) {
+ case WAITING_FOR_REQUIRED:
+ // No need to swap, we don't have yet injected anything
+ break;
+ case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
+ // Only swap *non* instance-bound dependencies
+ if (!dc.isInstanceBound()) {
+ if (dc.isRequired()) {
+ dc.invokeCallback(EventType.SWAPPED, oldEvent, newEvent);
+ }
+ }
+ break;
+ case TRACKING_OPTIONAL:
+ dc.invokeCallback(EventType.SWAPPED, oldEvent, newEvent);
+ break;
+ default:
+ }
+ }
+
private boolean allRequiredAvailable() {
boolean available = true;
for (DependencyContext d : m_dependencies) {
@@ -656,8 +1244,6 @@ public class ComponentImpl implements Co
}
d.start();
}
- // The started dependencies has probably called our handleAdded method: we now have to run our state machine.
- handleChange();
}
private void stopDependencies() {
@@ -739,68 +1325,6 @@ public class ComponentImpl implements Co
}
}
}
-
- void instantiateComponent() {
- m_logger.debug("instantiating component.");
-
- // TODO add more complex factory instantiations of one or more components in a composition here
- if (m_componentInstance == null) {
- if (m_componentDefinition instanceof Class) {
- try {
- m_componentInstance = createInstance((Class<?>) m_componentDefinition);
- }
- catch (Exception e) {
- m_logger.log(Logger.LOG_ERROR, "Could not instantiate class " + m_componentDefinition, e);
- }
- }
- else {
- if (m_instanceFactoryCreateMethod != null) {
- Object factory = null;
- if (m_instanceFactory != null) {
- if (m_instanceFactory instanceof Class) {
- try {
- factory = createInstance((Class<?>) m_instanceFactory);
- }
- catch (Exception e) {
- m_logger.log(Logger.LOG_ERROR, "Could not create factory instance of class " + m_instanceFactory + ".", e);
- }
- }
- else {
- factory = m_instanceFactory;
- }
- }
- else {
- // TODO review if we want to try to default to something if not specified
- // for now the JavaDoc of setFactory(method) reflects the fact that we need
- // to review it
- }
- if (factory == null) {
- m_logger.log(Logger.LOG_ERROR, "Factory cannot be null.");
- }
- else {
- try {
- m_componentInstance = InvocationUtil.invokeMethod(factory,
- factory.getClass(), m_instanceFactoryCreateMethod,
- new Class[][] {{}, {Component.class}}, new Object[][] {{}, {this}}, false);
- }
- catch (Exception e) {
- m_logger.log(Logger.LOG_ERROR, "Could not create service instance using factory " + factory + " method " + m_instanceFactoryCreateMethod + ".", e);
- }
- }
- }
- }
-
- if (m_componentInstance == null) {
- m_componentInstance = m_componentDefinition;
- }
-
- // configure the bundle context
- autoConfigureImplementation(BundleContext.class, m_context);
- autoConfigureImplementation(ServiceRegistration.class, NULL_REGISTRATION);
- autoConfigureImplementation(DependencyManager.class, m_manager);
- autoConfigureImplementation(Component.class, this);
- }
- }
private void destroyComponent() {
m_componentInstance = null;
@@ -810,7 +1334,7 @@ public class ComponentImpl implements Co
for (DependencyContext d : m_dependencies) {
if (d.isRequired() && !d.isInstanceBound()) {
for (Event e : m_dependencyEvents.get(d)) {
- d.invokeCallback(EventType.ADDED, e);
+ invokeCallbackSafe(d, EventType.ADDED, e);
}
}
}
@@ -836,7 +1360,7 @@ public class ComponentImpl implements Co
for (DependencyContext d : m_dependencies) {
if (d.isRequired() && d.isInstanceBound()) {
for (Event e : m_dependencyEvents.get(d)) {
- d.invokeCallback(EventType.ADDED, e);
+ invokeCallbackSafe(d, EventType.ADDED, e);
}
}
}
@@ -846,7 +1370,7 @@ public class ComponentImpl implements Co
for (DependencyContext d : m_dependencies) {
if (! d.isRequired()) {
for (Event e : m_dependencyEvents.get(d)) {
- d.invokeCallback(EventType.ADDED, e);
+ invokeCallbackSafe(d, EventType.ADDED, e);
}
}
}
@@ -856,7 +1380,7 @@ public class ComponentImpl implements Co
for (DependencyContext d : m_dependencies) {
if (!d.isInstanceBound() && d.isRequired()) {
for (Event e : m_dependencyEvents.get(d)) {
- d.invokeCallback(EventType.REMOVED, e);
+ invokeCallbackSafe(d, EventType.REMOVED, e);
}
}
}
@@ -866,7 +1390,7 @@ public class ComponentImpl implements Co
for (DependencyContext d : m_dependencies) {
if (! d.isRequired()) {
for (Event e : m_dependencyEvents.get(d)) {
- d.invokeCallback(EventType.REMOVED, e);
+ invokeCallbackSafe(d, EventType.REMOVED, e);
}
}
}
@@ -876,80 +1400,53 @@ public class ComponentImpl implements Co
for (DependencyContext d : m_dependencies) {
if (d.isInstanceBound()) {
for (Event e : m_dependencyEvents.get(d)) {
- d.invokeCallback(EventType.REMOVED, e);
+ invokeCallbackSafe(d, EventType.REMOVED, e);
}
}
}
}
+
+ /**
+ * This method ensures that a dependency callback is invoked only one time;
+ */
+ private void invokeCallbackSafe(DependencyContext dc, EventType type, Event event) {
+ if (m_invokeCallbackCache.put(event, event) == null) {
+ dc.invokeCallback(type, event);
+ }
+ }
+
+ /**
+ * Clears the cache of invoked components callbacks.
+ * We only clear the cache when the state machine is not running.
+ * The cache is used to avoid calling the same bind callback twice.
+ * See FELIX-4913.
+ */
+ private void clearInvokeCallbackCache() {
+ if (! isHandlingChange()) {
+ m_invokeCallbackCache.clear();
+ }
+ }
private void invoke(String name) {
if (name != null) {
- // if a callback instance was specified, look for the method there, if not,
- // ask the service for its composition instances
- Object[] instances = m_callbackInstance != null ? new Object[] { m_callbackInstance } : getCompositionInstances();
-
- long t1 = System.nanoTime();
- try {
- invokeCallbackMethod(instances, name,
- new Class[][] {{ Component.class }, {}},
- new Object[][] {{ this }, {}},
- false);
- } finally {
- long t2 = System.nanoTime();
- m_stopwatch.put(name, t2 - t1);
- }
- }
- }
-
- @SuppressWarnings("unchecked")
- public <T> T getInstance() {
- Object[] instances = getCompositionInstances();
- return instances.length == 0 ? null : (T) instances[0];
- }
-
- public Object[] getInstances() {
- return getCompositionInstances();
- }
-
- public void invokeCallbackMethod(Object[] instances, String methodName, Class<?>[][] signatures, Object[][] parameters) {
- invokeCallbackMethod(instances, methodName, signatures, parameters, true);
- }
+ // if a callback instance was specified, look for the method there, if not,
+ // ask the service for its composition instances
+ Object[] instances = m_callbackInstance != null ? new Object[] { m_callbackInstance } : getCompositionInstances();
- public void invokeCallbackMethod(Object[] instances, String methodName, Class<?>[][] signatures,
- Object[][] parameters, boolean logIfNotFound) {
- boolean callbackFound = false;
- for (int i = 0; i < instances.length; i++) {
+ long t1 = System.nanoTime();
try {
- InvocationUtil.invokeCallbackMethod(instances[i], methodName, signatures, parameters);
- callbackFound |= true;
- }
- catch (NoSuchMethodException e) {
- // if the method does not exist, ignore it
- }
- catch (InvocationTargetException e) {
- // the method itself threw an exception, log that
- m_logger.log(Logger.LOG_ERROR, "Invocation of '" + methodName + "' failed.", e.getCause());
- }
- catch (Throwable e) {
- m_logger.log(Logger.LOG_ERROR, "Could not invoke '" + methodName + "'.", e);
- }
- }
-
- // If the callback is not found, we don't log if the method is on an AbstractDecorator.
- // (Aspect or Adapter are not interested in user dependency callbacks)
- if (logIfNotFound && ! callbackFound && ! (getInstance() instanceof AbstractDecorator)) {
- if (m_logger == null) {
- System.out.println("Callback \"" + methodName + "\" not found on componnent instances "
- + Arrays.toString(getInstances()));
- } else {
- m_logger.log(LogService.LOG_ERROR, "Callback \"" + methodName + "\" callback not found on componnent instances "
- + Arrays.toString(getInstances()));
+ invokeCallbackMethod(instances, name,
+ new Class[][] {{ Component.class }, {}},
+ new Object[][] {{ this }, {}},
+ false);
+ } finally {
+ long t2 = System.nanoTime();
+ m_stopwatch.put(name, t2 - t1);
}
-
}
}
-
- private Object createInstance(Class<?> clazz) throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+
+ private Object createInstance(Class<?> clazz) throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Constructor<?> constructor = clazz.getConstructor(VOID);
constructor.setAccessible(true);
return constructor.newInstance();
@@ -961,40 +1458,6 @@ public class ComponentImpl implements Co
}
}
- @Override
- public boolean isAvailable() {
- return m_state == TRACKING_OPTIONAL;
- }
-
- @Override
- public boolean isActive() {
- return m_active.get();
- }
-
- @Override
- public Component add(final ComponentStateListener l) {
- m_listeners.add(l);
- return this;
- }
-
- @Override
- public Component remove(ComponentStateListener l) {
- m_listeners.remove(l);
- return this;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public List<DependencyContext> getDependencies() {
- return (List<DependencyContext>) m_dependencies.clone();
- }
-
- @Override
- public Component setImplementation(Object implementation) {
- m_componentDefinition = implementation;
- return this;
- }
-
private void autoConfigureImplementation(Class<?> clazz, Object instance) {
if (((Boolean) m_autoConfig.get(clazz)).booleanValue()) {
configureImplementation(clazz, instance, (String) m_autoConfigInstance.get(clazz));
@@ -1053,85 +1516,6 @@ public class ComponentImpl implements Co
FieldUtil.updateDependencyField(targets, fieldName, update, add, clazz, event, dc, m_logger);
}
- @Override
- public ServiceRegistration getServiceRegistration() {
- return m_registration;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public <K,V> Dictionary<K, V> getServiceProperties() {
- if (m_serviceProperties != null) {
- // Applied patch from FELIX-4304
- Hashtable<Object, Object> serviceProperties = new Hashtable<>();
- addTo(serviceProperties, m_serviceProperties);
- return (Dictionary<K, V>) serviceProperties;
- }
- return null;
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public Component setServiceProperties(final Dictionary<?, ?> serviceProperties) {
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- Dictionary<Object, Object> properties = null;
- m_serviceProperties = (Dictionary<Object, Object>) serviceProperties;
- if ((m_registration != null) && (m_serviceName != null)) {
- properties = calculateServiceProperties();
- m_registration.setProperties(properties);
- }
- }
- });
- return this;
- }
-
- public Component setCallbacks(String init, String start, String stop, String destroy) {
- ensureNotActive();
- m_callbackInit = init;
- m_callbackStart = start;
- m_callbackStop = stop;
- m_callbackDestroy = destroy;
- return this;
- }
-
- public Component setCallbacks(Object instance, String init, String start, String stop, String destroy) {
- ensureNotActive();
- m_callbackInstance = instance;
- m_callbackInit = init;
- m_callbackStart = start;
- m_callbackStop = stop;
- m_callbackDestroy = destroy;
- return this;
- }
-
- @Override
- public Component setFactory(Object factory, String createMethod) {
- ensureNotActive();
- m_instanceFactory = factory;
- m_instanceFactoryCreateMethod = createMethod;
- return this;
- }
-
- @Override
- public Component setFactory(String createMethod) {
- return setFactory(null, createMethod);
- }
-
- @Override
- public Component setComposition(Object instance, String getMethod) {
- ensureNotActive();
- m_compositionManager = instance;
- m_compositionManagerGetMethod = getMethod;
- return this;
- }
-
- @Override
- public Component setComposition(String getMethod) {
- return setComposition(null, getMethod);
- }
-
private Object[] getCompositionInstances() {
Object[] instances = null;
if (m_compositionManagerGetMethod != null) {
@@ -1157,71 +1541,6 @@ public class ComponentImpl implements Co
return instances;
}
- @Override
- public DependencyManager getDependencyManager() {
- return m_manager;
- }
-
- public ComponentDependencyDeclaration[] getComponentDependencies() {
- List<DependencyContext> deps = getDependencies();
- if (deps != null) {
- ComponentDependencyDeclaration[] result = new ComponentDependencyDeclaration[deps.size()];
- for (int i = 0; i < result.length; i++) {
- DependencyContext dep = (DependencyContext) deps.get(i);
- if (dep instanceof ComponentDependencyDeclaration) {
- result[i] = (ComponentDependencyDeclaration) dep;
- }
- else {
- result[i] = new SCDImpl(dep.toString(), (dep.isAvailable() ? 1 : 0) + (dep.isRequired() ? 2 : 0), dep.getClass().getName());
- }
- }
- return result;
- }
- return null;
- }
-
- public String getName() {
- StringBuffer sb = new StringBuffer();
- Object serviceName = m_serviceName;
- if (serviceName instanceof String[]) {
- String[] names = (String[]) serviceName;
- for (int i = 0; i < names.length; i++) {
- if (i > 0) {
- sb.append(", ");
- }
- sb.append(names[i]);
- }
- appendProperties(sb);
- } else if (serviceName instanceof String) {
- sb.append(serviceName.toString());
- appendProperties(sb);
- } else {
- Object implementation = m_componentDefinition;
- if (implementation != null) {
- if (implementation instanceof Class) {
- sb.append(((Class<?>) implementation).getName());
- } else {
- // If the implementation instance does not override "toString", just display
- // the class name, else display the component using its toString method
- try {
- Method m = implementation.getClass().getMethod("toString", new Class[0]);
- if (m.getDeclaringClass().equals(Object.class)) {
- sb.append(implementation.getClass().getName());
- } else {
- sb.append(implementation.toString());
- }
- } catch (java.lang.NoSuchMethodException e) {
- // Just display the class name
- sb.append(implementation.getClass().getName());
- }
- }
- } else {
- sb.append(super.toString());
- }
- }
- return sb.toString();
- }
-
private void appendProperties(StringBuffer result) {
Dictionary<Object, Object> properties = calculateServiceProperties();
if (properties != null) {
@@ -1254,91 +1573,6 @@ public class ComponentImpl implements Co
}
}
- @Override
- public BundleContext getBundleContext() {
- return m_context;
- }
-
- @Override
- public Bundle getBundle() {
- return m_bundle;
- }
-
- public long getId() {
- return m_id;
- }
-
- public String getClassName() {
- Object serviceInstance = m_componentInstance;
- if (serviceInstance != null) {
- return serviceInstance.getClass().getName();
- }
-
- Object implementation = m_componentDefinition;
- if (implementation != null) {
- if (implementation instanceof Class) {
- return ((Class<?>) implementation).getName();
- }
- return implementation.getClass().getName();
- }
-
- Object instanceFactory = m_instanceFactory;
- if (instanceFactory != null) {
- return instanceFactory.getClass().getName();
- } else {
- // unexpected.
- return ComponentImpl.class.getName();
- }
- }
-
- public String[] getServices() {
- if (m_serviceName instanceof String[]) {
- return (String[]) m_serviceName;
- } else if (m_serviceName instanceof String) {
- return new String[] { (String) m_serviceName };
- } else {
- return null;
- }
- }
-
- public int getState() {
- return (isAvailable() ? ComponentDeclaration.STATE_REGISTERED : ComponentDeclaration.STATE_UNREGISTERED);
- }
-
- public void ensureNotActive() {
- if (m_active.get()) {
- throw new IllegalStateException("Can't modify an already started component.");
- }
- }
-
- public ComponentDeclaration getComponentDeclaration() {
- return this;
- }
-
- @Override
- public String toString() {
- if (m_logger.getDebugKey() != null) {
- return m_logger.getDebugKey();
- }
- return getClassName();
- }
-
- @Override
- public void setThreadPool(Executor threadPool) {
- ensureNotActive();
- m_executor = new DispatchExecutor(threadPool, m_logger);
- }
-
- @Override
- public Logger getLogger() {
- return m_logger;
- }
-
- @Override
- public Map<String, Long> getCallbacksTime() {
- return m_stopwatch;
- }
-
private Executor getExecutor() {
return m_executor;
}
Modified: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentScheduler.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentScheduler.java?rev=1683504&r1=1683503&r2=1683504&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentScheduler.java (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentScheduler.java Thu Jun 4 10:15:35 2015
@@ -114,7 +114,7 @@ public class ComponentScheduler {
// But if the "parallel" system property is specified, the component will use the threadpool only if it's
// classname is starting with one of the prefixes specified in the property.
if (parallel == null || requiresThreadPool(c, parallel)) {
- ((ComponentContext) c).setThreadPool(execFactory.getExecutorFor(c));
+ createComponentExecutor(execFactory, c);
}
return true; // start the component now, possibly using the threadpool (see above).
}
Modified: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java?rev=1683504&r1=1683503&r2=1683504&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java Thu Jun 4 10:15:35 2015
@@ -196,15 +196,13 @@ public class ConfigurationDependencyImpl
// If this is initial settings, or a configuration update, we handle it synchronously.
// We'll conclude that the dependency is available only if invoking updated did not cause
// any ConfigurationException.
- if (settings != null) {
- Object[] instances = m_component.getInstances();
- if (instances != null) {
- try {
- invokeUpdated(settings);
- } catch (ConfigurationException e) {
- logConfigurationException(e);
- throw e;
- }
+ Object[] instances = m_component.getInstances();
+ if (instances != null) {
+ try {
+ invokeUpdated(settings);
+ } catch (ConfigurationException e) {
+ logConfigurationException(e);
+ throw e;
}
}
@@ -244,15 +242,8 @@ public class ConfigurationDependencyImpl
break;
case REMOVED:
// The state machine is stopping us. We have to invoke updated(null).
- try {
- m_updateInvokedCache.set(false);
- invokeUpdated(null);
- } catch (ConfigurationException e) {
- logConfigurationException(e);
- } finally {
- // Reset for the next time the state machine calls invokeAdd
- m_updateInvokedCache.set(false);
- }
+ // Reset for the next time the state machine calls invokeAdd
+ m_updateInvokedCache.set(false);
break;
default:
break;
Modified: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/ServiceRaceTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/ServiceRaceTest.java?rev=1683504&r1=1683503&r2=1683504&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/ServiceRaceTest.java (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/ServiceRaceTest.java Thu Jun 4 10:15:35 2015
@@ -178,6 +178,7 @@ public class ServiceRaceTest extends Tes
m_threadpool.execute(new Runnable() {
public void run() {
try {
+ // simulate a configuration suppression.
confDependency.updated(null);
}
catch (ConfigurationException e) {
@@ -189,11 +190,9 @@ public class ServiceRaceTest extends Tes
// Ensure that client has been stopped, then destroyed, then unbound from all dependencies
expectedStep += 2; // stop/destroy
expectedStep += DEPENDENCIES; // removed all dependencies
- expectedStep += 1; // removed configuration
step.waitForStep(expectedStep, STEP_WAIT);
step.ensure();
Assert.assertEquals(0, theClient.getDependencies());
- Assert.assertNull(theClient.getConfiguration());
debug("finished one test loop");
if ((loop + 1) % 100 == 0) {
@@ -213,12 +212,11 @@ public class ServiceRaceTest extends Tes
}
public void updated(Dictionary conf) throws ConfigurationException {
- m_conf = conf;
if (conf != null) {
+ Assert.assertNotNull(conf);
Assert.assertEquals("bar", conf.get("foo"));
+ m_conf = conf;
m_step.step(1);
- } else {
- m_step.step();
}
}