Skip to content

Commit 6cbef53

Browse files
authored
Merge pull request #275 from mazeneko/add-pagebles-for-criteria
Add utilities for Pageable to be used with Doma Criteria.
2 parents 04f1593 + 1ed99cc commit 6cbef53

File tree

4 files changed

+454
-0
lines changed

4 files changed

+454
-0
lines changed

doma-spring-boot-core/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@
7979
</archive>
8080
</configuration>
8181
</plugin>
82+
<plugin>
83+
<groupId>org.apache.maven.plugins</groupId>
84+
<artifactId>maven-compiler-plugin</artifactId>
85+
<configuration>
86+
<annotationProcessorPaths>
87+
<path>
88+
<groupId>org.seasar.doma</groupId>
89+
<artifactId>doma-processor</artifactId>
90+
<version>${doma.version}</version>
91+
</path>
92+
</annotationProcessorPaths>
93+
</configuration>
94+
</plugin>
8295
</plugins>
8396
</build>
8497
</project>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.seasar.doma.boot;
2+
3+
import java.util.Optional;
4+
5+
import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel;
6+
7+
/**
8+
* A resolver that maps property names to {@link PropertyMetamodel}
9+
*/
10+
@FunctionalInterface
11+
public interface PropertyMetamodelResolver {
12+
/**
13+
* Resolves the specified property name into a {@link PropertyMetamodel}.
14+
*
15+
* @param propertyName the name of the property to resolve
16+
* @return an {@link Optional} containing the resolved {@link PropertyMetamodel}
17+
* if found,
18+
* or an empty {@link Optional} if the property name cannot be resolved
19+
*/
20+
Optional<PropertyMetamodel<?>> resolve(String propertyName);
21+
}
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
package org.seasar.doma.boot;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.Optional;
6+
import java.util.function.Consumer;
7+
import java.util.function.Function;
8+
import java.util.stream.Collectors;
9+
10+
import org.seasar.doma.jdbc.criteria.declaration.OrderByNameDeclaration;
11+
import org.seasar.doma.jdbc.criteria.metamodel.EntityMetamodel;
12+
import org.seasar.doma.jdbc.criteria.metamodel.PropertyMetamodel;
13+
import org.seasar.doma.jdbc.criteria.statement.EntityQueryable;
14+
import org.springframework.data.domain.Pageable;
15+
16+
/**
17+
* An adapter that integrates {@link Pageable} with Doma Criteria API.
18+
* <p>
19+
* This class allows converting {@link Pageable} pagination and sort information
20+
* into Doma Criteria API's limit, offset, and order-by specifications.
21+
* <p>
22+
* Example usage:
23+
* <pre>{@code
24+
* public Page<Task> getPage(Pageable pageable) {
25+
* final var task_ = new Task_();
26+
* final var p = UnifiedQueryPageable.from(pageable, task_);
27+
* final var content = this.queryDsl
28+
* .from(task_)
29+
* .offset(p.offset())
30+
* .limit(p.limit())
31+
* .orderBy(p.orderBy())
32+
* .fetch();
33+
* final var total = this.queryDsl
34+
* .from(task_)
35+
* .select(Expressions.count())
36+
* .fetchOne();
37+
* return new PageImpl<>(content, pageable, total);
38+
* }
39+
* }</pre>
40+
*
41+
* @author mazeneko
42+
*/
43+
public class UnifiedQueryPageable {
44+
private final Pageable pageable;
45+
private final SortConfig sortConfig;
46+
47+
/**
48+
* A configuration holder for sort resolution.
49+
*/
50+
public record SortConfig(
51+
/** a resolver that maps property names to {@link PropertyMetamodel} */
52+
PropertyMetamodelResolver propertyMetamodelResolver,
53+
/** a consumer that applies default ordering when no valid sort can be determined */
54+
Consumer<OrderByNameDeclaration> defaultOrder) {
55+
}
56+
57+
private UnifiedQueryPageable(
58+
Pageable pageable,
59+
SortConfig sortConfig) {
60+
this.pageable = pageable;
61+
this.sortConfig = sortConfig;
62+
}
63+
64+
public Pageable getPageable() {
65+
return pageable;
66+
}
67+
68+
public SortConfig getSortConfig() {
69+
return sortConfig;
70+
}
71+
72+
/**
73+
* Creates a {@link UnifiedQueryPageable}, resolving sort properties based on the entity's property names.
74+
*
75+
* @param pageable {@link Pageable} object to convert
76+
* @param sortTargetEntity the target entity whose properties are used for sorting
77+
* @return the {@link UnifiedQueryPageable}
78+
*/
79+
public static UnifiedQueryPageable from(
80+
Pageable pageable,
81+
EntityMetamodel<?> sortTargetEntity) {
82+
return UnifiedQueryPageable.from(pageable, sortTargetEntity, c -> {
83+
});
84+
}
85+
86+
/**
87+
* Creates a {@link UnifiedQueryPageable}, resolving sort properties based on the entity's property names.
88+
*
89+
* @param pageable {@link Pageable} object to convert
90+
* @param sortTargetEntity the target entity whose properties are used for sorting
91+
* @param defaultOrder a consumer that applies default ordering when no valid sort can be determined
92+
* @return the {@link UnifiedQueryPageable}
93+
*/
94+
public static UnifiedQueryPageable from(
95+
Pageable pageable,
96+
EntityMetamodel<?> sortTargetEntity,
97+
Consumer<OrderByNameDeclaration> defaultOrder) {
98+
final var nameToMetamodel = sortTargetEntity
99+
.allPropertyMetamodels()
100+
.stream()
101+
.collect(Collectors.toMap(PropertyMetamodel::getName, Function.identity()));
102+
final var sortConfig = new SortConfig(
103+
propertyName -> Optional.ofNullable(nameToMetamodel.get(propertyName)),
104+
defaultOrder);
105+
return new UnifiedQueryPageable(
106+
pageable,
107+
sortConfig);
108+
}
109+
110+
/**
111+
* Creates a {@link UnifiedQueryPageable}
112+
*
113+
* @param pageable {@link Pageable} object to convert
114+
* @param sortConfig sort configuration
115+
* @return the {@link UnifiedQueryPageable}
116+
*/
117+
public static UnifiedQueryPageable of(Pageable pageable, SortConfig sortConfig) {
118+
return new UnifiedQueryPageable(pageable, sortConfig);
119+
}
120+
121+
/**
122+
* Creates a {@link UnifiedQueryPageable}
123+
*
124+
* @param pageable {@link Pageable} object to convert
125+
* @param propertyMetamodelResolver a resolver that maps property names to {@link PropertyMetamodel}
126+
* @return the {@link UnifiedQueryPageable}
127+
*/
128+
public static UnifiedQueryPageable of(
129+
Pageable pageable,
130+
PropertyMetamodelResolver propertyMetamodelResolver) {
131+
return UnifiedQueryPageable.of(pageable, propertyMetamodelResolver, c -> {
132+
});
133+
}
134+
135+
/**
136+
* Creates a {@link UnifiedQueryPageable}
137+
*
138+
* @param pageable {@link Pageable} object to convert
139+
* @param propertyMetamodelResolver a resolver that maps property names to {@link PropertyMetamodel}
140+
* @param defaultOrder a consumer that applies default ordering when no valid sort can be determined
141+
* @return the {@link UnifiedQueryPageable}
142+
*/
143+
public static UnifiedQueryPageable of(
144+
Pageable pageable,
145+
PropertyMetamodelResolver propertyMetamodelResolver,
146+
Consumer<OrderByNameDeclaration> defaultOrder) {
147+
final var sortConfig = new SortConfig(
148+
propertyMetamodelResolver,
149+
defaultOrder);
150+
return new UnifiedQueryPageable(pageable, sortConfig);
151+
}
152+
153+
/**
154+
* Converts {@link Pageable} to {@link EntityQueryable#limit(Integer)}
155+
*
156+
* @return the limit.
157+
* if {@link Pageable#isUnpaged()} is {@code true} then null.
158+
*/
159+
public Integer limit() {
160+
return pageable.isUnpaged() ? null : pageable.getPageSize();
161+
}
162+
163+
/**
164+
* Converts {@link Pageable} to {@link EntityQueryable#offset(Integer)}
165+
*
166+
* @return the offset.
167+
* if {@link Pageable#isUnpaged()} is {@code true} then null.
168+
*/
169+
public Integer offset() {
170+
return pageable.isUnpaged()
171+
? null
172+
: Math.multiplyExact(pageable.getPageNumber(), pageable.getPageSize());
173+
}
174+
175+
/**
176+
* Creates an {@link OrderByNameDeclaration} consumer based on the
177+
* {@link Pageable}'s sort information using the provided {@link PropertyMetamodelResolver}.
178+
* <p>
179+
* If the {@link Pageable} is unsorted or no matching {@link PropertyMetamodel} is found,
180+
* a default ordering is applied.
181+
*
182+
* @return a consumer that configures ordering based on the resolved {@link PropertyMetamodel} instances
183+
*/
184+
public Consumer<OrderByNameDeclaration> orderBy() {
185+
return orderBy(missingProperties -> {
186+
});
187+
}
188+
189+
/**
190+
* Creates an {@link OrderByNameDeclaration} consumer based on the
191+
* {@link Pageable}'s sort information using the provided {@link PropertyMetamodelResolver}.
192+
* <p>
193+
* If the {@link Pageable} is unsorted, or if no {@link PropertyMetamodel} can be resolved
194+
* for a given sort property, a default ordering is applied.
195+
* <p>
196+
* The provided {@code handleMissingProperties} consumer is called with a list of
197+
* property names that could not be resolved. This can be used to throw an exception,
198+
* log a warning, or handle the situation in a custom way.
199+
*
200+
* @param handleMissingProperties a callback that handles property names which could not be resolved
201+
* @return a consumer that configures ordering based on the resolved {@link PropertyMetamodel} instances
202+
*/
203+
public Consumer<OrderByNameDeclaration> orderBy(
204+
Consumer<List<String>> handleMissingProperties) {
205+
if (pageable.getSort().isUnsorted()) {
206+
return sortConfig.defaultOrder();
207+
}
208+
final var orderSpecifiers = new ArrayList<Consumer<OrderByNameDeclaration>>();
209+
final var missingProperties = new ArrayList<String>();
210+
for (final var order : pageable.getSort()) {
211+
sortConfig
212+
.propertyMetamodelResolver()
213+
.resolve(order.getProperty())
214+
.ifPresentOrElse(
215+
propertyMetamodel -> orderSpecifiers.add(
216+
switch (order.getDirection()) {
217+
case ASC -> c -> c.asc(propertyMetamodel);
218+
case DESC -> c -> c.desc(propertyMetamodel);
219+
}),
220+
() -> missingProperties.add(order.getProperty()));
221+
}
222+
if (!missingProperties.isEmpty()) {
223+
handleMissingProperties.accept(missingProperties);
224+
}
225+
if (orderSpecifiers.isEmpty()) {
226+
return sortConfig.defaultOrder();
227+
}
228+
return c -> orderSpecifiers.forEach(orderSpecifier -> orderSpecifier.accept(c));
229+
}
230+
}

0 commit comments

Comments
 (0)