User Tools

Site Tools


sensorweaver:developer-guide

SensorWeaver Developer Guide

SensorWeaver is Java software. Middleware components are available as OSGi bundles, available publicly in Maven repositories. This guide shows how to use SensorWeaver Middleware in your software projects.

General requirements:

  • Knowledge of Java, OSGi, Maven.
  • Java Development Kit 8
  • An IDE, preferably with Maven support
  • For testing/deployment
    • An OSGi container for testing. Karaf is strongly suggested
    • A Mosquitto MQTT broker or another

A project containing most of the examples shown in this guide can be downloaded at this location

Development environment setup

You'll need Java 8 JDK to compile and run your project. Download and install one for your system.

SensorWeaver components are distributed using Maven WNLab repositories.

In order to access released and development JARs you need to configure your development environment.

The best way to achieve this is to add third-party repos to your global maven settings.xml in the default profile. This removes the need of configuring each maven project.

Configure your Maven settings.xml according to the following:

settings.xml
<profiles>
   <profile>
      <id>wnlab</id>
      <repositories>
         <!-- WNlab Repos -->
         <repository>
            <id>wnlab-snapshots</id>
            <url>http://ala.isti.cnr.it:8081/nexus/content/repositories/wnlab-snapshots</url>
            <releases>
               <enabled>false</enabled>
               <updatePolicy>always</updatePolicy>
               <checksumPolicy>warn</checksumPolicy>
            </releases>
            <snapshots>
               <enabled>true</enabled>
               <updatePolicy>always</updatePolicy>
               <checksumPolicy>fail</checksumPolicy>
            </snapshots>
         </repository>
         <repository>
            <id>wnlab-releases</id>
            <url>http://ala.isti.cnr.it:8081/nexus/content/repositories/wnlab-releases</url>
            <releases>
               <enabled>true</enabled>
               <updatePolicy>always</updatePolicy>
               <checksumPolicy>warn</checksumPolicy>
            </releases>
            <snapshots>
               <enabled>fail</enabled>
               <updatePolicy>always</updatePolicy>
               <checksumPolicy>fail</checksumPolicy>
            </snapshots>
         </repository>
      </repositories>
   </profile>
</profiles>
<activeProfiles>
   <activeProfile>wnlab</activeProfile>
</activeProfiles>

As an alternative, you can include just the repositories tag listed above, inside of your Maven project pom.xml.

Maven repositories include documentation and source code artifacts.

Source code can be accessed in read-only in WNLab Subversion repository if needed.

Project setup

Create a Maven project using the IDE of choice.

The project must generate an OSGi bundle, so:

pom.xml
<packaging>bundle</bundle>
 
<!-- Other declarations -->
 
<build>
   <plugins>
      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <version>3.1</version>
         <configuration>
            <source>1.8</source>
            <target>1.8</target>
         </configuration>
      </plugin>
      <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
         <version>2.3.7</version>
         <extensions>true</extensions>
         <configuration>
            <instructions>
               <Bundle-Name>${project.name}</Bundle-Name>
               <Bundle-Description>${project.description}</Bundle-Description>
               <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
               <Bundle-Provider>Your Company Name</Bundle-Provider>
               <!-- Here you can put other manifest headers -->
            </instructions>
         </configuration>
      </plugin>
   </plugins>
</build>

Tip: You can find a list of manifest headers on the OSGI wiki

Then add the middleware-api dependency to your project pom.xml:

pom.xml
<dependencies>
   <dependency>
      <groupId>it.cnr.isti.sensorweaver.middleware</groupId>
      <artifactId>middleware-api</artifactId>
      <version>2.1.0</version>
   </dependency>
</dependencies>

Tip: Check the latest middleware-api version on WNLab Nexus Repo or use maven version ranges to be up to date with the latest API.

Getting an instance of the Middleware service

Inside of the OSGi runtime environment, the Middleware stands as an OSGi service. Clients that needs Middleware services have to obtain an instance through the OSGi registry. There are several ways to achieve this. Here we list two solutions:

  • Querying the service registry programmatically using BundleActivator
  • Blueprint (strongly recommended)

