Search code examples
restgrailshateoas

Why can't I get HAL support to work in grails 2.3.8?


I am following the directions in the docs, here:

http://grails.org/doc/2.3.8/guide/webServices.html#hypermedia

Why won't grails produce HAL-formatted output, as shown in the documentation?

I have a domain object which I have mapped with the @Resource annotation:

@Resource(uri='/documentCatalogs', formats = ['json', 'xml'], readOnly = true)
class DocumentCatalog {
    String entityType
    String actionCode
    ...
}

...and in my conf/spring/resources.groovy, I have configured the HAL JSON renderer beans:

import com.cscinfo.platform.api.formslibrary.DocumentCatalog
import grails.rest.render.hal.HalJsonCollectionRenderer
import grails.rest.render.hal.HalJsonRenderer

// Place your Spring DSL code here
beans = {
    halDocumentCatalogRenderer(HalJsonRenderer, DocumentCatalog)
    halDocumentCatalogCollectionRenderer(HalJsonCollectionRenderer, DocumentCatalog)
}

Using the debugger, I confirmed that the initialize() method on HalJsonRenderer is called and that it is constructed with the correct targetType.

I send a rest call using Postman:

http://localhost:8080/formslibrary/documentCatalogs/3
Accept application/hal+json

And I get back a response which is regular JSON and doesn't contain any links:

{
    "class": "com.cscinfo.platform.api.formslibrary.DocumentCatalog",
    "id": 3,
    "actionCode": "WITH",
    "entityType": "LLP",
...
}

What did I miss? Is there some plugin or configuration setting I have to enable for this behavior? Is there some additional mapping property somewhere that's not documented?


Solution

  • Figured it out! There are multiple aspects of the fix...

    I had to add "hal" as one of the listed formats in the @Resource annotation:

    @Resource(uri='/documentCatalogs', formats = ['json', 'xml', 'hal'])
    

    Some hunting around in the debugger revealed that Grails will blithely ignore the Accept header, based on the UserAgent string that is sent from the client. (In my case, since I'm using Postman, it was the Google Chrome UA string.)

    One workaround for the Accept header issue is to add ".hal" to the end of the URL:

    http://localhost:8080/formslibrary/documentCatalogs/3.hal
    

    This isn't a very good solution IMO, since the HAL URLs generated by the renderer don't end in ".hal" by default.

    A better solution is to fix Grails' handling of the accept header by updating the config. In Config.groovy, you will see a line that says:

    grails.mime.disable.accept.header.userAgents = ['Gecko', 'WebKit', 'Presto', 'Trident']
    

    Change it to:

    grails.mime.disable.accept.header.userAgents = ['None']
    

    This forces Grails to honor the Accept header, regardless of the user agent.

    Hope this helps somebody else who's hitting the same issue.

    P.S. It's really helpful to put a breakpoint in the ResponseMimeTypesApi#getMimeTypesFormatAware(...) method.