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

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


In [step2] we have implemented the JAX-RS Client which consumes the UserService#findAll() with JAX-RS:

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

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

We have seen that JAX-RS Client fails when UserService#findAll(Pageable pageable) is called, because we have not annoted this method with JAX-RS:

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)

In this article we wish to export and import with JAX-RS the UserService#findAll(Pageable pageable) which returns the paginated list of the User which uses Spring Data – Commons structures :

  • Pageable : this parameter is the pagination request.
  • Page: the findAll method returns this structure which is the result of the pagination.

On other words we will annotate the UserService#findAll(Pageable pageable) like this :

@POST
@Path("/findAllPage")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
Page<User> findAll(Pageable pageable);

However when I have managed JAX-RS with Spring Data structure, I had 2 big problems coming from :

  • Spring Data – Commons: Spring Data Page and Pageable are interfaces and not Pojo. JAX-RS works with Pojo which are annotated with JAXB annotations. How do manage Spring Data Page and Pageable interfaces with JAX-RS? Fortunately, JAXB 2.0 provides XmlJavaTypeAdapter (please read Using JAXB 2.0’s XmlJavaTypeAdapter) which gives the capability to serialize Page and Pageable interfaces with JAXB. I have suggested this idea to Oliver Gierke, the lead of the Spring Data and he is developing JAXB Adapter with SpringDataJaxb.java. However in this article we will not use this class (because this class is not finished) but we will create our own JAXB adapter which will be more simply to understand how to work the JAXB Adapter.
  • Apache CXF: on server side when CXF tries to serialize the Page class with JAXB, it fails because the JAXBContext doesn’t know the User class. This problem comes from that CXF is not able to populate the JAXBContext automaticly with Parameterized class (in our case , we have Page<T> and T=User, the JAXBContext is filled just with Page but not with User). You can fix the problem by setting extra class to the JSONProvider :
    <bean id="jsonProvider" class="org.apache.cxf.jaxrs.provider.JSONProvider">
    	<property name="singleJaxbContext" value="true" />
    	<property name="extraClass">
    		<list>
    			<value>fr.opensagres.domain.User</value>
    		</list>
    	</property>
    </bean>
    

    We will do that in this article, but I have created the patch CXF-4359 which fixes the explained problem below to avoid declaring extra classes for JSONProvider.

Download

You can download eclipse_spring_dosgi_step3.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.

Spring Data & JAXB Adapter

At the end of this article, we will annotate the UserService#findAll(Pageable pagable) with JAX-RS and our JAXB Adapter like this :

@POST
@Path("/findAllPage")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@XmlJavaTypeAdapter(PageAdapter.class)
Page<User> findAll(@XmlJavaTypeAdapter(PageableAdapter.class)  Pageable pageable);

But as soon as Spring Data will provide the JAXB Adapter for Page and Pageable, you will able to annotate the service without the JAXB Adapter like this :

@POST
@Path("/findAllPage")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
Page<User> findAll(Pageable pageable);

because Spring Data will declare the JAXB Adapter SpringDataJaxb.java in the package-info.java.

JAX-RS & JAXB Adapter for Pageable

In this section we will annotate UserService#findAll(Pageable pageable) with JAX-RS annotations like this:

@POST
@Path("/findAllPage")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
Page<User> findAll(Pageable pageable);

Here we have used @POST annotation because pageable is a complex parameter (it’s not a primitive type like String, Integer, etc…).

Modify the fr.opensagres.services.UserService interface like this:

package fr.opensagres.services;

import java.util.Collection;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import fr.opensagres.domain.User;

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

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

	@POST
	@Path("/findAllPage")
	@Consumes({ MediaType.APPLICATION_JSON })
	@Produces({ MediaType.APPLICATION_JSON })
	Page<User> findAll(Pageable pageable);

	Collection<User> findByFirstNameLikeAndLastNameLike(String firstName,
			String lastName);

	Page<User> findByFirstNameLikeAndLastNameLike(String firstName,
			String lastName, Pageable pageable);

	User saveUser(User user);

}

If you run the Client Remoting – JAXRS DOSGi – Simple OSGi Client launch, you will see in the console, the following error :

