What can Fuchsia do for me?

Fuchsia can materialize an outcast service (not visible in the OSGi platform) into the OSGi platform. Natively fuchsia can materialize:

  • mDNS/DNSSD, bringing up the provider’s IP and the service type. This allows you to connect directly to the service without having to implement the discovery

  • UPnP services published on the network

  • JAX-WS endpoint, creating an instance of the service in OSGi platform (the interfaces must be available in the platform)

  • JSON-RPC endpoint, makes available into the platform a service for this remote instance, as expected it is mandatory to have the interfaces in order to create the OSGi instance of the endpoint

  • PuSH (PUbSubHubbub) hub, if you have a feed that uses PuSH as hub, all updates are sent via eventAdmin. Thus, avoiding you to implement the protocol

  • MQTT provider, so you can receive your MQTT messages locally (via EventAdmin) without having to implement the subscriber for this protocol

All those are already built-in features, the materialization of an external service into OSGi platform, this processor is called: Importer.

The second type of processor is called Exporter, this type enables us to turn an OSGi service instance into a service that can be access outside of OSGi, exposed as a web service for instance. The built-in exporters are:

  • JAX-WS, based in an OSGi service instance, publishes a WebService to be accessed remotely. You can use Fuchsia to instantiate the exported service or use directly Apache CXF.

Of course those processors (importers or exporters) are available without needing to code a single line.

Albeit, Fuchsia has built-in processors, we are aware that most of applications use their own protocol or adopt their protocol specific device, knowing that Fuchsia was build to be extensible, thus implementing and deploying a customized processor is quite straight forward.

Core concepts

Fuchsia

Fuchsia is an extensible tool for integrating external communication protocols inside an OSGi platform. It provides a common interface for creating and managing external connections.

As most of modern applications require multiple sources of information such as web services, databases or ubiquitous devices. The integration among those different services become a major issue when dealing with multitude of them.

In ubiquitous applications this kind of integration is even more important since the dynamicity and the multitude of protocol grows with number of devices supported.

So, developers are suddenly facing the challenge of mastering several protocol, and their respective tools for debugging, dependency managing, native calls access, access configuration, etc. That is where Fuchsia plays a key role, by providing the same way of dealing with different protocols, creating a single entry-point for all protocols supported.

In Appsgate all those characteristics are gathered in order to provide an Smart Home environment.

You can administrate and monitor all the exchange of your OSGi platform with externals protocols through an unique control panel.

There exist essentially 4 types of entities inside fuchsia that you need to understand before go hands-on the code:
  • Declaration, it is the metadata that describes a device or a service

  • Discovery, responsible for the detection of a device/service, in Fuchsia that means create a Declaration and adding it to the OSGi registry

  • Processors, it represents the task itself, being split in two types Importer or Exporter

  • Linkers, connects a declaration to a processor, evaluate the constraints to be respected in order to connect those two.

Declarations

Think in Declaration as a property file, although that it is not a file, it is an service registered in an OSGi platform, as long as this service is registered with the proper platform, Fuchsia will recognize it.

Its function is to indicate that a given device/service is available with certain properties, and is the role of an Importer/Exporter (explained later) to receive those information as inputs and create a bridge between the OSGi platform and this device (all by using the properties given by the Declaration), allowing other services to access this device without knowing its protocol.

Discoveries

As seen before, the way that we inform the Fuchsia platform the existence of a device/service is through a declaration Declaration, that can be registered manually of course (since its a service registered in OSGi platform with the interface org.ow2.chameleon.fuchsia.core.declaration.ImportDeclaration), but this can be done by fuchsia discoveries, that use specialized discovery protocols to extract all the relevant information of a given service/device and publish in OSGi platform a Declaration containing all those informations.

Available discovery implementations:
  • Filebased discovery

  • mDNS/DNSsd

  • uPnP

Processors

Importers

In order to trigger importers to perform their job, they must to receive an import declaration which is configuration (think of it as a key value mapping, just like java properties). The importation declaration will recognized by Fuchsia as long as they are registered in OSGi with the proper Fuchsia interface (for an importer for example must be org.ow2.chameleon.fuchsia.core.declaration.ImportDeclaration )

Available implementations:
  • JAX-RS

  • JAX-WS

  • JSON-RPC

  • MQTT

  • PUbSubHubBub

  • Protobuffer RPC

Exporters

Analogue to the importers, the exporters must to receive a declaration, but in this case an export declaration. But its role is to provide an external access (by external meaning externally to the OSGi platform) to the services available in the current OSGi platform, for instance by publishing a WS.

Available implementations:
  • JAX-WS

  • Protobuffer RPC

Linkers

The existence of a declaration and a processor is not enough to trigger a connection between them, there has to exist a linker. This linker is responsible to check the condition on both sides (processor and declaration) see if they match, and just then establish a connection between them.

Installation

Using as maven dependency

Adopting Fuchsia is a straightforward task if you are used to Maven. First we need to add the OW2 maven repository to our list of repositories (as below).

<project ... >
...
<repositories>
	<repository>
		<id>ow2-release</id>
		<name>OW2-Release</name>
		<url>http://repository.ow2.org/nexus/content/groups/public/</url>
	</repository>
	<repository>
		<id>ow2-snapshot</id>
		<name>OW2-Snapshot</name>
		<url>http://repository.ow2.org/nexus/content/repositories/snapshots/</url>
	</repository>
</repositories>
...
</project>

Now, we have to declare one or more Fuchsia module as a maven dependency of your project.

<dependency>
    <groupId>org.ow2.chameleon.fuchsia</groupId>
    <artifactId>org.ow2.chameleon.fuchsia.core</artifactId>
    <version>0.0.1</version>
</dependency>

Here we reference the version 0.0.1 of Fuchsia, but of course you can update that version for the version you are willing to use.

Note
The catch

This method is quick and fast to integrate for compilation, although, depending on the importer adopted, it may require 3td party dependencies to be installed in your runtime platform manually. In order to not worry in finding all the dependencies for the importer adopted, you can use Fuchsia compilation profiles to gather all the dependencies needed according to your importers.

