Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "OSGi R7 Remote Services between Python and Java"

(Running Hello Example in Apache Karaf)
Line 69: Line 69:
 
The '''service.intents=osgi.async''' indicates that the service methods with Promise and CompletableFuture  ([https://osgi.org/specification/osgi.cmpn/7.0.0/service.remoteservices.html#d0e1407 or Future or CompletionStage]) are Async Remote Services and so do not block.   
 
The '''service.intents=osgi.async''' indicates that the service methods with Promise and CompletableFuture  ([https://osgi.org/specification/osgi.cmpn/7.0.0/service.remoteservices.html#d0e1407 or Future or CompletionStage]) are Async Remote Services and so do not block.   
  
Here is a Python consumer for this service
+
==Launching via Bndtools Project Template==
<hr>
+
<source lang="Python">
+
from pelix.ipopo.decorators import ComponentFactory,Instantiate,Requires,Validate
+
 
+
@ComponentFactory("remote-hello-consumer-factory")
+
# The '(service.imported=*)' filter only allows remote services to be injected
+
@Requires("_helloservice", "org.eclipse.ecf.examples.hello.IHello",
+
          False, False, "(service.imported=*)", False)
+
@Instantiate("remote-hello-consumer")
+
class RemoteHelloConsumer(object):
+
 
+
    def __init__(self):
+
        self._helloservice = None
+
        self._name = 'Python'
+
        self._msg = 'Hello Java'
+
 
+
    @Validate
+
    def _validate(self, bundle_context):
+
        # call it!
+
        resp = self._helloservice.sayHello(self._name + 'Sync', self._msg)
+
        print(
+
            "{0} IHello service consumer received sync response: {1}".format(
+
                self._name,
+
                resp))
+
 
+
        # call sayHelloAsync which returns Future and we add lambda to print
+
        # the result when done
+
        self._helloservice.sayHelloAsync(
+
            self._name + 'Async',
+
            self._msg).add_done_callback(
+
            lambda f: print(
+
                'async response: {0}'.format(
+
                    f.result())))
+
        print("done with sayHelloAsync method")
+
 
+
        # call sayHelloAsync which returns Future and we add lambda to print
+
        # the result when done
+
        self._helloservice.sayHelloPromise(
+
            self._name + 'Promise',
+
            self._msg).add_done_callback(
+
            lambda f: print(
+
                'promise response: {0}'.format(
+
                    f.result())))
+
        print("done with sayHelloPromise method")
+
 
+
</source>
+
<hr>
+
This example uses [http://ipopo.readthedocs.io ipopo] to inject the IHello service proxy into the RemoteHelloConsumer instance...into the self._helloservice .  The use of the @ComponentFactory, @Requires, @Instantiate, and @Validate decorators results in this behavior.  See the [https://ipopo.readthedocs.io/en/0.7.0/ ipopo project version 0.8.0] for more information.  The result is that when the _validate method is called, the self._helloservice instance has been discovered and injected and so is ready for calling/use.
+
 
+
Note in _validate that the first call is to the self._helloservice.sayHello method. 
+
 
+
<source lang="Python">
+
        # call it!
+
        resp = self._helloservice.sayHello(self._name + 'Sync', self._msg)
+
        print(
+
            "{0} IHello service consumer received sync response: {1}".format(
+
                self._name,
+
                resp))
+
</source>
+
<hr>
+
This is a synchronous remote method call, and so the calling thread will block until a result is returned or the timeout (50000ms) results in exception.
+
 
+
Note that for both the consumer's calling the sayHelloAsync and sayHelloPromise methods
+
 
+
<source lang="Python">
+
        # call sayHelloAsync which returns Future and we add lambda to print
+
        # the result when done
+
        self._helloservice.sayHelloAsync(
+
            self._name + 'Async',
+
            self._msg).add_done_callback(
+
            lambda f: print(
+
                'async response: {0}'.format(
+
                    f.result())))
+
        print("done with sayHelloAsync method")
+
 
+
        # call sayHelloAsync which returns Future and we add lambda to print
+
        # the result when done
+
        self._helloservice.sayHelloPromise(
+
            self._name + 'Promise',
+
            self._msg).add_done_callback(
+
            lambda f: print(
+
                'promise response: {0}'.format(
+
                    f.result())))
+
        print("done with sayHelloPromise method")
+
</source>
+
<hr>
+
When this consumer calls the sayHelloAsync or sayHelloPromise methods the distribution provider will immediately return a [https://docs.python.org/3/library/concurrent.futures.html#future-objects Future] instance and proceed with the remote call.  The [https://docs.python.org/3/library/concurrent.futures.html#future-objects Future] will then call the function back provided to it via [https://docs.python.org/3/library/concurrent.futures.html#future-objects Future.add_done_callback] method.  These callbacks will probably (depending upon timing) be called after the _validate method is completed.
+
 
+
The Python.Java distribution provider takes care of the conversion between CompletableFuture and Future, and Promise -> Future in keeping with the contract of the OSGi Async Remote Services.
+
 
+
==Running via Bndtools Project Template==
+
  
 
With the ECF Photon/3.14.0 release, support has been added for using Bndtools to do remote service development.  Before doing the below please use [https://wiki.eclipse.org/Bndtools_Support_for_Remote_Services_Development these instructions for setting up a workspace using the ECF/bndtools.workspace template].
 
With the ECF Photon/3.14.0 release, support has been added for using Bndtools to do remote service development.  Before doing the below please use [https://wiki.eclipse.org/Bndtools_Support_for_Remote_Services_Development these instructions for setting up a workspace using the ECF/bndtools.workspace template].
Line 195: Line 104:
 
</pre>
 
</pre>
  
==Running via Apache Karaf==
+
==Launching via Apache Karaf==
  
 
Using Karaf 4.2+ [https://wiki.eclipse.org/EIG:Install_into_Apache_Karaf see this page to install ECF Photon into Karaf].
 
Using Karaf 4.2+ [https://wiki.eclipse.org/EIG:Install_into_Apache_Karaf see this page to install ECF Photon into Karaf].
Line 221: Line 130:
 
</pre>
 
</pre>
  
===Running the Python Hello Consumer===
+
This output indicates that the HelloImpl service has been registered and exported for remote access from Python.
 +
 
 +
==Launching the Python Consumer==
  
TBD with ipopo 0.8.0
+
See [https://github.com/tcalmant/ipopo/blob/rsa-integration/docs/tutorials/rsa_pythonjava.rst this page] from the iPOPO documentation for starting the Python-side sample code.

Revision as of 18:38, 25 July 2018

Introduction

OSGi Remote Services have traditionally been declared, implemented, and consumed in Java. For example this tutorial. ECF's Photon release, however, includes a new distribution provider for connecting Java/OSGi and Python runtimes.

In addition, a Python implementation of OSGi R7 RSA has recently been contributed to the iPopo framework. iPopo is a pure Python implementation of much of OSGi, and iPopo 0.8.0+ includes a pure-Python implementation of RSA.

This tutorial shows the use of OSGi R7 Remote Services and Python.Java Distribution Provider to expose OSGi remote services both from Java->Python, and Python->Java. The tutorial presents a sample remote service, but developers can use the same mechanisms to expose and consume arbitrary services between Python and Java.

Hello Service

IHello service declaration:


import java.util.concurrent.CompletableFuture;
import org.osgi.util.promise.Promise;
 
public interface IHello {
 
    String sayHello(String from, String message);
 
    CompletableFuture<String> sayHelloAsync(String from, String message);
 
    Promise<String> sayHelloPromise(String from, String message);
}

IHello source with documentation.

This interface defines one synchronous method sayHello(String from, String message) and two async methods: sayHelloAsync, and sayHelloPromise. These are OSGi R7 asynchronous methods strictly because of the CompletableFuture and Promise return types and the presence of the osgi.async service intent in the Component properties. As per the Async Remote Services specification, return types are interpreted by the distribution provider as async methods, and so will not block the calling thread even if the underlying communication is blocked or fails.

Java Implementation of Hello service:


@Component(immediate=true,enabled=false,property = { "service.exported.interfaces=*",
"service.exported.configs=ecf.py4j.host",
"osgi.basic.timeout:Long=50000",
"service.intents=osgi.async"}) // osgi.async intent to get the async rs behavior
public class HelloImpl implements IHello {
 
    @Override
    public String sayHello(String from, String message) {
        System.out.println("Java.sayHello called by "+from+" with message: '"+message+"'");
        return "Java says: Hi "+from + ", nice to see you";
    }
    @Override
    public CompletableFuture<String> sayHelloAsync(String from, String message) {
        System.out.println("Java.sayHelloAsync called by "+from+" with message: '"+message+"'");
        CompletableFuture<String> result = new CompletableFuture<String>();
        result.complete("JavaAsync says: Hi "+from + ", nice to see you");
        return result;
    }
    @Override
    public Promise<String> sayHelloPromise(String from, String message) {
        System.out.println("Java.sayHelloPromise called by "+from+" with message: '"+message+"'");
        Deferred<String> deferred = new Deferred<String>();
        deferred.resolve("JavaPromise says: Hi "+from + ", nice to see you");
        return deferred.getPromise();
    }
}

the full HelloImpl source with documentation.

Note the remote service properties specified in the @Component annotation. service.exported.interfaces=* is an OSGi standard property indicating that this service should be exported as a remote service. The service.exported.configs=ecf.py4j.host is another standard property indicating that this service should be exported via the Python.Java distribution provider.

osgi.basic.timeout=Long:50000 indicates that the remote method invocation for all methods in this interface should timeout after 50000ms/50s. This timeout can be set to any desired value and defaults to 30000ms (30s).

The service.intents=osgi.async indicates that the service methods with Promise and CompletableFuture (or Future or CompletionStage) are Async Remote Services and so do not block.

Launching via Bndtools Project Template

With the ECF Photon/3.14.0 release, support has been added for using Bndtools to do remote service development. Before doing the below please use these instructions for setting up a workspace using the ECF/bndtools.workspace template.

1. Create an Empty Bndtools Project in the workspace (of any name).

2. Choose File->New->Run Descriptor File (.bndrun) and select the **Python.Java Hello Example**

Pyjavahello1.png

3. Choose Next> and give some filename for the .bndrun file

4. Open the created filename.bndrun file.

5. Click the **Resolve** button on the lower right of the bndrun editor underneath the Run Requirements list.

6. Click Finish on the resolve window presented.

7. Click the 'Debug OSGi' link in the upper right of the bndrun editor to start the example.

This should result in some console output similar to

osgi> SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Jul 09, 2018 5:06:56 PM py4j.GatewayServer fireServerStarted
INFO: Gateway Server Started
osgi> 17:07:48.611;EXPORT_REGISTRATION;exportedSR={org.eclipse.ecf.examples.hello.IHello}={service.intents=osgi.async, service.exported.configs=ecf.py4j.host, service.id=84, service.bundleid=4, service.scope=bundle, osgi.basic.timeout=50000, component.name=org.eclipse.ecf.examples.hello.javahost.HelloImpl, service.exported.interfaces=*, component.id=6};cID=URIID [uri=py4j://127.0.0.1:25333/java];rsId=1
--Endpoint Description---
<endpoint-descriptions xmlns="http://www.osgi.org/xmlns/rsa/v1.0.0">
  <endpoint-description>
.. the rest of the endpoint description

Launching via Apache Karaf

Using Karaf 4.2+ see this page to install ECF Photon into Karaf.

1. Install the Python.Java distribution provider and the Hello example:

karaf@root()>feature:repo-add ecf
karaf@root()>feature:install -v ecf-rs-examples-python.java-hello

This should result in some console output similar to

osgi> SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Jul 09, 2018 5:06:56 PM py4j.GatewayServer fireServerStarted
INFO: Gateway Server Started
osgi> 17:07:48.611;EXPORT_REGISTRATION;exportedSR={org.eclipse.ecf.examples.hello.IHello}={service.intents=osgi.async, service.exported.configs=ecf.py4j.host, service.id=84, service.bundleid=4, service.scope=bundle, osgi.basic.timeout=50000, component.name=org.eclipse.ecf.examples.hello.javahost.HelloImpl, service.exported.interfaces=*, component.id=6};cID=URIID [uri=py4j://127.0.0.1:25333/java];rsId=1
--Endpoint Description---
<endpoint-descriptions xmlns="http://www.osgi.org/xmlns/rsa/v1.0.0">
  <endpoint-description>
.. the rest of the endpoint description

This output indicates that the HelloImpl service has been registered and exported for remote access from Python.

Launching the Python Consumer

See this page from the iPOPO documentation for starting the Python-side sample code.

Back to the top