View Javadoc
1   /**
2    * Copyright 2005-2014 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.criteria;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.ojb.broker.query.Criteria;
20  import org.apache.ojb.broker.query.Query;
21  import org.apache.ojb.broker.query.QueryFactory;
22  import org.joda.time.DateTime;
23  import org.kuali.rice.core.api.criteria.AndPredicate;
24  import org.kuali.rice.core.api.criteria.CompositePredicate;
25  import org.kuali.rice.core.api.criteria.CountFlag;
26  import org.kuali.rice.core.api.criteria.CriteriaValue;
27  import org.kuali.rice.core.api.criteria.EqualIgnoreCasePredicate;
28  import org.kuali.rice.core.api.criteria.EqualPredicate;
29  import org.kuali.rice.core.api.criteria.GenericQueryResults;
30  import org.kuali.rice.core.api.criteria.GreaterThanOrEqualPredicate;
31  import org.kuali.rice.core.api.criteria.GreaterThanPredicate;
32  import org.kuali.rice.core.api.criteria.InIgnoreCasePredicate;
33  import org.kuali.rice.core.api.criteria.InPredicate;
34  import org.kuali.rice.core.api.criteria.LessThanOrEqualPredicate;
35  import org.kuali.rice.core.api.criteria.LessThanPredicate;
36  import org.kuali.rice.core.api.criteria.LikePredicate;
37  import org.kuali.rice.core.api.criteria.LookupCustomizer;
38  import org.kuali.rice.core.api.criteria.MultiValuedPredicate;
39  import org.kuali.rice.core.api.criteria.NotEqualIgnoreCasePredicate;
40  import org.kuali.rice.core.api.criteria.NotEqualPredicate;
41  import org.kuali.rice.core.api.criteria.NotInIgnoreCasePredicate;
42  import org.kuali.rice.core.api.criteria.NotInPredicate;
43  import org.kuali.rice.core.api.criteria.NotLikePredicate;
44  import org.kuali.rice.core.api.criteria.NotNullPredicate;
45  import org.kuali.rice.core.api.criteria.NullPredicate;
46  import org.kuali.rice.core.api.criteria.OrPredicate;
47  import org.kuali.rice.core.api.criteria.OrderByField;
48  import org.kuali.rice.core.api.criteria.OrderDirection;
49  import org.kuali.rice.core.api.criteria.Predicate;
50  import org.kuali.rice.core.api.criteria.PropertyPathPredicate;
51  import org.kuali.rice.core.api.criteria.QueryByCriteria;
52  import org.kuali.rice.core.api.criteria.SingleValuedPredicate;
53  import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb;
54  
55  import java.sql.Timestamp;
56  import java.util.ArrayList;
57  import java.util.HashSet;
58  import java.util.List;
59  import java.util.Set;
60  
61  public class CriteriaLookupDaoOjb extends PlatformAwareDaoBaseOjb implements CriteriaLookupDao {
62  
63      @Override
64      public <T> GenericQueryResults<T> lookup(final Class<T> queryClass, final QueryByCriteria criteria) {
65          return lookup(queryClass, criteria, LookupCustomizer.Builder.<T>create().build());
66      }
67  
68      @Override
69      public <T> GenericQueryResults<T> lookup(final Class<T> queryClass, final QueryByCriteria criteria, LookupCustomizer<T> customizer) {
70          if (queryClass == null) {
71              throw new IllegalArgumentException("queryClass is null");
72          }
73  
74          if (criteria == null) {
75              throw new IllegalArgumentException("criteria is null");
76          }
77  
78          if (customizer == null) {
79              throw new IllegalArgumentException("customizer is null");
80          }
81  
82          final Criteria parent = new Criteria();
83  
84          if (criteria.getPredicate() != null) {
85              addPredicate(criteria.getPredicate(), parent, customizer.getPredicateTransform());
86          }
87  
88          switch (criteria.getCountFlag()) {
89              case ONLY:
90                  return forCountOnly(queryClass, criteria, parent);
91              case NONE:
92                  return forRowResults(queryClass, criteria, parent, criteria.getCountFlag(), customizer.getResultTransform());
93              case INCLUDE:
94                  return forRowResults(queryClass, criteria, parent, criteria.getCountFlag(), customizer.getResultTransform());
95              default: throw new UnsupportedCountFlagException(criteria.getCountFlag());
96          }
97      }
98  
99      /** gets results where the actual rows are requested. */
100     private <T> GenericQueryResults<T> forRowResults(final Class<T> queryClass, final QueryByCriteria criteria, final Criteria ojbCriteria, CountFlag flag, LookupCustomizer.Transform<T, T> transform) {
101         final org.apache.ojb.broker.query.QueryByCriteria ojbQuery = QueryFactory.newQuery(queryClass, ojbCriteria);
102         final GenericQueryResults.Builder<T> results = GenericQueryResults.Builder.<T>create();
103 
104         if (flag == CountFlag.INCLUDE) {
105             results.setTotalRowCount(getPersistenceBrokerTemplate().getCount(ojbQuery));
106         }
107 
108         //ojb's is 1 based, our query api is zero based
109         final int startAtIndex = criteria.getStartAtIndex() != null ? criteria.getStartAtIndex() + 1 : 1;
110         ojbQuery.setStartAtIndex(startAtIndex);
111 
112         if (criteria.getMaxResults() != null) {
113             //not subtracting one from MaxResults in order to retrieve
114             //one extra row so that the MoreResultsAvailable field can be set
115             ojbQuery.setEndAtIndex(criteria.getMaxResults() + startAtIndex);
116         }
117 
118         if (CollectionUtils.isNotEmpty(criteria.getOrderByFields())) {
119             for (OrderByField orderByField : criteria.getOrderByFields()) {
120                 if (OrderDirection.ASCENDING.equals(orderByField.getOrderDirection())) {
121                     ojbQuery.addOrderBy(orderByField.getFieldName(), true);
122                 } else if (OrderDirection.DESCENDING.equals(orderByField.getOrderDirection())) {
123                     ojbQuery.addOrderBy(orderByField.getFieldName(), false);
124                 }
125             }
126         }
127 
128         @SuppressWarnings("unchecked")
129         final List<T> rows = new ArrayList<T>(getPersistenceBrokerTemplate().getCollectionByQuery(ojbQuery));
130 
131         if (criteria.getMaxResults() != null && rows.size() > criteria.getMaxResults()) {
132             results.setMoreResultsAvailable(true);
133             //remove the extra row that was returned
134             rows.remove(criteria.getMaxResults().intValue());
135         }
136 
137         results.setResults(transformResults(rows, transform));
138         return results.build();
139     }
140 
141     private static <T> List<T> transformResults(List<T> results, LookupCustomizer.Transform<T, T> transform) {
142         final List<T> list = new ArrayList<T>();
143         for (T r : results) {
144             list.add(transform.apply(r));
145         }
146         return list;
147     }
148 
149     /** gets results where only the count is requested. */
150     private <T> GenericQueryResults<T> forCountOnly(final Class<T> queryClass, final QueryByCriteria criteria, final Criteria ojbCriteria) {
151         final Query ojbQuery = QueryFactory.newQuery(queryClass, ojbCriteria);
152         final GenericQueryResults.Builder<T> results = GenericQueryResults.Builder.<T>create();
153         results.setTotalRowCount(getPersistenceBrokerTemplate().getCount(ojbQuery));
154 
155         return results.build();
156     }
157 
158     /** adds a predicate to a Criteria.*/
159     private void addPredicate(Predicate p, Criteria parent, LookupCustomizer.Transform<Predicate, Predicate> transform) {
160         p = transform.apply(p);
161 
162         if (p instanceof PropertyPathPredicate) {
163             final String pp = ((PropertyPathPredicate) p).getPropertyPath();
164             if (p instanceof NotNullPredicate) {
165                 parent.addNotNull(pp);
166             } else if (p instanceof NullPredicate) {
167                 parent.addIsNull(pp);
168             } else if (p instanceof SingleValuedPredicate) {
169                 addSingleValuePredicate((SingleValuedPredicate) p, parent);
170             } else if (p instanceof MultiValuedPredicate) {
171                 addMultiValuePredicate((MultiValuedPredicate) p, parent);
172             } else {
173                 throw new UnsupportedPredicateException(p);
174             }
175         } else if (p instanceof CompositePredicate) {
176             addCompositePredicate((CompositePredicate) p, parent, transform);
177         } else {
178             throw new UnsupportedPredicateException(p);
179         }
180     }
181 
182     /** adds a single valued predicate to a Criteria. */
183     private void addSingleValuePredicate(SingleValuedPredicate p, Criteria parent) {
184         final Object value = getVal(p.getValue());
185         final String pp = p.getPropertyPath();
186         if (p instanceof EqualPredicate) {
187             parent.addEqualTo(pp, value);
188         } else if (p instanceof EqualIgnoreCasePredicate) {
189             parent.addEqualTo(genUpperFunc(pp), ((String) value).toUpperCase());
190         } else if (p instanceof GreaterThanOrEqualPredicate) {
191             parent.addGreaterOrEqualThan(pp, value);
192         } else if (p instanceof GreaterThanPredicate) {
193             parent.addGreaterThan(pp, value);
194         } else if (p instanceof LessThanOrEqualPredicate) {
195             parent.addLessOrEqualThan(pp, value);
196         } else if (p instanceof LessThanPredicate) {
197             parent.addLessThan(pp, value);
198         } else if (p instanceof LikePredicate) {
199             //no need to convert * or ? since ojb handles the conversion/escaping
200             parent.addLike(genUpperFunc(pp), ((String) value).toUpperCase());
201         } else if (p instanceof NotEqualPredicate) {
202             parent.addNotEqualTo(pp, value);
203         } else if (p instanceof NotEqualIgnoreCasePredicate) {
204             parent.addNotEqualTo(genUpperFunc(pp), ((String) value).toUpperCase());
205         } else if (p instanceof NotLikePredicate) {
206             parent.addNotLike(pp, value);
207         } else {
208             throw new UnsupportedPredicateException(p);
209         }
210     }
211 
212     /** adds a multi valued predicate to a Criteria. */
213     private void addMultiValuePredicate(MultiValuedPredicate p, Criteria parent) {
214         final String pp = p.getPropertyPath();
215         if (p instanceof InPredicate) {
216             final Set<?> values = getVals(p.getValues());
217             parent.addIn(pp, values);
218         } else if (p instanceof InIgnoreCasePredicate) {
219             final Set<String> values = toUpper(getValsUnsafe(((InIgnoreCasePredicate) p).getValues()));
220             parent.addIn(genUpperFunc(pp), values);
221         } else if (p instanceof NotInPredicate) {
222             final Set<?> values = getVals(p.getValues());
223             parent.addNotIn(pp, values);
224         } else if (p instanceof NotInIgnoreCasePredicate) {
225             final Set<String> values = toUpper(getValsUnsafe(((NotInIgnoreCasePredicate) p).getValues()));
226             parent.addNotIn(genUpperFunc(pp), values);
227         } else {
228             throw new UnsupportedPredicateException(p);
229         }
230     }
231 
232     /** adds a composite predicate to a Criteria. */
233     private void addCompositePredicate(final CompositePredicate p, final Criteria parent,  LookupCustomizer.Transform<Predicate, Predicate> transform) {
234         for (Predicate ip : p.getPredicates()) {
235             final Criteria inner = new Criteria();
236             addPredicate(ip, inner, transform);
237             if (p instanceof AndPredicate) {
238                 parent.addAndCriteria(inner);
239             } else if (p instanceof OrPredicate) {
240                 parent.addOrCriteria(inner);
241             } else {
242                 throw new UnsupportedPredicateException(p);
243             }
244         }
245     }
246 
247     private static <U extends CriteriaValue<?>> Object getVal(U toConv) {
248         Object o = toConv.getValue();
249         if (o instanceof DateTime) {
250             return new Timestamp(((DateTime) o).getMillis());
251         }
252         return o;
253     }
254 
255     //this is unsafe b/c values could be converted resulting in a classcast exception
256     @SuppressWarnings("unchecked")
257     private static <T, U extends CriteriaValue<T>> Set<T> getValsUnsafe(Set<? extends U> toConv) {
258         return (Set<T>) getVals(toConv);
259     }
260 
261     private static Set<?> getVals(Set<? extends CriteriaValue<?>> toConv) {
262         final Set<Object> values = new HashSet<Object>();
263         for (CriteriaValue<?> value : toConv) {
264             values.add(getVal(value));
265         }
266         return values;
267     }
268 
269     //eliding performance for function composition....
270     private static Set<String> toUpper(Set<String> strs) {
271         final Set<String> values = new HashSet<String>();
272         for (String value : strs) {
273             values.add(value.toUpperCase());
274         }
275         return values;
276     }
277 
278     private String getUpperFunction() {
279         return getDbPlatform().getUpperCaseFunction();
280     }
281 
282     private String genUpperFunc(String pp) {
283         return new StringBuilder(getUpperFunction()).append("(").append(pp).append(")").toString();
284     }
285 
286     /** this is a fatal error since this implementation should support all known predicates. */
287     private static class UnsupportedPredicateException extends RuntimeException {
288         private UnsupportedPredicateException(Predicate predicate) {
289             super("Unsupported predicate [" + String.valueOf(predicate) + "]");
290         }
291     }
292 
293     /** this is a fatal error since this implementation should support all known count flags. */
294     private static class UnsupportedCountFlagException extends RuntimeException {
295         private UnsupportedCountFlagException(CountFlag flag) {
296             super("Unsupported predicate [" + String.valueOf(flag) + "]");
297         }
298     }
299 }