11516 [Thread-5] ERROR org.apache.cxf.jaxrs.client.AbstractClient  - .No message body writer has been found for class : class org.springframework.data.domain.PageRequest, ContentType : application/json.

when UserService#findAll(Pageable pageable) is called in the FindAllUsersThread of the fr.opensagres.simpleclient bundle :

Pageable pageable = new PageRequest(0, 2);
...
// 2) findAll with Pagination
usersPage = userService.findAll(pageable);

This error comes from that Pageable implementation (in our case PageRequest) cannot be serialized with JAXB:

  • Pageable is an interface and not a Pojo.
  • Pageable is not annotated with JAXB.

To resolve this problem we need to tell to CXF that Pageable must be transformed to a Pojo annotated with JAXB before JAXB serialisation. To do that,
we need create an implementation of JAXB XmlTypeAdapter PageableAdapter which transforms the Pageable to a Pojo annotated with JAXB and after we will able to use our PageableAdapter like this:

@POST
@Path("/findAllPage")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
Page findAll(@XmlJavaTypeAdapter(PageableAdapter.class)  Pageable pageable);

In our case we will implement org.springframework.data.domain.jaxb.PageableAdapter in the fr.opensagres.services bundles to avoid creating a new bundles.

Import JAXB Adapter packages

To implement and use JAXB Adapter, you need import in the fr.opensagres.services bundle:

  • javax.xml.bind.annotation.
  • javax.xml.bind.annotation.adapters.

Modify the MANIFEST.MF of the fr.opensagres.services bundle like this :

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Services API
Bundle-SymbolicName: fr.opensagres.services
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Export-Package: fr.opensagres.services
Import-Package: fr.opensagres.domain,
 javax.ws.rs;resolution:=optional,
 javax.ws.rs.core;resolution:=optional,
 javax.xml.bind.annotation;resolution:=optional,
 javax.xml.bind.annotation.adapters;resolution:=optional,
 org.springframework.data.domain

UserService & JAXB Adapter for Pageable

Modify the UserService like this :

package fr.opensagres.services;

import java.util.Collection;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.jaxb.PageableAdapter;

import fr.opensagres.domain.User;

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

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

	@POST
	@Path("/findAllPage")
	@Consumes({ MediaType.APPLICATION_JSON })
	@Produces({ MediaType.APPLICATION_JSON })
	Page<User> findAll(@XmlJavaTypeAdapter(PageableAdapter.class)  Pageable pageable);

	Collection<User> findByFirstNameLikeAndLastNameLike(String firstName,
			String lastName);

	Page<User> findByFirstNameLikeAndLastNameLike(String firstName,
			String lastName, Pageable pageable);

	User saveUser(User user);

}

JaxbOrder

Here we must create a Pojo annotated with JAXB which represents the Spring Data Sort.Order. Create the org.springframework.data.domain.jaxb.JAxbOrder class in the fr.opensagres.services bundle like this :

package org.springframework.data.domain.jaxb;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "order")
@XmlAccessorType(XmlAccessType.FIELD)
public class JaxbOrder {

	private String direction;
	private String property;

	public String getDirection() {
		return direction;
	}

	public void setDirection(String direction) {
		this.direction = direction;
	}

	public String getProperty() {
		return property;
	}

	public void setProperty(String property) {
		this.property = property;
	}

}

JaxbSort

Here we must create a Pojo annotated with JAXB which represents the Spring Data Sort. Create the org.springframework.data.domain.jaxb.JAxbSort class in the fr.opensagres.services bundle like this :

package org.springframework.data.domain.jaxb;

import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "sort")
@XmlAccessorType(XmlAccessType.FIELD)
public class JaxbSort {

	private List<JaxbOrder> orders;

	public void setOrders(List<JaxbOrder> orders) {
		this.orders = orders;
	}

	public List<JaxbOrder> getOrders() {
		return orders;
	}
}

JaxbPageable

Here we must create a Pojo annotated with JAXB which represents the Spring Data Pageable. Create the org.springframework.data.domain.jaxb.JAxbPageable class in the fr.opensagres.services bundle like this :

package org.springframework.data.domain.jaxb;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "pageable")
@XmlAccessorType(XmlAccessType.FIELD)
public class JaxbPageable {

	private int pageNumber;

	private int pageSize;

	private int offset;

	private JaxbSort sort;

