1

I have created a (very) simple test to determine how to send and receive events using Apache Felix.

This is my sender:

package be.pxl;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;

import java.util.HashMap;

@Component(name = "be.pxl.Publisher", immediate = true)
public class Publisher {

    EventAdmin admin;

    @Activate
    public void run(Object object) {
        System.out.println("IN PUBLISHER");
        Event event = new Event("event", new HashMap<String, Object>());
        System.out.println("\tEVENT: " + event);
        admin.postEvent(event);
        System.out.println("\tADMIN: " + admin);
    }

    @Reference(name="be.pxl.admin", service = EventAdmin.class)
    protected void setEventAdmin(EventAdmin admin) {
        this.admin = admin;
    }

}

This is my receiver:

package be.pxl;

import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import java.util.Dictionary;
import java.util.Hashtable;

@Component(name = "be.pxl.Subscriber", immediate = true)
public class Subscriber implements EventHandler {

    private BundleContext context;

    @Activate
    public void run(Object object) {
        System.out.println("IN SUBSCRIBER");
        System.out.println("\tIN RUN METHOD");
        String[] topics = new String[]{"event"};
        Dictionary props = new Hashtable();
        props.put(EventConstants.EVENT_TOPIC, topics);
        System.out.println("\t\tCONTEXT: " + context);
        context.registerService(EventHandler.class.getName(), this, props);
        System.out.println("\t\tCONTEXT AFTER REGISTERSERVICE: " + context);
    }

    public void handleEvent(Event event) {
        System.out.println("IN SUBSCRIBER");
        String text = event.getProperty("text").toString();
        System.out.println("\tEVENT CALLED: " + text);
    }

    @Reference(name="be.pxl.context", service=BundleContext.class)
    protected void setBundleContex(BundleContext context) {
        this.context = context;
    }

}

This is the pom of my sender:

    <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>be.pxl</groupId>
    <artifactId>EventSender</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.service.event</artifactId>
            <version>1.3.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.core</artifactId>
            <version>6.0.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.service.component.annotations</artifactId>
            <version>1.3.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.osgi</groupId>
            <artifactId>org.eclipse.osgi.services</artifactId>
            <version>3.2.100.v20100503</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>2.4.0</version>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Bundle-Vendor>SmartCampus</Bundle-Vendor>
                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
                        <Export-Package>
                            be.pxl.*;version="1.0.0"
                        </Export-Package>
                        <Import-Package>
                            org.osgi.service.component.annotations
                            org.eclipse.osgi.service
                            org.osgi.core
                            org.osgi.service.event
                        </Import-Package>
                        <_dsannotations>*</_dsannotations>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Everything compiles fine. I create it using mvn clean package, then I install this jar file in my apache felix container and start it. However, nothing happens. Nothing get pritns out.

Thanks in advance!

1 Answers1

1

You appear to be most of the way there! As you've identified, Event Admin uses a whiteboard model to receive events. The important thing is that you need to tell the whiteboard which topics you want to listen to, which you do.

%%% Update %%%

Event admin topic names use a hierarchy of tokens separated by / characters. When publishing an event you do so to a specific topic, for example foo/bar/baz. When receiving events the EventHandler will be called for topics that match its registered interest(s). These interests can either be for a specific topic, or they can end with a * to indicate a wildcard match. For example foo/bar/* would receive events sent to foo/bar/baz and events sent to foo/bar/fizzbuzz.

%%% Back to the original %%%

There are, however a couple of issues with your code:

Firstly:

@Reference(name="be.pxl.context", service=BundleContext.class)
protected void setBundleContex(BundleContext context) {
    this.context = context;
}

This is not how you access the BundleContext for your bundle. If you do need a BundleContext then it should be injected as a parameter into your @Activate annotated method. A BundleContext should never be registered as a service (it represents your bundle's private access to the OSGi framework), and it would not surprise me to find that this reference is unsatisfied in your example. You don't actually need the BundleContext however because...

Secondly:

@Activate
public void run(Object object) {
    System.out.println("IN SUBSCRIBER");
    System.out.println("\tIN RUN METHOD");
    String[] topics = new String[]{"event"};
    Dictionary props = new Hashtable();
    props.put(EventConstants.EVENT_TOPIC, topics);
    System.out.println("\t\tCONTEXT: " + context);
    context.registerService(EventHandler.class.getName(), this, props);
    System.out.println("\t\tCONTEXT AFTER REGISTERSERVICE: " + context);
}

This is not the right way to write an activate method (and as a result it may not be being called), nor should you be registering your component as a service here. When you make your class an @Component it will automatically be registered as a service using each directly implemented interface. This means that:

@Component(name = "be.pxl.Subscriber", immediate = true)
public class Subscriber implements EventHandler {
    ...
}

is already an OSGi EventHandler service!

You can add service properties to your component using the @Component annotation, or from the OSGi R7 release (due in a couple of months) using Component Property annotations. In this case you want to set your event.topics property like this:

@Component(property="event.topics=event")

You can then get rid of the activate method completely if you like.

Finally:

Event Admin is not a message queue, and your publisher is a one-shot send. Therefore if your publisher sends the event before the handler is fully registered then it will never receive the event. Consider making the publisher send periodic events, or be certain that the receiver starts before the publisher so that you see the message.

P.S.

It's not technically a problem, but I see that you're using version 2.4 of the maven-bundle-plugin. This is very old and the current released version of bnd is 3.5.0. The Bnd team have also started providing their own Maven plugins (such as the bnd-maven-plugin) that you might want to look at.

Tim Ward
  • 1,169
  • 8
  • 9
  • Thank you for this very helpful explanation! But how do I use the property that I declare with the Component annotation? Also, do I need a BundleActivator for my component to start? The purpose of this demo is to immediately start the subscriber, then start the publisher in the Apache Felix container, and print out the text that the publisher sent. Also, how can I be certain that the receiver starts before the publisher? Thanks in advance! – abstract christmas tree Dec 06 '17 at 19:01
  • **how do I use the property that I declare with @Component?** The answer already shows how to declare it, so I assume your question is about event admin topics. I have added a section for this. **do I need a BundleActivator?** Definitely not. I recommend that people don't write Bundle Activators as there's very little reason to use the low level API in modern OSGi applications. **how can I be certain that the receiver starts before the publisher?** If you have the gogo shell installed you can check DS components using the `scr:list` and `scr:info` commands – Tim Ward Dec 07 '17 at 08:35