Blueprint

Blueprint ease the components wiring task and removes the need of having OSGi dependencies in the project. In order to use blueprint the developer must take into account that a blueprint specification implementation will be required at runtime. Middleware 2.0 is based on blueprint, therefore a blueprint implementation is needed anyhow.

You won't need OSGi dependencies in your pom, so remove them if unused.

Let's assume that you want to wire the Middleware instance with your component, MySensorPublisher, when the bundle starts.

MySensorPublisher.java
package it.cnr.isti.sample;
 
import it.cnr.isti.sensorweaver.middleware.api.Middleware;
 
public class MySensorPublisher {
 
	private Middleware middleware;
 
	public MySensorPublisher(Middleware middleware) {
		this.middleware = middleware;
	}
 
	public void start() {
		// This method is called when the bundle starts, your application logic starts here.
	}
 
	public void stop() {
		// This method is called when the bundle stops, use it to clean resources, close connections, etc.
	}
 
}

Assuming that you're following Maven conventions on project structure, create the folder src/main/resources/OSGI-INF/blueprint.

Create a services.xml file inside of this folder with this content:

services.xml
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
	xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
	http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd 
	http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.1.0.xsd">
 
	<reference id="middleware"
		interface="it.cnr.isti.sensorweaver.middleware.api.Middleware" availability="mandatory" timeout="10000" />
 
	<bean id="mySensorPublisher" class="it.cnr.isti.sample.MySensorPublisher"
		activation="eager" init-method="start" destroy-method="stop" depends-on="middleware" >
		<argument ref="middleware" />
	</bean>
</blueprint>

This configuration basically tells the blueprint runtime to

  • create an instance of MySensorPublisher when the bundle starts (activation=eager) by wiring it with a proxy reference of the Middleware using the constructor
  • call the method start of the instance when the bundle is started and the bean is initialized
  • call the method stop of the instance when the bundle gets stopped

That is enough to automatically manage the lifecycle and wiring of your component. For additional documentation on blueprint you can take a look at Apache Aries or the OSGi compendium specifications for OSGi 4.x.x.

API and usage

Middleware service methods executes asynchronous tasks when called. Whenever a method, is called a Future object is returned (documentation). This solution removes the need to implement callbacks for every method call, improving code readability. The client can synchronize with the end of the middleware task by calling the get method on the Future instance, retrieving a result of the computation, when expected, or an asynchronous task execution exception.

Here is the Middleware API:

package it.cnr.isti.sensorweaver.middleware.api;
 
public interface Middleware {
 
	public Future<DataFeedClient> announceDataFeed(DataFeedDescriptor descriptor);
 
	public Future<RegistrationToken> registerDiscoverer(
			DataFeedDiscoverer discoverer);
 
	public Future<RegistrationToken> registerListener(DataFeedListener listener);
 
}
  • announceDataFeed allows to announce the availability of a new data feed on the Middleware. It returns, asynchronously, a DataFeedClient instance that is used for sending data feed messages and unregister the datafeed.
  • registerDiscoverer allows to register a DataFeedDiscoverer instance, a callback object that is notified about lifecycle events related to a set of data feeds.
  • registerListener allows to register a DataFeedListener instance, a callback object that is notified each time a message is produced by a data feed

The complete set of API can be found in the middleware-api bundle.

Publish a Data Feed

In order to announce a data feed, you have to describe:

  • A set of properties (e.g.: hardware identification of sensor, monitored feature, etc.)
  • Message elements that will be contained in each data feed message

You can use a DataFeedDescriptor builder in order to accomplish this, like in the following example:

import it.cnr.isti.sensorweaver.middleware.api.datafeed.descriptor.DataFeedDescriptor;
import it.cnr.isti.sensorweaver.middleware.api.datafeed.descriptor.DataFeedBuilder;
import it.cnr.isti.sensorweaver.middleware.api.common.descriptor.Property;
 
/** ... **/
 
