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