diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index e5531389..1fb1b14d 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -30,10 +30,6 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.XmlType; - import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -47,7 +43,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; * @author Oliver Gierke * @author Greg Turnquist */ -@XmlType(name = "link", namespace = Link.ATOM_NAMESPACE) @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(value = "templated", ignoreUnknown = true) @AllArgsConstructor(access = AccessLevel.PACKAGE) @@ -66,15 +61,15 @@ public class Link implements Serializable { public static final String REL_NEXT = "next"; public static final String REL_LAST = "last"; - private @XmlAttribute @Wither String rel; - private @XmlAttribute @Wither String href; - private @XmlAttribute @Wither String hreflang; - private @XmlAttribute @Wither String media; - private @XmlAttribute @Wither String title; - private @XmlAttribute @Wither String type; - private @XmlAttribute @Wither String deprecation; - private @XmlTransient @JsonIgnore UriTemplate template; - private @XmlTransient @JsonIgnore List affordances; + private @Wither String rel; + private @Wither String href; + private @Wither String hreflang; + private @Wither String media; + private @Wither String title; + private @Wither String type; + private @Wither String deprecation; + private @JsonIgnore UriTemplate template; + private @JsonIgnore List affordances; /** * Creates a new link to the given URI with the self rel. diff --git a/src/main/java/org/springframework/hateoas/PagedResources.java b/src/main/java/org/springframework/hateoas/PagedResources.java index d0fede10..98660168 100644 --- a/src/main/java/org/springframework/hateoas/PagedResources.java +++ b/src/main/java/org/springframework/hateoas/PagedResources.java @@ -20,9 +20,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Optional; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlRootElement; - import org.springframework.util.Assert; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -33,10 +30,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; * * @author Oliver Gierke */ -@XmlRootElement(name = "pagedEntities") public class PagedResources extends Resources { - public static PagedResources NO_PAGE = new PagedResources(); + public static PagedResources NO_PAGE = new PagedResources<>(); private PageMetadata metadata; @@ -169,10 +165,10 @@ public class PagedResources extends Resources { */ public static class PageMetadata { - @XmlAttribute @JsonProperty private long size; - @XmlAttribute @JsonProperty private long totalElements; - @XmlAttribute @JsonProperty private long totalPages; - @XmlAttribute @JsonProperty private long number; + @JsonProperty private long size; + @JsonProperty private long totalElements; + @JsonProperty private long totalPages; + @JsonProperty private long number; protected PageMetadata() { diff --git a/src/main/java/org/springframework/hateoas/Resource.java b/src/main/java/org/springframework/hateoas/Resource.java index 7807cf06..43948b45 100644 --- a/src/main/java/org/springframework/hateoas/Resource.java +++ b/src/main/java/org/springframework/hateoas/Resource.java @@ -18,9 +18,6 @@ package org.springframework.hateoas; import java.util.Arrays; import java.util.Collection; -import javax.xml.bind.annotation.XmlAnyElement; -import javax.xml.bind.annotation.XmlRootElement; - import org.springframework.util.Assert; import com.fasterxml.jackson.annotation.JsonUnwrapped; @@ -31,7 +28,6 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped; * @author Oliver Gierke * @author Greg Turnquist */ -@XmlRootElement public class Resource extends ResourceSupport { private final T content; @@ -73,7 +69,6 @@ public class Resource extends ResourceSupport { * @return the content */ @JsonUnwrapped - @XmlAnyElement public T getContent() { return content; } diff --git a/src/main/java/org/springframework/hateoas/ResourceSupport.java b/src/main/java/org/springframework/hateoas/ResourceSupport.java index 16c50cdc..9235650a 100755 --- a/src/main/java/org/springframework/hateoas/ResourceSupport.java +++ b/src/main/java/org/springframework/hateoas/ResourceSupport.java @@ -21,8 +21,6 @@ import java.util.List; import java.util.Optional; import java.util.stream.Collectors; -import javax.xml.bind.annotation.XmlElement; - import org.springframework.util.Assert; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -104,7 +102,6 @@ public class ResourceSupport implements Identifiable { * * @return */ - @XmlElement(name = "link", namespace = Link.ATOM_NAMESPACE) @JsonProperty("links") public List getLinks() { return links; diff --git a/src/main/java/org/springframework/hateoas/Resources.java b/src/main/java/org/springframework/hateoas/Resources.java index 5ca199b1..5886ad87 100644 --- a/src/main/java/org/springframework/hateoas/Resources.java +++ b/src/main/java/org/springframework/hateoas/Resources.java @@ -21,10 +21,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; -import javax.xml.bind.annotation.XmlAnyElement; -import javax.xml.bind.annotation.XmlElementWrapper; -import javax.xml.bind.annotation.XmlRootElement; - import org.springframework.util.Assert; import com.fasterxml.jackson.annotation.JsonProperty; @@ -34,7 +30,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; * * @author Oliver Gierke */ -@XmlRootElement(name = "entities") public class Resources extends ResourceSupport implements Iterable { private final Collection content; @@ -98,8 +93,6 @@ public class Resources extends ResourceSupport implements Iterable { * * @return the content will never be {@literal null}. */ - @XmlAnyElement - @XmlElementWrapper @JsonProperty("content") public Collection getContent() { return Collections.unmodifiableCollection(content); diff --git a/src/main/java/org/springframework/hateoas/VndErrors.java b/src/main/java/org/springframework/hateoas/VndErrors.java index 6fc18f32..fead6044 100644 --- a/src/main/java/org/springframework/hateoas/VndErrors.java +++ b/src/main/java/org/springframework/hateoas/VndErrors.java @@ -20,11 +20,6 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlType; - import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -38,10 +33,8 @@ import com.fasterxml.jackson.annotation.JsonValue; * @see https://github.com/blongden/vnd.error * @author Oliver Gierke */ -@XmlRootElement(name = "errors") public class VndErrors implements Iterable { - @XmlElement(name = "error")// private final List vndErrors; /** @@ -162,11 +155,10 @@ public class VndErrors implements Iterable { * * @author Oliver Gierke */ - @XmlType public static class VndError extends ResourceSupport { - @XmlAttribute @JsonProperty private final String logref; - @XmlElement @JsonProperty private final String message; + @JsonProperty private final String logref; + @JsonProperty private final String message; /** * Creates a new {@link VndError} with the given logref, a message as well as some {@link Link}s. diff --git a/src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java b/src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java index c521caee..37c1848b 100644 --- a/src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java +++ b/src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java @@ -17,8 +17,6 @@ package org.springframework.hateoas.hal; import java.util.List; -import javax.xml.bind.annotation.XmlElement; - import org.springframework.hateoas.Link; import org.springframework.hateoas.ResourceSupport; @@ -38,7 +36,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; public abstract class ResourceSupportMixin extends ResourceSupport { @Override - @XmlElement(name = "link") @JsonProperty("_links") @JsonInclude(Include.NON_EMPTY) @JsonSerialize(using = Jackson2HalModule.HalLinkListSerializer.class) diff --git a/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java b/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java index eacd65dc..d1d3883d 100644 --- a/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java +++ b/src/main/java/org/springframework/hateoas/hal/ResourcesMixin.java @@ -17,8 +17,6 @@ package org.springframework.hateoas.hal; import java.util.Collection; -import javax.xml.bind.annotation.XmlElement; - import org.springframework.hateoas.Resources; import com.fasterxml.jackson.annotation.JsonInclude; @@ -39,7 +37,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; public abstract class ResourcesMixin extends Resources { @Override - @XmlElement(name = "embedded") @JsonProperty("_embedded") @JsonInclude(Include.NON_EMPTY) @JsonSerialize(using = Jackson2HalModule.HalResourcesSerializer.class) diff --git a/src/main/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsModule.java b/src/main/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsModule.java index 16a959c3..246addb6 100644 --- a/src/main/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsModule.java +++ b/src/main/java/org/springframework/hateoas/hal/forms/Jackson2HalFormsModule.java @@ -19,8 +19,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; -import javax.xml.bind.annotation.XmlElement; - import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.Link; @@ -89,7 +87,6 @@ public class Jackson2HalFormsModule extends SimpleModule { abstract class ResourcesMixin extends Resources { @Override - @XmlElement(name = "embedded") @JsonProperty("_embedded") @JsonInclude(Include.NON_EMPTY) @JsonDeserialize(using = HalFormsResourcesDeserializer.class) diff --git a/src/test/java/org/springframework/hateoas/PagedResourcesMarshallingTest.java b/src/test/java/org/springframework/hateoas/PagedResourcesMarshallingTest.java deleted file mode 100755 index 9afa2a61..00000000 --- a/src/test/java/org/springframework/hateoas/PagedResourcesMarshallingTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2013-2017 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 static org.assertj.core.api.Assertions.*; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.StringReader; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.charset.Charset; -import java.util.ArrayList; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.core.io.ClassPathResource; - -/** - * Test for marshalling / unmarshalling of {@link PagedResources}. - * - * @author Eric Bottard - */ -public class PagedResourcesMarshallingTest { - - Marshaller marshaller; - Unmarshaller unmarshaller; - - String xmlReference; - PagedResources pagedResources; - - public PagedResourcesMarshallingTest() throws IOException { - xmlReference = readFile(new ClassPathResource("pagedresources.xml")); - } - - @Before - public void setUp() throws Exception { - - JAXBContext context = JAXBContext.newInstance(PagedResources.class); - marshaller = context.createMarshaller(); - unmarshaller = context.createUnmarshaller(); - - pagedResources = new PagedResources<>(new ArrayList<>(), null); - } - - /** - * @see #98 - */ - @Test - public void jaxbUnMarshalling() throws Exception { - - assertThat(unmarshaller.unmarshal(new StringReader(xmlReference))).isEqualTo(pagedResources); - } - - public static class Inner {} - - private static String readFile(org.springframework.core.io.Resource resource) throws IOException { - - try (FileInputStream stream = new FileInputStream(resource.getFile())) { - FileChannel fc = stream.getChannel(); - MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); - return Charset.defaultCharset().decode(bb).toString(); - } - } -} diff --git a/src/test/java/org/springframework/hateoas/ResourceIntegrationTest.java b/src/test/java/org/springframework/hateoas/ResourceIntegrationTest.java index 9493506c..a3066c4d 100755 --- a/src/test/java/org/springframework/hateoas/ResourceIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/ResourceIntegrationTest.java @@ -38,7 +38,6 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; public class ResourceIntegrationTest extends AbstractJackson2MarshallingIntegrationTest { static final String REFERENCE = "{\"firstname\":\"Dave\",\"lastname\":\"Matthews\",\"links\":[{\"rel\":\"self\",\"href\":\"localhost\"}]}"; - static final String XML_REFERENCE = ""; @Test public void inlinesContent() throws Exception { @@ -53,29 +52,6 @@ public class ResourceIntegrationTest extends AbstractJackson2MarshallingIntegrat assertThat(write(resource)).isEqualTo(REFERENCE); } - /** - * @see #124 - * @see #154 - */ - @Test - public void marshalsResourceToXml() throws Exception { - - Person person = new Person(); - person.firstname = "Dave"; - person.lastname = "Matthews"; - - PersonResource resource = new PersonResource(person); - resource.add(new Link("/foo", "bar")); - - JAXBContext context = JAXBContext.newInstance(PersonResource.class, Person.class); - StringWriter writer = new StringWriter(); - - Marshaller marshaller = context.createMarshaller(); - marshaller.marshal(resource, writer); - - assertThat(new Diff(XML_REFERENCE, writer.toString()).similar()).isTrue(); - } - /** * @see #14 */ @@ -90,7 +66,6 @@ public class ResourceIntegrationTest extends AbstractJackson2MarshallingIntegrat assertThat(result.getContent().lastname).isEqualTo("Matthews"); } - @XmlRootElement static class PersonResource extends Resource { public PersonResource(Person person) { @@ -101,10 +76,9 @@ public class ResourceIntegrationTest extends AbstractJackson2MarshallingIntegrat } @JsonAutoDetect(fieldVisibility = Visibility.ANY) - @XmlRootElement static class Person { - @XmlAttribute String firstname; - @XmlAttribute String lastname; + String firstname; + String lastname; } } diff --git a/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java b/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java index 1d828f7c..416a7a83 100755 --- a/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java +++ b/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTest.java @@ -49,8 +49,6 @@ import com.fasterxml.jackson.databind.SerializationFeature; public class VndErrorsMarshallingTest { ObjectMapper jackson2Mapper; - Marshaller marshaller; - Unmarshaller unmarshaller; RelProvider relProvider = new EvoInflectorRelProvider(); @@ -74,10 +72,6 @@ public class VndErrorsMarshallingTest { jackson2Mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, null, null)); jackson2Mapper.configure(SerializationFeature.INDENT_OUTPUT, true); - JAXBContext context = JAXBContext.newInstance(VndErrors.class); - marshaller = context.createMarshaller(); - unmarshaller = context.createUnmarshaller(); - VndError error = new VndError("42", "Validation failed!", // new Link("http://...", "describes"), new Link("http://...", "help")); errors = new VndErrors(error, error, error); @@ -91,18 +85,6 @@ public class VndErrorsMarshallingTest { assertThat(jackson2Mapper.writeValueAsString(errors)).isEqualToIgnoringWhitespace(json2Reference); } - /** - * @see #62, #154 - */ - @Test - public void jaxbMarshalling() throws Exception { - - Writer writer = new StringWriter(); - marshaller.marshal(errors, writer); - - assertThat(new Diff(xmlReference, writer.toString()).similar()).isTrue(); - } - /** * @see #93, #94 */ @@ -111,15 +93,6 @@ public class VndErrorsMarshallingTest { assertThat(jackson2Mapper.readValue(jsonReference, VndErrors.class)).isEqualTo(errors); } - /** - * @see #93, #94 - */ - @Test - public void jaxbUnMarshalling() throws Exception { - VndErrors actual = (VndErrors) unmarshaller.unmarshal(new StringReader(xmlReference)); - assertThat(actual).isEqualTo(errors); - } - private static String readFile(org.springframework.core.io.Resource resource) throws IOException { try (FileInputStream stream = new FileInputStream(resource.getFile())) { diff --git a/src/test/java/org/springframework/hateoas/hal/RenderHypermediaForDefaultAcceptHeadersTest.java b/src/test/java/org/springframework/hateoas/hal/RenderHypermediaForDefaultAcceptHeadersTest.java new file mode 100644 index 00000000..10ab4e74 --- /dev/null +++ b/src/test/java/org/springframework/hateoas/hal/RenderHypermediaForDefaultAcceptHeadersTest.java @@ -0,0 +1,216 @@ +/* + * Copyright 2017 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.hal; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; +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 lombok.Data; +import lombok.experimental.Wither; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.Resources; +import org.springframework.hateoas.config.EnableHypermediaSupport; +import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * @author Greg Turnquist + */ +@RunWith(SpringRunner.class) +@WebAppConfiguration +@ContextConfiguration +public class RenderHypermediaForDefaultAcceptHeadersTest { + + @Autowired WebApplicationContext context; + + MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = webAppContextSetup(this.context).build(); + } + + /** + * Verify the default {@literal Accept} header used by a browser (which favors XML/HTML) still yields HAL-JSON. + */ + @Test + public void browserBasedDefaultAcceptHeadersShouldProduceHalJson() throws Exception { + + this.mockMvc.perform(get("/employees").accept(MediaType.APPLICATION_XHTML_XML, MediaType.ALL)) // + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_UTF8_VALUE)); + } + + /** + * Verify the default {@literal Accept} header used by cURL still yields HAL-JSON. + */ + @Test + public void curlBasedDefaultAcceptHeadersShouldProduceHalJson() throws Exception { + + this.mockMvc.perform(get("/employees").accept(MediaType.ALL)) // + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_UTF8_VALUE)); + } + + @RestController + static class EmployeeController { + + private final static Map EMPLOYEES = new TreeMap<>(); + + static { + EMPLOYEES.put(0, new Employee("Frodo Baggins", "ring bearer")); + EMPLOYEES.put(1, new Employee("Bilbo Baggins", "burglar")); + } + + @GetMapping("/employees") + public Resources> all() { + + // Create a list of Resource's to return + List> employees = new ArrayList<>(); + + // Fetch each Resource using the controller's findOne method. + for (int i = 0; i < EMPLOYEES.size(); i++) { + employees.add(findOne(i)); + } + + // Generate an "Affordance" based on this method (the "self" link) + Link selfLink = linkTo(methodOn(EmployeeController.class).all()).withSelfRel(); + + // Return the collection of employee resources along with the composite affordance + return new Resources<>(employees, selfLink); + } + + @GetMapping("/employees/{id}") + public Resource findOne(@PathVariable Integer id) { + + // Start the affordance with the "self" link, i.e. this method. + Link findOneLink = linkTo(methodOn(EmployeeController.class).findOne(id)).withSelfRel(); + + // Define final link as means to find entire collection. + Link employeesLink = linkTo(methodOn(EmployeeController.class).all()).withRel("employees"); + + // Return the affordance + a link back to the entire collection resource. + return new Resource<>(EMPLOYEES.get(id), findOneLink, employeesLink); + } + + @PostMapping("/employees") + public ResponseEntity newEmployee(@RequestBody Employee employee) { + + int newEmployeeId = EMPLOYEES.size(); + + EMPLOYEES.put(newEmployeeId, employee); + + try { + return ResponseEntity.noContent().location(new URI(findOne(newEmployeeId).getLink(Link.REL_SELF).map(link -> link.expand().getHref()).orElse(""))) + .build(); + } catch (URISyntaxException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + + @PutMapping("/employees/{id}") + public ResponseEntity updateEmployee(@RequestBody Employee employee, @PathVariable Integer id) { + + EMPLOYEES.put(id, employee); + + try { + return ResponseEntity.noContent().location(new URI(findOne(id).getLink(Link.REL_SELF).map(link -> link.expand().getHref()).orElse(""))) + .build(); + } catch (URISyntaxException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + + @PatchMapping("/employees/{id}") + public ResponseEntity partiallyUpdateEmployee(@RequestBody Employee employee, @PathVariable Integer id) { + + Employee oldEmployee = EMPLOYEES.get(id); + Employee newEmployee = oldEmployee; + + if (employee.getName() != null) { + newEmployee = newEmployee.withName(employee.getName()); + } + + if (employee.getRole() != null) { + newEmployee = newEmployee.withRole(employee.getRole()); + } + + EMPLOYEES.put(id, newEmployee); + + try { + return ResponseEntity.noContent().location(new URI(findOne(id).getLink(Link.REL_SELF).map(link -> link.expand().getHref()).orElse(""))) + .build(); + } catch (URISyntaxException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + } + + @Configuration + @EnableWebMvc + @EnableHypermediaSupport(type = { HypermediaType.HAL }) + static class TestConfig { + + @Bean + EmployeeController employeeController() { + return new EmployeeController(); + } + } + + @Data + @Wither + static class Employee { + + private final String name; + private final String role; + } +}