Jump to: navigation, search

Using Spring with ECF Remote Services

Revision as of 06:59, 13 February 2010 by Angelo.zerr.gmail.com (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Introduction

Work is underway to integrate ECF's OSGi 4.2 Remote Services with Spring Dynamic Module so that it's easy to declare your OSGi Remote Services rather than exporting and consuming them programmatically. This work represents an integration and collaboration between the ECF project and the (proposed) Virgo project.

See (for the moment) Bug 302113. This bug is being used to track the contribution to ECF for this work. Attached to this bug are zips containing bundles

  • org.eclipse.ecf.examples.remoteservices.hello.dm.config.log4j: fragment to configure log4j required by Spring Extender bundle..
  • org.eclipse.ecf.examples.remoteservices.hello.dm.consumer: bundle of Consumer Hello sample managed with Spring DM.
  • org.eclipse.ecf.examples.remoteservices.hello.dm.host: bundle of Host Hello sample managed with Spring DM.
  • org.eclipse.ecf.springframework : bundle of ECF Spring support.

These samples work with ECF 3.2, which is scheduled for release on Feb 19, 2010. If you want test with older version of ECF, use the examples stored into ecf_3.0.0 of the zip that you can find on Bug 302113.

Using ECF's OSGi Remote Services Implementation

There are two roles in OSGi Remote Services: the host (aka the service exporter/publisher), and the consumer (aka the client/service consumer).

The host is responsible for exporting/publishing the remote service, and the consumer is responsible for using or consuming the service.

With ECF's OSGi Remote Services standard implementation, you can manage the 'host and consumer via several means:

Here is a page that describes how to use standard Java code to export (host) and consume (consumer) a simple example service. Another example with Java is given below.

Using Remote Services via Java Code

To describe what the Spring DM implementation is doing (described below, it's instructive to first describe what happens when OSGi Remote Services are exported programatically...i.e. via Java code. This section briefly shows how to export a remote service via OSGi standard Remote Services, and how to consume that same remote service via the OSGi ServiceTracker.

ECFRemotingServicesWithJavaCode.png

This scheme shows:

  • The host bundle creates the ECF IContainer instance(s), and exports/publishes the remote service via a single call to the OSGi BundleContext.registerService method with service properties set as specified by OSGi 4.2 Remote Services standard. According to the Remote Services standard specification, the service properties indicate that the service being registered should be exported.
  • The consumer bundle creates the ECF IContainer instance, and create an OSGi ServiceTracker to track the IHello service. With network-based discovery (i.e. ECF discovery), the ServiceTracker is asynchronously notified when the remote service is discovered.

Exporting a Remote Service

  • Create an IContainer instance. NOTE: In ECF 3.2's OSGi Remote Service implementation, it is OPTIONAL to do this step...as the IContainer instance will be created lazily by ECF's implementation.
  • Export the service via the OSGi service registry:
    // Setup properties for remote service distribution, as per OSGi 4.2 remote services
      // specification (chap 13 in compendium spec)
      Properties props = new Properties();
      // add OSGi service property indicated export of all interfaces exposed by service (wildcard)
      props.put(IDistributionConstants.SERVICE_EXPORTED_INTERFACES, IDistributionConstants.SERVICE_EXPORTED_INTERFACES_WILDCARD);
      // add OSGi service property specifying config
      props.put(IDistributionConstants.SERVICE_EXPORTED_CONFIGS, containerType);
      // add ECF service property specifying container factory args
      props.put(IDistributionConstants.SERVICE_EXPORTED_CONTAINER_FACTORY_ARGUMENTS, containerId);
      // register/publish remote service
      helloRegistration = bundleContext.registerService(IHello.class.getName(), new Hello(), props);

    Here the same code with the value of the constants replaced:

    // Setup properties for remote service distribution, as per OSGi 4.2 remote services
      // specification (chap 13 in compendium spec)
      Properties props = new Properties();
      // add OSGi service property indicated export of all interfaces exposed by service (wildcard)
      props.put("service.exported.interfaces", "*");
      // add OSGi service property specifying config
      props.put("service.exported.configs", "ecf.generic.server");
      // add ECF service property specifying container factory args
      props.put("org.eclipse.ecf.containerFactoryArgs", "ecftcp://localhost:3787/server");
      // register/publish remote service
      helloRegistration = bundleContext.registerService(IHello.class.getName(), new Hello(), props);

Consuming a Remote Service

  • Create an ECF IContainer instance with Java code such as the following:
    getContainerFactory().createContainer("ecf.generic.client");
    ...
     
    private IContainerFactory getContainerFactory() {
      if (containerFactoryServiceTracker == null) {
        containerFactoryServiceTracker = new ServiceTracker(bundleContext, IContainerFactory.class.getName(), null);
        containerFactoryServiceTracker.open();
      }
      return (IContainerFactory) containerFactoryServiceTracker.getService();
    }
  • Consume the remote service with OSGi ServiceTracker:
    // Create service tracker to track IHello instances that have the 'service.imported'
      // property set (as defined by OSGi 4.2 remote services spec).
      helloServiceTracker = new ServiceTracker(bundleContext, createRemoteFilter(), this);
      helloServiceTracker.open();
      ...
     
      private Filter createRemoteFilter() throws InvalidSyntaxException {
        // This filter looks for IHello instances that have the 
        // 'service.imported' property set, as specified by OSGi 4.2
        // remote services spec (Chapter 13)
        return bundleContext.createFilter("(&(" + org.osgi.framework.Constants.OBJECTCLASS + "=" + IHello.class.getName() + ")(" + SERVICE_IMPORTED + "=*))");
      }
      ...
     
      public Object addingService(ServiceReference reference) {
    		System.out.println("IHello service proxy being added");
    		// Since this reference is for a remote service,
    		// The service object returned is a proxy implementing the
    		// IHello interface
    		IHello hello = (IHello) bundleContext.getService(reference);
    		// This makes a remote 'hello' call
    		hello.hello(CONSUMER_NAME);
       ...
       }

    Here the same code of creation of OSGi Filter with constant value:

    private Filter createRemoteFilter() throws InvalidSyntaxException {
      return bundleContext.createFilter("(&(objectClass=org.eclipse.ecf.examples.remoteservices.hello.IHello)(service.imported=*))");

Remote Services with Spring DM

The Spring DM example code is found in these two projects/bundles:

  • org.eclipse.ecf.examples.remoteservices.hello.dm.host: host bundle -- which exports the Hello service implementation with ECF IContainer type "ecf.generic.server".
  • org.eclipse.ecf.examples.remoteservices.hello.dm.consumer: consumer bundle -- which creates a Thread HelloClientThread, and calls the service IHello every second by using an ECF IContainer type of "ecf.generic.client".

Here is a diagram showing how ECF's OSGi Remote Services implementation works with Spring DM:

ECFRemotingServicesWithSpringDM.png

This scheme shows that ECF IContainer creation and exporting and consuming services can be done declaratively with Spring DM. Host and Consumer bundles define a XML Spring file (in this sample module-context.xml is used but you can used any name and it's better to split files for simple bean definition and osgi definition).

The Spring bundle Extender load each XML Spring files stored into META-INF/Spring.

  • Host bundle define Spring file module-context.xml which exports the services (with ECF 3.2 no need to create the IContainer) by using <osgi:service with classic Spring DM.
  • Consumer bundle define Spring file module-context.xml which create the ECF Container by using ECF Spring support and consume the service by using <osgi:reference with classic Spring DM.

The Consumer Spring file module-context.xml define a Spring bean which create and start a Thread which use the service IHello getted. The IHello service is filled to this Thread by using Spring Dependency Injection. Here the code for this Thread to test the actual calling of the remote IHello service:

package org.eclipse.ecf.examples.internal.remoteservices.hello.dm.consumer;
 
import org.eclipse.ecf.examples.remoteservices.hello.IHello;
 
public class HelloClientThread extends Thread {
 
  private IHello hello;
 
  public void setHello(IHello hello) {
    this.hello = hello;
  }
 
  @Override
  public void run() {
    while (true) {
      try {
        hello.hello("HelloClientThread (Spring DM)");
	System.out.println("IHello service called with HelloClientThread (Spring DM).");
        Thread.sleep(1000);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}

When run, will see in the console:

org.springframework.osgi.service.ServiceUnavailableException: service matching filter=[(&(objectClass=org.eclipse.ecf.examples.remoteservices.hello.IHello)(service.imported=*))] unavailable
	at org.springframework.osgi.service.importer.support.internal.aop.ServiceDynamicInterceptor.getTarget(ServiceDynamicInterceptor.java:419)

which means that the Consumer is searching IHello service from the OSGi services registry. Once the service is discovered and can be accessed, the console will display each second:

IHello service called with HelloClientThread (Spring DM).

Using Spring DM on Host

  • Create an ECF IContainer server instance declaratively by using ECF Spring support:
    <bean class="org.eclipse.ecf.springframework.HostContainerFactoryBean">
    	<property name="containerType" value="ecf.generic.server" />
    </bean>

    In ECF 3.2's OSGi Remote Service implementation, this declaration is NOT required/optional as described above. The ECF implementation will lazily create the "ecf.generic.server" instance lazily.

  • Export your remote service declaratively with Spring DM:
    <bean id="helloService" class="org.eclipse.ecf.examples.remoteservices.hello.impl.Hello" />
     
    <osgi:service ref="helloService"
    	interface="org.eclipse.ecf.examples.remoteservices.hello.IHello">
    	<osgi:service-properties>
    		<entry key="service.exported.interfaces" value="*" />
    		<entry key="service.exported.configs" value="ecf.generic.server" />
                    <entry key="org.eclipse.ecf.containerFactoryArgs" value="ecftcp://localhost:3787/server" />
    	</osgi:service-properties>
    </osgi:service>

Host Spring file module-context.xml

Here the full XML file spring module-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
	xsi:schemaLocation="http://www.springframework.org/schema/osgi  
       http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd
       http://www.springframework.org/schema/beans   
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
 
        <!-- 1. Create an ECF (server) IContainer -->
	<bean class="org.eclipse.ecf.springframework.HostContainerFactoryBean">
		<property name="containerType" value="ecf.generic.server" />
	</bean>
 
	<!-- 2. Publish Hello Service implementation -->
	<bean id="helloService" class="org.eclipse.ecf.examples.remoteservices.hello.impl.Hello" />
 
	<osgi:service ref="helloService"
		interface="org.eclipse.ecf.examples.remoteservices.hello.IHello">
		<osgi:service-properties>
			<entry key="service.exported.interfaces" value="*" />
			<entry key="service.exported.configs" value="ecf.generic.server" />
			<entry key="org.eclipse.ecf.containerFactoryArgs" value="ecftcp://localhost:3787/server" />
		</osgi:service-properties>
	</osgi:service>
 
</beans>

Using Spring DM on Consumer

  • Create an ECF IContainer client instance declaratively via Spring ECF Spring support:
    <bean class="org.eclipse.ecf.springframework.ConsumerContainerFactoryBean">
    	<property name="containerType" value="ecf.generic.client" />
    </bean>
  • Retrieve the service with declarative mean by using Spring Dynamic Module :
    <osgi:reference id="helloService"
    	interface="org.eclipse.ecf.examples.remoteservices.hello.IHello"
    	timeout="1000" cardinality="0..1" filter="(service.imported=*)" />
  • Inject the service to the Thread HelloClientHread and start the Thread:
    <bean id="helloClient"
      class="org.eclipse.ecf.examples.internal.remoteservices.hello.dm.consumer.HelloClientThread"
      init-method="start" destroy-method="interrupt">
      <property name="hello" ref="helloService" />
    </bean>

Note you have cardinality="0..1" defined into <osgi:reference. this attribute is very important. Indead by default cardinality="1..1" and it means that the service MUST be retrieved. When you start the consumer bundle the service can be retrieved the first time and if you set not the cardinality to "0..1", the bean HelloClientThread which use this service can be NOT created.

Consumer Spring file module-context.xml

Here the full XML file spring module-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
	xsi:schemaLocation="http://www.springframework.org/schema/osgi  
       http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd
       http://www.springframework.org/schema/beans   
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
 
        <!-- 1. Create an ECF (client) IContainer -->
	<bean class="org.eclipse.ecf.springframework.ConsumerContainerFactoryBean">
		<property name="containerType" value="ecf.generic.client" />
	</bean>
 
        <!-- 2. Retrieve service from OSGi services registry -->
	<osgi:reference id="helloService"
		interface="org.eclipse.ecf.examples.remoteservices.hello.IHello"
		timeout="1000" cardinality="0..1" filter="(service.imported=*)" />
 
        <!-- 3. Create Thread HelloClientThread which call the IHello service which is retrieved from OSGI services and filled with Spring Dependency Injection-->
	<bean id="helloClient"
		class="org.eclipse.ecf.examples.internal.remoteservices.hello.dm.consumer.HelloClientThread"
		init-method="start" destroy-method="interrupt">
		<property name="hello" ref="helloService" />
	</bean>
 
</beans>

Getting and Using the ECF/Spring Integration Bundle

ECF provides a very small and simple bundle org.eclipse.ecf.springframework which is a support for Spring to manage:

This bundle is now available in the ECF incubation area. In anonymous CVS, it can be accessed here

protocol: pserver
host: dev.eclipse.org
path: /cvsroot/rt
Eclipse repository: :pserver:anonymous@dev.eclipse.org:/cvsroot/rt
module: org.eclipse.ecf/incubation/bundles/org.eclipse.ecf.springframework

NOTE: This project is currently in incubation, and so is not distributed as part of ECF's upcoming 3.2 release (Feb 19, 2010). We expect to include this work in our Helios/3.3 release, however.

NOTE: The ECF project is working with the propose Virgo project, to provide integration between ECF's support for OSGi 4.2 Remote Services and Virgo. The work described on this page will very likely be done in concert with the Virgo team.

Early/initial documentation of the contents of this project follows:

ContainerFactoryBean

HostContainerFactoryBean

HostContainerFactoryBean is Spring factory bean to create an ECF IContainer on server side. Here the XML bean declaration to create an ECF IContainer with type "ecf.generic.server"

<bean class="org.eclipse.ecf.springframework.HostContainerFactoryBean">
  <property name="containerType" value="ecf.generic.server" />
</bean>

This déclaration is the same thing like:

ContainerFactory.getDefault().createContainer("ecf.generic.server");
containerFactory parameter

The ECF IContainerFactory can be retrieved from OSGi services regisrty. With Java, you write this code:

IContainerFactory containerFactory = getContainerFactoryByUsingServiceTracker();
containerFactory.createContainer("ecf.generic.server");

You can manage that with XML Spring declaration like this:

<osgi:reference id="containerFactory"
		interface="org.eclipse.ecf.core.IContainerFactory" timeout="1000" />
 
<bean class="org.eclipse.ecf.springframework.HostContainerFactoryBean">
  <property name="containerType" value="ecf.generic.server" />
  <property name="containerFactory" ref="containerFactory" />
</bean>
containerManager parameter

You can use too the IContainerManager to create a factory into Java:

IContainerManager containerManager = getContainerManagerByUsingServiceTracker();
IContainerFactory containerFactory = containerManager.getContainerFactory();
containerFactory.createContainer("ecf.generic.server");

You can manage that with XML Spring declaration like this:

<osgi:reference id="containerManager"
		interface="org.eclipse.ecf.core.IContainerManager" timeout="1000" />
 
<bean class="org.eclipse.ecf.springframework.HostContainerFactoryBean">
  <property name="containerType" value="ecf.generic.server" />
  <property name="containerManager" ref="containerManager" />
</bean>
containerId parameter

ECF ID can be used to set the URL server where the client must be connected. Here the java code used to set the server URL at "ecftcp://localhost:3787/server":

ID containerID = IDFactory.getDefault().createStringID("ecftcp://localhost:3787/server");
IContainer container = ContainerFactory.getDefault().createContainer("ecf.generic.server", containerID);

You can manage that with XML Spring declaration like this:

<bean id="containerId"
  class="org.eclipse.ecf.springframework.identity.StringIDFactoryBean">
  <property name="stringID" value="ecftcp://localhost:3787/server" />
</bean>
 
<bean class="org.eclipse.ecf.springframework.HostContainerFactoryBean">
  <property name="containerType" value="ecf.generic.server" />
  <property name="containerId" ref="containerId" />
</bean>

ConsumerContainerFactoryBean

ConsumerContainerFactoryBean is Spring factory bean to create an ECF IContainer on client side. Here the XML bean declaration to create an ECF IContainer with type "ecf.generic.client"

<bean class="org.eclipse.ecf.springframework.ConsumerContainerFactoryBean">
  <property name="containerType" value="ecf.generic.client" />
</bean>

You can too set the IContainerFactory or the IContainerManager retrieved by OSGi services registry like HostContainerFactoryBean .

targetId parameter

ECF ID can be used to set the URL server where the client must be connected. Here teh java code used to set the server URL at "ecftcp://localhost:3787/server":

ID targetID = IDFactory.getDefault().createStringID("ecftcp://localhost:3787/server");
IContainer container = ContainerFactory.getDefault().createContainer("ecf.generic.client");
container.connect(targetID ,null);

You can manage that with XML Spring declaration like this :

<bean id="targetId"
  class="org.eclipse.ecf.springframework.identity.StringIDFactoryBean">
  <property name="stringID" value="ecftcp://localhost:3787/server" />
</bean>
 
<bean class="org.eclipse.ecf.springframework.ConsumerContainerFactoryBean">
  <property name="containerType" value="ecf.generic.client" />
  <property name="targetId" ref="targetId" />
</bean>

IDFactoryBean

StringIDFactoryBean

StringIDFactoryBean is Spring factory bean to create an ECF String ID. Here the XML bean declaration to create an ECF String ID with value ecftcp://localhost:3787/server":

<bean id="containerId"
  class="org.eclipse.ecf.springframework.identity.StringIDFactoryBean">
  <property name="stringID" value="ecftcp://localhost:3787/server" />
</bean>

This déclaration is the same thing like:

ID stringID = IDFactory.getDefault().createStringID("ecftcp://localhost:3787/server");
idFactory parameter

The ECF IIDFactory can be retrieved from OSGi services regisrty. With Java, you can code like this:

NOT SURE FOR THAT!!!

IIDFactory idFactory = getIDFactoryByUsingServiceTracker();
idFactory.createStringID("ecftcp://localhost:3787/server");

You can manage that with XML Spring declaration like this:

<osgi:reference id="idFactory"
		interface="org.eclipse.ecf.core.IIDFactory" timeout="1000" />
 
<bean id="containerId"
  class="org.eclipse.ecf.springframework.identity.StringIDFactoryBean">
  <property name="stringID" value="ecftcp://localhost:3787/server" />
  <property name="idFactory" ref="idFactory" />
</bean>

LongIDFactoryBean

Exist but NOT tested. TODO: create another IDFactoryBean.

Example Usage: TODO

ConnectContextFactoryBean

ConnectContextFactoryBean is Spring factory bean to create an ECF IConnectContext.

Example Usage: TODO

Creation of Launch/Product

Launch (Eclipse Application or OSGi Framework) is used to start your Host and Consumer Bundle. With launch, you can configre the bundles that you want start and configure it (start-level, auto-start...). Into DM example you have products (wgich generate launch) that you can use to start Host/Consumer bundles.

If you wish use ECF with Spring into Host/Consumer you must :

  • add required dependencies like Spring DM and ECF dependencies (and other like your API services, log4j configuration....)
  • configure several bundles to auto-start to true.

Product/Dependencies

SCOT COULD YOU EXPLAIN EACH BUNDLE OF ECF (discovery, ssl....)??? ECFRemotingServicesHostDependenciesProduct.png

Product/Configuration

Here a screenshot of the Configuration tab of the product "Hello Service DM Host (generic).product" from the bundle Host org.eclipse.ecf.examples.remoteservices.hello.dm.host :

ECFRemotingServicesHostConfigurationProduct.png

As you can see, you must set the auto-start to true for 3 bundles :

  • org.eclipse.ecf.examples.remoteservices.hello.dm.host which is the bundle which publish/export the implementation of the services.
  • org.eclipse.ecf.osgi.services.distribution : SCOTT COULD YOU EXPLAIN THE GOAL OF THIS BUNDLE PLEASE??? THANK A LOT.
  • org.springframework.osgi.extender which is the Spring Extender Bundle which detect for each bundles if it contains XML Spring files and load it. It's adwice to start this bunlde before others bundles. To manage that you can for instance set the start-level to 3.

This "recipe" must be followed too for the Consumer bundle :

ECFRemotingServicesConsumerConfigurationProduct.PNG