Hello World Tutorial

In this tutorial we will create one publisher and one listener. The publisher will create multiple EHello events that listener will receive and print out on the standard output.

This tutorial will explain:

  • Creating a publisher.
  • Creating a listener.
  • Defining a simple event.
  • Defining a "catch-all" query.

The preferred way of completing this tutorial is to download the skeleton project and use it to implement functionality of this tutorial. For impatient ones, whole source code of the completed tutorial can be downloaded from here.

Publisher Top

First, we will create a publisher for the EHello event. The easiest way is for the publisher to extend the BasePublisher class. This class implements the ContextPublisher interface which COPAL expects for all publishers to implement and it abstracts away the boilerplate code for registration and unregistration process with COPAL and finding the required OSGi services. If you need more control during this process or the BasePublisher class does not behave as you want it to, you can always implement ContextPublisher interface and use it instead of BasePublisher.

Lets move forward with our implementation task, BasePublisher contains two abstract methods which our publisher needs to implement:

protected abstract boolean start(ContextEventType type);
protected abstract void stop(ContextEventType type);

These two methods notify the publisher when it should respectively start and stop publishing the events of specified type. If we try to publish an event of some type for which the start method was not called, a ContextException will be thrown. The result of the start methods should return the boolean value telling the underlying BasePublisher if the publisher has successfully started publishing events of specified type. The stop method will be invoked only when the start method returns true for specified type.

As first step we create a publisher in the publishers subproject that just print a message on the standard output when these methods were invoke:

import at.ac.tuwien.infosys.sm4all.copal.api.event.ContextEventType;
import at.ac.tuwien.infosys.sm4all.copal.api.publisher.BasePublisher;

public class HelloPublisher extends BasePublisher {

    public HelloPublisher() {
        super("HelloPublisher", "EHello");
    }

    @Override
    protected boolean start(final ContextEventType type) {
        System.out.println("publisher started for events: " + type.getName());
        return true;
    }

    @Override
    protected void stop(final ContextEventType type) {
        System.out.println("publisher stopped for events: " + type.getName());
    }
}

In the constructor, we must pass two values "HelloPublisher" and "EHello" to underlying BasePublisher‘s constructor. The first value tells the source ID which will be associated with every published event from this publisher. You can think of it as a name of the publisher, because it has to be unique within the namespace of all publisher source IDs. The second value tells all types of published events. To distinguish any other names in the system from event names, we made a simple, unenforced convention that it should start with a letter ‘E'. We could have easily just named the event "Hello" and it would not clash with the source ID of the publisher. ContextPublisher can specify that it is publishing more than one type of events, but in our example we are publishing only EHello events. The start method will be invoked separately for each published type.

We also need to register the publisher with COPAL. We can do this by extending the PublishersActivator class that implements the OSGi's BundleActivator interface and override its start method. The PublishersActivator class provides us with the mechanism to automatically register and unregister BasePublishers whenever the ContextPublisherRegistry becomes available, or unavailable respectively, as a service to underlying OSGi. Its start method is called when the bundle is activated, so we can use it to tell the activator which publisher to automatically register and unregister with COPAL.

import at.ac.tuwien.infosys.sm4all.copal.api.publisher.PublishersActivator;

public class Activator extends PublishersActivator {

    @Override
    protected void start() {
        System.out.println("publishers activator started");
        register(new HelloPublisher());
    }
}

If you used the skeleton project for this tutorial, you have to set the full path to this activator in the publishers subproject' pom.xml file as the value of the bundle.activator property. We can run the bundles with mvn install pax:run and you should see only this message on the standard output:

publishers activator started
osgi> close

You should notice that our publisher's start method was never invoked. The publishers was never invoked because COPAL does not know anything about the EHello event, which our publisher claims to publish, and waits for somebody to register the EHello event before it allows the publishing of it. This is where the publishers.cfg.xml file is used, because it allows us to define the published events. The PublishersActivator class will automatically read this file and register all events that are defined in it with COPAL. In the skeleton project, an empty publishers.cfg.xml file is located in the src/main/resources directory of the publishers subproject.

