1 /*
2  * Copyright 2008-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.repository.query;
17
18 import static org.springframework.data.jpa.repository.query.QueryUtils.*;
19 import static org.springframework.data.repository.query.parser.Part.Type.*;
20
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.stream.Collectors;
26
27 import javax.persistence.criteria.CriteriaBuilder;
28 import javax.persistence.criteria.CriteriaQuery;
29 import javax.persistence.criteria.Expression;
30 import javax.persistence.criteria.ParameterExpression;
31 import javax.persistence.criteria.Path;
32 import javax.persistence.criteria.Predicate;
33 import javax.persistence.criteria.Root;
34 import javax.persistence.criteria.Selection;
35 import javax.persistence.metamodel.SingularAttribute;
36
37 import org.springframework.data.domain.Sort;
38 import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
39 import org.springframework.data.mapping.PropertyPath;
40 import org.springframework.data.repository.query.ReturnedType;
41 import org.springframework.data.repository.query.parser.AbstractQueryCreator;
42 import org.springframework.data.repository.query.parser.Part;
43 import org.springframework.data.repository.query.parser.Part.Type;
44 import org.springframework.data.repository.query.parser.PartTree;
45 import org.springframework.lang.Nullable;
46 import org.springframework.util.Assert;
47
48 /**
49  * Query creator to create a {@link CriteriaQuery} from a {@link PartTree}.
50  *
51  * @author Oliver Gierke
52  * @author Mark Paluch
53  * @author Michael Cramer
54  * @author Mark Paluch
55  * @author Reda.Housni-Alaoui
56  * @author Moritz Becker
57  * @author Andrey Kovalev
58  */

