Migrating to CXF with Jaxb from xfire with agesi - java

I'm migrating my xfire soap project which uses aegis for databinding to cxf with jaxb. I got the new cxf project working for old xfire requests with aegis binding. But when i move the databinding to jaxb unmarshalling errror occurs.
This is my cxf web service definition.
<!--<bean id="aegisBean" class="org.apache.cxf.aegis.databinding.AegisDatabinding" scope="prototype"/> -->
<bean id="jaxbBean" class="org.apache.cxf.jaxb.JAXBDataBinding" scope="prototype"/>
<bean id="jaxws-and-aegis-service-factory" class="org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean"
scope="prototype">
<property name="dataBinding" ref="jaxbBean"/>
<property name="serviceConfigurations">
<list>
<bean class="org.apache.cxf.jaxws.support.JaxWsServiceConfiguration"/>
<bean class="org.apache.cxf.aegis.databinding.XFireCompatibilityServiceConfiguration"/>
<bean class="org.apache.cxf.service.factory.DefaultServiceConfiguration"/>
</list>
</property>
</bean>
<jaxws:endpoint id="trace" address="/trace" implementor="#traceImplBean">
<jaxws:serviceFactory>
<ref bean="jaxws-and-aegis-service-factory"/>
</jaxws:serviceFactory>
<jaxws:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
</jaxws:outInterceptors>
</jaxws:endpoint>
I used #XMLRootElement Annotaion on my DTOs as following.
#XmlRootElement(name = "context" )
public class Context implements Serializable {
private KeyValues keyValues;
.....
}
#XmlRootElement(name = "keyValues" )
public class KeyValues implements Serializable {
private String tag;
private String value;
....
}
One method which i tested generated following soap request for cxf
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:pay="http://example.project.com">
<soapenv:Header/>
<soapenv:Body>
<pay:trace>
<pay:context>
<keyValues>
<tag>tag</tag>
<value>value</value>
</keyValues>
</pay:context>
</pay:trace>
</soapenv:Body>
</soapenv:Envelope>
HOWEVER old xfire generate following request, I have mark the difference.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:pay="http://example.project.com" xmlns:api="http://example.com">
<soapenv:Header/>
<soapenv:Body>
<pay:trace>
<pay:context>
<api:keyValues>
***<api:KeyValues>***
<api:tag>tag</api:tag>
<api:value>value</api:value>
***</api:KeyValues>***
</api:keyValues>
</pay:context>
</pay:trace>
</soapenv:Body>
</soapenv:Envelope>
I got following exception when i tried to send xfire request to cxf service.
javax.xml.bind.UnmarshalException: unexpected element (uri:"http://example.project.com", local:"keyValues"). Expected elements are <{}keyValues>
So I think i need to add additional tags to cxf request inorder to compatible with xfire. Does anyone knows how to resolve this ?
Thanks in advance.

JAXB, by default, uses unqualified elements whereas Aegis/XFire by default used qualified elements. Couple ways around that:
1) For every element, specify the namespace.
#XmlElement(name = "tag", namespace = "http:...")
likely easier:
2) Add a package-info.java with:
#javax.xml.bind.annotation.XmlSchema(namespace = "http://......",
elementFormDefault = XmlNsForm.QUALIFIED)

Related

Remove namespace from SOAP request and response

