Accueil > Apache CXF, DOSGi > Eclipse RCP/RAP and Remoting with JAX-RS, Spring Data JPA and CXF DOSGi [step2]

Eclipse RCP/RAP and Remoting with JAX-RS, Spring Data JPA and CXF DOSGi [step2]


In [step1] we have downloaded CXF DOSGi « Multi Bundle Distribution » and created the fr.opensagres.remoting.exporter.dosgi.jaxrs bundle
to export on server side the UserService#findAll() with JAX-RS:

@Path("/user")
public interface UserService {

	@GET
	@Path("/findAll")
	@Produces(MediaType.APPLICATION_JSON)
	Collection<User> findAll();
...
}

In this article we will create the importer bundle fr.opensagres.remoting.importer.dosgi.jaxrs which will create a JAX-RS Client with Spring bean :

<jaxrs:client id="jaxrsUserService" address="http://127.0.0.1:9000/fr/opensagres/services/UserService"
	serviceClass="fr.opensagres.services.UserService" inheritHeaders="true">
</jaxrs:client>

and will register this JAX-RS Client in the OSGi register services as UserService :

<osgi:service ref="jaxrsUserService" interface="fr.opensagres.services.UserService" />

For recall, the Simple Client bundle fr.opensagres.simpleclient consumes a UserService in the Thread FindAllUsersThread from the OSGi registry services and display User list every 5 seconds. In our case the UserService instance will be the JAX-RS Client retrieved from the OSGi registry services :

<osgi:reference id="userService" interface="fr.opensagres.services.UserService" cardinality="0..1" timeout="1000" />

This JAX-RS Client UserService will be setted in the FindAllUsersThread with Dependency Injection :

<bean id="FindAllUsersThread" class="fr.opensagres.simpleclient.FindAllUsersThread"
		init-method="start" destroy-method="interrupt">
	<property name="userService" ref="userService"></property>
</bean>

This FindAllUsersThread (client side) consumes the UserService#findAll() every 5 seconds to display user list on the console :

// 1) findAll
users = userService.findAll();
displayUsers("findAll", users);

Download

You can download eclipse_spring_dosgi_step2.zip which contains the bundles and JARs explained in this article.

To use this zip, unzip it :

  1. import those projects in a workspace.
  2. open the TargetPlatform/eclipsespring.target file and click on Set as Target Platform. If you have problem with Target Definition, please read Problem with Install with Nebula (Incubation) P2.
  3. select TargetPlatform/launch/Server Remoting – JAXRS DOSGi – JPA Dao.launch to test the Remoting UserService JAX-RS with Mock Dao and Run it.
  4. select TargetPlatform/launch/Server Remoting – JAXRS DOSGi – JPA Dao.launch to test the Remoting UserService JAX-RS with JPA Dao and Run it.
  5. select TargetPlatform/launch/Client Remoting – JAXRS DOSGi – Simple OSGi Client.launch to test the Client Remoting UserService JAX-RS and Run it.

JAX-RS Client Side

Here we will create importer bundle which creates JAX-RS Client UserService by using the URL of the exported UserService (server side) and register this JAX-RS Client
in the OSGi registry services. Client Layer (RCP, RAP, Simple Client with Activator) will retrieve from the OSGi registry services this JAX-RS Client to consume UserService on server side.

Importer Bundle

Create the OSGi bundle fr.opensagres.remoting.importer.dosgi.jaxrs.

According the CXF DOSGi documentation , client must declared the OSGI-INF/remote-service/remote-services.xml like this:

<endpoint-descriptions xmlns="http://www.osgi.org/xmlns/rsa/v1.0.0">
  <endpoint-description>
    <property name="objectClass">
      <array>
        <value>fr.opensagres.services.UserService</value>
      </array>
    </property>
    <property name="endpoint.id">http://127.0.0.1:9000/fr/opensagres/services/UserService</property>
    <property name="service.imported.configs">org.apache.cxf.rs</property>
  </endpoint-description>
</endpoint-descriptions>

But I don’t like this mean because the URL of the services is hard coded in the XML file remote-services.xm. I prefer using XML Spring file to declare a bean with <jaxrs:client to manage the JAX-RS Client because with Spring it’s possible to use Spring EL to set the base URL and configure the base URL in a properties file hosted in a OSGi fragment (see here).

This mean allows to configure one time the base URL in a properties file and every the services can use the shared base URL property.

module-osgi-context

Create the META-INF/spring/module-osgi-context.xml to import the UserService with JAX-RS like this :