Defining EHello Event Top

To define the EHello event, the publishers.cfg.xml file should look like this:

<Context xmlns="http://www.sm4all-project.eu/COPAL">
  <Event name="EHello" />
</Context>

The EHello event does not carry any information within itself that COPAL should be aware of and therefore it does not need any additional configuration elements. This configuration does not tell that EHello event cannot carry any additional information, it just tells that COPAL should not care about the content of the EHello event, e.g. if the EHello event contained a Message element the COPAL system would just ignore it.

The above definition of the EHello event is actually an abbreviated version of this definition:

<Event name="EHello" rootElement= "EHello" />

We can remove rootElement whenever the name of the event and the name of the root element are same value. If we wanted to define an EHello event which has a different name of the root element (e.g. HelloWorld), we would need to explicitly state the name of the root element using rootElement. COPAL requires that each event has configured name of its root element, but in special cases we can use the COPAL's default behavior that name of the event and name of the root element are same value.

As we can see, the minimal definition of event is to give it a name, and only requirement is that that name must be unique in the namespace of all event names. There would be no sense if there were two different EHello events, because how would listener state in which one it is interested.

If we run the bundle now, we would see the expected messages:

publishers activator started
publisher started for events: EHello
osgi> close
publisher stopped for events: EHello

From this output, we can also see that the publisher's stop method is invoked when the bundle is stopped. This is what one should expect, because when OSGi is stopping, it will invoke the stop method in the PublishersActivator class, which automatically stops all registered publishers. This makes the stop method good place for any cleanup code that publisher needs to do before it stops publishing events of specified type.

Publishing EHelo Event Top

Now is time to actually publish EHello events. We will need to instantiate a XML DocumentBuilder class because all events in COPAL are published as XML Documents, and then we will create a simple Timer that publishes an EHello event every two seconds in the start method and stop it in the stop method. We use the BasePublisher's publish(ContextEvent) method to publish created EHello events.

import java.util.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;

import at.ac.tuwien.infosys.sm4all.copal.api.ContextException;
import at.ac.tuwien.infosys.sm4all.copal.api.event.ContextEventType;
import at.ac.tuwien.infosys.sm4all.copal.api.event.xml.XMLEvent;
import at.ac.tuwien.infosys.sm4all.copal.api.event.xml.XMLEventType;
import at.ac.tuwien.infosys.sm4all.copal.api.publisher.BasePublisher;

public class HelloPublisher extends BasePublisher {

    private Timer timer;

    public HelloPublisher() {
        super("HelloPublisher", "EHello");
    }

    @Override
    protected boolean start(final ContextEventType type) {
        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);

        try {
            final DocumentBuilder builder = factory.newDocumentBuilder();
            this.timer = new Timer();
            this.timer.schedule(new TimerTask() {

                @Override
                public void run() {
                    final Document event = builder.newDocument();
                    event.appendChild(event.createElementNS(null, "EHello"));
                    try {
                        publish(new XMLEvent((XMLEventType) type, getSourceID(), event));
                        System.out.println("EHello event published");
                    } catch (final ContextException ex) {
                        System.out.println("Something went wrong");
                        ex.printStackTrace();
                    }
                }
            }, 0, 2000);

            System.out.println("publisher started for events: " + type.getName());
            return true;
        } catch (final ParserConfigurationException ex) {
            return false;
        }
    }

    @Override
    protected void stop(final ContextEventType type) {
        this.timer.cancel();
        System.out.println("publisher stopped for events: " + type.getName());
    }
}

When we run the bundle, we should see something like this on the standard output:

publishers activator started
publisher started for events: EHello
EHello published
EHello published
EHello published
...

