From f15740fb25f7babb34f423e9a54c331cb213e43a Mon Sep 17 00:00:00 2001 From: Greg Turnquist Date: Tue, 13 Aug 2019 13:01:49 -0500 Subject: [PATCH] #1038 - Update documentation and testing for ALPS. Also make the ALPS types support deserialization using @JsonCreator-based private constructor calls. --- src/main/asciidoc/mediatypes.adoc | 81 +++++++++++++ .../hateoas/mediatype/alps/Alps.java | 15 ++- .../hateoas/mediatype/alps/Descriptor.java | 24 +++- .../hateoas/mediatype/alps/Doc.java | 18 ++- .../hateoas/mediatype/alps/Ext.java | 12 +- .../alps/AlpsWebFluxIntegrationTest.java | 109 ++++++++++++++++++ .../alps/AlpsWebMvcIntegrationTest.java | 102 ++++++++++++++++ .../support/WebFluxEmployeeController.java | 36 ++++++ .../support/WebMvcEmployeeController.java | 37 ++++++ 9 files changed, 425 insertions(+), 9 deletions(-) create mode 100644 src/test/java/org/springframework/hateoas/mediatype/alps/AlpsWebFluxIntegrationTest.java create mode 100644 src/test/java/org/springframework/hateoas/mediatype/alps/AlpsWebMvcIntegrationTest.java diff --git a/src/main/asciidoc/mediatypes.adoc b/src/main/asciidoc/mediatypes.adoc index 0aee54c4..5bae8f3f 100644 --- a/src/main/asciidoc/mediatypes.adoc +++ b/src/main/asciidoc/mediatypes.adoc @@ -2,6 +2,7 @@ = Media types :code-dir: ../../../src/docs/java/org/springframework/hateoas :resource-dir: ../../../src/docs/resources/org/springframework/hateoas +:test-dir: ../../../src/test/java/org/springframework/hateoas :linkattrs: [[mediatypes.hal]] @@ -408,6 +409,86 @@ https://github.com/spring-projects/spring-hateoas/issues[open a ticket, window=" NOTE: *UBER media type* is not associated in any way with *Uber Technologies Inc.*, the ride sharing company. +[[mediatypes.alps]] +== ALPS - Application-Level Profile Semantics + +https://tools.ietf.org/html/draft-amundsen-richardson-foster-alps-01[ALPS, window="_blank"] is a media type for providing +profile-based metadata about another resource. + +[quote, Mike Amundsen, ALPS spec] +____ +An ALPS document can be used as a profile to +explain the application semantics of a document with an application- +agnostic media type (such as HTML, HAL, Collection+JSON, Siren, +etc.). This increases the reusability of profile documents across +media types. +____ + +ALPS requires no special activation. Instead you "build" an `Alps` record and return it from either a Spring MVC or a Spring WebFlux web method as shown below: + +.Building an `Alps` record +==== +[source, java, tabsize=2, indent=0] +---- +include::{test-dir}/support/WebMvcEmployeeController.java[tag=alps-profile] +---- +* This example leverages `PropertyUtils.getExposedProperties()` to extract metadata about the domain object's attributes. +==== + +This fragment has test data plugged in. It yields JSON like this: + +.ALPS JSON +==== +---- +{ + "version": "1.0", + "doc": { + "format": "TEXT", + "href": "https://example.org/samples/full/doc.html", + "value": "value goes here" + }, + "descriptor": [ + { + "id": "class field [name]", + "name": "name", + "type": "SEMANTIC", + "descriptor": [ + { + "id": "embedded" + } + ], + "ext": { + "id": "ext [name]", + "href": "https://example.org/samples/ext/name", + "value": "value goes here" + }, + "rt": "rt for [name]" + }, + { + "id": "class field [role]", + "name": "role", + "type": "SEMANTIC", + "descriptor": [ + { + "id": "embedded" + } + ], + "ext": { + "id": "ext [role]", + "href": "https://example.org/samples/ext/role", + "value": "value goes here" + }, + "rt": "rt for [role]" + } + ] +} +---- +==== + +Instead of linking each field "automatically" to a domain object's fields, you can write them by hand if you like. It's also possible +to use Spring Framework's message bundles and the `MessageSource` interface. This gives you the ability to delegate these values to +locale-specific message bundles and even internationalize the metadata. + [[mediatypes.custom]] == Registering a custom media type diff --git a/src/main/java/org/springframework/hateoas/mediatype/alps/Alps.java b/src/main/java/org/springframework/hateoas/mediatype/alps/Alps.java index bbc314e9..4e9c610a 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/alps/Alps.java +++ b/src/main/java/org/springframework/hateoas/mediatype/alps/Alps.java @@ -24,7 +24,9 @@ import org.springframework.hateoas.mediatype.alps.Descriptor.DescriptorBuilder; import org.springframework.hateoas.mediatype.alps.Doc.DocBuilder; import org.springframework.hateoas.mediatype.alps.Ext.ExtBuilder; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; /** @@ -38,14 +40,23 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; */ @Value @Builder(builderMethodName = "alps") -@JsonPropertyOrder({"version", "doc", "descriptor"}) +@JsonPropertyOrder({ "version", "doc", "descriptor" }) @JsonInclude(JsonInclude.Include.NON_NULL) public class Alps { - private final String version = "1.0"; + private final String version; private final Doc doc; private final List descriptor; + @JsonCreator + private Alps(@JsonProperty("version") String version, @JsonProperty("doc") Doc doc, + @JsonProperty("descriptor") List descriptor) { + + this.version = "1.0"; + this.doc = doc; + this.descriptor = descriptor; + } + /** * Returns a new {@link DescriptorBuilder}. * diff --git a/src/main/java/org/springframework/hateoas/mediatype/alps/Descriptor.java b/src/main/java/org/springframework/hateoas/mediatype/alps/Descriptor.java index c9fb045b..20d27808 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/alps/Descriptor.java +++ b/src/main/java/org/springframework/hateoas/mediatype/alps/Descriptor.java @@ -20,7 +20,9 @@ import lombok.Value; import java.util.List; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; /** @@ -33,14 +35,32 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; */ @Value @Builder -@JsonPropertyOrder({"id", "href", "name", "type", "doc", "descriptor", "ext"}) +@JsonPropertyOrder({ "id", "href", "name", "type", "doc", "descriptor", "ext" }) @JsonInclude(JsonInclude.Include.NON_NULL) public class Descriptor { - private final String id, href, name; + private final String id; + private final String href; + private final String name; private final Doc doc; private final Type type; private final Ext ext; private final String rt; private final List descriptor; + + @JsonCreator + private Descriptor(@JsonProperty("id") String id, @JsonProperty("href") String href, + @JsonProperty("name") String name, @JsonProperty("doc") Doc doc, @JsonProperty("type") Type type, + @JsonProperty("ext") Ext ext, @JsonProperty("rt") String rt, + @JsonProperty("descriptor") List descriptor) { + + this.id = id; + this.href = href; + this.name = name; + this.doc = doc; + this.type = type; + this.ext = ext; + this.rt = rt; + this.descriptor = descriptor; + } } diff --git a/src/main/java/org/springframework/hateoas/mediatype/alps/Doc.java b/src/main/java/org/springframework/hateoas/mediatype/alps/Doc.java index c28bcd81..120c5a3f 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/alps/Doc.java +++ b/src/main/java/org/springframework/hateoas/mediatype/alps/Doc.java @@ -15,13 +15,14 @@ */ package org.springframework.hateoas.mediatype.alps; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Value; import org.springframework.util.Assert; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; /** @@ -34,12 +35,12 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; */ @Value @Builder -@AllArgsConstructor -@JsonPropertyOrder({"format", "href", "value"}) +@JsonPropertyOrder({ "format", "href", "value" }) @JsonInclude(JsonInclude.Include.NON_NULL) public class Doc { - private final String href, value; + private final String href; + private final String value; private final Format format; /** @@ -57,4 +58,13 @@ public class Doc { this.value = value; this.format = format; } + + @JsonCreator + private Doc(@JsonProperty("href") String href, @JsonProperty("value") String value, + @JsonProperty("format") Format format) { + + this.href = href; + this.value = value; + this.format = format; + } } diff --git a/src/main/java/org/springframework/hateoas/mediatype/alps/Ext.java b/src/main/java/org/springframework/hateoas/mediatype/alps/Ext.java index cc9a2f0c..2d35c501 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/alps/Ext.java +++ b/src/main/java/org/springframework/hateoas/mediatype/alps/Ext.java @@ -18,6 +18,8 @@ package org.springframework.hateoas.mediatype.alps; import lombok.Builder; import lombok.Value; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; /** @@ -30,10 +32,18 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; */ @Value @Builder -@JsonPropertyOrder({"id", "href", "value"}) +@JsonPropertyOrder({ "id", "href", "value" }) public class Ext { private final String id; private final String href; private final String value; + + @JsonCreator + private Ext(@JsonProperty("id") String id, @JsonProperty("href") String href, @JsonProperty("value") String value) { + + this.id = id; + this.href = href; + this.value = value; + } } diff --git a/src/test/java/org/springframework/hateoas/mediatype/alps/AlpsWebFluxIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/alps/AlpsWebFluxIntegrationTest.java new file mode 100644 index 00000000..ea5ec89d --- /dev/null +++ b/src/test/java/org/springframework/hateoas/mediatype/alps/AlpsWebFluxIntegrationTest.java @@ -0,0 +1,109 @@ +/* + * 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 + * + * https://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.mediatype.alps; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType.*; + +import reactor.test.StepVerifier; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.config.EnableHypermediaSupport; +import org.springframework.hateoas.config.WebClientConfigurer; +import org.springframework.hateoas.support.WebFluxEmployeeController; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.config.EnableWebFlux; + +/** + * @author Greg Turnquist + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration +public class AlpsWebFluxIntegrationTest { + + @Autowired WebTestClient testClient; + + @Test + void profileEndpointReturnsAlps() { + + this.testClient.get().uri("/profile") // + .accept(MediaTypes.ALPS_JSON) // + .exchange() // + .expectStatus().isOk() // + .returnResult(Alps.class).getResponseBody() // + .as(StepVerifier::create) // + .expectNextMatches(alps -> { + + assertThat(alps.getVersion()).isEqualTo("1.0"); + assertThat(alps.getDoc().getFormat()).isEqualByComparingTo(Format.TEXT); + assertThat(alps.getDoc().getHref()).isEqualTo("https://example.org/samples/full/doc.html"); + assertThat(alps.getDoc().getValue()).isEqualTo("value goes here"); + assertThat(alps.getDescriptor()).hasSize(2); + + assertThat(alps.getDescriptor().get(0).getId()).isEqualTo("class field [name]"); + assertThat(alps.getDescriptor().get(0).getName()).isEqualTo("name"); + assertThat(alps.getDescriptor().get(0).getType()).isEqualByComparingTo(Type.SEMANTIC); + assertThat(alps.getDescriptor().get(0).getDescriptor()).hasSize(1); + assertThat(alps.getDescriptor().get(0).getDescriptor().get(0).getId()).isEqualTo("embedded"); + assertThat(alps.getDescriptor().get(0).getExt().getId()).isEqualTo("ext [name]"); + assertThat(alps.getDescriptor().get(0).getExt().getHref()).isEqualTo("https://example.org/samples/ext/name"); + assertThat(alps.getDescriptor().get(0).getExt().getValue()).isEqualTo("value goes here"); + assertThat(alps.getDescriptor().get(0).getRt()).isEqualTo("rt for [name]"); + + assertThat(alps.getDescriptor().get(1).getId()).isEqualTo("class field [role]"); + assertThat(alps.getDescriptor().get(1).getName()).isEqualTo("role"); + assertThat(alps.getDescriptor().get(1).getType()).isEqualByComparingTo(Type.SEMANTIC); + assertThat(alps.getDescriptor().get(1).getDescriptor()).hasSize(1); + assertThat(alps.getDescriptor().get(1).getDescriptor().get(0).getId()).isEqualTo("embedded"); + assertThat(alps.getDescriptor().get(1).getExt().getId()).isEqualTo("ext [role]"); + assertThat(alps.getDescriptor().get(1).getExt().getHref()).isEqualTo("https://example.org/samples/ext/role"); + assertThat(alps.getDescriptor().get(1).getExt().getValue()).isEqualTo("value goes here"); + assertThat(alps.getDescriptor().get(1).getRt()).isEqualTo("rt for [role]"); + + return true; + }) // + .verifyComplete(); + } + + @Configuration + @EnableWebFlux + @EnableHypermediaSupport(type = HAL) + static class TestConfig { + + @Bean + WebFluxEmployeeController employeeController() { + return new WebFluxEmployeeController(); + } + + @Bean + WebTestClient webTestClient(WebClientConfigurer webClientConfigurer, ApplicationContext ctx) { + + return WebTestClient.bindToApplicationContext(ctx).build() // + .mutate() // + .exchangeStrategies(webClientConfigurer.hypermediaExchangeStrategies()) // + .build(); + } + } + +} diff --git a/src/test/java/org/springframework/hateoas/mediatype/alps/AlpsWebMvcIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/alps/AlpsWebMvcIntegrationTest.java new file mode 100644 index 00000000..f67b9271 --- /dev/null +++ b/src/test/java/org/springframework/hateoas/mediatype/alps/AlpsWebMvcIntegrationTest.java @@ -0,0 +1,102 @@ +/* + * 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 + * + * https://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.mediatype.alps; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.*; +import static org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.config.EnableHypermediaSupport; +import org.springframework.hateoas.support.WebMvcEmployeeController; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * @author Greg Turnquist + */ +@ExtendWith(SpringExtension.class) +@WebAppConfiguration +@ContextConfiguration +public class AlpsWebMvcIntegrationTest { + + @Autowired WebApplicationContext context; + + MockMvc mockMvc; + + @BeforeEach + void setUp() { + this.mockMvc = webAppContextSetup(this.context).build(); + } + + @Test + void profileEndpointReturnsAlps() throws Exception { + + this.mockMvc.perform(get("/profile").accept(MediaTypes.ALPS_JSON_VALUE)) // + .andDo(print()) // + .andExpect(status().isOk()) // + .andExpect(jsonPath("$.version", is("1.0"))) // + .andExpect(jsonPath("$.doc.format", is("TEXT"))) + .andExpect(jsonPath("$.doc.href", is("https://example.org/samples/full/doc.html"))) + .andExpect(jsonPath("$.doc.value", is("value goes here"))).andExpect(jsonPath("$.descriptor", hasSize(2))) + + .andExpect(jsonPath("$.descriptor[0].id", is("class field [name]"))) + .andExpect(jsonPath("$.descriptor[0].name", is("name"))) + .andExpect(jsonPath("$.descriptor[0].type", is("SEMANTIC"))) + .andExpect(jsonPath("$.descriptor[0].descriptor", hasSize(1))) + .andExpect(jsonPath("$.descriptor[0].descriptor[0].id", is("embedded"))) + .andExpect(jsonPath("$.descriptor[0].ext.id", is("ext [name]"))) + .andExpect(jsonPath("$.descriptor[0].ext.href", is("https://example.org/samples/ext/name"))) + .andExpect(jsonPath("$.descriptor[0].ext.value", is("value goes here"))) + .andExpect(jsonPath("$.descriptor[0].rt", is("rt for [name]"))) + + .andExpect(jsonPath("$.descriptor[1].id", is("class field [role]"))) + .andExpect(jsonPath("$.descriptor[1].name", is("role"))) + .andExpect(jsonPath("$.descriptor[1].type", is("SEMANTIC"))) + .andExpect(jsonPath("$.descriptor[1].descriptor", hasSize(1))) + .andExpect(jsonPath("$.descriptor[1].descriptor[0].id", is("embedded"))) + .andExpect(jsonPath("$.descriptor[1].ext.id", is("ext [role]"))) + .andExpect(jsonPath("$.descriptor[1].ext.href", is("https://example.org/samples/ext/role"))) + .andExpect(jsonPath("$.descriptor[1].ext.value", is("value goes here"))) + .andExpect(jsonPath("$.descriptor[1].rt", is("rt for [role]"))); + } + + @Configuration + @EnableWebMvc + @EnableHypermediaSupport(type = HAL) + static class TestConfig { + + @Bean + WebMvcEmployeeController employeeController() { + return new WebMvcEmployeeController(); + } + } + +} diff --git a/src/test/java/org/springframework/hateoas/support/WebFluxEmployeeController.java b/src/test/java/org/springframework/hateoas/support/WebFluxEmployeeController.java index 11c3408a..4bd0dc99 100644 --- a/src/test/java/org/springframework/hateoas/support/WebFluxEmployeeController.java +++ b/src/test/java/org/springframework/hateoas/support/WebFluxEmployeeController.java @@ -15,12 +15,17 @@ */ package org.springframework.hateoas.support; +import static org.springframework.hateoas.MediaTypes.*; +import static org.springframework.hateoas.mediatype.PropertyUtils.*; +import static org.springframework.hateoas.mediatype.alps.Alps.*; import static org.springframework.hateoas.server.reactive.WebFluxLinkBuilder.*; import static reactor.function.TupleUtils.*; +import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.TreeMap; +import java.util.stream.Collectors; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -30,6 +35,11 @@ import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.Links; +import org.springframework.hateoas.mediatype.alps.Alps; +import org.springframework.hateoas.mediatype.alps.Descriptor; +import org.springframework.hateoas.mediatype.alps.Ext; +import org.springframework.hateoas.mediatype.alps.Format; +import org.springframework.hateoas.mediatype.alps.Type; import org.springframework.hateoas.server.reactive.WebFluxLinkBuilder; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -174,4 +184,30 @@ public class WebFluxEmployeeController { .build() // ); } + + @GetMapping(value = "/profile", produces = ALPS_JSON_VALUE) + Alps profile() { + + return Alps.alps() // + .doc(doc() // + .href("https://example.org/samples/full/doc.html") // + .value("value goes here") // + .format(Format.TEXT) // + .build()) // + .descriptor(getExposedProperties(Employee.class).stream() // + .map(property -> Descriptor.builder() // + .id("class field [" + property.getName() + "]") // + .name(property.getName()) // + .type(Type.SEMANTIC) // + .ext(Ext.builder() // + .id("ext [" + property.getName() + "]") // + .href("https://example.org/samples/ext/" + property.getName()) // + .value("value goes here") // + .build()) // + .rt("rt for [" + property.getName() + "]") // + .descriptor(Collections.singletonList(Descriptor.builder().id("embedded").build())) // + .build()) // + .collect(Collectors.toList())) + .build(); + } } diff --git a/src/test/java/org/springframework/hateoas/support/WebMvcEmployeeController.java b/src/test/java/org/springframework/hateoas/support/WebMvcEmployeeController.java index 55a67875..2d0c50fe 100644 --- a/src/test/java/org/springframework/hateoas/support/WebMvcEmployeeController.java +++ b/src/test/java/org/springframework/hateoas/support/WebMvcEmployeeController.java @@ -15,9 +15,13 @@ */ package org.springframework.hateoas.support; +import static org.springframework.hateoas.MediaTypes.*; +import static org.springframework.hateoas.mediatype.PropertyUtils.*; +import static org.springframework.hateoas.mediatype.alps.Alps.*; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -29,6 +33,11 @@ import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; +import org.springframework.hateoas.mediatype.alps.Alps; +import org.springframework.hateoas.mediatype.alps.Descriptor; +import org.springframework.hateoas.mediatype.alps.Ext; +import org.springframework.hateoas.mediatype.alps.Format; +import org.springframework.hateoas.mediatype.alps.Type; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -174,4 +183,32 @@ public class WebMvcEmployeeController { .toUri()) // .build(); } + + // tag::alps-profile[] + @GetMapping(value = "/profile", produces = ALPS_JSON_VALUE) + Alps profile() { + + return Alps.alps() // + .doc(doc() // + .href("https://example.org/samples/full/doc.html") // + .value("value goes here") // + .format(Format.TEXT) // + .build()) // + .descriptor(getExposedProperties(Employee.class).stream() // + .map(property -> Descriptor.builder() // + .id("class field [" + property.getName() + "]") // + .name(property.getName()) // + .type(Type.SEMANTIC) // + .ext(Ext.builder() // + .id("ext [" + property.getName() + "]") // + .href("https://example.org/samples/ext/" + property.getName()) // + .value("value goes here") // + .build()) // + .rt("rt for [" + property.getName() + "]") // + .descriptor(Collections.singletonList(Descriptor.builder().id("embedded").build())) // + .build()) // + .collect(Collectors.toList())) + .build(); + } + // end::alps-profile[] }