1 /*
2 * Copyright 2013-2020 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.springframework.data.jpa.domain;
17
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.List;
23
24 import javax.persistence.metamodel.Attribute;
25 import javax.persistence.metamodel.PluralAttribute;
26
27 import org.springframework.data.domain.Sort;
28 import org.springframework.lang.Nullable;
29 import org.springframework.util.Assert;
30
31 /**
32 * Sort option for queries that wraps JPA meta-model {@link Attribute}s for sorting.
33 *
34 * @author Thomas Darimont
35 * @author Oliver Gierke
36 * @author Christoph Strobl
37 * @author David Madden
38 */
39 public class JpaSort extends Sort {
40
41 private static final long serialVersionUID = 1L;
42
43 /**
44 * Creates a new {@link JpaSort} for the given attributes with the default sort direction.
45 *
46 * @param attributes must not be {@literal null} or empty.
47 * @deprecated since 2.3, use {@link JpaSort#of(Attribute...)} instead.
48 */
49 @Deprecated
50 public JpaSort(Attribute<?, ?>... attributes) {
51 this(DEFAULT_DIRECTION, attributes);
52 }
53
54 /**
55 * Creates a new {@link JpaSort} instance with the given {@link Path}s.
56 *
57 * @param paths must not be {@literal null} or empty.
58 * @deprecated since 2.3, use {@link JpaSort#of(Path...))} instead.
59 */
60 @Deprecated
61 public JpaSort(Path<?, ?>... paths) {
62 this(DEFAULT_DIRECTION, paths);
63 }
64
65 /**
66 * Creates a new {@link JpaSort} for the given direction and attributes.
67 *
68 * @param direction the sorting direction.
69 * @param attributes must not be {@literal null} or empty.
70 * @deprecated since 2.3, use {@link JpaSort#of(Direction, Attribute...)} instead.
71 */
72 @Deprecated
73 public JpaSort(Direction direction, Attribute<?, ?>... attributes) {
74 this(direction, paths(attributes));
75 }
76
77 /**
78 * Creates a new {@link JpaSort} for the given direction and {@link Path}s.
79 *
80 * @param direction the sorting direction.
81 * @param paths must not be {@literal null} or empty.
82 * @deprecated since 2.3, use {@link JpaSort#of(Direction, Path...)} instead.
83 */
84 @Deprecated
85 public JpaSort(Direction direction, Path<?, ?>... paths) {
86 this(direction, Arrays.asList(paths));
87 }
88
89 private JpaSort(Direction direction, List<Path<?, ?>> paths) {
90 this(Collections.<Order> emptyList(), direction, paths);
91 }
92
93 private JpaSort(List<Order> orders, @Nullable Direction direction, List<Path<?, ?>> paths) {
94 super(combine(orders, direction, paths));
95 }
96
97 private JpaSort(List<Order> orders) {
98 super(orders);
99 }
100
101 /**
102 * Creates a new {@link JpaSort} for the given attributes with the default sort direction.
103 *
104 * @param attributes must not be {@literal null} or empty.
105 */
106 public static JpaSort of(Attribute<?, ?>... attributes) {
107 return new JpaSort(attributes);
108 }
109
110 /**
111 * Creates a new {@link JpaSort} instance with the given {@link Path}s.
112 *
113 * @param paths must not be {@literal null} or empty.
114 */
115 public static JpaSort of(JpaSort.Path<?, ?>... paths) {
116 return new JpaSort(paths);
117 }
118
119 /**
120 * Creates a new {@link JpaSort} for the given direction and attributes.
121 *
122 * @param direction the sorting direction.
123 * @param attributes must not be {@literal null} or empty.
124 */
125 public static JpaSort of(Direction direction, Attribute<?, ?>... attributes) {
126 return new JpaSort(direction, attributes);
127 }
128
129 /**
130 * Creates a new {@link JpaSort} for the given direction and {@link Path}s.
131 *
132 * @param direction the sorting direction.
133 * @param paths must not be {@literal null} or empty.
134 */
135 public static JpaSort of(Direction direction, Path<?, ?>... paths) {
136 return new JpaSort(direction, paths);
137 }
138
139 /**
140 * Returns a new {@link JpaSort} with the given sorting criteria added to the current one.
141 *
142 * @param direction can be {@literal null}.
143 * @param attributes must not be {@literal null}.
144 * @return
145 */
146 public JpaSort and(@Nullable Direction direction, Attribute<?, ?>... attributes) {
147
148 Assert.notNull(attributes, "Attributes must not be null!");
149
150 return and(direction, paths(attributes));
151 }
152
153 /**
154 * Returns a new {@link JpaSort} with the given sorting criteria added to the current one.
155 *
156 * @param direction can be {@literal null}.
157 * @param paths must not be {@literal null}.
158 * @return
159 */
160 public JpaSort and(@Nullable Direction direction, Path<?, ?>... paths) {
161
162 Assert.notNull(paths, "Paths must not be null!");
163
164 List<Order> existing = new ArrayList<Order>();
165
166 for (Order order : this) {
167 existing.add(order);
168 }
169
170 return new JpaSort(existing, direction, Arrays.asList(paths));
171 }
172
173 /**
174 * Returns a new {@link JpaSort} with the given sorting criteria added to the current one.
175 *
176 * @param direction can be {@literal null}.
177 * @param properties must not be {@literal null} or empty.
178 * @return
179 */
180 public JpaSort andUnsafe(@Nullable Direction direction, String... properties) {
181
182 Assert.notEmpty(properties, "Properties must not be empty!");
183
184 List<Order> orders = new ArrayList<Order>();
185
186 for (Order order : this) {
187 orders.add(order);
188 }
189
190 for (String property : properties) {
191 orders.add(new JpaOrder(direction, property));
192 }
193
194 return new JpaSort(orders, direction, Collections.<Path<?, ?>> emptyList());
195 }
196
197 /**
198 * Turns the given {@link Attribute}s into {@link Path}s.
199 *
200 * @param attributes must not be {@literal null} or empty.
201 * @return
202 */
203 private static Path<?, ?>[] paths(Attribute<?, ?>[] attributes) {
204
205 Assert.notNull(attributes, "Attributes must not be null!");
206 Assert.notEmpty(attributes, "Attributes must not be empty!");
207
208 Path<?, ?>[] paths = new Path[attributes.length];
209
210 for (int i = 0; i < attributes.length; i++) {
211 paths[i] = path(attributes[i]);
212 }
213
214 return paths;
215 }
216
217 private static List<Order> combine(List<Order> orders, @Nullable Direction direction, List<Path<?, ?>> paths) {
218
219 List<Order> result = new ArrayList<Sort.Order>(orders);
220
221 for (Path<?, ?> path : paths) {
222 result.add(new Order(direction, path.toString()));
223 }
224
225 return result;
226 }
227
228 /**
229 * Creates a new {@link Path} for the given {@link Attribute}.
230 *
231 * @param attribute must not be {@literal null}.
232 * @return
233 */
234 public static <A extends Attribute<T, S>, T, S> Path<T, S> path(A attribute) {
235
236 Assert.notNull(attribute, "Attribute must not be null!");
237 return new Path<>(Collections.singletonList(attribute));
238 }
239
240 /**
241 * Creates a new {@link Path} for the given {@link PluralAttribute}.
242 *
243 * @param attribute must not be {@literal null}.
244 * @return
245 */
246 public static <P extends PluralAttribute<T, ?, S>, T, S> Path<T, S> path(P attribute) {
247
248 Assert.notNull(attribute, "Attribute must not be null!");
249 return new Path<>(Collections.singletonList(attribute));
250 }
251
252 /**
253 * Creates new unsafe {@link JpaSort} based on given properties.
254 *
255 * @param properties must not be {@literal null} or empty.
256 * @return
257 */
258 public static JpaSort unsafe(String... properties) {
259 return unsafe(Sort.DEFAULT_DIRECTION, properties);
260 }
261
262 /**
263 * Creates new unsafe {@link JpaSort} based on given {@link Direction} and properties.
264 *
265 * @param direction must not be {@literal null}.
266 * @param properties must not be {@literal null} or empty.
267 * @return
268 */
269 public static JpaSort unsafe(Direction direction, String... properties) {
270
271 Assert.notNull(direction, "Direction must not be null!");
272 Assert.notEmpty(properties, "Properties must not be empty!");
273 Assert.noNullElements(properties, "Properties must not contain null values!");
274
275 return unsafe(direction, Arrays.asList(properties));
276 }
277
278 /**
279 * Creates new unsafe {@link JpaSort} based on given {@link Direction} and properties.
280 *
281 * @param direction must not be {@literal null}.
282 * @param properties must not be {@literal null} or empty.
283 * @return
284 */
285 public static JpaSort unsafe(Direction direction, List<String> properties) {
286
287 Assert.notEmpty(properties, "Properties must not be empty!");
288
289 List<Order> orders = new ArrayList<>(properties.size());
290
291 for (String property : properties) {
292 orders.add(new JpaOrder(direction, property));
293 }
294
295 return new JpaSort(orders);
296 }
297
298 /**
299 * Value object to abstract a collection of {@link Attribute}s.
300 *
301 * @author Oliver Gierke
302 */
303 public static class Path<T, S> {
304
305 private final Collection<Attribute<?, ?>> attributes;
306
307 private Path(List<? extends Attribute<?, ?>> attributes) {
308 this.attributes = Collections.unmodifiableList(attributes);
309 }
310
311 /**
312 * Collects the given {@link Attribute} and returning a new {@link Path} pointing to the attribute type.
313 *
314 * @param attribute must not be {@literal null}.
315 * @return
316 */
317 public <A extends Attribute<S, U>, U> Path<S, U> dot(A attribute) {
318 return new Path<S, U>(add(attribute));
319 }
320
321 /**
322 * Collects the given {@link PluralAttribute} and returning a new {@link Path} pointing to the attribute type.
323 *
324 * @param attribute must not be {@literal null}.
325 * @return
326 */
327 public <P extends PluralAttribute<S, ?, U>, U> Path<S, U> dot(P attribute) {
328 return new Path<S, U>(add(attribute));
329 }
330
331 private List<Attribute<?, ?>> add(Attribute<?, ?> attribute) {
332
333 Assert.notNull(attribute, "Attribute must not be null!");
334
335 List<Attribute<?, ?>> newAttributes = new ArrayList<Attribute<?, ?>>(attributes.size() + 1);
336 newAttributes.addAll(attributes);
337 newAttributes.add(attribute);
338 return newAttributes;
339 }
340
341 /*
342 * (non-Javadoc)
343 * @see java.lang.Object#toString()
344 */
345 @Override
346 public String toString() {
347
348 StringBuilder builder = new StringBuilder();
349
350 for (Attribute<?, ?> attribute : attributes) {
351 builder.append(attribute.getName()).append(".");
352 }
353
354 return builder.length() == 0 ? "" : builder.substring(0, builder.lastIndexOf("."));
355 }
356 }
357
358 /**
359 * Custom {@link Order} that keeps a flag to indicate unsafe property handling, i.e. the String provided is not
360 * necessarily a property but can be an arbitrary expression piped into the query execution. We also keep an
361 * additional {@code ignoreCase} flag around as the constructor of the superclass is private currently.
362 *
363 * @author Christoph Strobl
364 * @author Oliver Gierke
365 */
366 public static class JpaOrder extends Order {
367
368 private static final long serialVersionUID = 1L;
369
370 private final boolean unsafe;
371 private final boolean ignoreCase;
372
373 /**
374 * Creates a new {@link JpaOrder} instance. if order is {@literal null} then order defaults to
375 * {@link Sort#DEFAULT_DIRECTION}
376 *
377 * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}.
378 * @param property must not be {@literal null}.
379 */
380 private JpaOrder(@Nullable Direction direction, String property) {
381 this(direction, property, NullHandling.NATIVE);
382 }
383
384 /**
385 * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to
386 * {@link Sort#DEFAULT_DIRECTION}.
387 *
388 * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}.
389 * @param property must not be {@literal null}.
390 * @param nullHandlingHint can be {@literal null}, will default to {@link NullHandling#NATIVE}.
391 */
392 private JpaOrder(@Nullable Direction direction, String property, NullHandling nullHandlingHint) {
393 this(direction, property, nullHandlingHint, false, true);
394 }
395
396 private JpaOrder(@Nullable Direction direction, String property, NullHandling nullHandling, boolean ignoreCase,
397 boolean unsafe) {
398
399 super(direction, property, nullHandling);
400 this.ignoreCase = ignoreCase;
401 this.unsafe = unsafe;
402 }
403
404 /*
405 * (non-Javadoc)
406 * @see org.springframework.data.domain.Sort.Order#with(org.springframework.data.domain.Sort.Direction)
407 */
408 @Override
409 public JpaOrder with(Direction order) {
410 return new JpaOrder(order, getProperty(), getNullHandling(), isIgnoreCase(), this.unsafe);
411 }
412
413 /*
414 * (non-Javadoc)
415 * @see org.springframework.data.domain.Sort.Order#with(org.springframework.data.domain.Sort.NullHandling)
416 */
417 @Override
418 public JpaOrder with(NullHandling nullHandling) {
419 return new JpaOrder(getDirection(), getProperty(), nullHandling, isIgnoreCase(), this.unsafe);
420 }
421
422 /**
423 * Creates new {@link Sort} with potentially unsafe {@link Order} instances.
424 *
425 * @param properties must not be {@literal null}.
426 * @return
427 */
428 public Sort withUnsafe(String... properties) {
429
430 Assert.notEmpty(properties, "Properties must not be empty!");
431 Assert.noNullElements(properties, "Properties must not contain null values!");
432
433 List<Order> orders = new ArrayList<>(properties.length);
434
435 for (String property : properties) {
436 orders.add(new JpaOrder(getDirection(), property, getNullHandling(), isIgnoreCase(), this.unsafe));
437 }
438
439 return Sort.by(orders);
440 }
441
442 /*
443 * (non-Javadoc)
444 * @see org.springframework.data.domain.Sort.Order#ignoreCase()
445 */
446 @Override
447 public JpaOrder ignoreCase() {
448 return new JpaOrder(getDirection(), getProperty(), getNullHandling(), true, this.unsafe);
449 }
450
451 /*
452 * (non-Javadoc)
453 * @see org.springframework.data.domain.Sort.Order#isIgnoreCase()
454 */
455 @Override
456 public boolean isIgnoreCase() {
457 return super.isIgnoreCase() || ignoreCase;
458 }
459
460 /**
461 * @return true if {@link JpaOrder} created {@link #withUnsafe(String...)}.
462 */
463 public boolean isUnsafe() {
464 return unsafe;
465 }
466 }
467 }
468