	public int getPageNumber() {
		return pageNumber;
	}

	public void setPageNumber(int pageNumber) {
		this.pageNumber = pageNumber;
	}

	public int getPageSize() {
		return pageSize;
	}

	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}

	public int getOffset() {
		return offset;
	}

	public void setOffset(int offset) {
		this.offset = offset;
	}

	public void setSort(JaxbSort sort) {
		this.sort = sort;
	}

	public JaxbSort getSort() {
		return sort;
	}
}

SortAdapter

At this step we can create the JAXB Adapter for the Sort to marshall/unmarshall Spring Data Sort to JaxbSort. Create the org.springframework.data.domain.jaxb.SortAdapter class like this:

package org.springframework.data.domain.jaxb;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.bind.annotation.adapters.XmlAdapter;

import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;

public class SortAdapter extends XmlAdapter<JaxbSort, Sort> {

	private static final SortAdapter INSTANCE = new SortAdapter();

	public static SortAdapter getInstance() {
		return INSTANCE;
	}

	@Override
	public JaxbSort marshal(Sort sort) throws Exception {
		if (sort == null) {
			return null;
		}
		JaxbSort pojoSort = new JaxbSort();
		List<JaxbOrder> pojoOrders = new ArrayList<JaxbOrder>();
		pojoSort.setOrders(pojoOrders);
		for (Iterator<Order> orders = sort.iterator(); orders.hasNext();) {
			Order order = orders.next();
			JaxbOrder pojoOrder = new JaxbOrder();
			pojoOrder.setDirection(order.getDirection().name());
			pojoOrder.setProperty(order.getProperty());
			pojoOrders.add(pojoOrder);
		}
		return pojoSort;
	}

	@Override
	public Sort unmarshal(JaxbSort pojo) throws Exception {
		if (pojo == null) {
			return null;
		}
		List<Order> orders = new ArrayList<Sort.Order>();
		for (JaxbOrder pojoOrder : pojo.getOrders()) {
			Direction direction = Direction
					.fromString(pojoOrder.getDirection());
			Order order = new Order(direction, pojoOrder.getProperty());
			orders.add(order);
		}

		return new Sort(orders);
	}

}

PageableAdapter

At this step we can create the JAXB Adapter for the Pageable to marshall/unmarshall Spring Data Pageable to JaxbPageable. Create the org.springframework.data.domain.jaxb.PageableAdapter class like this:

package org.springframework.data.domain.jaxb;

import javax.xml.bind.annotation.adapters.XmlAdapter;

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

public class PageableAdapter extends XmlAdapter<JaxbPageable, Pageable> {

	@Override
	public JaxbPageable marshal(Pageable pageable) throws Exception {
		JaxbPageable pojo = new JaxbPageable();
		pojo.setOffset(pageable.getOffset());
		pojo.setPageNumber(pageable.getPageNumber());
		pojo.setPageSize(pageable.getPageSize());
		pojo.setSort(SortAdapter.getInstance().marshal(pageable.getSort()));
		return pojo;
	}

	@Override
	public Pageable unmarshal(JaxbPageable pojo) throws Exception {
		Sort sort = SortAdapter.getInstance().unmarshal(pojo.getSort());
		if (sort != null) {
			return new PageRequest(pojo.getPageNumber(), pojo.getPageSize(),
					sort);
		}
		return new PageRequest(pojo.getPageNumber(), pojo.getPageSize());
	}

}

Run

If you run the Client Remoting – JAXRS DOSGi – Simple OSGi Client launch, you will see in the console (client side) this trace :

6453 [Thread-5] INFO  org.apache.cxf.interceptor.LoggingOutInterceptor  - Outbound Message
---------------------------
ID: 4
Address: http://192.168.1.11:9000/fr/opensagres/services/UserService/user/findAllPage
Http-Method: POST
Content-Type: application/json
Headers: {Content-Type=[application/json], Accept=[application/json]}
Payload: {"pageable":{"pageNumber":0,"pageSize":2,"offset":0}}
--------------------------------------

which shows you Pageable is well serialized with JAXB, but you will see the following error :

