Previous Topic

Next Topic

Book Contents

Book Index

Applying Paging Constraints to Collection Resources

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.1
Content-Type: application/json
Accept: application/json
Authorization: Basic YWRtaW46YWRtaW4=

HTTP/1.1 200 OK
Content-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.1
Content-Type: application/json
Accept: application/json
Authorization: Basic YWRtaW46YWRtaW4=

HTTP/1.1 200 OK
Content-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.1
Content-Type: application/json
Accept: application/json
Authorization: Basic YWRtaW46YWRtaW4=

HTTP/1.1 400 Bad Request
Content-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)
              ......
  }
}