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 :
- import those projects in a workspace.
- 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.
- select TargetPlatform/launch/Server Remoting – JAXRS DOSGi – JPA Dao.launch to test the Remoting UserService JAX-RS with Mock Dao and Run it.
- select TargetPlatform/launch/Server Remoting – JAXRS DOSGi – JPA Dao.launch to test the Remoting UserService JAX-RS with JPA Dao and Run it.
- 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);
-
mai 31, 2012 à 1:56Eclipse RCP/RAP and Remoting with JAX-RS, Spring Data JPA and CXF DOSGi [step1] « Angelo's Blog
-
juin 6, 2012 à 12:34Eclipse RCP/RAP and Remoting with JAX-RS, Spring Data JPA and CXF DOSGi [step3] « Angelo's Blog