001/** 002 * Copyright 2005-2016 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 */ 016package org.kuali.rice.krad.criteria; 017 018import org.apache.commons.collections.CollectionUtils; 019import org.apache.ojb.broker.query.Criteria; 020import org.apache.ojb.broker.query.Query; 021import org.apache.ojb.broker.query.QueryFactory; 022import org.joda.time.DateTime; 023import org.kuali.rice.core.api.criteria.AndPredicate; 024import org.kuali.rice.core.api.criteria.CompositePredicate; 025import org.kuali.rice.core.api.criteria.CountFlag; 026import org.kuali.rice.core.api.criteria.CriteriaValue; 027import org.kuali.rice.core.api.criteria.EqualIgnoreCasePredicate; 028import org.kuali.rice.core.api.criteria.EqualPredicate; 029import org.kuali.rice.core.api.criteria.GenericQueryResults; 030import org.kuali.rice.core.api.criteria.GreaterThanOrEqualPredicate; 031import org.kuali.rice.core.api.criteria.GreaterThanPredicate; 032import org.kuali.rice.core.api.criteria.InIgnoreCasePredicate; 033import org.kuali.rice.core.api.criteria.InPredicate; 034import org.kuali.rice.core.api.criteria.LessThanOrEqualPredicate; 035import org.kuali.rice.core.api.criteria.LessThanPredicate; 036import org.kuali.rice.core.api.criteria.LikePredicate; 037import org.kuali.rice.core.api.criteria.LookupCustomizer; 038import org.kuali.rice.core.api.criteria.MultiValuedPredicate; 039import org.kuali.rice.core.api.criteria.NotEqualIgnoreCasePredicate; 040import org.kuali.rice.core.api.criteria.NotEqualPredicate; 041import org.kuali.rice.core.api.criteria.NotInIgnoreCasePredicate; 042import org.kuali.rice.core.api.criteria.NotInPredicate; 043import org.kuali.rice.core.api.criteria.NotLikePredicate; 044import org.kuali.rice.core.api.criteria.NotNullPredicate; 045import org.kuali.rice.core.api.criteria.NullPredicate; 046import org.kuali.rice.core.api.criteria.OrPredicate; 047import org.kuali.rice.core.api.criteria.OrderByField; 048import org.kuali.rice.core.api.criteria.OrderDirection; 049import org.kuali.rice.core.api.criteria.Predicate; 050import org.kuali.rice.core.api.criteria.PropertyPathPredicate; 051import org.kuali.rice.core.api.criteria.QueryByCriteria; 052import org.kuali.rice.core.api.criteria.SingleValuedPredicate; 053import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb; 054 055import java.sql.Timestamp; 056import java.util.ArrayList; 057import java.util.HashSet; 058import java.util.List; 059import java.util.Set; 060 061public class CriteriaLookupDaoOjb extends PlatformAwareDaoBaseOjb implements CriteriaLookupDao { 062 063 @Override 064 public <T> GenericQueryResults<T> lookup(final Class<T> queryClass, final QueryByCriteria criteria) { 065 return lookup(queryClass, criteria, LookupCustomizer.Builder.<T>create().build()); 066 } 067 068 @Override 069 public <T> GenericQueryResults<T> lookup(final Class<T> queryClass, final QueryByCriteria criteria, LookupCustomizer<T> customizer) { 070 if (queryClass == null) { 071 throw new IllegalArgumentException("queryClass is null"); 072 } 073 074 if (criteria == null) { 075 throw new IllegalArgumentException("criteria is null"); 076 } 077 078 if (customizer == null) { 079 throw new IllegalArgumentException("customizer is null"); 080 } 081 082 final Criteria parent = new Criteria(); 083 084 if (criteria.getPredicate() != null) { 085 addPredicate(criteria.getPredicate(), parent, customizer.getPredicateTransform()); 086 } 087 088 switch (criteria.getCountFlag()) { 089 case ONLY: 090 return forCountOnly(queryClass, criteria, parent); 091 case NONE: 092 return forRowResults(queryClass, criteria, parent, criteria.getCountFlag(), customizer.getResultTransform()); 093 case INCLUDE: 094 return forRowResults(queryClass, criteria, parent, criteria.getCountFlag(), customizer.getResultTransform()); 095 default: throw new UnsupportedCountFlagException(criteria.getCountFlag()); 096 } 097 } 098 099 /** 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}