1
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
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
70 public ParameterMetadataProvider(CriteriaBuilder builder, ParametersParameterAccessor accessor,
71 EscapeCharacter escape) {
72 this(builder, accessor.iterator(), accessor.getParameters(), escape);
73 }
74
75
83 public ParameterMetadataProvider(CriteriaBuilder builder, Parameters<?, ?> parameters, EscapeCharacter escape) {
84 this(builder, null, parameters, escape);
85 }
86
87
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
115 public List<ParameterMetadata<?>> getExpressions() {
116 return expressions;
117 }
118
119
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
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
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
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
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
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
217 public ParameterExpression<T> getExpression() {
218 return expression;
219 }
220
221
224 public boolean isIsNullParameter() {
225 return Type.IS_NULL.equals(type);
226 }
227
228
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
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