Craig Walls has been professionally developing software for over 14 years (and longer than that for the pure geekiness of it). He is the author of Spring in Action (now in its second edition) and XDoclet in Action, both published by Manning and is currently writing about OSGi and Spring-DM. Craig has posted 9 posts at DZone. View Full User Profile

Pax Runner Profiles and Distributed OSGi

05.05.2009
| 6916 views |
  • submit to reddit

Last week we looked at how Pax Runner makes simple work of starting an OSGi framework (whichever one you want) and loading it up with a selection of bundles. We also saw how easy it is to switch between different OSGi implementations and versions of those implementations. And we saw how flexible Pax Runner can be with how it provisions bundles, whether it be from the file system, from a Maven repository, or from a ZIP file.

This week's article continues the discussion of Pax Runner with a two-for-one deal. First, we're going to take a look at Pax Runner profiles, one of the handiest Pax Runner features. Then we're going to use a profile to setup an environment for us to try out Distributed OSGi, a new feature of OSGi 4.2.

Using Pax Runner Profiles

As we develop OSGi-based applications, there'll always be several third-party bundles that our bundles will depend upon. Although adding bundles to a Pax Runner provisioning scheme is rather simple, sometimes seeking out the bundles we need and making sure we didn't miss anything can be quite a chore. Profiles offer a way to identify a collection of bundles that we'd like Pax Runner to install and start for us without explicitly identifying them one by one.

For example, most every application needs some form of logging. But what bundles do we need to install to support logging in an OSGi application? With Pax Runner's "log" profile, we needn't worry ourselves with figuring out which bundles to install. Instead, we just tell Pax Runner that we want logging by starting it like this:

sandbox% pax-run.sh --p=e --profiles=log

Here I've given Pax Runner two command line options. The first, --p=e is just a shorthand version of --platform=equinox that we discussed last week. The second option is the one that's the most interesting. It tells Pax Runner that we want it to use its "log" profile to install and start whatever bundles are necessary to support logging in an OSGi application. What bundles does it install? Well, if we issue Equinox's ss command, we'll see them:

osgi> ss

Framework is launched.

id State Bundle
0 ACTIVE org.eclipse.osgi_3.4.3.R34x_v20081215-1030
1 ACTIVE org.eclipse.osgi.util_3.1.300.v20080303
2 ACTIVE org.eclipse.osgi.services_3.1.200.v20070605
3 ACTIVE org.ops4j.pax.logging.pax-logging-api_1.3.0
4 ACTIVE org.ops4j.pax.logging.pax-logging-service_1.3.0

osgi>

Bundles 3 and 4 represent Pax Logging, an implementation of the OSGi Logging Service with an API that mimics several popular logging frameworks such as Log4J or Commons Logging. We'll talk more about Pax Logging later. But for now its enough to know that we didn't need need to seek out Pax Logging to enable logging in our application. We just needed to tell Pax Runner to use its "log" profile.

The "log" profile is only one of many profiles available for Pax Runner. For a complete list of available profiles, visit http://paxrunner.ops4j.org/display/paxrunner/Pax+Runner+profiles+list.

Now let's try something a bit more interesting than simply adding a couple of bundles to support logging. The release of OSGi R4.2 is just around the corner and one of the new features in it is a thing called Distributed OSGi. In a nutshell, Distributed OSGi (or D-OSGi for short) offers a way to automatically publish OSGi services as web services. There's already an implementation of D-OSGi available from the Apache CXF project.

Let's say that we want to use D-OSGi to create a web service based in OSGi. To use the CXF implementation of D-OSGi, we'd need several dozen bundles installed as a prerequisite. That could be tedious and prone to human error (what if we forget a bundle or accidentally install an incompatible version?). But with Pax Runner's "cxf.dosgi" profile, it's no big deal to get started with D-OSGi:

sandbox% pax-run.sh --p=e --profiles=cxf.dosgi

Once the OSGi framework has started, we can get a short status (ss) to see what the profile has given us:

osgi> ss

Framework is launched.

