Mapping

We opt for Jersey to implement the specified REST resources. As the reference JAX-RS implementation it is well supported, extensible and blends good enough with the Spring container. Considering that the first client of the API is to be the Odalic UI, AngularJS web application, choosing JSON as the primary data format was reasonable as well. When choosing how to map the domain objects to JSON, two opposing concerns emerged:

  1. ease of mapping directly the domain objects by marking them with JAXB annotations (work for JSON too, but even then require certain concessions to the classes design) on one hand,
  2. and the need to build a robust domain model that would fare well when its objects would be passed to the algorithm as feedback, or its classes used when adding new functionality.

Although JAXB allows to annotate the class fields and make the constructor private, and there is no need to make the mapped class to be a true Java Bean, this still does not make its instances reliable to use within the domain model: fields missing in the original JSON would still be initialized to null, so even than the domain class would have to validated before use. And that makes the domain model too fragile from the beginning.

In the end a solution separating the domain classes (used throughout the project code) and their counterparts (located in cz.cuni.mff.xrg.odalic.api.rest.values), that would be mapped to JSON, was chosen. Thanks to XmlAdapters most of the domain classes can be seamlessly converted to the mapped ones and back, and it is even possible to use them in the method signatures that handle HTTP requests, making the mapping almost invisible to the rest of the server code. This solution has some drawbacks, mainly It proved to be next to impossible to inject dependencies from Spring container to XmlAdapters. For example an XmlAdapter cannot be used to convert from Task JSON value to Task domain object, if you want to make sure that the file referred by task exists. In this case you have to manually convert from the cz.cuni.mff.xrg.odalic.api.rest.values.TaskValue to cz.cuni.mff.xrg.odalic.tasks.Task. in the Jersey resource class, where the file service can be injected without a problem. On the other hand there are some perks:

  • XmlAdapters are resolved recursively on the whole class hierarchy, so if you adapt a domain class that contains fields pointing to instances of other domain classes, you do not have to change the type of the fields in the mapped version, their XmlAdapters will be used automatically.
  • The adapters work well alongside custom JSON serializers and de-serializers from jackson-databind library. This allows to convert fields of complex type, e.g. nested Maps with non-String keys. This helped to encode a feedback on individual, sparsely distributed table cells in a relatively compact way (see cz.cuni.mff.xrg.odalic.api.rest.values.DisambiguationValue and compare with the output produced, as seen in in the REST API specification examples).

Other features

The API implementation relies on other useful Jersey features, such as request and response filters and exception mappers. The filters are used to set CORS headers, log the requests and to facilitate authorization and authentication. An exception mapper was used to convert the exceptions thrown during the course of request processing to JSON messages presented in the specification. Concerning the overall exception handling strategy, we chose to strictly separate the API from the underlying services and vowed not to raise web application exceptions in code that is not aware of being executed in web application. This required responsible mapping from general exceptions raised by the implementing services to the WebApplicationExceptions, that were in the end turned by the mapper into appropriate HTTP responses.