View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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    * http://www.opensource.org/licenses/ecl2.php
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.kuali.rice.krad.data.jpa;
17  
18  import java.sql.Timestamp;
19  import java.util.Arrays;
20  import java.util.Collection;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Set;
24  
25  import org.joda.time.DateTime;
26  import org.kuali.rice.core.api.criteria.AndPredicate;
27  import org.kuali.rice.core.api.criteria.CompositePredicate;
28  import org.kuali.rice.core.api.criteria.CriteriaValue;
29  import org.kuali.rice.core.api.criteria.EqualIgnoreCasePredicate;
30  import org.kuali.rice.core.api.criteria.EqualPredicate;
31  import org.kuali.rice.core.api.criteria.ExistsSubQueryPredicate;
32  import org.kuali.rice.core.api.criteria.GreaterThanOrEqualPredicate;
33  import org.kuali.rice.core.api.criteria.GreaterThanPredicate;
34  import org.kuali.rice.core.api.criteria.InIgnoreCasePredicate;
35  import org.kuali.rice.core.api.criteria.InPredicate;
36  import org.kuali.rice.core.api.criteria.LessThanOrEqualPredicate;
37  import org.kuali.rice.core.api.criteria.LessThanPredicate;
38  import org.kuali.rice.core.api.criteria.LikeIgnoreCasePredicate;
39  import org.kuali.rice.core.api.criteria.LikePredicate;
40  import org.kuali.rice.core.api.criteria.MultiValuedPredicate;
41  import org.kuali.rice.core.api.criteria.NotEqualIgnoreCasePredicate;
42  import org.kuali.rice.core.api.criteria.NotEqualPredicate;
43  import org.kuali.rice.core.api.criteria.NotInIgnoreCasePredicate;
44  import org.kuali.rice.core.api.criteria.NotInPredicate;
45  import org.kuali.rice.core.api.criteria.NotLikeIgnoreCasePredicate;
46  import org.kuali.rice.core.api.criteria.NotLikePredicate;
47  import org.kuali.rice.core.api.criteria.NotNullPredicate;
48  import org.kuali.rice.core.api.criteria.NullPredicate;
49  import org.kuali.rice.core.api.criteria.OrPredicate;
50  import org.kuali.rice.core.api.criteria.OrderByField;
51  import org.kuali.rice.core.api.criteria.OrderDirection;
52  import org.kuali.rice.core.api.criteria.Predicate;
53  import org.kuali.rice.core.api.criteria.PredicateFactory;
54  import org.kuali.rice.core.api.criteria.PropertyPathPredicate;
55  import org.kuali.rice.core.api.criteria.QueryByCriteria;
56  import org.kuali.rice.core.api.criteria.SingleValuedPredicate;
57  import org.kuali.rice.core.api.criteria.SubQueryPredicate;
58  import org.kuali.rice.krad.data.jpa.NativeJpaQueryTranslator.TranslationContext;
59  
60  /**
61   * Base {@link QueryTranslator} implementation.
62   *
63   * @author Kuali Rice Team (rice.collab@kuali.org)
64   */
65  @SuppressWarnings("rawtypes")
66  abstract class QueryTranslatorBase<C, Q> implements QueryTranslator<C, Q> {
67  
68      public static final int MULTI_VALUE_CHUNK_SIZE = 1000;
69  
70      /**
71       * Creates a criteria from the given type.
72       *
73       * @param entityClass the type to create the criteria from.
74       * @return a criteria created from the given type.
75       */
76  	protected abstract C createCriteria(Class entityClass);
77  
78  	/**
79  	 * Creates a new criteria parsing context from the given type for an inner subquery. The parent context is stored to
80  	 * allow references between the inner and outer queries.
81  	 *
82  	 * @param queryClazz
83  	 *            the type of the query.
84  	 * @param parentContext
85  	 *            The {@link TranslationContext} of the outer query into which the subquery will be added as a
86  	 *            {@link Predicate}.
87  	 */
88  	protected abstract C createCriteriaForSubQuery(Class queryClazz, C parentContext);
89  
90      /**
91       * Creates a critera from the given parent critiera.
92       *
93       * @param parent the parent critera to create a criteria from.
94       * @return a critera created from a given parent criteria.
95       */
96      protected abstract C createInnerCriteria(C parent);
97  
98      /**
99       * Generates the uppercase function form of the property.
100      *
101      * @param pp the property to modify.
102      * @return the uppercase function form of the property.
103      */
104     protected abstract String genUpperFunc(String pp);
105 
106     /**
107      * Adds a NOT NULL clause to the property.
108      *
109      * @param criteria the criteria to add to.
110      * @param propertyPath the property to add to.
111      */
112     protected abstract void addNotNull(C criteria, String propertyPath);
113 
114     /**
115      * Adds an IS NULL clause to the property.
116      *
117      * @param criteria the criteria to add to.
118      * @param propertyPath the property to add to.
119      */
120     protected abstract void addIsNull(C criteria, String propertyPath);
121 
122     /**
123      * Adds a "=" clause to the property.
124      *
125      * @param criteria the criteria to add to.
126      * @param propertyPath the property to add to.
127      * @param value the value to compare.
128      */
129     protected abstract void addEqualTo(C criteria, String propertyPath, Object value);
130 
131     /**
132      * Adds a ">=" clause to the property.
133      *
134      * @param criteria the criteria to add to.
135      * @param propertyPath the property to add to.
136      * @param value the value to compare.
137      */
138     protected abstract void addGreaterOrEqualTo(C criteria, String propertyPath, Object value);
139 
140     /**
141      * Adds a ">" clause to the property.
142      *
143      * @param criteria the criteria to add to.
144      * @param propertyPath the property to add to.
145      * @param value the value to compare.
146      */
147     protected abstract void addGreaterThan(C criteria, String propertyPath, Object value);
148 
149     /**
150      * Adds a "<=" clause to the property.
151      *
152      * @param criteria the criteria to add to.
153      * @param propertyPath the property to add to.
154      * @param value the value to compare.
155      */
156     protected abstract void addLessOrEqualTo(C criteria, String propertyPath, Object value);
157 
158     /**
159      * Adds a "<" clause to the property.
160      *
161      * @param criteria the criteria to add to.
162      * @param propertyPath the property to add to.
163      * @param value the value to compare.
164      */
165     protected abstract void addLessThan(C criteria, String propertyPath, Object value);
166 
167     /**
168      * Adds a LIKE clause to the property.
169      *
170      * @param criteria the criteria to add to.
171      * @param propertyPath the property to add to.
172      * @param value the value to compare.
173      */
174     protected abstract void addLike(C criteria, String propertyPath, Object value);
175 
176     /**
177      * Adds a != clause to the property.
178      *
179      * @param criteria the criteria to add to.
180      * @param propertyPath the property to add to.
181      * @param value the value to compare.
182      */
183     protected abstract void addNotEqualTo(C criteria, String propertyPath, Object value);
184 
185     /**
186      * Adds a NOT LIKE clause to the property.
187      *
188      * @param criteria the criteria to add to.
189      * @param propertyPath the property to add to.
190      * @param value the value to compare.
191      */
192     protected abstract void addNotLike(C criteria, String propertyPath, Object value);
193 
194     /**
195 	 * Adds a NOT LIKE clause to the property, ignoring case.
196 	 * 
197 	 * @param criteria
198 	 *            the criteria to add to.
199 	 * @param propertyPath
200 	 *            the property to add to.
201 	 * @param value
202 	 *            the value to compare.
203 	 */
204 	protected abstract void addNotLikeIgnoreCase(C criteria, String propertyPath, String value);
205 
206 	/**
207 	 * Adds an IN clause to the property.
208 	 * 
209 	 * @param criteria
210 	 *            the criteria to add to.
211 	 * @param propertyPath
212 	 *            the property to add to.
213 	 * @param values
214 	 *            the values to compare.
215 	 */
216     protected abstract void addIn(C criteria, String propertyPath, Collection values);
217 
218     /**
219      * Adds a NOT IN clause to the property.
220      *
221      * @param criteria the criteria to add to.
222      * @param propertyPath the property to add to.
223      * @param values the values to compare.
224      */
225     protected abstract void addNotIn(C criteria, String propertyPath, Collection values);
226 
227     /**
228      * Adds an AND clause between criteria.
229      *
230      * @param criteria the criteria to add to.
231      * @param inner the criteria to AND.
232      */
233     protected abstract void addAnd(C criteria, C inner);
234 
235     /**
236      * Adds an OR clause between criteria.
237      *
238      * @param criteria the criteria to add to.
239      * @param inner the criteria to OR.
240      */
241     protected abstract void addOr(C criteria, C inner);
242 
243 	/**
244 	 * Adds an EXISTS clause to the criteria.
245 	 *
246 	 * @param criteria
247 	 *            the criteria to add to.
248 	 * @param subQueryType
249 	 *            The data object type of the inner subquery
250 	 * @param subQueryPredicate
251 	 *            Additional predicates to apply to the inner query - may be null.
252 	 */
253 	protected abstract void addExistsSubquery(C criteria, String subQueryType, Predicate subQueryPredicate);
254 
255     /**
256      * Adds an order by clause to the given criteria
257      *
258      * @param criteria the criteria to add the order by clause to
259      * @param propertyPath The attribute name to order by
260      * @param sortAscending Boolean that determines whether to sort by ascending order
261      */
262     protected abstract void addOrderBy(C criteria, String propertyPath, boolean sortAscending);
263 
264 
265     /**
266      * Adds a "=" clause to the property, ignoring case.
267      *
268      * @param criteria the criteria to add to.
269      * @param propertyPath the property to add to.
270      * @param value the value to compare.
271      */
272     protected void addEqualToIgnoreCase(C criteria, String propertyPath, String value) {
273         addEqualTo(criteria, genUpperFunc(propertyPath), value.toUpperCase());
274     }
275 
276     /**
277      * Adds a != clause to the property, ignoring case.
278      *
279      * @param criteria the criteria to add to.
280      * @param propertyPath the property to add to.
281      * @param value the value to compare.
282      */
283     protected void addNotEqualToIgnoreCase(C criteria, String propertyPath, String value) {
284         addNotEqualTo(criteria, genUpperFunc(propertyPath), value.toUpperCase());
285     }
286 
287     /**
288      * Adds a LIKE clause to the property, ignoring case.
289      *
290      * @param criteria the criteria to add to.
291      * @param propertyPath the property to add to.
292      * @param value the value to compare.
293      */
294     protected void addLikeIgnoreCase(C criteria, String propertyPath, String value){
295         addLike(criteria, genUpperFunc(propertyPath),value.toUpperCase());
296     }
297 
298     /**
299      * An error to throw when the {@link Predicate} is not recognized.
300      *
301      * <p>This is a fatal error since this implementation should support all known predicates.</p>
302      */
303     protected static class UnsupportedPredicateException extends RuntimeException {
304 		private static final long serialVersionUID = 1L;
305 
306 		/**
307 		 * Creates an exception for if the {@link Predicate} is not recognized.
308 		 *
309 		 * @param predicate
310 		 *            the {@link Predicate} in error.
311 		 */
312         protected UnsupportedPredicateException(Predicate predicate) {
313             super("Unsupported predicate [" + String.valueOf(predicate) + "]");
314         }
315     }
316 
317     /**
318      * {@inheritDoc}
319      */
320     @Override
321     public C translateCriteria(Class queryClazz, QueryByCriteria qbc) {
322         final C parent = createCriteria(queryClazz);
323 
324         if (qbc.getPredicate() != null) {
325             addPredicate(qbc.getPredicate(), parent);
326         }
327 
328         if (!qbc.getOrderByFields().isEmpty()) {
329             addOrderBy(qbc.getOrderByFields(), parent);
330         }
331 
332         return parent;
333     }
334 
335     /**
336      * Adds one or more order by clauses to the criteria
337      *
338      * @param orderByFields
339      * @param parent the pareent criteria to add to
340      */
341     protected void addOrderBy(List<OrderByField> orderByFields, C parent) {
342         for (OrderByField field : orderByFields) {
343             addOrderBy(parent, field.getFieldName(), OrderDirection.ASCENDING.equals(field.getOrderDirection()));
344         }
345     }
346 
347     /**
348      * Adds a predicate to a criteria.
349      *
350      * @param p the {@link Predicate} to add.
351      * @param parent the parent criteria to add to.
352      */
353     protected void addPredicate(Predicate p, C parent) {
354         if (p instanceof PropertyPathPredicate) {
355             final String pp = ((PropertyPathPredicate) p).getPropertyPath();
356             if (p instanceof NotNullPredicate) {
357                 addNotNull(parent, pp);
358             } else if (p instanceof NullPredicate) {
359                 addIsNull(parent, pp);
360             } else if (p instanceof SingleValuedPredicate) {
361                 addSingleValuePredicate((SingleValuedPredicate) p, parent);
362             } else if (p instanceof MultiValuedPredicate) {
363                 addMultiValuePredicate((MultiValuedPredicate) p, parent);
364             } else {
365                 throw new UnsupportedPredicateException(p);
366             }
367         } else if (p instanceof CompositePredicate) {
368             addCompositePredicate((CompositePredicate) p, parent);
369 		} else if (p instanceof SubQueryPredicate) {
370 			addSubQueryPredicate((SubQueryPredicate) p, parent);
371         } else {
372             throw new UnsupportedPredicateException(p);
373         }
374     }
375 
376     /**
377      * Adds a single valued predicate to a criteria.
378      *
379      * @param p the single valued predicate to add.
380      * @param parent the parent criteria to add to.
381      */
382     protected void addSingleValuePredicate(SingleValuedPredicate p, C parent) {
383 		final Object value = getVal(p.getValue());
384         final String pp = p.getPropertyPath();
385         if (p instanceof EqualPredicate) {
386             addEqualTo(parent, pp, value);
387         } else if (p instanceof EqualIgnoreCasePredicate) {
388             addEqualToIgnoreCase(parent, pp, (String) value);
389         } else if (p instanceof GreaterThanOrEqualPredicate) {
390             addGreaterOrEqualTo(parent, pp, value);
391         } else if (p instanceof GreaterThanPredicate) {
392             addGreaterThan(parent, pp, value);
393         } else if (p instanceof LessThanOrEqualPredicate) {
394             addLessOrEqualTo(parent, pp, value);
395         } else if (p instanceof LessThanPredicate) {
396             addLessThan(parent, pp, value);
397         } else if (p instanceof LikePredicate) {
398             //no need to convert * or ? since ojb handles the conversion/escaping
399             addLike(parent, pp, value);
400         } else if(p instanceof LikeIgnoreCasePredicate){
401             addLikeIgnoreCase(parent,pp,(String)value);
402         } else if (p instanceof NotEqualPredicate) {
403             addNotEqualTo(parent, pp, value);
404         } else if (p instanceof NotEqualIgnoreCasePredicate) {
405             addNotEqualToIgnoreCase(parent, pp, (String) value);
406 		} else if (p instanceof NotLikeIgnoreCasePredicate) {
407 			addNotLikeIgnoreCase(parent, pp, (String) value);
408         } else if (p instanceof NotLikePredicate) {
409             addNotLike(parent, pp, value);
410         } else {
411             throw new UnsupportedPredicateException(p);
412         }
413     }
414 
415     /**
416      * Adds a multi-valued predicate to a criteria.
417      *
418      * @param p the multi-valued predicate to add.
419      * @param parent the parent criteria to add to.
420      */
421     protected void addMultiValuePredicate(MultiValuedPredicate p, C parent) {
422         if(p.getValues().size() > MULTI_VALUE_CHUNK_SIZE) {
423             // This predicate is too large and needs to be split into multiple smaller predicates
424             splitMultiValuePredicate(p, parent);
425         } else {
426             final String pp = p.getPropertyPath();
427             if (p instanceof InPredicate) {
428                 final Set<?> predicateValues = getVals(p.getValues());
429                 addIn(parent, pp, predicateValues);
430             } else if (p instanceof InIgnoreCasePredicate) {
431                 final Set<String> predicateValues = toUpper(getValsUnsafe(((InIgnoreCasePredicate) p).getValues()));
432                 addIn(parent, genUpperFunc(pp), predicateValues);
433             } else if (p instanceof NotInPredicate) {
434                 final Set<?> predicateValues = getVals(p.getValues());
435                 addNotIn(parent, pp, predicateValues);
436             } else if (p instanceof NotInIgnoreCasePredicate) {
437                 final Set<String> predicateValues = toUpper(getValsUnsafe(((NotInIgnoreCasePredicate) p).getValues()));
438                 addNotIn(parent, genUpperFunc(pp), predicateValues);
439             } else {
440                 throw new UnsupportedPredicateException(p);
441             }
442         }
443     }
444 
445     /**
446      * This method takes in a multi-value predicate which has more values than
447      * can fit into a single SQL clause and splits them up into multiple
448      * clauses which are concatenated by an OR statement.
449      * @param p The predicate which needs to be split into smaller predicates
450      * @param parent The criteria to add the predicate to
451      */
452     private void splitMultiValuePredicate(MultiValuedPredicate p, C parent) {
453         final String pp = p.getPropertyPath();
454         int chunkCount = (int)Math.ceil(p.getValues().size() / (double)MULTI_VALUE_CHUNK_SIZE);
455         Predicate[] multiValuePredicateChunks = new Predicate[chunkCount];
456         Object[] values = p.getValues().toArray();
457         int start = 0;
458         for(int i = 0; i < chunkCount; i++) {
459             Object[] valueChunk = Arrays.copyOfRange(values, start, start + MULTI_VALUE_CHUNK_SIZE);
460             if (p instanceof InPredicate) {
461                 multiValuePredicateChunks[i] = PredicateFactory.in(pp, valueChunk);
462             } else if (p instanceof InIgnoreCasePredicate) {
463                 multiValuePredicateChunks[i] = PredicateFactory.inIgnoreCase(pp, (CharSequence[])valueChunk);
464             } else if (p instanceof NotInPredicate) {
465                 multiValuePredicateChunks[i] = PredicateFactory.notIn(pp, valueChunk);
466             } else if (p instanceof NotInIgnoreCasePredicate) {
467                 multiValuePredicateChunks[i] = PredicateFactory.notInIgnoreCase(pp, (CharSequence[])valueChunk);
468             } else {
469                 throw new UnsupportedPredicateException(p);
470             }
471 
472             start += MULTI_VALUE_CHUNK_SIZE;
473         }
474         addPredicate(PredicateFactory.or(multiValuePredicateChunks), parent);
475     }
476 
477     /**
478      * Adds a composite predicate to a criteria.
479      *
480      * @param p the composite predicate to add.
481      * @param parent the parent criteria to add to.
482      */
483     protected void addCompositePredicate(final CompositePredicate p, final C parent) {
484         for (Predicate ip : p.getPredicates()) {
485             final C inner = createInnerCriteria(parent);
486             addPredicate(ip, inner);
487             if (p instanceof AndPredicate) {
488                 addAnd(parent, inner);
489             } else if (p instanceof OrPredicate) {
490                 addOr(parent, inner);
491             } else {
492                 throw new UnsupportedPredicateException(p);
493             }
494         }
495     }
496 
497     /**
498 	 * Adds a predicate representing a sub-query to the criteria.
499 	 *
500 	 * @param p the subquery predicate to add.
501 	 * @param parent the parent criteria to add to.
502 	 */
503 	protected void addSubQueryPredicate(SubQueryPredicate p, C parent) {
504 		if (p instanceof ExistsSubQueryPredicate) {
505 			addExistsSubquery(parent, p.getSubQueryType(), p.getSubQueryPredicate());
506 		} else {
507 			throw new UnsupportedPredicateException(p);
508 		}
509 	}
510 
511 	/**
512 	 * Converts any {@link DateTime} values to {@link Timestamp}s.
513 	 *
514 	 * @param toConv the {@link CriteriaValue} to convert.
515 	 * @param <U> the type of the {@link CriteriaValue}.
516 	 * @return the {@link CriteriaValue} converted.
517 	 */
518     protected static <U extends CriteriaValue<?>> Object getVal(U toConv) {
519         Object o = toConv.getValue();
520         if (o instanceof DateTime) {
521             return new Timestamp(((DateTime) o).getMillis());
522         }
523         return o;
524     }
525 
526     /**
527      * Converts a set of {@link CriteriaValue}s.
528      *
529      * <p>This is unsafe because values could be converted resulting in a class cast exception.</p>
530      *
531      * @param toConv the {@link CriteriaValue} to convert.
532      * @param <U> the initial type of the {@link CriteriaValue} set.
533      * @param <T> the final type of the {@link CriteriaValue} set.
534      * @return the {@link CriteriaValue} set converted.
535      */
536     @SuppressWarnings("unchecked")
537     protected static <T, U extends CriteriaValue<T>> Set<T> getValsUnsafe(Set<? extends U> toConv) {
538         return (Set<T>) getVals(toConv);
539     }
540 
541     /**
542      * Converts a set of {@link CriteriaValue}s to an undefined type.
543      *
544      * @param toConv the {@link CriteriaValue} to convert.
545      * @return a set of {@link CriteriaValue}s as an undefined type.
546      */
547     protected static Set<?> getVals(Set<? extends CriteriaValue<?>> toConv) {
548         final Set<Object> values = new HashSet<Object>();
549         for (CriteriaValue<?> value : toConv) {
550             values.add(getVal(value));
551         }
552         return values;
553     }
554 
555     /**
556      * Converts a set of strings to upper case.
557      *
558      * <p>Here we are eliding performance for function composition.</p>
559      *
560      * @param strs the set of strings to convert.
561      * @return a set of uppercase strings.
562      */
563     private static Set<String> toUpper(Set<String> strs) {
564         final Set<String> values = new HashSet<String>();
565         for (String value : strs) {
566             values.add(value.toUpperCase());
567         }
568         return values;
569     }
570 }