diff --git a/spring-security-rest-full/pom.xml b/spring-security-rest-full/pom.xml index 997b99ffdf..3eb5bc9bd6 100644 --- a/spring-security-rest-full/pom.xml +++ b/spring-security-rest-full/pom.xml @@ -159,6 +159,13 @@ + + org.springframework + spring-test + ${org.springframework.version} + test + + junit junit-dep diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/controller/FooController.java b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/FooController.java index 7a9889ae46..5e05db1056 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/controller/FooController.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/FooController.java @@ -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); diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/controller/RootController.java b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/RootController.java new file mode 100644 index 0000000000..0f35a29338 --- /dev/null +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/RootController.java @@ -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); + } + +} diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/error/RestResponseEntityExceptionHandler.java b/spring-security-rest-full/src/main/java/org/baeldung/web/error/RestResponseEntityExceptionHandler.java index be2d4331c1..a465a82d75 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/error/RestResponseEntityExceptionHandler.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/error/RestResponseEntityExceptionHandler.java @@ -28,8 +28,14 @@ public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionH // 400 - @ExceptionHandler({ ConstraintViolationException.class, DataIntegrityViolationException.class }) - public ResponseEntity handleBadRequest(final RuntimeException ex, final WebRequest request) { + @ExceptionHandler({ ConstraintViolationException.class }) + public ResponseEntity 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 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 handleBadRequest(final EntityNotFoundException ex, final WebRequest request) { + protected ResponseEntity 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); } diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedDiscoverabilityListener.java b/spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedDiscoverabilityListener.java index 5d24a38ccb..dc65c754c6 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedDiscoverabilityListener.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedDiscoverabilityListener.java @@ -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); } diff --git a/spring-security-rest-full/src/test/java/org/baeldung/common/web/AbstractLiveTest.java b/spring-security-rest-full/src/test/java/org/baeldung/common/web/AbstractLiveTest.java index c205997a08..b16c6ca041 100644 --- a/spring-security-rest-full/src/test/java/org/baeldung/common/web/AbstractLiveTest.java +++ b/spring-security-rest-full/src/test/java/org/baeldung/common/web/AbstractLiveTest.java @@ -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 { protected final Class clazz; + @Autowired + protected IMarshaller marshaller; + public AbstractLiveTest(final Class clazzToSet) { super(); @@ -36,31 +45,96 @@ public abstract class AbstractLiveTest { @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"; } diff --git a/spring-security-rest-full/src/test/java/org/baeldung/spring/ConfigTest.java b/spring-security-rest-full/src/test/java/org/baeldung/spring/ConfigTest.java new file mode 100644 index 0000000000..56f3de6cb0 --- /dev/null +++ b/spring-security-rest-full/src/test/java/org/baeldung/spring/ConfigTest.java @@ -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 + +} \ No newline at end of file diff --git a/spring-security-rest-full/src/test/java/org/baeldung/test/IMarshaller.java b/spring-security-rest-full/src/test/java/org/baeldung/test/IMarshaller.java new file mode 100644 index 0000000000..1eefbe8789 --- /dev/null +++ b/spring-security-rest-full/src/test/java/org/baeldung/test/IMarshaller.java @@ -0,0 +1,15 @@ +package org.baeldung.test; + +import java.util.List; + +public interface IMarshaller { + + String encode(final T entity); + + T decode(final String entityAsString, final Class clazz); + + List decodeList(final String entitiesAsString, final Class clazz); + + String getMime(); + +} diff --git a/spring-security-rest-full/src/test/java/org/baeldung/test/JacksonMarshaller.java b/spring-security-rest-full/src/test/java/org/baeldung/test/JacksonMarshaller.java new file mode 100644 index 0000000000..99deafaee4 --- /dev/null +++ b/spring-security-rest-full/src/test/java/org/baeldung/test/JacksonMarshaller.java @@ -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 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 decode(final String resourceAsString, final Class 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 List decodeList(final String resourcesAsString, final Class clazz) { + Preconditions.checkNotNull(resourcesAsString); + + List entities = null; + try { + if (clazz.equals(Foo.class)) { + entities = objectMapper.readValue(resourcesAsString, new TypeReference>() { + // ... + }); + } 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(); + } + +} diff --git a/spring-security-rest-full/src/test/java/org/baeldung/web/FooLiveTest.java b/spring-security-rest-full/src/test/java/org/baeldung/web/FooLiveTest.java index 18f0659af6..b158b22376 100644 --- a/spring-security-rest-full/src/test/java/org/baeldung/web/FooLiveTest.java +++ b/spring-security-rest-full/src/test/java/org/baeldung/web/FooLiveTest.java @@ -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 { public FooLiveTest() { super(Foo.class); } + // API + + @Override + public final void create() { + create(new Foo(randomAlphabetic(6))); + } + } diff --git a/spring-security-rest-full/src/test/java/org/baeldung/web/util/HTTPLinkHeaderUtil.java b/spring-security-rest-full/src/test/java/org/baeldung/web/util/HTTPLinkHeaderUtil.java new file mode 100644 index 0000000000..bb3919eacc --- /dev/null +++ b/spring-security-rest-full/src/test/java/org/baeldung/web/util/HTTPLinkHeaderUtil.java @@ -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.
+ * https://api.github.com/users/steveklabnik/gists?page=2>; rel="next", ; rel="last" + */ + public static List extractAllURIs(final String linkHeader) { + Preconditions.checkNotNull(linkHeader); + + final List 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.
+ * 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); + } + +}