View Javadoc

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