6485 [Thread-5] INFO  org.apache.cxf.interceptor.LoggingInInterceptor  - Inbound Message
----------------------------
ID: 4
Response-Code: 500
Encoding: ISO-8859-1
Content-Type: text/plain
Headers: {Content-Length=[66], content-type=[text/plain], Date=[Mon, 04 Jun 2012 08:38:35 GMT], Server=[Jetty(7.5.4.v20111024)]}
Payload: No message body writer has been found for response class PageImpl.
--------------------------------------

This error comes from the server side which tries to serialize Page interface with JAXB. To fix this problem we need to create a JAXB Adaptor for Page like we have done with Pageable.

JAX-RS & JAXB Adapter for Page

In this section we will create the JAXB Adapter PageAdapter for Page to use it like this :

@POST
@Path("/findAllPage")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@XmlJavaTypeAdapter(PageAdapter.class)
Page<User> findAll(@XmlJavaTypeAdapter(PageableAdapter.class)  Pageable pageable);

Modify the fr.opensagres.services.UserService interface like this:

package fr.opensagres.services;

import java.util.Collection;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import fr.opensagres.domain.User;

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

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

	@POST
	@Path("/findAllPage")
	@Consumes({ MediaType.APPLICATION_JSON })
	@Produces({ MediaType.APPLICATION_JSON })
	@XmlJavaTypeAdapter(PageAdapter.class)
	Page<User> findAll(@XmlJavaTypeAdapter(PageableAdapter.class)  Pageable pageable);

	Collection<User> findByFirstNameLikeAndLastNameLike(String firstName,
			String lastName);

	Page<User> findByFirstNameLikeAndLastNameLike(String firstName,
			String lastName, Pageable pageable);

	User saveUser(User user);

}

JaxbPage

Here we must create a Pojo annotated with JAXB which represents the Spring Data Page. Create the org.springframework.data.domain.jaxb.JAxbPage class in the fr.opensagres.services bundle like this :

package org.springframework.data.domain.jaxb;

import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "page")
@XmlAccessorType(XmlAccessType.FIELD)
public class JaxbPage {

	@SuppressWarnings("rawtypes")
	private List content;

	private int number;

	private int size;

	private int totalPages;

	private int numberOfElements;

	private long totalElements;

	private JaxbSort sort;

	@SuppressWarnings("rawtypes")
	public List getContent() {
		return content;
	}

	@SuppressWarnings("rawtypes")
	public void setContent(List content) {
		this.content = content;
	}

	public int getNumber() {
		return number;
	}

	public void setNumber(int number) {
		this.number = number;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public int getTotalPages() {
		return totalPages;
	}

	public void setTotalPages(int totalPages) {
		this.totalPages = totalPages;
	}

	public int getNumberOfElements() {
		return numberOfElements;
	}

	public void setNumberOfElements(int numberOfElements) {
		this.numberOfElements = numberOfElements;
	}

	public long getTotalElements() {
		return totalElements;
	}

	public void setTotalElements(long totalElements) {
		this.totalElements = totalElements;
	}

	public JaxbSort getSort() {
		return sort;
	}

	public void setSort(JaxbSort sort) {
		this.sort = sort;
	}

}

PageResponse

We need to create an implementation of Page by using the JaxPage. To do that, create the org.springframework.data.domain.jaxb.PageResponse like this :


package org.springframework.data.domain.jaxb;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;

@SuppressWarnings("rawtypes")
public class PageResponse implements Page {

	private final JaxbPage pojo;

	public PageResponse(JaxbPage pojo) {
		this.pojo = pojo;
	}

	public List getContent() {
		return pojo.getContent() != null ? pojo.getContent()
				: Collections.EMPTY_LIST;
	}

	public int getNumber() {
		return pojo.getNumber();
	}

	public int getNumberOfElements() {
		return pojo.getNumberOfElements();
	}

	public int getSize() {
		return pojo.getSize();
	}

	public Sort getSort() {
		try {
			return SortAdapter.getInstance().unmarshal(pojo.getSort());
		} catch (Exception e) {
			return null;
		}
	}

	public long getTotalElements() {
		return pojo.getTotalElements();
	}

	public int getTotalPages() {
		return pojo.getTotalPages();
	}

	public boolean hasContent() {
		return getContent().size() > 0;
	}

	public boolean hasNextPage() {
		return false;
	}

	public boolean hasPreviousPage() {
		return false;
	}

