pagination work

This commit is contained in:
eugenp 2014-01-05 18:21:51 +02:00
parent c7ce725cf6
commit bce77ee846
11 changed files with 347 additions and 24 deletions

View File

@ -159,6 +159,13 @@
<!-- test scoped -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit-dep</artifactId>

View File

@ -1,6 +1,5 @@
package org.baeldung.web.controller;
import java.net.URI;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@ -10,7 +9,6 @@ import org.baeldung.persistence.model.Foo;
import org.baeldung.persistence.service.IFooService;
import org.baeldung.web.exception.MyResourceNotFoundException;
import org.baeldung.web.hateoas.PaginatedResultsRetrievedEvent;
import org.baeldung.web.util.LinkUtil;
import org.baeldung.web.util.ResourceCreated;
import org.baeldung.web.util.RestPreconditions;
import org.baeldung.web.util.SingleResourceRetrieved;
@ -27,7 +25,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriTemplate;
import com.google.common.base.Preconditions;
@ -77,21 +74,9 @@ public class FooController {
return resultPage.getContent();
}
// discover
@RequestMapping(value = "admin", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) {
final String rootUri = request.getRequestURL().toString();
final URI fooUri = new UriTemplate("{rootUri}/{resource}").expand(rootUri, "foo");
final String linkToFoo = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection");
response.addHeader("Link", linkToFoo);
}
// write
@RequestMapping(value = "admin/foo", method = RequestMethod.POST)
@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public void create(@RequestBody final Foo resource, final HttpServletRequest request, final HttpServletResponse response) {
Preconditions.checkNotNull(resource);

View File

@ -0,0 +1,37 @@
package org.baeldung.web.controller;
import java.net.URI;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.baeldung.web.util.LinkUtil;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.util.UriTemplate;
@Controller
public class RootController {
public RootController() {
super();
}
// API
// discover
@RequestMapping(value = "admin", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) {
final String rootUri = request.getRequestURL().toString();
final URI fooUri = new UriTemplate("{rootUri}/{resource}").expand(rootUri, "foo");
final String linkToFoo = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection");
response.addHeader("Link", linkToFoo);
}
}

View File

@ -28,8 +28,14 @@ public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionH
// 400
@ExceptionHandler({ ConstraintViolationException.class, DataIntegrityViolationException.class })
public ResponseEntity<Object> handleBadRequest(final RuntimeException ex, final WebRequest request) {
@ExceptionHandler({ ConstraintViolationException.class })
public ResponseEntity<Object> handleBadRequest(final ConstraintViolationException ex, final WebRequest request) {
final String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
@ExceptionHandler({ DataIntegrityViolationException.class })
public ResponseEntity<Object> handleBadRequest(final DataIntegrityViolationException ex, final WebRequest request) {
final String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
@ -52,7 +58,7 @@ public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionH
// 404
@ExceptionHandler(value = { EntityNotFoundException.class, MyResourceNotFoundException.class })
protected ResponseEntity<Object> handleBadRequest(final EntityNotFoundException ex, final WebRequest request) {
protected ResponseEntity<Object> handleNotFound(final RuntimeException ex, final WebRequest request) {
final String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
}

View File

@ -100,7 +100,7 @@ class PaginatedResultsRetrievedDiscoverabilityListener implements ApplicationLis
// template
protected void plural(final UriComponentsBuilder uriBuilder, final Class clazz) {
final String resourceName = clazz.getSimpleName() + "s";
final String resourceName = clazz.getSimpleName().toLowerCase() + "s";
uriBuilder.path("/" + resourceName);
}

View File

@ -1,16 +1,22 @@
package org.baeldung.common.web;
import static org.apache.commons.lang3.RandomStringUtils.randomNumeric;
import static org.baeldung.web.util.HTTPLinkHeaderUtil.extractURIByRel;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import java.io.Serializable;
import java.util.List;
import org.baeldung.test.IMarshaller;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.google.common.base.Preconditions;
import com.google.common.net.HttpHeaders;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.response.Response;
import com.jayway.restassured.specification.RequestSpecification;
@ -19,6 +25,9 @@ public abstract class AbstractLiveTest<T extends Serializable> {
protected final Class<T> clazz;
@Autowired
protected IMarshaller marshaller;
public AbstractLiveTest(final Class<T> clazzToSet) {
super();
@ -36,31 +45,96 @@ public abstract class AbstractLiveTest<T extends Serializable> {
@Test
public void whenResourcesAreRetrievedPaged_then200IsReceived() {
final Response response = givenAuth().get(getFooURL() + "?page=1&size=10");
final Response response = givenAuth().get(getFooURL() + "?page=0&size=10");
assertThat(response.getStatusCode(), is(200));
}
@Test
public void whenPageOfResourcesAreRetrievedOutOfBounds_then404IsReceived() {
final Response response = givenAuth().get(getFooURL() + "?page=" + randomNumeric(5) + "&size=10");
final String url = getFooURL() + "?page=" + randomNumeric(5) + "&size=10";
final Response response = givenAuth().get(url);
assertThat(response.getStatusCode(), is(404));
}
@Test
// @Ignore("create is not done yet")
public void givenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources() {
// restTemplate.createResource();
create();
final Response response = givenAuth().get(getFooURL() + "?page=1&size=10");
final Response response = givenAuth().get(getFooURL() + "?page=0&size=10");
assertFalse(response.body().as(List.class).isEmpty());
}
@Test
public void whenFirstPageOfResourcesAreRetrieved_thenSecondPageIsNext() {
final Response response = givenAuth().get(getFooURL() + "?page=0&size=2");
final String uriToNextPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "next");
assertEquals(getFooURL() + "?page=1&size=2", uriToNextPage);
}
@Test
public void whenFirstPageOfResourcesAreRetrieved_thenNoPreviousPage() {
final Response response = givenAuth().get(getFooURL() + "?page=0&size=2");
final String uriToPrevPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "prev");
assertNull(uriToPrevPage);
}
@Test
public void whenSecondPageOfResourcesAreRetrieved_thenFirstPageIsPrevious() {
create();
create();
final Response response = givenAuth().get(getFooURL() + "?page=1&size=2");
final String uriToPrevPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "prev");
assertEquals(getFooURL() + "?page=0&size=2", uriToPrevPage);
}
@Test
public void whenLastPageOfResourcesIsRetrieved_thenNoNextPageIsDiscoverable() {
final Response first = givenAuth().get(getFooURL() + "?page=0&size=2");
final String uriToLastPage = extractURIByRel(first.getHeader(HttpHeaders.LINK), "last");
final Response response = givenAuth().get(uriToLastPage);
final String uriToNextPage = extractURIByRel(response.getHeader(HttpHeaders.LINK), "next");
assertNull(uriToNextPage);
}
// count
// template method
public abstract void create();
protected final void create(final T resource) {
createAsUri(resource);
}
final String createAsUri(final T resource) {
final Response response = createAsResponse(resource);
Preconditions.checkState(response.getStatusCode() == 201, "create operation: " + response.getStatusCode());
final String locationOfCreatedResource = response.getHeader(HttpHeaders.LOCATION);
Preconditions.checkNotNull(locationOfCreatedResource);
return locationOfCreatedResource;
}
final Response createAsResponse(final T resource) {
Preconditions.checkNotNull(resource);
final RequestSpecification givenAuthenticated = givenAuth();
final String resourceAsString = marshaller.encode(resource);
return givenAuthenticated.contentType(marshaller.getMime()).body(resourceAsString).post(getFooURL());
}
//
private String getFooURL() {
return "http://localhost:8080/spring-security-rest-full/foos";
}

View File

@ -0,0 +1,17 @@
package org.baeldung.spring;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
@ComponentScan("org.baeldung.test")
public class ConfigTest extends WebMvcConfigurerAdapter {
public ConfigTest() {
super();
}
// API
}

View File

@ -0,0 +1,15 @@
package org.baeldung.test;
import java.util.List;
public interface IMarshaller {
<T> String encode(final T entity);
<T> T decode(final String entityAsString, final Class<T> clazz);
<T> List<T> decodeList(final String entitiesAsString, final Class<T> clazz);
String getMime();
}

View File

@ -0,0 +1,97 @@
package org.baeldung.test;
import java.io.IOException;
import java.util.List;
import org.baeldung.persistence.model.Foo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
@Component
public final class JacksonMarshaller implements IMarshaller {
private final Logger logger = LoggerFactory.getLogger(JacksonMarshaller.class);
private final ObjectMapper objectMapper;
public JacksonMarshaller() {
super();
objectMapper = new ObjectMapper();
}
// API
@Override
public final <T> String encode(final T resource) {
Preconditions.checkNotNull(resource);
String entityAsJSON = null;
try {
entityAsJSON = objectMapper.writeValueAsString(resource);
} catch (final JsonParseException parseEx) {
logger.error("", parseEx);
} catch (final JsonMappingException mappingEx) {
logger.error("", mappingEx);
} catch (final IOException ioEx) {
logger.error("", ioEx);
}
return entityAsJSON;
}
@Override
public final <T> T decode(final String resourceAsString, final Class<T> clazz) {
Preconditions.checkNotNull(resourceAsString);
T entity = null;
try {
entity = objectMapper.readValue(resourceAsString, clazz);
} catch (final JsonParseException parseEx) {
logger.error("", parseEx);
} catch (final JsonMappingException mappingEx) {
logger.error("", mappingEx);
} catch (final IOException ioEx) {
logger.error("", ioEx);
}
return entity;
}
@SuppressWarnings("unchecked")
@Override
public final <T> List<T> decodeList(final String resourcesAsString, final Class<T> clazz) {
Preconditions.checkNotNull(resourcesAsString);
List<T> entities = null;
try {
if (clazz.equals(Foo.class)) {
entities = objectMapper.readValue(resourcesAsString, new TypeReference<List<Foo>>() {
// ...
});
} else {
entities = objectMapper.readValue(resourcesAsString, List.class);
}
} catch (final JsonParseException parseEx) {
logger.error("", parseEx);
} catch (final JsonMappingException mappingEx) {
logger.error("", mappingEx);
} catch (final IOException ioEx) {
logger.error("", ioEx);
}
return entities;
}
@Override
public final String getMime() {
return MediaType.APPLICATION_JSON.toString();
}
}

View File

@ -1,12 +1,28 @@
package org.baeldung.web;
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
import org.baeldung.common.web.AbstractLiveTest;
import org.baeldung.persistence.model.Foo;
import org.baeldung.spring.ConfigTest;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { ConfigTest.class }, loader = AnnotationConfigContextLoader.class)
public class FooLiveTest extends AbstractLiveTest<Foo> {
public FooLiveTest() {
super(Foo.class);
}
// API
@Override
public final void create() {
create(new Foo(randomAlphabetic(6)));
}
}

View File

@ -0,0 +1,69 @@
package org.baeldung.web.util;
import java.util.List;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
public final class HTTPLinkHeaderUtil {
private HTTPLinkHeaderUtil() {
throw new AssertionError();
}
//
/**
* ex. <br>
* https://api.github.com/users/steveklabnik/gists?page=2>; rel="next", <https://api.github.com/users/steveklabnik/gists?page=3>; rel="last"
*/
public static List<String> extractAllURIs(final String linkHeader) {
Preconditions.checkNotNull(linkHeader);
final List<String> linkHeaders = Lists.newArrayList();
final String[] links = linkHeader.split(", ");
for (final String link : links) {
final int positionOfSeparator = link.indexOf(';');
linkHeaders.add(link.substring(1, positionOfSeparator - 1));
}
return linkHeaders;
}
public static String extractURIByRel(final String linkHeader, final String rel) {
if (linkHeader == null) {
return null;
}
String uriWithSpecifiedRel = null;
final String[] links = linkHeader.split(", ");
String linkRelation = null;
for (final String link : links) {
final int positionOfSeparator = link.indexOf(';');
linkRelation = link.substring(positionOfSeparator + 1, link.length()).trim();
if (extractTypeOfRelation(linkRelation).equals(rel)) {
uriWithSpecifiedRel = link.substring(1, positionOfSeparator - 1);
break;
}
}
return uriWithSpecifiedRel;
}
static Object extractTypeOfRelation(final String linkRelation) {
final int positionOfEquals = linkRelation.indexOf('=');
return linkRelation.substring(positionOfEquals + 2, linkRelation.length() - 1).trim();
}
/**
* ex. <br>
* https://api.github.com/users/steveklabnik/gists?page=2>; rel="next"
*/
public static String extractSingleURI(final String linkHeader) {
Preconditions.checkNotNull(linkHeader);
final int positionOfSeparator = linkHeader.indexOf(';');
return linkHeader.substring(1, positionOfSeparator - 1);
}
}