CXF Canceled Universal Integrated Services Object Does Not Work

I am new to quiet services and started quite well until I decided to play with some complex objects. The problem I am facing is exposing the object going to the server (creating an 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 the 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 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 that I use to build the application.

 <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 (decisions, signs or any direction) is appreciated.

Oh, I'm using cxf 2.4.1 and Spring 3.0.5.RELEASE. This is an exact copy of my deployed application.

Thanks.

+6
source share
3 answers

As stated in http://cxf.547215.n5.nabble.com/MessageBodyReader-not-picked-up-td564496.html , it turned out that I needed a ParameterHandler. Since I had a large set of objects in my application, I did not want to create a separate ParameterHandler <> for each, so I made a small change:

Using the method described in JAXB inheritance that is not associated with a subclass of the marshaled class , I created the base type "BaseType", which all 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; } } } 
+1
source

Implement a default constructor with no arguments and one with an argument for each of the three properties. That should make JAXB happy.

0
source

It's a little weird to pass a complex "XML marchable" object like @FormParam. @FormParam are mainly for simple or primitive types like strings, integers, etc.

One of the nice things about JAX-RS is that it will automatically create marshall / unmarshall objects for you, you say what type of media files to use. This way you can define your endpoints as (adding @Consumes and no annotation to the member):

 @POST @Path("/member") @Consumes({ MediaType.APPLICATION_XML }) boolean addMember(ComplexType member); 

And POST the XML representation of the element as the request body, not the form parameter and set the Content-Type header of your request to application/xml . Then CXF automatically reads and analyzes the request body to the ComplexType instance and passes it to your method.

You can use curl for this. Create an XML file with a serialized object (member.xml) and run:

 curl -k --request POST --header "Content-Type: application/xml" --data @member.xml https://localhost:8080/path/to/service 
0
source

Source: https://habr.com/ru/post/895914/


All Articles