#837 - Add more media types.
Introduce `src/docs/resources` and add `{resource-dir}` as an asciidoctor variable.
Add content for HAL-FORMS, Collection+JSON, and UBER.
This commit is contained in:
13
pom.xml
13
pom.xml
@@ -280,6 +280,7 @@
|
||||
<sectids>false</sectids>
|
||||
<attributes>
|
||||
<code-dir>${basedir}/src/docs/java/org/springframework/hateoas</code-dir>
|
||||
<resource-dir>${basedir}/src/docs/resources/org/springframework/hateoas</resource-dir>
|
||||
<docinfo>shared</docinfo>
|
||||
<linkcss>true</linkcss>
|
||||
<icons>font</icons>
|
||||
@@ -698,6 +699,18 @@
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>add-docs-resources</id>
|
||||
<phase>generate-test-resources</phase>
|
||||
<goals>
|
||||
<goal>add-test-resource</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<resources>
|
||||
<resource>src/docs/resources</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
// tag::code[]
|
||||
@Configuration
|
||||
@EnableHypermediaSupport(type = HypermediaType.COLLECTION_JSON)
|
||||
public class CollectionJsonApplication {
|
||||
|
||||
}
|
||||
// end::code[]
|
||||
@@ -117,10 +117,10 @@ public class EmployeeController {
|
||||
// Return the affordance + a link back to the entire collection resource.
|
||||
return new EntityModel<>(EMPLOYEES.get(id), //
|
||||
findOneLink //
|
||||
.andAffordance(afford(methodOn(controllerClass) //
|
||||
.updateEmployee(null, id))) // <2>
|
||||
.andAffordance(afford(methodOn(controllerClass) //
|
||||
.partiallyUpdateEmployee(null, id)))); // <3>
|
||||
.andAffordance( //
|
||||
afford(methodOn(controllerClass).updateEmployee(null, id))) // <2>
|
||||
.andAffordance( //
|
||||
afford(methodOn(controllerClass).partiallyUpdateEmployee(null, id)))); // <3>
|
||||
}
|
||||
// end::get[]
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
// tag::code[]
|
||||
@Configuration
|
||||
@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)
|
||||
public class HalFormsApplication {
|
||||
|
||||
}
|
||||
// end::code[]
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
|
||||
import org.springframework.hateoas.mediatype.hal.HalConfiguration.RenderSingleLinks;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class SampleAppConfiguration {
|
||||
|
||||
// tag::1[]
|
||||
@Bean
|
||||
public HalConfiguration globalPolicy() {
|
||||
return new HalConfiguration() //
|
||||
.withRenderSingleLinks(RenderSingleLinks.AS_ARRAY); // <1>
|
||||
}
|
||||
// end::1[]
|
||||
|
||||
// tag::2[]
|
||||
@Bean
|
||||
public HalConfiguration linkRelationBasedPolicy() {
|
||||
return new HalConfiguration() //
|
||||
.withRenderSingleLinksFor( //
|
||||
IanaLinkRelations.ITEM, RenderSingleLinks.AS_ARRAY) // <1>
|
||||
.withRenderSingleLinksFor( //
|
||||
LinkRelation.of("prev"), RenderSingleLinks.AS_SINGLE); // <2>
|
||||
}
|
||||
// end::2[]
|
||||
|
||||
// tag::3[]
|
||||
@Bean
|
||||
public HalConfiguration patternBasedPolicy() {
|
||||
return new HalConfiguration() //
|
||||
.withRenderSingleLinksFor( //
|
||||
"http*", RenderSingleLinks.AS_ARRAY); // <1>
|
||||
}
|
||||
// end::3[]
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
|
||||
|
||||
/**
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
// tag::code[]
|
||||
@Configuration
|
||||
@EnableHypermediaSupport(type = HypermediaType.UBER)
|
||||
public class UberApplication {
|
||||
|
||||
}
|
||||
// end::code[]
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"collection": {
|
||||
"version": "1.0",
|
||||
"href": "http://example.org/friends/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"collection": {
|
||||
"version": "1.0",
|
||||
"href": "http://example.org/friends/",
|
||||
"links": [
|
||||
{
|
||||
"rel": "feed",
|
||||
"href": "http://example.org/friends/rss"
|
||||
}
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
"href": "http://example.org/friends/jdoe",
|
||||
"data": [
|
||||
{
|
||||
"name": "fullname",
|
||||
"value": "J. Doe",
|
||||
"prompt": "Full Name"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"value": "jdoe@example.org",
|
||||
"prompt": "Email"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"rel": "blog",
|
||||
"href": "http://examples.org/blogs/jdoe",
|
||||
"prompt": "Blog"
|
||||
},
|
||||
{
|
||||
"rel": "avatar",
|
||||
"href": "http://examples.org/images/jdoe",
|
||||
"prompt": "Avatar",
|
||||
"render": "image"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "http://example.org/friends/msmith",
|
||||
"data": [
|
||||
{
|
||||
"name": "fullname",
|
||||
"value": "M. Smith",
|
||||
"prompt": "Full Name"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"value": "msmith@example.org",
|
||||
"prompt": "Email"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"rel": "blog",
|
||||
"href": "http://examples.org/blogs/msmith",
|
||||
"prompt": "Blog"
|
||||
},
|
||||
{
|
||||
"rel": "avatar",
|
||||
"href": "http://examples.org/images/msmith",
|
||||
"prompt": "Avatar",
|
||||
"render": "image"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"href": "http://example.org/friends/rwilliams",
|
||||
"data": [
|
||||
{
|
||||
"name": "fullname",
|
||||
"value": "R. Williams",
|
||||
"prompt": "Full Name"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"value": "rwilliams@example.org",
|
||||
"prompt": "Email"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"rel": "blog",
|
||||
"href": "http://examples.org/blogs/rwilliams",
|
||||
"prompt": "Blog"
|
||||
},
|
||||
{
|
||||
"rel": "avatar",
|
||||
"href": "http://examples.org/images/rwilliams",
|
||||
"prompt": "Avatar",
|
||||
"render": "image"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"queries": [
|
||||
{
|
||||
"rel": "search",
|
||||
"href": "http://example.org/friends/search",
|
||||
"prompt": "Search",
|
||||
"data": [
|
||||
{
|
||||
"name": "search",
|
||||
"value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"template": {
|
||||
"data": [
|
||||
{
|
||||
"name": "fullname",
|
||||
"value": "",
|
||||
"prompt": "Full Name"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"value": "",
|
||||
"prompt": "Email"
|
||||
},
|
||||
{
|
||||
"name": "blog",
|
||||
"value": "",
|
||||
"prompt": "Blog"
|
||||
},
|
||||
{
|
||||
"name": "avatar",
|
||||
"value": "",
|
||||
"prompt": "Avatar"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"collection": {
|
||||
"version": "1.0",
|
||||
"href": "http://example.org/friends/", // <1>
|
||||
"links": [ // <2>
|
||||
{
|
||||
"rel": "feed",
|
||||
"href": "http://example.org/friends/rss"
|
||||
},
|
||||
{
|
||||
"rel": "queries",
|
||||
"href": "http://example.org/friends/?queries"
|
||||
},
|
||||
{
|
||||
"rel": "template",
|
||||
"href": "http://example.org/friends/?template"
|
||||
}
|
||||
],
|
||||
"items": [ // <3>
|
||||
{
|
||||
"href": "http://example.org/friends/jdoe",
|
||||
"data": [ // <4>
|
||||
{
|
||||
"name": "fullname",
|
||||
"value": "J. Doe",
|
||||
"prompt": "Full Name"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"value": "jdoe@example.org",
|
||||
"prompt": "Email"
|
||||
}
|
||||
],
|
||||
"links": [ // <5>
|
||||
{
|
||||
"rel": "blog",
|
||||
"href": "http://examples.org/blogs/jdoe",
|
||||
"prompt": "Blog"
|
||||
},
|
||||
{
|
||||
"rel": "avatar",
|
||||
"href": "http://examples.org/images/jdoe",
|
||||
"prompt": "Avatar",
|
||||
"render": "image"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"collection": {
|
||||
"version": "1.0",
|
||||
"href": "http://example.org/friends/",
|
||||
"queries": [
|
||||
{
|
||||
"rel": "search",
|
||||
"href": "http://example.org/friends/search",
|
||||
"prompt": "Search",
|
||||
"data": [
|
||||
{
|
||||
"name": "search",
|
||||
"value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"collection": {
|
||||
"version": "1.0",
|
||||
"href": "http://example.org/friends/",
|
||||
"template": {
|
||||
"data": [
|
||||
{
|
||||
"name": "full-name",
|
||||
"value": "",
|
||||
"prompt": "Full Name"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"value": "",
|
||||
"prompt": "Email"
|
||||
},
|
||||
{
|
||||
"name": "blog",
|
||||
"value": "",
|
||||
"prompt": "Blog"
|
||||
},
|
||||
{
|
||||
"name": "avatar",
|
||||
"value": "",
|
||||
"prompt": "Avatar"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"collection": {
|
||||
"version": "1.0",
|
||||
"href": "http://example.org/friends/",
|
||||
"error": {
|
||||
"title": "Server Error",
|
||||
"code": "X1C2",
|
||||
"message": "The server have encountered an error, please wait and try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"template": {
|
||||
"data": [
|
||||
{
|
||||
"name": "full-name",
|
||||
"value": "W. Chandry"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"value": "wchandry@example.org"
|
||||
},
|
||||
{
|
||||
"name": "blog",
|
||||
"value": "http://example.org/blogs/wchandry"
|
||||
},
|
||||
{
|
||||
"name": "avatar",
|
||||
"value": "http://example.org/images/wchandry"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"firstName" : "Frodo",
|
||||
"lastName" : "Baggins",
|
||||
"role" : "ring bearer",
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "http://localhost:8080/employees/1"
|
||||
}
|
||||
},
|
||||
"_templates" : {
|
||||
"default" : {
|
||||
"title" : null,
|
||||
"method" : "put",
|
||||
"contentType" : "",
|
||||
"properties" : [ {
|
||||
"name" : "firstName",
|
||||
"required" : true
|
||||
}, {
|
||||
"name" : "lastName",
|
||||
"required" : true
|
||||
}, {
|
||||
"name" : "role",
|
||||
"required" : true
|
||||
} ]
|
||||
},
|
||||
"partiallyUpdateEmployee" : {
|
||||
"title" : null,
|
||||
"method" : "patch",
|
||||
"contentType" : "",
|
||||
"properties" : [ {
|
||||
"name" : "firstName",
|
||||
"required" : false
|
||||
}, {
|
||||
"name" : "lastName",
|
||||
"required" : false
|
||||
}, {
|
||||
"name" : "role",
|
||||
"required" : false
|
||||
} ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"firstName" : "Frodo",
|
||||
"lastName" : "Baggins",
|
||||
"role" : "ring bearer",
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "http://localhost:8080/employees/1"
|
||||
}
|
||||
},
|
||||
"_templates" : {
|
||||
"default" : {
|
||||
"title" : null,
|
||||
"method" : "put",
|
||||
"contentType" : "",
|
||||
"properties" : [ {
|
||||
"name" : "firstName",
|
||||
"required" : true
|
||||
}, {
|
||||
"name" : "lastName",
|
||||
"required" : true
|
||||
}, {
|
||||
"name" : "role",
|
||||
"required" : true
|
||||
} ]
|
||||
},
|
||||
"partiallyUpdateEmployee" : {
|
||||
"title" : null,
|
||||
"method" : "patch",
|
||||
"contentType" : "",
|
||||
"properties" : [ {
|
||||
"name" : "firstName",
|
||||
"required" : false
|
||||
}, {
|
||||
"name" : "lastName",
|
||||
"required" : false
|
||||
}, {
|
||||
"name" : "role",
|
||||
"required" : false
|
||||
} ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"_links": {
|
||||
"item": [
|
||||
{ "href": "http://myhost/cart/42" },
|
||||
{ "href": "http://myhost/inventory/12" }
|
||||
]
|
||||
},
|
||||
"customer": "Dave Matthews"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"_links": {
|
||||
"item": [{ "href": "http://myhost/inventory/12" }]
|
||||
},
|
||||
"customer": "Dave Matthews"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"_links": {
|
||||
"item": { "href": "http://myhost/inventory/12" }
|
||||
},
|
||||
"customer": "Dave Matthews"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"_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"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"uber" : {
|
||||
"version" : "1.0",
|
||||
"data" : [ {
|
||||
"rel" : [ "self" ],
|
||||
"url" : "/employees/1"
|
||||
}, {
|
||||
"name" : "employee",
|
||||
"data" : [ {
|
||||
"name" : "role",
|
||||
"value" : "ring bearer"
|
||||
}, {
|
||||
"name" : "name",
|
||||
"value" : "Frodo"
|
||||
} ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ This allows clients to turn parameterized templates into URIs without having to
|
||||
|
||||
.Using links with templated URIs
|
||||
====
|
||||
[source, java, indent=0]
|
||||
[source, java, indent=0, tabsize=2]
|
||||
----
|
||||
include::{code-dir}/FundamentalsTest.java[tags=templatedLinks]
|
||||
----
|
||||
@@ -211,7 +211,7 @@ The following code shows how to take a *self* link and associate two more afford
|
||||
|
||||
.Connecting affordances to `GET /employees/{id}`
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[source, java, indent=0, tabsize=2]
|
||||
----
|
||||
include::{code-dir}/EmployeeController.java[tag=get]
|
||||
----
|
||||
@@ -226,7 +226,7 @@ Imagine that the related methods *afforded* above looking like this:
|
||||
|
||||
.`updateEmpoyee` method that responds to `PUT /employees/{id}`
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[source, java, indent=0, tabsize=2]
|
||||
----
|
||||
include::{code-dir}/EmployeeController.java[tag=put]
|
||||
----
|
||||
@@ -234,7 +234,7 @@ include::{code-dir}/EmployeeController.java[tag=put]
|
||||
|
||||
.`partiallyUpdateEmployee` method that responds to `PATCH /employees/{id}`
|
||||
====
|
||||
[source,java,indent=0]
|
||||
[source, java, indent=0, tabsize=2]
|
||||
----
|
||||
include::{code-dir}/EmployeeController.java[tag=patch]
|
||||
----
|
||||
@@ -271,50 +271,9 @@ The same resource above will render the following HAL-FORMS document:
|
||||
|
||||
.HAL-FORMS document with affordances
|
||||
====
|
||||
[source,json]
|
||||
[source, json, tabsize=2]
|
||||
----
|
||||
{
|
||||
"firstName" : "Frodo",
|
||||
"lastName" : "Baggins",
|
||||
"role" : "ring bearer",
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "http://localhost:8080/employees/1"
|
||||
}
|
||||
},
|
||||
"_templates" : { // <1>
|
||||
"default" : {
|
||||
"title" : null,
|
||||
"method" : "put", // <2>
|
||||
"contentType" : "",
|
||||
"properties" : [ { // <3>
|
||||
"name" : "firstName",
|
||||
"required" : true // <4>
|
||||
}, {
|
||||
"name" : "lastName",
|
||||
"required" : true
|
||||
}, {
|
||||
"name" : "role",
|
||||
"required" : true
|
||||
} ]
|
||||
},
|
||||
"partiallyUpdateEmployee" : { // <5>
|
||||
"title" : null,
|
||||
"method" : "patch", // <6>
|
||||
"contentType" : "",
|
||||
"properties" : [ {
|
||||
"name" : "firstName",
|
||||
"required" : false // <7>
|
||||
}, {
|
||||
"name" : "lastName",
|
||||
"required" : false
|
||||
}, {
|
||||
"name" : "role",
|
||||
"required" : false
|
||||
} ]
|
||||
}
|
||||
}
|
||||
}
|
||||
include::{resource-dir}/docs/mediatype/hal/forms/hal-forms-sample-with-notes.json[]
|
||||
----
|
||||
<1> The `_templates` attribute provided by HAL-FORMS with affordance-based information.
|
||||
<2> The `updateEmployee` method's `@PutMapping` annotation is translated to `put`.
|
||||
@@ -330,7 +289,7 @@ attributes), includes enough extra details for full interaction with the resourc
|
||||
|
||||
In fact, this type of document makes it easy to write custom client-side code to generate an HTML form:
|
||||
|
||||
[source,html]
|
||||
[source, html, tabsize=2]
|
||||
----
|
||||
<form method="put" action="http://localhost:8080/employees/1">
|
||||
<input type="text" id="firstName" name="firstName"/>
|
||||
|
||||
@@ -1,9 +1,96 @@
|
||||
[[mediatypes]]
|
||||
= Media types
|
||||
:code-dir: ../../../src/docs/java/org/springframework/hateoas
|
||||
:resource-dir: ../../../src/docs/resources/org/springframework/hateoas
|
||||
|
||||
[[mediatypes.hal]]
|
||||
== HAL – Hypertext Application Language
|
||||
|
||||
https://tools.ietf.org/html/draft-kelly-json-hal-08[JSON Hypertext Application Language] or HAL is one of the simplest
|
||||
and most widely adopted hypermedia media types adopted when not discussing specific web stacks.
|
||||
|
||||
It was the first spec-based media type adopted by Spring HATEAOS.
|
||||
|
||||
[[mediatypes.hal.configuration]]
|
||||
=== Configuring link rendering
|
||||
|
||||
In HAL, the `_links` entry is a JSON object. The property names are <<fundamentals.link-relations,link relations>> and
|
||||
each value is either https://tools.ietf.org/html/draft-kelly-json-hal-07#section-4.1.1[a link object or an array of link objects].
|
||||
|
||||
For a given link relation that has two or more links, the spec is clear on representation:
|
||||
|
||||
.HAL document with two links associated with one relation
|
||||
====
|
||||
[source, json, tabsize=2]
|
||||
----
|
||||
include::{resource-dir}/docs/mediatype/hal/hal-multiple-entry-link-relation.json[]
|
||||
----
|
||||
====
|
||||
|
||||
But if there is only one link for a given relation, the spec is ambiguous. You could render that as either a single object
|
||||
or as 1-item array.
|
||||
|
||||
By default, Spring HATEOAS uses the most terse approach and renders a single-link relation like this:
|
||||
|
||||
.HAL document with single link rendered as an object
|
||||
====
|
||||
[source, json, tabsize=2]
|
||||
----
|
||||
include::{resource-dir}/docs/mediatype/hal/hal-single-entry-link-relation-object.json[]
|
||||
----
|
||||
====
|
||||
|
||||
Some users prefer to not switch between arrays and objects when consuming HAL. They would prefer this type of rendering:
|
||||
|
||||
.HAL with single link rendered as an array
|
||||
====
|
||||
[source, json, tabsize=2]
|
||||
----
|
||||
include::{resource-dir}/docs/mediatype/hal/hal-single-entry-link-relation-array.json[]
|
||||
----
|
||||
====
|
||||
|
||||
If you wish to customize this policy, all you have to do is inject a `HalConfiguration` bean into your application configuration.
|
||||
There are multiple choices.
|
||||
|
||||
.Global HAL single-link rendering policy
|
||||
====
|
||||
[source, java, indent=0, tabsize=2]
|
||||
----
|
||||
include::{code-dir}/SampleAppConfiguration.java[tag=1]
|
||||
----
|
||||
<1> Override Spring HATEOAS's default by rendering ALL single-link relations as arrays.
|
||||
====
|
||||
|
||||
If you prefer to only override some particular link relations, you can create a `HalConfiguration`
|
||||
bean like this:
|
||||
|
||||
.Link relation-based HAL single-link rendering policy
|
||||
====
|
||||
[source, java, indent=0, tabsize=2]
|
||||
----
|
||||
include::{code-dir}/SampleAppConfiguration.java[tag=2]
|
||||
----
|
||||
<1> Always render `item` link relations as an array.
|
||||
<2> Render `prev` link relations as an object when there is only one link.
|
||||
====
|
||||
|
||||
If neither of these match your needs, you can use an Ant-style path patterns:
|
||||
|
||||
.Pattern-based HAL single-link rendering policy
|
||||
====
|
||||
[source,java,indent=0,tabsize=2]
|
||||
----
|
||||
include::{code-dir}/SampleAppConfiguration.java[tag=3]
|
||||
----
|
||||
<1> Render all link relations that start with `http` as an array.
|
||||
|
||||
NOTE: The pattern-based approach uses Spring's `AntPathMatcher`.
|
||||
====
|
||||
|
||||
All of these `HalConfiguration` withers can be combined to form one comprehensive policy. Be sure to test your API
|
||||
extensively to avoid surprises.
|
||||
|
||||
[[mediatypes.hal.curie-provider]]
|
||||
=== [[spis.curie-provider]] Using the `CurieProvider` API
|
||||
|
||||
@@ -33,26 +120,151 @@ Note that now the `ex:` prefix automatically appears before all rel values that
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, json]
|
||||
[source, json, tabsize=2]
|
||||
----
|
||||
{
|
||||
_"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"
|
||||
}
|
||||
include::{resource-dir}/docs/mediatype/hal/hal-with-curies.json[]
|
||||
----
|
||||
====
|
||||
|
||||
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.hal-forms]]
|
||||
== HAL-FORMS
|
||||
|
||||
https://rwcbook.github.io/hal-forms/[HAL-FORMS] is designed to add runtime FORM support to the <<mediatypes.hal,HAL media type>>.
|
||||
|
||||
[quote, Mike Amundsen, HAL-FORMS spec]
|
||||
____
|
||||
HAL-FORMS "looks like HAL." However, it is important to keep in mind that HAL-FORMS is not the same as HAL — the two
|
||||
should not be thought of as interchangeable in any way.
|
||||
____
|
||||
|
||||
To enable this media type, put the following configuration in your code:
|
||||
|
||||
.HAL-FORMS enabled application
|
||||
====
|
||||
[source, java, tabsize=2]
|
||||
----
|
||||
include::{code-dir}/HalFormsApplication.java[tag=code]
|
||||
----
|
||||
====
|
||||
|
||||
Anytime a client supplies an `Accept` header with `application/prs.hal-forms+json`, you can expect something like this:
|
||||
|
||||
.HAL-FORMS sample document
|
||||
====
|
||||
[source, json, tabsize=2]
|
||||
----
|
||||
include::{resource-dir}/docs/mediatype/hal/forms/hal-forms-sample.json[]
|
||||
----
|
||||
====
|
||||
|
||||
Checkout the https://rwcbook.github.io/hal-forms/[HAL-FORMS spec] to understand the details of the *_templates* attribute.
|
||||
Read about the <<fundamentals.affordances,Affordances API>> to augment your controllers with this extra metadata.
|
||||
|
||||
As for single-item (`EntityModel`) and aggregate root collections (`CollectionModel`), Spring HATEOAS renders them
|
||||
identically to <<mediatypes.hal,HAL documents>>.
|
||||
|
||||
[[mediatypes.collection-json]]
|
||||
== Collection+JSON
|
||||
|
||||
http://amundsen.com/media-types/collection/format/[Collection+JSON] is a JSON spec registered with IANA-approved media type `application/vnd.collection+json`.
|
||||
|
||||
[quote, Mike Amundsen, Collection+JSON spec]
|
||||
____
|
||||
http://amundsen.com/media-types/collection/[Collection+JSON] is a JSON-based read/write hypermedia-type designed to support
|
||||
management and querying of simple collections.
|
||||
____
|
||||
|
||||
Collection+JSON provides a uniform way to represent both single item resources as well as collections.
|
||||
|
||||
To enable this media type, put the following configuration in your code:
|
||||
|
||||
.Collection+JSON enabled application
|
||||
====
|
||||
[source, java, tabsize=2]
|
||||
----
|
||||
include::{code-dir}/CollectionJsonApplication.java[tag=code]
|
||||
----
|
||||
====
|
||||
|
||||
This configuration will make your application respond to requests that have an `Accept` header of `application/vnd.collection+json`
|
||||
as shown below.
|
||||
|
||||
The following example from the spec shows a single item:
|
||||
|
||||
.Collection+JSON single item example
|
||||
====
|
||||
[source, json, tabsize=2]
|
||||
----
|
||||
include::{resource-dir}/docs/mediatype/collectionjson/spec-part3.json[]
|
||||
----
|
||||
<1> The `self` link is stored in the document's `href` attribute.
|
||||
<2> The document's top `links` section contains collection-level links (minus the `self` link).
|
||||
<3> The `items` section contains a collection of data. Since this is a single-item document, it only has one entry.
|
||||
<4> The `data` section contains actual content. It's made up of properties.
|
||||
<5> The item's individual `links`.
|
||||
====
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
The previous fragment was lifted from the spec. When Spring HATEOAS renders an `EntityModel`, it will:
|
||||
|
||||
* Put the `self` link into both the document's `href` attribute and the item-level `href` attribute.
|
||||
* Put the rest of the model's links into both the top-level `links` as well as the item-level `links`.
|
||||
* Extract the properties from the `EntityModel` and turn them into
|
||||
====
|
||||
|
||||
When rendering a collection of resources, the document is almost the same, except there will be multiple entries inside
|
||||
the `items` JSON array, one for each entry.
|
||||
|
||||
Spring HATEOAS more specifically will:
|
||||
|
||||
* Put the entire collection's `self` link into the top-level `href` attribute.
|
||||
* The `CollectionModel` links (minus `self`) will be put into the top-level `links`.
|
||||
* Each item-level `href` will contain the corresponding `self` link for each entry from the `CollectionModel.content` collection.
|
||||
* Each item-level `links` will contain all other links for each entry from `CollectionModel.content`.
|
||||
|
||||
|
||||
[[mediatypes.uber]]
|
||||
== UBER - Uniform Basis for Exchanging Representations
|
||||
|
||||
http://uberhypermedia.org/[UBER] is an experimental JSON spec
|
||||
|
||||
NOTE: *UBER media type* is not associated in any way with Uber Technologies Inc., the ride sharing company.
|
||||
|
||||
[quote, Mike Amundsen, UBER spec]
|
||||
____
|
||||
The UBER document format is a minimal read/write hypermedia type designed to support simple state transfers and ad-hoc
|
||||
hypermedia-based transitions.
|
||||
____
|
||||
|
||||
UBER provides a uniform way to represent both single item resources as well as collections.
|
||||
|
||||
To enable this media type, put the following configuration in your code:
|
||||
|
||||
.UBER+JSON enabled application
|
||||
====
|
||||
[source, java, tabsize=2]
|
||||
----
|
||||
include::{code-dir}/UberApplication.java[tag=code]
|
||||
----
|
||||
====
|
||||
|
||||
This configuration will make your application respond to requests using the `Accept` header `application/vnd.amundsen-uber+json`
|
||||
as show below:
|
||||
|
||||
.UBER sample document
|
||||
====
|
||||
[source, json, tabsize=2]
|
||||
----
|
||||
include::{resource-dir}/docs/mediatype/uber/uber-sample.json[]
|
||||
----
|
||||
====
|
||||
|
||||
This media type is still under development as is the spec itself. Feel free to
|
||||
https://github.com/spring-projects/spring-hateoas/issues[open a ticket] if you run into issues using it.
|
||||
|
||||
[[mediatypes.custom]]
|
||||
== Registering a custom media type
|
||||
|
||||
|
||||
Reference in New Issue
Block a user