Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
EIG:OSGi Remote Services
OSGI Remote Services
some description should go here:
Contents
- 1 OSGI Remote Services
- 1.1 Introduction to OSGI Remote Services
- 1.2 Remoting
- 1.2.1 Creating an OSGi Service
- 1.2.2 Registering an OSGi Remote Service
- 1.2.3 Service Properties
- 1.2.4 Example
- 1.2.5 Using OSGi Remote Services
- 1.2.6 Example: Service Reference
- 1.2.7 Example: Service Tracker
- 1.2.8 Example: Service References with Filter
- 1.2.9 Asynchronous Remote Services (not OSGi standard)
- 1.2.10 Example
- 1.2.11 Example (Future)
- 1.3 Authentication
Introduction to OSGI Remote Services
Chapter 13 in the OSGi 4.2 compendium specifies a new standard for service-oriented architecture (SOA). This standard uses the well-established OSGi service registry to expose remote services, i.e. OSGi services that are distributed over a network.
ECF's support for this standard is implemented as a layered set of application programming interfaces (APIs). The modular structure allows service builders and service consumers to use an 'ala carte' model for defining, creating, deploying, integrating, and running distributed services. Such an 'ala carte' model is helpful for implementing distributed applications for two primary reasons:
1. It reduces overall system complexity. Networked/distributed applications frequently can quickly become very complicated. By only including modules that are actually necessary, rather than including/requiring functionality needed to support other use cases for distributed services, overall system complexity is reduced. For a lucid presentation about the value of modularity for simplifying complicated systems, see How Module Systems Drive Architectures.
2. It provides flexibility. Networked applications frequently require a good deal of flexibility, i.e. the flexibility to replace one transport with another, one security model for another, one deployment and management approach with another, synchronous RPC with asynchronous messaging. The flexibility of using OSGi modularity and ECF allows service developers and consumers to mix-and-match transport protocols and serialization formats, supports interoperability and integration with existing systems, and supports use of synchronous and/or asynchronous invocation patterns as needed.
Other Bundles
Bundle | Description |
---|---|
ch.ethz.iks.r_osgi.remote | R-OSGi Implementation. Only required when using "org.eclipse.ecf.provider.r_osgi" |
ch.ethz.iks.slp | SLP Implementation. Only required when using "org.eclipse.ecf.provider.jslp" |
org.apache.zookeeper | Apache ZooKeeper Implementation. Only required when using "org.eclipse.ecf.provider.zookeeper" |
org.objectweb.asm | Java bytecode manipulation and analysis framework. |
org.xbill.dns | Available from Orbit. Only required when using "org.eclipse.ecf.provider.dnssd" |
org.eclipse.ecf.provider.jgroups | Available from http://localhost:3282/jgroups
"org.eclipse.ecf.provider.jgroups.manager" |
Remoting
When we talk about services, we really talk about Java objects. We just call them services because of the way we use these Java objects. Google for "SOA Patterns" for more information on this topic. These Java objects are POJO's, as far as ECF is concerned. There is no need to implement anything from ECF in order for the "service" to be exposed as a remote service. The process to make a Java object (slash service) available on a remote machine is the mechanism we call "Remoting".
Creating an OSGi Service
OSGi Services should be defined by/registered under a specific interface.
public interface MyService { public String hello(String name); }
There should then exist some implementation of this service:
public class MyServiceImpl implements MyService { public String hello(String name) { return "Hi " + name + "! My name is Budhead."; } }
Registering an OSGi Remote Service
The registration of an OSGi Remote Service is nearly the same as with any regular service in an OSGi Framework (e.g. Eclipse Equinox). The only additional thing you need to do is setting some service properties. For the Eclipse Communication Framework (ECF) only two properties are required: The "service.exported.interfaces" property marks the service for export (as an OSGi Remote Service) and defines under which interfaces this service can be exported. The "service.exported.configs" property is a list of configuration types (endpoint types) that should be used to export the service. The other properties are optional settings for the distribution provider.
Service Properties
Property Name | Type | Example | Description |
---|---|---|---|
service.exported.configs | String+ | "ecf.generic.server", "ecf.r_osgi.peer" etc. | Types of endpoints that should be used for the export of the service. Distribution Providers create endpoints for each type they support. See the list of Distribution Providers for all of the supported Providers and their configuration. |
service.exported.intents | String+ | Intents that the distribution provider must implement. | |
service.exported.intents.extra | String+ | Configurable intents the distribution provider must implement (based on a topology). | |
service.exported.interfaces | String+ | "*" | Interfaces to export. |
service.intents | String+ | Intents that are already implemented by the exported service implementation. |
Example
public class Activator implements BundleActivator { private MyService myService = null; private ServiceRegistration myServiceRegistration = null; private static BundleContext context; public static BundleContext getContext() { return context; } public void start(BundleContext bundleContext) throws Exception { Activator.context = bundleContext; // Instantiate a service this.myService = new MyServiceImpl(); // Register the service instance as an OSGi Remote Service Properties props = new Properties(); props.put("service.exported.interfaces", "*"); props.put("service.exported.configs", "ecf.r_osgi.peer"); this.myServiceRegistration = bundleContext.registerService(MyService.class.getName(), this.myService, props); } public void stop(BundleContext bundleContext) throws Exception { // Unregister the service if(this.myServiceRegistration != null) { this.myServiceRegistration.unregister(); } this.myServiceRegistration = null; this.myService = null; } }
Using OSGi Remote Services
If you would like to use OSGi Remote Services this is as simple as with regular services in the framework. You don't need to do anything different as usual, all remote services discovered by an ECF Discovery Provider are registered with the service registry of your OSGi Framework. So you either use the "getServiceReference(s)" method of your bundle context or create a service tracker for your service.
If you have local and remote services of the same type (e.g. registered under the same interface) in your OSGi Framework, local services can be filtered out by using an LDAP filter on the property "service.imported".
Example: Service Reference
ServiceReference myServiceReference = bundleContext.getServiceReference(MyServiceInterface.class.getName()); if(myServiceReference != null) { MyService myService = (MyService) bundleContext.getService(myServiceReference); if(myService != hull) { System.out.println(myService.hello("Beavis")); } else { throw new ServiceException("Service object is null."); } } else { throw new ServiceException("Service reference is null."); }
Example: Service Tracker
public class MyServiceTracker extends ServiceTracker { public MyServiceTracker() { super(Activator.getContext(), MyService.class.getName(), null); } @Override public Object addingService(ServiceReference reference) { MyService myService = null; if(reference != null) { myService = (MyService) Activator.getContext().getService(reference); if(myService != null) { System.out.println("MyServiceInterface has come."); } else { throw new ServiceException("Service object is null."); } } else { throw new ServiceException("Service reference is null."); } return myService; } @Override public void removedService(ServiceReference reference, Object service) { System.out.println("MyServiceInterface has gone."); } }
Example: Service References with Filter
Filter filter = bundleContext.createFilter("(service.imported=*)"); ServiceReference[] myServiceReferences = bundleContext.getServiceReferences(MyService.class.getName(), filter.toString());
Asynchronous Remote Services (not OSGi standard)
Usualy Remote Procedure Calls operates synchronously. ECF provides an API for asynchronous calls that are executed in some other thread and don't block. This is sometimes desirable, for example, if your user interface thread is doing the calling, as remote methods can/could block for a much longer time than would be acceptable for users.
This asynchronous Remote Service implementation is no OSGi standard. For background on the discussion about asynchronous remote services invocation in general and possible future standardization by the OSGI standards organization see Peter Kriens blog entry, and Scott Lewis' blog entry.
For the Implementation of an asynchronous Remote Service the only thing you need to do is implementing an additional interface for your service. This interface should extend the IAsyncRemoteServiceProxy interface with an "Async" added to the interface name. This interface then should map all of the methods of your original interface with "Async" added to the method name and an additional callback parameter. Also this interface must be located in the same(!) package as the original interface of your service. The last parameter of the asynchronous method should then be an instance of the IAsyncCallback<?> interface with the original return type of your method as generic type parameter.
Another approach (next to IAsyncCallback<?>) to achieving asynchronous invocation of remote services are futures. Future objects allow the consumer to perform operations in between initiating a remote call and receiving a result.
Both of these approaches can be used seperate and also togehter.
public interface MyServiceAsync extends IAsyncRemoteServiceProxy { public void helloAsync(String name, IAsyncCallback<String> callback); // and/or public IFuture helloAsync(String name); }
Note that with this you will have direct dependencies to ECF bundles. Also some of the distribution providers still don't handle the registration of multiple interfaces correctly.
Example
if(myService instanceof MyServiceAsync) { ((MyServiceAsync)myService).helloAsync("Beavis", new MyServiceCallback()); }
public class MyServiceCallback implements IAsyncCallback<String> { @Override public void onSuccess(String result) { System.out.println(result); } @Override public void onFailure(Throwable e) { e.printStackTrace(); } }
Example (Future)
This initiates a remote call, performs some additional local operations and then gets a result from the future.
IFuture future = ((MyServiceAsync)myService).helloAsync("Beavis"); // Do other local operations here... String result = (String) future.get(); System.out.println(result);
Discovery is to discover exposed objects ECF implements a simple OSGi TopologyManager, the BasicTopologyManager. This is useful for simple RemoteServices, but if you have a more complex service environment with several service providers and comsumers etc. you need to implement your own TopologyManager.
Keep oin mind, if you implement your own TopologyManager you should not use the org.eclipse.ecf.osgi.services.distribution bundle anymore, as this is the bundle with the BasicTopologyManager.
class MyTopologyManager extends AbstractTopologyManager implements EventHook, EndpointListener { }
Authentication
This section adds authentication to a remote service.
Generic Server
ECF has a provider architecture for remote services, and so supports several different providers, but the ECF generic is the default.
For the ECF generic provider, it is very possible to programatically configure both the host and consumer for performing authentication during connect. Because OSGi does not specify any of this, however, it does require the use of ECF API.
Specifically, on the host/server, the following API allows the connect handler policy callback to be specified during configuration (e.g. DS activation):
org.eclipse.ecf.provider.generic.ServerSOContainer.setConnectPolicy(IConnectHandlerPolicy)
There are two ways to setup/call this method on the host/server container instance. One way is to configure this prior to the remote service export, the other way is to specify your own IHostContainerSelector service which will be called during the remote service export. See the discussion of the IConsumerContainerSelector service customization below...it's quite similar.
On the consumer/client, this method is ultimately called by the ECF remote service admin impl when the remote service is discovered and imported:
org.eclipse.ecf.core.IContainer.connect(ID, IConnectContext)
The connect context that's passed in to this call contains callbacks that can be used to (e.g.) show the user an interface for requesting the password entry from a user, or reading/using a password from somewhere (local store) and using that or something else (i.e. 2 above), or even returning a value specified via a system property (e.g.).
For ECF remote services, the easiest way to specify a non-null IConnectContext is to register a custom implementation of this service:
org.eclipse.ecf.osgi.services.remoteserviceadmin.IConsumerContainerSelector
The default implementation of the IConsumerContainerSelector service is this class:
org.eclipse.ecf.osgi.services.remoteserviceadmin.ConsumerContainerSelector
When the ECF remote service admin impl imports a remote service, the IConsumerContainerSelector whiteboard service is called back to select/create/configure the consumer container (and host container in the case of IHostContainerSelector).
To customize the IConnectContext used during the connect call, override this method:
org.eclipse.ecf.osgi.services.remoteserviceadmin.AbstractConsumerContainerSelector.getConnectContext(IContainer, ID)
the default impl returns null. Your impl would return a non-null IConnectContext, and then register an instance with the IConsumerContainerSelector service type. Then, during remote service import, your getConnectContext impl method will be called, and it may return an appropriate connect context (see also the utility class org.eclipse.ecf.core.security.ConnectContextFactory for creating IConnectContext impls).
Example bundles
We have created a two new example bundles (one customizing the remote service host to do authentication, the other customizing the remote service consumer/client to do the sending of the authentication information (i.e. username/password).
A couple of technical notes
1) There are multiple ways to configure an ECF container instance (server or client) prior to use for remote services. In order to show this wrt configuring authentication, in this example we did it in different ways for the host/server and client/consumer respectively.
For the host/server, we created the generic server container instance via the IContainerManager, and then configured it with an IConnectHandlerPolicy in the host Activator start [1]. This is all done *prior* to the registration and export of the remote service that occurs on line 43 of [1].
For the consumer/client, we registered a new instance of IConsumerContainerSelector in the Activator [2], and this consumer container selector's createContainer method gets called *when the remote service is discovered for the first time*. The createContainer method [2] not only creates the ecf.generic.client container (in super class), but it also sets an instance of IConnectInitiatorPolicy, which gets called to create the connectData holding the appropriate credentials.
2) The default ecf generic container does *not* use encryption for the connectData, so such credentials could be intercepted. It is possible, however, to use an an SSLServerSOContainer instance, which uses SSL for the connection. As you might expect, this does require necessary certificate availability and keystore configuration, to allow for the SSL socket connection to be used.
[1] com.mycorp.examples.timeservice.host.generic.auth.Activator
[2] com.mycorp.examples.timeservice.consumer.ds.generic.auth.GenericAuthConsumerContainerSelector