id	State       Bundle
0	ACTIVE      org.eclipse.osgi_3.4.3.R34x_v20081215-1030
1	ACTIVE      org.eclipse.osgi.util_3.1.300.v20080303
2	ACTIVE      org.eclipse.osgi.services_3.1.200.v20070605
3	ACTIVE      org.apache.geronimo.specs.geronimo-annotation_1.0_spec_1.1.1
4	ACTIVE      org.apache.geronimo.specs.geronimo-activation_1.1_spec_1.0.2
5	ACTIVE      org.apache.geronimo.specs.geronimo-javamail_1.4_spec_1.2.0
6	ACTIVE      org.apache.geronimo.specs.geronimo-ws-metadata_2.0_spec_1.1.2
7	ACTIVE      com.springsource.org.jdom_1.0.0
8	ACTIVE      org.ops4j.pax.logging.pax-logging-api_1.3.0
9	ACTIVE      org.ops4j.pax.logging.pax-logging-service_1.3.0
10	ACTIVE      org.ops4j.pax.web.service_0.5.2
11	ACTIVE      com.springsource.org.aopalliance_1.0.0
12	ACTIVE      org.springframework.aop_2.5.6
13	ACTIVE      org.springframework.beans_2.5.6
14	ACTIVE      org.springframework.context_2.5.6
15	ACTIVE      org.springframework.context.support_2.5.6
16	ACTIVE      org.springframework.core_2.5.6
17	ACTIVE      com.springsource.org.objectweb.asm_2.2.3
18	ACTIVE      com.springsource.edu.emory.mathcs.backport_3.1.0
19	ACTIVE      com.springsource.net.sf.cglib_2.1.3
20	ACTIVE      org.springframework.osgi.extensions.annotations_1.2.0
21	ACTIVE      org.springframework.osgi.core_1.2.0
22	ACTIVE      org.springframework.osgi.extender_1.2.0
23	ACTIVE      org.springframework.osgi.io_1.2.0
24	ACTIVE      org.apache.servicemix.bundles.jaxb-impl_2.1.6.1
25	ACTIVE      org.apache.servicemix.bundles.wsdl4j_1.6.1.1
26	ACTIVE      org.apache.servicemix.bundles.xmlsec_1.3.0.1
27	ACTIVE      org.apache.servicemix.bundles.wss4j_1.5.4.1
28	ACTIVE      org.apache.servicemix.bundles.xmlschema_1.4.2.1
29	ACTIVE      org.apache.servicemix.bundles.asm_2.2.3.1
30	ACTIVE      org.apache.servicemix.bundles.xmlresolver_1.2.0.1
31	ACTIVE      org.apache.servicemix.bundles.neethi_2.0.4.1
32	ACTIVE      org.apache.servicemix.bundles.woodstox_3.2.7.1
33	ACTIVE      org.apache.servicemix.specs.saaj-api-1.3_1.1.1
34	ACTIVE      org.apache.servicemix.specs.stax-api-1.0_1.1.1
35	ACTIVE      org.apache.servicemix.specs.jaxb-api-2.1_1.1.1
36	ACTIVE      org.apache.servicemix.specs.jaxws-api-2.1_1.1.1
37	ACTIVE      org.apache.cxf.cxf-bundle-minimal_2.2.0.SNAPSHOT
38	ACTIVE      cxf-dosgi-ri-discovery-local_1.0.0.SNAPSHOT
39	INSTALLED   cxf-dosgi-ri-dsw-cxf_1.0.0.SNAPSHOT

osgi> 

Wow! Just because we specified the "cxf.dosgi" profile, we were given three dozen bundles (in addition to the core Equinox bundles)! That was pretty easy, wasn't it?

Well, although it is impressive that we got just over three dozen bundles installed just by asking for the "cxf.dosgi" profile, there is one small problem. Notice that bundle 39 hasn't been started. That's because CXF depends on a new hook functionality that isn't part of the latest released version of Equinox. But, those hooks are part of the latest and greatest snapshot of Equinox and we're only using the latest released version.

