Repository: jakartaee/data
Pull request #37 (2022-10-05 06:41:40)

Return type of Page for repository methods
:bomb: Please don't merge it.

This PR aims to start the discussion around the ```Page``` and the ```PageableRepository``` new method.


https://github.com/jakartaee/data/issues/36

otaviojava: I've update using this comments as base:
https://github.com/jakartaee/data/issues/36#issuecomment-1268074831

Patch:
diff --git a/api/src/main/java/jakarta/data/repository/Page.java b/api/src/main/java/jakarta/data/repository/Page.java
new file mode 100644
index 00000000..792a5e8c
--- /dev/null
+++ b/api/src/main/java/jakarta/data/repository/Page.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.repository;
+
+
+/**
+ * A page is a sublist of a list of objects. It provides information about its position relative to the entire list.
+ *
+ * @param <T> the entity type
+ */
+public interface Page<T> extends Slice<T> {
+
+    /**
+     * Returns the total amount of elements.
+     * @return the total amount of elements
+     */
+    long getTotalElements();
+
+    /**
+     * Returns the total number of pages.
+     * @return the total number of pages
+     */
+    long getTotalPages();
+}
\ No newline at end of file
diff --git a/api/src/main/java/jakarta/data/repository/Pageable.java b/api/src/main/java/jakarta/data/repository/Pageable.java
index b4054170..389db07f 100644
--- a/api/src/main/java/jakarta/data/repository/Pageable.java
+++ b/api/src/main/java/jakarta/data/repository/Pageable.java
@@ -17,10 +17,14 @@
  */
 package jakarta.data.repository;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
 /**
- * <p>Abstract interface for pagination information.</p>
+ * <p>This class represents pagination information.</p>
  *
  * <p><code>Pageable</code> is optionally specified as a parameter to a
  * repository method in one of the parameter positions after the
@@ -56,9 +60,12 @@ public class Pageable {
 
     private final long page;
 
-    private Pageable(long size, long page) {
+    private final List<Sort> sorts;
+
+    private Pageable(long size, long page, List<Sort> sorts) {
         this.size = size;
         this.page = page;
+        this.sorts = sorts;
     }
 
     /**
@@ -79,13 +86,22 @@ public class Pageable {
         return page;
     }
 
+    /**
+     * Return the order collection if it was specified on this <code>Pageable</code>, otherwise an empty list.
+     *
+     * @return the order collection
+     */
+    public List<Sort> getSorts() {
+        return sorts;
+    }
+
     /**
      * Returns the Pageable requesting the next Page.
      *
      * @return The next pageable.
      */
     public Pageable next() {
-        return new Pageable(this.size, (page + 1));
+        return new Pageable(this.size, (page + 1), this.sorts);
     }
 
     @Override
@@ -97,12 +113,12 @@ public class Pageable {
             return false;
         }
         Pageable pageable = (Pageable) o;
-        return size == pageable.size && page == pageable.page;
+        return size == pageable.size && page == pageable.page && sorts.equals(pageable.sorts);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(size, page);
+        return Objects.hash(size, page, sorts);
     }
 
     @Override
@@ -129,14 +145,59 @@ public class Pageable {
      * @param page The page
      * @param size The size
      * @return The pageable
+     * @throws IllegalArgumentException when page or size are negative or zero
      */
     public static Pageable of(long page, long size) {
+        return of(page, size, Collections.emptyList());
+    }
+
+    /**
+     * Creates a new Pageable at the given size, page and Sort
+     *
+     * @param page The page
+     * @param size The size
+     * @param sort the sort
+     * @return The pageable
+     * @throws IllegalArgumentException when page or size are negative or zero
+     */
+    public static Pageable of(long page, long size, Sort sort) {
+        Objects.requireNonNull(sort, "sort is required");
+        return of(page, size, Collections.singletonList(sort));
+    }
+
+    /**
+     * Creates a new Pageable at the given size, page and Sort
+     *
+     * @param page  The page
+     * @param size  The size
+     * @param sorts the sorts
+     * @return The pageable
+     * @throws IllegalArgumentException when page or size are negative or zero
+     */
+    public static Pageable of(long page, long size, Sort... sorts) {
+        return of(page, size, List.of(sorts));
+    }
+
+    /**
+     * Creates a new Pageable at the given size, page and Sort
+     *
+     * @param page  The page
+     * @param size  The size
+     * @param sorts the sorts
+     * @return The pageable
+     * @throws IllegalArgumentException when page or size are negative
+     * @throws NullPointerException     when sorts is null
+     */
+    public static Pageable of(long page, long size, Iterable<Sort> sorts) {
         if (page < 1) {
             throw new IllegalArgumentException("page: " + page);
         } else if (size < 1) {
             throw new IllegalArgumentException("size: " + size);
         }
-        return new Pageable(size, page);
+        Objects.requireNonNull(sorts, "sorts is required");
+        return new Pageable(size, page, StreamSupport.stream(sorts.spliterator(), false)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toUnmodifiableList()));
     }
 
     /**
diff --git a/api/src/main/java/jakarta/data/repository/PageableRepository.java b/api/src/main/java/jakarta/data/repository/PageableRepository.java
index dda06c5d..fb6f8a76 100644
--- a/api/src/main/java/jakarta/data/repository/PageableRepository.java
+++ b/api/src/main/java/jakarta/data/repository/PageableRepository.java
@@ -28,6 +28,13 @@ package jakarta.data.repository;
  */
 public interface PageableRepository<T, K> extends CrudRepository<T, K> {
 
-
+    /**
+     * Returns a {@link Page} of entities meeting the paging restriction provided in the {@link Pageable} object.
+     *
+     * @param pageable the pageable to request a paginated result, must not be null.
+     * @return a page of entities
+     * @throws NullPointerException when pageable is null
+     */
+    Page<T> findAll(Pageable pageable);
 
 }