Compiling a distribution

In order to generate a Fuchsia distribution there is only one mandatory profile the core, all the other modules are optional and can be included in the distribution by adding the appropriate maven profile ( mvn -P parameter ). The maven profile and its contents are specified right below.

  • core - This module contains all the basic Fuchsia types, fuchsia shell tool is installed automatically in this profile as well, even though its not part of the bundle core

  • discoveries

    • mdns - the implementation of dnssd/mdns discovery protocol

    • upnp

    • discovery-filebased

    • discovery-philips-hue

  • importers

    • importer-jax-ws

    • importer-philips-hue

    • mqtt

    • push

  • exporter

    • exporter-jaw-ws

  • tools

    • grid

    • shell

Configuration profiles

Config profiles are the instantiation of the linker and the processor. Those two tend to be customized, since the filter adopted in the default instantiation of the linker might not be valid for for case

  • discoveries:

    • mdns-config

  • importers:

    • importer-cxf-config

    • mqtt-config

    • push-config

  • exporters:

    • exporter-cxf-config

Thus, in order to compile Fuchsia with the grid, exporter-jax-ws and mqtt, would be enough to:

  1. go to the distribution directory

  2. type mvn -Pcore,grid,exporter-jax-ws,mqtt

After the compilation process is finished, your Fuchsia distribution is ready to use:

  1. go to the distribution/target/chameleon-distribution/

  2. type ./chameleon.sh --interactive (or the apropriate script according to your OS)

Input-Output of processors

As seen in [Fuchsia 101](Fuchsia-101), importers use import declaration as input. The information contained in the import declaration must be enough in order that the processor execute its role properly.

JAX-WS importer

