I have two components defined: TargetService and Component.
TargetService
public interface TargetService {
}
@org.osgi.service.component.annotations.Component(
name = "TargetService",
configurationPolicy = ConfigurationPolicy.REQUIRE,
immediate = true)
public class TargetServiceImpl implements TargetService {
@Activate
public void activate(Map properties) {
println("=== TargetService Activate " + properties.get("number") + " ===");
}
@Deactivate
public void deactivate(Map properties) {
println("=== TargetService Deactivate " + properties.get("number") + " ===");
}
private void println(String message) {
System.out.println(message);
}
}
Component
@org.osgi.service.component.annotations.Component(
name = "Component",
configurationPolicy = ConfigurationPolicy.REQUIRE,
immediate = true)
public class Component {
private TargetService target;
@Reference(unbind = "unbindTargetService")
public void bindTargetService(TargetService target) {
this.target = target;
}
public void unbindTargetService(TargetService target) {
this.target = null;
}
@Activate
public void activate(Map properties) {
println("=== Component Activate " + properties.get("number") + " ===");
println(properties);
}
@Deactivate
public void deactivate(Map properties) {
println("=== Component Deactivate " + properties.get("number") + " ===");
println(properties);
}
private void println(Map properties) {
if (properties != null) {
Iterator it = properties.entrySet().iterator( );
while (it.hasNext()) {
println(it.next().toString());
}
}
}
private void println(String message) {
System.out.println(message);
}
}
I want Components 101 and 102 to be bound to TargetService 1 and Component 201 to be bound to TargetService 2, like so:
Let's create their configurations:
> enter configuration
configuration> create -f Component
configuration> set TargetService.target (number=1)
configuration> set number 101
configuration> save
configuration> create -f Component
configuration> set TargetService.target (number=1)
configuration> set number 102
configuration> save
configuration> create -f Component
configuration> set TargetService.target (number=2)
configuration> set number 201
configuration> save
configuration> show
[-] Component._0
factory PID: Component
location: -
change count: 1
properties:
TargetService.target= (number=1)
number= 101
service.factoryPid= Component
service.pid= Component._0
[-] Component._1
factory PID: Component
location: -
change count: 1
properties:
TargetService.target= (number=1)
number= 102
service.factoryPid= Component
service.pid= Component._1
[-] Component._2
factory PID: Component
location: -
change count: 1
properties:
TargetService.target= (number=2)
number= 201
service.factoryPid= Component
service.pid= Component._2
Components 101 and 102 should be activated as soon as their references for TargetService 1 are satisfied.
But when TargetService configuration becomes available, a NullPointerException occurs.
configuration> create -f TargetService
configuration> set number 1
configuration> save
configuration> [stderr] ## DEBUG: errors - FrameworkErrorEvent bundle #28
[stderr] ## DEBUG: errors - FrameworkErrorEvent throwable:
[stderr] java.lang.NullPointerException
[stderr] at org.knopflerfish.bundle.component.Component.newComponentConfiguration(Component.java:774)
[stderr] at org.knopflerfish.bundle.component.Component.newComponentConfigurations(Component.java:752)
[stderr] at org.knopflerfish.bundle.component.Component.satisfied(Component.java:483)
[stderr] at org.knopflerfish.bundle.component.Component.refAvailable(Component.java:714)
[stderr] at org.knopflerfish.bundle.component.Reference.refAvailable(Reference.java:456)
[stderr] at org.knopflerfish.bundle.component.ReferenceListener.serviceChanged(ReferenceListener.java:497)
[stderr] at org.knopflerfish.bundle.component.ReferenceListener.serviceEvent(ReferenceListener.java:455)
[stderr] at org.knopflerfish.bundle.component.ComponentServiceListener.serviceChanged(ComponentServiceListener.java:71)
[stderr] at org.knopflerfish.framework.Listeners.serviceChanged(Listeners.java:430)
[stderr] at org.knopflerfish.framework.PermissionOps.callServiceChanged(PermissionOps.java:340)
[stderr] at org.knopflerfish.framework.Services.register(Services.java:179)
[stderr] at org.knopflerfish.framework.BundleContextImpl.registerService(BundleContextImpl.java:274)
[stderr] at org.knopflerfish.bundle.component.ComponentService.registerService(ComponentService.java:72)
[stderr] at org.knopflerfish.bundle.component.ComponentConfiguration.registerComponentService(ComponentConfiguration.java:376)
[stderr] at org.knopflerfish.bundle.component.ImmediateComponent.activateComponentConfiguration(ImmediateComponent.java:60)
[stderr] at org.knopflerfish.bundle.component.Component.satisfied(Component.java:485)
[stderr] at org.knopflerfish.bundle.component.Component.resolvedConstraint(Component.java:582)
[stderr] at org.knopflerfish.bundle.component.Component.cmConfigUpdated(Component.java:645)
[stderr] at org.knopflerfish.bundle.component.CMConfig.cmPidSet(CMConfig.java:241)
[stderr] at org.knopflerfish.bundle.component.CMConfig.access$100(CMConfig.java:53)
[stderr] at org.knopflerfish.bundle.component.CMConfig$CMPid.set(CMConfig.java:341)
[stderr] at org.knopflerfish.bundle.component.CMConfig$CMPid.configUpdated(CMConfig.java:391)
[stderr] at org.knopflerfish.bundle.component.CMHandler.configurationEvent(CMHandler.java:116)
[stderr] at org.knopflerfish.bundle.cm.ListenerEvent.sendEvent(ListenerEvent.java:84)
[stderr] at org.knopflerfish.bundle.cm.ListenerEventQueue.run(ListenerEventQueue.java:90)
[stderr] at java.lang.Thread.run(Thread.java:745)
[stdout] === TargetService Activate 1 ===
I think the problem is in how Reference.update method works.
void update(String ccid, Map dict, boolean useNoId) {
boolean before = isSatisfied();
boolean doCheck = updateMinCardinality(dict) && isSatisfied() != before;
if (listener != null) {
// We only have one listener, check if it still is true;
if (listener.checkTargetChanged(ccid, dict)) {
if (listener.isOnlyId(useNoId ? Component.NO_CCID : ccid)) {
// Only one ccid change listener target
listener.setTarget(ccid, dict);
} else {
// We have multiple listener we need multiple listeners
factoryListeners = new TreeMap();
for (String p : listener.getIds()) {
factoryListeners.put(p, listener);
}
listener = null;
// NYI, optimize, we don't have to checkTargetChanged again
}
} else if (useNoId) {
// No change, just make sure that ccid is registered
listener.addId(ccid, true);
}
}
...
The first call to this method (ccid = "Component._0") sets the target to (number=1).
The second call to this method (with ccid = "Component._1") has no effect:
listener.checkTargetChanged return false, because target hasn't changed since the previous call ((number=1)),
and useNoId is false, so neither branch there is taken and ccid "Component._1" is lost.
The third call creates factoryListeners with only two entries:
factoryListeners = {TreeMap@3447} size = 2
0 = {TreeMap$Entry@3453} "Component._0" -> "ReferenceListener(Reference TargetService in Immediate component: Component, target=(number=1))"
1 = {TreeMap$Entry@3454} "Component._2" -> "ReferenceListener(Reference TargetService in Immediate component: Component, target=(number=2))"
Later, Component.newComponentConfiguration("Component._1".....) calls ref.getListener("Component._1").numAvailable( ), but ref.getListener("Component._1") returns null.