Browsed by
Category: CXF

Jackson based JAX-RS Providers(JSON, XML, CSV) Example

Jackson based JAX-RS Providers(JSON, XML, CSV) Example

This blog post discusses returning multiple data formats from your RESTFul endpoints using Jackson JAX-RS providers. Jackson provides a lot of providers that you can see here . The providers allow you to return a POJO(plain old java object) from your REST annotated methods and give back the appropriate media type. Jackson also gives you the ability make your own Entity Providers. Documentation for how to do that is here below I’ll show a generic way to provide CSV’s along with XML and JSON.

Pom

Sample Model Objects

REST Resource

Custom Entity Provider

Configuration

Instructions

Screen Shots

Pom

[code language=”xml”]
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>datadidit.helpful.hints</groupId>
<artifactId>parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>jetty-cxf</artifactId>
<name>jetty-cxf</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.version}</version>
</plugin>
</plugins>
</build>
</project>
[/code]

Model

Simple model object.

[code language=”java”]
package datadidit.helpful.hints.csv.test.model;

import java.util.Date;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="SimpleSample")
public class SimpleSample {
private String firstName;

private String lastName;

private Date dob;

public SimpleSample(){}

public SimpleSample(String firstName, String lastName, Date dob){
this.dob = dob;
this.firstName = firstName;
this.lastName = lastName;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public Date getDob() {
return dob;
}

public void setDob(Date dob) {
this.dob = dob;
}

}
[/code]

POJO with a Data Structure(Map) embedded in it. This implements the CSVTransformer interface so the CSV Entity Provider below can know how to flatten the POJO.

[code language=”java”]
package datadidit.helpful.hints.csv.test.model;

import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.annotation.XmlRootElement;

import datadidit.helpful.hints.csv.provider.CSVTransformer;

@XmlRootElement(name="ComplexSample")
public class ComplexSample implements CSVTransformer{
private String studentId;

private Map<String, String> grades;

public ComplexSample(){}

public ComplexSample(String studentId, Map<String, String> grades){
this.studentId = studentId;
this.grades = grades;
}

@Override
public Map<?, ?> flatten() {
Map<String, Object> myMap = new HashMap<>();
myMap.put("studentId", studentId);
myMap.putAll(grades);

return myMap;
}

public String getStudentId() {
return studentId;
}

public void setStudentId(String studentId) {
this.studentId = studentId;
}

public Map<String, String> getGrades() {
return grades;
}

public void setGrades(Map<String, String> grades) {
this.grades = grades;
}

}
[/code]

REST

REST Endpoint defining URLs for the Web Service.

[code language=”java”]
package datadidit.helpful.hints.rest;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import datadidit.helpful.hints.csv.provider.CSVTransformer;
import datadidit.helpful.hints.csv.test.model.ComplexSample;
import datadidit.helpful.hints.csv.test.model.SimpleSample;

@Path("CustomProvider")
public class CXFSampleResource {
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, "application/csv"})
@Path("test/{caseToUse}")
public List<?> doSomething(@PathParam("caseToUse") @DefaultValue("simple") String caseToUse){
List<Object> test = new ArrayList<>();
if(caseToUse.equalsIgnoreCase("simple")){
for(SimpleSample samp : this.generateSimpleSample())
test.add(samp);
}else{
for(ComplexSample samp : this.generateComplexSample())
test.add(samp);
}

System.out.println("Hello: "+test);
return test;
}

public List<SimpleSample> generateSimpleSample(){
List<SimpleSample> samples = new ArrayList<>();
samples.add(new SimpleSample("hello", "world", new Date()));
samples.add(new SimpleSample("hello", "chad", new Date()));
samples.add(new SimpleSample("hello", "marcus", new Date()));
samples.add(new SimpleSample("hello", "joy", new Date()));
samples.add(new SimpleSample("hello", "mom", new Date()));

return samples;
}

public List<ComplexSample> generateComplexSample(){
Map<String, String> grades = new HashMap<>();
grades.put("Class1", "A");
grades.put("Class2", "B");
grades.put("Class3", "C");
grades.put("Class4", "D");

List<ComplexSample> samples = new ArrayList<>();
samples.add(new ComplexSample(UUID.randomUUID().toString(), grades));
samples.add(new ComplexSample(UUID.randomUUID().toString(), grades));
samples.add(new ComplexSample(UUID.randomUUID().toString(), grades));
samples.add(new ComplexSample(UUID.randomUUID().toString(), grades));

return samples;
}
}
[/code]

Custom Entity Provider

Generic Interface that provides method for flattening a POJO so that the POJO
can be converted to a csv.

[code language=”java”]
package datadidit.helpful.hints.csv.provider;

import java.util.Map;

public interface CSVTransformer {
/**
* Utility method to Flatten POJO so that it can be converted into a CSV
* @return
*/
Map<?,?> flatten();
}
[/code]

Generic Entity Provider to generate a CSV file from a List of POJOs. Uses Jacksons CSV dataformat.

[code language=”java”]
package datadidit.helpful.hints.csv.provider;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;

import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.fasterxml.jackson.dataformat.csv.CsvSchema.Builder;

