Skip to main content
Jump to: navigation, search

Tutorial: Python for OSGi Services


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

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

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

@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 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.

  1. The __init__ method can be passed a dictionary (argument name='props') of string->string types to initialize the HelloServiceImpl instance.

The complete Python code is available here.

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 callback the Module Resolver to provide the hello module Python code. In the org.eclipse.ecf.examples.protobuf.hello.pythonhost bundle, the ModuleResolver service impl is PythonHostBundleModuleResolver. This implementation reads the hello module source code from /python-src directory inside the bundle. This happens only when the import hook is hit by the actual execution of the import hello python statement.

IHello Service Consumer

The service consumer (caller of the IHello service) is available in this project. This consumer has the IHello service instance injected by Declarative Services, and then it calls the sayHello service instance in a separate thread:

	@Reference(policy=ReferencePolicy.DYNAMIC,cardinality=ReferenceCardinality.OPTIONAL,target="(service.imported=*)")
	void bindHello(IHello hello) {
		this.helloService = hello;
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					HelloMsgContent result = helloService.sayHello(createRequest());
					System.out.println("Java received sayHello result="+result);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}}).start();
	}

This is the line that actually makes the remote call of the Python-implemented remote service:

...
HelloMsgContent result = helloService.sayHello(createRequest());
...

This call results in the synchronous execution of the python code in HelloServiceImpl

    def sayHello(self,pbarg):
        print("sayHello called with arg="+str(pbarg))
        return create_hellomsgcontent('responding back to java hello ')

Running the Example

The following example bundles and their dependencies (in examples module) must be present in an Eclipse workspace to run this example:

org.eclipse.ecf.examples.protobuf.hello
org.eclipse.ecf.examples.protobuf.hello.consumer
org.eclipse.ecf.examples.protobuf.hello.javahost
org.eclipse.ecf.examples.protobuf.hello.provider
org.eclipse.ecf.examples.protobuf.hello.pythonhost

Once in workspace, the launch config examples/org.eclipse.ecf.examples.protobuf.hello.consumer/protobufhello.java.launch can be used to launch the example from the Eclipse Run/Debug Configurations dialog. The org.eclipse.ecf.examples.protobuf.hello.provider bundle is responsible for

  1. Launching Python 3 via the ExampleProtobufPythonLauncher and the ExampleProtobufPythonLaunchCommandProvider python command.
  2. Connect the Python process to the Java process via the ExampleProtobufPy4jProvider distribution provider

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