how to introduce etag and version headers with spring data rest abstraction

505 views Asked by At

Hi I am using spring data rest with Jpa in my project to expose HAL based rest webservices .It works well for most of my cases and for further customization I do use additional controllers and call spring data repositories to fetch data for me and use hateos resource for displaying those with links exposed via hateos entityLinks .This is great and works for most of my use cases . Now I have a few additional requirement wherein I want to put etag headers for server caching and instance level version link headers [https://www.rfc-editor.org/rfc/rfc5988] more specific https://www.rfc-editor.org/rfc/rfc5829#page-3 as below for my responses .

GET /81822 HTTP/1.1 ...

HTTP/1.1 OK
Host: dumbserver.com
Content-Type: application/json
Link: </81822 ;v=1.1>; rel="previous";
       </81822 >; rel="current";
       </81822 /version-history>; rel="version-history";

{

Is this possible via using hateos interfaces or will I have to go for a custom approach by adding these via HttpServletResponse or responseentity.getHeaader and add custom code for handling versioning .I think Spring data rest or hateos must work on providing abstractions for these as well.

1

There are 1 answers

0
Snekse On

I think you pretty much have to roll your own once you override a Spring Data Rest endpoint from a controller, even if your controller is annotated with @RepositoryRestController.

We did this by creating a base controller that is responsible for wrapping our entity as a Resource which in turn gets wrapped in a ResponseEntity

Our ETag happens to be based on the entity versionNumber which has a @Version annotation.

protected ResponseEntity<Resource<T>> createResponse(T entity, 
                   PersistentEntityResourceAssembler assembler) 
{
    def bb = ResponseEntity.ok()
    bb.eTag(ETag.from("${entity.versionNumber}").toString())
    return bb.body(createResource(entity, assembler))
}

protected Resource<T> createResource(T entity, ResourceAssembler assembler) {
    def resource = resourceCreator.toResource(assembler, entity)
    resource.add(buildSelfLink(resource))
    resource
}

Link buildSelfLink(Resource<T> resource) {
    linkBuilder.buildSelfLinkWithId(this.class, resource.content.id)
}

Link prependLinkWithApiBase(ControllerLinkBuilder linkBuilder) {
    URI uri = linkBuilder.toUri()
    String origPath = uri.rawPath
    String newPath = "$API_BASE$origPath"
    log.debug("Replacing {} with {}", origPath, newPath)
    def uriBuilder = UriComponentsBuilder.fromUri(uri).replacePath(newPath)
    new Link(uriBuilder.toUriString())
}

Link buildSelfLinkWithId(Class c, def id) {
    /*
        When trying to use `methodOn` to properly build 
        the self URI, we ran into this. 
        Going the dumb route instead.

        java.lang.ClassCastException: 
          Cannot cast com.MyEntity$$EnhancerByCGLIB$$a1654b7b
        to com.MyEntity
        at java.lang.Class.cast(Class.java:3369) ~[na:1.8.0_92]
        at org.sfw.hateoas.core.DummyInvocationUtils
            $InvocationRecordingMethodInterceptor.intercept(DummyInvocationUtils.java:100)
            ~[spring-hateoas-0.20.0.RELEASE.jar:na]
     */

    //Due to https://github.com/spring-projects/spring-hateoas/issues/434 we have hoops to jump
    def linkBuilder = linkTo(c).slash(id)
    prependLinkWithApiBase(linkBuilder)
}