	public boolean isFirstPage() {
		return false;
	}

	public boolean isLastPage() {
		return false;
	}

	public Iterator iterator() {
		return null;
	}

}

PageAdapter

At this step we can create the JAXB Adapter for the Page to marshall/unmarshall Spring Data Page to JaxbPage. Create the org.springframework.data.domain.jaxb.PageAdapter class like this:

package org.springframework.data.domain.jaxb;

import javax.xml.bind.annotation.adapters.XmlAdapter;

import org.springframework.data.domain.Page;

@SuppressWarnings("rawtypes")
public class PageAdapter extends XmlAdapter<JaxbPage, Page> {

	public Page unmarshal(final JaxbPage pojo) throws Exception {
		return new PageResponse(pojo);
	}

	public JaxbPage marshal(Page page) throws Exception {
		JaxbPage pojo = new JaxbPage();
		pojo.setContent(page.getContent());
		pojo.setNumber(page.getNumber());
		pojo.setNumberOfElements(page.getNumberOfElements());
		pojo.setSize(page.getSize());
		pojo.setTotalElements(page.getTotalElements());
		pojo.setTotalPages(page.getTotalPages());
		return pojo;
	}

}

Run

If you run the Client Remoting – JAXRS DOSGi – Simple OSGi Client launch, you will see in the console (client side) this error :

----------------------------
ID: 3
Response-Code: 500
Encoding: ISO-8859-1
Content-Type: text/plain
Headers: {Content-Length=[197], content-type=[text/plain], Server=[Jetty(7.5.4.v20111024)]}
Payload: JAXBException occurred : class fr.opensagres.domain.User nor any of its super class is known to this context.. class fr.opensagres.domain.User nor any of its super class is known to this context.. 

You can see this error on the console (server side):

---------------------------
ID: 8
Response-Code: 500
Content-Type: text/plain
Headers: {Content-Type=[text/plain]}
Payload: JAXBException occurred : class fr.opensagres.domain.User nor any of its super class is known to this context.. class fr.opensagres.domain.User nor any of its super class is known to this context.. 
...
23468 [qtp14112795-42] WARN  org.apache.cxf.jaxrs.provider.AbstractJAXBProvider  - javax.xml.bind.MarshalException
 - with linked exception:
[javax.xml.bind.JAXBException: class fr.opensagres.domain.User nor any of its super class is known to this context.]
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(Unknown Source)
	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(Unknown Source)
	at org.apache.cxf.jaxrs.provider.JSONProvider.marshal(JSONProvider.java:455)

As you can see, the error comes from the JSONProvider whith JAXBContext which doesn’t contains the User class. This problem comes from that CXF is not able to to populate the JAXBContext automaticly with Parameterized class (in our case , we have Page<T> and T=User, the JAXBContext is filled just with JaxbPage but not with User). I have created to CXF Team the patch CXF-4359 which fixes this problem. But for the moment you can fix the problem by setting extra class to the JSONProvider.

Configure JSONProvide with extra class User

To resolve the problem :

Payload: JAXBException occurred : class fr.opensagres.domain.User nor any of its super class is known to this context.. class fr.opensagres.domain.User nor any of its super class is known to this 

and by waiting the apply of the patch CXF-4359 (hope CXF will accept this patch), we must configure the JSONProvider with extra class User on client and server side :

<bean id="jsonProvider" class="org.apache.cxf.jaxrs.provider.JSONProvider">
	<property name="singleJaxbContext" value="true" />
	<property name="extraClass">
		<list>
			<value>fr.opensagres.domain.User</value>
		</list>
	</property>
</bean> 

Server side

On server side, modify the module-osgi-context.xml of the fr.opensagres.remoting.exporter.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                      http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">

	<osgi:reference id="userService" interface="fr.opensagres.services.UserService" />

