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());
+    }
+}