Using Amdatu Remote with Celix Remote Services

The Remote Service Admin specification can also be used in a polyglot environment, for example, to call C code from Java or the other way around. Using the recent changes in the Apache Celix project makes it possible to use its remote service implementation in combination with Amdatu Remote.

Java code

The example used for this demonstration is based on the "calculator" service provided in the Celix source code. Basically, this service provides three methods:

  1. add, to add two given numbers;
  2. subtract, to subtract two given numbers;
  3. sqrt, to take the square root of a given number.

The Java interface of this service looks like:

Calculator service interface
public interface Calculator {
    double add(double a, double b);
    double sub(double a, double b);
    double sqrt(double a);
}

This interface is completely analog to the calculator service as implemented in the examples of Celix. 

Adding a Java client

With only this interface, it is already possible to import the calculator service from a Celix framework. However, we have no clients consuming the service, so lets add one. To add a client in Java that actually talks to the calculator service, we add a couple of Gogo commands, allowing you to call the calculator service from a Gogo shell:

Java calculator client
public class Activator extends DependencyActivatorBase {
    public static final String SCOPE = "calc";
    public static final String[] FUNCTIONS = { "add", "sub", "sqrt" };

    private volatile Calculator m_calc;

    public void add(CommandSession session, double a, double b) {
        session.getConsole().printf("%f + %f = %f%n", a, b, m_calc.add(a, b));
    }
    public void sub(CommandSession session, double a, double b) {
        session.getConsole().printf("%f - %f = %f%n", a, b, m_calc.sub(a, b));
    }
    public void sqrt(CommandSession session, double a) {
        session.getConsole().printf("sqrt(%f) = %f%n", a, m_calc.sqrt(a));
    }

 
    public void init(BundleContext context, DependencyManager manager) throws Exception {
        Properties props = new Properties();
        props.put(CommandProcessor.COMMAND_SCOPE, SCOPE);
        props.put(CommandProcessor.COMMAND_FUNCTION, FUNCTIONS);
        manager.add(createComponent()
			.setInterface(Object.class.getName(), props)
            .setImplementation(this)
			.add(createServiceDependency().setService(Calculator.class).setRequired(true)));
    }
    public void destroy(BundleContext context, DependencyManager manager) throws Exception {}
}

Assuming we've packaged the calculator service interface in a bundle called org.amdatu.remote.demo.calc.demo.api and the calculator client in a bundle called org.amdatu.remote.demo.calc.demo.client, we can use the following Bnd Run Configuration to start the calculator Java client:

Java-based calculator client run configuration
-runfw: org.apache.felix.framework;version='[4,5)'
-runee: JavaSE-1.6
-runproperties: \
	org.osgi.framework.bootdelegation=javax.*,\
	org.amdatu.remote.discovery.configured.endpoints='http://localhost:9999/org.apache.celix.discovery.configured',\
	org.osgi.service.http.port=9000
-runbundles: \
	jackson-core-asl;version='[1.9.8,1.9.9)',\
	jackson-mapper-asl;version='[1.9.8,1.9.9)',\
	javax.servlet;version='[2.5.0,2.5.1)',\
	org.amdatu.remote.demo.calc.demo.api;version=latest,\
	org.amdatu.remote.demo.calc.demo.client;version=latest,\
	org.amdatu.remote.discovery.configured;version=latest,\
	org.amdatu.remote.admin.http;version=latest,\
	org.amdatu.remote.topology.promiscuous;version=latest,\
	org.apache.felix.configadmin;version='[1.8.0,1.8.1)',\
	org.apache.felix.dependencymanager;version='[3.1.0,3.1.1)',\
	org.apache.felix.dependencymanager.shell;version='[3.0.1,3.0.2)',\
	org.apache.felix.gogo.command;version='[0.12.0,0.12.1)',\
	org.apache.felix.gogo.runtime;version='[0.10.0,0.10.1)',\
	org.apache.felix.gogo.shell;version='[0.10.0,0.10.1)',\
	org.apache.felix.http.jetty;version='[2.2.2,2.3)',\
	org.apache.felix.log;version='[1.0.1,1.0.2)',\
	org.apache.felix.metatype;version='[1.0.6,1.0.7)',\
	org.osgi.service.remoteserviceadmin;version='[6,7)',\
	osgi.enterprise;version='[5,6)'

On the Celix side, we need to run the remote-service-cfg example, containing the necessary bundles along with the calculator service implementation. Before running the example, we need to parameterise to make it use the RSA implementation at the Java side. This is done by setting the DISCOVERY_CFG_POLL_ENDPOINTS property to the URL on which the configured discovery of Amdatu can be found:

Starting the C-based calculator service
[celix/deploy/remote-services-cfg]$ export DISCOVERY_CFG_POLL_ENDPOINTS='http://localhost:9000/org.amdatu.remote.discovery.configured'
[celix/deploy/remote-services-cfg]$ sh run.sh
...
RSA: Export services (org.apache.celix.calc.api.Calculator)
...
-> _

