diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java
index 6cd925ec..2f99b454 100755
--- a/src/main/java/org/springframework/hateoas/Link.java
+++ b/src/main/java/org/springframework/hateoas/Link.java
@@ -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)
diff --git a/src/main/java/org/springframework/hateoas/Links.java b/src/main/java/org/springframework/hateoas/Links.java
index 51a27c07..178a90cd 100644
--- a/src/main/java/org/springframework/hateoas/Links.java
+++ b/src/main/java/org/springframework/hateoas/Links.java
@@ -87,6 +87,16 @@ public class Links implements Iterable {
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 {
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()
diff --git a/src/main/java/org/springframework/hateoas/PagedResources.java b/src/main/java/org/springframework/hateoas/PagedResources.java
index 2aa03649..c027237f 100644
--- a/src/main/java/org/springframework/hateoas/PagedResources.java
+++ b/src/main/java/org/springframework/hateoas/PagedResources.java
@@ -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 extends Resources {
+ public static PagedResources> NO_PAGE = new PagedResources();
+
@org.codehaus.jackson.annotate.JsonProperty("page")
@com.fasterxml.jackson.annotation.JsonProperty("page")
private PageMetadata metadata;
@@ -97,13 +99,37 @@ public class PagedResources extends Resources {
return new PagedResources(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 extends Resources {
}
/**
+ * 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.
*
diff --git a/src/test/java/org/springframework/hateoas/PagedResourcesUnitTest.java b/src/test/java/org/springframework/hateoas/PagedResourcesUnitTest.java
new file mode 100644
index 00000000..512b806f
--- /dev/null
+++ b/src/test/java/org/springframework/hateoas/PagedResourcesUnitTest.java
@@ -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 resources;
+
+ @Before
+ public void setUp() {
+ resources = new PagedResources(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()));
+ }
+}
diff --git a/src/test/java/org/springframework/hateoas/hal/Jackson1HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/Jackson1HalIntegrationTest.java
index 441b72ca..6bd6edf7 100644
--- a/src/test/java/org/springframework/hateoas/hal/Jackson1HalIntegrationTest.java
+++ b/src/test/java/org/springframework/hateoas/hal/Jackson1HalIntegrationTest.java
@@ -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> 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> setupAnnotatedPagedResources() {
+
+ List> content = new ArrayList>();
+ content.add(new Resource(new SimpleAnnotatedPojo("test1", 1), new Link("localhost")));
+ content.add(new Resource(new SimpleAnnotatedPojo("test2", 2), new Link("localhost")));
+
+ return new PagedResources>(content, new PageMetadata(2, 0, 4), PAGINATION_LINKS);
+ }
+
private static Resources> setupAnnotatedResources() {
List> content = new ArrayList>();
diff --git a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java
index 6fe7497b..b9213cef 100644
--- a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java
+++ b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java
@@ -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> 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> setupAnnotatedPagedResources() {
+
+ List> content = new ArrayList>();
+ content.add(new Resource(new SimpleAnnotatedPojo("test1", 1), new Link("localhost")));
+ content.add(new Resource(new SimpleAnnotatedPojo("test2", 2), new Link("localhost")));
+
+ return new PagedResources>(content, new PageMetadata(2, 0, 4), PAGINATION_LINKS);
+ }
+
private static Resources> setupAnnotatedResources() {
List> content = new ArrayList>();