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