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.dao.impl;
017
018 import org.apache.commons.beanutils.PropertyUtils;
019 import org.apache.commons.lang.StringUtils;
020 import org.apache.ojb.broker.query.Criteria;
021 import org.apache.ojb.broker.query.Query;
022 import org.apache.ojb.broker.query.QueryByCriteria;
023 import org.apache.ojb.broker.query.QueryFactory;
024 import org.kuali.rice.core.api.datetime.DateTimeService;
025 import org.kuali.rice.core.api.search.SearchOperator;
026 import org.kuali.rice.core.api.util.RiceKeyConstants;
027 import org.kuali.rice.core.api.util.type.TypeUtils;
028 import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion;
029 import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb;
030 import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform;
031 import org.kuali.rice.kns.service.KNSServiceLocator;
032 import org.kuali.rice.krad.bo.BusinessObject;
033 import org.kuali.rice.krad.bo.InactivatableFromTo;
034 import org.kuali.rice.krad.dao.LookupDao;
035 import org.kuali.rice.krad.lookup.CollectionIncomplete;
036 import org.kuali.rice.krad.lookup.LookupUtils;
037 import org.kuali.rice.krad.service.DataDictionaryService;
038 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
039 import org.kuali.rice.krad.service.PersistenceStructureService;
040 import org.kuali.rice.krad.util.GlobalVariables;
041 import org.kuali.rice.krad.util.KRADConstants;
042 import org.kuali.rice.krad.util.KRADPropertyConstants;
043 import org.kuali.rice.krad.util.ObjectUtils;
044 import org.springframework.dao.DataIntegrityViolationException;
045 import org.springmodules.orm.ojb.OjbOperationException;
046
047 import java.math.BigDecimal;
048 import java.sql.Timestamp;
049 import java.text.ParseException;
050 import java.util.ArrayList;
051 import java.util.Collection;
052 import java.util.Iterator;
053 import java.util.List;
054 import java.util.Map;
055
056 /**
057 * OJB implementation of the LookupDao interface
058 *
059 * @deprecated use new KRAD Data framework {@link org.kuali.rice.krad.data.DataObjectService}
060 */
061 @Deprecated
062 public class LookupDaoOjb extends PlatformAwareDaoBaseOjb implements LookupDao {
063 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupDaoOjb.class);
064 private DateTimeService dateTimeService;
065 private PersistenceStructureService persistenceStructureService;
066 private DataDictionaryService dataDictionaryService;
067
068 /**
069 * Since 2.3
070 * This version of findCollectionBySearchHelper is needed for version compatibility. It allows executeSearch
071 * to behave the same way as it did prior to 2.3. The value for searchResultsLimit will be retrieved from the
072 * KNS version of LookupUtils.
073 *
074 * @see org.kuali.rice.krad.dao.LookupDao#findCollectionBySearchHelper(java.lang.Class, java.util.Map, boolean,
075 * boolean)
076 */
077 public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded,
078 boolean usePrimaryKeyValuesOnly) {
079 Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(businessObjectClass);
080 return findCollectionBySearchHelper(businessObjectClass, formProps, unbounded, usePrimaryKeyValuesOnly,
081 searchResultsLimit);
082 }
083
084 /**
085 * @see org.kuali.rice.krad.dao.LookupDao#findCollectionBySearchHelper(java.lang.Class, java.util.Map, boolean,
086 * boolean, Integer)
087 *
088 * If searchResultsLimit is null, the search results will not be limited by any other means.
089 */
090 public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded,
091 boolean usePrimaryKeyValuesOnly, Integer searchResultsLimit) {
092 BusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
093 if (usePrimaryKeyValuesOnly) {
094 return executeSearch(businessObjectClass, getCollectionCriteriaFromMapUsingPrimaryKeysOnly(
095 businessObjectClass, formProps), unbounded, searchResultsLimit);
096 }
097
098 Criteria crit = getCollectionCriteriaFromMap(businessObject, formProps);
099 return executeSearch(businessObjectClass, crit, unbounded, searchResultsLimit);
100 }
101
102 /**
103 * Builds up criteria object based on the object and map.
104 */
105 public Criteria getCollectionCriteriaFromMap(BusinessObject example, Map formProps) {
106 Criteria criteria = new Criteria();
107 Iterator propsIter = formProps.keySet().iterator();
108 while (propsIter.hasNext()) {
109 String propertyName = (String) propsIter.next();
110 Boolean caseInsensitive = Boolean.TRUE;
111 if ( KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined( example.getClass(), propertyName )) {
112 // If forceUppercase is true, both the database value and the user entry should be converted to Uppercase -- so change the caseInsensitive to false since we don't need to
113 // worry about the values not matching. However, if forceUppercase is false, make sure to do a caseInsensitive search because the database value and user entry
114 // could be mixed case. Thus, caseInsensitive will be the opposite of forceUppercase.
115 caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase( example.getClass(), propertyName );
116 }
117 if ( caseInsensitive == null ) { caseInsensitive = Boolean.TRUE; }
118 boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator
119 .getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
120
121 if (formProps.get(propertyName) instanceof Collection) {
122 Iterator iter = ((Collection) formProps.get(propertyName)).iterator();
123 while (iter.hasNext()) {
124 String searchValue = (String) iter.next();
125 if (!caseInsensitive) {
126 // Verify that the searchValue is uppercased if caseInsensitive is false
127 searchValue = searchValue.toUpperCase();
128 }
129 if (!createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps )) {
130 throw new RuntimeException("Invalid value in Collection");
131 }
132 }
133 }
134 else {
135 String searchValue = (String) formProps.get(propertyName);
136 if (!caseInsensitive) {
137 // Verify that the searchValue is uppercased if caseInsensitive is false
138 searchValue = searchValue.toUpperCase();
139 }
140 if (!createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps)) {
141 continue;
142 }
143 }
144 }
145 return criteria;
146 }
147
148 public Criteria getCollectionCriteriaFromMapUsingPrimaryKeysOnly(Class businessObjectClass, Map formProps) {
149 BusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
150 Criteria criteria = new Criteria();
151 List pkFields = KRADServiceLocatorWeb.getLegacyDataAdapter().listPrimaryKeyFieldNames(businessObjectClass);
152 Iterator pkIter = pkFields.iterator();
153 while (pkIter.hasNext()) {
154 String pkFieldName = (String) pkIter.next();
155 String pkValue = (String) formProps.get(pkFieldName);
156
157 if (StringUtils.isBlank(pkValue)) {
158 throw new RuntimeException("Missing pk value for field " + pkFieldName + " when a search based on PK values only is performed.");
159 }
160 else {
161 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
162 if (pkValue.contains(op.op())) {
163 throw new RuntimeException("Value \"" + pkValue + "\" for PK field " + pkFieldName + " contains wildcard/operator characters.");
164 }
165 }
166 }
167 boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.
168 getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(businessObjectClass, pkFieldName);
169 createCriteria(businessObject, pkValue, pkFieldName, false, treatWildcardsAndOperatorsAsLiteral, criteria);
170 }
171 return criteria;
172 }
173
174 private BusinessObject checkBusinessObjectClass(Class businessObjectClass) {
175 if (businessObjectClass == null) {
176 throw new IllegalArgumentException("BusinessObject class passed to LookupDaoOjb findCollectionBySearchHelper... method was null");
177 }
178 BusinessObject businessObject = null;
179 try {
180 businessObject = (BusinessObject) businessObjectClass.newInstance();
181 }
182 catch (IllegalAccessException e) {
183 throw new RuntimeException("LookupDaoOjb could not get instance of " + businessObjectClass.getName(), e);
184 }
185 catch (InstantiationException e) {
186 throw new RuntimeException("LookupDaoOjb could not get instance of " + businessObjectClass.getName(), e);
187 }
188 return businessObject;
189 }
190
191 private Collection executeSearch(Class businessObjectClass, Criteria criteria, boolean unbounded, Integer searchResultsLimit) {
192 Collection searchResults = new ArrayList();
193 Long matchingResultsCount = null;
194 try {
195 if (!unbounded && searchResultsLimit == null) {
196 searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(businessObjectClass);
197 }
198 // A negative number in searchResultsLimit means the search results should be unlimited.
199 if (!unbounded && (searchResultsLimit != null) && searchResultsLimit >= 0) {
200 matchingResultsCount = new Long(getPersistenceBrokerTemplate().getCount(QueryFactory.newQuery(businessObjectClass, criteria)));
201 applySearchResultsLimit(businessObjectClass, criteria, getDbPlatform(), searchResultsLimit);
202 }
203 if ((matchingResultsCount == null) || (matchingResultsCount.intValue() <= searchResultsLimit.intValue())) {
204 matchingResultsCount = new Long(0);
205 }
206 searchResults = getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(businessObjectClass, criteria));
207 // populate Person objects in business objects
208 List bos = new ArrayList();
209 bos.addAll(searchResults);
210 searchResults = bos;
211
212
213 }
214 catch (OjbOperationException e) {
215 throw new RuntimeException("LookupDaoOjb encountered exception during executeSearch", e);
216 }
217 catch (DataIntegrityViolationException e) {
218 throw new RuntimeException("LookupDaoOjb encountered exception during executeSearch", e);
219 }
220 return new CollectionIncomplete(searchResults, matchingResultsCount);
221 }
222
223
224 /**
225 * This method applies the search results limit to the search criteria for this BO
226 *
227 * @param businessObjectClass BO class to search on / get limit for
228 * @param criteria search criteria
229 * @param platform database platform
230 * @param limit limit to use. If limit is null, getSearchResultsLimit will be called using the businessObjectClass
231 * to see if a limit can be found for this particular businessObjectClass.
232 */
233 protected void applySearchResultsLimit(Class businessObjectClass, Criteria criteria, DatabasePlatform platform,
234 Integer limit) {
235 String limitSql = null;
236 if (limit != null) {
237 limitSql = platform.applyLimitSql(limit);
238 } else {
239 limit = LookupUtils.getSearchResultsLimit(businessObjectClass, null);
240 if (limit != null) {
241 limitSql = platform.applyLimitSql(limit);
242 }
243 }
244 if (StringUtils.isNotBlank(limitSql)) {
245 criteria.addSql(limitSql);
246 }
247 }
248
249 public boolean createCriteria(Object example, String searchValue, String propertyName, Object criteria) {
250 return createCriteria( example, searchValue, propertyName, false, false, criteria );
251 }
252
253 public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria) {
254 return createCriteria( example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, null );
255 }
256
257 public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria, Map searchValues) {
258 // if searchValue is empty and the key is not a valid property ignore
259 if (!(criteria instanceof Criteria) || StringUtils.isBlank(searchValue) || !ObjectUtils.isWriteable(example, propertyName, persistenceStructureService)) {
260 return false;
261 }
262
263 // get property type which is used to determine type of criteria
264 Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
265 if (propertyType == null) {
266 return false;
267 }
268
269 // build criteria
270 if (example instanceof InactivatableFromTo) {
271 if (KRADPropertyConstants.ACTIVE.equals(propertyName)) {
272 addInactivateableFromToActiveCriteria(example, searchValue, (Criteria) criteria, searchValues);
273 } else if (KRADPropertyConstants.CURRENT.equals(propertyName)) {
274 addInactivateableFromToCurrentCriteria(example, searchValue, (Criteria) criteria, searchValues);
275 } else if (!KRADPropertyConstants.ACTIVE_AS_OF_DATE.equals(propertyName)) {
276 addCriteria(propertyName, searchValue, propertyType, caseInsensitive,
277 treatWildcardsAndOperatorsAsLiteral, (Criteria) criteria);
278 }
279 } else {
280 addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral,
281 (Criteria) criteria);
282 }
283
284 return true;
285 }
286
287 /**
288 * Find count of records meeting criteria based on the object and map.
289 */
290 public Long findCountByMap(Object example, Map formProps) {
291 Criteria criteria = new Criteria();
292 // iterate through the parameter map for key values search criteria
293 Iterator propsIter = formProps.keySet().iterator();
294 while (propsIter.hasNext()) {
295 String propertyName = (String) propsIter.next();
296 String searchValue = (String) formProps.get(propertyName);
297
298 // if searchValue is empty and the key is not a valid property ignore
299 if (StringUtils.isBlank(searchValue) || !(PropertyUtils.isWriteable(example, propertyName))) {
300 continue;
301 }
302
303 // get property type which is used to determine type of criteria
304 Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
305 if (propertyType == null) {
306 continue;
307 }
308 Boolean caseInsensitive = Boolean.TRUE;
309 if ( KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined( example.getClass(), propertyName )) {
310 caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase( example.getClass(), propertyName );
311 }
312 if ( caseInsensitive == null ) { caseInsensitive = Boolean.TRUE; }
313
314 boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator
315 .getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
316
317 if (!caseInsensitive) {
318 // Verify that the searchValue is uppercased if caseInsensitive is false
319 searchValue = searchValue.toUpperCase();
320 }
321
322 // build criteria
323 addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria);
324 }
325
326 // execute query and return result list
327 Query query = QueryFactory.newQuery(example.getClass(), criteria);
328
329 return new Long(getPersistenceBrokerTemplate().getCount(query));
330 }
331
332 /**
333 * @see org.kuali.rice.krad.dao.LookupDao#findObjectByMap(java.lang.Object, java.util.Map)
334 */
335 @Override
336 public <T extends Object> T findObjectByMap(Class<T> type, Map<String, String> formProps) {
337 T example = null;
338 try {
339 example = type.newInstance();
340 } catch (IllegalAccessException e) {
341 throw new RuntimeException("Cannot get new instance of " + type.getName(), e);
342 } catch (InstantiationException e) {
343 throw new RuntimeException("Cannot instantiate " + type.getName(), e);
344 }
345
346 if ( KRADServiceLocatorWeb.getLegacyDataAdapter().isPersistable(example.getClass())) {
347 Criteria criteria = new Criteria();
348
349 // iterate through the parameter map for search criteria
350 for (Map.Entry<String, String> formProp : formProps.entrySet()) {
351
352 String propertyName = formProp.getKey();
353 String searchValue = "";
354 if (formProp.getValue() != null) {
355 searchValue = formProp.getValue();
356 }
357
358 if (StringUtils.isNotBlank(searchValue) & PropertyUtils.isWriteable(example, propertyName)) {
359 Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
360 if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) {
361 criteria.addEqualTo(propertyName, cleanNumeric(searchValue));
362 } else if (TypeUtils.isTemporalClass(propertyType)) {
363 criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(searchValue) ) );
364 } else {
365 criteria.addEqualTo(propertyName, searchValue);
366 }
367 }
368 }
369
370 // execute query and return result list
371 Query query = QueryFactory.newQuery(example.getClass(), criteria);
372 return (T)getPersistenceBrokerTemplate().getObjectByQuery(query);
373 }
374 return null;
375 }
376
377
378 /**
379 * Adds to the criteria object based on the property type and any query characters given.
380 */
381 private void addCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
382 if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.OR.op())) {
383 addOrCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
384 return;
385 }
386
387 if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.AND.op())) {
388 addAndCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
389 return;
390 }
391
392 if (StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NULL.op()) || StringUtils.equalsIgnoreCase(propertyValue, SearchOperator.NOT_NULL.op())) {
393 // KULRICE-6846 null Lookup criteria causes sql exception
394 if (StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
395 criteria.addNotNull(propertyName);
396 }
397 else {
398 criteria.addIsNull(propertyName);
399 }
400 }
401 else if (TypeUtils.isStringClass(propertyType)) {
402 // KULRICE-85 : made string searches case insensitive - used new DBPlatform function to force strings to upper case
403 if ( caseInsensitive ) {
404 propertyName = getDbPlatform().getUpperCaseFunction() + "(" + propertyName + ")";
405 propertyValue = propertyValue.toUpperCase();
406 }
407 if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
408 addNotCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
409 } else if (
410 !treatWildcardsAndOperatorsAsLiteral && propertyValue != null && (
411 StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())
412 || propertyValue.startsWith(">")
413 || propertyValue.startsWith("<") ) ) {
414 addStringRangeCriteria(propertyName, propertyValue, criteria);
415 } else {
416 if (treatWildcardsAndOperatorsAsLiteral) {
417 propertyValue = StringUtils.replace(propertyValue, "*", "\\*");
418 }
419 criteria.addLike(propertyName, propertyValue);
420 }
421 } else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType) ) {
422 addNumericRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria);
423 } else if (TypeUtils.isTemporalClass(propertyType)) {
424 addDateRangeCriteria(propertyName, propertyValue, treatWildcardsAndOperatorsAsLiteral, criteria);
425 } else if (TypeUtils.isBooleanClass(propertyType)) {
426 criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue));
427 } else {
428 LOG.error("not adding criterion for: " + propertyName + "," + propertyType + "," + propertyValue);
429 }
430 }
431
432 /**
433 * Translates criteria for active status to criteria on the active from and to fields
434 *
435 * @param example - business object being queried on
436 * @param activeSearchValue - value for the active search field, should convert to boolean
437 * @param criteria - Criteria object being built
438 * @param searchValues - Map containing all search keys and values
439 */
440 protected void addInactivateableFromToActiveCriteria(Object example, String activeSearchValue, Criteria criteria, Map searchValues) {
441 Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
442
443 String activeBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(activeSearchValue);
444 if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
445 // (active from date <= date or active from date is null) and (date < active to date or active to date is null)
446 Criteria criteriaBeginDate = new Criteria();
447 criteriaBeginDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
448
449 Criteria criteriaBeginDateNull = new Criteria();
450 criteriaBeginDateNull.addIsNull(KRADPropertyConstants.ACTIVE_FROM_DATE);
451 criteriaBeginDate.addOrCriteria(criteriaBeginDateNull);
452
453 criteria.addAndCriteria(criteriaBeginDate);
454
455 Criteria criteriaEndDate = new Criteria();
456 criteriaEndDate.addGreaterThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
457
458 Criteria criteriaEndDateNull = new Criteria();
459 criteriaEndDateNull.addIsNull(KRADPropertyConstants.ACTIVE_TO_DATE);
460 criteriaEndDate.addOrCriteria(criteriaEndDateNull);
461
462 criteria.addAndCriteria(criteriaEndDate);
463 }
464 else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
465 // (date < active from date) or (active from date is null) or (date >= active to date)
466 Criteria criteriaNonActive = new Criteria();
467 criteriaNonActive.addGreaterThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
468
469 Criteria criteriaEndDate = new Criteria();
470 criteriaEndDate.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
471 criteriaNonActive.addOrCriteria(criteriaEndDate);
472
473 criteria.addAndCriteria(criteriaNonActive);
474 }
475 }
476
477 /**
478 * Translates criteria for current status to criteria on the active from field
479 *
480 * @param example - business object being queried on
481 * @param currentSearchValue - value for the current search field, should convert to boolean
482 * @param criteria - Criteria object being built
483 */
484 protected void addInactivateableFromToCurrentCriteria(Object example, String currentSearchValue, Criteria criteria, Map searchValues) {
485 Criteria maxBeginDateCriteria = new Criteria();
486
487 Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
488
489 maxBeginDateCriteria.addLessOrEqualThan(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
490
491 List<String> groupByFieldList = dataDictionaryService.getGroupByAttributesForEffectiveDating(example
492 .getClass());
493 if (groupByFieldList == null) {
494 return;
495 }
496
497 // join back to main query with the group by fields
498 String[] groupBy = new String[groupByFieldList.size()];
499 for (int i = 0; i < groupByFieldList.size(); i++) {
500 String groupByField = groupByFieldList.get(i);
501 groupBy[i] = groupByField;
502
503 maxBeginDateCriteria.addEqualToField(groupByField, Criteria.PARENT_QUERY_PREFIX + groupByField);
504 }
505
506 String[] columns = new String[1];
507 columns[0] = "max(" + KRADPropertyConstants.ACTIVE_FROM_DATE + ")";
508
509 QueryByCriteria query = QueryFactory.newReportQuery(example.getClass(), columns, maxBeginDateCriteria, true);
510 query.addGroupBy(groupBy);
511
512 String currentBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(currentSearchValue);
513 if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
514 criteria.addIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query);
515 } else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
516 criteria.addNotIn(KRADPropertyConstants.ACTIVE_FROM_DATE, query);
517 }
518 }
519
520 /**
521 * @param propertyName
522 * @param propertyValue
523 * @param propertyType
524 * @param criteria
525 */
526 private void addOrCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
527 addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.OR.op());
528 }
529
530 /**
531 * @param propertyName
532 * @param propertyValue
533 * @param propertyType
534 * @param criteria
535 */
536 private void addAndCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
537 addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.AND.op());
538 }
539
540 private void addNotCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
541
542 String[] splitPropVal = StringUtils.split(propertyValue, SearchOperator.NOT.op());
543
544 int strLength = splitPropVal.length;
545 // if more than one NOT operator assume an implicit and (i.e. !a!b = !a&!b)
546 if (strLength > 1) {
547 String expandedNot = SearchOperator.NOT + StringUtils.join(splitPropVal, SearchOperator.AND.op() + SearchOperator.NOT.op());
548 // we know that since this method was called, treatWildcardsAndOperatorsAsLiteral must be false
549 addCriteria(propertyName, expandedNot, propertyType, caseInsensitive, false, criteria);
550 }
551 else {
552 // only one so add a not like
553 criteria.addNotLike(propertyName, splitPropVal[0]);
554 }
555 }
556
557 /**
558 * Builds a sub criteria object joined with an 'AND' or 'OR' (depending on splitValue) using the split values of propertyValue. Then joins back the
559 * sub criteria to the main criteria using an 'AND'.
560 */
561 private void addLogicalOperatorCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria, String splitValue) {
562 String[] splitPropVal = StringUtils.split(propertyValue, splitValue);
563
564 Criteria subCriteria = new Criteria();
565 for (int i = 0; i < splitPropVal.length; i++) {
566 Criteria predicate = new Criteria();
567
568 addCriteria(propertyName, splitPropVal[i], propertyType, caseInsensitive, false, predicate);
569 if (splitValue.equals(SearchOperator.OR.op())) {
570 subCriteria.addOrCriteria(predicate);
571 }
572 if (splitValue.equals(SearchOperator.AND.op())) {
573 subCriteria.addAndCriteria(predicate);
574 }
575 }
576
577 criteria.addAndCriteria(subCriteria);
578 }
579
580 private java.sql.Date parseDate(String dateString) {
581 dateString = dateString.trim();
582 try {
583 return dateTimeService.convertToSqlDate(dateString);
584 } catch (ParseException ex) {
585 return null;
586 }
587 }
588
589 /**
590 * Adds to the criteria object based on query characters given
591 */
592 private void addDateRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
593
594 if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
595 if (treatWildcardsAndOperatorsAsLiteral)
596 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
597 String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
598 criteria.addBetween(propertyName, parseDate( ObjectUtils.clean(rangeValues[0] ) ), parseDate( ObjectUtils.clean(rangeValues[1] ) ) );
599 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
600 if (treatWildcardsAndOperatorsAsLiteral)
601 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
602 criteria.addGreaterOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
603 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
604 if (treatWildcardsAndOperatorsAsLiteral)
605 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
606 criteria.addLessOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
607 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
608 if (treatWildcardsAndOperatorsAsLiteral)
609 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
610 criteria.addGreaterThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
611 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
612 if (treatWildcardsAndOperatorsAsLiteral)
613 throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
614 criteria.addLessThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
615 } else {
616 criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
617 }
618 }
619
620 private BigDecimal cleanNumeric( String value ) {
621 String cleanedValue = value.replaceAll( "[^-0-9.]", "" );
622 // ensure only one "minus" at the beginning, if any
623 if ( cleanedValue.lastIndexOf( '-' ) > 0 ) {
624 if ( cleanedValue.charAt( 0 ) == '-' ) {
625 cleanedValue = "-" + cleanedValue.replaceAll( "-", "" );
626 } else {
627 cleanedValue = cleanedValue.replaceAll( "-", "" );
628 }
629 }
630 // ensure only one decimal in the string
631 int decimalLoc = cleanedValue.lastIndexOf( '.' );
632 if ( cleanedValue.indexOf( '.' ) != decimalLoc ) {
633 cleanedValue = cleanedValue.substring( 0, decimalLoc ).replaceAll( "\\.", "" ) + cleanedValue.substring( decimalLoc );
634 }
635 try {
636 return new BigDecimal( cleanedValue );
637 } catch ( NumberFormatException ex ) {
638 GlobalVariables.getMessageMap().putError(KRADConstants.DOCUMENT_ERRORS, RiceKeyConstants.ERROR_CUSTOM, new String[] { "Invalid Numeric Input: " + value });
639 return null;
640 }
641 }
642
643 /**
644 * Adds to the criteria object based on query characters given
645 */
646 private void addNumericRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
647
648 if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
649 if (treatWildcardsAndOperatorsAsLiteral)
650 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
651 String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
652 criteria.addBetween(propertyName, cleanNumeric( rangeValues[0] ), cleanNumeric( rangeValues[1] ));
653 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
654 if (treatWildcardsAndOperatorsAsLiteral)
655 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
656 criteria.addGreaterOrEqualThan(propertyName, cleanNumeric(propertyValue));
657 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
658 if (treatWildcardsAndOperatorsAsLiteral)
659 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
660 criteria.addLessOrEqualThan(propertyName, cleanNumeric(propertyValue));
661 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
662 if (treatWildcardsAndOperatorsAsLiteral)
663 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
664 criteria.addGreaterThan(propertyName, cleanNumeric( propertyValue ) );
665 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
666 if (treatWildcardsAndOperatorsAsLiteral)
667 throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
668 criteria.addLessThan(propertyName, cleanNumeric(propertyValue));
669 } else {
670 criteria.addEqualTo(propertyName, cleanNumeric(propertyValue));
671 }
672 }
673
674 /**
675 * Adds to the criteria object based on query characters given
676 */
677 private void addStringRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {
678
679 if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
680 String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
681 criteria.addBetween(propertyName, rangeValues[0], rangeValues[1]);
682 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
683 criteria.addGreaterOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
684 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
685 criteria.addLessOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
686 } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
687 criteria.addGreaterThan(propertyName, ObjectUtils.clean(propertyValue));
688 } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
689 criteria.addLessThan(propertyName, ObjectUtils.clean(propertyValue));
690 } else {
691 criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue));
692 }
693 }
694
695 public void setDateTimeService(DateTimeService dateTimeService) {
696 this.dateTimeService = dateTimeService;
697 }
698
699 public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
700 this.persistenceStructureService = persistenceStructureService;
701 }
702
703 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
704 this.dataDictionaryService = dataDictionaryService;
705 }
706 }