1
16 package org.springframework.data.jpa.repository.query;
17
18 import static org.springframework.data.jpa.repository.query.QueryParameterSetter.ErrorHandling.*;
19
20 import java.lang.reflect.Proxy;
21 import java.util.Collections;
22 import java.util.Date;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.function.Function;
27
28 import javax.persistence.Parameter;
29 import javax.persistence.Query;
30 import javax.persistence.TemporalType;
31 import javax.persistence.criteria.ParameterExpression;
32
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 import org.springframework.lang.Nullable;
37 import org.springframework.util.Assert;
38
39
47 interface QueryParameterSetter {
48
49 void setParameter(BindableQuery query, JpaParametersParameterAccessor accessor, ErrorHandling errorHandling);
50
51
52 QueryParameterSetter NOOP = (query, values, errorHandling) -> {};
53
54
57 class NamedOrIndexedQueryParameterSetter implements QueryParameterSetter {
58
59 private final Function<JpaParametersParameterAccessor, Object> valueExtractor;
60 private final Parameter<?> parameter;
61 private final @Nullable TemporalType temporalType;
62
63
68 NamedOrIndexedQueryParameterSetter(Function<JpaParametersParameterAccessor, Object> valueExtractor,
69 Parameter<?> parameter, @Nullable TemporalType temporalType) {
70
71 Assert.notNull(valueExtractor, "ValueExtractor must not be null!");
72
73 this.valueExtractor = valueExtractor;
74 this.parameter = parameter;
75 this.temporalType = temporalType;
76 }
77
78
82 @SuppressWarnings("unchecked")
83 @Override
84 public void setParameter(BindableQuery query, JpaParametersParameterAccessor accessor,
85 ErrorHandling errorHandling) {
86
87 Object value = valueExtractor.apply(accessor);
88
89 if (temporalType != null) {
90
91
92
93
94
95
96 if (parameter instanceof ParameterExpression) {
97 errorHandling.execute(() -> query.setParameter((Parameter<Date>) parameter, (Date) value, temporalType));
98 } else if (query.hasNamedParameters() && parameter.getName() != null) {
99 errorHandling.execute(() -> query.setParameter(parameter.getName(), (Date) value, temporalType));
100 } else {
101
102 Integer position = parameter.getPosition();
103
104 if (position != null
105 && (query.getParameters().size() >= parameter.getPosition()
106 || query.registerExcessParameters()
107 || errorHandling == LENIENT)) {
108
109 errorHandling.execute(() -> query.setParameter(parameter.getPosition(), (Date) value, temporalType));
110 }
111 }
112
113 } else {
114
115 if (parameter instanceof ParameterExpression) {
116 errorHandling.execute(() -> query.setParameter((Parameter<Object>) parameter, value));
117 } else if (query.hasNamedParameters() && parameter.getName() != null) {
118 errorHandling.execute(() -> query.setParameter(parameter.getName(), value));
119
120 } else {
121
122 Integer position = parameter.getPosition();
123
124 if (position != null
125 && (query.getParameters().size() >= position
126 || errorHandling == LENIENT
127 || query.registerExcessParameters())) {
128 errorHandling.execute(() -> query.setParameter(position, value));
129 }
130 }
131 }
132 }
133 }
134
135 enum ErrorHandling {
136
137 STRICT {
138
139 @Override
140 public void execute(Runnable block) {
141 block.run();
142 }
143 },
144
145 LENIENT {
146
147 @Override
148 public void execute(Runnable block) {
149
150 try {
151 block.run();
152 } catch (RuntimeException rex) {
153 LOG.info("Silently ignoring", rex);
154 }
155 }
156 };
157
158 private static final Logger LOG = LoggerFactory.getLogger(ErrorHandling.class);
159
160 abstract void execute(Runnable block);
161 }
162
163
166 class QueryMetadataCache {
167
168 private Map<String, QueryMetadata> cache = Collections.emptyMap();
169
170
177 public QueryMetadata getMetadata(String cacheKey, Query query) {
178
179 QueryMetadata queryMetadata = cache.get(cacheKey);
180
181 if (queryMetadata == null) {
182
183 queryMetadata = new QueryMetadata(query);
184
185 Map<String, QueryMetadata> cache;
186
187 if (this.cache.isEmpty()) {
188 cache = Collections.singletonMap(cacheKey, queryMetadata);
189 } else {
190 cache = new HashMap<>(this.cache);
191 cache.put(cacheKey, queryMetadata);
192 }
193
194 synchronized (this) {
195 this.cache = cache;
196 }
197 }
198
199 return queryMetadata;
200 }
201 }
202
203
206 class QueryMetadata {
207
208 private final boolean namedParameters;
209 private final Set<Parameter<?>> parameters;
210 private final boolean registerExcessParameters;
211
212 QueryMetadata(Query query) {
213
214 this.namedParameters = QueryUtils.hasNamedParameter(query);
215 this.parameters = query.getParameters();
216
217
218
219
220
221
222
223
224 this.registerExcessParameters = query.getParameters().size() == 0
225 && unwrapClass(query).getName().startsWith("org.eclipse");
226 }
227
228 QueryMetadata(QueryMetadata metadata) {
229
230 this.namedParameters = metadata.namedParameters;
231 this.parameters = metadata.parameters;
232 this.registerExcessParameters = metadata.registerExcessParameters;
233 }
234
235
241 public BindableQuery withQuery(Query query) {
242 return new BindableQuery(this, query);
243 }
244
245
248 public Set<Parameter<?>> getParameters() {
249 return parameters;
250 }
251
252
255 public boolean hasNamedParameters() {
256 return this.namedParameters;
257 }
258
259 public boolean registerExcessParameters() {
260 return this.registerExcessParameters;
261 }
262
263
271 private static Class<?> unwrapClass(Query query) {
272
273 Class<? extends Query> queryType = query.getClass();
274
275 try {
276
277 return Proxy.isProxyClass(queryType)
278 ? query.unwrap(null).getClass()
279 : queryType;
280
281 } catch (RuntimeException e) {
282
283 LoggerFactory.getLogger(QueryMetadata.class).warn("Failed to unwrap actual class for Query proxy.", e);
284
285 return queryType;
286 }
287 }
288 }
289
290
293 class BindableQuery extends QueryMetadata {
294
295 private final Query query;
296 private final Query unwrapped;
297
298 BindableQuery(QueryMetadata metadata, Query query) {
299 super(metadata);
300 this.query = query;
301 this.unwrapped = Proxy.isProxyClass(query.getClass()) ? query.unwrap(null) : query;
302 }
303
304 private BindableQuery(Query query) {
305 super(query);
306 this.query = query;
307 this.unwrapped = Proxy.isProxyClass(query.getClass()) ? query.unwrap(null) : query;
308 }
309
310 public static BindableQuery from(Query query) {
311 return new BindableQuery(query);
312 }
313
314 public Query getQuery() {
315 return query;
316 }
317
318 public <T> Query setParameter(Parameter<T> param, T value) {
319 return unwrapped.setParameter(param, value);
320 }
321
322 public Query setParameter(Parameter<Date> param, Date value, TemporalType temporalType) {
323 return unwrapped.setParameter(param, value, temporalType);
324 }
325
326 public Query setParameter(String name, Object value) {
327 return unwrapped.setParameter(name, value);
328 }
329
330 public Query setParameter(String name, Date value, TemporalType temporalType) {
331 return query.setParameter(name, value, temporalType);
332 }
333
334 public Query setParameter(int position, Object value) {
335 return unwrapped.setParameter(position, value);
336 }
337
338 public Query setParameter(int position, Date value, TemporalType temporalType) {
339 return unwrapped.setParameter(position, value, temporalType);
340 }
341 }
342 }
343