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 "Tutorial: Python+Java for OSGi R7 Remote Services"

Line 74: Line 74:
 
==Introduction==
 
==Introduction==
  
OSGi Services are an excellent way to implement small, dynamic, component-based systems.  With features as service versioning, support for real dynamics, service injection (e.g. the service component runtime aka declarative services), service dependency-handling, a flexible broker, and a clear separation between service contract and implementation make it a valuable technology particularly for the Internet of Things.   
+
OSGi Services are an excellent way to implement small, dynamic, component-based systems.  With features like service versioning, dynamics, service injection and instance dependency-handling, and a clear separation between contract and implementation make it a valuable technology for the Internet of Things.   
  
Traditionally, however, OSGi services have been both declared and implemented in Java.  With the OSGi Remote Services specification, and ECF's implementation of that [https://www.osgi.org/developer/specifications/ specification], it's now possible to create OSGi services implemented in Python and used/consumed in Java.  This tutorial shows a simple example of creating an OSGi service implemented in Python, that uses [https://developers.google.com/protocol-buffers/ Google Protocol Buffers] for performant cross-language serialization, and that automatically inherits all of the aspects of an OSGi service (dynamics, service injection, versioning, etc.).
+
Since OSGi is a Java-based framework, services have traditionally been both declared and implemented in Java.  With the OSGi Remote Services specification, and ECF's implementation of both the Remote Services and Remote Service Admin specifications -[https://www.osgi.org/developer/specifications/ specification], it's now possible to implement and use OSGi services implemented in Python or Java.   
 +
 
 +
This tutorial shows a simple example of implementing an OSGi service implemented in Python, that uses [https://developers.google.com/protocol-buffers/ Google Protocol Buffers] for high-performance cross-language serialization.  These services automatically inherit all of the aspects of a Java-only OSGi service (dynamics, service injection, versioning support, etc.).
  
 
==Declaring a Service==
 
==Declaring a Service==
Line 91: Line 93:
 
This interface is declared [https://github.com/ECF/Py4j-RemoteServicesProvider/blob/master/examples/org.eclipse.ecf.examples.protobuf.hello/src/org/eclipse/ecf/examples/protobuf/hello/IHello.java here] in [https://github.com/ECF/Py4j-RemoteServicesProvider/tree/master/examples/org.eclipse.ecf.examples.protobuf.hello this project].
 
This interface is declared [https://github.com/ECF/Py4j-RemoteServicesProvider/blob/master/examples/org.eclipse.ecf.examples.protobuf.hello/src/org/eclipse/ecf/examples/protobuf/hello/IHello.java here] in [https://github.com/ECF/Py4j-RemoteServicesProvider/tree/master/examples/org.eclipse.ecf.examples.protobuf.hello this project].
  
The HelloMsgContent type was generated by the Protocol Buffers via a message declaration
+
The HelloMsgContent class was generated by the Protocol Buffers protoc compiler based upon the following message declaration
  
 
<pre>
 
<pre>
Line 109: Line 111:
 
This message declaration, when run through the protoc compiler, generates the Java HelloMsgContent source code, and also generates a Python version of the same class.  The hellomsg.proto file is [https://github.com/ECF/Py4j-RemoteServicesProvider/blob/master/examples/org.eclipse.ecf.examples.protobuf.hello/hellomsg.proto here].
 
This message declaration, when run through the protoc compiler, generates the Java HelloMsgContent source code, and also generates a Python version of the same class.  The hellomsg.proto file is [https://github.com/ECF/Py4j-RemoteServicesProvider/blob/master/examples/org.eclipse.ecf.examples.protobuf.hello/hellomsg.proto here].
  
With the IHello interface and the HelloMsgContent class, the service declaration is complete.
+
With the IHello interface and the HelloMsgContent class, the IHello service declaration is complete.
  
 
==Python Implementation of IHello Service==
 
==Python Implementation of IHello Service==
Line 116: Line 118:
  
 
<pre>
 
<pre>
from osgiservicebridge.bridge import Py4jServiceBridge, _wait_for_sec
 
 
from osgiservicebridge.protobuf import protobuf_remote_service, protobuf_remote_service_method
 
 
from hellomsg_pb2 import HelloMsgContent
 
from hellomsg_pb2 import HelloMsgContent
 +
from osgiservicebridge.protobuf import protobuf_remote_service
 +
from osgiservicebridge.protobuf import protobuf_remote_service_method
 +
from osgiservicebridge import ECF_SERVICE_EXPORTED_ASYNC_INTERFACES
  
@protobuf_remote_service(objectClass=['org.eclipse.ecf.examples.protobuf.hello.IHello'])
+
def create_hellomsgcontent(message):
 +
    resmsg = HelloMsgContent()
 +
    resmsg.h = 'Another response from Python'
 +
    resmsg.f = 'frompython'
 +
    resmsg.to = 'tojava'
 +
    resmsg.hellomsg = message
 +
    for x in range(0,5):
 +
        resmsg.x.append(float(x))
 +
    return resmsg
 +
 
 +
@protobuf_remote_service(objectClass=['org.eclipse.ecf.examples.protobuf.hello.IHello'],export_properties = { ECF_SERVICE_EXPORTED_ASYNC_INTERFACES: 'org.eclipse.ecf.examples.protobuf.hello.IHello' })
 
class HelloServiceImpl:
 
class HelloServiceImpl:
 
      
 
      
 +
    def __init__(self,props):
 +
        self.props = props
 +
       
 
     @protobuf_remote_service_method(arg_type=HelloMsgContent,return_type=HelloMsgContent)
 
     @protobuf_remote_service_method(arg_type=HelloMsgContent,return_type=HelloMsgContent)
 
     def sayHello(self,pbarg):
 
     def sayHello(self,pbarg):
 
         print("sayHello called with arg="+str(pbarg))
 
         print("sayHello called with arg="+str(pbarg))
         resmsg = HelloMsgContent()
+
         return create_hellomsgcontent('responding back to java hello ')
        resmsg.h = 'Another response from Python'
+
 
        resmsg.f = 'frompython'
+
        resmsg.to = 'tojava'
+
        resmsg.hellomsg = 'Greetings from Python!!'
+
        for x in range(0,5):
+
            resmsg.x.append(float(x))
+
        return resmsg
+
   
+
   
+
if __name__ == '__main__':
+
    bridge = Py4jServiceBridge()
+
    print("bridge created")
+
    bridge.connect()
+
    print("bridge connected")
+
    hsid = bridge.export(HelloServiceImpl())
+
    print("exported")
+
    _wait_for_sec(5)
+
    bridge.unexport(hsid)
+
    print("unexported")
+
    bridge.disconnect()
+
    print("disconnected...exiting")
+
 
</pre>
 
</pre>
  
Note a couple of things about the above Python code:
+
Notes about this code:
  
 
#A HelloServiceImpl class that implements the '''sayHello''' method
 
#A HelloServiceImpl class that implements the '''sayHello''' method
Line 166: Line 161:
 
</pre>
 
</pre>
 
indicates the types (Python class) of the pbarg type and the '''sayHello''' return type.  In this example they are both HelloMsgContent, but they may be of any type as long as it is a protocol buffers Message type.
 
indicates the types (Python class) of the pbarg type and the '''sayHello''' return type.  In this example they are both HelloMsgContent, but they may be of any type as long as it is a protocol buffers Message type.
#The implementation of '''__main__''' method creates and then connects a Py4jServiceBridge instance.  This
 
Py4jServiceBridge is responsible for exporting the service from Python to Java. 
 
  
The complete Python code is available [https://github.com/ECF/Py4j-RemoteServicesProvider/blob/master/examples/org.eclipse.ecf.examples.protobuf.hello/python-src/run.py here] in the org.eclipse.ecf.examples.protobuf.hello [https://github.com/ECF/Py4j-RemoteServicesProvider/tree/master/examples/org.eclipse.ecf.examples.protobuf.hello project].
+
The complete Python code is available [https://github.com/ECF/Py4j-RemoteServicesProvider/blob/master/examples/org.eclipse.ecf.examples.protobuf.hello.pythonhost/python-src/hello.py here] in the org.eclipse.ecf.examples.protobuf.hello.pythonhost [https://github.com/ECF/Py4j-RemoteServicesProvider/tree/master/examples/org.eclipse.ecf.examples.protobuf.hello.pythonhost project].
 +
 
 +
The HelloServiceImpl class, along with the @protobuf_remote_service and @protobuf_remote_service_method decorators completes the python implementation.
 +
 
 +
===Accessing the hello Module===
 +
 
 +
To create an instance of HelloServiceImpl at runtime, some other python code has to be able to import the hello module.  Python 3 has support for creating an '''Import Hook''', that allows Java code (in this case an OSGi bundle) to resolve and import statement like this
 +
 
 +
<pre>
 +
import hello
 +
</pre>
 +
 
 +
This Py4j remote services provider now allows a '''Module Resolver''' service to be registered resulting in a Python 3 Import Hook to call back the Module Resolver to provide the Python code for the hello module.  In the org.eclipse.ecf.examples.protobuf.hello.pythonhost bundle, there is a Module Resolver service implementation called [https://github.com/ECF/Py4j-RemoteServicesProvider/blob/master/examples/org.eclipse.ecf.examples.protobuf.hello.pythonhost/src/org/eclipse/ecf/examples/protobuf/hello/pythonhost/PythonHostBundleModuleResolver.java PythonHostBundleModuleResolver] this implementation of the [https://github.com/ECF/Py4j-RemoteServicesProvider/blob/master/bundles/org.eclipse.ecf.provider.direct/src/org/eclipse/ecf/provider/direct/ModuleResolver.java ModuleResolver] service interface simply reads the hello module source code from /python-src directory inside the bundle when the hook is hit by the 'import hello' python statement.
 +
 
  
The declaration and implementation complete this service, and so now it may be consumed as an OSGi service.
 
  
 
==Example Hello Consumer==
 
==Example Hello Consumer==

Revision as of 20:50, 12 February 2018


Introduction

OSGi Services are an excellent way to implement small, dynamic, component-based systems. With features like service versioning, dynamics, service injection and instance dependency-handling, and a clear separation between contract and implementation make it a valuable technology for the Internet of Things.

Since OSGi is a Java-based framework, services have traditionally been both declared and implemented in Java. With the OSGi Remote Services specification, and ECF's implementation of both the Remote Services and Remote Service Admin specifications -specification, it's now possible to implement and use OSGi services implemented in Python or Java.

This tutorial shows a simple example of implementing an OSGi service implemented in Python, that uses Google Protocol Buffers for high-performance cross-language serialization. These services automatically inherit all of the aspects of a Java-only OSGi service (dynamics, service injection, versioning support, etc.).

Declaring a Service

Here is a simple example Java interface declaring a single 'sayHello' method:

public interface IHello {

	HelloMsgContent sayHello(HelloMsgContent message) throws Exception;
}

This interface is declared here in this project.

The HelloMsgContent class was generated by the Protocol Buffers protoc compiler based upon the following message declaration

syntax = "proto3";

package org.eclipse.ecf.examples.protobuf.hello;

message HelloMsgContent {
	string h = 1;
	string f = 2;
	string to = 3;
	string hellomsg = 4;
	repeated double x = 5;
}

This message declaration, when run through the protoc compiler, generates the Java HelloMsgContent source code, and also generates a Python version of the same class. The hellomsg.proto file is here.

With the IHello interface and the HelloMsgContent class, the IHello service declaration is complete.

Python Implementation of IHello Service

To implement this IHello service in Python it's necessary to provide a Python implementation class. Here's an example:

from hellomsg_pb2 import HelloMsgContent
from osgiservicebridge.protobuf import protobuf_remote_service
from osgiservicebridge.protobuf import protobuf_remote_service_method
from osgiservicebridge import ECF_SERVICE_EXPORTED_ASYNC_INTERFACES

def create_hellomsgcontent(message):
    resmsg = HelloMsgContent()
    resmsg.h = 'Another response from Python'
    resmsg.f = 'frompython'
    resmsg.to = 'tojava'
    resmsg.hellomsg = message
    for x in range(0,5):
        resmsg.x.append(float(x))
    return resmsg

@protobuf_remote_service(objectClass=['org.eclipse.ecf.examples.protobuf.hello.IHello'],export_properties = { ECF_SERVICE_EXPORTED_ASYNC_INTERFACES: 'org.eclipse.ecf.examples.protobuf.hello.IHello' })
class HelloServiceImpl:
    
    def __init__(self,props):
        self.props = props
        
    @protobuf_remote_service_method(arg_type=HelloMsgContent,return_type=HelloMsgContent)
    def sayHello(self,pbarg):
        print("sayHello called with arg="+str(pbarg))
        return create_hellomsgcontent('responding back to java hello ')

Notes about this code:

  1. A HelloServiceImpl class that implements the sayHello method
  2. The HelloServiceImpl class has a decorator:
@protobuf_remote_service(objectClass=['org.eclipse.ecf.examples.protobuf.hello.IHello'])
class HelloServiceImpl:

associates the HelloServiceImpl Python class with the IHello Java service interface.

  1. The sayHello method also has a decorator:
    @protobuf_remote_service_method(arg_type=HelloMsgContent,return_type=HelloMsgContent)
    def sayHello(self,pbarg):

indicates the types (Python class) of the pbarg type and the sayHello return type. In this example they are both HelloMsgContent, but they may be of any type as long as it is a protocol buffers Message type.

The complete Python code is available here in the org.eclipse.ecf.examples.protobuf.hello.pythonhost project.

The HelloServiceImpl class, along with the @protobuf_remote_service and @protobuf_remote_service_method decorators completes the python implementation.

Accessing the hello Module

To create an instance of HelloServiceImpl at runtime, some other python code has to be able to import the hello module. Python 3 has support for creating an Import Hook, that allows Java code (in this case an OSGi bundle) to resolve and import statement like this

import hello

This Py4j remote services provider now allows a Module Resolver service to be registered resulting in a Python 3 Import Hook to call back the Module Resolver to provide the Python code for the hello module. In the org.eclipse.ecf.examples.protobuf.hello.pythonhost bundle, there is a Module Resolver service implementation called PythonHostBundleModuleResolver this implementation of the ModuleResolver service interface simply reads the hello module source code from /python-src directory inside the bundle when the hook is hit by the 'import hello' python statement.


Example Hello Consumer

The example service consumer is available in this project. This consumer uses has the IHello service instance dynamically injected by Declarative Services and then it uses the service via this code (in activate method):

			HelloMsgContent.Builder b1 = HelloMsgContent.newBuilder();
			b1.addX(1.1);
			b1.addX(1.2);
			b1.setF("fromjava");
			b1.setTo("topython");
			b1.setHellomsg("Hello message from java!");
			b1.setH("An additional message from java");
			HelloMsgContent request = b1.build();
			HelloMsgContent result = this.helloService.sayHello(request);
			System.out.println("Received result="+result);

The second to last line makes the call from Java to Python via the IHello service proxy.

The Java Gateway

The only other piece required is a bundle to start the Java-side gateway, so that when the Python code is run it can connect to the Java gateway. The Java Gateway is created and configured via this component in this project.

Running the Hello Example

First, start the Java side (with the org.eclipse.ecf.examples.protobuf.hello bundle, org.eclipse.ecf.examples.protobuf.hello.consumer, and org.eclipse.ecf.examples.protobuf.hello.provider bundles). If run within Eclipse you may use the protobufhello.javaconsumer.launch config (note that the target platform must be set first to this target: releng/org.eclipse.ecf.provider.py4j.releng.target/ecf-oxygen.target).

Then the run.py should be started from Python. Prior to starting the following Python libraries must be installed: protobuf version 3.2.0, py4j version 0.10.6, and the osgiservicebridge package located in this project.

Once the run.py is started and the Py4jServiceBridge is connected to the JavaGateway, the IHello proxy will be created, injected into the HelloConsumer class, and then the sayHello call will be made to Python->HelloServiceImpl->sayHello.

This IHello service is just an example. Any other service could be similarly declared, implemented in Python, and injected and consumed in Java.

Background and Related Articles

Tutorial:_Creating_Custom_Distribution_Providers

Getting Started with ECF's OSGi Remote Services Implementation

OSGi Remote Services and ECF

Asynchronous Proxies for Remote Services

Static File-based Discovery of Remote Service Endpoints

Download ECF Remote Services/RSA Implementation

How to Add Remote Services/RSA to Your Target Platform

Back to the top