This has nothing to do with profiles, but it gives me a chance to show you one more Pax Runner trick. Let's tell Pax Runner to go to the bleeding edge and use the latest snapshot of Equinox:

sandbox% pax-run.sh --p=e --profiles=cxf.dosgi --snapshot

The --snapshot option tells Pax Runner to use the latest snapshot version of the target OSGi framework. In this case, I'm asking for the latest non-released version of Equinox, which is 3.5 M7 as I write this.

A quick review of the installed bundles (using ss) indicates that all of the CXF D-OSGi bundles (and their dependencies) are installed and ready for us to work with D-OSGi.

And that concludes today's fun with Pax Runner profiles. I could put a bow on this article and start thinking about what I'm going to write for next week, but...as long as we have D-OSGi support in place, let's see what we can do with it.

Publishing Distributed OSGi Services

To demonstrate the power of D-OSGi we're going to create two bundles. One will provide a simple OSGi service that we'll ask D-OSGi to publish as a web service. The other bundle will contain the interface for that service.

Although it's not strictly necessary that an OSGi service implementation and its interface be packaged in separate bundles, it makes a lot of sense to do so, especially when using D-OSGi. That's because we'll want to distribute the interface bundle (separate from the implementation) along with any clients that use it.

Let's start with the service interface:

package com.osgiknowhow.sandbox;

public interface PigLatinService {
String translate(String text);
}
Download the example code

Save yourself some typing and download the code for this article.

BTW, I could've (and in hindsight, should've) used Pax Construct to build these examples. But I decided to keep it very basic for those readers who may not have bought into the Pax Construct development model yet. And, on a more personal note, I wanted to remind myself of what it was like to develop bundles without Pax Construct. Now that I've had that experience, don't expect too many example projects on this blog to be done without Pax Construct ever again.

In case you missed it, I wrote about Pax Construct a few weeks ago. Check it out so that you'll be ready to work with the examples in my future entries.

As you can see, we're going to build a service that translates phrases into Pig Latin. It's a fairly simple service with a single method that takes a String and returns a translated String. Don't worry, though...as far as I know, there's no way that you can contract swine flu by working with this example.

If you've downloaded the example code, you'll find the interface bundle in the "piginterface" project. You can build it using Maven like this:

piginterface% mvn install

You've probably noticed that although we're building an OSGi bundle, I've not shown you how to define the META-INF/MANIFEST.MF file. That's because I'm using the Felix Bundle Plugin for Maven to automatically generate the manifest for me. Here's what it produced for the interface bundle:

Manifest-Version: 1.0
Bundle-Name: Pig Latin Service Interface
Built-By: wallsc
Build-Jdk: 1.5.0_16
Created-By: Apache Maven Bundle Plugin
Import-Package: com.osgiknowhow.sandbox
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.osgiknowhow.sandbox.pig-latin-service-interface
Tool: Bnd-0.0.255
Bnd-LastModified: 1241320637536
Export-Package: com.osgiknowhow.sandbox
Bundle-Version: 1.0.0

Again, just to be clear, we don't have to write the manifest file ourselves. Let Maven and the bundle plugin do all of the work.

Now for the implementation bundle and the service implementation class:

package com.osgiknowhow.sandbox.internal;

import com.osgiknowhow.sandbox.PigLatinService;

public class PigLatinServiceImpl implements PigLatinService {
private final String VOWELS = "AEIOUaeiou";

public String translate(String text) {
String[] words = text.split("\\s");
String result = "";

for(String word : words) {
result += translateWord(word) + " ";
}

return result.trim();
}

private String translateWord(String word) {
StringBuffer result = new StringBuffer();
String start = "";
for(int i=0; i<word.length(); i++) {
char c = word.charAt(i);
if(VOWELS.indexOf(c) >= 0) {
start = word.substring(i);
break;
}
result.append(c);
}

return start + "-" + result + "AY";
}
}

Aside from the translation logic, there's nothing very special about the PigLatinServiceImpl. It's just a POJO that implements the PigLatinService interface.

