diff --git a/pom.xml b/pom.xml index 3b70056b..41b3e926 100644 --- a/pom.xml +++ b/pom.xml @@ -280,6 +280,7 @@ false ${basedir}/src/docs/java/org/springframework/hateoas + ${basedir}/src/docs/resources/org/springframework/hateoas shared true font @@ -698,6 +699,18 @@ + + add-docs-resources + generate-test-resources + + add-test-resource + + + + src/docs/resources + + + diff --git a/src/docs/java/org/springframework/hateoas/CollectionJsonApplication.java b/src/docs/java/org/springframework/hateoas/CollectionJsonApplication.java new file mode 100644 index 00000000..19bc2feb --- /dev/null +++ b/src/docs/java/org/springframework/hateoas/CollectionJsonApplication.java @@ -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[] diff --git a/src/docs/java/org/springframework/hateoas/EmployeeController.java b/src/docs/java/org/springframework/hateoas/EmployeeController.java index 4e762005..1582fde1 100644 --- a/src/docs/java/org/springframework/hateoas/EmployeeController.java +++ b/src/docs/java/org/springframework/hateoas/EmployeeController.java @@ -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[] diff --git a/src/docs/java/org/springframework/hateoas/HalFormsApplication.java b/src/docs/java/org/springframework/hateoas/HalFormsApplication.java new file mode 100644 index 00000000..848ae5a9 --- /dev/null +++ b/src/docs/java/org/springframework/hateoas/HalFormsApplication.java @@ -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[] diff --git a/src/docs/java/org/springframework/hateoas/SampleAppConfiguration.java b/src/docs/java/org/springframework/hateoas/SampleAppConfiguration.java new file mode 100644 index 00000000..4743020f --- /dev/null +++ b/src/docs/java/org/springframework/hateoas/SampleAppConfiguration.java @@ -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[] + +} diff --git a/src/docs/java/org/springframework/hateoas/UberApplication.java b/src/docs/java/org/springframework/hateoas/UberApplication.java new file mode 100644 index 00000000..21f463fd --- /dev/null +++ b/src/docs/java/org/springframework/hateoas/UberApplication.java @@ -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[] diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part1.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part1.json new file mode 100644 index 00000000..cc3ba489 --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part1.json @@ -0,0 +1,6 @@ +{ + "collection": { + "version": "1.0", + "href": "http://example.org/friends/" + } +} \ No newline at end of file diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part2.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part2.json new file mode 100644 index 00000000..613a9f24 --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part2.json @@ -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" + } + ] + } + } +} \ No newline at end of file diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part3.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part3.json new file mode 100644 index 00000000..6b004b96 --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part3.json @@ -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" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part4.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part4.json new file mode 100644 index 00000000..071694a1 --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part4.json @@ -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": "" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part5.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part5.json new file mode 100644 index 00000000..c9bc6620 --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part5.json @@ -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" + } + ] + } + } +} \ No newline at end of file diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part6.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part6.json new file mode 100644 index 00000000..f07cf1fb --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part6.json @@ -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." + } + } +} \ No newline at end of file diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part7.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part7.json new file mode 100644 index 00000000..7bf9c32d --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/collectionjson/spec-part7.json @@ -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" + } + ] + } +} \ No newline at end of file diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/forms/hal-forms-sample-with-notes.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/forms/hal-forms-sample-with-notes.json new file mode 100644 index 00000000..ca352158 --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/forms/hal-forms-sample-with-notes.json @@ -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 + } ] + } + } +} \ No newline at end of file diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/forms/hal-forms-sample.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/forms/hal-forms-sample.json new file mode 100644 index 00000000..ca352158 --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/forms/hal-forms-sample.json @@ -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 + } ] + } + } +} \ No newline at end of file diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-multiple-entry-link-relation.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-multiple-entry-link-relation.json new file mode 100644 index 00000000..f2343a9b --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-multiple-entry-link-relation.json @@ -0,0 +1,9 @@ +{ + "_links": { + "item": [ + { "href": "http://myhost/cart/42" }, + { "href": "http://myhost/inventory/12" } + ] + }, + "customer": "Dave Matthews" +} diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-single-entry-link-relation-array.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-single-entry-link-relation-array.json new file mode 100644 index 00000000..97a86396 --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-single-entry-link-relation-array.json @@ -0,0 +1,6 @@ +{ + "_links": { + "item": [{ "href": "http://myhost/inventory/12" }] + }, + "customer": "Dave Matthews" +} diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-single-entry-link-relation-object.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-single-entry-link-relation-object.json new file mode 100644 index 00000000..c2530efe --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-single-entry-link-relation-object.json @@ -0,0 +1,6 @@ +{ + "_links": { + "item": { "href": "http://myhost/inventory/12" } + }, + "customer": "Dave Matthews" +} diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-with-curies.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-with-curies.json new file mode 100644 index 00000000..54afd4fe --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/hal/hal-with-curies.json @@ -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" +} diff --git a/src/docs/resources/org/springframework/hateoas/docs/mediatype/uber/uber-sample.json b/src/docs/resources/org/springframework/hateoas/docs/mediatype/uber/uber-sample.json new file mode 100644 index 00000000..760e99e4 --- /dev/null +++ b/src/docs/resources/org/springframework/hateoas/docs/mediatype/uber/uber-sample.json @@ -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" + } ] + } ] + } +} \ No newline at end of file diff --git a/src/main/asciidoc/fundamentals.adoc b/src/main/asciidoc/fundamentals.adoc index 1fabb9fd..687a26fe 100644 --- a/src/main/asciidoc/fundamentals.adoc +++ b/src/main/asciidoc/fundamentals.adoc @@ -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] ----
diff --git a/src/main/asciidoc/mediatypes.adoc b/src/main/asciidoc/mediatypes.adoc index 12dadae3..3307e932 100644 --- a/src/main/asciidoc/mediatypes.adoc +++ b/src/main/asciidoc/mediatypes.adoc @@ -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 <> 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 <>. + +[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 <> 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.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