External Requirement:
  • JAX-WS service published

  • Interface(a') that represents the WSDL available in the OSGi platform that will import the service

Import Declaration information required:
  • jax-ws.importer.interfaces

  • className

  • endpoint.url

Output:
  • OSGi service registered with a' representing the JAX-WS (Remote service)

Detailed Description

Today to import the JAX-WS service Fushsia uses CXF. CXF is used in order to reduce the complexity in creating the connection and handling with different issues that may be raised.

MQTT importer

External Requirement:
  • MQTT server with the address known

  • EventAdmin service configured in the machine that will import this service

Import Declaration information required:
  • id

  • mqtt.queue

Output:
  • A message will be published in EventAdmin in the queue $mqtt.queue, meaning that it will use the same name of queue as MQTT

Detailed Description

MQTT is a protocol largely diffused protocol for message exchange due to its good performance. The implementation used by Fuchsia to support MQTT was RabbitMQ

PubSubHubBub importer

PubSubHubBub, or simply PuSH, is a publish/subscribe protocol created by google and used by some of its tools.

External Requirement:
  • PuSh server address

Import Declaration information required:
  • push.hub.url

  • push.hub.topic

  • push.subscriber.callback

  • push.eventAdmin.queue

Output:
  • A message will be published in EventAdmin in the queue $push.eventAdmin.queue, meaning that it will use the same name of queue as PuSH

Detailed Description

There are no well known and portable PuSH implementations, Fuchsia implemented partially PuSH specification

Basic

Creating a bundle

We can use maven plugin to create an iPOJO artifact.

mvn org.apache.maven.plugins:maven-archetype-plugin:generate \
-DinteractiveMode=false \
-DarchetypeArtifactId=maven-ipojo-plugin \
-DarchetypeGroupId=org.apache.felix \
-DarchetypeVersion=1.11.0 \
-DartifactId=config \
-DgroupId=org.adele \
-Dpackage=org.adele \
-Dversion=0.0.1-EXAMPLE

Make sure to add Fuchsia dependency:

<dependency>
    <groupId>org.ow2.chameleon.fuchsia</groupId>
    <artifactId>org.ow2.chameleon.fuchsia.core</artifactId>
    <version>0.0.1</version>
</dependency>

That is it, now you are ready to add your custom code.

Warning

Make sure to replace the version 0.0.1 of Fuchsia, by the version that you are using

Import a JAX-WS service

After create your project, we have couple tasks to accomplish before be able to use the imported JAX-WS service:

Prepare the environment
  • [ ] Instantiate the importer

  • [ ] Instantiate the linker

As result of fuchsia
  • [ ] Inform fuchsia the JAX-WS info

  • [ ] Fetch your local instance

Preparation

One step at a time, so lets first instantiate our importer, we first need to find out the name of our importer JAX-WS, which is org.ow2.chameleon.fuchsia.importer.jaxws.JAXWSImporter, with that in mind we can instantiate it indicating what are the Declarations that he should consider as a declaration that concerns it.

In our case we may say that, any declaration containing a value for the key endpoint.url, is a valid declaration to be processed by this importer, thus this give us:

    Instance cxfimporter = Instance.instance()
            .of("org.ow2.chameleon.fuchsia.importer.jaxws.JAXWSImporter")
            .with("target").setto("(endpoint.url=*)");

With this we can check our first task Instantiate the importer. Following our list we have instantiated the Linker.

The Linkers job is to evaluate if a given Declaration and a Service match together, so essentially he impose the condition in order to connect this two entities. We can instantiate our linker in following manner:

    Instance cxfimporterlinker = instance()
            .of(FuchsiaConstants.DEFAULT_IMPORTATION_LINKER_FACTORY_NAME)
            .with("fuchsia.linker.filter.importDeclaration").setto("(endpoint.url=*)")
            .with("fuchsia.linker.filter.importerService").setto("(instance.name=cxfimporterlinker)");

In this linker we are saying "if a declaration contains endpoint.url than connect it with the importer in which the instance is called cxfimporterlinker".

In order to Inform fuchsia the JAX-WS info, we have several options, either we can publish a service that follows the interface org.ow2.chameleon.fuchsia.core.declaration.ImportDeclaration, or we can use the Filebased-Discovery, which translate files deployed in the folder named load into a ImportDeclaration.

You can check that everything is up and running by typing importer on the console, you should see at least:

Importer [cxfimporter] provided by bundle org.ow2.chameleon.fuchsia.importer.jax-rs (46)
	*importer name = cxfimporter
	Service properties:
		factory.name = org.ow2.chameleon.fuchsia.importer.jaxws.JAXWSImporter
		instance.name = cxfimporter
		objectClass = [Ljava.lang.String;@191c428
		service.id = 335
		target = (endpoint.url=*)

And check that the FilebasedDiscovery is running as well by typing discovery and you will see:

Discovery [Fuchsia-FilebasedDiscovery] provided by bundle fuchsia-filebased-discovery (37)
	Service properties:
		factory.name = Fuchsia-FilebasedDiscovery-Factory
		fuchsia.system.filebased.discovery.directory = load
		fuchsia.system.filebased.discovery.polling = 2000
		instance.name = Fuchsia-FilebasedDiscovery
		objectClass = [Ljava.lang.String;@20e9c6
		service.id = 321

Result

So far we’ve our checklist look like this:

  • [*] Instantiate the importer

  • [*] Instantiate the linker

  • [ ] Inform fuchsia the JAX-WS info

  • [ ] Use the remote instance

The last two steps consist in informing the Fuchsia the address of our JAX-WS service. For that its enough to deploy a file in the folder load (thanks to the Filebased-Discovery) with the following content:

id=virtual-camera
className=org.ow2.chameleon.fuchsia.exporter.cxf.examples.base.PojoSampleToBeExportedIface
jax-ws.importer.interfaces=[org.ow2.chameleon.fuchsia.exporter.cxf.examples.base.PojoSampleToBeExportedIface]
endpoint.url=http://localhost:8080/cxf/service/PojoSampleToBeExportedIface

deviceType=camera
deviceSubType=another
device.serialNumber=virtual-camera

Then, just request your service as a regular dependency (as shown below), then you will see your dependency being injected and ready for use without much trouble.

@Component
@Instantiate
public class Client {

    @Requires
    PojoSampleToBeExportedIface myRemoteService;

    @Validate
    public void validate(){

        System.out.println("---->"+myRemoteService.getMessage2());

    }

}

Import a PuSH service

PubSubHubbub (PuSH) is a google protocol create to be fast, in fact it is a publish / subscribe mechanism that avoids the polling technique. A small diagram can show you the idea behind the protocol

PuSH

Prepare

So we are going to need, we are assuming that the publisher and hub addresses are well known.

  • [*] know the address of the publisher

  • [*] know the address of the hub

  • [ ] Instantiate the Importer

  • [ ] Instantiate the linker

  • [ ] Inform fuchsia the Hub and Publisher addresses

  • [ ] Visualize the messages

The first two steps you have to know already those addresses, the next two steps can be done by using maven profile. Meaning that we can generate a chameleon distribution with all our fuchsia dependencies, linker and importer instantiated just by typing into the project repository:

mvn clean install -f distribution/pom.xml -Pcore,push,push-config,discovery-filebased && \
cd distribution/target/chameleon-distribution/ && \
sh chameleon.sh --interactive

Now lets verify our checklist:

  • [*] know the address of the publisher

  • [*] know the address of the hub

  • [*] Instantiate the Importer

  • [*] Instantiate the linker

  • [*] Inform fuchsia the Hub and Publisher addresses

  • [ ] Visualize the messages

The next step is done by add a file with all the information we already have about the PuSH server (example of the file below)

id=push-dispatch-event-admin
deviceType=camera
deviceSubType=another

push.hub.topic=http://blogname.blogspot.com/feeds/posts/default
push.hub.url=http://localhost:8080/hub/subscribe
push.subscriber.callback=http://localhost:8080/push
push.eventAdmin.queue=public

device.serialNumber=push-dispatch-event-admin
Warning

The client must be accessible from the hub, since the hub is the one that pushes the change into clients callback

So we are able to see the redirection (from PuSH into Event admin) we install Web Console Event Admin Plugin.

By default fuchsia distribution will install the felix webconsole, that can be access in the URL http://localhost:8080/system/console/events.

Now when you receive an update from the Hub it will be automatically redirected to eventAdmin message bus.

Import a MQTT service

As MQTT is an efficient message exchange protocol, Fuchsia has built in a MQTT importer that receives and forward messages from MQTT bus into EventAdmin bus.

The address of the MQTT server is supposedly known, and the next two steps (importer and linker instantiation) can be done with the maven profile, according to the command below.

mvn clean install -f distribution/pom.xml -Pcore,mqtt,mqtt-config,discovery-filebased && \
cd distribution/target/chameleon-distribution/ && \
sh chameleon.sh --interactive

So lets verify how are we in relation to our checklist:

Lets create our checklist:

  • [*] know the address of the MQTT server

  • [*] Instantiate the Importer

  • [*] Instantiate the linker

  • [ ] Inform fuchsia about the MQTT server address

  • [ ] Visualize the messages

We tell Fuchsia which MQTT server (and other parameters) by deploying a file into the load directory containing those info (with the content below):

id=mqtt-dispatch-event-admin
deviceType=camera
deviceSubType=another

mqtt.queue=public
mqtt.server.host=localhost
mqtt.server.port=5672

You can use WebConsole to see all the messages that come from MQTT and redirected to EventAdmin. The information mqtt.server.host and mqtt.server.port are optional, in case those informations are not available it will use the information defined as default by RabbitMQ.

So, after installing your MQTT server (in our case RabbitMQ) make sure its running

sudo /etc/init.d/rabbitmq-server status

Exporting Protobuffer RPC service

In order to be able to export your Protobuffer Service, feel things need to be done:

  • [ ] Protobuffer class generated (Step 1)

  • [ ] Implement the service methods for the Protobuffer generated class (Step 2)

  • [ ] Protobuffer class instance added to OSGi Registry (Step 3)

  • [ ] Instantiate the exporter and the linker (Step 4)

  • [ ] Declare the exportation (Step 5)

  • [ ] Create the runtime platform (Step 6)

Warning

Protobuffer version 2.5.0 must be used, any version other than that may produce errors in the remote call

Step 1

Follow the installation procedure in Protobuffer website to get the command for compile .proto file.

Here it is an example of a proto file that can be used for a first glance at the functionality.


package tutorial;

option java_package = "com.google.code.cxf.protobuf.addressbook";
option java_outer_classname = "AddressBookProtos";

message Person {
  required string name = 1;
  required int32 id = 2;        // Unique ID number for this person.
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

message AddressBook {
  repeated Person person = 1;
}

service AddressBookService {
  rpc addPerson(Person) returns(AddressBookSize);
  rpc listPeople(NamePattern) returns(AddressBook);
}

message AddressBookServiceMessage {
  optional Person addPerson = 1;
  optional NamePattern listPeople = 2;
}

message AddressBookSize {
  optional int32 size = 1;
}

message NamePattern {
  optional string pattern = 1;
}

From this file you can generate your class by invoking

protoc --java_out=src/main/java/ addressbook.proto

Step 2

When rpc service is declared in the proto file, this will generate an interface that must to be implemented in order that this rpc service can be called. Since the proto file is not a language, we cannot define what the method should to at this point, it is pretty logic, right?

So in our case we should implement two methods addPerson and listPeople, the exactly two services declared in the proto file.

public class AddressBookServiceImpl extends AddressBookProtos.AddressBookService {
    Map<Integer, AddressBookProtos.Person> records = new ConcurrentHashMap<Integer, AddressBookProtos.Person>();

    public void listPeople(RpcController controller,
                           AddressBookProtos.NamePattern request, RpcCallback<AddressBookProtos.AddressBook> done) {
	...
    }

    public void addPerson(RpcController controller,
                          AddressBookProtos.Person request, RpcCallback<AddressBookProtos.AddressBookSize> done) {
        ...
    }

}

An example of body for these two methods can be:

public class AddressBookServiceImpl extends AddressBookProtos.AddressBookService {
    Map<Integer, AddressBookProtos.Person> records = new ConcurrentHashMap<Integer, AddressBookProtos.Person>();

    public void listPeople(RpcController controller,
                           AddressBookProtos.NamePattern request, RpcCallback<AddressBookProtos.AddressBook> done) {
        AddressBookProtos.AddressBook.Builder addressbook = AddressBookProtos.AddressBook
                .newBuilder();

        for (AddressBookProtos.Person person : records.values()) {
            if (person.getName().indexOf(request.getPattern()) >= 0) {
                addressbook.addPerson(person);
            }
        }

        done.run(addressbook.build());
    }

    public void addPerson(RpcController controller,
                          AddressBookProtos.Person request, RpcCallback<AddressBookProtos.AddressBookSize> done) {
        if (records.containsKey(request.getId())) {
            System.out.println("Warning: will replace existing person: " + records.get(request.getId()).getName());
        }
        records.put(request.getId(), request);
        done.run(AddressBookProtos.AddressBookSize.newBuilder().setSize(
                records.size()).build());
    }

}

As you can see, its not something straight forward as you thought it should be.

Step 3

In this step, we instantiate our service (in java level) and publish it in the OSGi registry. There are several ways of doing this Step so pick up your flavor. We will do like this:

@Component
@Instantiate
public class RegisterBookService {

    ...

    @Validate
    public void validate(){

        Dictionary serviceProperties=new Hashtable<String,Object>();

        context.registerService(
		new String[]{com.google.protobuf.Service.class.getName(),AddressBookProtos.AddressBookService.class.getName()},
		new AddressBookServiceImpl(),
		serviceProperties);

    }

}

This procedure will allow the fuchsia exporter to fetch this instance and export it.

Step 4

As you should have seen in previous sections, the exporter (for our case) and a linker must be instantiated, they are not instantiated automatically.

@Configuration
public class Config {
    Instance ProtobufferExporter = instance()
            .of("org.ow2.chameleon.fuchsia.exporter.protobuffer.ProtobufferExporter")
            .with("target").setto("(rpc.export.address=*)");

    Instance ProtobufferExporterLinker = instance()
            .of(FuchsiaConstants.DEFAULT_EXPORTATION_LINKER_FACTORY_NAME)
            .with(ExportationLinker.FILTER_EXPORTDECLARATION_PROPERTY).setto("(rpc.export.address=*)")
            .with(ExportationLinker.FILTER_EXPORTERSERVICE_PROPERTY).setto("(instance.name=ProtobufferExporter)");
}

See that the filters can be customized, they defined the export declaration that should be catch by this exporter.

Step 5

The export declaration is the intention, in fact it is this file that you express to the framework "Look, i have X instance, and i would like to expose (export) that instance as a Protobuffer RPC service".

This sentenced is converted in:

@Component
@Instantiate
public class ExportDeclaration {

    ...

    @Validate
    public void validate(){

        Map<String, Object> metadata=new HashMap<String, Object>();

        metadata.put("id","export-tests");
        metadata.put("exporter.id","myservice");
        metadata.put("rpc.export.address","http://localhost:8889/AddressBookService");
        metadata.put("rpc.export.class","org.ow2.chameleon.fuchsia.protobuffer.protoclass.AddressBookProtos$AddressBookService");
        metadata.put("rpc.export.message","org.ow2.chameleon.fuchsia.protobuffer.protoclass.AddressBookProtos$AddressBookServiceMessage");

        org.ow2.chameleon.fuchsia.core.declaration.ExportDeclaration declaration = ExportDeclarationBuilder.fromMetadata(metadata).build();

        Dictionary<String, Object> props = new Hashtable<String, Object>();

        String clazzes[] = new String[]{org.ow2.chameleon.fuchsia.core.declaration.ExportDeclaration.class.getName()};
        ServiceRegistration registration = context.registerService(clazzes, declaration, props);

    }

}

Step 6

Assuming that you have compiled fuchsia (in previous sections), you can use distribution module to generate a distribution with dependencies needed.

mvn clean install -Pcore,discovery-filebased,protobuffer && cd target/chameleon-distribution && \
sh chameleon.sh --interactive

To verify that everything was published properly, it is enough to access the url http://localhost:8889/AddressBookService?proto (pay attention that the base url is the same URL we declared in out metadata file)

Importing Protobuffer RPC service

Here, we can see the similarity in the importation process, if a given protocol is supported by Fuchsia the processors are used similarly independently of the protocol.

Prepare the environment
  • [ ] Instantiate the importer & linker (Step 1)

  • [ ] Publish your intention, in fuchsia the declaration (Step 2)

  • [ ] Fetch your local instance (Step 3)

Step 1

This step is mandatory for use of processors (importer, exporter). From the moment you instantiate one processor, it is usually required for you to create a linker otherwise your service cannot be notified in case of a declaration appearance.

@Configuration
public class ProtobufferImporterConfig {
	    Instance ProtobufferRPCImporter = instance()
		    .of("org.ow2.chameleon.fuchsia.importer.protobuffer.ProtobufferImporter")
		    .with("target").setto("(&(rpc.server.address=*)(rpc.proto.class=*)(rpc.proto.service=*)(rpc.proto.message=*))");

	    Instance ProtobufferRPCLinker = instance()
		    .of(FuchsiaConstants.DEFAULT_IMPORTATION_LINKER_FACTORY_NAME)
		    .with(ImportationLinker.FILTER_IMPORTDECLARATION_PROPERTY).setto("(&(rpc.server.address=*)(rpc.proto.class=*)(rpc.proto.service=*)(rpc.proto.message=*))")
		    .with(ImportationLinker.FILTER_IMPORTERSERVICE_PROPERTY).setto("(instance.name=ProtobufferRPCImporter)");
}

Step 2

id=cxf-protobuffer-importer

deviceType=camera
deviceSubType=another
device.serialNumber=virtual-camera

rpc.server.address=http://localhost:8889/AddressBookService
rpc.proto.class=org.ow2.chameleon.fuchsia.protobuffer.protoclass.AddressBookProtos
rpc.proto.service=AddressBookService
rpc.proto.message=AddressBookServiceMessage

Step 3

Now, you are ready to go, the only thing you have to do now is to annotate your dependency as usual, and enjoy it.

As always here is it the example we put to work:

@Component
@Instantiate
public class BookClient {

    private final BundleContext context;

    @Requires (filter = "(fuchsia.importer.id=cxf-protobuffer-importer)")
    AddressBookProtos.AddressBookService addressBook;

    public BookClient(BundleContext context){
        this.context=context;
    }


    @Validate
    public void validate(){

        SimpleRpcController controller = new SimpleRpcController();

        AddressBookProtos.Person.Builder person = AddressBookProtos.Person.newBuilder();

        person.setId(1);
        person.setName("Alice");
        AddressBookProtos.Person alice = person.build();

        addressBook.addPerson(controller, alice, new RpcCallback<AddressBookProtos.AddressBookSize>() {
            public void run(AddressBookProtos.AddressBookSize size) {
                System.out.println("\nThere are " + size.getSize()
                        + " person(s) in the address book now.");
            }
        });

        controller.reset();

        System.out.println("\nSearching for people with 'A' in their name.");
        addressBook.listPeople(controller, AddressBookProtos.NamePattern.newBuilder().setPattern("A")
                .build(), new RpcCallback<AddressBookProtos.AddressBook>() {
            public void run(AddressBookProtos.AddressBook response) {

                System.out.println("\nList of people found: \n");

                for (AddressBookProtos.Person person : response.getPersonList()) {

                    System.out.println("-->" + person.getName());

                }


            }
        });

    }

}

The only reason we added the filter in @Required was to make sure that the remote instance (created by imported) is injected. So this is not mandatory in regular cases.

Filebased-Discovery

First things first, in order to have an discovery we need to create a fuchsia distribution that embeds it, or to use an already existing distribution and add the modules required. Below we will show you how to use couples of discoveries that are available in the platform.

What it does?!

As seen in the introduction the fundamental task of a discovery is to create an instance of Declaration which represents the availability of a service/device, and the intrinsic information of such service/device are contained inside Declaration as properties (stored in a Map).

The idea behind having a filebased discovery, is in fact be able to deploy a file (property file style, containing key value mapping).

In order to activate the filebased discovery its enough to compile the distribution with the profile discovery-filebased (meaning mvn clean install -Pcore,discovery-filebased), from the moment you compile a distribution with this parameter and you launch this distribution a directory called load will be created in the root of the chameleon distribution ($FUCHSIA/distribution/target/chameleon-distribution), this directory will be used to deploy your files that contain the key-value that you need to be instantiated in the platform.

An example of such file is this one:

id=mqtt-dispatch-event-admin
exporter.id=camera-exporter

deviceType=camera
deviceSubType=another

mqtt.queue=public

If you type declaration in the console you should see:

Service properties:
		objectClass = [Ljava.lang.String;@26f6a1
		service.id = 319
Metadata
	id=mqtt-dispatch-event-admin
	deviceType=camera
	deviceSubType=another
	exporter.id=camera-exporter
	mqtt.queue=public

This indicated that the file that you have just deployed have been read and turned into a declaration.

mDNS

mDNS is a discovery protocol based on the previous Bonjour(c), from Apple. To perform a simple test of such protocol, its enough to compile a fuchsia distribution with the following parameters:

mvn clean install -Pcore,mdns,mdns-config

core and mdns are the modules that we need, in this case, the fuchsia core types and the discovery module itself. The 3td element mdns-config is the element responsible to configure the discovery. Even though the mdns module is available with the -Pmdns profile, it is necessary to create an instance of it, shaping it as we wish. We will explain in few paragraphs how to do that manually, but for now we will use an configuration example that setup the discovery to find out all the printers available in the local network, for that we add the profile -Pmdns-config.

To verify that the discovery works properly you can use the console.

If you type declaration on the console you should see an output similar to this:

You should see all your printers in form of:

Metadata
	id=hp LaserJet 2300 (scribe missions)
	discovery.mdns.device.name=hp LaserJet 2300 (scribe missions)
Metadata
	id=HP LaserJet 600 M602 [2F0B40]
	discovery.mdns.device.name=HP LaserJet 600 M602 [2F0B40]
	Service properties:
		objectClass = [Ljava.lang.String;@fcc720
		service.id = 312
...

And if you type discovery in the console you should see:

Discovery [DNSSDDiscovery] provided by bundle mdns (19)
	Service properties:
		dnssd.service.type = _printer._tcp.local.
		factory.name = DNSSDDiscoveryFactory
		instance.name = DNSSDDiscovery
		objectClass = [Ljava.lang.String;@1db4108
		service.id = 301

In this approach we used a configuration that was available in Fuchsia. But that is not usually the case; most of the time we are required to instantiate the discovery ourselves configure the filter in a way that is the application need it.

The following configuration that makes available to import all the printers on the local network inside the platform with requiring any other information than the type of the device that interest us.

@Configuration
public class DNSSDInitializer {
    Instance dnssdDiscovery = instance()
            .of("DNSSDDiscoveryFactory")
            .named("DNSSDDiscovery")
            .with("dnssd.service.type").setto("_printer._tcp.local.");

}
Tip

This instance can be created in any fashion, as long as it is an iPOJO instance.

Tutorial Style

Philips hue in Fuchsia

Description

We are ready to use Philips Hue, simply by:

  • deploying a philips hue discovery - detects the presense of a Philips Hue bridge and authenticates on it.

  • deploying a philips hue importer - makes the lamp accessible by OSGi

  • configure a linker between the discovery and importer

And at each step you can verify that they were correctly deployed, this is the biggest advantage of using Fuchsia as a device framework.

Building the platform

In order to build the platform you are going to download the source code. Then you need two other steps:

  • mvn clean install #compiles the project

  • mvn -f distribution/pom.xml clean install -Pcore,discovery-philips-hue,importer-philips-hue #creates a distribution with all philips base code

The only thing left to do is to start your platform, on directory distribution/target/chameleon-distribution launch the application:

  • sh chameleon.sh --interactive

That is it, at this point you have all the platform running, now you need to declare your intentions.

Declaring intentions

This is done by java code, you have to deploy a bundle in the platform that creates the intentions, which is basically instantiate the components: discovery, importer and the linkers. This can be done with the code below:

@Configuration
public class Config {

    Instance philipsBridgeImporter = instance()
            .of("org.ow2.chameleon.fuchsia.importer.philipshue.PhilipsHueBridgeImporter");

    Instance philipsImporter = instance()
            .of("org.ow2.chameleon.fuchsia.importer.philipshue.PhilipsHueImporter");

    Instance philipsLinkerBridge = instance()
            .of(FuchsiaConstants.DEFAULT_IMPORTATION_LINKER_FACTORY_NAME)
            .with(ImportationLinker.FILTER_IMPORTDECLARATION_PROPERTY).setto("(discovery.philips.bridge.type=*)")
            .with(ImportationLinker.FILTER_IMPORTERSERVICE_PROPERTY).setto("(instance.name=philipsBridgeImporter)");

    Instance philipsLinker = instance()
            .of(FuchsiaConstants.DEFAULT_IMPORTATION_LINKER_FACTORY_NAME)
            .with(ImportationLinker.FILTER_IMPORTDECLARATION_PROPERTY).setto("(discovery.philips.device.name=*)")
            .with(ImportationLinker.FILTER_IMPORTERSERVICE_PROPERTY).setto("(instance.name=philipsImporter)");

}

Check if everything is working

On the shell you can type the command below, and you should obtain the list of all installed importers

user@shelbie$ importer
        ._____________________________________________
        |name:philipsImporter
        |bundle:org.ow2.chameleon.fuchsia.importer.philips-hue[17]
        |importer name = PhilipsHueImporter
        |          ._____________________________________________
        |          |factory.name = org.ow2.chameleon.fuchsia.importer.philipshue.PhilipsHueImporter
Importer|          |instance.name = philipsImporter
        |Properties|objectClass = [Ljava.lang.String;@1e10aef
        |          |service.id = 317
        |          |target = (discovery.philips.device.name=*)
        |          |_____________________________________________
        |_____________________________________________
        ._____________________________________________
        |name:philipsBridgeImporter
        |bundle:org.ow2.chameleon.fuchsia.importer.philips-hue[17]
        |importer name = PhilipsHueBridgeImporter
        |          ._____________________________________________
        |          |factory.name = org.ow2.chameleon.fuchsia.importer.philipshue.PhilipsHueBridgeImporter
Importer|          |instance.name = philipsBridgeImporter
        |Properties|objectClass = [Ljava.lang.String;@194ef9f
        |          |service.id = 315
        |          |target = (discovery.philips.bridge.type=*)
        |          |_____________________________________________
        |_____________________________________________

user@shelbie$

After we can check is our linkers are all in place:

user@shelbie$ linker
                  ._____________________________________________
                  |name:philipsLinker
                  |bundle:org.ow2.chameleon.fuchsia.core[21]
                  |          ._____________________________________________
                  |          |factory.name = FuchsiaDefaultImportationLinkerFactory
                  |          |fuchsia.linker.filter.importDeclaration = (discovery.philips.device.name=*)
Importation Linker|          |fuchsia.linker.filter.importerService = (instance.name=philipsImporter)
                  |Properties|instance.name = philipsLinker
                  |          |objectClass = [Ljava.lang.String;@3e1a61
                  |          |service.id = 323
                  |          |_____________________________________________
                  |_____________________________________________
                  ._____________________________________________
                  |name:philipsLinkerBridge
                  |bundle:org.ow2.chameleon.fuchsia.core[21]
                  |          ._____________________________________________
                  |          |factory.name = FuchsiaDefaultImportationLinkerFactory
                  |          |fuchsia.linker.filter.importDeclaration = (discovery.philips.bridge.type=*)
Importation Linker|          |fuchsia.linker.filter.importerService = (instance.name=philipsBridgeImporter)
                  |Properties|instance.name = philipsLinkerBridge
                  |          |objectClass = [Ljava.lang.String;@1361707
                  |          |service.id = 321
                  |          |_____________________________________________
                  |_____________________________________________

user@shelbie$

Testing your PhilipsHue

There are two commands to manipulate and verify the actual state of the lamp, they are: phlist and phset

If your lamp was corrected detected, just type phlist (as below) and you should get the list of the philips light that are available along with their state.

user@shelbie$ phlist

In case you wanna change its color (in fact change the color of all lamps) you can use the command phset as below to set the color to red:

user@shelbie$ phset -on true -r 255

Export JSONRPC

Goal

Expose an OSGi service instance as a JSONRPC service that can be accessed remotely through a URL.

Requirements

  • service instance that will be exporter

  • declaration containing the metadata below:

    • fuchsia.export.jsonrpc.class - full name of the interface in which your instance implements. e.g. /org.ow2.chameleon.fuchsia.jsonrpc.exporter.experiment.DummyIface/

    • fuchsia.export.jsonrpc.instance - instance.name (in OSGi terms) of your service, which will be used to grab the proper instance to be exported. e.g. DummyPojoInstance

    • fuchsia.export.jsonrpc.url.context (optional) - indicated the context that will be added in URL, by default "JSONRPC", which will be used as a prefix in the URL for the instance as in the example: http://localhost:8080/JSONRPC/DummyPojoInstance

Example

Exporter instantiation
       Instance jsonRPCExporter = instance()
                .of("org.ow2.chameleon.fuchsia.exporter.jsonrpc.JSONRPCExporter")
                .with("target").setto("(fuchsia.export.jsonrpc.instance=*)");
Linker instantiation
        Instance jsonRPCExporterLinker = instance()
            .of(FuchsiaConstants.DEFAULT_EXPORTATION_LINKER_FACTORY_NAME)
            .with(ExportationLinker.FILTER_EXPORTDECLARATION_PROPERTY).setto("(fuchsia.export.jsonrpc.instance=*)")
            .with(ExportationLinker.FILTER_EXPORTERSERVICE_PROPERTY).setto("(instance.name=jsonRPCExporter)");
Export Declaration instantiation

        Map<String, Object> metadata=new HashMap<String, Object>();

        metadata.put("id","exporter-1");
        metadata.put("fuchsia.export.jsonrpc.class","org.ow2.chameleon.fuchsia.jsonrpc.exporter.experiment.DummyIface");
        metadata.put("fuchsia.export.jsonrpc.instance","DummyPojoInstance");

        ExportDeclaration declaration = ExportDeclarationBuilder.fromMetadata(metadata).build();

        Dictionary<String, Object> props = new Hashtable<String, Object>();

        String clazzes[] = new String[]{org.ow2.chameleon.fuchsia.core.declaration.ExportDeclaration.class.getName()};
        ServiceRegistration registration = context.registerService(clazzes, declaration, props);

Verification

Service was properly exported

You can use cURL to test if your instance was properly exported.

linux-shell# curl -i -X POST -d '{"jsonrpc": "2.0", "method": "helloworld", "params": ["earth"], "id": 1}' http://localhost:8080/JSONRPC/DummyPojoInstance

helloworld should be replaced by the method name declared on your interface to be exported

earth will be replaced by the parameters requested by your interface

http://localhost:8080/JSONRPC/DummyPojoInstance, consider this example an instance of the pattern "http://A:B/C/D", A and B depend on your framework configuration; C is constant and will never change; D is in fact the same "instance.name" of your OSGi service instance;

Import JSONRPC

Goal

Materialise an a remote JSONRPC service instance (available through a URL) into the current OSGi platform.

Requirements

  • URL of the JSONRPC object to be imported

  • declaration containing the metadata below:

    • id - a platform unique identifier

    • url - URL where the object was published. e.g. http://localhost:8080/JSONRPC/DummyPojoInstance

    • service.class - full class identifier used as interface, this reference will be used to register in OSGi. e.g. org.ow2.chameleon.fuchsia.examples.jsonrpc.exporter.experiment.DummyIface

    • configs - this is a constant, always set with the value jsonrpc

Example

Importer instantiation
     Instance jsonRPCImporter = instance()
            .of("Fuchsia-Importer:JSON-RPC")
            .with(ImporterService.TARGET_FILTER_PROPERTY).setto("(configs=jsonrpc)");
Linker instantiation
        Instance jsonRPCImporterLinker = instance()
            .of(FuchsiaConstants.DEFAULT_IMPORTATION_LINKER_FACTORY_NAME)
            .with(FILTER_IMPORTDECLARATION_PROPERTY).setto("(configs=jsonrpc)")
            .with(FILTER_IMPORTERSERVICE_PROPERTY).setto("(instance.name=jsonRPCImporter)");
Importer Declaration instantiation
        Map<String, Object> metadata=new HashMap<String, Object>();
        metadata.put(ID, "endipoint");
        metadata.put(URL, "http://localhost:8080/JSONRPC/DummyPojoInstance");
        metadata.put(SERVICE_CLASS, "org.ow2.chameleon.fuchsia.examples.jsonrpc.exporter.experiment.DummyIface");
        metadata.put(CONFIGS, "jsonrpc");

        ImportDeclaration declaration = ImportDeclarationBuilder.fromMetadata(metadata).build();

        String clazzes[] = new String[]{ImportDeclaration.class.getName()};

        Dictionary<String, Object> props = new Hashtable<String, Object>();

        ServiceRegistration registration = context.registerService(clazzes, declaration, props);

Verification

Service was properly imported

You can use felix shelbie shell and inspect the importer bundle to check if it has correcly imported our JSONRPC remote instance

shelbie-shell$ lb
..
   31|Active     |    1|OW2 Chameleon - Fuchsia Importer JSON-RPC (0.0.2.SNAPSHOT)
..
shelbie-shell$ inspect cap service 31
...
service; org.ow2.chameleon.fuchsia.examples.jsonrpc.exporter.experiment.DummyIface with properties:
   service.id = 693

Importer should provide a service with the interface that we’d configured just before in the Importer Declaration instantiation.

Export JAXWS

Goal

Expose an OSGi service instance as a JAXWS service that can be accessed remotely through a URL.

Requirements

  • service instance that will be exporter

  • declaration containing the metadata below:

    • fuchsia.export.cxf.class.name - full name of the interface in which your instance implements. e.g. /org.ow2.chameleon.fuchsia.exporter.cxf.examples.base.PojoSampleToBeExportedIface/

    • fuchsia.export.cxf.instance - instance.name (in OSGi terms) of your service, which will be used to grab the proper instance to be exported. e.g. DummyPojoInstance

    • fuchsia.export.cxf.url.context - indicated the context that will be added in URL, which will be used as a prefix in the URL for the instance as in the example: /PojoSampleToBeExported

Example

Exporter instantiation
    Instance cxfexporter = instance()
            .of("org.ow2.chameleon.fuchsia.exporter.jaxws.JAXWSExporter")
            .with("target").setto("(fuchsia.export.cxf.instance=*)");
Linker instantiation
    Instance cxfexporterlinker = instance()
            .of(FuchsiaConstants.DEFAULT_EXPORTATION_LINKER_FACTORY_NAME)
            .with(ExportationLinker.FILTER_EXPORTDECLARATION_PROPERTY).setto("(fuchsia.export.cxf.instance=*)")
            .with(ExportationLinker.FILTER_EXPORTERSERVICE_PROPERTY).setto("(instance.name=cxfexporter)");
Export Declaration instantiation

        Map<String, Object>  metadata=new HashMap<String, Object>();

        metadata.put("id","a");
        metadata.put("exporter.id","myservice");
        metadata.put("fuchsia.export.cxf.class.name",dummyInstance.getClass().getName());
        metadata.put("fuchsia.export.cxf.instance",dummyInstance);
        metadata.put("fuchsia.export.cxf.url.context","/PojoSampleToBeExported");

        ExportDeclaration declaration = ExportDeclarationBuilder.fromMetadata(metadata).build();

        Dictionary<String, Object> props = new Hashtable<String, Object>();
        String clazzes[] = new String[]{org.ow2.chameleon.fuchsia.core.declaration.ExportDeclaration.class.getName()};
        ServiceRegistration registration = context.registerService(clazzes, declaration, props);

Verification

Service was properly exported

Try to access the URL http://localhost:8080/cxf/PojoSampleToBeExported?wsdl this should show you the WSDL of the service exported

Of course that this URL is based on the example created above, if you changed the property "fuchsia.export.cxf.url.context" (which it is always the case) you have to replace the "PojoSampleToBeExported" on the URL by the proper name.

You should see something like:

<wsdl:definitions xmlns:ns1="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://base.examples.cxf.exporter.fuchsia.chameleon.ow2.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="PojoSampleToBeExported" targetNamespace="http://base.examples.cxf.exporter.fuchsia.chameleon.ow2.org/">
...
</wsdl:service>
</wsdl:definitions>

Import JAXWS

Goal

Materialise an a remote JAXWS service instance (available through a URL) into the current OSGi platform.

Requirements

  • URL of the JAXWS (SOAP) object to be imported

  • declaration containing the metadata below:

    • id - a platform unique identifier

    • endpoint.url - URL where the object was published. e.g. http://localhost:8080/cxf/PojoSampleToBeExported

    • className - full class identifier used as interface, this reference will be used to register in OSGi. e.g. org.ow2.chameleon.fuchsia.exporter.cxf.examples.base.PojoSampleToBeExportedIface

    • configs - this is a constant, always set with the value jsonrpc

    • jax-ws.importer.interfaces (optional) - interfaces that will charged by the importer

Example

Importer instantiation
    Instance cxfimporter = instance()
            .of("org.ow2.chameleon.fuchsia.importer.jaxws.JAXWSImporter")
            .named("cxfimporter")
            .with("target").setto("(endpoint.url=*)");
Linker instantiation
    Instance cxfimporterlinker = instance()
            .of(FuchsiaConstants.DEFAULT_IMPORTATION_LINKER_FACTORY_NAME)
            .named("cxfimporterlinker")
            .with(FILTER_IMPORTDECLARATION_PROPERTY).setto("(endpoint.url=*)")
            .with(FILTER_IMPORTERSERVICE_PROPERTY).setto("(instance.name=cxfimporter)");
Importer Declaration instantiation
        Map<String, Object>  metadata=new HashMap<String, Object>();

        metadata.put("id","b");
        metadata.put("className","org.ow2.chameleon.fuchsia.exporter.cxf.examples.base.PojoSampleToBeExportedIface");
        metadata.put("jax-ws.importer.interfaces","[org.ow2.chameleon.fuchsia.exporter.cxf.examples.base.PojoSampleToBeExportedIface]");
        metadata.put("endpoint.url","http://localhost:8080/cxf/PojoSampleToBeExported");

        ImportDeclaration declaration = ImportDeclarationBuilder.fromMetadata(metadata).build();

        Dictionary<String, Object> props = new Hashtable<String, Object>();
        props.put("endpoint.url","http://localhost:8080/cxf/PojoSampleToBeExported");
        String clazzes[] = new String[]{ImportDeclaration.class.getName()};
        ServiceRegistration registration = context.registerService(clazzes, declaration, props);

Verification

Service was properly imported

You can use felix shelbie shell and inspect the importer bundle to check if it has correcly imported our JAXWS remote instance

shelbie-shell$ lb
..
   52|Active     |    1|OW2 Chameleon - Fuchsia Importer JAX-WS (0.0.2.SNAPSHOT)
..
shelbie-shell$ inspect cap service 52 # you should find something similar to this output
...
service; org.ow2.chameleon.fuchsia.exporter.cxf.examples.base.PojoSampleToBeExportedIface

Importer should provide a service with the interface that we’d configured just before in the Importer Declaration instantiation.