<?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"
	xmlns:jaxrs="http://cxf.apache.org/jaxrs"
	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.xsd
       http://cxf.apache.org/jaxrs
	   http://cxf.apache.org/schemas/jaxrs.xsd">

	<jaxrs:client id="jaxrsUserService" address="http://127.0.0.1:9000/fr/opensagres/services/UserService"
		serviceClass="fr.opensagres.services.UserService" inheritHeaders="true">
	</jaxrs:client>

	<osgi:service ref="jaxrsUserService" interface="fr.opensagres.services.UserService" />

</beans>

Here some explanation of this XML Spring file :

  • JAX-RS Client is declared as a bean Spring with jaxrsUserService id like this :
    <jaxrs:client id="jaxrsUserService" address="http://127.0.0.1:9000/fr/opensagres/services/UserService"
    		serviceClass="fr.opensagres.services.UserService" inheritHeaders="true">
    	</jaxrs:client>
    

    this declaration create a JAX-RS Client with org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean#create() by using the JAX-RS annotations of the UserService. Here the adress is hard coded but we will use Spring EL to defines the base URL in a property file in (next section.

  • the jaxrsUserService bean is registered in the OSGi services registry like this :
     <osgi:service ref="jaxrsUserService" interface="fr.opensagres.services.UserService" />
     

MANIFEST.MF

Modify the MANIFEST.MF of the fr.opensagres.remoting.importer.dosgi.jaxrs bundle like this:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: JAX-RS Importer Services
Bundle-SymbolicName: fr.opensagres.remoting.importer.dosgi.jaxrs
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Import-Package: fr.opensagres.domain,
 fr.opensagres.services,
 javax.ws.rs,
 javax.ws.rs.core,
 org.apache.cxf.jaxrs.client,
 org.springframework.data.domain

Here explanation about import packages:

  • fr.opensagres.domain, fr.opensagres.services and org.springframework.data.domain are imported because UserService use classes from those packages.
  • javax.ws.rs and javax.ws.rs.core must be imported because UserService uses JAX-RS annotations.
  • org.apache.cxf.jaxrs.client muset be imported because the XML declaration <jaxrs:client uses org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean class. This import package avoid this error :
      Exception in thread "SpringOsgiExtenderThread-3" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jaxrsUserService': Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.apache.cxf.jaxrs.client.Client org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean.create()] threw exception; nested exception is java.lang.IllegalArgumentException: interface org.apache.cxf.jaxrs.client.Client is not visible from class loader
    ...
    Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.apache.cxf.jaxrs.client.Client org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean.create()] threw exception; nested exception is java.lang.IllegalArgumentException: interface org.apache.cxf.jaxrs.client.Client is not visible from class loader
    

At this step JAX-RS Client is created and registered in the OSGi services registry as our UserService.

JAX-RS Client Launches

Now we must create a launch to use our importer bundle with CXF DOSGi. I find it’s the hardest task for manage remoting with CXF DOSGi.

Client Remoting – JAXRS DOSGi – Simple OSGi Client

Here we will create a launch to consume with remoting the UserService by the Simple OSGi Client fr.opensagres.simpleclient. This bundle starts a Thread FindAllUsersThread created by the moduel-context.xml Spring file :

<bean id="FindAllUsersThread" class="fr.opensagres.simpleclient.FindAllUsersThread"
		init-method="start" destroy-method="interrupt">
		<property name="userService" ref="userService"></property>
</bean>

and get the UserService instance by Dependency Injection retrieved from the OSGi services registry (in our case the JAX-RS Client) :

<!-- Consume UserService from the OSGi services registry -->
	<osgi:reference id="userService" interface="fr.opensagres.services.UserService"
		cardinality="0..1" timeout="1000" />

To create Client Remoting – JAXRS DOSGi- Simple OSGi Client:

  • Duplicate Simple OSGi Client – Mock Dao and rename it to Client Remoting – JAXRS DOSGi – Simple OSGi Client.
  • Unselect fr.opensagres.dao
  • Unselect fr.opensagres.dao.mock
  • Unselect fr.opensagres.data.injector
  • Unselect fr.opensagres.services.impl
  • select fr.opensagres.remoting.importer.dosgi.jaxrs with Auto-Start to true.

Now we must select CXF DOSGi (for client side):

  • select org.apache.cxf.bundle-minimal
  • select org.codehaus.jettison.jettison
  • select cxf-dosgi-ri-discovery-local with Auto-Start to true.

Click on Add Required Bundles button to select the dependent bundles of CXF DOSGi. I don’t know why, but some bundles are selected although we need not. So unselect:

  • msv-core
  • org.apache.ws.security.wss4j
  • stax-2api
  • woodstox-core-asl
  • xsdlib