I have a SOAP request similar to this:
<SOAP-ENV:Envelope>
<SOAP-ENV:Header>
....
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<MyRequest>
<Param>3</Param>
</MyRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
and the response should be like
<SOAP-ENV:Envelope>
<SOAP-ENV:Header>
....
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<MyResponse>
<Value>3234</Value>
<Value>542</Value>
</MyResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
My problem is that my JAX-WS SOAP service expects a namespace e.g. xmlns="http://myapp.example.com" in MyRequest. It also adds this namespace to the MyResponse element too.
I have managed to write an Interceptor that will add my apps target namespace to the request message if it is blank. This works fine.
My issue right now is that I am not sure how to remove this namespace from the response message.
I know the best solution would be to have clients use namespaces, however this is not possible on the project I am working on.
CXF comes with Transformation Feature, Please have look here
A sample bean extract from page.
<bean id="transformFeature" class="org.apache.cxf.feature.StaxTransformFeature">
<property name="outTransformElements">
<map>
<!-- change "book" to "thebook" -->
<entry key="book" value="thebook"/>
<!-- drop the namespace from "book" -->
<entry key="{http://books}book" value="book"/>
<!-- qualify "book" with "http://books" -->
<entry key="book" value="{http://books}thebook"/>
<!-- change namespace to "http://books" for all the elements with the "http://book" namespace -->
<entry key="{http://book}*" value="{http://books}*"/>
</map>
</property>
</bean>
That's pretty broad since we don't know what you are using, but if you can use Metro (JAX-WS) then you can take advantage of what they call SOAPMessage Handler (have a look here). It works pretty much like a Servlet filter component.
Have something that implements SOAPHandler<SOAPMessageContext> and remove the namespace declaration(s) there.

Objects as parameters in Apache CXF REST service method