Now we need to publish an instance of PigLatinServiceImpl service into the OSGi service registry. There are several ways we could do this, including writing a bundle activator, using OSGi Declarative Services, or even IPOJO. But since I'm a big fan of Spring-DM, I'm going to use that to publish the service. (Besides, in case you didn't notice, we already have Spring-DM installed for us thanks to the "cxf.dosgi" profile).

To do that, let's create a Spring application context definition that declares PigLatinServiceImpl as a Spring bean:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="pigLatinService"
class="com.osgiknowhow.sandbox.internal.PigLatinServiceImpl" />

</beans>

If you're familiar with Spring, you'll recognize that this is a very basic Spring configuration file that declares a single bean in the Spring context. Now let's create a companion Spring context definition file that declares this bean to be an OSGi service:

<beans:beans xmlns="http://www.springframework.org/schema/osgi" 
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/osgi
http://www.springframework.org/schema/osgi/spring-osgi.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<service ref="pigLatinService"
interface="com.osgiknowhow.sandbox.PigLatinService" />

</beans:beans>

The <service> element is part of Spring-DM's configuration schema and simply tells Spring-DM to publish the bean identified by the ref attribute into the OSGi service registry under the interface specified by the interface attribute.

Note that it's a best practice to keep the standard Spring bean declarations in one Spring context definition file and the Spring-DM details in a separate file. That's so that the Spring beans can be used in a non-OSGi context (such as integration tests) without involving Spring-DM or an OSGi framework. In this case, the standard Spring bean declaration is in a file named pig-latin-context.xml and the Spring-DM configuration is in a file named pig-latin-osgi.xml. Both files are placed in the bundle's META-INF/spring folder so that the Spring-DM extender can find them.

More about Spring-DM (and a shameless plug)

Although I'm leaning on Spring-DM to publish the Pig Latin service, Spring-DM is a bit outside of the scope of this week's article. You can expect me to blog more about Spring-DM here in the future. But if you're wanting to know more right away, I do cover Spring-DM in my book, Modular Java.

As it stands, if we were to build and deploy the service bundle, the bean would be published as an OSGi service, but would not be exposed as a D-OSGi web service. That's because we haven't added the tiny bit of D-OSGi magic to make that happen. Let's add it now:

    <service ref="pigLatinService" 
interface="com.osgiknowhow.sandbox.PigLatinService">
<service-properties>
<beans:entry key="osgi.remote.interfaces" value="*" />
</service-properties>
</service>

By setting the osgi.remote.interfaces service property to "*", we're telling the D-OSGi implementation (CXF in our case) that we want this service to be exposed as a web service using all of the interfaces that the service is published under (right now, that's only the PigLatinService interface).

Now we're ready to deploy the bundle to see if it works. The first step is to build it:

pigservice% mvn install

Again, the service bundle project leans on the Felix Bundle Plugin for Maven to automatically produce a manifest for the bundle. In case you're curious, here's what it came up with for the implementation bundle:

Manifest-Version: 1.0
Built-By: wallsc
Created-By: Apache Maven Bundle Plugin
Import-Package: com.osgiknowhow.sandbox
Bnd-LastModified: 1241145464887
Export-Package: com.osgiknowhow.sandbox
Bundle-Version: 1.0.0
Bundle-Name: Pig Latin Service
Build-Jdk: 1.5.0_16
Private-Package: com.osgiknowhow.sandbox.internal
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.osgiknowhow.sandbox.pig-latin-service
Tool: Bnd-0.0.255

With the bundle having been built, let's start Pax Runner. We'll need to be sure to use the "cxf.dosgi" profile as before and instruct it to load our service and interface bundles:

piglatin% pax-run.sh --p=e --profiles=cxf.dosgi --snapshot pigservice/target/pig-latin-service-1.0.0.jar piginterface/target/pig-latin-interface-1.0.0.jar

After it starts up, you should be able to point your browser at http://localhost:9000/com/osgiknowhow/sandbox/PigLatinService?wsdl to see that the service is being published. Notice that the URL of the web service is derived from the exported package name. But we can control that by setting a few more properties:

    <service ref="pigLatinService" 
