I have a Java server application that uses CXF to provide SOAP and REST web services. Currently it uses the reference implementation of JAX-B for XML marshalling/unmarshalling, but I have configured it to replace Jettison with Jackson for JSON marshalling/unmarshalling. I use Spring for DI and application context configuration.
The REST web service configuration snippets looks as follows:
web.xml
<servlet>
<display-name>Myapp REST Services</display-name>
<servlet-name>MyappWebServices</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyappWebServices</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
applicationContext.xml
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<bean id="jsonProvider" class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
<jaxrs:server id="myappCoreSvcRest" address="/rest">
<jaxrs:serviceBeans>
<ref bean="fooService" />
<ref bean="barService" />
</jaxrs:serviceBeans>
<jaxrs:providers>
<ref bean="jsonProvider" />
</jaxrs:providers>
</jaxrs:server>
This configuration works and will return either XML or JSON depending on the HTTP Accept header. What I like about this configuration is that it is based in Spring and it is super easy to create and use an alternate JSON encoder. Details on configuring CXF can be found here.
My problem is that now I have a new (additional) REST web service to provide and I would like to use a different JAX-B XML binding for this new web service. I understand that MOXy can do this, but I am unable to figure out how to configure a CXF end point so that it will use MOXy for marshalling/unmarshalling (and furthermore how to tell Moxy about my custom XML mapping file). I also would like this new web service to return either XML or JSON depending on the Accept header. I also have read that MOXy 2.4+ can handle that too!
Ideally I could use MOXy for this new endpoint without affecting the other existing servlets.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Off hand I do not know the exact config for CXF, but below I have provided some links to using MOXy with Spring. Please feel free to contact me, and I can help you implement this:
- http://blog.bdoughan.com/p/contact_01.html
My problem is that now I have a new (additional) REST web service to
provide and I would like to use a different JAX-B XML binding for this
new web service. I understand that MOXy can do this, but I am unable
to figure out how to configure a CXF end point so that it will use
MOXy for marshalling/unmarshalling (and furthermore how to tell Moxy
about my custom XML mapping file).
When using MOXy with a JAX-RS implementation you can use a ContextResolver
to bootstrap from MOXy's external mapping file:
package blog.bindingfile.jaxrs;
import java.io.*;
import java.util.*;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import blog.bindingfile.Customer;
@Provider
@Produces({"application/xml", "application/json"})
public class CustomerContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext jc;
public CustomerContextResolver() {
ClassLoader cl = Customer.class.getClassLoader();
InputStream bindings =
cl.getResourceAsStream("blog/bindingfile/binding.xml");
try {
Map<String, Object> props = new HashMap<String, Object>(1);
props.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, bindings);
jc = JAXBContext.newInstance(new Class[] {Customer.class} , props);
} catch(JAXBException e) {
throw new RuntimeException(e);
} finally {
try {
bindings.close();
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}
public JAXBContext getContext(Class<?> clazz) {
if(Customer.class == clazz) {
return jc;
}
return null;
}
}
For a Complext Example
- http://blog.bdoughan.com/2011/04/moxys-xml-metadata-in-jax-rs-service.html
For More Information on Using MOXy with Spring
- http://wiki.eclipse.org/EclipseLink/Examples/MOXy/Spring
I also would like this new web service to return either XML or JSON
depending on the Accept header. I also have read that MOXy 2.4+ can
handle that too!
Yes, JSON binding is being added to EclipseLink 2.4. To leverage this in your application it should be a simple matter of creating a MessageBodyReader
and a MessageBodyWriter
:
package org.example;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import javax.xml.transform.stream.StreamSource;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.ext.*;
import javax.xml.bind.*;
@Provider
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class MOXyJSONProvider implements
MessageBodyReader<Object>, MessageBodyWriter<Object>{
@Context
protected Providers providers;
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return true;
}
public Object readFrom(Class<Object> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
try {
Class<?> domainClass = getDomainClass(genericType);
Unmarshaller u = getJAXBContext(domainClass, mediaType).createUnmarshaller();
u.setProperty("eclipselink.media-type", mediaType.toString());
u.setProperty("eclipselink.json.include-root", false);
return u.unmarshal(new StreamSource(entityStream), domainClass).getValue();
} catch(JAXBException jaxbException) {
throw new WebApplicationException(jaxbException);
}
}
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return true;
}
public void writeTo(Object object, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException,
WebApplicationException {
try {
Class<?> domainClass = getDomainClass(genericType);
Marshaller m = getJAXBContext(domainClass, mediaType).createMarshaller();
m.setProperty("eclipselink.media-type", mediaType.toString());
m.setProperty("eclipselink.json.include-root", false);
m.marshal(object, entityStream);
} catch(JAXBException jaxbException) {
throw new WebApplicationException(jaxbException);
}
}
public long getSize(Object t, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return -1;
}
private JAXBContext getJAXBContext(Class<?> type, MediaType mediaType)
throws JAXBException {
ContextResolver<JAXBContext> resolver
= providers.getContextResolver(JAXBContext.class, mediaType);
JAXBContext jaxbContext;
if(null == resolver || null == (jaxbContext = resolver.getContext(type))) {
return JAXBContext.newInstance(type);
} else {
return jaxbContext;
}
}
private Class<?> getDomainClass(Type genericType) {
if(genericType instanceof Class) {
return (Class<?>) genericType;
} else if(genericType instanceof ParameterizedType) {
return (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
} else {
return null;
}
}
}
You may also be able to create an extension of JSONProvider
:
- http://cxf.apache.org/apidocs/org/apache/cxf/jaxrs/provider/JSONProvider.html
For More Information
- MOXy as Your JAX-RS JSON Provider - Client Side
- MOXy as Your JAX-RS JSON Provider - Server Side
- Specifying EclipseLink MOXy as Your JAXB Provider