Creating a Basic JAX-RS endpoint
When starting with the JAX-RS development, trying to visualize or model the way the RESTful resources will be accessed can be helpful. One simple approach is to draw a table against each resource and list the HTTP verbs, formats, and URIs this resource will support. The other approach is to try to imagine what part of the application URI space will need to be supported by a dedicated handler.
These handlers will be mapped to JAX-RS root resource classes. For example, assuming /books and /book URI segments need to be supported, then one can imagine the developer starting to work on the following two root resource classes, alongside with a couple of simple beans:
/**
* The Book JAXB bean.
**/
@XmlRootElement(name = "book", namespace = "http://books")
public class Book {
private String name;
private String author;
private Date publicationDate;
private List<String> reviews;
// setters and getters are omitted
}
/**
* The Collection of Book instances.
**/
@XmlRootElement(name = "books", namespace = "http://books")
public class Books {
// XmlJavaTypeAdapter is available
private Map<Long, Book> books =
Collections.synchronizedMap(new HashMap<Long, Book>());
public void addBook(Long id, Book b) {
books.put(id, b);
}
public Book getBook(Long id) {
return books.get(id);
}
public void deleteBook(Long id) {
books.remove(id);
}
public void addBookReview(Long id, String review) {
getBook(id).addReview(review);
}
}
/**
* BookStore root resource class is responsible for handling
* URIs ending with '/books', '/books/{id}', etc. This resource
* will let users get the list of all books and add new books
**/
@Path("/books")
public class BooksStore {
private static AtomicLong ID = new AtomicLong();
private Books books;
// Thread-safe UriInfo instance providing the
// extended information about the current URI
@Context
private UriInfo uriInfo;
/**
* Injects the Books storage
**/
public void setBooks(Books books) {
this.books = books;
}
/**
* Returns the list of all the books
**/
@GET
@Produces("application/xml")
public Books getAllBooks() {
retun books;
}
/**
* Adds a new Book to the internal storage and returns
* an HTTP Location header pointing to a new Book resource.
**/
@POST
@Consumes("application/xml")
public Response addBook(Book book) {
// New Book ID
Long id = ID.incrementAndGet();
books.add(id, book);
// Get the base URI of the application and wrap it into a builder.
// UriBuilder makes it easy to compose new URIs.
UriBuilder builder = uriInfo.getBaseUriBuilder();
// Build a new book resource URI
// with say a '/book/1' segment added to the base URI
builder.path("/book/" + id);
URI newBookResourceURI = builder.build();
// Return 201 and the Location header
retun Response.created().location(uri).build();
}
}
/**
* BookStore root resource class is responsible for handling
* URIs ending with '/book', '/book/{id}', etc. This resource
* will let users get, update, and delete individual books.
**/
@Path("/book")
public class BookHandler {
private Books books;
/**
* Injects the Books storage
**/
public void setBooks(Books books) {
this.books = books;
}
@GET
@Produces("application/xml")
@Path("{id}")
public Book getBook(@PathParam("id") Long id) {
return books.getBook(id);
}
@PUT
@Consumes("text/plain")
@Path("{id}")
public void setBookReview(@PathParam("id") Long id, String review) {
books.addBookReview(review);
}
@DELETE
@Path("{id}")
public void deleteBook(@PathParam("id") Long id) {
books.deleteBook(id);
}
}
The developer has prototyped two root resource classes, BooksStore, and BookHandler. Next the configuration for the new JAX-RS endpoint has been added (see below for the example), the store.war has been built and deployed to a servlet container listening on localhost:8080. Given that the name of the war, 'store' in this case, contributes to the URI path, the base URI of the Store application is http://localhost:8080/store.
The BookStore will handle HTTP GET requests with the URIs such as http://localhost:8080/store/books and return the list of Books in the XML format. It will also accept POST requests with new Books being submitted in the XML format to http://localhost:8080/store/books.
The BookStore.getAllBooks() method implementation is simple, while BookStore.addBook(Book) is a bit more involved, but it simply follows a basic pattern to do with adding new resources. Particularly, POST usually adds new resources and the typical response is to return a 201 status with the Location header pointing to a new resource URI. For example, if the new Book id is "1" then given that the base URI is http://localhost:8080/store/, the unique resource URI of the new Book resource will be http://localhost:8080/store/book/1.
The client code or browser script which was used to add the new Book can choose to follow the http://localhost:8080/store/book/1 using GET. In this case another root resource, BookHandler will handle GET, as well as PUT and DELETE requests, all targeted at the individual Book resources.
Note that instead of introducing a dedicated BookHandler root resource supporting the URIs such as '/book/1', the developer couild have opted for supporting 'books/1' URIs instead, and thus all the BookHandler methods could have been implemented as part of the BooksStore class. For example:
@Path("/books")
public class BooksStore {
@GET
@Produces("application/xml")
public Books getAllBooks() {...}
@POST
@Consumes("application/xml")
public Response addBook(Book book) {...}
@GET
@Produces("application/xml")
@Path("{id}")
public Book getBook(@PathParam("id") Long id) {...}
@PUT
@Consumes("text/plain")
@Path("{id}")
public void setBookReview(@PathParam("id") Long id, String review)
{...}
@DELETE
@Path("{id}")
public void deleteBook(@PathParam("id") Long id) {...}
...
}
Many options are available and JAX-RS makes it easy for developers to structure their Java web services applications as needed
And here is how the JAX-RS endpoint can be configured:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource=
"classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml"/>
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<bean class="org.books.BooksStore" id="storeBean"/>
<bean class="org.books.BookHandler" id="bookBean"/>
<jaxrs:server id="bookservice" address="/">
<jaxrs:serviceBeans>
<ref bean="storeBean" />
<ref bean="bookBean" />
</jaxrs:serviceBeans>
</jaxrs:server>
</beans>
Please also see a jaxrs_intro demo in the TSF Examples distribution.