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:
A project containing most of the examples shown in this guide can be downloaded at this location
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:
<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.
Create a Maven project using the IDE of choice.
The project must generate an OSGi bundle, so:
<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:
<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.
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:
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.
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:
<?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
MySensorPublisher
when the bundle starts (activation=eager) by wiring it with a proxy reference of the Middleware using the constructorstart
of the instance when the bundle is started and the bean is initializedstop
of the instance when the bundle gets stoppedThat 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.
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.
In order to announce a data feed, you have to describe:
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(); }
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.
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.
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();
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
.
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.