001/**
002 * Copyright 2005-2015 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.dao.impl;
017
018import org.apache.commons.beanutils.PropertyUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.ojb.broker.query.Criteria;
021import org.apache.ojb.broker.query.Query;
022import org.apache.ojb.broker.query.QueryByCriteria;
023import org.apache.ojb.broker.query.QueryFactory;
024import org.kuali.rice.core.api.datetime.DateTimeService;
025import org.kuali.rice.core.api.search.SearchOperator;
026import org.kuali.rice.core.api.util.RiceKeyConstants;
027import org.kuali.rice.core.api.util.type.TypeUtils;
028import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion;
029import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb;
030import org.kuali.rice.core.framework.persistence.platform.DatabasePlatform;
031import org.kuali.rice.kns.service.KNSServiceLocator;
032import org.kuali.rice.krad.bo.BusinessObject;
033import org.kuali.rice.krad.bo.InactivatableFromTo;
034import org.kuali.rice.krad.dao.LookupDao;
035import org.kuali.rice.krad.lookup.CollectionIncomplete;
036import org.kuali.rice.krad.lookup.LookupUtils;
037import org.kuali.rice.krad.service.DataDictionaryService;
038import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
039import org.kuali.rice.krad.service.PersistenceStructureService;
040import org.kuali.rice.krad.util.GlobalVariables;
041import org.kuali.rice.krad.util.KRADConstants;
042import org.kuali.rice.krad.util.KRADPropertyConstants;
043import org.kuali.rice.krad.util.ObjectUtils;
044import org.springframework.dao.DataIntegrityViolationException;
045import org.springmodules.orm.ojb.OjbOperationException;
046
047import java.math.BigDecimal;
048import java.sql.Timestamp;
049import java.text.ParseException;
050import java.util.ArrayList;
051import java.util.Collection;
052import java.util.Iterator;
053import java.util.List;
054import 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
062public 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        try {
544            int strLength = splitPropVal.length;
545            // if Not'ed empty criteria
546            if (strLength == 0)
547                throw new IllegalArgumentException("Improper syntax of NOT operator in " + propertyName);
548            // if more than one NOT operator assume an implicit and (i.e. !a!b = !a&!b)
549            if (strLength > 1) {
550                String expandedNot = SearchOperator.NOT + StringUtils.join(splitPropVal, SearchOperator.AND.op() + SearchOperator.NOT.op());
551                // we know that since this method was called, treatWildcardsAndOperatorsAsLiteral must be false
552                addCriteria(propertyName, expandedNot, propertyType, caseInsensitive, false, criteria);
553            }
554            else {
555                // only one so add a not like
556                criteria.addNotLike(propertyName, splitPropVal[0]);
557            }
558        } catch (IllegalArgumentException ex) {
559            GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_NOT_SYNTAX, propertyName);
560        }
561    }
562
563    /**
564     * Builds a sub criteria object joined with an 'AND' or 'OR' (depending on splitValue) using the split values of propertyValue. Then joins back the
565     * sub criteria to the main criteria using an 'AND'.
566     */
567    private void addLogicalOperatorCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria, String splitValue) {
568        String[] splitPropVal = StringUtils.split(propertyValue, splitValue);
569
570        Criteria subCriteria = new Criteria();
571        for (int i = 0; i < splitPropVal.length; i++) {
572                Criteria predicate = new Criteria();
573
574            addCriteria(propertyName, splitPropVal[i], propertyType, caseInsensitive, false, predicate);
575            if (splitValue.equals(SearchOperator.OR.op())) {
576                subCriteria.addOrCriteria(predicate);
577            }
578            if (splitValue.equals(SearchOperator.AND.op())) {
579                subCriteria.addAndCriteria(predicate);
580            }
581        }
582
583        criteria.addAndCriteria(subCriteria);
584    }
585    
586    private java.sql.Date parseDate(String dateString) {
587                dateString = dateString.trim();
588                try {
589                        return dateTimeService.convertToSqlDate(dateString);
590                } catch (ParseException ex) {
591                        return null;
592                }
593        }
594
595    /**
596         * Adds to the criteria object based on query characters given
597         */
598    private void addDateRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
599
600        try {
601            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
602                if (treatWildcardsAndOperatorsAsLiteral)
603                    throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
604                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
605                if (rangeValues.length < 2)
606                    throw new IllegalArgumentException("Improper syntax of BETWEEN operator in " + propertyName);
607
608                criteria.addBetween(propertyName, parseDate( ObjectUtils.clean(rangeValues[0] ) ), parseDate( ObjectUtils.clean(rangeValues[1] ) ) );
609            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
610                if (treatWildcardsAndOperatorsAsLiteral)
611                    throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
612                criteria.addGreaterOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
613            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
614                if (treatWildcardsAndOperatorsAsLiteral)
615                    throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
616                criteria.addLessOrEqualThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
617            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
618                if (treatWildcardsAndOperatorsAsLiteral)
619                    throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
620                criteria.addGreaterThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
621            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
622                if (treatWildcardsAndOperatorsAsLiteral)
623                    throw new RuntimeException("Wildcards and operators are not allowed on this date field: " + propertyName);
624                criteria.addLessThan(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
625            } else {
626                criteria.addEqualTo(propertyName, parseDate( ObjectUtils.clean(propertyValue) ) );
627            }
628        } catch (IllegalArgumentException ex) {
629            GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_BETWEEN_SYNTAX, propertyName);
630        }
631    }
632
633    private BigDecimal cleanNumeric( String value ) {
634        String cleanedValue = value.replaceAll( "[^-0-9.]", "" );
635        // ensure only one "minus" at the beginning, if any
636        if ( cleanedValue.lastIndexOf( '-' ) > 0 ) {
637            if ( cleanedValue.charAt( 0 ) == '-' ) {
638                cleanedValue = "-" + cleanedValue.replaceAll( "-", "" );
639            } else {
640                cleanedValue = cleanedValue.replaceAll( "-", "" );
641            }
642        }
643        // ensure only one decimal in the string
644        int decimalLoc = cleanedValue.lastIndexOf( '.' );
645        if ( cleanedValue.indexOf( '.' ) != decimalLoc ) {
646            cleanedValue = cleanedValue.substring( 0, decimalLoc ).replaceAll( "\\.", "" ) + cleanedValue.substring( decimalLoc );
647        }
648        try {
649            return new BigDecimal( cleanedValue );
650        } catch ( NumberFormatException ex ) {
651            GlobalVariables.getMessageMap().putError(KRADConstants.DOCUMENT_ERRORS, RiceKeyConstants.ERROR_CUSTOM, new String[] { "Invalid Numeric Input: " + value });
652            return null;
653        }
654    }
655
656    /**
657     * Adds to the criteria object based on query characters given
658     */
659    private void addNumericRangeCriteria(String propertyName, String propertyValue, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
660
661        try {
662            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
663                if (treatWildcardsAndOperatorsAsLiteral)
664                    throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
665                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
666                if (rangeValues.length < 2)
667                    throw new IllegalArgumentException("Improper syntax of BETWEEN operator in " + propertyName);
668
669                criteria.addBetween(propertyName, cleanNumeric( rangeValues[0] ), cleanNumeric( rangeValues[1] ));
670            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
671                if (treatWildcardsAndOperatorsAsLiteral)
672                    throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
673                criteria.addGreaterOrEqualThan(propertyName, cleanNumeric(propertyValue));
674            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
675                if (treatWildcardsAndOperatorsAsLiteral)
676                    throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
677                criteria.addLessOrEqualThan(propertyName, cleanNumeric(propertyValue));
678            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
679                if (treatWildcardsAndOperatorsAsLiteral)
680                    throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
681                criteria.addGreaterThan(propertyName, cleanNumeric( propertyValue ) );
682            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
683                if (treatWildcardsAndOperatorsAsLiteral)
684                    throw new RuntimeException("Cannot use wildcards and operators on numeric field " + propertyName);
685                criteria.addLessThan(propertyName, cleanNumeric(propertyValue));
686            } else {
687                criteria.addEqualTo(propertyName, cleanNumeric(propertyValue));
688            }
689        } catch (IllegalArgumentException ex) {
690            GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_BETWEEN_SYNTAX, propertyName);
691        }
692    }
693
694    /**
695     * Adds to the criteria object based on query characters given
696     */
697    private void addStringRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {
698
699        try {
700            if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
701                String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
702                if (rangeValues.length < 2)
703                    throw new IllegalArgumentException("Improper syntax of BETWEEN operator in " + propertyName);
704
705                criteria.addBetween(propertyName, rangeValues[0], rangeValues[1]);
706            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
707                criteria.addGreaterOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
708            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
709                criteria.addLessOrEqualThan(propertyName, ObjectUtils.clean(propertyValue));
710            } else if (propertyValue.startsWith(SearchOperator.GREATER_THAN.op())) {
711                criteria.addGreaterThan(propertyName, ObjectUtils.clean(propertyValue));
712            } else if (propertyValue.startsWith(SearchOperator.LESS_THAN.op())) {
713                criteria.addLessThan(propertyName, ObjectUtils.clean(propertyValue));
714            } else {
715                criteria.addEqualTo(propertyName, ObjectUtils.clean(propertyValue));
716            }
717        } catch (IllegalArgumentException ex) {
718            GlobalVariables.getMessageMap().putError("lookupCriteria[" + propertyName + "]", RiceKeyConstants.ERROR_BETWEEN_SYNTAX, propertyName);
719        }
720    }
721
722        public void setDateTimeService(DateTimeService dateTimeService) {
723                this.dateTimeService = dateTimeService;
724        }
725
726    public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
727        this.persistenceStructureService = persistenceStructureService;
728    }
729
730    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
731        this.dataDictionaryService = dataDictionaryService;
732    }
733}