@Produces("application/csv")
public class CSVBodyWriter implements MessageBodyWriter<Object>{
Logger logger = Logger.getLogger(CSVBodyWriter.class.getName());

public long getSize(Object myCollectionOfObjects, Class type, Type genericType, Annotation[] annotations,
MediaType arg4) {
return 0;
}

public boolean isWriteable(Class type, Type genericType, Annotation[] annotations,
MediaType arg3) {
return true;
}

public void writeTo(Object myCollectionOfObjects, Class type, Type genericType, Annotation[] annotations,
MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityHeaders)
throws IOException, WebApplicationException {
//Whatever makes it in here should be a list
List<?> myList = new ArrayList<>();
if(myCollectionOfObjects instanceof List && ((myList=(List<?>)myCollectionOfObjects).size()>0)){
CsvMapper csvMapper = new CsvMapper();
CsvSchema schema = null;

/*
* If it’s not a flat POJO must implement
* CSVTransformer
*/
if(implementsCSVTransformer(myList.get(0).getClass())){
Class[] params = {};
try {
Method meth = CSVTransformer.class.getDeclaredMethod("flatten", params);

/*
* Create a new list using the toMap() function
*/
List<Map<String, ?>> listOfMaps = new ArrayList<>();
Set<String> headers = null;
for(Object obj : myList){
Map<String, ?> keyVals = (Map<String, ?>) meth.invoke(obj, params);

if(schema==null){
schema = this.buildSchemaFromKeySet(keyVals.keySet());
headers = keyVals.keySet();
}

//Validate that latest headers are the same as the original ones
if(headers.equals(keyVals.keySet()))
listOfMaps.add(keyVals);
else
logger.warning("Headers should be the same for each objects in the list, excluding this object "+keyVals);
}

csvMapper.writer(schema).writeValue(entityHeaders, listOfMaps);
} catch (Exception e) {
throw new IOException("Unable to retrieve flatten() "+e.getMessage());
}
}else{
schema = csvMapper.schemaFor(myList.get(0).getClass()).withHeader();
csvMapper.writer(schema).writeValue(entityHeaders, myList);
}
}else if(myList.isEmpty()){
logger.warning("Nothing in list to convert to CSV….");
entityHeaders.write(myList.toString().getBytes(Charset.forName("UTF-8")));
}else{
throw new IOException("Not in proper format must pass a java.util.List to use this MessageBodyWriter…");
}
}

public CsvSchema buildSchemaFromKeySet(Set<String> keySet){
Builder build = CsvSchema.builder();
for(String field : keySet){
build.addColumn(field);
}
CsvSchema schema = build.build().withHeader();
return schema;
}

public Boolean implementsCSVTransformer(Class arg1){
Class[] interfaces = arg1.getInterfaces();
for(Class aClass : interfaces){
if(aClass.getName().equals(CSVTransformer.class.getName()))
return true;
}

return false;
}
}
[/code]

Configuration

This xml file configures the CXF servlet, extension mappings and providers for your Web Service to use. Some good docs on this configuration file can be found here.

[code language=”xml”]
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>CSV Provider Test</display-name>
<servlet>
<servlet-name>MyApplication</servlet-name>
<servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
<!– Name of the resource –>
<init-param>
<param-name>jaxrs.serviceClasses</param-name>
<param-value>
datadidit.helpful.hints.rest.CXFSampleResource,
</param-value>
</init-param>
<!– Name of the providers –>
<init-param>
<param-name>jaxrs.providers</param-name>
<param-value>
com.fasterxml.jackson.jaxrs.xml.JacksonJaxbXMLProvider,
com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider,
datadidit.helpful.hints.csv.provider.CSVBodyWriter
</param-value>
</init-param>
<!– Name of the extensions –>
<init-param>
<param-name>jaxrs.extensions</param-name>
<param-value>
csv=application/csv
json=application/json
xml=application/xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>MyApplication</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
[/code]

Instructions

  1. From the root of the project run ‘mvn jetty:run’
  2. For the simple example run:
    • xml: http://localhost:8080/CustomProvider/test/simple
    • json: http://localhost:8080/CustomProvider/test/simple.json
    • csv: http://localhost:8080/CustomProvider/test/simple.csv
  3. For the complex example run:
    • xml: http://localhost:8080/CustomProvider/test/complex
    • json: http://localhost:8080/CustomProvider/test/complex.json
    • csv: http://localhost:8080/CustomProvider/test/complex.csv

Screen Shots

When you hit the ‘.csv’ extensions depending on your browser you may notice that the csv is just downloaded as a file.

simple_xml

simple_json

complex_xml

complex_json

CXF Jetty Example

CXF Jetty Example

This is a simple example of how to combine CXF and Jetty to get a simple web services up and running.

Pom for this example:

[code language=”xml”]
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>datadidit.helpful.hints</groupId>
<artifactId>parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>jetty-cxf</artifactId>
<name>jetty-cxf</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.version}</version>
</plugin>
</plugins>
</build>
</project>
[/code]

 

Here is a simple REST endpoint:

[code language=”Java”]
package datadidit.helpful.hints.rest.cxf;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("hello")
public class HelloCXF {
@GET
@Produces({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON})
public Response sayHello(){
return Response.ok("Hello").build();
}

@GET
@Produces({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON})
@Path("{name}")
public Response sayHelloToSomeone(@PathParam("name") @DefaultValue("world") String name){
return Response.ok("Hello "+name).build();
}
}
[/code]

Configuration file(web.xml) necessary for configuring Jetty and CXF:

[code language=”xml”]
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>CXF Jetty Example</display-name>
<servlet>
<servlet-name>MyApplication</servlet-name>
<servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
<!– Name of the resource –>
<init-param>
<param-name>jaxrs.serviceClasses</param-name>
<param-value>
datadidit.helpful.hints.rest.cxf.HelloCXF
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>MyApplication</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
[/code]

With this set up I’m able to run the following steps to see my webservice from the browser:

  1. From root of project run ‘mvn jetty:run’
  2. Navigate to http://localhost:8080/hello and the word ‘Hello’ will  be shown to you from the browser
  3. Next navigate to http://localhost:8080/hello/world and the phrase ‘Hello world’ will be shown to you from the browser.