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:
-
[ ] Instantiate the importer
-
[ ] Instantiate the linker
-
[ ] 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
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.
-
[ ] 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.