#837 - First chunk of improvements to reference documentation.
Introduced new structure to reference docs that reflects the new library structure. Completely rewrote the Fundamentals section. Adapted to all type renames in the codebase. Fixed anchors in migration chapter. Integrated PlantUML plugin for Asciidoctor generation to generate diagrams.
This commit is contained in:
10
pom.xml
10
pom.xml
@@ -297,6 +297,11 @@
|
||||
<artifactId>asciidoctorj-pdf</artifactId>
|
||||
<version>1.5.0-alpha.16</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctorj-diagram</artifactId>
|
||||
<version>1.5.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
|
||||
@@ -321,6 +326,9 @@
|
||||
<highlightjsdir>js/highlight</highlightjsdir>
|
||||
<highlightjs-theme>atom-one-dark-reasonable</highlightjs-theme>
|
||||
</attributes>
|
||||
<requires>
|
||||
<require>asciidoctor-diagram</require>
|
||||
</requires>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
@@ -658,7 +666,7 @@
|
||||
<artifactId>reactor-extra</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Needs to be after Jadler to make sure it sees the Servlet 3.0 dependency pulled in for testing -->
|
||||
|
||||
<dependency>
|
||||
|
||||
91
src/main/asciidoc/client.adoc
Normal file
91
src/main/asciidoc/client.adoc
Normal file
@@ -0,0 +1,91 @@
|
||||
[[client]]
|
||||
= Client-side Support
|
||||
|
||||
This section describes Spring HATEOAS's support for clients.
|
||||
|
||||
[[client.traverson]]
|
||||
== Traverson
|
||||
|
||||
Spring HATEOAS provides an API for client-side service traversal. It is inspired by the https://blog.codecentric.de/en/2013/11/traverson/[Traverson JavaScript library].
|
||||
The following example shows how to use it:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("user", 27);
|
||||
|
||||
Traverson traverson = new Traverson(new URI("http://localhost:8080/api/"), MediaTypes.HAL_JSON);
|
||||
String name = traverson.follow("movies", "movie", "actor").
|
||||
withTemplateParameters(parameters).
|
||||
toObject("$.name");
|
||||
----
|
||||
====
|
||||
|
||||
You can set up a `Traverson` instance by pointing it to a REST server and configuring the media types you want to set as `Accept` headers. You can then define the relation names you want to discover and follow. Relation names can either be simple names or JSONPath expressions (starting with an `$`).
|
||||
|
||||
The sample then hands a parameter map into the execution. The parameters are used to expand URIs (which are templated) found during the traversal. The traversal is concluded by accessing the representation of the final traversal. In the preceding example, we evaluate a JSONPath expression to access the actor's name.
|
||||
|
||||
The preceding example is the simplest version of traversal, where the `rel` values are strings and, at each hop, the same template parameters are applied.
|
||||
|
||||
There are more options to customize template parameters at each level.
|
||||
The following example shows these options.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{baseDir}/src/test/java/org/springframework/hateoas/client/TraversonTest.java[tag=hop-with-param]
|
||||
----
|
||||
|
||||
The static `rel(...)` function is a convenient way to define a single `Hop`. Using `.withParameter(key, value)` makes it simple to specify URI template variables.
|
||||
|
||||
NOTE: `.withParameter()` returns a new `Hop` object that is chainable. You can string together as many `.withParameter` as you like. The result is a single `Hop` definition.
|
||||
The following example shows one way to do so:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{baseDir}/src/test/java/org/springframework/hateoas/client/TraversonTest.java[tag=hop-put]
|
||||
----
|
||||
====
|
||||
|
||||
You can also load an entire `Map` of parameters by using `.withParameters(Map)`.
|
||||
|
||||
NOTE: `follow()` is chainable, meaning you can string together multiple hops, as shown in the preceding examples. You can either put multiple string-based `rel` values (`follow("items", "item")`) or a single hop with specific parameters.
|
||||
|
||||
=== `EntityModel<T>` vs. `CollectionModel<T>`
|
||||
|
||||
The examples shown so far demonstrate how to sidestep Java's type erasure and convert a single JSON-formatted resource into a `EntityModel<Item>` object. However, what if you get a collection like an `\_embedded` HAL collection?
|
||||
You can do so with only one slight tweak, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
CollectionModelType<Item> collectionModelType =
|
||||
TypeReferences.CollectionModelType<Item>() {};
|
||||
|
||||
CollectionModel<Item> itemResource = traverson.//
|
||||
follow(rel("items")).//
|
||||
toObject(collectionModelType);
|
||||
----
|
||||
====
|
||||
|
||||
Instead of fetching a single resource, this one deserializes a collection into `CollectionModel`.
|
||||
|
||||
[[client.link-discoverer]]
|
||||
== Using `LinkDiscoverer` Instances
|
||||
|
||||
When working with hypermedia enabled representations, a common task is to find a link with a particular relation type in it. Spring HATEOAS provides https://code.google.com/p/json-path[JSONPath]-based implementations of the `LinkDiscoverer` interface for either the default representation rendering or HAL out of the box. When using `@EnableHypermediaSupport`, we automatically expose an instance supporting the configured hypermedia type as a Spring bean.
|
||||
|
||||
Alternatively, you can setup and use an instance as follows:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
String content = "{'_links' : { 'foo' : { 'href' : '/foo/bar' }}}";
|
||||
LinkDiscoverer discoverer = new HalLinkDiscoverer();
|
||||
Link link = discoverer.findLinkWithRel("foo", content);
|
||||
|
||||
assertThat(link.getRel(), is("foo"));
|
||||
assertThat(link.getHref(), is("/foo/bar"));
|
||||
----
|
||||
====
|
||||
209
src/main/asciidoc/fundamentals.adoc
Normal file
209
src/main/asciidoc/fundamentals.adoc
Normal file
@@ -0,0 +1,209 @@
|
||||
[[fundamentals]]
|
||||
= Fundamentals
|
||||
|
||||
This section covers the basics of Spring HATEOAS and its fundamental domain abstractions.
|
||||
|
||||
[[fundamentals.links]]
|
||||
== Links
|
||||
|
||||
The fundamental idea of hypermedia is to enrich the representation of a resource with hypermedia elements.
|
||||
The simplest form of that are links.
|
||||
They indicate a client that it can navigate to a certain resource.
|
||||
The semantics of a related resource are defined in a so called link relation.
|
||||
You might have seen this in the header of an HTML file already:
|
||||
|
||||
.A link in an HTML document
|
||||
====
|
||||
[source, html]
|
||||
----
|
||||
<link href="theme.css" rel="stylesheet" type="text/css" />
|
||||
----
|
||||
====
|
||||
|
||||
As you can see the link points to a resource `theme.css` and indicates that it is a style sheet.
|
||||
Links often carry additional information, like the media type that the resource pointed to will return.
|
||||
However, the fundamental building blocks of a link are its reference and relation.
|
||||
|
||||
Spring HATEOAS let's you work with links through its immutable `Link` value type.
|
||||
Its constructor take both an hypertext reference and a link relation, the latter being defaulted to the IANA link relation `self`.
|
||||
Read more on the latter in <<fundamentals.link-relations>>.
|
||||
|
||||
.Using links
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Link link = new Link("/something");
|
||||
assertThat(link.getHref()).isEqualTo("/something");
|
||||
assertThat(link.getRel()).isEqualTo(IanaLinkRelation.SELF);
|
||||
|
||||
Link link = new Link("/something", "my-rel");
|
||||
assertThat(link.getHref()).isEqualTo("/something");
|
||||
assertThat(link.getRel()).isEqualTo(LinkRelation.of"my-rel");
|
||||
----
|
||||
====
|
||||
|
||||
`Link` exposes other attributes as defined in https://tools.ietf.org/html/rfc5988[RFC-5988].
|
||||
You can set them by calling the corresponding wither method on a `Link` instance.
|
||||
|
||||
Find more information on how to create links pointing to Spring MVC and Spring WebFlux controllers in <<server.link-builder>>.
|
||||
|
||||
[[fundamentals.uri-templates]]
|
||||
== URI templates
|
||||
|
||||
For a Spring HATEOAS `Link`, the hypertext reference can not only be a URI, but also a URI template according to https://tools.ietf.org/html/rfc6570[RFC-6570].
|
||||
A URI template contains so called template variables and allows expansion of these parameters.
|
||||
This allows clients to turn parameterized templates into URIs without having to know about the structure of the final URI, it only needs to know about the names or the variables.
|
||||
|
||||
.Using links with templated URIs
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Link link = new Link("/{segment}/something{?parameter}");
|
||||
assertThat(link.isTemplated()).isTrue(); <1>
|
||||
assertThat(link.getVariableNames()).containsAll("segment", "parameter"); <2>
|
||||
|
||||
Map<String, Object> values = new HashMap<>();
|
||||
values.put("segment", "path");
|
||||
values.put("parameter", 42);
|
||||
|
||||
assertThat(link.expand(values).getHref()) <3>
|
||||
.isEqualTo("/path/something?parameter=42");
|
||||
----
|
||||
<1> The `Link` instance indicates that is templated, i.e. it contains a URI template.
|
||||
<2> It exposes the parameters contained in the template.
|
||||
<3> It allows expansion of the parameters.
|
||||
====
|
||||
|
||||
URI templates can be constructed manually and template variables added later on.
|
||||
|
||||
.Working with URI templates
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
UriTemplate template = new UriTemplate("/{segment}/something")
|
||||
.with(new TemplateVariable("parameter", VariableType.REQUEST_PARAM);
|
||||
|
||||
assertThat(template.toString()).isEqualTo("/{segment}/something{?parameter}");
|
||||
----
|
||||
====
|
||||
|
||||
[[fundamentals.link-relations]]
|
||||
== Link relations
|
||||
|
||||
To indicate the relationship of target resource to the current one so called link relations are used.
|
||||
Spring HATEOAS provides a `LinkRelation` type to easily create `String`-based instances of it.
|
||||
|
||||
|
||||
[[fundamentals.link-relations.iana]]
|
||||
=== IANA link relations
|
||||
|
||||
The Internet Assigned Numbers Authority contains a set of https://www.iana.org/assignments/link-relations/link-relations.xhtml[predefined link relations].
|
||||
They can be referred to via `IanaLinkRelations`.
|
||||
|
||||
.Using IANA link relations
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Link link = new Link("/some-resource"), IanaLinkRelations.NEXT);
|
||||
|
||||
assertThat(link.getRel()).isEqualTo(LinkRelation.of("next"));
|
||||
assertThat(IanaLinkRelation.isIanaRel(link.getRel())).isTrue();
|
||||
----
|
||||
====
|
||||
|
||||
[[fundamentals.representation-models]]
|
||||
== [[fundamentals.resources]] Representation models
|
||||
|
||||
To easily create hypermedia enriched representations, Spring HATEOAS provides a set of classes with `RepresentationModel` at their root.
|
||||
It's basically a container for a collection of ``Link``s and has convenient methods to add those to the model.
|
||||
The models can later be rendered into various media type formats that will define how the hypermedia elements look in the representation.
|
||||
For more information on this, have a look at <<mediatypes>>
|
||||
|
||||
.The `RepresentationModel` class hierarchy
|
||||
====
|
||||
[plantuml, diagram-classes, svg]
|
||||
----
|
||||
class RepresentationModel
|
||||
class EntityModel
|
||||
class CollectionModel
|
||||
class PagedModel
|
||||
|
||||
EntityModel -|> RepresentationModel
|
||||
CollectionModel -|> RepresentationModel
|
||||
PagedModel -|> CollectionModel
|
||||
----
|
||||
====
|
||||
|
||||
The default way to work with a `RepresentationModel` is to create a subclass of it to contain all the properties the representation is supposed to contain, create instances of that class, populate the properties and enrich it with links.
|
||||
|
||||
.A sample representation model type
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
class PersonModel extends RepresentationModel<PersonModel> {
|
||||
|
||||
String firstname, lastname;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The generic self-typing is necessary to let `RepresentationModel.add(…)` return instances of itself.
|
||||
The model type can now be used like this:
|
||||
|
||||
.Using the person representation model
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
PersonModel model = new PersonModel();
|
||||
model.firstname = "Dave";
|
||||
model.lastname = "Matthews";
|
||||
model.add(new Link("http://myhost/people/42"));
|
||||
----
|
||||
====
|
||||
|
||||
If you returned such an instance from a Spring MVC or WebFlux controller and the client sent an `Accept` header set to `application/hal+json`, the response would look as follows:
|
||||
|
||||
.The HAL representation generated for the person representation model
|
||||
====
|
||||
[source, json]
|
||||
----
|
||||
{
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "http://myhost/people/42"
|
||||
}
|
||||
}
|
||||
"firstname" : "Dave",
|
||||
"lastname" : "Matthews"
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[fundamentals.entity-model]]
|
||||
=== Item resource representation model
|
||||
|
||||
For a resource that's backed by a singular object or concept, a convenience `EntityModel` type exists.
|
||||
Instead of creating a custom model type for each concept, you can just reuse an already existing type and wrap instances of it into the `EntityModel`.
|
||||
|
||||
.Using `EntityModel` to wrap existing objects
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Person person = new Person("Dave", "Matthews");
|
||||
EntityModel<Person> model = new EntityModel<>(person);
|
||||
----
|
||||
====
|
||||
|
||||
=== Collection resource representation model
|
||||
|
||||
For resources that a conceptually collections, a `CollectionModel` is available.
|
||||
Its elements can either be simple objects or `RepresentationModel` instances in turn.
|
||||
|
||||
.Using `EntityModel` to wrap existing objects
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Collection<Person> people = Collections.singleton(new Person("Dave", "Matthews"));
|
||||
CollectionModel<Person> model = new CollectionModel<>(people);
|
||||
----
|
||||
====
|
||||
@@ -14,317 +14,10 @@ NOTE: Copies of this document may be made for your own use and for distribution
|
||||
[[preface]]
|
||||
== Preface
|
||||
include::migrate-to-1.0.adoc[leveloffset=+2]
|
||||
include::fundamentals.adoc[leveloffset=+1]
|
||||
include::mediatypes.adoc[leveloffset=+1]
|
||||
include::server.adoc[leveloffset=+1]
|
||||
|
||||
[[fundamentals]]
|
||||
== Fundamentals
|
||||
|
||||
This section covers the basics of Spring HATEOAS.
|
||||
|
||||
[[fundamentals.jaxb-json]]
|
||||
=== Jackson integration
|
||||
|
||||
As representations for REST web services are usually rendered in JSON, the natural choice of technology to achieve this is Jackson. To follow HATEOAS principles, you need to incorporate links into those representations. Spring HATEOAS provides a set of useful types to ease working with those representations.
|
||||
|
||||
[[fundamentals.links]]
|
||||
=== Links
|
||||
The `Link` value object follows the Atom link definition and consists of a `rel` attribute and an `href` attribute. It contains a few constants for well known `rel` values, such as `self`, `next`, and others.
|
||||
The following example shows some typical links and how you might test them:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Link link = new Link("http://localhost:8080/something");
|
||||
assertThat(link.getHref(), is("http://localhost:8080/something"));
|
||||
assertThat(link.getRel(), is(IanaLinkRelation.SELF));
|
||||
|
||||
Link link = new Link("http://localhost:8080/something", "my-rel");
|
||||
assertThat(link.getHref(), is("http://localhost:8080/something"));
|
||||
assertThat(link.getRel(), is("my-rel"));
|
||||
----
|
||||
====
|
||||
|
||||
[[fundamentals.resources]]
|
||||
=== Resources
|
||||
|
||||
As pretty much every representation of a resource contains some links (at least the `self` one), we provide a base class (called `ResourceSupport`) to actually inherit from when designing representation classes.
|
||||
The following example shows a `PersonResource` class that extends the `ResourceSupport` base class:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
class PersonResource extends ResourceSupport {
|
||||
|
||||
String firstname;
|
||||
String lastname;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Inheriting from `ResourceSupport` lets you add links.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
PersonResource resource = new PersonResource();
|
||||
resource.firstname = "Dave";
|
||||
resource.lastname = "Matthews";
|
||||
resource.add(new Link("http://myhost/people"));
|
||||
----
|
||||
|
||||
This would render as follows in JSON:
|
||||
|
||||
[source, json]
|
||||
----
|
||||
{
|
||||
"firstname" : "Dave",
|
||||
"lastname" : "Matthews",
|
||||
"_links" : [
|
||||
{
|
||||
"rel" : "self",
|
||||
"href" : "http://myhost/people"
|
||||
}
|
||||
]
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
You can then access links contained in that resource.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Link selfLink = new Link("http://myhost/people");
|
||||
assertThat(resource.getId(), is(selfLink));
|
||||
assertThat(resource.getLink(IanaLinkRelation.SELF.value()), is(selfLink));
|
||||
----
|
||||
====
|
||||
|
||||
[[fundamentals.obtaining-links]]
|
||||
=== Obtaining Links
|
||||
|
||||
This section describes how to obtain links by using the link builder.
|
||||
|
||||
[[fundamentals.obtaining-links.builder]]
|
||||
==== Link Builder
|
||||
Now we have the domain vocabulary in place, but the main challenge remains: how to create the actual URIs to be wrapped into `Link` instances in a less fragile way. Right now, we would have to duplicate URI strings all over the place. Doing so is brittle and unmaintainable.
|
||||
|
||||
Assume you have your Spring MVC controllers implemented as follows:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Controller
|
||||
@RequestMapping("/people")
|
||||
class PersonController {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
public HttpEntity<PersonResource> showAll() { … }
|
||||
|
||||
@RequestMapping(value = "/{person}", method = RequestMethod.GET)
|
||||
public HttpEntity<PersonResource> show(@PathVariable Long person) { … }
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
We see two conventions here. The first is a collection resource that is exposed through `@RequestMapping` annotation of the controller class, with individual elements of that collection exposed as direct sub resources. The collection resource might be exposed at a simple URI (as just shown) or more complex ones (such as `/people/{id}/addresses`). Suppose you would like to link to the collection resource of all people. Following the approach from up above would cause two problems:
|
||||
|
||||
* To create an absolute URI, you would need to look up the protocol, hostname, port, servlet base, and other values. This is cumbersome and requires ugly manual string concatenation code.
|
||||
* You probably do not want to concatenate the `/people` on top of your base URI, because you would then have to maintain the information in multiple places. If you change the mapping, you then have to change all the clients pointing to it.
|
||||
|
||||
Spring Hateoas now provides a `ControllerLinkBuilder` that lets you create links by pointing to controller classes.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
import static org.sfw.hateoas.mvc.ControllerLinkBuilder.*;
|
||||
|
||||
Link link = linkTo(PersonController.class).withRel("people");
|
||||
assertThat(link.getRel(), is("people"));
|
||||
assertThat(link.getHref(), endsWith("/people"));
|
||||
----
|
||||
====
|
||||
|
||||
The `ControllerLinkBuilder` uses Spring's `ServletUriComponentsBuilder` under the hood to obtain the basic URI information from the current request. Assuming your application runs at `http://localhost:8080/your-app`, this is exactly the URI on top of which you are constructing additional parts. The builder now inspects the given controller class for its root mapping and, thus, ends up with `http://localhost:8080/your-app/people`. You can also build more nested links as well.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Person person = new Person(1L, "Dave", "Matthews");
|
||||
// /person / 1
|
||||
Link link = linkTo(PersonController.class).slash(person.getId()).withSelfRel();
|
||||
assertThat(link.getRel(), is(IanaLinkRelation.SELF.value()));
|
||||
assertThat(link.getHref(), endsWith("/people/1"));
|
||||
----
|
||||
====
|
||||
|
||||
If your domain class implements the `Identifiable` interface, the `slash(…)` method invokes `getId()` on the given object instead of `toString()`. Thus, you can abbreviate the link creation shown in the preceding example to the following:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
class Person implements Identifiable<Long> {
|
||||
public Long getId() { … }
|
||||
}
|
||||
|
||||
Link link = linkTo(PersonController.class).slash(person).withSelfRel();
|
||||
----
|
||||
====
|
||||
|
||||
The builder also allows creating URI instances to build up (for example, response header values):
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setLocation(linkTo(PersonController.class).slash(person).toUri());
|
||||
return new ResponseEntity<PersonResource>(headers, HttpStatus.CREATED);
|
||||
----
|
||||
====
|
||||
|
||||
[[fundamentals.obtaining-links.builder.methods]]
|
||||
==== Building Links that Point to Methods
|
||||
|
||||
As of version 0.4, you can even build links that point to methods or create dummy controller method invocations. The first approach is to hand a `Method` instance to the `ControllerLinkBuilder`.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Method method = PersonController.class.getMethod("show", Long.class);
|
||||
Link link = linkTo(method, 2L).withSelfRel();
|
||||
|
||||
assertThat(link.getHref(), endsWith("/people/2")));
|
||||
----
|
||||
====
|
||||
|
||||
This is still a bit dissatisfying, as we have to first get a `Method` instance, which throws an exception and is generally quite cumbersome. At least we do not repeat the mapping. An even better approach is to have a dummy method invocation of the target method on a controller proxy, which we can create byi using the `methodOn(…)` helper.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Link link = linkTo(methodOn(PersonController.class).show(2L)).withSelfRel();
|
||||
assertThat(link.getHref(), endsWith("/people/2")));
|
||||
----
|
||||
====
|
||||
|
||||
`methodOn(…)` creates a proxy of the controller class that records the method invocation and exposes it in a proxy created for the return type of the method. This allows the fluent expression of the method for which we want to obtain the mapping. However, there are a few constraints on the methods that can be obtained byusing this technique:
|
||||
|
||||
* The return type has to be capable of proxying, as we need to expose the method invocation on it.
|
||||
* The parameters handed into the methods are generally neglected (except the ones referred to through `@PathVariable`, because they make up the URI).
|
||||
|
||||
[[fundamentals.obtaining-links.entity-links]]
|
||||
==== Using the `EntityLinks` Interface
|
||||
|
||||
So far, we have created links by pointing to the web-framework implementations (that is, the Spring MVC controllers) and inspected the mapping. In many cases, these classes essentially read and write representations backed by a model class.
|
||||
|
||||
The `EntityLinks` interface now exposes an API to look up a `Link` or `LinkBuilder` based on the model types. The methods essentially return links that point either to the collection resource (such as `/people`) or to a single resource (such as `/people/1`).
|
||||
The following example shows how to use `EntityLinks`:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
EntityLinks links = …;
|
||||
LinkBuilder builder = links.linkFor(CustomerResource.class);
|
||||
Link link = links.linkToSingleResource(CustomerResource.class, 1L);
|
||||
----
|
||||
====
|
||||
|
||||
`EntityLinks` is available for dependency injection by activating `@EnableEntityLinks` in your Spring MVC configuration. Activating this functionality causes all the Spring MVC controllers available in the current `ApplicationContext` to be inspected for the `@ExposesResourceFor(…)` annotation. The annotation exposes which model type the controller manages. Beyond that, we assume that you follow the URI mapping convention of a class level base mapping and assume that you have controller methods handling an appended `/{id}`. The following example shows an implementation of an `EntityLinks`-capable controller:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Controller
|
||||
@ExposesResourceFor(Order.class)
|
||||
@RequestMapping("/orders")
|
||||
class OrderController {
|
||||
|
||||
@RequestMapping
|
||||
ResponseEntity orders(…) { … }
|
||||
|
||||
@RequestMapping("/{id}")
|
||||
ResponseEntity order(@PathVariable("id") … ) { … }
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The controller exposes that it manages `Order` instances and exposes handler methods that are mapped to our convention. When youy enable `EntityLinks` through `@EnableEntityLinks` in your Spring MVC configuration, you can create links to the controller, as follows:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Controller
|
||||
class PaymentController {
|
||||
|
||||
@Autowired EntityLinks entityLinks;
|
||||
|
||||
@RequestMapping(…, method = HttpMethod.PUT)
|
||||
ResponseEntity payment(@PathVariable Long orderId) {
|
||||
|
||||
Link link = entityLinks.linkToSingleResource(Order.class, orderId);
|
||||
…
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
You can then refer to the `Order` instances without referring to the `OrderController`.
|
||||
|
||||
[[fundamentals.resource-assembler]]
|
||||
=== Resource Assembler
|
||||
|
||||
As the mapping from an entity to a resource type must be used in multiple places, it makes sense to create a dedicated class responsible for doing so. The conversion contains very custom steps but also a few boilerplate steps:
|
||||
|
||||
. Instantiation of the resource class
|
||||
. Adding a link with a `rel` of `self` pointing to the resource that gets rendered.
|
||||
|
||||
Spring Hateoas now provides a `ResourceAssemblerSupport` base class that helps reduce the amount of code you need to write.
|
||||
The following example shows how to use it:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
class PersonResourceAssembler extends ResourceAssemblerSupport<Person, PersonResource> {
|
||||
|
||||
public PersonResourceAssembler() {
|
||||
super(PersonController.class, PersonResource.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersonResource toResource(Person person) {
|
||||
|
||||
PersonResource resource = createResource(person);
|
||||
// … do further mapping
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Setting the class up as we did in the preceding example gives you the following benefits:
|
||||
|
||||
* There are a handful of `createResource(…)` methods that let you create an instance of the resource and have a `Link` with a rel of `self` added to it. The href of that link is determined by the configured controller's request mapping plus the ID of the `Identifiable` (for example, `/people/1`).
|
||||
* The resource type gets instantiated by reflection and expects a no-arg constructor. If you want to use a dedicated constructor or avoid the reflection performance overhead, you can override `instantiateResource(…)`.
|
||||
|
||||
You can then use the assembler to either assemble a single resource or an `Iterable` of the resources.
|
||||
The following example creates a list of `Person` resources:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Person person = new Person(…);
|
||||
Iterable<Person> people = Collections.singletonList(person);
|
||||
|
||||
PersonResourceAssembler assembler = new PersonResourceAssembler();
|
||||
PersonResource resource = assembler.toResource(person);
|
||||
List<PersonResource> resources = assembler.toResources(people);
|
||||
----
|
||||
====
|
||||
|
||||
[[configuration]]
|
||||
== Configuration
|
||||
@@ -334,168 +27,11 @@ This section describes how to configure Spring HATEOAS.
|
||||
[[configuration.at-enable]]
|
||||
=== Using `@EnableHypermediaSupport`
|
||||
|
||||
To let the `ResourceSupport` subtypes be rendered according to the specification of various hypermedia representations types, you can activate support for a particular hypermedia representation format through `@EnableHypermediaSupport`. The annotation takes a `HypermediaType` enumeration as its argument. Currently, we support http://tools.ietf.org/html/draft-kelly-json-hal[HAL] as well as a default rendering. Using the annotation triggers the following:
|
||||
To let the `RepresentationModel` subtypes be rendered according to the specification of various hypermedia representations types, you can activate support for a particular hypermedia representation format through `@EnableHypermediaSupport`. The annotation takes a `HypermediaType` enumeration as its argument. Currently, we support http://tools.ietf.org/html/draft-kelly-json-hal[HAL] as well as a default rendering. Using the annotation triggers the following:
|
||||
|
||||
* It registers necessary Jackson modules to render `Resource` and `Resources` in the hypermedia specific format.
|
||||
* It registers necessary Jackson modules to render `EntityModel` and `CollectionModel` in the hypermedia specific format.
|
||||
* If JSONPath is on the classpath, it automatically registers a `LinkDiscoverer` instance to look up links by their `rel` in plain JSON representations (see <<client.link-discoverer>>).
|
||||
* By default, it enables `@EnableEntityLinks` (see <<fundamentals.obtaining-links.entity-links>>) and automatically picks up `EntityLinks` implementations and bundles them into a `DelegatingEntityLinks` instance that you can autowire.
|
||||
* It automatically picks up all `RelProvider` implementations in the `ApplicationContext` and bundles them into a `DelegatingRelProvider` that you can autowire. It registers providers to consider `@Relation` on domain types as well as Spring MVC controllers. If the https://github.com/atteo/evo-inflector[EVO inflector] is on the classpath, collection `rel` values are derived by using the pluralizing algorithm implemented in the library (see <<spis.rel-provider>>).
|
||||
|
||||
|
||||
[[spis]]
|
||||
== SPIs
|
||||
|
||||
This section describes the service provider interfaces (SPIs) available in Spring HATEOAS.
|
||||
|
||||
[[spis.rel-provider]]
|
||||
=== Using the `RelProvider` API
|
||||
|
||||
When building links, you usually need to determine the relation type to be used for the link. In most cases, the relation type is directly associated with a (domain) type. We encapsulate the detailed algorithm to look up the relation types behind a `RelProvider` API that lets you determine the relation types for single and collection resources. The algorithm for looking up the relation type follows:
|
||||
|
||||
. If the type is annotated with `@Relation`, we use the values configured in the annotation.
|
||||
. If not, we default to the uncapitalized simple class name plus an appended `List` for the collection `rel`.
|
||||
. If the https://github.com/atteo/evo-inflector[EVO inflector] JAR is in the classpath, we use the plural of the single resource `rel` provided by the pluralizing algorithm.
|
||||
. `@Controller` classes annotated with `@ExposesResourceFor` (see <<fundamentals.obtaining-links.entity-links>> for details) transparently look up the relation types for the type configured in the annotation, so that you can use `relProvider.getSingleResourceRelFor(MyController.class)` and get the relation type of the domain type exposed.
|
||||
|
||||
A `RelProvider` is automatically exposed as a Spring bean when you use `@EnableHypermediaSupport`. You can plug in custom providers by implementing the interface and exposing them as Spring beans in turn.
|
||||
|
||||
[[spis.curie-provider]]
|
||||
=== Using the `CurieProvider` API
|
||||
|
||||
The http://tools.ietf.org/html/rfc5988=section-4[Web Linking RFC] describes registered and extension link relation types. Registered rels are well-known strings registered with the http://www.iana.org/assignments/link-relations/link-relations.xhtml[IANA registry of link relation types]. Extension `rel` URIs can be used by applications that do not wish to register a relation type. Each one is a URI that uniquely identifies the relation type. The `rel` URI can be serialized as a compact URI or http://www.w3.org/TR/curie[Curie]. For example, a curie of `ex:persons` stands for the link relation type `http://example.com/rels/persons` if `ex` is defined as `http://example.com/rels/{rel}`. If curies are used, the base URI must be present in the response scope.
|
||||
|
||||
The `rel` values created by the default `RelProvider` are extension relation types and, as a result, must be URIs, which can cause a lot of overhead. The `CurieProvider` API takes care of that: It lets you define a base URI as a URI template and a prefix that stands for that base URI. If a `CurieProvider` is present, the `RelProvider` prepends all `rel` values with the curie prefix. Furthermore a `curies` link is automatically added to the HAL resource.
|
||||
|
||||
The following configuration defines a default curie provider:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableHypermediaSupport(type= {HypermediaType.HAL})
|
||||
public class Config {
|
||||
|
||||
@Bean
|
||||
public CurieProvider curieProvider() {
|
||||
return new DefaultCurieProvider("ex", new UriTemplate("http://www.example.com/rels/{rel}"));
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Note that now the `ex:` prefix automatically appears before all rel values that are not registered with IANA, as in `ex:orders`. Clients can use the `curies` link to resolve a curie to its full form.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, json]
|
||||
----
|
||||
{
|
||||
_"links" : {
|
||||
"self" : { href: "http://myhost/person/1" },
|
||||
"curies" : {
|
||||
"name" : "ex",
|
||||
"href" : "http://example.com/rels/{rel}",
|
||||
"templated" : true
|
||||
},
|
||||
"ex:orders" : { href : "http://myhost/person/1/orders" }
|
||||
},
|
||||
"firstname" : "Dave",
|
||||
"lastname" : "Matthews"
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Since the purpose of the `CurieProvider` API is to allow for automatic curie creation, you can define only one `CurieProvider` bean per application scope.
|
||||
|
||||
[[client]]
|
||||
== Client-side Support
|
||||
|
||||
This section describes Spring HATEOAS's support for clients.
|
||||
|
||||
[[client.traverson]]
|
||||
=== Traverson
|
||||
|
||||
Spring HATEOAS provides an API for client-side service traversal. It is inspired by the https://blog.codecentric.de/en/2013/11/traverson/[Traverson JavaScript library].
|
||||
The following example shows how to use it:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("user", 27);
|
||||
|
||||
Traverson traverson = new Traverson(new URI("http://localhost:8080/api/"), MediaTypes.HAL_JSON);
|
||||
String name = traverson.follow("movies", "movie", "actor").
|
||||
withTemplateParameters(parameters).
|
||||
toObject("$.name");
|
||||
----
|
||||
====
|
||||
|
||||
You can set up a `Traverson` instance by pointing it to a REST server and configuring the media types you want to set as `Accept` headers. You can then define the relation names you want to discover and follow. Relation names can either be simple names or JSONPath expressions (starting with an `$`).
|
||||
|
||||
The sample then hands a parameter map into the execution. The parameters are used to expand URIs (which are templated) found during the traversal. The traversal is concluded by accessing the representation of the final traversal. In the preceding example, we evaluate a JSONPath expression to access the actor's name.
|
||||
|
||||
The preceding example is the simplest version of traversal, where the `rel` values are strings and, at each hop, the same template parameters are applied.
|
||||
|
||||
There are more options to customize template parameters at each level.
|
||||
The following example shows these options.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{baseDir}/src/test/java/org/springframework/hateoas/client/TraversonTest.java[tag=hop-with-param]
|
||||
----
|
||||
|
||||
The static `rel(...)` function is a convenient way to define a single `Hop`. Using `.withParameter(key, value)` makes it simple to specify URI template variables.
|
||||
|
||||
NOTE: `.withParameter()` returns a new `Hop` object that is chainable. You can string together as many `.withParameter` as you like. The result is a single `Hop` definition.
|
||||
The following example shows one way to do so:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{baseDir}/src/test/java/org/springframework/hateoas/client/TraversonTest.java[tag=hop-put]
|
||||
----
|
||||
====
|
||||
|
||||
You can also load an entire `Map` of parameters by using `.withParameters(Map)`.
|
||||
|
||||
NOTE: `follow()` is chainable, meaning you can string together multiple hops, as shown in the preceding examples. You can either put multiple string-based `rel` values (`follow("items", "item")`) or a single hop with specific parameters.
|
||||
|
||||
==== `Resource<T>` vs. `Resources<T>`
|
||||
|
||||
The examples shown so far demonstrate how to sidestep Java's type erasure and convert a single JSON-formatted resource into a `Resource<Item>` object. However, what if you get a collection like an _embedded HAL collection?
|
||||
You can do so with only one slight tweak, as the following example shows:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ParameterizedTypeReference<Resources<Item>> resourceParameterizedTypeReference =
|
||||
new ParameterizedTypeReference<Resources<Item>>() {};
|
||||
|
||||
Resources<Item> itemResource = traverson.//
|
||||
follow(rel("items")).//
|
||||
toObject(resourceParameterizedTypeReference);
|
||||
----
|
||||
====
|
||||
|
||||
Instead of fetching a single resource, this one deserializes a collection into `Resources`.
|
||||
|
||||
[[client.link-discoverer]]
|
||||
=== Using `LinkDiscoverer` Instances
|
||||
|
||||
When working with hypermedia enabled representations, a common task is to find a link with a particular relation type in it. Spring HATEOAS provides https://code.google.com/p/json-path[JSONPath]-based implementations of the `LinkDiscoverer` interface for either the default representation rendering or HAL out of the box. When using `@EnableHypermediaSupport`, we automatically expose an instance supporting the configured hypermedia type as a Spring bean.
|
||||
|
||||
Alternatively, you can setup and use an instance as follows:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
String content = "{'_links' : { 'foo' : { 'href' : '/foo/bar' }}}";
|
||||
LinkDiscoverer discoverer = new HalLinkDiscoverer();
|
||||
Link link = discoverer.findLinkWithRel("foo", content);
|
||||
|
||||
assertThat(link.getRel(), is("foo"));
|
||||
assertThat(link.getHref(), is("/foo/bar"));
|
||||
----
|
||||
====
|
||||
include::client.adoc[leveloffset=+1]
|
||||
|
||||
59
src/main/asciidoc/mediatypes.adoc
Normal file
59
src/main/asciidoc/mediatypes.adoc
Normal file
@@ -0,0 +1,59 @@
|
||||
[[mediatypes]]
|
||||
= Media types
|
||||
|
||||
[[mediatypes.hal]]
|
||||
== HAL – Hypertext Application Language
|
||||
|
||||
[[mediatypes.hal.curie-provider]]
|
||||
=== [[spis.curie-provider]] Using the `CurieProvider` API
|
||||
|
||||
The http://tools.ietf.org/html/rfc5988=section-4[Web Linking RFC] describes registered and extension link relation types. Registered rels are well-known strings registered with the http://www.iana.org/assignments/link-relations/link-relations.xhtml[IANA registry of link relation types]. Extension `rel` URIs can be used by applications that do not wish to register a relation type. Each one is a URI that uniquely identifies the relation type. The `rel` URI can be serialized as a compact URI or http://www.w3.org/TR/curie[Curie]. For example, a curie of `ex:persons` stands for the link relation type `http://example.com/rels/persons` if `ex` is defined as `http://example.com/rels/{rel}`. If curies are used, the base URI must be present in the response scope.
|
||||
|
||||
The `rel` values created by the default `RelProvider` are extension relation types and, as a result, must be URIs, which can cause a lot of overhead. The `CurieProvider` API takes care of that: It lets you define a base URI as a URI template and a prefix that stands for that base URI. If a `CurieProvider` is present, the `RelProvider` prepends all `rel` values with the curie prefix. Furthermore a `curies` link is automatically added to the HAL resource.
|
||||
|
||||
The following configuration defines a default curie provider:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableHypermediaSupport(type= {HypermediaType.HAL})
|
||||
public class Config {
|
||||
|
||||
@Bean
|
||||
public CurieProvider curieProvider() {
|
||||
return new DefaultCurieProvider("ex", new UriTemplate("http://www.example.com/rels/{rel}"));
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Note that now the `ex:` prefix automatically appears before all rel values that are not registered with IANA, as in `ex:orders`. Clients can use the `curies` link to resolve a curie to its full form.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, json]
|
||||
----
|
||||
{
|
||||
_"links" : {
|
||||
"self" : { href: "http://myhost/person/1" },
|
||||
"curies" : {
|
||||
"name" : "ex",
|
||||
"href" : "http://example.com/rels/{rel}",
|
||||
"templated" : true
|
||||
},
|
||||
"ex:orders" : { href : "http://myhost/person/1/orders" }
|
||||
},
|
||||
"firstname" : "Dave",
|
||||
"lastname" : "Matthews"
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Since the purpose of the `CurieProvider` API is to allow for automatic curie creation, you can define only one `CurieProvider` bean per application scope.
|
||||
|
||||
[[mediatypes.custom]]
|
||||
== Registering a custom media type
|
||||
|
||||
TODO
|
||||
@@ -1,10 +1,10 @@
|
||||
[migrate-to-1.0]
|
||||
[[migrate-to-1.0]]
|
||||
= Migrating to Spring HATEOAS 1.0
|
||||
|
||||
For 1.0 we took the chance to re-evaluate some of the design and package structure choices we had made for the 0.x branch.
|
||||
There had been an incredible amount of feedback on it and the major version bump seemed to be the most natural place to refactor those.
|
||||
|
||||
[migrate-to-1.0.changes]
|
||||
[[migrate-to-1.0.changes]]
|
||||
== The changes
|
||||
|
||||
The biggest changes in package structure were driven by the introduction of a hypermedia type registration API to support additional media types in Spring HATEOAS.
|
||||
@@ -13,6 +13,7 @@ This lead to the clear separation of client and server APIs (packages named resp
|
||||
The easiest way to get your code base upgraded to the new API is by using the <<migrate-to-1.0.script, migration script>>.
|
||||
Before we jump to that, here are the changes at a quick glance.
|
||||
|
||||
[[migrate-to-1.0.changes.representation-models]]
|
||||
=== Representation models
|
||||
|
||||
The `ResourceSupport`/`Resource`/`Resources`/`PagedResources` group of classes never really felt appropriately named.
|
||||
@@ -34,7 +35,7 @@ Also the name changes have been reflected in the classes contained in `TypeRefer
|
||||
* `ControllerLinkBuilder` has been moved into `server.mvc` and deprecated to be replaced by `WebMvcLinkBuilder`.
|
||||
* `VndError` has been moved to the `mediatype.vnderror` package.
|
||||
|
||||
[migrate-to-1.0.script]
|
||||
[[migrate-to-1.0.script]]
|
||||
== The migration script
|
||||
|
||||
You can find https://github.com/spring-projects/spring-hateoas/tree/master/etc[a script] to run from your application root that will update all import statements and static method references to Spring HATEOAS types that moved in our source code repository.
|
||||
@@ -43,6 +44,7 @@ By default it will inspect all Java source files and replace the legacy Spring H
|
||||
|
||||
.Sample application of the migration script
|
||||
====
|
||||
[source]
|
||||
----
|
||||
$ ./migrate-to-1.0.sh
|
||||
|
||||
@@ -55,4 +57,7 @@ Done!
|
||||
----
|
||||
====
|
||||
|
||||
Note that the script will not necessarily be able to entirely fix all changes, but it should cover the most important refactorings.
|
||||
|
||||
Now verify the changes made to the files in your favorite Git client and commit as appropriate.
|
||||
In case you find method or type references unmigrated, please open a ticket in out issue tracker.
|
||||
|
||||
236
src/main/asciidoc/server.adoc
Normal file
236
src/main/asciidoc/server.adoc
Normal file
@@ -0,0 +1,236 @@
|
||||
[[server]]
|
||||
= Server-side support
|
||||
|
||||
[[server.link-builder]]
|
||||
== [[fundamentals.obtaining-links]] [[fundamentals.obtaining-links.builder]] Building links
|
||||
|
||||
Now we have the domain vocabulary in place, but the main challenge remains: how to create the actual URIs to be wrapped into `Link` instances in a less fragile way. Right now, we would have to duplicate URI strings all over the place. Doing so is brittle and unmaintainable.
|
||||
|
||||
Assume you have your Spring MVC controllers implemented as follows:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Controller
|
||||
class PersonController {
|
||||
|
||||
@GetMapping("/people")
|
||||
HttpEntity<PersonModel> showAll() { … }
|
||||
|
||||
@GetMapping(value = "/{person}", method = RequestMethod.GET)
|
||||
HttpEntity<PersonModel> show(@PathVariable Long person) { … }
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
We see two conventions here. The first is a collection resource that is exposed through `@GetMapping` annotation of the controller method, with individual elements of that collection exposed as direct sub resources. The collection resource might be exposed at a simple URI (as just shown) or more complex ones (such as `/people/{id}/addresses`). Suppose you would like to link to the collection resource of all people. Following the approach from up above would cause two problems:
|
||||
|
||||
* To create an absolute URI, you would need to look up the protocol, hostname, port, servlet base, and other values. This is cumbersome and requires ugly manual string concatenation code.
|
||||
* You probably do not want to concatenate the `/people` on top of your base URI, because you would then have to maintain the information in multiple places. If you change the mapping, you then have to change all the clients pointing to it.
|
||||
|
||||
Spring HATEOAS now provides a `WebMvcLinkBuilder` that lets you create links by pointing to controller classes.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
import static org.sfw.hateoas.server.mvc.WebMvcLinkBuilder.*;
|
||||
|
||||
Link link = linkTo(PersonController.class).withRel("people");
|
||||
|
||||
assertThat(link.getRel()).isEqualTo(LinkRelation.of("people"));
|
||||
assertThat(link.getHref()).endsWith("/people");
|
||||
----
|
||||
====
|
||||
|
||||
The `WebMvcLinkBuilder` uses Spring's `ServletUriComponentsBuilder` under the hood to obtain the basic URI information from the current request. Assuming your application runs at `http://localhost:8080/your-app`, this is exactly the URI on top of which you are constructing additional parts. The builder now inspects the given controller class for its root mapping and, thus, ends up with `http://localhost:8080/your-app/people`. You can also build more nested links as well.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Person person = new Person(1L, "Dave", "Matthews");
|
||||
// /person / 1
|
||||
Link link = linkTo(PersonController.class).slash(person.getId()).withSelfRel();
|
||||
assertThat(link.getRel(), is(IanaLinkRelation.SELF.value()));
|
||||
assertThat(link.getHref(), endsWith("/people/1"));
|
||||
----
|
||||
====
|
||||
|
||||
The builder also allows creating URI instances to build up (for example, response header values):
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setLocation(linkTo(PersonController.class).slash(person).toUri());
|
||||
|
||||
return new ResponseEntity<PersonModel>(headers, HttpStatus.CREATED);
|
||||
----
|
||||
====
|
||||
|
||||
[[fundamentals.obtaining-links.builder.methods]]
|
||||
==== Building Links that Point to Methods
|
||||
|
||||
As of version 0.4, you can even build links that point to methods or create dummy controller method invocations. The first approach is to hand a `Method` instance to the `WebMvcLinkBuilder`.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Method method = PersonController.class.getMethod("show", Long.class);
|
||||
Link link = linkTo(method, 2L).withSelfRel();
|
||||
|
||||
assertThat(link.getHref()).endsWith("/people/2"));
|
||||
----
|
||||
====
|
||||
|
||||
This is still a bit dissatisfying, as we have to first get a `Method` instance, which throws an exception and is generally quite cumbersome. At least we do not repeat the mapping. An even better approach is to have a dummy method invocation of the target method on a controller proxy, which we can create byi using the `methodOn(…)` helper.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Link link = linkTo(methodOn(PersonController.class).show(2L)).withSelfRel();
|
||||
|
||||
assertThat(link.getHref()).endsWith("/people/2");
|
||||
----
|
||||
====
|
||||
|
||||
`methodOn(…)` creates a proxy of the controller class that records the method invocation and exposes it in a proxy created for the return type of the method. This allows the fluent expression of the method for which we want to obtain the mapping. However, there are a few constraints on the methods that can be obtained byusing this technique:
|
||||
|
||||
* The return type has to be capable of proxying, as we need to expose the method invocation on it.
|
||||
* The parameters handed into the methods are generally neglected (except the ones referred to through `@PathVariable`, because they make up the URI).
|
||||
|
||||
[[server.link-builder.webmvc]]
|
||||
== Building links in Spring MVC
|
||||
|
||||
[[server.link-builder.webflux]]
|
||||
== Building links in Spring WebFlux
|
||||
|
||||
TODO
|
||||
|
||||
[[server.entity-links]]
|
||||
== [[fundamentals.obtaining-links.entity-links]] Using the `EntityLinks` interface
|
||||
|
||||
So far, we have created links by pointing to the web-framework implementations (that is, the Spring MVC controllers) and inspected the mapping. In many cases, these classes essentially read and write representations backed by a model class.
|
||||
|
||||
The `EntityLinks` interface now exposes an API to look up a `Link` or `LinkBuilder` based on the model types. The methods essentially return links that point either to the collection resource (such as `/people`) or to a single resource (such as `/people/1`).
|
||||
The following example shows how to use `EntityLinks`:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
EntityLinks links = …;
|
||||
LinkBuilder builder = links.linkFor(Customer.class);
|
||||
Link link = links.linkToItemResource(Customer.class, 1L);
|
||||
----
|
||||
====
|
||||
|
||||
`EntityLinks` is available for dependency injection by activating either `@EnableHypermediaSupprt` or `@EnableEntityLinks` in your Spring MVC configuration. Activating this functionality causes all the Spring MVC controllers available in the current `ApplicationContext` to be inspected for the `@ExposesResourceFor(…)` annotation. The annotation exposes which model type the controller manages. Beyond that, we assume that you follow the URI mapping convention of a class level base mapping and assume that you have controller methods handling an appended `/{id}`. The following example shows an implementation of an `EntityLinks`-capable controller:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Controller
|
||||
@ExposesResourceFor(Order.class)
|
||||
class OrderController {
|
||||
|
||||
@GetMapping("/orders")
|
||||
ResponseEntity orders(…) { … }
|
||||
|
||||
@GetMapping("/{id}")
|
||||
ResponseEntity order(@PathVariable("id") … ) { … }
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The controller exposes that it manages `Order` instances and exposes handler methods that are mapped to our convention. When youy enable `EntityLinks` through `@EnableEntityLinks` in your Spring MVC configuration, you can create links to the controller, as follows:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Controller
|
||||
class PaymentController {
|
||||
|
||||
private final EntityLinks entityLinks;
|
||||
|
||||
PaymentController(EntityLinks entityLinks) {
|
||||
this.entityLinks = entityLinks;
|
||||
}
|
||||
|
||||
@PutMapping(…)
|
||||
ResponseEntity payment(@PathVariable Long orderId) {
|
||||
|
||||
Link link = entityLinks.linkToItemResource(Order.class, orderId);
|
||||
…
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
You can then refer to the `Order` instances without referring to the `OrderController`.
|
||||
|
||||
|
||||
[[server.representation-model-assembler]]
|
||||
== [[fundamentals.resource-assembler]] Representation model assembler
|
||||
|
||||
As the mapping from an entity to a representation model must be used in multiple places, it makes sense to create a dedicated class responsible for doing so. The conversion contains very custom steps but also a few boilerplate steps:
|
||||
|
||||
. Instantiation of the model class
|
||||
. Adding a link with a `rel` of `self` pointing to the resource that gets rendered.
|
||||
|
||||
Spring HATEOAS now provides a `RepresentationModelAssemblerSupport` base class that helps reduce the amount of code you need to write.
|
||||
The following example shows how to use it:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
class PersonModelAssembler extends RepresentationModelAssemblerSupport<Person, PersonModel> {
|
||||
|
||||
public PersonModelAssembler() {
|
||||
super(PersonController.class, PersonModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersonModel toModel(Person person) {
|
||||
|
||||
PersonModel resource = createResource(person);
|
||||
// … do further mapping
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Setting the class up as we did in the preceding example gives you the following benefits:
|
||||
|
||||
* There are a handful of `createModelWithId(…)` methods that let you create an instance of the resource and have a `Link` with a rel of `self` added to it. The href of that link is determined by the configured controller's request mapping plus the ID of the entity (for example, `/people/1`).
|
||||
* The resource type gets instantiated by reflection and expects a no-arg constructor. If you want to use a dedicated constructor or avoid the reflection performance overhead, you can override `instantiateModel(…)`.
|
||||
|
||||
You can then use the assembler to either assemble a `RepresentationModel` or a `CollectionModel`.
|
||||
The following example creates a `CollectionModel` of `PersonModel` instances:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
Person person = new Person(…);
|
||||
Iterable<Person> people = Collections.singletonList(person);
|
||||
|
||||
PersonModelAssembler assembler = new PersonModelAssembler();
|
||||
PersonModel model = assembler.toModel(person);
|
||||
CollectionModel<PersonModel> model = assembler.toCollectionModel(people);
|
||||
----
|
||||
====
|
||||
|
||||
[[server.rel-provider]]
|
||||
== [[spis.rel-provider]] Using the `RelProvider` API
|
||||
|
||||
When building links, you usually need to determine the relation type to be used for the link. In most cases, the relation type is directly associated with a (domain) type. We encapsulate the detailed algorithm to look up the relation types behind a `RelProvider` API that lets you determine the relation types for single and collection resources. The algorithm for looking up the relation type follows:
|
||||
|
||||
. If the type is annotated with `@Relation`, we use the values configured in the annotation.
|
||||
. If not, we default to the uncapitalized simple class name plus an appended `List` for the collection `rel`.
|
||||
. If the https://github.com/atteo/evo-inflector[EVO inflector] JAR is in the classpath, we use the plural of the single resource `rel` provided by the pluralizing algorithm.
|
||||
. `@Controller` classes annotated with `@ExposesResourceFor` (see <<fundamentals.obtaining-links.entity-links>> for details) transparently look up the relation types for the type configured in the annotation, so that you can use `relProvider.getItemResourceRelFor(MyController.class)` and get the relation type of the domain type exposed.
|
||||
|
||||
A `RelProvider` is automatically exposed as a Spring bean when you use `@EnableHypermediaSupport`. You can plug in custom providers by implementing the interface and exposing them as Spring beans in turn.
|
||||
Reference in New Issue
Block a user