1
16 package org.springframework.data.jpa.repository.query;
17
18 import java.util.List;
19 import java.util.function.Function;
20
21 import javax.persistence.Query;
22 import javax.persistence.TemporalType;
23
24 import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
25 import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
26 import org.springframework.data.jpa.repository.query.QueryParameterSetter.NamedOrIndexedQueryParameterSetter;
27 import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
28 import org.springframework.data.repository.query.Parameter;
29 import org.springframework.data.repository.query.Parameters;
30 import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
31 import org.springframework.data.spel.EvaluationContextProvider;
32 import org.springframework.expression.EvaluationContext;
33 import org.springframework.expression.Expression;
34 import org.springframework.expression.spel.standard.SpelExpressionParser;
35 import org.springframework.lang.Nullable;
36 import org.springframework.util.Assert;
37
38
47 abstract class QueryParameterSetterFactory {
48
49 @Nullable
50 abstract QueryParameterSetter create(ParameterBinding binding, DeclaredQuery declaredQuery);
51
52
58 static QueryParameterSetterFactory basic(JpaParameters parameters) {
59
60 Assert.notNull(parameters, "JpaParameters must not be null!");
61
62 return new BasicQueryParameterSetterFactory(parameters);
63 }
64
65
73 static QueryParameterSetterFactory forCriteriaQuery(JpaParameters parameters, List<ParameterMetadata<?>> metadata) {
74
75 Assert.notNull(parameters, "JpaParameters must not be null!");
76 Assert.notNull(metadata, "ParameterMetadata must not be null!");
77
78 return new CriteriaQueryParameterSetterFactory(parameters, metadata);
79 }
80
81
91 static QueryParameterSetterFactory parsing(SpelExpressionParser parser,
92 QueryMethodEvaluationContextProvider evaluationContextProvider, Parameters<?, ?> parameters) {
93
94 Assert.notNull(parser, "SpelExpressionParser must not be null!");
95 Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
96 Assert.notNull(parameters, "Parameters must not be null!");
97
98 return new ExpressionBasedQueryParameterSetterFactory(parser, evaluationContextProvider, parameters);
99 }
100
101
109 private static QueryParameterSetter createSetter(Function<JpaParametersParameterAccessor, Object> valueExtractor,
110 ParameterBinding binding, @Nullable JpaParameter parameter) {
111
112 TemporalType temporalType = parameter != null && parameter.isTemporalParameter()
113 ? parameter.getRequiredTemporalType()
114 : null;
115
116 return new NamedOrIndexedQueryParameterSetter(valueExtractor.andThen(binding::prepare),
117 ParameterImpl.of(parameter, binding), temporalType);
118 }
119
120
127 private static class ExpressionBasedQueryParameterSetterFactory extends QueryParameterSetterFactory {
128
129 private final SpelExpressionParser parser;
130 private final QueryMethodEvaluationContextProvider evaluationContextProvider;
131 private final Parameters<?, ?> parameters;
132
133
138 ExpressionBasedQueryParameterSetterFactory(SpelExpressionParser parser,
139 QueryMethodEvaluationContextProvider evaluationContextProvider, Parameters<?, ?> parameters) {
140
141 Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
142 Assert.notNull(parser, "SpelExpressionParser must not be null!");
143 Assert.notNull(parameters, "Parameters must not be null!");
144
145 this.evaluationContextProvider = evaluationContextProvider;
146 this.parser = parser;
147 this.parameters = parameters;
148 }
149
150
154 @Nullable
155 @Override
156 public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery declaredQuery) {
157
158 if (!binding.isExpression()) {
159 return null;
160 }
161
162 Expression expression = parser.parseExpression(binding.getExpression());
163
164 return createSetter(values -> evaluateExpression(expression, values), binding, null);
165 }
166
167
174 @Nullable
175 private Object evaluateExpression(Expression expression, JpaParametersParameterAccessor accessor) {
176
177 EvaluationContext context = evaluationContextProvider.getEvaluationContext(parameters, accessor.getValues());
178
179 return expression.getValue(context, Object.class);
180 }
181 }
182
183
190 private static class BasicQueryParameterSetterFactory extends QueryParameterSetterFactory {
191
192 private final JpaParameters parameters;
193
194
197 BasicQueryParameterSetterFactory(JpaParameters parameters) {
198
199 Assert.notNull(parameters, "JpaParameters must not be null!");
200
201 this.parameters = parameters;
202 }
203
204
208 @Override
209 public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery declaredQuery) {
210
211 Assert.notNull(binding, "Binding must not be null.");
212
213 JpaParameter parameter;
214
215 if (declaredQuery.hasNamedParameter()) {
216 parameter = findParameterForBinding(binding);
217 } else {
218
219 int parameterIndex = binding.getRequiredPosition() - 1;
220 JpaParameters bindableParameters = parameters.getBindableParameters();
221
222 Assert.isTrue(
223 parameterIndex < bindableParameters.getNumberOfParameters(),
224 () -> String.format(
225 "At least %s parameter(s) provided but only %s parameter(s) present in query.",
226 binding.getRequiredPosition(),
227 bindableParameters.getNumberOfParameters()
228 )
229 );
230
231 parameter = bindableParameters.getParameter(binding.getRequiredPosition() - 1);
232 }
233
234 return parameter == null
235 ? QueryParameterSetter.NOOP
236 : createSetter(values -> getValue(values, parameter), binding, parameter);
237 }
238
239 @Nullable
240 private JpaParameter findParameterForBinding(ParameterBinding binding) {
241
242 JpaParameters bindableParameters = parameters.getBindableParameters();
243
244 for (JpaParameter bindableParameter : bindableParameters) {
245 if (binding.getRequiredName().equals(getName(bindableParameter))) {
246 return bindableParameter;
247 }
248 }
249
250 return null;
251 }
252
253 private Object getValue(JpaParametersParameterAccessor accessor, Parameter parameter) {
254 return accessor.getValue(parameter);
255 }
256
257 private static String getName(JpaParameter p) {
258 return p.getName().orElseThrow(() -> new IllegalStateException(ParameterBinder.PARAMETER_NEEDS_TO_BE_NAMED));
259 }
260 }
261
262
267 private static class CriteriaQueryParameterSetterFactory extends QueryParameterSetterFactory {
268
269 private final JpaParameters parameters;
270 private final List<ParameterMetadata<?>> expressions;
271
272
279 CriteriaQueryParameterSetterFactory(JpaParameters parameters, List<ParameterMetadata<?>> metadata) {
280
281 Assert.notNull(parameters, "JpaParameters must not be null!");
282 Assert.notNull(metadata, "Expressions must not be null!");
283
284 this.parameters = parameters;
285 this.expressions = metadata;
286 }
287
288
292 @Override
293 public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery declaredQuery) {
294
295 int parameterIndex = binding.getRequiredPosition() - 1;
296
297 Assert.isTrue(
298 parameterIndex < expressions.size(),
299 () -> String.format(
300 "At least %s parameter(s) provided but only %s parameter(s) present in query.",
301 binding.getRequiredPosition(),
302 expressions.size()
303 )
304 );
305
306 ParameterMetadata<?> metadata = expressions.get(parameterIndex);
307
308 if (metadata.isIsNullParameter()) {
309 return QueryParameterSetter.NOOP;
310 }
311
312 JpaParameter parameter = parameters.getBindableParameter(parameterIndex);
313 TemporalType temporalType = parameter.isTemporalParameter() ? parameter.getRequiredTemporalType() : null;
314
315 return new NamedOrIndexedQueryParameterSetter(values -> {
316 return getAndPrepare(parameter, metadata, values);
317 }, metadata.getExpression(), temporalType);
318 }
319
320 @Nullable
321 private Object getAndPrepare(JpaParameter parameter, ParameterMetadata<?> metadata,
322 JpaParametersParameterAccessor accessor) {
323 return metadata.prepare(accessor.getValue(parameter));
324 }
325 }
326
327 private static class ParameterImpl<T> implements javax.persistence.Parameter<T> {
328
329 private final Class<T> parameterType;
330 private final @Nullable String name;
331 private final @Nullable Integer position;
332
333
340 static javax.persistence.Parameter<?> of(@Nullable JpaParameter parameter, ParameterBinding binding) {
341
342 Class<?> type = parameter == null ? Object.class : parameter.getType();
343
344 return new ParameterImpl<>(type, getName(parameter, binding), binding.getPosition());
345 }
346
347
354 private ParameterImpl(Class<T> parameterType, @Nullable String name, @Nullable Integer position) {
355
356 this.name = name;
357 this.position = position;
358 this.parameterType = parameterType;
359 }
360
361
365 @Nullable
366 @Override
367 public String getName() {
368 return name;
369 }
370
371
375 @Nullable
376 @Override
377 public Integer getPosition() {
378 return position;
379 }
380
381
385 @Override
386 public Class<T> getParameterType() {
387 return parameterType;
388 }
389
390 @Nullable
391 private static String getName(@Nullable JpaParameter parameter, ParameterBinding binding) {
392
393 if (parameter == null) {
394 return binding.getName();
395 }
396
397 return parameter.isNamedParameter()
398 ? parameter.getName().orElseThrow(() -> new IllegalArgumentException("o_O parameter needs to have a name!"))
399 : null;
400 }
401 }
402 }
403