001 /**
002 * Copyright 2005-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krad.criteria;
017
018 import org.apache.ojb.broker.query.Criteria;
019 import org.apache.ojb.broker.query.Query;
020 import org.apache.ojb.broker.query.QueryFactory;
021 import org.joda.time.DateTime;
022 import org.kuali.rice.core.api.criteria.AndPredicate;
023 import org.kuali.rice.core.api.criteria.CompositePredicate;
024 import org.kuali.rice.core.api.criteria.CountFlag;
025 import org.kuali.rice.core.api.criteria.CriteriaDateTimeValue;
026 import org.kuali.rice.core.api.criteria.CriteriaLookupService;
027 import org.kuali.rice.core.api.criteria.CriteriaValue;
028 import org.kuali.rice.core.api.criteria.EqualIgnoreCasePredicate;
029 import org.kuali.rice.core.api.criteria.EqualPredicate;
030 import org.kuali.rice.core.api.criteria.GenericQueryResults;
031 import org.kuali.rice.core.api.criteria.GreaterThanOrEqualPredicate;
032 import org.kuali.rice.core.api.criteria.GreaterThanPredicate;
033 import org.kuali.rice.core.api.criteria.InIgnoreCasePredicate;
034 import org.kuali.rice.core.api.criteria.InPredicate;
035 import org.kuali.rice.core.api.criteria.LessThanOrEqualPredicate;
036 import org.kuali.rice.core.api.criteria.LessThanPredicate;
037 import org.kuali.rice.core.api.criteria.LikePredicate;
038 import org.kuali.rice.core.api.criteria.LookupCustomizer;
039 import org.kuali.rice.core.api.criteria.MultiValuedPredicate;
040 import org.kuali.rice.core.api.criteria.NotEqualIgnoreCasePredicate;
041 import org.kuali.rice.core.api.criteria.NotEqualPredicate;
042 import org.kuali.rice.core.api.criteria.NotInIgnoreCasePredicate;
043 import org.kuali.rice.core.api.criteria.NotInPredicate;
044 import org.kuali.rice.core.api.criteria.NotLikePredicate;
045 import org.kuali.rice.core.api.criteria.NotNullPredicate;
046 import org.kuali.rice.core.api.criteria.NullPredicate;
047 import org.kuali.rice.core.api.criteria.OrPredicate;
048 import org.kuali.rice.core.api.criteria.Predicate;
049 import org.kuali.rice.core.api.criteria.PropertyPathPredicate;
050 import org.kuali.rice.core.api.criteria.QueryByCriteria;
051 import org.kuali.rice.core.api.criteria.SingleValuedPredicate;
052 import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb;
053
054 import java.sql.Timestamp;
055 import java.util.ArrayList;
056 import java.util.HashSet;
057 import java.util.List;
058 import java.util.Set;
059
060 public class CriteriaLookupDaoOjb extends PlatformAwareDaoBaseOjb implements CriteriaLookupDao {
061
062 @Override
063 public <T> GenericQueryResults<T> lookup(final Class<T> queryClass, final QueryByCriteria criteria) {
064 return lookup(queryClass, criteria, LookupCustomizer.Builder.<T>create().build());
065 }
066
067 @Override
068 public <T> GenericQueryResults<T> lookup(final Class<T> queryClass, final QueryByCriteria criteria, LookupCustomizer<T> customizer) {
069 if (queryClass == null) {
070 throw new IllegalArgumentException("queryClass is null");
071 }
072
073 if (criteria == null) {
074 throw new IllegalArgumentException("criteria is null");
075 }
076
077 if (customizer == null) {
078 throw new IllegalArgumentException("customizer is null");
079 }
080
081 final Criteria parent = new Criteria();
082
083 if (criteria.getPredicate() != null) {
084 addPredicate(criteria.getPredicate(), parent, customizer.getPredicateTransform());
085 }
086
087 switch (criteria.getCountFlag()) {
088 case ONLY:
089 return forCountOnly(queryClass, criteria, parent);
090 case NONE:
091 return forRowResults(queryClass, criteria, parent, criteria.getCountFlag(), customizer.getResultTransform());
092 case INCLUDE:
093 return forRowResults(queryClass, criteria, parent, criteria.getCountFlag(), customizer.getResultTransform());
094 default: throw new UnsupportedCountFlagException(criteria.getCountFlag());
095 }
096 }
097
098 /** gets results where the actual rows are requested. */
099 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 }