	<osgi:service interface="fr.opensagres.services.UserService">
		<osgi:service-properties>
			<entry key="service.exported.interfaces" value="*" />
			<entry key="service.exported.configs" value="org.apache.cxf.rs" />
			<entry key="service.exported.intents" value="HTTP" />
			<entry key="org.apache.cxf.rs.databinding" value="jaxb" />
			<!-- Logs -->
			<entry key="org.apache.cxf.rs.in.interceptors" value="org.apache.cxf.interceptor.LoggingInInterceptor" />
			<entry key="org.apache.cxf.rs.out.interceptors" value="org.apache.cxf.interceptor.LoggingOutInterceptor" />
			<!-- JAXB Context -->
			<entry key="org.apache.cxf.rs.provider">
				<array>
					<ref bean="jsonProvider" />
				</array>
			</entry>
		</osgi:service-properties>
		<ref bean="userService" />
	</osgi:service>

	<bean id="jsonProvider" class="org.apache.cxf.jaxrs.provider.JSONProvider">
		<property name="singleJaxbContext" value="true" />
		<property name="extraClass">
			<list>
				<value>fr.opensagres.domain.User</value>
			</list>
		</property>
	</bean>

</beans>

Here the jsonProvider bean declare extra class User for JSONProvider and it is used by the org.apache.cxf.rs.provider property :

<!-- JAXB Context -->
<entry key="org.apache.cxf.rs.provider">
	<array>
		<ref bean="jsonProvider" />
	</array>
</entry>

If you run the Client Remoting – JAXRS DOSGi – Simple OSGi Client launch, you will see in the console (server side) you will see this trace :

13968 [qtp9137209-44] INFO  org.apache.cxf.interceptor.LoggingOutInterceptor  - Outbound Message
---------------------------
ID: 4
Response-Code: 200
Content-Type: application/json
Headers: {Date=[Mon, 04 Jun 2012 09:11:37 GMT]}
Payload: {"page":{"content":[{"@xsi.type":"user","firstName":"Angelo","lastName":"Zerr"},{"@xsi.type":"user","firstName":"Pascal","lastName":"Leclercq"}],"number":0,"size":2,"totalPages":5,"numberOfElements":2,"totalElements":10}}
--------------------------------------

which shows you that Page is well serialized with JAXB. But on the console (client side), you will see the following error :

6516 [Thread-5] ERROR org.apache.cxf.jaxrs.client.AbstractClient  - .Problem with reading the response message, class : interface org.springframework.data.domain.Page, ContentType : application/json.
org.apache.cxf.jaxrs.client.ClientWebApplicationException: .Problem with reading the response message, class : interface org.springframework.data.domain.Page, ContentType : application/json.
	at org.apache.cxf.jaxrs.client.AbstractClient.reportMessageHandlerProblem(AbstractClient.java:593)
...
Caused by: javax.ws.rs.WebApplicationException: java.lang.NullPointerException
	at org.apache.cxf.jaxrs.provider.JSONProvider.readFrom(JSONProvider.java:240)
	at org.apache.cxf.jaxrs.client.AbstractClient.readBody(AbstractClient.java:437)
	... 27 more
Caused by: java.lang.NullPointerException
	at com.sun.org.apache.xalan.internal.xsltc.trax.SAX2DOM.startElement(Unknown Source)

This error comes from that JSONProvider is not configured with extra class User on client side.

Client side

On server side, 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:providers>
			<ref bean="jsonProvider" />
		</jaxrs:providers>
	</jaxrs:client>

	<bean id="jsonProvider" class="org.apache.cxf.jaxrs.provider.JSONProvider">
		<property name="singleJaxbContext" value="true" />
		<property name="extraClass">
			<list>
				<value>fr.opensagres.domain.User</value>
			</list>
		</property>
	</bean>

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

To avoid having problem with ClassNotFoundException with JSONProvider you must import org.apache.cxf.jaxrs.provider package. Modify the MANIFEST.MF 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;version="1.0.0",
 javax.ws.rs.core;version="1.1.1",
 javax.ws.rs.ext;version="1.1.0",
 org.apache.cxf.interceptor;version="2.5.2",
 org.apache.cxf.jaxrs.client;version="2.5.2",
 org.apache.cxf.jaxrs.provider;version="2.5.2",
 org.apache.cxf.jaxrs.utils,
 org.springframework.data.domain

Run

If you run the Client Remoting – JAXRS DOSGi – Simple OSGi Client launch, you will see in the console, this CXF trace :

--------------------------------------
2094 [Thread-5] INFO  org.apache.cxf.interceptor.LoggingInInterceptor  - Inbound Message
----------------------------
ID: 2
Response-Code: 200
Encoding: ISO-8859-1
Content-Type: application/json
Headers: {Content-Length=[221], content-type=[application/json], Date=[Wed, 06 Jun 2012 10:17:10 GMT], Server=[Jetty(7.5.4.v20111024)]}
Payload: {"page":{"content":[{"@xsi.type":"user","firstName":"Angelo","lastName":"Zerr"},{"@xsi.type":"user","firstName":"Pascal","lastName":"Leclercq"}],"number":0,"size":2,"totalPages":5,"numberOfElements":2,"totalElements":10}}
--------------------------------------

And you can check that paginated list of the User is well displayed:

----------------- findAll with Pagination [0,2] ----------------- 
User [Angelo Zerr]
User [Pascal Leclercq]

Finish

At end we can finish to annotate the other methods of the UserService and create RCP and RAP launhes for manage remoting.

JAX-RS

Modify the UserService like this:

package fr.opensagres.services;

import java.util.Collection;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.jaxb.PageAdapter;
import org.springframework.data.domain.jaxb.PageableAdapter;

import fr.opensagres.domain.User;

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

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