It takes a little while (around 10 seconds, since we're using configured discovery) before both sides are able to communicate with each other. Once this is done, you can use the Gogo shell at the Java side to invoke the actual service:

g! add 3 6
3.000000 + 6.000000 = 9.000000
g! _

That's it! With only a couple of lines of Java code, we are already able to import an existing C service.

Adding a Java service interface

We've seen how we can import and use a C-based service implementation in Java, but we can also do it the other way around: let a C-based client use a service implementation from Java. For this to work, we need to implement the calculator service in Java:

Java based calculator service
public class Activator extends DependencyActivatorBase implements Calculator {
    public double add(double a, double b) {
        System.out.printf("Add(%f, %f) => %f\n", a, b, (a + b));
        return a + b;
    }

    public double sub(double a, double b) {
        return a - b;
    }

    public double sqrt(double a) {
        if (a < 0) {
            throw new IllegalArgumentException("Cannot take square root!");
        }
        return Math.sqrt(a);
    }

    public void init(BundleContext context, DependencyManager manager) throws Exception {
        Properties props = new Properties();
        props.put(RemoteConstants.SERVICE_EXPORTED_INTERFACES, Calculator.class.getName());

        manager.add(createComponent()
            .setInterface(Calculator.class.getName(), props)
            .setImplementation(this));
    }

    public void destroy(BundleContext context, DependencyManager manager) throws Exception {}
}

If we package our service implementation in a bundle called org.amdatu.demo.calc.demo.impl, we can run our Java-based service using the following Bnd Run Configuration:

Java-based calculator service run configuration
-runfw: org.apache.felix.framework;version='[4,5)'
-runee: JavaSE-1.6
-runproperties: \
	org.osgi.framework.bootdelegation=javax.*,\
	org.amdatu.remote.discovery.configured.endpoints='http://localhost:9999/org.apache.celix.discovery.configured',\
	org.osgi.service.http.port=9000
-runbundles: \
	jackson-core-asl;version='[1.9.8,1.9.9)',\
	jackson-mapper-asl;version='[1.9.8,1.9.9)',\
	javax.servlet;version='[2.5.0,2.5.1)',\
	org.amdatu.remote.demo.calc.demo.api;version=latest,\
	org.amdatu.remote.demo.calc.demo.impl;version=latest,\
	org.amdatu.remote.discovery.configured;version=latest,\
	org.amdatu.remote.admin.http;version=latest,\
	org.amdatu.remote.topology.promiscuous;version=latest,\
	org.apache.felix.configadmin;version='[1.8.0,1.8.1)',\
	org.apache.felix.dependencymanager;version='[3.1.0,3.1.1)',\
	org.apache.felix.dependencymanager.shell;version='[3.0.1,3.0.2)',\
	org.apache.felix.gogo.command;version='[0.12.0,0.12.1)',\
	org.apache.felix.gogo.runtime;version='[0.10.0,0.10.1)',\
	org.apache.felix.gogo.shell;version='[0.10.0,0.10.1)',\
	org.apache.felix.http.jetty;version='[2.2.2,2.3)',\
	org.apache.felix.log;version='[1.0.1,1.0.2)',\
	org.apache.felix.metatype;version='[1.0.6,1.0.7)',\
	org.osgi.service.remoteserviceadmin;version='[6,7)',\
	osgi.enterprise;version='[5,6)'

On the Celix side, we need to run the remote-service-cfg-client example, containing the necessary bundles along with the calculator client implementation (again a shell-based version). Before running the example, we need to parameterise to make it use the RSA implementation at the Java side. This is done by setting the DISCOVERY_CFG_POLL_ENDPOINTS property to the URL on which the configured discovery of Amdatu can be found:

Starting the C-based calculator service
[celix/deploy/remote-services-cfg-client]$ export DISCOVERY_CFG_POLL_ENDPOINTS='http://localhost:9000/org.amdatu.remote.discovery.configured'
[celix/deploy/remote-services-cfg-client]$ sh run.sh
...
RSA: Import service org.apache.celix.calc.api.Calculator
...
-> add 3 7
CALCULATOR_SHELL: Add: 3.000000 + 7.000000 = 10.000000

About proxies and endpoints

The Java runtime allows you to use introspection to determine what methods can or should be invoked. C, on the other hand, has no such facilities, and as such needs additional code to handle received method invocations (called an "endpoint") or to invoke remote methods (by using a "proxy"). For the calculator demo used in this example, the endpoint and proxy are already available and aligned with the Java code to make them work correctly. When creating new endpoints and/or proxies, you should consider the following:

  • JSON is used as intermediary language for marshalling requests, meaning that subtle type differences (signed vs unsigned) get "lost" in translation;
  • The RSA HTTP implementation of Amdatu only support primitive types, most of the Java collections and "simple" POJOs: all fields must have appropriate getters and setters and a default constructor must be present. The proxy and/or endpoint in Celix has no such limitation as it is hand-coded and can do all kinds of nifty stuff;
  • Java supports exceptions, but in C there is no such concept (apart from using error result codes). Extra care needs to be given to handling exceptions;
  • Java supports method/function overloading, but C does not (directly);
  • Java strings are UTF-8 encoded by default, in C strings are just a series of characters: define a standard encoding and use this consistently.

Source code and other artifacts

  File Modified

ZIP Archive amdatu-celix-src.tgz Example project code.

Aug 21, 2014 by Jan Willem Janssen