interface="com.osgiknowhow.sandbox.PigLatinService">
<service-properties>
<beans:entry key="osgi.remote.interfaces" value="*" />
<b><beans:entry key="osgi.remote.configuration.type" value="pojo"/>
<beans:entry key="osgi.remote.configuration.pojo.address"
value="http://localhost:9000/piglatin"/></b>
</service-properties>
</service>

Configured this way, the new URL for the service is http://localhost:9000/piglatin (meaning that you can see its WSDL at http://localhost:9000/piglatin?wsdl).

Consuming Services with D-OSGi

Now that we've seen how to expose OSGi services as web service using D-OSGi, it would seem that our natural next step is to write a client to consume our Pig Latin translating web service. To illustrate client-side D-OSGi, we'll start by creating an all new bundle project for the client. Within that bundle, we'll create the client class:

package com.osgiknowhow.sandbox.client;

import com.osgiknowhow.sandbox.PigLatinService;

public class PigLatinClient {
public void tryIt() {
System.out.println("TRANSLATED: " +
pigLatinService.translate("Dogs hate cats"));
}

private PigLatinService pigLatinService;
public void setPigLatinService(PigLatinService pigLatinService) {
this.pigLatinService = pigLatinService;
}
}

The tryIt() method is where all of the fun happens. It makes a call to the PigLatinService's translate() to translate a sample set of text. Notice that PigLatinClient is injected (through a setter method) with a PigLatinService. The details of where that PigLatinService are forthcoming, but first let's see how we wire this bean in the Spring application context.

Within the client bundle's META-INF/spring, I've created a pig-latin-context.xml file. This is a simple Spring context definition file that wires up the PigLatinClient bean:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="pigLatinClient"
class="com.osgiknowhow.sandbox.client.PigLatinClient"
init-method="tryIt">
<property name="pigLatinService" ref="pigLatinService" />
</bean>

</beans>

The pigLatinClient is fairly straightforward. Its pigLatinService property is wired with a reference to a bean whose ID is "pigLatinService". What makes it a little special is that it has an init-method that triggers the tryIt() method upon bean creation.

As for the "pigLatinService" bean, it is defined in a separate META-INF/spring/pig-latin-osgi.xml file:

<beans:beans xmlns="http://www.springframework.org/schema/osgi" 
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/osgi
http://www.springframework.org/schema/osgi/spring-osgi.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<reference id="pigLatinService"
interface="com.osgiknowhow.sandbox.PigLatinService" />

</beans:beans>

Here I'm using Spring-DM's <reference> element to declare a bean whose ID is "pigLatinService" and whose implementation can be found in the OSGi service registry under the com.osgiknowhow.sandbox.PigLatinService interface.

The big question here: Where does the pigLatinService bean come from? Spring-DM assumes that it will be available in the OSGi service registry. But how does it get there? Remember: The "D" in D-OSGi standards for "distributed". That means that our service will likely be in a completely different OSGi runtime than the client. So how does the remote service wind up in the client's OSGi service registry?

Well, that magic is accomplished by adding one more file to the client bundle: OSGI-INF/remote-service/remote-services.xml:

<service-descriptions xmlns="http://www.osgi.org/xmlns/sd/v1.0.0">
<service-description>
<provide interface="com.osgiknowhow.sandbox.PigLatinService"/>
<property name="osgi.remote.interfaces">*</property>
<property name="osgi.remote.configuration.type">pojo</property>
<property name="osgi.remote.configuration.pojo.address">http://localhost:9000/piglatin</property>
</service-description>
</service-descriptions>

This file tells the D-OSGi implementation that this bundle will be consuming a service that is actually a web service hosted elsewhere. D-OSGi will take this file and make sure that there's a proxy to the remote service in the OSGi service registry so that when Spring-DM comes looking for com.osgiknowhow.sandbox.PigLatinService it will be able to find it.

Okay, all that's left to do is build everything and fire up the service and client bundles, each in a separate OSGi runtime. First build it:

pigclient% mvn install

