diff --git a/queries/sample-requests.rest b/queries/sample-requests.rest index edf0eadaca9e16bf4109ae0e61ac523a460cd65a..2c2e2235bb00c6f37a348c34450f3bcb30fb2262 100644 --- a/queries/sample-requests.rest +++ b/queries/sample-requests.rest @@ -1,8 +1,8 @@ ### Get a Hello message -GET http://localhost:9998/mylibrary/setup +GET http://localhost:9998/mylibrary/library ### Init the database with two authors -PUT http://localhost:9998/mylibrary/setup/init +PUT http://localhost:9998/mylibrary/library/init ### Get author 1 in JSON GET http://localhost:9998/mylibrary/authors/1 @@ -38,7 +38,7 @@ Content-type: application/json {"name":"John","firstname":"Smith","biography":"My life"} ### ReInit the database with two authors -PUT http://localhost:9998/mylibrary/setup/init +PUT http://localhost:9998/mylibrary/library/init ### Fully update an author PUT http://localhost:9998/mylibrary/authors/1 @@ -51,7 +51,7 @@ Content-type: application/json GET http://localhost:9998/mylibrary/authors/1000 Accept: application/json -### If a resource doesn't exist an exception is raised, and the 404 http status code is returned +### TODO : (FIX IT) If a resource doesn't exist an exception is raised, and the 404 http status code is returned GET http://localhost:9998/mylibrary/authors/1000 Accept: text/xml @@ -65,7 +65,7 @@ Accept: application/json sortKey: firstname ### Init the database with 10k random authors -PUT http://localhost:9998/mylibrary/setup/init/10000 +PUT http://localhost:9998/mylibrary/library/init/10000 ### Get page 3 with page size of 10 authors sorted by lastname GET http://localhost:9998/mylibrary/authors/page?pageSize=10&page=3 diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/model/Library.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/model/Library.java index 0b5410257d7bea05f6a75ccef1cb1496848b38d3..c42b95e416ad4b04ffb2a4190e6ecb0688d5a9b3 100644 --- a/src/main/java/fr/univtln/bruno/samples/jaxrs/model/Library.java +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/model/Library.java @@ -42,9 +42,12 @@ public class Library { @JsonIgnore final MutableLongObjectMap<Author> authors = LongObjectMaps.mutable.empty(); + final MutableLongObjectMap<Book> books = LongObjectMaps.mutable.empty(); + private static final String AUTHOR_NOT_FOUND = "Author not found"; /** * used mainly to provide easy XML Serialization + * * @return the list of authors */ @XmlElementWrapper(name = "authors") @@ -53,8 +56,10 @@ public class Library { public List<Author> getAuthorsAsList() { return authors.toList(); } + /** * used mainly to provide easy XML Serialization + * * @return the list of books */ @XmlElementWrapper(name = "books") @@ -64,8 +69,6 @@ public class Library { return books.toList(); } - final MutableLongObjectMap<Book> books = LongObjectMaps.mutable.empty(); - /** * Adds an author to the model. * @@ -113,7 +116,7 @@ public class Library { if (author.id != 0) throw new BusinessException(Response.Status.INTERNAL_SERVER_ERROR, "Id shouldn't be given in data"); author.id = id; - if (!authors.containsKey(id)) throw new BusinessException(Response.Status.NOT_FOUND, "Author not found"); + if (!authors.containsKey(id)) throw new BusinessException(Response.Status.NOT_FOUND, AUTHOR_NOT_FOUND); authors.put(id, author); return author; } @@ -125,7 +128,7 @@ public class Library { * @throws BusinessException if not found */ public void removeAuthor(long id) throws BusinessException { - if (!authors.containsKey(id)) throw new BusinessException(Response.Status.NOT_FOUND, "Author not found"); + if (!authors.containsKey(id)) throw new BusinessException(Response.Status.NOT_FOUND, AUTHOR_NOT_FOUND); authors.remove(id); } @@ -137,7 +140,7 @@ public class Library { * @throws NotFoundException if not found exception */ public Author getAuthor(long id) throws BusinessException { - if (!authors.containsKey(id)) throw new BusinessException(Response.Status.NOT_FOUND, "Author not found"); + if (!authors.containsKey(id)) throw new BusinessException(Response.Status.NOT_FOUND, AUTHOR_NOT_FOUND); return authors.get(id); } @@ -151,13 +154,7 @@ public class Library { return authors.size(); } - /** - * Returns a sorted, filtered and paginated list of authors. - * - * @param paginationInfo the pagination info - * @return the sorted, filtered page. - */ - public List<Author> getAuthorsWithFilter(PaginationInfo paginationInfo) { + private Stream<Author> buildSortedFilteredStream(PaginationInfo paginationInfo) { //We build a author stream, first we add sorting Stream<Author> authorStream = authors.stream() .sorted(Comparator.comparing(auteur -> switch (valueOf(paginationInfo.getSortKey().toUpperCase())) { @@ -174,6 +171,22 @@ public class Library { if (paginationInfo.getBiography() != null) authorStream = authorStream.filter(author -> author.getBiography().contains(paginationInfo.getBiography())); + return authorStream; + } + + /** + * Returns a sorted, filtered and paginated list of authors. + * + * @param paginationInfo the pagination info + * @return the sorted, filtered page. + */ + public Page<Author> getAuthorsWithFilter(PaginationInfo paginationInfo) { + + + //We count the total number of results before limit and offset + long elementTotal = buildSortedFilteredStream(paginationInfo).count(); + + Stream<Author> authorStream = buildSortedFilteredStream(paginationInfo); //Finally add pagination instructions. if ((paginationInfo.getPage() > 0) && (paginationInfo.getPageSize() > 0)) { authorStream = authorStream @@ -181,7 +194,11 @@ public class Library { .limit(paginationInfo.getPageSize()); } - return authorStream.collect(Collectors.toList()); + return Page.newInstance(paginationInfo.getPageSize(), + paginationInfo.getPage(), + elementTotal, + authorStream.collect(Collectors.toList()) + ); } /** @@ -233,7 +250,7 @@ public class Library { @XmlElementWrapper(name = "books") @XmlElements({@XmlElement(name = "book")}) @JsonIdentityReference(alwaysAsId = true) - Set<Book> books; + private Set<Book> books; @XmlID @XmlAttribute(name = "id") @@ -270,7 +287,7 @@ public class Library { @XmlElementWrapper(name = "authors") @XmlElements({@XmlElement(name = "author")}) @JsonIdentityReference(alwaysAsId = true) - Set<Author> authors; + private Set<Author> authors; @XmlID @XmlAttribute(name = "id") diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/model/Page.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/model/Page.java new file mode 100644 index 0000000000000000000000000000000000000000..9e5ad419e1d716d85e7eff2d6d2d1eb999efb65d --- /dev/null +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/model/Page.java @@ -0,0 +1,29 @@ +package fr.univtln.bruno.samples.jaxrs.model; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.experimental.FieldDefaults; + +import java.util.List; + +@FieldDefaults(level = AccessLevel.PRIVATE) +@Getter +public class Page<T> { + final long pageSize; + final long pageNumber; + final long elementTotal; + final List<T> content; + final long pageTotal; + + private Page(long pageSize, long pageNumber, long elementTotal, List<T> content) { + this.pageSize = pageSize; + this.pageNumber = pageNumber; + this.elementTotal = elementTotal; + this.content = content; + this.pageTotal = elementTotal / pageSize; + } + + public static <V> Page<V> newInstance(long pageSize, long pageNumber, long elementTotal, List<V> content) { + return new Page<>(pageSize, pageNumber, elementTotal, content); + } +} diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/AdminResource.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/AdminResource.java index 00d2b3705491be08b3560ba6662fea3a8f773eba..4e02ccfd64287a3c49dbd1a1d706789abd2d149b 100644 --- a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/AdminResource.java +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/AdminResource.java @@ -1,32 +1,28 @@ package fr.univtln.bruno.samples.jaxrs.resources; - -import fr.univtln.bruno.samples.jaxrs.exceptions.BusinessException; -import fr.univtln.bruno.samples.jaxrs.exceptions.IllegalArgumentException; -import fr.univtln.bruno.samples.jaxrs.model.Library; -import fr.univtln.bruno.samples.jaxrs.model.Library.Author; -import fr.univtln.bruno.samples.jaxrs.model.Library.Book; import fr.univtln.bruno.samples.jaxrs.security.InMemoryLoginModule; import fr.univtln.bruno.samples.jaxrs.security.User; import fr.univtln.bruno.samples.jaxrs.security.annotations.BasicAuth; import fr.univtln.bruno.samples.jaxrs.security.annotations.JWTAuth; +import fr.univtln.bruno.samples.jaxrs.security.filter.request.BasicAuthenticationFilter; +import fr.univtln.bruno.samples.jaxrs.security.filter.request.JsonWebTokenFilter; import io.jsonwebtoken.Jwts; import jakarta.annotation.security.RolesAllowed; -import jakarta.ws.rs.*; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.*; import lombok.extern.java.Log; import javax.naming.AuthenticationException; -import java.security.SecureRandom; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; -import java.util.Collections; import java.util.Date; -import java.util.Set; /** - * The Biblio resource. + * A administration class for the libraryBiblio resource. * A demo JAXRS class, that manages authors and offers a secured access. */ @Log @@ -35,79 +31,6 @@ import java.util.Set; @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML}) public class AdminResource { - //A random number generator - private static final SecureRandom random = new SecureRandom(); - - /** - * The simpliest method that just return "hello" in plain text with GET on the default path "biblio". - * - * @return the string - */ - @SuppressWarnings("SameReturnValue") - @GET - @Produces(MediaType.TEXT_PLAIN) - public String sayHello() { - return "hello"; - } - - /** - * An init method that add two authors with a PUT on the default path. - * - * @return the number of generated authors. - * @throws IllegalArgumentException the illegal argument exception - */ - @PUT - @Path("init") - public int init() throws BusinessException { - Library.demoLibrary.removesAuthors(); - Author author1 = Library.demoLibrary.addAuthor(Library.Author.builder().firstname("Alfred").name("Martin").build()); - Library.Author author2 = Library.demoLibrary.addAuthor(Author.builder().firstname("Marie").name("Durand").build()); - - Library.demoLibrary.addBook(Book.builder().title("title1").authors(Set.of(author1)).build()); - Library.demoLibrary.addBook(Book.builder().title("title2").authors(Set.of(author1, author2)).build()); - Library.demoLibrary.addBook(Book.builder().title("title3").authors(Set.of(author2)).build()); - Library.demoLibrary.addBook(Book.builder().title("title4").authors(Set.of(author2)).build()); - - return Library.demoLibrary.getAuthorsNumber(); - } - - /** - * An init method that add a given number of random authors whose names are just random letters on PUT. - * The number of authors if given in the path avec bound to the name size. The needed format (an integer) is checked with a regular expression [0-9]+ - * The parameter is injected with @PathParam - * - * @param size the number of authors to add - * @return the int number of generated authors. - * @throws IllegalArgumentException the illegal argument exception - */ - @PUT - @Path("init/{size:[0-9]+}") - public int init(@PathParam("size") int size) throws BusinessException { - Library.demoLibrary.removesAuthors(); - for (int i = 0; i < size; i++) - Library.demoLibrary.addAuthor( - Author.builder() - .firstname(randomString(random.nextInt(6) + 2)) - .name(randomString(random.nextInt(6) + 2)).build()); - return Library.demoLibrary.getAuthorsNumber(); - } - - /** - * A random string generator - * - * @param targetStringLength the length of the String - * @return - */ - private String randomString(int targetStringLength) { - int letterA = 97; - int letterZ = 122; - return random.ints(letterA, letterZ + 1) - .limit(targetStringLength) - .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) - .toString(); - } - - /** * A GET method to access the context of the request : The URI, the HTTP headers, the request and the security context (needs authentication see below). * @@ -121,7 +44,7 @@ public class AdminResource { @Path("context") public String getContext(@Context UriInfo uriInfo, @Context HttpHeaders httpHeaders, @Context Request request, @Context SecurityContext securityContext) { String result = "UriInfo: (" + uriInfo.getRequestUri().toString() + ")\n" - + "Method: ("+request.getMethod()+")\n" + + "Method: (" + request.getMethod() + ")\n" + "HttpHeaders(" + httpHeaders.getRequestHeaders().toString() + ")\n"; if (securityContext != null) { @@ -135,10 +58,10 @@ public class AdminResource { /** * A GET restricted to ADMIN role with basic authentication. - * @see fr.univtln.bruno.samples.jaxrs.security.filter.BasicAuthenticationFilter * * @param securityContext the security context * @return the restricted to admins + * @see BasicAuthenticationFilter */ @GET @Path("adminsonly") @@ -150,10 +73,10 @@ public class AdminResource { /** * A GET restricted to USER role with basic authentication (and not ADMIN !). - * @see fr.univtln.bruno.samples.jaxrs.security.filter.BasicAuthenticationFilter * * @param securityContext the security context * @return the restricted to users + * @see BasicAuthenticationFilter */ @GET @Path("usersonly") @@ -165,10 +88,10 @@ public class AdminResource { /** * A GET restricted to USER & ADMIN roles, secured with a JWT Token. - * @see fr.univtln.bruno.samples.jaxrs.security.filter.JsonWebTokenFilter * * @param securityContext the security context * @return the string + * @see JsonWebTokenFilter */ @GET @Path("secured") diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/AuthorRessource.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/AuthorRessource.java index 9e5daf9f7b81c247a16e881f4b587c3dbffbb6fa..768f92bef6b757aa7e12effa512e1dcbecabf650 100644 --- a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/AuthorRessource.java +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/AuthorRessource.java @@ -4,13 +4,13 @@ import fr.univtln.bruno.samples.jaxrs.exceptions.BusinessException; import fr.univtln.bruno.samples.jaxrs.exceptions.IllegalArgumentException; import fr.univtln.bruno.samples.jaxrs.exceptions.NotFoundException; import fr.univtln.bruno.samples.jaxrs.model.Library; +import fr.univtln.bruno.samples.jaxrs.model.Page; import fr.univtln.bruno.samples.jaxrs.status.Status; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import lombok.extern.java.Log; import java.util.Collection; -import java.util.List; @Log @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML}) @@ -102,7 +102,7 @@ public class AuthorRessource { */ @GET @Path("filter") - public List<Library.Author> getFilteredAuteurs(@QueryParam("name") String name, + public Page<Library.Author> getFilteredAuteurs(@QueryParam("name") String name, @QueryParam("firstname") String firstname, @QueryParam("biography") String biography, @HeaderParam("sortKey") @DefaultValue("name") String sortKey) { @@ -112,7 +112,7 @@ public class AuthorRessource { .biography(biography) .sortKey(sortKey) .build(); - log.info(paginationInfo.toString()); + return Library.demoLibrary.getAuthorsWithFilter(paginationInfo); } @@ -124,7 +124,7 @@ public class AuthorRessource { */ @GET @Path("page") - public List<Library.Author> getAuteursPage(@BeanParam PaginationInfo paginationInfo) { + public Page<Library.Author> getAuteursPage(@BeanParam PaginationInfo paginationInfo) { return Library.demoLibrary.getAuthorsWithFilter(paginationInfo); } diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/LibraryRessource.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/LibraryRessource.java index b12c599f9239c72797ef219ac2bf1dc20bd7af82..ce96ffcd176de3158a150aa6669bbea10a79273b 100644 --- a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/LibraryRessource.java +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/LibraryRessource.java @@ -1,18 +1,95 @@ package fr.univtln.bruno.samples.jaxrs.resources; +import fr.univtln.bruno.samples.jaxrs.exceptions.BusinessException; +import fr.univtln.bruno.samples.jaxrs.exceptions.IllegalArgumentException; import fr.univtln.bruno.samples.jaxrs.model.Library; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import lombok.extern.java.Log; +import java.security.SecureRandom; +import java.util.Set; + @Log @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML}) @Path("library") public class LibraryRessource { + + //A random number generator + private static final SecureRandom random = new SecureRandom(); + @GET - public Library getAuteurs() { + public Library getLibrary() { return Library.demoLibrary; } + + /** + * The simpliest method that just return "hello" in plain text with GET on the default path "biblio". + * + * @return the string + */ + @SuppressWarnings("SameReturnValue") + @GET + @Produces(MediaType.TEXT_PLAIN) + public String sayHello() { + return "hello"; + } + + /** + * An init method that add two authors with a PUT on the default path. + * + * @return the number of generated authors. + * @throws IllegalArgumentException the illegal argument exception + */ + @PUT + @Path("init") + public int init() throws BusinessException { + Library.demoLibrary.removesAuthors(); + Library.Author author1 = Library.demoLibrary.addAuthor(Library.Author.builder().firstname("Alfred").name("Martin").build()); + Library.Author author2 = Library.demoLibrary.addAuthor(Library.Author.builder().firstname("Marie").name("Durand").build()); + + Library.demoLibrary.addBook(Library.Book.builder().title("title1").authors(Set.of(author1)).build()); + Library.demoLibrary.addBook(Library.Book.builder().title("title2").authors(Set.of(author1, author2)).build()); + Library.demoLibrary.addBook(Library.Book.builder().title("title3").authors(Set.of(author2)).build()); + Library.demoLibrary.addBook(Library.Book.builder().title("title4").authors(Set.of(author2)).build()); + + return Library.demoLibrary.getAuthorsNumber(); + } + + /** + * An init method that add a given number of random authors whose names are just random letters on PUT. + * The number of authors if given in the path avec bound to the name size. The needed format (an integer) is checked with a regular expression [0-9]+ + * The parameter is injected with @PathParam + * + * @param size the number of authors to add + * @return the int number of generated authors. + * @throws IllegalArgumentException the illegal argument exception + */ + @PUT + @Path("init/{size:[0-9]+}") + public int init(@PathParam("size") int size) throws BusinessException { + Library.demoLibrary.removesAuthors(); + for (int i = 0; i < size; i++) + Library.demoLibrary.addAuthor( + Library.Author.builder() + .firstname(randomString(random.nextInt(6) + 2)) + .name(randomString(random.nextInt(6) + 2)).build()); + return Library.demoLibrary.getAuthorsNumber(); + } + + /** + * A random string generator + * + * @param targetStringLength the length of the String + * @return + */ + private String randomString(int targetStringLength) { + int letterA = 97; + int letterZ = 122; + return random.ints(letterA, letterZ + 1) + .limit(targetStringLength) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + } + } diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/MySecurityContext.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/MySecurityContext.java index 1ab16f818c33ef4d37e0317160dfdd3929bb9fbc..94367dcc4922480aff298200850521ae95a820d7 100644 --- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/MySecurityContext.java +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/MySecurityContext.java @@ -1,5 +1,7 @@ package fr.univtln.bruno.samples.jaxrs.security; +import fr.univtln.bruno.samples.jaxrs.security.filter.request.BasicAuthenticationFilter; +import fr.univtln.bruno.samples.jaxrs.security.filter.request.JsonWebTokenFilter; import jakarta.ws.rs.core.SecurityContext; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -10,8 +12,8 @@ import java.security.Principal; /** * This class define a specific security context after an authentication with either the basic or the JWT filters. * - * @see fr.univtln.bruno.samples.jaxrs.security.filter.BasicAuthenticationFilter - * @see fr.univtln.bruno.samples.jaxrs.security.filter.JsonWebTokenFilter + * @see BasicAuthenticationFilter + * @see JsonWebTokenFilter */ @FieldDefaults(level = AccessLevel.PRIVATE) @AllArgsConstructor(staticName = "newInstance") diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/BasicAuth.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/BasicAuth.java index f09c007d5d6f86a0c9881997be131835f43f599c..3372f738eda458dab4d6e606d147e12a81daa43b 100644 --- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/BasicAuth.java +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/BasicAuth.java @@ -1,5 +1,6 @@ package fr.univtln.bruno.samples.jaxrs.security.annotations; +import fr.univtln.bruno.samples.jaxrs.security.filter.request.BasicAuthenticationFilter; import jakarta.ws.rs.NameBinding; import java.lang.annotation.Retention; @@ -11,7 +12,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * A annotation for method to be secured with Basic Auth - * @see fr.univtln.bruno.samples.jaxrs.security.filter.BasicAuthenticationFilter + * @see BasicAuthenticationFilter */ @NameBinding @Retention(RUNTIME) diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/JWTAuth.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/JWTAuth.java index d7834fcb1b341ebe37d09d72a2f66cb5599ac77d..6e9971c4b551009286cd0be95f5e582240afc45b 100644 --- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/JWTAuth.java +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/JWTAuth.java @@ -1,5 +1,6 @@ package fr.univtln.bruno.samples.jaxrs.security.annotations; +import fr.univtln.bruno.samples.jaxrs.security.filter.request.JsonWebTokenFilter; import jakarta.ws.rs.NameBinding; import java.lang.annotation.Retention; @@ -11,7 +12,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * A annotation for method to be secured with Java Web Token (JWT) - * @see fr.univtln.bruno.samples.jaxrs.security.filter.JsonWebTokenFilter + * @see JsonWebTokenFilter */ @NameBinding @Retention(RUNTIME) diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/BasicAuthenticationFilter.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/request/BasicAuthenticationFilter.java similarity index 98% rename from src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/BasicAuthenticationFilter.java rename to src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/request/BasicAuthenticationFilter.java index a431005f235f14cd30c731e1281cb16ece95671d..ca27e98b9f459ffb2f9a521bc5c938858b1ad436 100644 --- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/BasicAuthenticationFilter.java +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/request/BasicAuthenticationFilter.java @@ -1,4 +1,4 @@ -package fr.univtln.bruno.samples.jaxrs.security.filter; +package fr.univtln.bruno.samples.jaxrs.security.filter.request; import fr.univtln.bruno.samples.jaxrs.security.MySecurityContext; import fr.univtln.bruno.samples.jaxrs.security.annotations.BasicAuth; diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/JsonWebTokenFilter.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/request/JsonWebTokenFilter.java similarity index 98% rename from src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/JsonWebTokenFilter.java rename to src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/request/JsonWebTokenFilter.java index 088b2b26a2d42d0ee89d00b4802f2d059cf64d69..f0d2dfb8f5b1a44df38017f55ba806b051716885 100644 --- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/JsonWebTokenFilter.java +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/request/JsonWebTokenFilter.java @@ -1,4 +1,4 @@ -package fr.univtln.bruno.samples.jaxrs.security.filter; +package fr.univtln.bruno.samples.jaxrs.security.filter.request; import fr.univtln.bruno.samples.jaxrs.security.InMemoryLoginModule; import fr.univtln.bruno.samples.jaxrs.security.MySecurityContext; diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/response/PaginationLinkFilter.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/response/PaginationLinkFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..7b6b85b05b492f2cf74f66f5bec5b2098483ca48 --- /dev/null +++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/response/PaginationLinkFilter.java @@ -0,0 +1,76 @@ +package fr.univtln.bruno.samples.jaxrs.security.filter.response; + +import fr.univtln.bruno.samples.jaxrs.model.Page; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.core.Link; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Provider; + +import java.util.ArrayList; +import java.util.List; + +@Provider +public class PaginationLinkFilter implements ContainerResponseFilter { + public static final String JAXRS_SAMPLE_TOTAL_COUNT = "JAXRS_Sample-Total-Count"; + public static final String JAXRS_SAMPLE_PAGE_COUNT = "JAXRS_Sample-Page-Count"; + public static final String PREV_REL = "previous"; + public static final String NEXT_REL = "next"; + public static final String FIRST_REL = "first"; + public static final String LAST_REL = "last"; + public static final String PAGE_QUERY_PARAM = "page"; + public static final int FIRST_PAGE = 1; + + @Override + public void filter(ContainerRequestContext requestContext, + ContainerResponseContext responseContext) { + + //If the entity in the response is not a Page we stop here + if (!(responseContext.getEntity() instanceof Page)) { + return; + } + + UriInfo uriInfo = requestContext.getUriInfo(); + Page entity = (Page) responseContext.getEntity(); + + //We replace the entity by the content of the page (we remove the envelope). + responseContext.setEntity(entity.getContent()); + + List<Link> linksList = new ArrayList<>(); + + //We add the need semantic links in the header + //Not on the first page + if (entity.getPageSize() > FIRST_PAGE) { + linksList.add(Link.fromUriBuilder(uriInfo.getRequestUriBuilder() + .replaceQueryParam(PAGE_QUERY_PARAM, + entity.getPageNumber() - 1)) + .rel(PREV_REL) + .build()); + linksList.add(Link.fromUriBuilder(uriInfo.getRequestUriBuilder() + .replaceQueryParam(PAGE_QUERY_PARAM, + 1)) + .rel(FIRST_REL) + .build()); + } + //Not on the last + if (entity.getPageSize() < entity.getPageTotal()) { + linksList.add(Link.fromUriBuilder(uriInfo.getRequestUriBuilder() + .replaceQueryParam(PAGE_QUERY_PARAM, + entity.getPageNumber() + 1)) + .rel(NEXT_REL) + .build()); + linksList.add(Link.fromUriBuilder(uriInfo.getRequestUriBuilder() + .replaceQueryParam(PAGE_QUERY_PARAM, + entity.getPageTotal())) + .rel(LAST_REL) + .build()); + } + + responseContext.getHeaders() + .addAll("Link", linksList.toArray(Link[]::new)); + //We add pagination metadata in the header + responseContext.getHeaders().add(JAXRS_SAMPLE_TOTAL_COUNT, entity.getElementTotal()); + responseContext.getHeaders().add(JAXRS_SAMPLE_PAGE_COUNT, entity.getPageTotal()); + } +}