package com.dineshonjava.ws.rest;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import com.dineshonjava.ws.xml.Book;
import com.dineshonjava.ws.xml.Student;
/**
* @author Dinesh Rajput
*
*/
@XmlRootElement
public class GetBooksResponse {
private Student student;
private List<Book> books = new ArrayList<Book>();
@XmlElement
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
@XmlElement
@XmlElementWrapper(name = "books")
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
}
JAXB's default naming convention is to use your class or bean getter name as-is, but starting with a lower case letter. In this example, once marshaled to XML the element names will be getBooksResponse, student, and books. I will show an example of how to override the default naming later in this article.package com.dineshonjava.ws.xml;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
/**
* @author Dinesh Rajput
*
*/
@XmlType(name="students")
public class Student {
private long rollNumber;
private String name;
private String course;
@XmlElement(name="rollNumber")
public long getRollNumber() {
return rollNumber;
}
public void setRollNumber(long rollNumber) {
this.rollNumber = rollNumber;
}
@XmlElement(name="name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlElement(name="course")
public String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
}
In the example above, we use @XmlType at the class level instead of @XmlRootElement because it is not the root element. Also notice the name parameter in each of the annotations. This is how you override JAXB's default element naming.package com.dineshonjava.ws.xml;
import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlType;
/**
* @author Dinesh Rajput
*
*/
@XmlType(propOrder = { "publishDate", "publishNumber", "publishedBooks", "bookName" } )
@XmlAccessorType(XmlAccessType.FIELD)
public class Book {
@XmlElement
public Date publishDate;
@XmlElement
public long publishNumber;
@XmlElement
public String bookName;
@XmlElement
@XmlElementWrapper(name = "publishedBooks")
public PublishedBooks[] publishedBooks;
public Date getPublishDate() {
return publishDate;
}
public void setPublishDate(Date publishDate) {
this.publishDate = publishDate;
}
public long getPublishNumber() {
return publishNumber;
}
public void setPublishNumber(long publishNumber) {
this.publishNumber = publishNumber;
}
public String getBookName() {
return bookName;
}
public PublishedBooks[] getPublishedBooks() {
return publishedBooks;
}
public void setPublishedBooks(PublishedBooks[] publishedBooks) {
this.publishedBooks = publishedBooks;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
}
In the example above I used the @XmlAccessorType(XmlAccessType.FIELD) to allow me to place the @XmlElement annotations on the private fields instead of on the getter methods.package com.dineshonjava.ws.xml;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
/**
* @author Dinesh Rajput
*
*/
@XmlType(propOrder = { "bookId", "description", "quantity", "unitPrice",
"subTotal", "tax", "total" } )
public class PublishedBooks {
private long bookId;
private String description;
private short quantity;
private double unitPrice;
@XmlElement
public long getBookId() {
return bookId;
}
public void setBookId(long bookId) {
this.bookId = bookId;
}
@XmlElement
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@XmlElement
public short getQuantity() {
return quantity;
}
public void setQuantity(short quantity) {
this.quantity = quantity;
}
@XmlElement
public double getUnitPrice() {
return unitPrice;
}
public void setUnitPrice(double unitPrice) {
this.unitPrice = unitPrice;
}
@XmlElement
public double getSubTotal() {
return unitPrice * quantity;
}
@XmlElement
public double getTax() {
return getSubTotal() * 0.15F;
}
@XmlElement
public double getTotal() {
return getSubTotal() + getTax();
}
}
In the example above there are no setter methods that correspond to getSubTotal, getTax and getTotal. Now lets create a JAX-RS RESTful web service that can return this object graph in the response.package com.dineshonjava.ws.order;
import java.util.Date;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
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 javax.ws.rs.core.Response.ResponseBuilder;
import com.dineshonjava.ws.error.BookNotFoundException;
import com.dineshonjava.ws.rest.GetBooksResponse;
import com.dineshonjava.ws.xml.Book;
import com.dineshonjava.ws.xml.PublishedBooks;
import com.dineshonjava.ws.xml.Student;
/**
* @author Dinesh Rajput
*
*/
@Path("/order/books")
public class BookResource {
@GET
@Produces("text/xml")
public GetBooksResponse getBooks() {
GetBooksResponse response = new GetBooksResponse();
Student student = new Student();
PublishedBooks publishedBooks1;
PublishedBooks publishedBooks2;
// student
student.setRollNumber(12345);
student.setName("Dinesh Rajput");
student.setCourse("Computer Science");
response.setStudent(student);
// first book
Book book1 = new Book();
book1.setPublishNumber(54321);
book1.setPublishDate(new Date());
book1.setBookName("Java");
publishedBooks1 = new PublishedBooks();
publishedBooks1.setBookId(77777);
publishedBooks1.setDescription("winning lottery ticket");
publishedBooks1.setQuantity((short) 10);
publishedBooks1.setUnitPrice(5.00F);
publishedBooks2 = new PublishedBooks();
publishedBooks2.setBookId(12121212);
publishedBooks2.setDescription("Real World Java EE Patterns Rethinking Best Practices");
publishedBooks2.setQuantity((short) 1);
publishedBooks2.setUnitPrice(40.40F);
book1.setPublishedBooks(new PublishedBooks[] { publishedBooks1, publishedBooks2 } );
response.getBooks().add(book1);
// second book
Book book2 = new Book();
book2.setPublishNumber(12345);
book2.setPublishDate(new Date());
publishedBooks1 = new PublishedBooks();
publishedBooks1.setBookId(787878);
publishedBooks1.setDescription("JavaServer Faces 2.0, The Complete Reference");
publishedBooks1.setQuantity((short) 10);
publishedBooks1.setUnitPrice(31.49F);
publishedBooks2 = new PublishedBooks();
publishedBooks2.setBookId(1111111);
publishedBooks2.setDescription("Beginning Java EE 6 with GlassFish 3, Second Edition");
publishedBooks2.setQuantity((short) 1);
publishedBooks2.setUnitPrice(41.73F);
book2.setPublishedBooks(new PublishedBooks[] { publishedBooks1, publishedBooks1 } );
response.getBooks().add(book2);
return response;
}
@GET
@Path("{publishNumber}")
@Produces(MediaType.TEXT_PLAIN)
public Response updateOrder(@PathParam("publishNumber") String publishNumber) throws BookNotFoundException {
ResponseBuilder response;
if ("12345".equals(publishNumber)) {
response = Response.status(Response.Status.ACCEPTED).entity( "Saved changes to book '" +publishNumber+ "'.");
} else {
throw new BookNotFoundException("Publisher number '" + publishNumber +
"' does not exist.");
}
return response.build();
}
}
Notice the @Produces("text/xml") annotation, and that the method returns a GetBooksResponse object. Since the GetBooksResponse is annotated with JAXB annotations, JAX-RS will automatically marshal the response to XML.package com.dineshonjava.ws.order;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import com.dineshonjava.ws.xml.Book;
/**
* @author Dinesh Rajput
*
*/
@XmlRootElement
public class UpdateBookRequest {
private Book book;
@XmlElement
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
}
Now lets use this object in a PUT request:@PUT
@Path("{publishNumber}.xml")
@Produces(MediaType.TEXT_PLAIN)
public Response updateOrder(@PathParam("publishNumber") String publishNumber) throws BookNotFoundException {
ResponseBuilder response;
if ("12345".equals(publishNumber)) {
response = Response.status(Response.Status.ACCEPTED).entity( "Saved changes to book '" +publishNumber+ "'.");
} else {
throw new BookNotFoundException("Publisher number '" + publishNumber +
"' does not exist.");
}
return response.build();
}
Since the UpdateBookRequest is annotated with JAXB annotations, JAX-RS will automatically unmarshal it from XML.package com.dineshonjava.ws.error;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
/**
* @author Dinesh Rajput
*
*/
public class BookNotFoundException extends WebApplicationException {
/**
* version
*/
private static final long serialVersionUID = 1L;
public BookNotFoundException(String message) {
super(Response.status(Status.NOT_FOUND).entity(message).type(
MediaType.TEXT_PLAIN).build());
}
}
The custom exception extends the WebApplicationException in JAX-RS. There are many constructors in WebApplicationException. I chose to use the one that lets me provide the complete response data.<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>JaxRSJaxBApps</display-name>
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey-serlvet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>



Labels: REST