59 public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extends Object>, Predicate> {
60
61     private final CriteriaBuilder builder;
62     private final Root<?> root;
63     private final CriteriaQuery<? extends Object> query;
64     private final ParameterMetadataProvider provider;
65     private final ReturnedType returnedType;
66     private final PartTree tree;
67     private final EscapeCharacter escape;
68
69     /**
70      * Create a new {@link JpaQueryCreator}.
71      *
72      * @param tree must not be {@literal null}.
73      * @param type must not be {@literal null}.
74      * @param builder must not be {@literal null}.
75      * @param provider must not be {@literal null}.
76      */

77     public JpaQueryCreator(PartTree tree, ReturnedType type, CriteriaBuilder builder,
78             ParameterMetadataProvider provider) {
79
80         super(tree);
81         this.tree = tree;
82
83         CriteriaQuery<?> criteriaQuery = createCriteriaQuery(builder, type);
84
85         this.builder = builder;
86         this.query = criteriaQuery.distinct(tree.isDistinct());
87         this.root = query.from(type.getDomainType());
88         this.provider = provider;
89         this.returnedType = type;
90         this.escape = provider.getEscape();
91     }
92
93     /**
94      * Creates the {@link CriteriaQuery} to apply predicates on.
95      *
96      * @param builder will never be {@literal null}.
97      * @param type will never be {@literal null}.
98      * @return must not be {@literal null}.
99      */

100     protected CriteriaQuery<? extends Object> createCriteriaQuery(CriteriaBuilder builder, ReturnedType type) {
101
102         Class<?> typeToRead = type.getTypeToRead();
103
104         return typeToRead == null || tree.isExistsProjection() ? builder.createTupleQuery()
105                 : builder.createQuery(typeToRead);
106     }
107
108     /**
109      * Returns all {@link javax.persistence.criteria.ParameterExpression} created when creating the query.
110      *
111      * @return the parameterExpressions
112      */

113     public List<ParameterMetadata<?>> getParameterExpressions() {
114         return provider.getExpressions();
115     }
116
117     /*
118      * (non-Javadoc)
119      * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator)
120      */

121     @Override
122     protected Predicate create(Part part, Iterator<Object> iterator) {
123
124         return toPredicate(part, root);
125     }
126
127     /*
128      * (non-Javadoc)
129      * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator)
130      */

131     @Override
132     protected Predicate and(Part part, Predicate base, Iterator<Object> iterator) {
133         return builder.and(base, toPredicate(part, root));
134     }
135
136     /*
137      * (non-Javadoc)
138      * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object)
139      */

140     @Override
141     protected Predicate or(Predicate base, Predicate predicate) {
142         return builder.or(base, predicate);
143     }
144
145     /**
146      * Finalizes the given {@link Predicate} and applies the given sort. Delegates to
147      * {@link #complete(Predicate, Sort, CriteriaQuery, CriteriaBuilder, Root)} and hands it the current {@link CriteriaQuery}
148      * and {@link CriteriaBuilder}.
149      */

150     @Override
151     protected final CriteriaQuery<? extends Object> complete(Predicate predicate, Sort sort) {
152         return complete(predicate, sort, query, builder, root);
153     }
154
155     /**
156      * Template method to finalize the given {@link Predicate} using the given {@link CriteriaQuery} and
157      * {@link CriteriaBuilder}.
158      *
159      * @param predicate
160      * @param sort
161      * @param query
162      * @param builder
163      * @return
164      */

165     @SuppressWarnings({ "unchecked""rawtypes" })
166     protected CriteriaQuery<? extends Object> complete(@Nullable Predicate predicate, Sort sort,
167             CriteriaQuery<? extends Object> query, CriteriaBuilder builder, Root<?> root) {
168
169         if (returnedType.needsCustomConstruction()) {
170
171             List<Selection<?>> selections = new ArrayList<>();
172
173             for (String property : returnedType.getInputProperties()) {
174
175                 PropertyPath path = PropertyPath.from(property, returnedType.getDomainType());
176                 selections.add(toExpressionRecursively(root, path, true).alias(property));
177             }
178
179             query = query.multiselect(selections);
180
181         } else if (tree.isExistsProjection()) {
182
183             if (root.getModel().hasSingleIdAttribute()) {
184
185                 SingularAttribute<?, ?> id = root.getModel().getId(root.getModel().getIdType().getJavaType());
186                 query = query.multiselect(root.get((SingularAttribute) id).alias(id.getName()));
187
188             } else {
189
190                 query = query.multiselect(root.getModel().getIdClassAttributes().stream()//
191                         .map(it -> (Selection<?>) root.get((SingularAttribute) it).alias(it.getName()))
192                         .collect(Collectors.toList()));
193             }
194
195         } else {
196             query = query.select((Root) root);
197         }
198
199         CriteriaQuery<? extends Object> select = query.orderBy(QueryUtils.toOrders(sort, root, builder));
200         return predicate == null ? select : select.where(predicate);
201     }
202
203     /**
204      * Creates a {@link Predicate} from the given {@link Part}.
205      *
206      * @param part
207      * @param root
208      * @return
209      */

210     private Predicate toPredicate(Part part, Root<?> root) {
211         return new PredicateBuilder(part, root).build();
212     }
213
214     /**
215      * Simple builder to contain logic to create JPA {@link Predicate}s from {@link Part}s.
216      *
217      * @author Phil Webb
218      * @author Oliver Gierke
219      */

220     @SuppressWarnings({ "unchecked""rawtypes" })
221     private class PredicateBuilder {
222
223         private final Part part;
224         private final Root<?> root;
225
226         /**
227          * Creates a new {@link PredicateBuilder} for the given {@link Part} and {@link Root}.
228          *
229          * @param part must not be {@literal null}.
230          * @param root must not be {@literal null}.
231          */

232         public PredicateBuilder(Part part, Root<?> root) {
233
234             Assert.notNull(part, "Part must not be null!");
235             Assert.notNull(root, "Root must not be null!");
236             this.part = part;
237             this.root = root;
238         }
239
240         /**
241          * Builds a JPA {@link Predicate} from the underlying {@link Part}.
242          *
243          * @return
244          */

245         public Predicate build() {
246
247             PropertyPath property = part.getProperty();
248             Type type = part.getType();
249
250             switch (type) {
251                 case BETWEEN:
252                     ParameterMetadata<Comparable> first = provider.next(part);
253                     ParameterMetadata<Comparable> second = provider.next(part);
254                     return builder.between(getComparablePath(root, part), first.getExpression(), second.getExpression());
255                 case AFTER:
256                 case GREATER_THAN:
257                     return builder.greaterThan(getComparablePath(root, part),
258                             provider.next(part, Comparable.class).getExpression());
259                 case GREATER_THAN_EQUAL:
260                     return builder.greaterThanOrEqualTo(getComparablePath(root, part),
261                             provider.next(part, Comparable.class).getExpression());
262                 case BEFORE:
263                 case LESS_THAN:
264                     return builder.lessThan(getComparablePath(root, part), provider.next(part, Comparable.class).getExpression());
265                 case LESS_THAN_EQUAL:
266                     return builder.lessThanOrEqualTo(getComparablePath(root, part),
267                             provider.next(part, Comparable.class).getExpression());
268                 case IS_NULL:
269                     return getTypedPath(root, part).isNull();
270                 case IS_NOT_NULL:
271                     return getTypedPath(root, part).isNotNull();
272                 case NOT_IN:
273                     // cast required for eclipselink workaround, see DATAJPA-433
274                     return upperIfIgnoreCase(getTypedPath(root, part)).in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression()).not();
275                 case IN:
276                     // cast required for eclipselink workaround, see DATAJPA-433
277                     return upperIfIgnoreCase(getTypedPath(root, part)).in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression());
278                 case STARTING_WITH:
279                 case ENDING_WITH:
280                 case CONTAINING:
281                 case NOT_CONTAINING:
282
283                     if (property.getLeafProperty().isCollection()) {
284
285                         Expression<Collection<Object>> propertyExpression = traversePath(root, property);
286                         ParameterExpression<Object> parameterExpression = provider.next(part).getExpression();
287
288                         // Can't just call .not() in case of negation as EclipseLink chokes on that.
289                         return type.equals(NOT_CONTAINING) ? isNotMember(builder, parameterExpression, propertyExpression)
290                                 : isMember(builder, parameterExpression, propertyExpression);
291                     }
292
293                 case LIKE:
294                 case NOT_LIKE:
295                     Expression<String> stringPath = getTypedPath(root, part);
296                     Expression<String> propertyExpression = upperIfIgnoreCase(stringPath);
297                     Expression<String> parameterExpression = upperIfIgnoreCase(provider.next(part, String.class).getExpression());
298                     Predicate like = builder.like(propertyExpression, parameterExpression, escape.getEscapeCharacter());
299                     return type.equals(NOT_LIKE) || type.equals(NOT_CONTAINING) ? like.not() : like;
300                 case TRUE:
301                     Expression<Boolean> truePath = getTypedPath(root, part);
302                     return builder.isTrue(truePath);
303                 case FALSE:
304                     Expression<Boolean> falsePath = getTypedPath(root, part);
305                     return builder.isFalse(falsePath);
306                 case SIMPLE_PROPERTY:
307                     ParameterMetadata<Object> expression = provider.next(part);
308                     Expression<Object> path = getTypedPath(root, part);
309                     return expression.isIsNullParameter() ? path.isNull()
310                             : builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression()));
311                 case NEGATING_SIMPLE_PROPERTY:
312                     return builder.notEqual(upperIfIgnoreCase(getTypedPath(root, part)),
313                             upperIfIgnoreCase(provider.next(part).getExpression()));
314                 case IS_EMPTY:
315                 case IS_NOT_EMPTY:
316
317                     if (!property.getLeafProperty().isCollection()) {
318                         throw new IllegalArgumentException("IsEmpty / IsNotEmpty can only be used on collection properties!");
319                     }
320
321                     Expression<Collection<Object>> collectionPath = traversePath(root, property);
322                     return type.equals(IS_NOT_EMPTY) ? builder.isNotEmpty(collectionPath) : builder.isEmpty(collectionPath);
323
324                 default:
325                     throw new IllegalArgumentException("Unsupported keyword " + type);
326             }
327         }
328
329         private <T> Predicate isMember(CriteriaBuilder builder, Expression<T> parameter,
330                 Expression<Collection<T>> property) {
331             return builder.isMember(parameter, property);
332         }
333
334         private <T> Predicate isNotMember(CriteriaBuilder builder, Expression<T> parameter,
335                 Expression<Collection<T>> property) {
336             return builder.isNotMember(parameter, property);
337         }
338
339         /**
340          * Applies an {@code UPPERCASE} conversion to the given {@link Expression} in case the underlying {@link Part}
341          * requires ignoring case.
342          *
343          * @param expression must not be {@literal null}.
344          * @return
345          */

