#64 - Extended support for PagedResources.

Changed Relation type to identify previous links to standardized value of prev. Added integration tests for HAL rendering of PagedResources.
This commit is contained in:
Oliver Gierke
2013-05-15 17:48:51 +02:00
parent 17d91aca55
commit a9ccfc6782
6 changed files with 203 additions and 3 deletions

View File

@@ -42,7 +42,7 @@ public class Link implements Serializable {
public static final String REL_SELF = "self";
public static final String REL_FIRST = "first";
public static final String REL_PREVIOUS = "previous";
public static final String REL_PREVIOUS = "prev";
public static final String REL_NEXT = "next";
public static final String REL_LAST = "last";
@@ -101,6 +101,25 @@ public class Link implements Serializable {
return rel;
}
/**
* Returns a {@link Link} pointing to the same URI but with the given relation.
*
* @param rel must not be {@literal null} or empty.
* @return
*/
public Link withRel(String rel) {
return new Link(href, rel);
}
/**
* Returns a {@link Link} pointing to the same URI but with the {@code self} relation.
*
* @return
*/
public Link withSelfRel() {
return withRel(Link.REL_SELF);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)

View File

@@ -87,6 +87,16 @@ public class Links implements Iterable<Link> {
return result;
}
/**
* Returns whether the {@link Links} container contains a {@link Link} with the given rel.
*
* @param rel
* @return
*/
public boolean hasLink(String rel) {
return getLink(rel) != null;
}
/**
* Creates a {@link Links} instance from the given RFC5988-compatible link format.
*
@@ -114,6 +124,15 @@ public class Links implements Iterable<Link> {
return new Links(links);
}
/**
* Returns whether the {@link Links} containter is empty.
*
* @return
*/
public boolean isEmpty() {
return links.isEmpty();
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()

View File

@@ -34,6 +34,8 @@ import org.springframework.util.Assert;
@com.fasterxml.jackson.annotation.JsonAutoDetect(fieldVisibility = com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY)
public class PagedResources<T> extends Resources<T> {
public static PagedResources<?> NO_PAGE = new PagedResources<Object>();
@org.codehaus.jackson.annotate.JsonProperty("page")
@com.fasterxml.jackson.annotation.JsonProperty("page")
private PageMetadata metadata;
@@ -97,13 +99,37 @@ public class PagedResources<T> extends Resources<T> {
return new PagedResources<T>(resources, metadata);
}
/**
* Returns the Link pointing to the next page (if set).
*
* @see #addPaginationLinks(Link)
* @return
*/
@com.fasterxml.jackson.annotation.JsonIgnore
@org.codehaus.jackson.annotate.JsonIgnore
public Link getNextLink() {
return getLink(Link.REL_NEXT);
}
/**
* Returns the Link pointing to the previous page (if set).
*
* @see #addPaginationLinks(Link)
* @return
*/
@com.fasterxml.jackson.annotation.JsonIgnore
@org.codehaus.jackson.annotate.JsonIgnore
public Link getPreviousLink() {
return getLink(Link.REL_PREVIOUS);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.ResourceSupport#toString()
*/
@Override
public String toString() {
return String.format("PagedResource { content: %s, metadata: %s }", getContent(), metadata);
return String.format("PagedResource { content: %s, metadata: %s, links: %s }", getContent(), metadata, getLinks());
}
/*
@@ -173,19 +199,35 @@ public class PagedResources<T> extends Resources<T> {
}
/**
* Creates a new {@link PageMetadata} from the given size, number, total elements and total pages.
*
* @param size
* @param number
* @param number zero-indexed, must be less than totalPages
* @param totalElements
* @param totalPages
*/
public PageMetadata(long size, long number, long totalElements, long totalPages) {
Assert.isTrue(number < totalPages,
"The current page number cannot be greater or equal to the total number of pages!");
this.size = size;
this.number = number;
this.totalElements = totalElements;
this.totalPages = totalPages;
}
/**
* Creates a new {@link PageMetadata} from the given size, numer and total elements.
*
* @param size the size of the page
* @param number the number of the page
* @param totalElements the total number of elements available
*/
public PageMetadata(long size, long number, long totalElements) {
this(size, number, totalElements, totalElements / size);
}
/**
* Returns the requested size of the page.
*

View File

@@ -0,0 +1,58 @@
/*
* 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;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.hateoas.PagedResources.PageMetadata;
/**
* Unit tests for {@link PagedResources}.
*
* @author Oliver Gierke
*/
public class PagedResourcesUnitTest {
static final PageMetadata metadata = new PagedResources.PageMetadata(10, 1, 200);
PagedResources<Object> resources;
@Before
public void setUp() {
resources = new PagedResources<Object>(Collections.emptyList(), metadata);
}
@Test
public void discoversNextLink() {
resources.add(new Link("foo", Link.REL_NEXT));
assertThat(resources.getNextLink(), is(notNullValue()));
}
@Test
public void discoversPreviousLink() {
resources.add(new Link("custom", Link.REL_PREVIOUS));
assertThat(resources.getPreviousLink(), is(notNullValue()));
}
}

View File

@@ -25,6 +25,9 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.hateoas.AbstractMarshallingIntegrationTests;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.PagedResources.PageMetadata;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
@@ -49,6 +52,10 @@ public class Jackson1HalIntegrationTest extends AbstractMarshallingIntegrationTe
static final String ANNOTATED_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"pojo\":{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}}}";
static final String ANNOTATED_EMBEDDED_RESOURCES_REFERENCE = "{\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]}}";
static final String ANNOTATED_PAGED_RESOURCES = "{\"_links\":{\"next\":{\"href\":\"foo\"},\"prev\":{\"href\":\"bar\"}},\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"page\":{\"size\":2,\"totalElements\":4,\"totalPages\":2,\"number\":0}}";
static final Links PAGINATION_LINKS = new Links(new Link("foo", Link.REL_NEXT), new Link("bar", Link.REL_PREVIOUS));
@Before
public void setUpModule() {
@@ -232,6 +239,30 @@ public class Jackson1HalIntegrationTest extends AbstractMarshallingIntegrationTe
assertThat(result, is(setupAnnotatedResources()));
}
@Test
public void serializesPagedResource() throws Exception {
assertThat(write(setupAnnotatedPagedResources()), is(ANNOTATED_PAGED_RESOURCES));
}
@Test
public void deserializesPagedResource() throws Exception {
PagedResources<Resource<SimpleAnnotatedPojo>> result = mapper.readValue(
ANNOTATED_PAGED_RESOURCES,
mapper.getTypeFactory().constructParametricType(PagedResources.class,
mapper.getTypeFactory().constructParametricType(Resource.class, SimpleAnnotatedPojo.class)));
assertThat(result, is(setupAnnotatedPagedResources()));
}
private static Resources<Resource<SimpleAnnotatedPojo>> setupAnnotatedPagedResources() {
List<Resource<SimpleAnnotatedPojo>> content = new ArrayList<Resource<SimpleAnnotatedPojo>>();
content.add(new Resource<SimpleAnnotatedPojo>(new SimpleAnnotatedPojo("test1", 1), new Link("localhost")));
content.add(new Resource<SimpleAnnotatedPojo>(new SimpleAnnotatedPojo("test2", 2), new Link("localhost")));
return new PagedResources<Resource<SimpleAnnotatedPojo>>(content, new PageMetadata(2, 0, 4), PAGINATION_LINKS);
}
private static Resources<Resource<SimpleAnnotatedPojo>> setupAnnotatedResources() {
List<Resource<SimpleAnnotatedPojo>> content = new ArrayList<Resource<SimpleAnnotatedPojo>>();

View File

@@ -25,6 +25,9 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.hateoas.AbstractJackson2MarshallingIntegrationTests;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.PagedResources.PageMetadata;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
@@ -49,6 +52,10 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg
static final String ANNOTATED_EMBEDDED_RESOURCE_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}},\"_embedded\":{\"pojo\":{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}}}";
static final String ANNOTATED_EMBEDDED_RESOURCES_REFERENCE = "{\"_links\":{},\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]}}";
static final String ANNOTATED_PAGED_RESOURCES = "{\"_links\":{\"next\":{\"href\":\"foo\"},\"prev\":{\"href\":\"bar\"}},\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"page\":{\"size\":2,\"totalElements\":4,\"totalPages\":2,\"number\":0}}";
static final Links PAGINATION_LINKS = new Links(new Link("foo", Link.REL_NEXT), new Link("bar", Link.REL_PREVIOUS));
@Before
public void setUpModule() {
@@ -238,6 +245,30 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg
assertThat(result, is(setupAnnotatedResources()));
}
@Test
public void serializesPagedResource() throws Exception {
assertThat(write(setupAnnotatedPagedResources()), is(ANNOTATED_PAGED_RESOURCES));
}
@Test
public void deserializesPagedResource() throws Exception {
PagedResources<Resource<SimpleAnnotatedPojo>> result = mapper.readValue(
ANNOTATED_PAGED_RESOURCES,
mapper.getTypeFactory().constructParametricType(PagedResources.class,
mapper.getTypeFactory().constructParametricType(Resource.class, SimpleAnnotatedPojo.class)));
assertThat(result, is(setupAnnotatedPagedResources()));
}
private static Resources<Resource<SimpleAnnotatedPojo>> setupAnnotatedPagedResources() {
List<Resource<SimpleAnnotatedPojo>> content = new ArrayList<Resource<SimpleAnnotatedPojo>>();
content.add(new Resource<SimpleAnnotatedPojo>(new SimpleAnnotatedPojo("test1", 1), new Link("localhost")));
content.add(new Resource<SimpleAnnotatedPojo>(new SimpleAnnotatedPojo("test2", 2), new Link("localhost")));
return new PagedResources<Resource<SimpleAnnotatedPojo>>(content, new PageMetadata(2, 0, 4), PAGINATION_LINKS);
}
private static Resources<Resource<SimpleAnnotatedPojo>> setupAnnotatedResources() {
List<Resource<SimpleAnnotatedPojo>> content = new ArrayList<Resource<SimpleAnnotatedPojo>>();