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 }