346         private <T> Expression<T> upperIfIgnoreCase(Expression<? extends T> expression) {
347
348             switch (part.shouldIgnoreCase()) {
349
350                 case ALWAYS:
351
352                     Assert.state(canUpperCase(expression), "Unable to ignore case of " + expression.getJavaType().getName()
353                             + " types, the property '" + part.getProperty().getSegment() + "' must reference a String");
354                     return (Expression<T>) builder.upper((Expression<String>) expression);
355
356                 case WHEN_POSSIBLE:
357
358                     if (canUpperCase(expression)) {
359                         return (Expression<T>) builder.upper((Expression<String>) expression);
360                     }
361
362                 case NEVER:
363                 default:
364
365                     return (Expression<T>) expression;
366             }
367         }
368
369         private boolean canUpperCase(Expression<?> expression) {
370             return String.class.equals(expression.getJavaType());
371         }
372
373         /**
374          * Returns a path to a {@link Comparable}.
375          *
376          * @param root
377          * @param part
378          * @return
379          */

380         private Expression<? extends Comparable> getComparablePath(Root<?> root, Part part) {
381             return getTypedPath(root, part);
382         }
383
384         private <T> Expression<T> getTypedPath(Root<?> root, Part part) {
385             return toExpressionRecursively(root, part.getProperty());
386         }
387
388         private <T> Expression<T> traversePath(Path<?> root, PropertyPath path) {
389
390             Path<Object> result = root.get(path.getSegment());
391             return (Expression<T>) (path.hasNext() ? traversePath(result, path.next()) : result);
392         }
393     }
394 }
395