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, falsetrue);
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(), truethis.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