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 "Using Spring with ECF Remote Services"

(OSGi Remote Service Introduction)
Line 14: Line 14:
 
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 [https://bugs.eclipse.org/bugs/show_bug.cgi?id=302113 Bug 302113].
 
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 [https://bugs.eclipse.org/bugs/show_bug.cgi?id=302113 Bug 302113].
  
==OSGi Remote Service Introduction==  
+
==OSGi Remote Services Introduction==  
  
 
There are two roles in OSGi Remote Services:  the '''host''' (aka the service server), and the '''consumer''' (aka the client).
 
There are two roles in OSGi Remote Services:  the '''host''' (aka the service server), and the '''consumer''' (aka the client).

Revision as of 14:33, 12 February 2010

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 publish and access 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.

OSGi Remote Services Introduction

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

To use ECF's OSGi Remote Services implementation:

  1. Create an ECF IContainer with a type (eg : "ecf.generic.client" for Consumer and "ecf.generic.server" for Host).
  2. Publish (for Host) or Access (for Consumer) services by using OSGi services registry.

You can manage that with several means :

Here is a page that describes how to use standard Java code to publish (host) and access (consumer) a simple example service.

To Use Spring Dynamic Modules:

  • The Publish or Access services from OSGi service registry is managed by Spring DM by using <osgi:service and <osgi:reference.
  • The creation of ECF IContainer can be managed too with Spring by using ECF Spring support.

To explain ECF with Spring Dynamic Module we will use the Hello example which publish/retrieve a basic 'hello world' service. The sample code is based on the latest from CVS HEAD.

Publishing and Accessing Remote Services via Java Code

You can found Hello example with OSGi Java code into :

  • org.eclipse.ecf.examples.remoteservices.hello.host: host bundle which publish the Hello service implémentation with an ECF IContainer type of "ecf.generic.server".
  • org.eclipse.ecf.examples.remoteservices.hello.consumer: consumer bundle which call one time the service IHello by using an ECF IContainer type of "ecf.generic.client".

ECFRemotingServicesWithJavaCode.png

This scheme shows:

  • The Host bundle create the ECF IContainer and publish the services into the start method of the Bundle.
  • The Consumer bundle create the ECF IContainer and create an OSGi ServiceTracker to track the IHello service into the start method of the Bundle.

Publishing a Remote Service on Service Host

  • 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.
  • Publish the service into OSGi services 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 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 remote service
      helloRegistration = bundleContext.registerService(IHello.class.getName(), new Hello(), props);

Accessing a Remote Service on Service Consumer

  • 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();
    }
  • Access 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=*))");

Using Spring DM with OSGi Remote Services

You can found Hello example with Spring DM into:

  • org.eclipse.ecf.examples.remoteservices.hello.dm.host : host bundle which publish the Hello service implémentation with an ECF IContainer type of ecf.generic.server".
  • org.eclipse.ecf.examples.remoteservices.hello.dm.consumer : consumer bundle which create a Thread HelloClientThread and call 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 show you that ECF IContainer creation and Publish/retrieve services can be totally declarative by using Spring DM. Host and Consumer bundle 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 simpel bean definition and osgi definition (cf Spring DM Doc).

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

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

The Consumer module-conext.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 cod eof this Thread :

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 you will launch this consumer you will see into 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).

Host

  • Create an ECF (server) IContainer with declarative mean 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.

  • Publish you services with declarative mean by using Spring Dynamic Module :
    <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>

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>

Consumer

  • Create an ECF (client) IContainer instance via Spring declarative structures 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.

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>

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

Back to the top