I am new at restful services, and had a relatively good start, until I decided to play with some complex objects. The problem I am hitting is about unmarshalling an object coming to the server (creating object from XML on the server side).
Below is my sample (representative) implementation of the service.
Here is my "complex object" data type.
package data;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class ComplexType {
private long id;
private String name;
private Boolean isRegistered;
public ComplexType() {
super();
}
public ComplexType(long id, String name, Boolean isRegistered) {
super();
this.id = id;
this.name = name;
this.isRegistered = isRegistered;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getIsRegistered() {
return isRegistered;
}
public void setIsRegistered(Boolean isRegistered) {
this.isRegistered = isRegistered;
}
}
This is my service API
package api;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import data.ComplexType;
public interface Service {
@GET
@Path("/nummembers")
int getNumElements();
@GET
@Path("/member/{id}")
ComplexType getMember(@PathParam("id") long id);
@POST
@Path("/member")
boolean addMember(@FormParam("member") ComplexType member);
}
And this is the implementation of the service:
package impl;
import java.util.HashMap;
import java.util.Map;
import data.ComplexType;
import api.Service;
public class ServiceImpl implements Service {
Map<Long, ComplexType> data;
public ServiceImpl() {
System.out.println("TestApp Starting");
data = new HashMap<Long, ComplexType>();
}
@Override
public int getNumElements() {
return data.size();
}
@Override
public ComplexType getMember(long id) {
if (data.containsKey(id)) {
return data.get(id);
}
ComplexType ct =
new ComplexType(id, "NAME" + new Long(id).toString(), (id % 2 == 1));
data.put(id, ct);
return ct;
}
@Override
public boolean addMember(ComplexType member) {
int preSize = data.size();
data.put(member.getId(), member);
return preSize < data.size(); // True if added
}
}
So, when I call getNumElements()
there is no problem. When I call getMember(long id)
I get a serialized "ComplexType" just fine. When I serialize a complex type and pass it as FormParam to addMember(ComplexType member)
, I always get Parameter Class data.ComplexType has no constructor with single String parameter, static valueOf(String) or fromString(String) methods
I tried to provide my custom provider, by writing the following class:
package impl;
import javax.ws.rs.ext.ContextResolver;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import data.ComplexType;
public class JaxbXmlContextResolver implements ContextResolver<Object> {
private static final Class<?>[] classes = new Class[] {ComplexType.class};
private static final JAXBContext context = initContext();
public static JAXBContext initContext() {
JAXBContext context = null;
try {
context = JAXBContext.newInstance(classes);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
return context;
}
@Override
public Object getContext(Class<?> arg0) {
return context;
}
}
And for the rest of the configurations, here is my web.xml:
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>TestApp</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:cxf.xml</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<display-name>CXF Servlet</display-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
</web-app>
And the cxf.xml it refers to:
<?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:util="http://www.springframework.org/schema/util"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:cxf="http://cxf.apache.org/core"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<bean id="myservice" class="impl.ServiceImpl" />
<bean id="jaxbXmlProvider" class="impl.JaxbXmlContextResolver" />
<jaxrs:server id="connectionService" address="/" >
<jaxrs:serviceBeans>
<ref bean="myservice" />
</jaxrs:serviceBeans>
<jaxrs:extensionMappings>
<entry key="xml" value="application/xml" />
</jaxrs:extensionMappings>
<jaxrs:providers>
<ref bean="jaxbXmlProvider" />
</jaxrs:providers>
</jaxrs:server>
</beans>
And for completeness, here is the pom.xml I am using to build the app.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<modelVersion>4.0.0</modelVersion>
<groupId>TESTAPP</groupId>
<artifactId>testApp</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>Test Application</name>
<url>http://www.mycompany.com</url>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
<version>2.4.1</version>
<exclusions>
<exclusion>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>net.sf.kxml</groupId>
<artifactId>kxml2</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Any help (solutions, pointers, or any kind of direction) will be greatly appreciated.
Oh, I am using cxf 2.4.1 and Spring 3.0.5.RELEASE. This is exact copy of my deployed application.
Thanks.
As it was stated at http://cxf.547215.n5.nabble.com/MessageBodyReader-not-picked-up-td564496.html, it turned out I needed a ParameterHandler. Since I had a large set of objects in my application, I did not want to create separate ParameterHandler<> for each, so I made a small change:
Using the technique described at JAXB inheritance, unmarshal to subclass of marshaled class, I created a base type "BaseType" which all the API data objects (TypeA, TypeB, ...) were inherited.
public class XmlParamHandler implements ParameterHandler<BaseType> {
private static final Logger log = LoggerFactory.getLogger(XmlParamHandler.class);
private static final JAXBContext jaxbContext = initContext();
private static JAXBContext initContext() throws JAXBException {
Class<?>[] classes = new Class[] {
com.mycompany.BaseType.class,
com.mycompany.TypeA.class,
com.mycompany.TypeB.class,
com.mycompany.TypeC.class,
com.mycompany.TypeD.class,
com.mycompany.TypeE.class,
com.mycompany.TypeF.class,
};
JAXBContext context = JAXBContext.newInstance(classes);
return context;
}
public static <T> T valueOf(String str) throws JAXBException {
if (str == null) {
return null;
}
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader sr = new StringReader(str);
@SuppressWarnings("unchecked")
T request = (T) unmarshaller.unmarshal(sr);
return request;
}
@Override
public BaseType fromString(String s) {
BaseType ct = null;
try {
return valueOf(s);
} catch (JAXBException e) {
return null;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With