===== 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. [[http://karaf.apache.org/|Karaf]] is strongly suggested * A [[http://mosquitto.org/|Mosquitto]] MQTT broker or another A project containing most of the examples shown in this guide can be downloaded at this [[http://ala.isti.cnr.it/svn/wnlab/SensorWeaver/sample-sensor/trunk/|location]] ==== Development environment setup ==== You'll need Java 8 JDK to compile and run your project. [[http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html|Download]] and install one for your system. SensorWeaver components are distributed using Maven [[http://ala.isti.cnr.it:8081/nexus/index.html#welcome|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: wnlab wnlab-snapshots http://ala.isti.cnr.it:8081/nexus/content/repositories/wnlab-snapshots false always warn true always fail wnlab-releases http://ala.isti.cnr.it:8081/nexus/content/repositories/wnlab-releases true always warn fail always fail wnlab 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 [[http://ala.isti.cnr.it/svn/wnlab/SensorWeaver/|WNLab Subversion]] repository if needed. ==== Project setup ==== Create a Maven project using the IDE of choice. The project must generate an OSGi bundle, so: *Set packaging type to //bundle// *Set Java compiler with [[http://maven.apache.org/plugins/maven-compiler-plugin/|maven compiler plugin]] *Include the [[http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html|maven bundle plugin]] in order to produce a jar bundle with OSGi manifest. bundle org.apache.maven.plugins maven-compiler-plugin 3.1 1.8 1.8 org.apache.felix maven-bundle-plugin 2.3.7 true ${project.name} ${project.description} ${project.artifactId} Your Company Name **Tip**: You can find a list of manifest headers on the [[http://wiki.osgi.org/wiki/Category:Manifest_Header|OSGI wiki]] Then add the //middleware-api// dependency to your project //pom.xml//: it.cnr.isti.sensorweaver.middleware middleware-api 2.1.0 Tip: Check the latest //middleware-api// version on [[http://ala.isti.cnr.it:8081/nexus/index.html#nexus-search;gav~it.cnr.isti.sensorweaver.middleware~middleware-api~~~|WNLab Nexus Repo]] or use [[https://maven.apache.org/enforcer/enforcer-rules/versionRanges.html|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. 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: 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 [[http://aries.apache.org/modules/blueprint.html|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 ([[http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html|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 announceDataFeed(DataFeedDescriptor descriptor); public Future registerDiscoverer( DataFeedDiscoverer discoverer); public Future 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 [[http://ala.isti.cnr.it:8081/nexus/index.html#nexus-search;gav~it.cnr.isti.sensorweaver.middleware~middleware-api~~~|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 [[http://karaf.apache.org/|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 [[http://ala.isti.cnr.it:8081/nexus/index.html#nexus-search;quick~sensorweaver-features|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 [[sensorweaver:admin-guide|Administrator Guide]] in order to setup and configure your OSGI environment.