One more time, I'm counting on the Felix bundle plugin to do the heavy lifting with regard to the bundle manifest. This time, it generated the following META-INF/MANIFEST.MF file:

Manifest-Version: 1.0
Bundle-Name: Pig Latin Client
Built-By: wallsc
Build-Jdk: 1.5.0_16
Private-Package: com.osgiknowhow.sandbox.client
Created-By: Apache Maven Bundle Plugin
Import-Package: com.osgiknowhow.sandbox
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.osgiknowhow.sandbox.pig-latin-client
Tool: Bnd-0.0.255
Bnd-LastModified: 1241320097283
Bundle-Version: 1.0.0

Next we'll start up the client OSGi runtime in a way similar to how we started the service runtime:

piglatin% pax-run.sh --p=e --profiles=cxf.dosgi --snapshot pigservice/target/pig-latin-client-1.0.0.jar piginterface/target/pig-latin-interface-1.0.0.jar

If it wasn't clear before, it should be clear now why the interface is kept in a separate bundle. The client bundle needs the interface bundle available so that it can import the service interface package. If it were in the implementation bundle, we'd have to deploy the service bundle alongside the client bundle, which would completely defeat any purpose for using D-OSGi.

Setting the Pax Runner Working Directory

When Pax Runner starts up, it gathers all of the bundles it's going to deploy in a working directory. By default, that directory is called runner and is created in the same directory that Pax Runner was started in.

In this article, we're starting two separate OSGi runtimes with Pax Runner. It's important to keep the two Pax Runner working directories from stepping on each other. If you start each Pax Runner instance from two different directories, you'll be okay. But if you start them from the same directory, then you'll need to override Pax Runner's default working directory with the --dir option:

   piglatin% pax-run.sh --dir=clientrunner --p=e --profiles=cxf.dosgi --snapshot pigservice/target/pig-latin-client-1.0.0.jar piginterface/target/pig-latin-interface-1.0.0.jar

If you downloaded the example code, you'll find some shell scripts that start Pax Runner for the client and service, ensuring that the Pax Runner instances stay out of each other's way. The scripts are called startClient.sh and startService.sh. (Sorry Windows users...I'm leaving it to you to make the necessary adjustments to convert these shell scripts into DOS batch scripts.)

At any rate, as the client bundle starts, Spring-DM will try to resolve the service from the OSGi service registry. Thanks to the remote-services.xml file, it will find the service, which is actually just a proxy to the remote service hosted in another OSGi runtime. With a reference to the service in hand, it calls the translate() method and prints the following:

TRANSLATED:  ogs-DAY ate-hAY ats-cAY

And that brings us to the end of this week's adventure in OSGi. To recap, we've looked at two different subjects this week:

  • We saw how Pax Runner profiles make it easy to load and start a selection of bundles that collectively provide some feature to the OSGi runtime. Pax Runner offers several profiles in its profile repository, but we focused on the "cxf.dosgi" profile, that pulls in three dozen bundles for the purpose of working with Distributed OSGi.
  • With the bundles from the "cxf.dosgi" profile in place, we tinkered with D-OSGi. We saw how creating a remote service is a simple matter of publishing it with an extra property or two. And consuming that service isn't much more difficult. The key thing is that both publication and consumption of D-OSGi services is a declarative activity--no Java code was harmed in the making of this article.

I hope that you're enjoying my weekly OSGi articles. I've got a long list of potential topics to write about. Next week, I think I'll revisit Pax Construct, looking at a few features that we didn't see the last time I wrote about it. But I'm also open to suggestions. If there's any OSGi-related topic that you'd like me to discuss here, feel free to ask. I make no promises, but I'll try to add it to my list for a future article.

From http://www.jroller.com/habuma

Published at DZone with permission of its author, Craig Walls.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

michael cheung replied on Tue, 2009/05/05 - 1:10pm

Although this look interesting, i would like to see a distributed OSGI multicast with round robin implementation (SLP or JGroup) that uses just NIO and google protocol buffer as the protocol instead of rest. Also, managing your configuration can be a nightmare, since every client will need to know the destination of the host. r-osgi is already doing this.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.