1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.krad.data.jpa;
17
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.List;
21
22 import javax.persistence.EntityManager;
23 import javax.persistence.Query;
24 import javax.persistence.TypedQuery;
25 import javax.persistence.criteria.CriteriaBuilder;
26 import javax.persistence.criteria.CriteriaDelete;
27 import javax.persistence.criteria.CriteriaQuery;
28 import javax.persistence.criteria.Order;
29 import javax.persistence.criteria.Path;
30 import javax.persistence.criteria.Predicate;
31 import javax.persistence.criteria.Root;
32 import javax.persistence.criteria.Subquery;
33
34 import org.apache.commons.lang.StringUtils;
35 import org.apache.log4j.Logger;
36 import org.kuali.rice.core.api.criteria.PropertyPath;
37 import org.kuali.rice.core.api.criteria.QueryByCriteria;
38
39
40
41
42
43
44 class NativeJpaQueryTranslator extends QueryTranslatorBase<NativeJpaQueryTranslator.TranslationContext, TypedQuery> {
45
46
47
48
49 protected static final String[] LOOKUP_WILDCARDS = { "*", "?" };
50
51
52
53
54 protected static final String[] ESCAPED_LOOKUP_WILDCARDS = { "\\*", "\\?" };
55
56
57
58
59 protected static final char[] JPQL_WILDCARDS = { '%', '_' };
60
61
62
63
64 protected EntityManager entityManager;
65
66
67
68
69 public static class TranslationContext {
70
71
72
73
74 CriteriaBuilder builder;
75
76
77
78
79 CriteriaQuery query;
80
81
82
83
84 Root root;
85
86
87
88
89 List<Predicate> predicates = new ArrayList<Predicate>();
90
91
92
93
94 TranslationContext parentTranslationContext;
95
96
97
98
99
100
101
102 TranslationContext(EntityManager entityManager, Class queryClass) {
103 builder = entityManager.getCriteriaBuilder();
104 query = builder.createQuery(queryClass);
105
106
107 root = query.from(query.getResultType());
108 }
109
110
111
112
113
114
115
116
117
118
119 TranslationContext( EntityManager entityManager, Class queryClass, TranslationContext parentContext ) {
120 this(entityManager, queryClass);
121 this.parentTranslationContext = parentContext;
122 }
123
124
125
126
127
128
129 TranslationContext(TranslationContext parent) {
130 builder = parent.builder;
131 query = parent.query;
132 root = parent.root;
133 parentTranslationContext = parent.parentTranslationContext;
134 }
135
136
137
138
139
140
141 void addPredicate(Predicate predicate) {
142 predicates.add(predicate);
143 }
144
145
146
147
148
149
150 void and(TranslationContext predicate) {
151 addPredicate(predicate.getCriteriaPredicate());
152 }
153
154
155
156
157
158
159 void addExistsSubquery(Subquery<?> subquery) {
160 predicates.add(builder.exists(subquery));
161 }
162
163
164
165
166
167
168 void or(TranslationContext predicate) {
169 List<Predicate> newpredicates = new ArrayList<Predicate>();
170
171
172 Predicate criteriaPredicate = getCriteriaPredicate();
173 Predicate orPredicate = null;
174 if(criteriaPredicate != null){
175 orPredicate = builder.or(new Predicate[] { predicate.getCriteriaPredicate(), getCriteriaPredicate() });
176 } else {
177 orPredicate = builder.or(predicate.getCriteriaPredicate());
178 }
179 newpredicates.add(orPredicate);
180 predicates = newpredicates;
181 }
182
183
184
185
186
187
188 Predicate getCriteriaPredicate() {
189 if (predicates.size() == 1) {
190 return predicates.get(0);
191 } else if(predicates.size() > 1){
192 return builder.and(predicates.toArray(new Predicate[predicates.size()]));
193 } else {
194 return null;
195 }
196 }
197
198
199
200
201
202
203
204 @SuppressWarnings("rawtypes")
205 Path attr(String attr) {
206 if (StringUtils.isBlank(attr)) {
207 throw new IllegalArgumentException("Encountered an empty attribute path");
208 }
209
210
211 String[] attrArray = attr.split("\\.");
212
213
214
215 if (attrArray.length > 0 && StringUtils.equals(attrArray[0], "parent") && parentTranslationContext != null) {
216 return parentTranslationContext.attr(StringUtils.substringAfter(attr, "."));
217 } else {
218 Path path = root;
219
220
221
222 for (String attrElement : attrArray) {
223 if (StringUtils.isBlank(attrElement)) {
224 throw new IllegalArgumentException("Encountered an empty path element in property path: "
225 + attr);
226 }
227 path = path.get(attrElement);
228 }
229 return path;
230 }
231 }
232 }
233
234
235
236
237
238
239 public NativeJpaQueryTranslator(EntityManager entityManager) {
240 this.entityManager = entityManager;
241 }
242
243
244
245
246 @Override
247 public TypedQuery createQuery(Class queryClazz, TranslationContext criteria) {
248 CriteriaQuery jpaQuery = criteria.query;
249
250 if (!criteria.predicates.isEmpty()) {
251 jpaQuery = jpaQuery.where(criteria.getCriteriaPredicate());
252 }
253 return entityManager.createQuery(jpaQuery);
254 }
255
256
257
258
259 @Override
260 public Query createDeletionQuery(Class queryClazz, TranslationContext criteria) {
261 CriteriaDelete jpaQuery = entityManager.getCriteriaBuilder().createCriteriaDelete(queryClazz);
262
263 if (!criteria.predicates.isEmpty()) {
264 jpaQuery = jpaQuery.where(criteria.getCriteriaPredicate());
265 }
266
267 return entityManager.createQuery(jpaQuery);
268 }
269
270
271
272
273 @Override
274 protected TranslationContext createCriteria(Class queryClazz) {
275 return new TranslationContext(entityManager, queryClazz);
276 }
277
278
279
280
281 @Override
282 protected TranslationContext createCriteriaForSubQuery(Class queryClazz, TranslationContext parentContext) {
283 return new TranslationContext(entityManager, queryClazz, parentContext);
284 }
285
286
287
288
289 @Override
290 protected TranslationContext createInnerCriteria(TranslationContext parent) {
291
292
293 return new TranslationContext(parent);
294 }
295
296
297
298
299 @Override
300 public void convertQueryFlags(QueryByCriteria qbc, TypedQuery query) {
301 final int startAtIndex = qbc.getStartAtIndex() != null ? qbc.getStartAtIndex() : 0;
302
303 query.setFirstResult(startAtIndex);
304
305 if (qbc.getMaxResults() != null) {
306
307
308 query.setMaxResults(qbc.getMaxResults());
309 }
310 }
311
312
313
314
315 @Override
316 protected void addAnd(TranslationContext criteria, TranslationContext inner) {
317 criteria.and(inner);
318 }
319
320
321
322
323 @Override
324 protected void addNotNull(TranslationContext criteria, String propertyPath) {
325 criteria.addPredicate(criteria.builder.isNotNull(criteria.attr(propertyPath)));
326 }
327
328
329
330
331 @Override
332 protected void addIsNull(TranslationContext criteria, String propertyPath) {
333 criteria.addPredicate(criteria.builder.isNull(criteria.attr(propertyPath)));
334 }
335
336
337
338
339
340
341
342
343
344
345
346 @SuppressWarnings("rawtypes")
347 protected Path translatePropertyPathIntoJpaPath(TranslationContext criteria, PropertyPath value) {
348 TranslationContext tempCriteria = criteria;
349 if (value.getDataType() != null) {
350 try {
351 tempCriteria = createCriteria(Class.forName(value.getDataType()));
352 } catch (ClassNotFoundException e) {
353
354 Logger.getLogger(this.getClass()).error(
355 "Unable to find data type " + value.getDataType()
356 + ". Falling back to the base root for the query: " + criteria.root.getJavaType());
357 }
358 }
359 return tempCriteria.attr(value.getPropertyPath());
360 }
361
362
363
364
365 @Override
366 protected void addEqualTo(TranslationContext criteria, String propertyPath, Object value) {
367
368 if (value instanceof PropertyPath) {
369
370
371
372 Path path = translatePropertyPathIntoJpaPath(criteria, (PropertyPath) value);
373 criteria.addPredicate(criteria.builder.equal(criteria.attr(propertyPath), path));
374 } else {
375 criteria.addPredicate(criteria.builder.equal(criteria.attr(propertyPath), value));
376 }
377 }
378
379
380
381
382 @Override
383 protected void addEqualToIgnoreCase(TranslationContext criteria, String propertyPath, String value) {
384 criteria.addPredicate(criteria.builder.equal(criteria.builder.upper(criteria.attr(propertyPath)), value.toUpperCase()));
385 }
386
387
388
389
390 @Override
391 protected void addGreaterOrEqualTo(TranslationContext criteria, String propertyPath, Object value) {
392 criteria.addPredicate(criteria.builder.greaterThanOrEqualTo(criteria.attr(propertyPath), (Comparable) value));
393 }
394
395
396
397
398 @Override
399 protected void addGreaterThan(TranslationContext criteria, String propertyPath, Object value) {
400 criteria.addPredicate(criteria.builder.greaterThan(criteria.attr(propertyPath), (Comparable) value));
401 }
402
403
404
405
406 @Override
407 protected void addLessOrEqualTo(TranslationContext criteria, String propertyPath, Object value) {
408 criteria.addPredicate(criteria.builder.lessThanOrEqualTo(criteria.attr(propertyPath), (Comparable) value));
409 }
410
411
412
413
414 @Override
415 protected void addLessThan(TranslationContext criteria, String propertyPath, Object value) {
416 criteria.addPredicate(criteria.builder.lessThan(criteria.attr(propertyPath), (Comparable) value));
417 }
418
419
420
421
422 @Override
423 protected void addLike(TranslationContext criteria, String propertyPath, Object value) {
424
425 criteria.addPredicate(criteria.builder.like(criteria.attr(propertyPath), fixSearchPattern(value.toString())));
426 }
427
428
429
430
431 @Override
432 protected void addLikeIgnoreCase(TranslationContext criteria, String propertyPath, String value){
433 criteria.addPredicate(criteria.builder.like(criteria.builder.upper(criteria.attr(propertyPath)),
434 fixSearchPattern(value.toUpperCase())));
435 }
436
437
438
439
440 @Override
441 protected void addNotLikeIgnoreCase(TranslationContext criteria, String propertyPath, String value) {
442 criteria.addPredicate(criteria.builder.notLike(criteria.builder.upper(criteria.attr(propertyPath)),
443 fixSearchPattern(value.toUpperCase())));
444 }
445
446
447
448
449 @Override
450 protected void addExistsSubquery(TranslationContext criteria, String subQueryType,
451 org.kuali.rice.core.api.criteria.Predicate subQueryPredicate) {
452 try {
453 Class<?> subQueryBaseClass = Class.forName(subQueryType);
454 Subquery<?> subquery = criteria.query.subquery(subQueryBaseClass);
455 TranslationContext subQueryJpaPredicate = createCriteriaForSubQuery(subQueryBaseClass, criteria);
456
457
458
459 if (subQueryPredicate != null) {
460 addPredicate(subQueryPredicate, subQueryJpaPredicate);
461 }
462
463 subquery.where(subQueryJpaPredicate.predicates.toArray(new Predicate[0]));
464 criteria.addExistsSubquery(subquery);
465 } catch (ClassNotFoundException e) {
466 throw new IllegalArgumentException(subQueryType + " can not be resolved to a class for JPA");
467 }
468 }
469
470
471
472
473
474
475
476
477
478
479 protected String fixSearchPattern(String value) {
480 StringBuilder fixedPattern = new StringBuilder(value);
481
482 for (int i = 0; i < LOOKUP_WILDCARDS.length; i++) {
483 String lookupWildcard = LOOKUP_WILDCARDS[i];
484 String escapedLookupWildcard = ESCAPED_LOOKUP_WILDCARDS[i];
485 char jpqlWildcard = JPQL_WILDCARDS[i];
486 int wildcardIndex = fixedPattern.indexOf(lookupWildcard);
487 int escapedWildcardIndex = fixedPattern.indexOf(escapedLookupWildcard);
488 while (wildcardIndex != -1) {
489 if (wildcardIndex == 0 || escapedWildcardIndex != wildcardIndex - 1) {
490 fixedPattern.setCharAt(wildcardIndex, jpqlWildcard);
491 wildcardIndex = fixedPattern.indexOf(lookupWildcard, wildcardIndex);
492 } else {
493 fixedPattern.replace(escapedWildcardIndex, wildcardIndex + 1, lookupWildcard);
494 wildcardIndex = fixedPattern.indexOf(lookupWildcard, wildcardIndex);
495 escapedWildcardIndex = fixedPattern.indexOf(escapedLookupWildcard, wildcardIndex);
496 }
497 }
498 }
499 return fixedPattern.toString();
500 }
501
502
503
504
505 @Override
506 protected void addNotEqualTo(TranslationContext criteria, String propertyPath, Object value) {
507
508 if (value instanceof PropertyPath) {
509
510
511
512 Path path = translatePropertyPathIntoJpaPath(criteria, (PropertyPath) value);
513 criteria.addPredicate(criteria.builder.notEqual(criteria.attr(propertyPath), path));
514 } else {
515 criteria.addPredicate(criteria.builder.notEqual(criteria.attr(propertyPath), value));
516 }
517 }
518
519
520
521
522 @Override
523 protected void addNotEqualToIgnoreCase(TranslationContext criteria, String propertyPath, String value) {
524 criteria.addPredicate(criteria.builder.notEqual(criteria.builder.upper(criteria.attr(propertyPath)), value.toUpperCase()));
525 }
526
527
528
529
530 @Override
531 protected void addNotLike(TranslationContext criteria, String propertyPath, Object value) {
532
533 criteria.addPredicate(criteria.builder.notLike(criteria.attr(propertyPath), fixSearchPattern(value.toString())));
534 }
535
536
537
538
539 @Override
540 protected void addIn(TranslationContext criteria, String propertyPath, Collection values) {
541 criteria.addPredicate(criteria.attr(propertyPath).in(values));
542 }
543
544
545
546
547 @Override
548 protected void addNotIn(TranslationContext criteria, String propertyPath, Collection values) {
549 criteria.addPredicate(criteria.builder.not(criteria.attr(propertyPath).in(values)));
550 }
551
552
553
554
555 @Override
556 protected void addOr(TranslationContext criteria, TranslationContext inner) {
557 criteria.or(inner);
558 }
559
560
561
562
563 @Override
564 protected void addOrderBy(TranslationContext criteria, String propertyPath, boolean sortAscending) {
565 List<Order> orderList = criteria.query.getOrderList();
566 if (orderList == null) {
567 orderList = new ArrayList<Order>();
568 }
569
570 if (propertyPath.contains(".")) {
571 String propertyPathStart = StringUtils.substringBefore( propertyPath, "." );
572 String propertyPathEnd = StringUtils.substringAfter( propertyPath, "." );
573
574 if (sortAscending) {
575 orderList.add(criteria.builder.asc(criteria.root.get(propertyPathStart).get(propertyPathEnd)));
576 } else {
577 orderList.add(criteria.builder.desc(criteria.root.get(propertyPathStart).get(propertyPathEnd)));
578 }
579 } else {
580 if (sortAscending) {
581 orderList.add(criteria.builder.asc(criteria.root.get(propertyPath)));
582 } else {
583 orderList.add(criteria.builder.desc(criteria.root.get(propertyPath)));
584 }
585 }
586
587 criteria.query.orderBy(orderList);
588 }
589
590
591
592
593
594
595 @Override
596 protected String genUpperFunc(String pp) {
597 throw new IllegalStateException("genUpperFunc should not have been invoked for NativeJpaQueryTranslator");
598 }
599
600 }