Skip to content
Merged
4 changes: 2 additions & 2 deletions api/src/main/java/jakarta/data/Limit.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
* Copyright (c) 2022,2024 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.
Expand Down Expand Up @@ -30,7 +30,7 @@
* query parameters. For example,</p>
*
* <pre>
* Product[] findByNameLike(String namePattern, Limit limit, Sort... sorts);
* Product[] findByNameLike(String namePattern, Limit limit, {@code Sort<?>...} sorts);
*
* ...
* mostExpensive50 = products.findByNameLike(pattern, Limit.of(50), Sort.desc("price"));
Expand Down
172 changes: 172 additions & 0 deletions api/src/main/java/jakarta/data/Order.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright (c) 2024 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;

import java.util.Iterator;
import java.util.List;

import jakarta.data.metamodel.StaticMetamodel;
import jakarta.data.page.Pageable;
import jakarta.data.repository.OrderBy;
import jakarta.data.repository.Query;

/**
* <p>Requests sorting on various entity attributes.</p>
*
* <p><code>Order</code> can be optionally specified as a
* parameter to a repository find method in any of the positions
* that are after the query parameters, or it can be used
* to obtain a {@link Pageable page request} that is similarly
* specified as a parameter to a repository find method.</p>
*
* <p>The {@code Order} class is useful in combination with the
* {@link StaticMetamodel} for helping to enforce type safety of
* sort criteria during development. For example,</p>
*
* <pre>
* {@code Page<Employee>} findByYearHired(int year, {@code Pageable<Employee>} pageRequest);
* ...
* page1 = employees.findByYearHired(Year.now(),
* Order.by(_Employee.salary.desc(),
* _Employee.lastName.asc(),
* _Employee.firstName.asc())
* .page(1)
* .size(10));
* </pre>
*
* <p>When combined on a method with static sort criteria
* (<code>OrderBy</code> keyword or {@link OrderBy} annotation or
* {@link Query} with an <code>ORDER BY</code> clause), the static
* sort criteria is applied first, followed by the dynamic sort criteria
* that is defined by {@link Sort} instances in the order listed.</p>
*
* <p>In the example above, the matching employees are sorted first by salary
* from highest to lowest. Employees with the same salary are then sorted
* alphabetically by last name. Employees with the same salary and last name
* are then sorted alphabetically by first name.</p>
*
* <p>A repository method will fail with a
* {@link jakarta.data.exceptions.DataException DataException}
* or a more specific subclass if</p>
* <ul>
* <li>an <code>Order</code> parameter is
* specified in combination with a {@link Pageable} parameter with
* {@link Pageable#sorts()}.</li>
* <li>the database is incapable of ordering with the requested
* sort criteria.</li>
* </ul>
*
* @param <T> entity class of the attributes that are used as sort criteria.
*/
public class Order<T> implements Iterable<Sort<T>> {

private final List<Sort<T>> sorts;

private Order(List<Sort<T>> sorts) {
this.sorts = sorts;
}

/**
* <p>Defines a list of {@link Sort} criteria, ordered from highest precedence
* to lowest precedence.</p>
*
* @param <T> entity class of the attributes that are used as sort criteria.
* @param sorts sort criteria to use, ordered from highest precedence to lowest precedence.
* @return a new instance indicating the order of precedence for sort criteria.
* This method never returns <code>null</code>.
*/
@SafeVarargs
public static final <T> Order<T> by(Sort<T>... sorts) {
return new Order<T>(List.of(sorts));
}

/**
* Determines whether this instance specifies matching {@link Sort} criteria
* in the same order of precedence as another instance.
*
* @return true if the other instance is an {@code Order} that specifies
* the same ordering of sort criteria as this instance.
*/
@Override
public boolean equals(Object other) {
return this == other
|| other instanceof Order s && sorts.equals(s.sorts);
}

/**
* Computes a hash code for this instance.
*
* @return hash code.
*/
@Override
public int hashCode() {
return sorts.hashCode();
}

/**
* Returns an iterator that follows the order of precedence for the
* {@link Sort} criteria, from highest precedence to lowest.
*
* @return iterator over the sort criteria.
*/
@Override
public Iterator<Sort<T>> iterator() {
return sorts.iterator();
}

/**
* Create a {@link Pageable page request} for the specified page number
* of page size 10 (the default for {@code Pageable})
* of the query results sorted according to any static sort criteria that
* is specified and the ordered list of {@link Sort} criteria
* that is represented by this instance.
*
* @param pageNumber requested page number.
* @return a request for a page of results that are sorted based on the sort criteria represented by this instance
* and with the specified page number. This method never returns <code>null</code>.
*/
public Pageable<T> page(long pageNumber) {
return Pageable.<T>ofPage(pageNumber).sortBy(sorts);
}

/**
* Create a {@link Pageable page request} for the first page of the specified page size
* of the query results sorted according to any static sort criteria that
* is specified and the ordered list of {@link Sort} criteria
* that is represented by this instance.
*
* @param size requested size of pages.
* @return a request for a page of results that are sorted based on the sort criteria represented by this instance
* and with the specified page size. This method never returns <code>null</code>.
*/
public Pageable<T> pageSize(int size) {
return Pageable.<T>ofSize(size).sortBy(sorts);
}

/**
* Textual representation of this instance, including the result of invoking
* {@link Sort#toString()} on each member of the sort criteria, in order of
* precedence from highest to lowest.
*
* @return textual representation of this instance.
*/
@Override
public String toString() {
return sorts.toString();
}
}
58 changes: 41 additions & 17 deletions api/src/main/java/jakarta/data/Sort.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022,2023 Contributors to the Eclipse Foundation
* Copyright (c) 2022,2024 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.
Expand All @@ -17,9 +17,11 @@
*/
package jakarta.data;