private DataFeedDescriptor buildDescriptor() {
	DataFeedBuilder builder = DataFeedDescriptor.builder();
	builder.property("hardwareId", "ABCD1234");
	builder.property("sensorType", "temperatureSensor");
	builder.parameter("temperature", new Property("Unit of Measure","Celsius"));
	return builder.build();
}

The descriptor can then be used to announce the availability of the datafeed to other distributed Middleware clients:

import it.cnr.isti.sensorweaver.middleware.api.datafeed.DataFeedClient;
 
/** ... **/
 
private void announceSensor() throws ExecutionException, InterruptedException {
	DataFeedDescriptor descriptor = buildDescriptor();
	DataFeedClient dataFeedClient = middleware.announceDataFeed(descriptor).get();
}

Publish Data Feed data

Once the client has a DataFeedClient it can start publishing messages, like in the following example:

import it.cnr.isti.sensorweaver.middleware.api.common.MessageBuilder;
 
/** ... **/
 
private void sendMessage() {
	MessageBuilder messageBuilder = dataFeedClient.buildMessage();
	messageBuilder.entry("temperature", Integer.toString(datasource.getValue()));
	messageBuilder.timestamp(Calendar.getInstance().getTimeInMillis());
	messageBuilder.send();
}

In order to publish a message for a given data feed you must produce values for all of the message elements defined previously in the data feed descriptor.

The datafeed client includes a method timestamp for setting the timestamp of the message. If not used, the middleware will set the timestamp at the time of the invocation of the send method.

Unregistering a Data Feed

To unregister a data feed you just have to call the method unregister on the related DataFeedClient.

private void unregister() throws InterruptedException, ExecutionException {
	dataFeedClient.unregister().get();
}

The operation notifies components listening to lifecycle events of the data feed that it went down. Sending new messages with the same data feed client will result in a runtime error.

Discover Data Feeds

In order to discover new data feeds you need to define a DataFeedDiscoverer first, like the following:

import it.cnr.isti.sensorweaver.middleware.api.datafeed.DataFeedDiscoverer;
/** ... **/
 
public class TemperatureSensorDiscoverer implements DataFeedDiscoverer {
 
	public void onFound(DataFeedDescriptor descriptor) {
		startListening(descriptor);
	}
 
	public void onRemoved(DataFeedDescriptor descriptor) {
		stopListening(descriptor);
	}
 
	public DescriptorFilter getFilter() {
		return new DescriptorFilter(new Property("sensorType", "temperatureSensor"));
	}
 
        /** other methods **/
}

The discoverer provides a filter which tells the Middleware about which data feeds the discoverer has to be notified on data feed lifecycle events. The filter matches with announced data feed descriptors that have the same property values.

To activate a discoverer you must register it on the Middleware;

Registration discovererToken = middleware.registerDiscoverer(myDiscoverer).get();

The registration of a discoverer returns a token which can be used later for unregistration:

discovererToken.unregister().get();

Listening to data feeds

In order to start listening for a data feed the middleware client must provide a DataFeedListener.

Each listener will listen to messages sent by a specific data feed.

    RegistrationToken registrationToken;
 
    /** ... **/
 
private void startListening(DataFeedDescriptor descriptor) throws InterruptedException, ExecutionException {
    registrationToken = middleware.registerListener(new LoggingListener(descriptor)).get();
}
 
private void stopListening() throws InterruptedException, ExecutionException {
    registrationToken.unregister().get();
}

The same unregistration mechanisms of DataFeedDiscoverer applies to DataFeedListener.

Testing your app

Applications can be easily tested in the Karaf OSGi environment, using provided logging and debugging tools.

You can download your preferred Apache Karaf release (latest version is strongly recommended) and install the middleware feature from the latest SensorWeaver karaf feature repository to start up your container.

Deafult feature configuration assumes that you have an MQTT broker online on port 1883 on your local machine.

Please refer to the Administrator Guide in order to setup and configure your OSGI environment.

sensorweaver/developer-guide.txt · Last modified: 2015-11-10 17:46 by luigi.fortunati