	@POST
	@Path("/findAllPage")
	@Consumes({ MediaType.APPLICATION_JSON })
	@Produces({ MediaType.APPLICATION_JSON })
	@XmlJavaTypeAdapter(PageAdapter.class)
	Page<User> findAll(
			@XmlJavaTypeAdapter(PageableAdapter.class) Pageable pageable);

	@GET
	@Path("/findByFirstNameLikeAndLastNameLike")
	@Produces(MediaType.APPLICATION_JSON)
	Collection<User> findByFirstNameLikeAndLastNameLike(
			@QueryParam("firstName") String firstName,
			@QueryParam("lastName") String lastName);

	@POST
	@Path("/findByFirstNameLikeAndLastNameLike")
	@Consumes({ MediaType.APPLICATION_JSON })
	@Produces({ MediaType.APPLICATION_JSON })
	@XmlJavaTypeAdapter(PageAdapter.class)
	Page<User> findByFirstNameLikeAndLastNameLike(
			@QueryParam("firstName") String firstName,
			@QueryParam("lastName") String lastName,
			@XmlJavaTypeAdapter(PageableAdapter.class) Pageable pageable);

	@POST
	@Path("/saveUser")
	@Consumes({ MediaType.APPLICATION_JSON })
	@Produces({ MediaType.APPLICATION_JSON })
	User saveUser(User user);

}

Launches

RCP

To create Client Remoting – JAXRS DOSGi- RCP Client:

  • Duplicate RCP Client – Mock Dao and rename it to Client Remoting – JAXRS DOSGi – RCP 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
...

If you start this launch, you will see RCP Application which display paginated list of the User with remoting mode by using JAX-RS.

RAP

To create Client Remoting – JAXRS DOSGi – RAP Client:

  • Duplicate RAP Client – Mock Dao and rename it to Client Remoting – JAXRS DOSGi – RAP 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
...

If you start this launch, you will see RCP Application which display paginated list of the User with remoting mode by using JAX-RS.

Conclusion

In this article we have seen how to manage Spring Data structure Page and Pageable which are interface with JAX-RS by developping JAXB Adapter. Once Spring Data will be released those jAXB Adapter, you will able to use services with Page and Pageable without declaring the JAXB Adapter in UserService like this:

@POST
@Path("/findAllPage")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
Page findAll(Pageable pageable);

If Apache CXF accepts the CXF-4359 we need NOT to declare extra class with JSONProvider. In the next artice [step4] we will see how to create a WAR of the UserService server side to deploy that in a Web Server (which doesn’t support OSGi) with ServletBridge. This feature is possible because CXF DOSGi supports OSGi HttpService.

Catégories :Apache CXF, DOSGi, Spring Data JPA
  1. novembre 30, 2012 à 2:13

    When I hit the step 4 link above, I get to see ‘Désolé, aucun article ne correspond à vos critères.’. Does it mean that this article doesn’t exist yet, or did it disappear?

    • novembre 30, 2012 à 2:21

      It was just a bad URL. I have fixed the problem, refresh your step3 page and you will able to access to step4.

  1. juin 6, 2012 à 12:35
  2. juin 18, 2012 à 7:30

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 :