import jakarta.data.metamodel.StaticMetamodel;
import jakarta.data.page.Pageable;
import jakarta.data.repository.OrderBy;
import jakarta.data.repository.Query;

import java.util.Objects;

/**
Expand All @@ -30,14 +32,16 @@
* a {@link Direction} and a property.</p>
*
* <p>Dynamic <code>Sort</code> criteria can be specified when
* {@link Pageable#sortBy(Sort[]) requesting a page of results}
* requesting a {@link Pageable#sortBy(Sort) page} of results,
* or can be optionally specified as
* parameters to a repository method in any of the positions that are after
* the query parameters. You can use <code>Sort...</code> to allow a variable
* number of <code>Sort</code> criteria. For example,</p>
* parameters to a repository find method in any of the positions that are after
* the query parameters.</p>
*
* <p>You can use {@code Sort<?>...} to allow a variable
* number of generic <code>Sort</code> criteria. For example,</p>
*
* <pre>
* Employee[] findByYearHired(int yearYired, Limit maxResults, Sort... sortBy);
* Employee[] findByYearHired(int yearYired, Limit maxResults, {@code Sort<?>...} sortBy);
* ...
* highestPaidNewHires = employees.findByYearHired(Year.now(),
* Limit.of(10),
Expand All @@ -46,6 +50,20 @@
* Sort.asc("firstName"));
* </pre>
*
* <p>You can use {@link Order} in combination with the
* {@link StaticMetamodel} to allow a variable number of
* typed <code>Sort</code> criteria. For example,</p>
*
* <pre>
* Employee[] findByYearHired(int yearYired, Limit maxResults, {@code Order<Employee>} sortBy);
* ...
* highestPaidNewHires = employees.findByYearHired(Year.now(),
* Limit.of(10),
* Order.by(_Employee.salary.desc(),
* _Employee.lastName.asc(),
* _Employee.firstName.asc()));
* </pre>
*
* <p>When combined on a method with static sort criteria
* (<code>OrderBy</code> keyword or {@link OrderBy} annotation or
* {@link Query} with an <code>ORDER BY</code> clause), the static
Expand All @@ -68,11 +86,12 @@
* sort criteria.</li>
* </ul>
*
* @param <T> entity class of the property upon which to sort.
* @param property name of the property to order by.
* @param isAscending whether ordering for this property is ascending (true) or descending (false).
* @param ignoreCase whether or not to request case insensitive ordering from a database with case sensitive collation.
*/
public record Sort(String property, boolean isAscending, boolean ignoreCase) {
public record Sort<T>(String property, boolean isAscending, boolean ignoreCase) {

/**
* <p>Defines sort criteria for an entity property. For more descriptive code, use:</p>
Expand Down Expand Up @@ -136,62 +155,67 @@ public boolean isDescending() {
/**
* Create a {@link Sort} instance
*
* @param <T> entity class of the sortable property.
* @param property the property name to order by
* @param direction the direction in which to order.
* @param ignoreCase whether to request a case insensitive ordering.
* @return a {@link Sort} instance. Never {@code null}.
* @throws NullPointerException when there is a null parameter
*/
public static Sort of(String property, Direction direction, boolean ignoreCase) {
public static <T> Sort<T> of(String property, Direction direction, boolean ignoreCase) {
Objects.requireNonNull(direction, "direction is required");
return new Sort(property, Direction.ASC.equals(direction), ignoreCase);
return new Sort<>(property, Direction.ASC.equals(direction), ignoreCase);
}

/**
* Create a {@link Sort} instance with {@link Direction#ASC ascending direction}
* that does not request case insensitive ordering.
*
* @param <T> entity class of the sortable property.
* @param property the property name to order by
* @return a {@link Sort} instance. Never {@code null}.
* @throws NullPointerException when the property is null
*/
public static Sort asc(String property) {
return new Sort(property, true, false);
public static <T> Sort<T> asc(String property) {
return new Sort<>(property, true, false);
}

/**
* Create a {@link Sort} instance with {@link Direction#ASC ascending direction}
* and case insensitive ordering.
*
* @param <T> entity class of the sortable property.
* @param property the property name to order by.
* @return a {@link Sort} instance. Never {@code null}.
* @throws NullPointerException when the property is null.
*/
public static Sort ascIgnoreCase(String property) {
return new Sort(property, true, true);
public static <T> Sort<T> ascIgnoreCase(String property) {
return new Sort<>(property, true, true);
}

/**
* Create a {@link Sort} instance with {@link Direction#DESC descending direction}
* that does not request case insensitive ordering.
*
* @param <T> entity class of the sortable property.
* @param property the property name to order by
* @return a {@link Sort} instance. Never {@code null}.
* @throws NullPointerException when the property is null
*/
public static Sort desc(String property) {
return new Sort(property, false, false);
public static <T> Sort<T> desc(String property) {
return new Sort<>(property, false, false);
}

/**
* Create a {@link Sort} instance with {@link Direction#DESC descending direction}
* and case insensitive ordering.
*
* @param <T> entity class of the sortable property.
* @param property the property name to order by.
* @return a {@link Sort} instance. Never {@code null}.
* @throws NullPointerException when the property is null.
*/
public static Sort descIgnoreCase(String property) {
return new Sort(property, false, true);
public static <T> Sort<T> descIgnoreCase(String property) {
return new Sort<>(property, false, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,23 @@
* <li>boolean attributes</li>
* <li>{@link TextAttribute textual attributes}</li>
* </ul>
*
* @param <T> entity class of the static metamodel.
*/
public interface SortableAttribute extends Attribute {
public interface SortableAttribute<T> extends Attribute {

/**
* Obtain a request for an ascending {@link Sort} based on the entity attribute.
*
* @return a request for an ascending sort on the entity attribute.
*/
Sort asc();
Sort<T> asc();

/**
* Obtain a request for a descending {@link Sort} based on the entity attribute.
*
* @return a request for a descending sort on the entity attribute.
*/
Sort desc();
Sort<T> desc();

}
Loading