It’s very important too to unselect javax.ws.rs.jsr311-api (JAX-RS API) and select org.apache.servicemix.specs.jsr311-api with Auto-Start to true to avoid this strange error:

875  [SpringOsgiExtenderThread-3] ERROR org.springframework.osgi.extender.internal.activator.ContextLoaderListener  - Application context refresh failed (OsgiBundleXmlApplicationContext(bundle=fr.opensagres.remoting.importer.dosgi.jaxrs, config=osgibundle:/META-INF/spring/*.xml))
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jaxrsUserService.proxyFactory': Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:
PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'serviceClass' threw exception; nested exception is java.lang.ExceptionInInitializerError
...

Run cient launch with server stopped

Don’t launch the server (don’t launch « Server Remoting – JAXRS DOSGi – Mock Dao » or « Server Remoting – JAXRS DOSGi – JPA Dao ») and run the launch « Client Remoting – JAXRS DOSGi- Simple OSGi Client ». You will see this log :

2218 [Thread-5] WARN  org.apache.cxf.phase.PhaseInterceptorChain  - Interceptor for {http://services.opensagres.fr/}UserService has thrown exception, unwinding now
org.apache.cxf.interceptor.Fault: Could not send Message.
...
	at fr.opensagres.simpleclient.FindAllUsersThread.run(FindAllUsersThread.java:33)
org.apache.cxf.jaxrs.client.ClientWebApplicationException: org.apache.cxf.interceptor.Fault: Could not send Message.
...
Caused by: java.net.ConnectException: ConnectException invoking http://127.0.0.1:9000/fr/opensagres/services/UserService/user/findAll: Connection refused: connect
	...
Caused by: java.net.ConnectException: Connection refused: connect
...

every 5 seconds. This log means that the Client Thread FindAllUsersThread tries to consumes the UserService every 5 seconds and server is not available.

Run cient launch with server started

Run the server launch (« Server Remoting – JAXRS DOSGi – Mock Dao » or « Server Remoting – JAXRS DOSGi – JPA Dao ») without stopping the client side and you will see the following logs every 5 seconds :

----------------- findAll ----------------- 
org.apache.cxf.jaxrs.client.ClientWebApplicationException: Method fr.opensagres.services.UserService.findAll is not a valid resource method
User [Angelo Zerr]
User [Pascal Leclercq]
User [Amine Bousta]
User [Mickael Baron]
User [Jawher Moussa]
User [Arnaud Cogoluegnes]
User [Lars Vogel]
User [Olivier Gierke]
User [Tom Schindl]
User [Wim Jongman]

Which means that UserService#findAll() is consumed with JAX-RS by FindAllUsersThread :

// 1) findAll
users = userService.findAll();
displayUsers("findAll", users);

And after the error :

1031 [Thread-5] ERROR org.apache.cxf.jaxrs.client.ClientProxyImpl  - Method fr.opensagres.services.UserService.findAll is not a valid resource method
	at org.apache.cxf.jaxrs.client.ClientProxyImpl.reportInvalidResourceMethod(ClientProxyImpl.java:546)

this error comes from the next code of FindAllUsersThread:

// 2) findAll with Pagination
usersPage = userService.findAll(pageable);

because :

Page<User> findAll(Pageable pageable);

have not JAX-RS annotation. We will do that in the next article [step3].

Logging

Like JAX-RS Server side, it’s interesting to set interceptor to logs JSON messages. To do that, modify the module-osgi-context.xml file like this:

<?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"
	xmlns:jaxrs="http://cxf.apache.org/jaxrs"
	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.xsd
       http://cxf.apache.org/jaxrs
	   http://cxf.apache.org/schemas/jaxrs.xsd">

	<jaxrs:client id="jaxrsUserService"
		address="http://127.0.0.1:9000/fr/opensagres/services/UserService"
		serviceClass="fr.opensagres.services.UserService" inheritHeaders="true">
		<jaxrs:inInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
		</jaxrs:inInterceptors>
		<jaxrs:outInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
		</jaxrs:outInterceptors>
	</jaxrs:client>

	<osgi:service ref="jaxrsUserService" interface="fr.opensagres.services.UserService" />

</beans>

Import the org.apache.cxf.interceptor package in the MANIFEST.MF to use the LoggingInInterceptor/LoggingOutInterceptor :

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: JAX-RS Importer Services
Bundle-SymbolicName: fr.opensagres.remoting.importer.dosgi.jaxrs
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Import-Package: fr.opensagres.domain,
 fr.opensagres.services,
 javax.ws.rs,
 javax.ws.rs.core,
 org.apache.cxf.jaxrs.client,
 org.apache.cxf.interceptor,
 org.springframework.data.domain

If you restart the launch you will see in the OSGi console the following logs for Outbound :

1171 [Thread-5] INFO  org.apache.cxf.interceptor.LoggingOutInterceptor  - Outbound Message
---------------------------
ID: 1
Address: http://127.0.0.1:9000/fr/opensagres/services/UserService/user/findAll
Http-Method: GET
Content-Type: application/xml
Headers: {Content-Type=[application/xml], Accept=[application/json]}
--------------------------------------

and you will see in the OSGi console the following logs for Inbound :

1234 [Thread-5] INFO  org.apache.cxf.interceptor.LoggingInInterceptor  - Inbound Message
----------------------------
ID: 1
Response-Code: 200
Encoding: ISO-8859-1
Content-Type: application/json
Headers: {Content-Length=[438], content-type=[application/json], Date=[Wed, 30 May 2012 16:22:23 GMT], Server=[Jetty(7.5.4.v20111024)]}
Payload: {"user":[{"firstName":"Angelo","lastName":"Zerr"},{"firstName":"Pascal","lastName":"Leclercq"},{"firstName":"Amine","lastName":"Bousta"},{"firstName":"Mickael","lastName":"Baron"},{"firstName":"Jawher","lastName":"Moussa"},{"firstName":"Arnaud","lastName":"Cogoluegnes"},{"firstName":"Lars","lastName":"Vogel"},{"firstName":"Olivier","lastName":"Gierke"},{"firstName":"Tom","lastName":"Schindl"},{"firstName":"Wim","lastName":"Jongman"}]}
--------------------------------------

Customize base URL with Fragment

At this step, the base URL of the server is hard coded in the XML Spring file. Imagine you have several JAX-RS Client Services which hard codes the base URL in XML Spring files and server URl changes. You need changes the every XML Spring bean <jaxrs:client. To avoid that, it’s better to set the base URL in a property file jaxrs-config.properties :

jaxrs-config.base-url=http://127.0.0.1:9000

This property file is hosted in a OSGi fragment in a OSGi fragment linked to the fr.opensagres.remoting.importer.dosgi.jaxrs.config linked to the fr.opensagres.remoting.importer.dosgi.jaxrs bundle

After that, we load this properties file in the XML Spring file of the fr.opensagres.remoting.importer.dosgi.jaxrs bundle :

<context:property-placeholder location="classpath:jaxrs-config.properties" />

and use jaxrs-config.base-url property like this :

<jaxrs:client id="jaxrsUserService"
	address="${jaxrs-config.base-url}/fr/opensagres/services/UserService"
	serviceClass="fr.opensagres.services.UserService" inheritHeaders="true">
</jaxrs:client>

Importer OSGi Bundle

Modify the module-osgi-context.xml of the fr.opensagres.remoting.importer.dosgi.jaxrs bundle like this:

<?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"
	xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:context="http://www.springframework.org/schema/context"
	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.xsd
	   http://www.springframework.org/schema/context 
	   http://www.springframework.org/schema/context/spring-context.xsd       
       http://cxf.apache.org/jaxrs
	   http://cxf.apache.org/schemas/jaxrs.xsd">

	<context:property-placeholder location="classpath:jaxrs-config.properties" />

	<jaxrs:client id="jaxrsUserService"
		address="${jaxrs-config.base-url}/fr/opensagres/services/UserService"
		serviceClass="fr.opensagres.services.UserService" inheritHeaders="true">
		<jaxrs:inInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
		</jaxrs:inInterceptors>
		<jaxrs:outInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
		</jaxrs:outInterceptors>
	</jaxrs:client>

	<osgi:service ref="jaxrsUserService" interface="fr.opensagres.services.UserService" />

</beans>

Importer configuration with OSGi Fragment

Create the OSGi fragment fr.opensagres.remoting.importer.dosgi.jaxrs.config. Create the jaxrs-config.propertyies file in src folder like this:

jaxrs-config.base-url=http://127.0.0.1:9000

Run client

In the Client Remoting – JAXRS DOSGi – Simple OSGi Client, select the OSGi fragment fr.opensagres.remoting.importer.dosgi.jaxrs.config. Run it to check that JAX-RS Client continues to work.

Conclusion

In this article we have developped the JAX-RS Client to consume in OSGi context the UserService exported with JAX-RS. In the next article [step3] we will manage the other methods with JAX-RS like :

Page<User> findAll(Pageable pageable);
Catégories :Apache CXF, DOSGi

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :