The following example demonstrates applying of collection resource paging using the CollectionPageService and CollectionPageAccessor utilities:
@Path("/demo/bookstore/books") public class BookStoreService { @Context // injected by the REST container private HttpServletRequest servletRequest; private final BookStore bookStore = new BookStore(); // underlying service /** * Retrieves an {@link BookCollectionResource} representation holding all books available in the store. The * {@link BookCollectionResource} representation and all embedded {@link BookResource} representations shall include * all of the mandatory and any of the optional fields annotated with {@code GET}. If {@code offset} and * {@code limit} parameters are present, the result will contain at most {@code limit} adapter representations * starting from {@code offset}. Resource field exclusion and inclusion may be applied based on the presence of * {@link QueryParameters#EXCLUDE} and {@link QueryParameters#INCLUDE} query parameters or * {@link HttpHeaders#X_REPRESENTATION_EXCLUDE} and {@link HttpHeaders#X_REPRESENTATION_INCLUDE} HTTP headers, which * values must represent a comma separated list of any of the fields of the book resource. .... */ @GET public Response getBooks() { RepresentationConstraints constraints = getRepresentationConstraints(); try { // extract and validate pagination parameters limit and offset using RequestContextUtils int offset = RequestContextUtils.getOffset(servletRequest); int limit = RequestContextUtils.getLimit(servletRequest); // create the collection resource page containing at most limit books starting form offset BookCollectionResource collection = getBookCollectionResourcePage(offset, limit, constraints); ResponseBuilder responseBuilder = Response.ok(collection); // add header for constraints of there are any constraints.toHttpHeaders(responseBuilder); return responseBuilder.build(); } catch (IllegalArgumentException iae) { ErrorDescriptionUtils.throwWebApplicationException(HttpURLConnection.HTTP_BAD_REQUEST, GeneralErrorCodes.ILLEGAL_PAGINATION_PARAMETERS, "Invalid value for offset and/or limit.", "Add valid offset and limit.", null, -1, iae); return null; } finally { // release the constraints constraints.release(); } } private BookCollectionResource getBookCollectionResourcePage( int offset, int limit, RepresentationConstraints constraints) { // get path for this request String path = servletRequest.getRequestURI().substring(servletRequest.getContextPath().length()); // remove first and last / path = path.endsWith( "/") ? path.substring(0, path.length() - 1) : path; path = path.charAt(0) == '/' ? path : "/" + path; UriBuilder uri = UriBuilder.fromPath(path); // add RepresentationConstraints query params to page hrefs constraints.toQueryParameters(uri); // generate unique accessor identifier for this request using the session identifier and the resource identifier String accessorID = servletRequest.getSession().getId() + path; // check if there is a CollectionPageAccessor already cached for this accessor identifier CollectionPageAccessor< Long> accessor = pageService.getCollectionPageAccessor(accessorID, Long.class); // if not cached, create and cache new CollectionPageAccessor holding the snapshot of the currently available books if (accessor == null) { // get the currently available books Collection<Book> books = bookStore.list(); Collection< Long> bookIDs = new ArrayList< Long>(); // extract a collection containing only the book identifiers // so that we avoid caching obsolete or invalidated objects for (Book book : books) { bookIDs.add(book.getId()); } // no books - handle empty collection if (books.isEmpty()) { if (offset != 0) { ErrorDescriptionUtils.throwWebApplicationException(HttpURLConnection.HTTP_BAD_REQUEST, GeneralErrorCodes.ILLEGAL_PAGINATION_PARAMETERS, "Non zero offset cannot be applied to an empty collection resource.", "Set offset to zero or simply remove the pagination constraints from the request.", null, -1, null); return null; } return BookResourceFactory.getBookCollectionResource(bookStore, bookIDs, constraints); } // create a new CollectionPageAccessor to cache the currently available books snapshot accessor = pageService.addCollectionPageAccessor(bookIDs, servletRequest.getSession().getId(), accessorID); } CollectionPage< Long> page = null; try { // get a CollectionPage of book identifiers based on the paging constraints page = accessor.getPage(offset, limit); } catch (IllegalArgumentException e) { ErrorDescriptionUtils.throwWebApplicationException(HttpURLConnection.HTTP_BAD_REQUEST, GeneralErrorCodes.ILLEGAL_PAGINATION_PARAMETERS, "Offset need to be in range and limit has to be a positive number.", "Make sure offset is in range and limit is positive number.", null, -1, e); return null; } // create new BookCollectionResource based on the books in the page BookCollectionResource collection = BookResourceFactory.getBookCollectionResource(bookStore, page, constraints); PaginationUtils.initPaginationCollectionFields(collection, uri, offset, limit, accessor.getCollection().size()); return collection; } .....}
A sample transfer of the first page BookCollectionResource page representation in JSON using HTTP 1.1 protocol:
GET /demo/bookstore/books?offset=0&limit=3 HTTP/1.1Content-Type: application/jsonAccept: application/jsonAuthorization: Basic YWRtaW46YWRtaW4=HTTP/1.1 200 OKContent-Type: application/json{ "offset": 0, "limit": 3, "first": "/demo/bookstore/books?limit=3&offset=0", "next": "/demo/bookstore/books?limit=3&offset=3", "last": "/demo/bookstore/books?limit=3&offset=3", "entries": [ { "href": "/demo/bookstore/books/1", "id": 1, "title": "Design Patterns: Elements of Reusable Object -Oriented Software ", "author": "Joshua Bloch", "count": 0 }, { "href": "/demo/bookstore/books/2", "id": 2, "title": "Refactoring: Improving the Design of Existing Code", "author": "Martin Fowler", "count": 2 }, { "href": "/demo/bookstore/books/3", "id": 3, "title": "The Pragmatic Programmer: From Journeyman to Master", "author": "Andrew Hunt", "count": 1 } ]}
By following the next navigational link the client can retrieve the next page of the BookCollectionResource representation based on the initially defined pagination constraints:
GET /demo/bookstore/books?offset=3&limit=3 HTTP/1.1Content-Type: application/jsonAccept: application/jsonAuthorization: Basic YWRtaW46YWRtaW4=HTTP/1.1 200 OKContent-Type: application/json{ "offset": 3, "limit": 3, "first": "/demo/bookstore/books?limit=3&offset=0", "previous": "/demo/bookstore/books?limit=3&offset=0", "last": "/demo/bookstore/books?limit=3&offset=3", "entries": [ { "href": "/demo/bookstore/books/4", "id": 4, "title": "Concurrent Programming in Java: Design Principles and Pattern", "author": "Doug Lea", "count": 3 }, { "href": "/demo/bookstore/books/5", "id": 5, "title": "Agile Software Development, Principles, Patterns, and Practices", "author": "Robert C. Martin", "count": 5 } ]}
A sample error response reporting an invalid pagination parameters error via an ErrorDescription representation in JSON:
GET /demo/bookstore/books?offset=10&limit=5 HTTP/1.1Content-Type: application/jsonAccept: application/jsonAuthorization: Basic YWRtaW46YWRtaW4=HTTP/1.1 400 Bad RequestContent-Type: application/json{ "status": 400, "code": 50040, "description": "Offset need to be in range and limit has to be a positive number.", "hint": "Make sure offset is in range and limit is positive number.", "exception": { "name": "java.lang.IllegalArgumentException", "code": -1, "message": "The offset given as argument is out of range: 10", "stacktrace": "java.lang.IllegalArgumentException: The offset given as argument is out of range: 10 com.prosyst.mbs.impl.services. rest.pagination.CollectionPageAccessorImpl.getPage(CollectionPageAccessorImpl.java:61) demo.bookstore. rest.BookStoreService.getPaginatedBookCollectionResource(BookStoreService.java:576) demo.bookstore. rest.BookStoreService.list(BookStoreService.java:226) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ...... }}