diff --git a/api/src/main/java/jakarta/data/repository/Param.java b/api/src/main/java/jakarta/data/repository/Param.java
index 81fb7246..ecb72977 100644
--- a/api/src/main/java/jakarta/data/repository/Param.java
+++ b/api/src/main/java/jakarta/data/repository/Param.java
@@ -30,5 +30,9 @@ import java.lang.annotation.Target;
 @Target(ElementType.PARAMETER)
 public @interface Param {
 
+    /**
+     * Returns The parameter name
+     * @return the parameter name
+     */
     String value();
 }
diff --git a/api/src/main/java/jakarta/data/repository/Query.java b/api/src/main/java/jakarta/data/repository/Query.java
index 08603bba..3ba36e13 100644
--- a/api/src/main/java/jakarta/data/repository/Query.java
+++ b/api/src/main/java/jakarta/data/repository/Query.java
@@ -30,6 +30,11 @@ import java.lang.annotation.Target;
 @Target(ElementType.METHOD)
 public @interface Query {
 
+    /**
+     * Defines the query to be executed when the annotated method is called.
+     *
+     * @return the query to be executed when the annotated method is called.
+     */
     String value();
 }
 
diff --git a/api/src/main/java/jakarta/data/repository/Slice.java b/api/src/main/java/jakarta/data/repository/Slice.java
new file mode 100644
index 00000000..73e34f1d
--- /dev/null
+++ b/api/src/main/java/jakarta/data/repository/Slice.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2022 Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package jakarta.data.repository;
+
+import java.util.List;
+
+/**
+ * A slice of data that indicates whether there's a next or previous slice available.
+ */
+public interface Slice<T>  {
+
+    /**
+     * Returns the page content as {@link List}.
+     *
+     * @return the page content as {@link List}.
+     */
+    List<T> getContent();
+
+    /**
+     * Returns whether the {@link Slice} has content at all.
+     *
+     * @return whether the {@link Slice} has content at all.
+     */
+    boolean hasContent();
+
+    /**
+     * Returns the number of elements currently on this Slice.
+     *
+     * @return the number of elements currently on this Slice.
+     */
+    int getNumberOfElements();
+
+    /**
+     * Returns the current {@link Pageable}
+     *
+     * @return the current Pageable
+     */
+    Pageable getPageable();
+
+    /**
+     * Returns the next {@link Pageable#next()}, or <code>null</code> if it is known that there is no next page.
+     *
+     * @return the next pageable
+     */
+  Pageable nextPageable();
+}


Test patch:
diff --git a/api/src/test/java/jakarta/data/repository/PageableTest.java b/api/src/test/java/jakarta/data/repository/PageableTest.java
index ff925332..5104ce61 100644
--- a/api/src/test/java/jakarta/data/repository/PageableTest.java
+++ b/api/src/test/java/jakarta/data/repository/PageableTest.java
@@ -17,9 +17,13 @@
  */
 package jakarta.data.repository;
 
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
 import static org.assertj.core.api.SoftAssertions.assertSoftly;
 
@@ -71,5 +75,53 @@ class PageableTest {
         assertThatIllegalArgumentException().isThrownBy(() -> Pageable.size(0));
         assertThatIllegalArgumentException().isThrownBy(() -> Pageable.size(-1));
     }
+
+    @Test
+    public void shouldReturnNPEWhenSortIsNull() {
+        Assertions.assertThrows(NullPointerException.class, () ->
+                Pageable.of(1L, 2L, (Sort) null));
+        Assertions.assertThrows(NullPointerException.class, () ->
+                Pageable.of(1L, 2L, (Iterable<Sort>) null));
+
+        Assertions.assertThrows(NullPointerException.class, () ->
+                Pageable.of(1L, 2L, Sort.asc("name"), null));
+    }
+
+    @Test
+    public void shouldCreatePageableSort() {
+        Pageable pageable = Pageable.of(1L, 3L, Sort.asc("name"));
+        Assertions.assertNotNull(pageable);
+        Assertions.assertEquals(1L, pageable.getPage());
+        Assertions.assertEquals(3L, pageable.getSize());
+        assertThat(pageable.getSorts())
+                .hasSize(1)
+                .contains(Sort.asc("name"));
+    }
+
+    @Test
+    public void shouldNotModifySort() {
+        Pageable pageable = Pageable.of(1L, 3L, Sort.asc("name"));
+        List<Sort> sorts = pageable.getSorts();
+        Assertions.assertThrows(UnsupportedOperationException.class, ()-> sorts.clear());
+
+    }
+
+    @Test
+    public void shouldNotModifySortOnNextPage() {
+        Pageable pageable = Pageable.of(1L, 3L, Sort.asc("name"), Sort.desc("age"));
+        Pageable next = pageable.next();
+        Assertions.assertEquals(3L, pageable.getSize());
+        Assertions.assertEquals(1L, pageable.getPage());
+        assertThat(pageable.getSorts())
+                .hasSize(2)
+                .contains(Sort.asc("name"), Sort.desc("age"));
+        Assertions.assertEquals(2L, next.getPage());
+        Assertions.assertEquals(3L, next.getSize());
+
+        assertThat(next.getSorts())
+                .hasSize(2)
+                .contains(Sort.asc("name"), Sort.desc("age"));
+
+    }
 }