What Exactly i need to do is to have a web service method in a REST API written in Apache CXF to accept a request like the following (preferably specifying a custom object as a parameter type)
{
"action":"read",
"resource:"new resource"
}
For now my method can do the same but it would expect a JSON string as the request body. But I need the service to describe the request parameters to the client. Meaning that in the wadl definition it should show the exact parameters that should be sent from the client. The ideal definition would be something similar to
<resource path="by-attrib">
<method name="GET">
<request>
<param name="Accept" style="header" type="xs:string"/>
<param name="Auth_Type" style="header" type="xs:string"/>
<param name="Authorization" style="header" type="xs:string"/>
<representation mediaType="application/json">
<param name="action" style="plain" type="xs:string"/>
<param name="resource" style="plain" type="xs:string"/>
</representation>
</request>
<response>
<representation mediaType="application/json">
<param name="result" style="plain" type="xs:string"/>
</representation>
</response>
</method>
</resource>
Is this possible with CXF?
Please note that using #FormParam is not what I need, if I use form params, I get issues in sending a request using XML to the same method
Thank You
Example with CXF and jackson
The service interface (use POST, not GET)
#POST
#Path("/yourservice")
#Consumes({ MediaType.APPLICATION_JSON})
#Produces({
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML})
public Result postYourService(YourData data) throws WebApplicationException;
The service impl (nothing special)
public Result postYourService(YourData data){
Result r = new Result();
r.setResult("result");
return r;
}
The data objects (Using jaxb to simplify encoding/decoding of json or xml)
#XmlRootElement(name = "YourData")
public class YourData {
private String action;
private String resource;
//getter & setters
}
#XmlRootElement(name = "Result")
public class Result {
private String result;
//getter & setters
}
The spring configuration of CXF server and jackson. Jackson provider class depends on which version of CXF are you using. Therefore if the JacksonJaxbJsonProvider is not on your cxf package, look at the documentation
<jaxrs:server id="yourServiceREST" address="/services">
<jaxrs:serviceBeans>
<ref bean="yourService" />
</jaxrs:serviceBeans>
<jaxrs:providers>
<!--<ref bean="authenticationFilter" />-->
<!--<ref bean="exceptionMapper" />-->
<!-- <ref bean="corsFilter" /> -->
<ref bean="jackson" />
</jaxrs:providers>
</jaxrs:server>
<bean id="yourService" class="YourServiceImpl">
</bean>
<bean id="jackson" class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider" />
Try to deploy and invoke to
POST /services/yourservice
{ "action":"read", "resource:"new resource"}
I do not know if WADL is going to be well generated because sometimes CXF fails. Be lucky!
actually I found the answer
It is to use bean injection
described in their documentation itself, sorry for not RTFM
http://cxf.apache.org/docs/jax-rs-basics.html
under Parameter Beans
Basic idea is to use a java Bean (zero argument constructor with setters and getters) and add it to the web service parameters
You however need to specify one of #QueryParam #FormParam #PathParam for it to work

CXF/JAXB fails at marshalling primitive long return value

I have a simple CXF method that returns a long
#GET
#Path("/count/{foo}/{bar}")
long count(#PathParam("foo") String foo, #PathParam("bar") String bar)
I have a CXF server with JAXB setup for it
<jaxrs:server id="myServer" address="/">
<jaxrs:providers>
<bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider">
<property name="singleJaxbContext" value="true" />
<property name="skipJaxbChecks" value="true" />
<property name="validateOutput" value="false" />
</bean>
</jaxrs:providers>
<jaxrs:serviceBeans>
<ref bean="myServiceImpl" />
</jaxrs:serviceBeans>
</jaxrs:server>
But when I call this method, the server fails at marshalling the result :
Caused by: com.sun.istack.SAXException2: unable to marshal type
"java.lang.Long" as an element because it is missing an
#XmlRootElement annotation
How come CXF is not able to marshal a long ?
Thanks for your help
EDIT
I am actually writing a mockup implementation of an existing service (for test purposes).
I have no control on the API of the existing service.
And its current implementation returns something like
Response-Code: 200
Encoding: ISO-8859-1
Content-Type: application/xml
Headers: {connection=[close], Content-Length=[3], content-type=[application/xml], Date=[Wed, 23 Jul 2014 08:00:31 GMT]}
Payload: 121
Which is no real XML i agree.
But the current client does not complain either, and does no contain any magic for it.
Why do you need to create xml structure for a single return value, You retrun it as text and add Annotation Produces with return type content as plain/text.
#GET
#Produces("text/plain")
#Path("/count/{foo}/{bar}")
long count(#PathParam("foo") String foo, #PathParam("bar") String bar)
If you are using cxf client you can read it as long as follows
Response response = client.get();
response.readEntity(Long.class);
EDIT
Remove the jaxrs:providers, cxf internally handles jaxb conversions by default
<jaxrs:server id="myServer" address="/">
<jaxrs:serviceBeans>
<ref bean="myServiceImpl" />
</jaxrs:serviceBeans>
</jaxrs:server>
Here is the below code I tested, however when I added the provider it gives error.
#Path("/add")
#Produces("application/xml")
#GET
public Long add(#QueryParam("v1")int v1, #QueryParam("v2")int v2){
long result =v1+v2;
return result;
}
#Path("/subtract")
#Produces("application/xml")
#GET
public Output subtract(#QueryParam("v1")int v1, #QueryParam("v2")int v2){
Output out = new Output();
out.setResult(v1-v2);
return out;
}

Apache CXF interceptor configuration

I created a SOAP interceptor as described in CXF docs:
public class SoapMessageInterceptor extends AbstractSoapInterceptor {
public SoapMessageInterceptor() {
super(Phase.USER_PROTOCOL);
}
public void handleMessage(SoapMessage soapMessage) throws Fault {
// ...
}
}
and registered it with the bus in Spring's application context:
<cxf:bus>
<cxf:inInterceptors>
<ref bean="soapMessageInterceptor"/>
</cxf:inInterceptors>
</cxf:bus>
<jaxws:endpoint id="customerWebServiceSoap"
implementor="#customerWebServiceSoapEndpoint"
address="/customerService"/>
All was working fine until I added a REST service:
<jaxrs:server id="customerWebServiceRest" address="/rest">
<jaxrs:serviceBeans>
<ref bean="customerWebServiceRestEndpoint" />
</jaxrs:serviceBeans>
</jaxrs:server>
The problems is that the SOAP interceptor is now being triggered on REST requests also, which results in a class cast exception when the REST service is invoked.
<ns1:XMLFault xmlns:ns1="http://cxf.apache.org/bindings/xformat">
<ns1:faultstring xmlns:ns1="http://cxf.apache.org/bindings/xformat">
java.lang.ClassCastException: org.apache.cxf.message.XMLMessage
cannot be cast to org.apache.cxf.binding.soap.SoapMessage
</ns1:faultstring>
</ns1:XMLFault>
Is there any way to restrict the interceptor to SOAP messages only through configuration?
Update
Looks like I missed the page in the docs that describes this.
Scroll down to Difference between JAXRS filters and CXF interceptors
You can attach interceptors to an individual endpoint rather than to the bus:
<jaxws:endpoint id="customerWebServiceSoap"
implementor="#customerWebServiceSoapEndpoint"
address="/customerService">
<jaxws:inInterceptors>
<ref bean="soapMessageInterceptor"/>
</jaxws:inInterceptors>
</jaxws:endpoint>
You can try to configure your interceptor like this:
<cxf:bus name="someBus">
<cxf:inInterceptors>
<ref bean="soapMessageInterceptor"/>
</cxf:inInterceptors>
</cxf:bus>
By defining the name of the bus, which according to documentation, identifies a bus as a unique Spring bean. Then in your JAX-WS endpoint configuration you need to specify the bus referencing to that name:
<jaxws:endpoint id="customerWebServiceSoap"
implementor="#customerWebServiceSoapEndpoint"
address="/customerService"
bus="someBus"/>
And this bus should work only on this JAX-WS endpoint.

Spring REST 3 to Support XML and JSON

If we develop REST using Spring MVC, it will support XML and JSON data. I have wrote ContentNegotiationViewResorver in my spring config bean app-servlet.xml
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"
p:order="1">
<property name="mediaTypes">
<map>
<entry key="xml" value="application/xml" />
<entry key="json" value="application/json" />
</map>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<property name="marshaller">
<bean class="org.springframework.oxm.xstream.XStreamMarshaller"
p:autodetectAnnotations="true" />
</property>
</bean>
<bean
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
</list>
</property>
</bean>
And my spring REST Controller is:
#Controller
#RequestMapping("/rest/customers")
class CustomerRestController {
protected Log log = LogFactory.getLog(CustomerRestController.class);
#RequestMapping(method = POST)
#ResponseStatus(CREATED)
public void createCustomer(#RequestBody Customer customer,
HttpServletResponse response) {
log.info(">>>" + customer.getName());
response.setHeader("Location", String.format("/rest/customers/%s",
customer.getNumber()));
}
#RequestMapping(value = "/{id}", method = GET)
#ResponseBody
public Customer showCustomer(#PathVariable String id) {
Customer c = new Customer("0001", "teddy", "bean");
return c;
}
#RequestMapping(value = "/{id}", method = PUT)
#ResponseStatus(OK)
public void updateCustomer(#RequestBody Customer customer) {
log.info("customer: " + customer.getName());
}
I set #XStreamAlias("customer") annotation in my customer domain class.
But when I try access http://localhost:8080/rest/customers/teddy.xml it always response JSON data.
I set #XmlRootElement(name="customer") annotation in my customer domain class.
But when I try access http://localhost:8080/rest/customers/teddy.json it always response XML data.
Is there some thing wrong ?
I think "xml" content type should be mapped to "text/xml" not to "application/xml". Also, to force content type resolvers based on extension, you can try to set the "favorPathExtension" property of "ContentNegotiatingViewResolver" to true(though it should have been true by default!)
EDIT: I have now added a working sample at this GIT location - git://github.com/bijukunjummen/mvc-samples.git, if you bring up the endpoint, using mvn tomcat:run, the json is served at http://localhost:8080/mvc-samples/rest/customers/teddy.json and xml at http://localhost:8080/mvc-samples/rest/customers/teddy.xml. This uses JAXB2 not XStream, as I am familiar with JAXB. One thing I noticed was that when my JAXB annotations were not correct in Customer class, Spring was serving out JSON and not XML the way you saw it(You can replicate it by removing the XMLRootElement annotation from Customer class), once I fixed up my annotations, I got back XML as expected. So it could be that there is something wrong with your XStream configuration.
EDIT 2: You are right!! I did not notice, once I got back xml, I assumed that json is working now. I see the problem, in AnnotationMethodHandlerAdapter, the handling for #ResponseBody is a little strange, it completely ignores the ViewResolvers, and uses the registered MessageConverters instead completely bypassing the ContentNegotiatingViewResolver, one workaround for now is to use #ModelAttribute annotation for response, instead of #ResponseBody, this way the view Resolvers are getting called. Try now using the project at git#github.com:bijukunjummen/mvc-samples.git and see if it works for you. This could be a Spring bug, you may try and bring it up in the Spring forum and see what they recommend.
What Accept headers are sent to your server?
Make sure the content type you would like to request is in this list.
Spring 3.1 solves the problem you mention using the new produces element on the #RequestMapping annotation. This allows you to control the HttpMessageConverter that Spring applies to your object.
I wrote a blog post about it:
http://springinpractice.com/2012/02/22/supporting-xml-and-json-web-service-endpoints-in-spring-3-1-using-responsebody/
I had the same problem. I assume you're using Spring 3 and you've used <mvc:annotation-driven/>. I'm not entirely sure, but I think this creates some conflict based on the message converters that the mvc namespace configures.
Using the oxm namespace worked for me:
#XmlRootElement(name="person")
class Person {
private String firstName;
private String lastName;
}
#Controller
#RequestMapping("person")
class PersonController {
#RequestMapping("list")
public #ResponseBody Person getPerson() {
Person p = new Person();
p.setFirstName("hello");
p.setLastName("world");
return p;
}
}
Content Configuration (mvc and internal view resolver are in another context):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<oxm:jaxb2-marshaller id="jaxbMarshaller">
<oxm:class-to-be-bound name="package.Person" />
</oxm:jaxb2-marshaller>
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="defaultContentType" value="text/html" />
<property name="ignoreAcceptHeader" value="true" />
<property name="favorPathExtension" value="true" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<property name="marshaller" ref="jaxbMarshaller" />
</bean>
</list>
</property>
</bean>
</beans>
This example uses JAXB, so you'd need jaxb-api and jaxb-impl on the classpath.
Also, just a tip, you don't need the app-servlet.xml. In your web.xml, set the config to null and let the Context Listener load them for you:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/mvc-context.xml, /WEB-INF/spring/content-negotiation-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value/>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Well I got a solution but I don't know if it's the right way in your method show customer:
#RequestMapping(value = "/{id}", method = GET)
#ResponseBody
public Customer showCustomer(#PathVariable String id) {
Customer c = new Customer("0001", "teddy", "bean");
return c;
}
In this part, we are using MVC of spring and in the controller we should be return a view, so I removed the annotation #ResponseBody and I return a String with the name of the view because in our XML we added a ContentNegotiatingViewResolver and when we have ResponseBody the contentnegociationviewresolver is ignored because is waiting for a view but we returned the object so the method should be like that:
#RequestMapping(value = "/{id}", method = GET)
public String showCustomer(#PathVariable String id, ModelMap model) {
Customer c = new Customer("0001", "teddy", "bean");
model.addAttribute("customer",c);
return "myView";
}
well that works for me, if you have problems you can add to your app-servlet.xml
this bean, but I don't think that you have to add this.
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/views/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
I got the answers from mkyong.com
Accessing the controller using a browswer will send a typical browser Accept header. It will not match any view resolver and default to the first one (application/xml) or it matches because application/xml is in the Accept list.
I can recommend using RestClient http://code.google.com/p/rest-client/ to have complete control over what Accept header (if one at all) you want to send.
I don't recommend using text/xml as the default character set is US-ASCII and not UTF-8. This might create funky encoding problems down the road. You can always specify the encoding but appliation/xml has a UTF-8 default encoding.

Categories

Resources