Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Canonical _links with Spring HATEOAS

We're building a RESTful web service similiar to the spring.io guide "Accessing JPA Data with REST". To reproduce the sample outputs below, it suffices to add a ManyToOne-Relation to Person as follows:

// ...

@Entity
public class Person {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  private String firstName;
  private String lastName;

  @ManyToOne
  private Person father;

  // getters and setters
}

A GET request after adding some sample data yields:

{
  "firstName" : "Paul",
  "lastName" : "Mustermann",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/1"
    },
    "father" : {
      "href" : "http://localhost:8080/people/1/father"
    }
  }
}

But, given Paul's father is stored with ID 2, our desired result would be the canonical url for its relation:

// ...
    "father" : {
      "href" : "http://localhost:8080/people/2"
    }
// ...

This of course will cause problems if father is null on some persons (OK, this does not make much sense here... ;)), but in this case we would like to not render the link in JSON at all.

We already tried to implement a ResourceProcessor to achieve this, but it seems that by the time the processor is called the links are not populated yet. We managed to add additional links pointing to the desired canonical url, but failed to modify the somehow later added links.

Question: Is there a generic approach to customize the link generation for all resources?

To clarify our need for canonical URLs: We use the SproutCore Javascript framework to access the RESTful web service. It uses an "ORM-like" abstraction of data sources for which we've implemented a generic handler of the JSON output Spring produces. When querying for all persons, we would need to send n*(1+q) requests (instead of just n) for n persons with q relations to other persons to sync them to the client side data source. This is because the default "non-canonical" link contains absolutely no information about a father being set or the father's id. It just seems that this causes a huge amount of unnecessary requests which could be easily avoided if the initial response would contain a little more information in the first place.

Another solution would be to add the father's id to the relation, e.g.:

"father" : {
  "href" : "http://localhost:8080/people/1/father",
  "id" : 2
}
like image 923
dkellner Avatar asked Oct 20 '22 05:10

dkellner


1 Answers

There is a discussion somewhere Spring Data Rest team explained why properties are rendered as links that way. Having said you could still achieve what you like by suppressing link generated by SDR and implementing ResourceProcessor. So your Person class would look like below. Note the annotation @RestResource(exported = false) to suppress link

@Entity
public class Person {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  private String firstName;
  private String lastName;

  @ManyToOne
  @RestResource(exported = false)
  private Person father;

  // getters and setters
}

Your ResourceProcessor class would look like

public class EmployeeResourceProcessor implements
        ResourceProcessor<Resource<Person>> {

    @Autowired
    private EntityLinks entityLinks;

    @Override
    public Resource<Person> process(Resource<Person> resource) {
        Person person = resource.getContent();
        if (person.getFather() != null) {
            resource.add(entityLinks.linkForSingleResour(Person.class, person.getFather().getId())
                .withRel("father"));
        }
        return resource;
    }

}

The above solution works only if father value is eagerly fetched along with Person. Otherwise you need to have property fatherId and use it instead of father property. Don't forget to use Jackson @ignore... to hide fatherId in response JSON.

Note: I haven't tested this myself but guessing it would work

like image 147
Stackee007 Avatar answered Oct 24 '22 12:10

Stackee007