Canonical _links with Spring HATEOAS

怎甘沉沦 提交于 2019-12-30 06:53:07

问题


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
}

回答1:


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




回答2:


Since I had the same problem, I created a Jira issue at spring-data-rest: https://jira.spring.io/browse/DATAREST-682

If enough people vote for it, perhaps we can convince some of the developers to implement it :-).




回答3:


It's weird that you're pushing to display the canonical link. Once that resource at /father is retrieved the self link should be canonical...but there's really not a good reason to force the father relationship to be canonical...maybe some cacheing scheme?

To your specific question...you're relying on auto-generated controllers so you've given up the right to make decisions about a lot of your links. If you were to have your own PersonController than you would be more in charge of the link structure. If you created your own controller you could then use EntityLinks https://github.com/spring-projects/spring-hateoas#entitylinks with the Father's ID..IE

@Controller
@ExposesResourceFor(Person.class)
@RequestMapping("/people")
class PersonController {

  @RequestMapping
  ResponseEntity people(…) { … }

  @RequestMapping("/{id}")
  ResponseEntity person(@PathVariable("id") … ) {
    PersonResource p = ....
    if(p.father){
      p.addLink(entityLinks.linkToSingleResource(Person.class, orderId).withRel("father");
    }
  }
}

But that seems like a lot of work to just change the URL



来源:https://stackoverflow.com/questions/24570279/canonical-links-with-spring-hateoas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!