The creation of same event over and over again can be very tedious, so we provided you with a helper method createEvent(DocumentBuilder) in the XMLContextEventType class. This method creates an instance of XML Document with a root element that has local name set to value of rootElement from event's definition. Remember that if the rootElement is missing than it has same value as name of the event. The createEvent method plus the fact that the XML Document is cloned before its is published means that we can reuse one XML Document when publishing. Each invocation of publish will actually publish a cloned instance of specified XML Document, therefore, any change to the XML Document after it is published will not change the published event, because it is a different instance of XML Document. The improved version of the start method looks like:

@Override
public boolean start(final ContextEventType type) {
    final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(true);

    try {
        final DocumentBuilder builder = factory.newDocumentBuilder();
        final Document event = ((XMLEventType) type).createEvent(builder);

        this.timer = new Timer();
        this.timer.schedule(new TimerTask() {

            @Override
            public void run() {
                try {
                    publish(new XMLEvent((XMLEventType) type, getSourceID(), event));
                    System.out.println("EHello event published");
                } catch (final ContextException ex) {
                    System.out.println("Something went wrong");
                    ex.printStackTrace();
                }
            }
        }, 0, 2000);

        System.out.println("publisher started for events: " + type.getName());
        return true;
    } catch (final ParserConfigurationException ex) {
        return false;
    }
}

If we run this example we should see the same result as in previous run.

Query & Listener Top

Now we can focus on receiving published EHello events. We do this by creating a query that receives all EHello events and registering a listener with it. You can register more than one listener with each query, and each listener can be registered to more than one query. The listeners subproject will hold the listener.

We define queries in the listeners.cfg.xml file (similarly to how we defined EHello event in the publisher.cfg.xml file). In our case, listener wants to be notified whenever an EHello event is published, therefore, the listeners.cfg.xml file looks like:

<Context xmlns="http://www.sm4all-project.eu/COPAL">
  <Query name="EHello.All" event="EHello" />
</Context>

When defining events, each query must have an unique name in the namespace of query names and at least must have the name of the event for which it receives notifications. The name of the event is the same name that is used in the publishers.cfg.xml to name the event. This query will be notified whenever an EHello event is published, because it does not have any other configuration elements.

A listener can be implemented either by implementing the ContextListener interface or extending the BaseListener class. The BaseListener is very simple and only implements the getName() method in the ContextListener interface. The interface also defines onEvent(ContextEvent) method which is called by the query, to which the listener is registered, whenever an event is received. Our listener will just print out a message when it receives an event:

import at.ac.tuwien.infosys.sm4all.copal.api.event.ContextEvent;
import at.ac.tuwien.infosys.sm4all.copal.api.listener.BaseListener;

public class SimpleListener extends BaseListener {

    public SimpleListener() {
        super("SimpleListener");
    }

    @Override
    public void onEvent(final ContextEvent event) {
        System.out.println(event.getType().getName() + " received");
    }
}

In the constructor, we pass the name of this listener to underlying BaseListener‘s constructor. The name of the listener must be unique within each query i.e. we can have two different listeners with same name as long as they are not registered to same query.

To register this listener with the query defined in the listeners.cfg.xml file, we use the ListenersActivator that also implements the OSGi's BundleActivator interface and override its start method. Similarly to the PublishersActivator class, the ListenersActivator class provides us with the mechanism to automatically create queries defined in the listeners.cfg.xml file and to register and unregister ContextListeners with it. Same as before, if you used the skeleton project for this tutorial, you have to also set the full path to this activator in the listeners subproject' pom.xml file as the value of the bundle.activator property.

import at.ac.tuwien.infosys.sm4all.copal.api.listener.ListenersActivator;

public class Activator extends ListenersActivator {

    @Override
    protected void start() {
        System.out.println("listeners activator started");
        register("EHello.All", new SimpleListener());
    }
}

As you can see we use the name of the query to register our listener. If we run the listener and publisher bundles, we would see something similar to this on the standard output:

publishers activator started
publisher started for events: EHello
listeners activator started
EHello received
EHello event published
EHello received
EHello event published
EHello received
EHello event published
...

This finishes the tutorial that implements a simple Hello World program using COPAL to pass the EHello events between publisher and listener.