thehyve/transmart-rest-api — pull request #2 by cataphract (2014-02-19 23:02:14+00:00)
Title: Support HAL serialization

Unfortunately, I could not find a way to use the native support Grails has in a
clean manner. We want to support normal json and hal side by side (using the
Accpet header), but Grails' `Renderer` for HAL uses Gson, while the normal Json
renderer uses `grails.converters.JSON`. Worse than that, Grails' HAL Renderer,
`HalJsonRenderer`, serializes objects property by property. When it finds a
property value that is a value, it invokes the same property-by-property
serialization routine recursively. The consequence of this is that, while we
can customize how an object type is rendered by overriding its `Renderer`, if
this object has sub-objects whose serialization we also want to customize, we
have to implement the serialization of these sub-objects inline, instead of
being able to automatically delegate to some object that knows specifically how
to serialize the sub-object. We cannot just invoke other renderers from the
main object's renderer because the each render() call on the renderer will
create a new serialization context.

On the other hand, if we have two classes `A` and `B`:

class B {} class A { B b }

we can, with `grails.converters.JSON`, register marshallers for `A` and `B`:

JSON.registerObjectMarshaller(A, { a -> [ b: a.b ] })
JSON.registerObjectMarshaller(B, ...)

and then `new A(b: new B())` just works. But, by implementing a
property-by-property serialization instead of using Gson's analogous custom
serialization mechanism (the `TypeAdapter`s), the `HalJsonRenderer` doesn't
allow the same sort of automatic serialization of sub-objects.

So we implement a new HAL renderer that, like the `JsonRenderer`, uses
`grails.converters.JSON`. We introduce a new `Renderer` class,
`HalOrJsonRenderer` and a new `grails.converters.JSON` marshaller class,
`CoreApiObjectMarshaller`. `HalOrJsonRenderer` essentially calls `object as
JSON`, though it uses the `grails.converters.JSON` in a slightly different
fashion and, crucially, sets the mime type in the `JSON` converter, either
`application/json` or `application/hal+json`/. When the converter delegates to
the marshaller, this mime type will be available.

When `marshalObject()` on the custom marshaller is invoked,
`CoreApiObjectMarshaller` has two courses of action available, depending on
whether it's working with a json or hal+json serialization. A different
instance of `CoreApiObjectMarshaller` is instantiated for each core-api object
we want to customize serization for. Each `CoreApiObjectMarshaller` instance
includes an instance of `HalOrJsonSerializationHelper`. This interface has
three essential methods: one for converting the objects (of the type it
supports) into maps, another one to retrieve a list of links for the object and
another to identify the entries of the created map which refer to collections.
These have to identified separately so that they can be included in an
`_embedded` property in the HAL response. The `CoreApiObjectMarshaller` either
passes for further serialization processing the `Map` returned by the helper
unchanged (in case of plain json) or it modifies the map with a `_links` and an
`_embedded` attribute, in case of a HAL response type.

Finally, `MarshallersRegistrar` binds everything together. It scans for helper
classes and, for each class:
1. registers the helper class as a bean,
2. instantiates the bean,
3. builds a `CoreApiObjectMarshaller` that includes this new helper object,
4. registers the marshaller with `grails.converters.JSON` and
5. builds a new bean of type `HalOrJsonRenderer` that targets the rendering of
the type supported by the helper class.