1 /*
2  * Copyright 2011-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 java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.function.Supplier;
25 import java.util.stream.Collectors;
26
27 import javax.persistence.criteria.CriteriaBuilder;
28 import javax.persistence.criteria.ParameterExpression;
29
30 import org.springframework.data.jpa.provider.PersistenceProvider;
31 import org.springframework.data.repository.query.Parameter;
32 import org.springframework.data.repository.query.Parameters;
33 import org.springframework.data.repository.query.ParametersParameterAccessor;
34 import org.springframework.data.repository.query.parser.Part;
35 import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
36 import org.springframework.data.repository.query.parser.Part.Type;
37 import org.springframework.expression.Expression;
38 import org.springframework.lang.Nullable;
39 import org.springframework.util.Assert;
40 import org.springframework.util.ClassUtils;
41 import org.springframework.util.CollectionUtils;
42 import org.springframework.util.ObjectUtils;
43
44 /**
45  * Helper class to allow easy creation of {@link ParameterMetadata}s.
46  *
47  * @author Oliver Gierke
48  * @author Thomas Darimont
49  * @author Mark Paluch
50  * @author Christoph Strobl
51  * @author Jens Schauder
52  * @author Andrey Kovalev
53  */

54 class ParameterMetadataProvider {
55
56     private final CriteriaBuilder builder;
57     private final Iterator<? extends Parameter> parameters;
58     private final List<ParameterMetadata<?>> expressions;
59     private final @Nullable Iterator<Object> bindableParameterValues;
60     private final EscapeCharacter escape;
61
62     /**
63      * Creates a new {@link ParameterMetadataProvider} from the given {@link CriteriaBuilder} and
64      * {@link ParametersParameterAccessor}.
65      *
66      * @param builder must not be {@literal null}.
67      * @param accessor must not be {@literal null}.
68      * @param escape must not be {@literal null}.
69      */

70     public ParameterMetadataProvider(CriteriaBuilder builder, ParametersParameterAccessor accessor,
71             EscapeCharacter escape) {
72         this(builder, accessor.iterator(), accessor.getParameters(), escape);
73     }
74
75     /**
76      * Creates a new {@link ParameterMetadataProvider} from the given {@link CriteriaBuilder} and {@link Parameters} with
77      * support for parameter value customizations via {@link PersistenceProvider}.
78      *
79      * @param builder must not be {@literal null}.
80      * @param parameters must not be {@literal null}.
81      * @param escape must not be {@literal null}.
82      */

83     public ParameterMetadataProvider(CriteriaBuilder builder, Parameters<?, ?> parameters, EscapeCharacter escape) {
84         this(builder, null, parameters, escape);
85     }
86
87     /**
88      * Creates a new {@link ParameterMetadataProvider} from the given {@link CriteriaBuilder} an {@link Iterable} of all
89      * bindable parameter values, and {@link Parameters}.
90      *
91      * @param builder must not be {@literal null}.
92      * @param bindableParameterValues may be {@literal null}.
93      * @param parameters must not be {@literal null}.
94      * @param escape must not be {@literal null}.
95      */

96     private ParameterMetadataProvider(CriteriaBuilder builder, @Nullable Iterator<Object> bindableParameterValues,
97             Parameters<?, ?> parameters, EscapeCharacter escape) {
98
99         Assert.notNull(builder, "CriteriaBuilder must not be null!");
100         Assert.notNull(parameters, "Parameters must not be null!");
101         Assert.notNull(escape, "EscapeCharacter must not be null!");
102
103         this.builder = builder;
104         this.parameters = parameters.getBindableParameters().iterator();
105         this.expressions = new ArrayList<>();
106         this.bindableParameterValues = bindableParameterValues;
107         this.escape = escape;
108     }
109
110     /**
111      * Returns all {@link ParameterMetadata}s built.
112      *
113      * @return the expressions
114      */

115     public List<ParameterMetadata<?>> getExpressions() {
116         return expressions;
117     }
118
119     /**
120      * Builds a new {@link ParameterMetadata} for given {@link Part} and the next {@link Parameter}.
121      */

122     @SuppressWarnings("unchecked")
123     public <T> ParameterMetadata<T> next(Part part) {
124
125         Assert.isTrue(parameters.hasNext(), () -> String.format("No parameter available for part %s.", part));
126
127         Parameter parameter = parameters.next();
128         return (ParameterMetadata<T>) next(part, parameter.getType(), parameter);
129     }
130
131     /**
132      * Builds a new {@link ParameterMetadata} of the given {@link Part} and type. Forwards the underlying
133      * {@link Parameters} as well.
134      *
135      * @param <T> is the type parameter of the returned {@link ParameterMetadata}.
136      * @param type must not be {@literal null}.
137      * @return ParameterMetadata for the next parameter.
138      */

139     @SuppressWarnings("unchecked")
140     public <T> ParameterMetadata<? extends T> next(Part part, Class<T> type) {
141
142         Parameter parameter = parameters.next();
143         Class<?> typeToUse = ClassUtils.isAssignable(type, parameter.getType()) ? parameter.getType() : type;
144         return (ParameterMetadata<? extends T>) next(part, typeToUse, parameter);
145     }
146
147     /**
148      * Builds a new {@link ParameterMetadata} for the given type and name.
149      *
150      * @param <T> type parameter for the returned {@link ParameterMetadata}.
151      * @param part must not be {@literal null}.
152      * @param type must not be {@literal null}.
153      * @param parameter providing the name for the returned {@link ParameterMetadata}.
154      * @return a new {@link ParameterMetadata} for the given type and name.
155      */

156     private <T> ParameterMetadata<T> next(Part part, Class<T> type, Parameter parameter) {
157
158         Assert.notNull(type, "Type must not be null!");
159
160         /*
161          * We treat Expression types as Object vales since the real value to be bound as a parameter is determined at query time.
162          */

163         @SuppressWarnings("unchecked")
164         Class<T> reifiedType = Expression.class.equals(type) ? (Class<T>) Object.class : type;
165
166         Supplier<String> name = () -> parameter.getName()
167                 .orElseThrow(() -> new IllegalArgumentException("o_O Parameter needs to be named"));
168
169         ParameterExpression<T> expression = parameter.isExplicitlyNamed() //
170                 ? builder.parameter(reifiedType, name.get()) //
171                 : builder.parameter(reifiedType);
172
173         Object value = bindableParameterValues == null ? ParameterMetadata.PLACEHOLDER : bindableParameterValues.next();
174
175         ParameterMetadata<T> metadata = new ParameterMetadata<>(expression, part, value, escape);
176         expressions.add(metadata);
177
178         return metadata;
179     }
180
181     EscapeCharacter getEscape() {
182         return escape;
183     }
184
185     /**
186      * @author Oliver Gierke
187      * @author Thomas Darimont
188      * @author Andrey Kovalev
189      * @param <T>
190      */

191     static class ParameterMetadata<T> {
192
193         static final Object PLACEHOLDER = new Object();
194
195         private final Type type;
196         private final ParameterExpression<T> expression;
197         private final EscapeCharacter escape;
198         private final boolean ignoreCase;
199
200         /**
201          * Creates a new {@link ParameterMetadata}.
202          */

203         public ParameterMetadata(ParameterExpression<T> expression, Part part, @Nullable Object value,
204                 EscapeCharacter escape) {
205
206             this.expression = expression;
207             this.type = value == null && Type.SIMPLE_PROPERTY.equals(part.getType()) ? Type.IS_NULL : part.getType();
208             this.ignoreCase = IgnoreCaseType.ALWAYS.equals(part.shouldIgnoreCase());
209             this.escape = escape;
210         }
211
212         /**
213          * Returns the {@link ParameterExpression}.
214          *
215          * @return the expression
216          */

217         public ParameterExpression<T> getExpression() {
218             return expression;
219         }
220
221         /**
222          * Returns whether the parameter shall be considered an {@literal IS NULL} parameter.
223          */

224         public boolean isIsNullParameter() {
225             return Type.IS_NULL.equals(type);
226         }
227
228         /**
229          * Prepares the object before it's actually bound to the {@link javax.persistence.Query;}.
230          *
231          * @param value must not be {@literal null}.
232          */

233         @Nullable
234         public Object prepare(Object value) {
235
236             Assert.notNull(value, "Value must not be null!");
237
238             Class<? extends T> expressionType = expression.getJavaType();
239
240             if (String.class.equals(expressionType)) {
241
242                 switch (type) {
243                     case STARTING_WITH:
244                         return String.format("%s%%", escape.escape(value.toString()));
245                     case ENDING_WITH:
246                         return String.format("%%%s", escape.escape(value.toString()));
247                     case CONTAINING:
248                     case NOT_CONTAINING:
249                         return String.format("%%%s%%", escape.escape(value.toString()));
250                     default:
251                         return value;
252                 }
253             }
254
255             return Collection.class.isAssignableFrom(expressionType) //
256                     ? upperIfIgnoreCase(ignoreCase, toCollection(value)) //
257                     : value;
258         }
259
260         /**
261          * Returns the given argument as {@link Collection} which means it will return it as is if it's a
262          * {@link Collections}, turn an array into an {@link ArrayList} or simply wrap any other value into a single element
263          * {@link Collections}.
264          *
265          * @param value the value to be converted to a {@link Collection}.
266          * @return the object itself as a {@link Collection} or a {@link Collection} constructed from the value.
267          */

268         @Nullable
269         private static Collection<?> toCollection(@Nullable Object value) {
270
271             if (value == null) {
272                 return null;
273             }
274
275             if (value instanceof Collection) {
276
277                 Collection<?> collection = (Collection<?>) value;
278                 return collection.isEmpty() ? null : collection;
279             }
280
281             if (ObjectUtils.isArray(value)) {
282
283                 List<Object> collection = Arrays.asList(ObjectUtils.toObjectArray(value));
284                 return collection.isEmpty() ? null : collection;
285             }
286
287             return Collections.singleton(value);
288         }
289
290         @Nullable
291         @SuppressWarnings("unchecked")
292         private static Collection<?> upperIfIgnoreCase(boolean ignoreCase, @Nullable Collection<?> collection) {
293
294             if (!ignoreCase || CollectionUtils.isEmpty(collection)) {
295                 return collection;
296             }
297
298             return ((Collection<String>) collection).stream() //
299                     .map(it -> it == null //
300                             ? null //
301                             : it.toUpperCase()) //
302                     .collect(Collectors.toList());
303         }
304     }
305 }
306