diff --git a/src/main/java/org/springframework/hateoas/ResourceSupport.java b/src/main/java/org/springframework/hateoas/ResourceSupport.java index c2cb23ff..cfb5cca4 100755 --- a/src/main/java/org/springframework/hateoas/ResourceSupport.java +++ b/src/main/java/org/springframework/hateoas/ResourceSupport.java @@ -98,6 +98,13 @@ public class ResourceSupport implements Identifiable { return Collections.unmodifiableList(links); } + /** + * Removes all {@link Link}s added to the resource so far. + */ + public void removeLinks() { + this.links.clear(); + } + /** * Returns the link with the given rel. * diff --git a/src/main/java/org/springframework/hateoas/mvc/HeaderLinksResponseEntity.java b/src/main/java/org/springframework/hateoas/mvc/HeaderLinksResponseEntity.java new file mode 100644 index 00000000..a9450744 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/mvc/HeaderLinksResponseEntity.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013 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.mvc; + +import java.util.List; + +import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; +import org.springframework.hateoas.ResourceProcessor; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; + +/** + * Special {@link ResponseEntity} that exposes {@link Link} instances in the contained {@link ResourceSupport} as link + * headers instead of in the body. Note, that this class is not intended to be used directly from user code but by + * support code that will transparently invoke the header exposure. If you use this class from a controller directly, + * the {@link Link}s will not be present in the {@link ResourceSupport} instance anymore when {@link ResourceProcessor}s + * kick in. + * + * @author Oliver Gierke + */ +public class HeaderLinksResponseEntity extends ResponseEntity { + + /** + * Creates a new {@link HeaderLinksResponseEntity} from the given {@link ResponseEntity}. + * + * @param entity must not be {@literal null}. + */ + private HeaderLinksResponseEntity(ResponseEntity entity) { + + super(entity.getBody(), getHeadersWithLinks(entity), entity.getStatusCode()); + entity.getBody().removeLinks(); + } + + /** + * Creates a new {@link HeaderLinksResponseEntity} from the given {@link HttpEntity} by defaulting the status code to + * {@link HttpStatus#OK}. + * + * @param entity must not be {@literal null}. + */ + private HeaderLinksResponseEntity(HttpEntity entity) { + this(new ResponseEntity(entity.getBody(), entity.getHeaders(), HttpStatus.OK)); + } + + /** + * Wraps the given {@link HttpEntity} into a {@link HeaderLinksResponseEntity}. Will default the status code to + * {@link HttpStatus#OK} if the given value is not a {@link ResponseEntity}. + * + * @param entity must not be {@literal null}. + * @return + */ + public static HeaderLinksResponseEntity wrap(HttpEntity entity) { + + Assert.notNull(entity, "Given HttpEntity must not be null!"); + + if (entity instanceof ResponseEntity) { + return new HeaderLinksResponseEntity((ResponseEntity) entity); + } else { + return new HeaderLinksResponseEntity(entity); + } + } + + /** + * Returns the {@link Link}s contained in the {@link ResourceSupport} of the given {@link ResponseEntity} as + * {@link HttpHeaders}. + * + * @param entity must not be {@literal null}. + * @return + */ + private static HttpHeaders getHeadersWithLinks(ResponseEntity entity) { + + List links = entity.getBody().getLinks(); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.putAll(entity.getHeaders()); + httpHeaders.add("Link", new Links(links).toString()); + + return httpHeaders; + } +} diff --git a/src/test/java/org/springframework/hateoas/mvc/HeaderLinksResponseEntityUnitTests.java b/src/test/java/org/springframework/hateoas/mvc/HeaderLinksResponseEntityUnitTests.java new file mode 100644 index 00000000..43b2fea6 --- /dev/null +++ b/src/test/java/org/springframework/hateoas/mvc/HeaderLinksResponseEntityUnitTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013 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.mvc; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.List; + +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.Resource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +/** + * Unit tests for {@link HeaderLinksResponseEntity}. + * + * @author Oliver Gierke + */ +public class HeaderLinksResponseEntityUnitTests { + + static final Object CONTENT = new Object(); + static final Link LINK = new Link("href", "rel"); + + Resource resource = new Resource(CONTENT, LINK); + ResponseEntity> entity = new ResponseEntity>(resource, HttpStatus.OK); + + @Test + public void movesRootResourceLinksToHeader() { + + HttpEntity> wrapper = HeaderLinksResponseEntity.wrap(entity); + + // No links in resource anymore + assertThat(wrapper.getBody().getLinks(), is(Matchers. empty())); + + // Link found in header + List linkHeader = wrapper.getHeaders().get("Link"); + assertThat(linkHeader, hasSize(1)); + + Link link = Link.valueOf(linkHeader.get(0)); + assertThat(link, is(LINK)); + } + + @Test + public void defaultStatusCodeToOkForHttpEntities() { + + HttpEntity> entity = new HttpEntity>(resource); + ResponseEntity> wrappedEntity = HeaderLinksResponseEntity.wrap(entity); + + assertThat(wrappedEntity.getStatusCode(), is(HttpStatus.OK)); + } +}