001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.dao.impl;
017
018import org.apache.commons.beanutils.PropertyUtils;
019import org.apache.commons.lang.StringUtils;
020import org.kuali.rice.core.api.datetime.DateTimeService;
021import org.kuali.rice.core.api.search.SearchOperator;
022import org.kuali.rice.core.api.util.RiceKeyConstants;
023import org.kuali.rice.core.api.util.type.TypeUtils;
024import org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria;
025import org.kuali.rice.core.framework.persistence.jpa.criteria.QueryByCriteria;
026import org.kuali.rice.core.framework.persistence.jpa.metadata.EntityDescriptor;
027import org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor;
028import org.kuali.rice.core.framework.persistence.jpa.metadata.MetadataManager;
029import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion;
030import org.kuali.rice.kns.service.KNSServiceLocator;
031import org.kuali.rice.krad.bo.InactivatableFromTo;
032import org.kuali.rice.krad.bo.PersistableBusinessObject;
033import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
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.KRADServiceLocatorInternal;
039import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
040import org.kuali.rice.krad.service.PersistenceStructureService;
041import org.kuali.rice.krad.util.GlobalVariables;
042import org.kuali.rice.krad.util.KRADConstants;
043import org.kuali.rice.krad.util.KRADPropertyConstants;
044import org.kuali.rice.krad.util.ObjectUtils;
045import org.springframework.dao.DataIntegrityViolationException;
046
047import javax.persistence.EntityManager;
048import javax.persistence.PersistenceContext;
049import javax.persistence.PersistenceException;
050import java.lang.reflect.Field;
051import java.math.BigDecimal;
052import java.sql.Timestamp;
053import java.text.ParseException;
054import java.util.ArrayList;
055import java.util.Collection;
056import java.util.Iterator;
057import java.util.List;
058import java.util.Map;
059
060/**
061 * This class is the JPA implementation of the LookupDao interface.
062 */
063public class LookupDaoJpa implements LookupDao {
064        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupDao.class);
065
066        private DateTimeService dateTimeService;
067        private PersistenceStructureService persistenceStructureService;
068        private DataDictionaryService dataDictionaryService;
069
070        @PersistenceContext
071        private EntityManager entityManager;
072
073    public Long findCountByMap(Object example, Map formProps) {
074                Criteria criteria = new Criteria(example.getClass().getName());
075
076                // iterate through the parameter map for key values search criteria
077                Iterator propsIter = formProps.keySet().iterator();
078                while (propsIter.hasNext()) {
079                        String propertyName = (String) propsIter.next();
080                        String searchValue = (String) formProps.get(propertyName);
081
082                        // if searchValue is empty and the key is not a valid property ignore
083                        if (StringUtils.isBlank(searchValue) || !(PropertyUtils.isWriteable(example, propertyName))) {
084                                continue;
085                        }
086
087                        // get property type which is used to determine type of criteria
088                        Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
089                        if (propertyType == null) {
090                                continue;
091                        }
092                        Boolean caseInsensitive = Boolean.TRUE;
093                        if (KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined(example.getClass(), propertyName)) {
094                        // 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 
095                        // 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 
096                        // could be mixed case.  Thus, caseInsensitive will be the opposite of forceUppercase. 
097                                caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase(example.getClass(), propertyName);
098                        }
099                        if (caseInsensitive == null) {
100                                caseInsensitive = Boolean.TRUE;
101                        }
102
103                        boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.
104                                        getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName); 
105                        // build criteria
106                if (!caseInsensitive) { 
107                        // Verify that the searchValue is uppercased if caseInsensitive is false 
108                        searchValue = searchValue.toUpperCase(); 
109                }
110                        addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria);
111                }
112
113                // execute query and return result list
114                return (Long) new QueryByCriteria(entityManager, criteria).toCountQuery().getSingleResult();
115        }
116
117        public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded, boolean usePrimaryKeyValuesOnly) {
118                PersistableBusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
119                if (usePrimaryKeyValuesOnly) {
120                        return executeSearch(businessObjectClass, getCollectionCriteriaFromMapUsingPrimaryKeysOnly(businessObjectClass, formProps), unbounded);
121                } else {
122                        Criteria crit = getCollectionCriteriaFromMap(businessObject, formProps);
123                        return executeSearch(businessObjectClass, crit, unbounded);
124                }
125        }
126
127        public Criteria getCollectionCriteriaFromMap(PersistableBusinessObject example, Map formProps) {
128                Criteria criteria = new Criteria(example.getClass().getName());
129                Iterator propsIter = formProps.keySet().iterator();
130                while (propsIter.hasNext()) {
131                        String propertyName = (String) propsIter.next();
132                        Boolean caseInsensitive = Boolean.TRUE;
133                        if (KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined(example.getClass(), propertyName)) {
134                                caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService().getAttributeForceUppercase(example.getClass(), propertyName);
135                        }
136                        if (caseInsensitive == null) {
137                                caseInsensitive = Boolean.TRUE;
138                        }
139            boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.
140                                getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
141                        if (formProps.get(propertyName) instanceof Collection) {
142                                Iterator iter = ((Collection) formProps.get(propertyName)).iterator();
143                                while (iter.hasNext()) {
144                    String searchValue = (String) iter.next();
145                        if (!caseInsensitive) { 
146                                // Verify that the searchValue is uppercased if caseInsensitive is false 
147                                searchValue = searchValue.toUpperCase(); 
148                        }
149                                        if (!createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps)) {
150                                                throw new RuntimeException("Invalid value in Collection");
151                                        }
152                                }
153                        } else {
154                String searchValue = (String) formProps.get(propertyName);
155                        if (!caseInsensitive) { 
156                                // Verify that the searchValue is uppercased if caseInsensitive is false 
157                                searchValue = searchValue.toUpperCase(); 
158                        }
159                                if (!createCriteria(example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, formProps)) {
160                                        continue;
161                                }
162                        }
163                }
164                return criteria;
165        }
166
167        public Criteria getCollectionCriteriaFromMapUsingPrimaryKeysOnly(Class businessObjectClass, Map formProps) {
168                PersistableBusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
169                Criteria criteria = new Criteria(businessObjectClass.getName());
170                List pkFields = persistenceStructureService.listPrimaryKeyFieldNames(businessObjectClass);
171                Iterator pkIter = pkFields.iterator();
172                while (pkIter.hasNext()) {
173                        String pkFieldName = (String) pkIter.next();
174                        String pkValue = (String) formProps.get(pkFieldName);
175
176                        if (StringUtils.isBlank(pkValue)) {
177                                throw new RuntimeException("Missing pk value for field " + pkFieldName + " when a search based on PK values only is performed.");
178                        } else {
179                                for (SearchOperator op :  SearchOperator.QUERY_CHARACTERS) {
180                    if (pkValue.contains(op.op())) {
181                        throw new RuntimeException("Value \"" + pkValue + "\" for PK field " + pkFieldName + " contains wildcard/operator characters.");
182                    }
183                }
184                        }
185            boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.
186                                getBusinessObjectDictionaryService().isLookupFieldTreatWildcardsAndOperatorsAsLiteral(businessObjectClass, pkFieldName);
187                        createCriteria(businessObject, pkValue, pkFieldName, false, treatWildcardsAndOperatorsAsLiteral, criteria);
188                }
189                return criteria;
190        }
191
192        private PersistableBusinessObject checkBusinessObjectClass(Class businessObjectClass) {
193                if (businessObjectClass == null) {
194                        throw new IllegalArgumentException("BusinessObject class passed to LookupDao findCollectionBySearchHelper... method was null");
195                }
196                PersistableBusinessObject businessObject = null;
197                try {
198                        businessObject = (PersistableBusinessObject) businessObjectClass.newInstance();
199                } catch (IllegalAccessException e) {
200                        throw new RuntimeException("LookupDao could not get instance of " + businessObjectClass.getName(), e);
201                } catch (InstantiationException e) {
202                        throw new RuntimeException("LookupDao could not get instance of " + businessObjectClass.getName(), e);
203                }
204                return businessObject;
205        }
206
207        private Collection executeSearch(Class businessObjectClass, Criteria criteria, boolean unbounded) {
208                Collection<PersistableBusinessObject> searchResults = new ArrayList<PersistableBusinessObject>();
209                Long matchingResultsCount = null;
210                try {
211                        Integer searchResultsLimit = LookupUtils.getSearchResultsLimit(businessObjectClass);
212                        if (!unbounded && (searchResultsLimit != null)) {
213                                matchingResultsCount = (Long) new QueryByCriteria(entityManager, criteria).toCountQuery().getSingleResult();
214                                searchResults = new QueryByCriteria(entityManager, criteria).toQuery().setMaxResults(searchResultsLimit).getResultList();
215                        } else {
216                                searchResults = new QueryByCriteria(entityManager, criteria).toQuery().getResultList();
217                        }
218                        if ((matchingResultsCount == null) || (matchingResultsCount.intValue() <= searchResultsLimit.intValue())) {
219                                matchingResultsCount = new Long(0);
220                        }
221                        // Temp solution for loading extension objects - need to find a
222                        // better way
223                        // Should look for a JOIN query, for the above query, that will grab
224                        // the PBOEs as well (1+n query problem)
225                        for (PersistableBusinessObject bo : searchResults) {
226                                if (bo.getExtension() != null) {
227                                        PersistableBusinessObjectExtension boe = bo.getExtension();
228                                        EntityDescriptor entity = MetadataManager.getEntityDescriptor(bo.getExtension().getClass());
229                                        Criteria extensionCriteria = new Criteria(boe.getClass().getName());
230                                        for (FieldDescriptor fieldDescriptor : entity.getPrimaryKeys()) {
231                                                try {
232                                                        Field field = bo.getClass().getDeclaredField(fieldDescriptor.getName());
233                                                        field.setAccessible(true);
234                                                        extensionCriteria.eq(fieldDescriptor.getName(), field.get(bo));
235                                                } catch (Exception e) {
236                                                        LOG.error(e.getMessage(), e);
237                                                }
238                                        }
239                                        try {
240                                                boe = (PersistableBusinessObjectExtension) new QueryByCriteria(entityManager, extensionCriteria).toQuery().getSingleResult();
241                                        } catch (PersistenceException e) {}
242                                        bo.setExtension(boe);
243                                }
244                        }
245                        // populate Person objects in business objects
246                        List bos = new ArrayList();
247                        bos.addAll(searchResults);
248                        searchResults = bos;
249                } catch (DataIntegrityViolationException e) {
250                        throw new RuntimeException("LookupDao encountered exception during executeSearch", e);
251                }
252                return new CollectionIncomplete(searchResults, matchingResultsCount);
253        }
254
255        /**
256         * Return whether or not an attribute is writeable. This method is aware
257         * that that Collections may be involved and handles them consistently with
258         * the way in which OJB handles specifying the attributes of elements of a
259         * Collection.
260         * 
261         * @param o
262         * @param p
263         * @return
264         * @throws IllegalArgumentException
265         */
266        private boolean isWriteable(Object o, String p) throws IllegalArgumentException {
267                if (null == o || null == p) {
268                        throw new IllegalArgumentException("Cannot check writeable status with null arguments.");
269                }
270
271                boolean b = false;
272
273                // Try the easy way.
274                if (!(PropertyUtils.isWriteable(o, p))) {
275
276                        // If that fails lets try to be a bit smarter, understanding that
277                        // Collections may be involved.
278                        if (-1 != p.indexOf('.')) {
279
280                                String[] parts = p.split("\\.");
281
282                                // Get the type of the attribute.
283                                Class c = ObjectUtils.getPropertyType(o, parts[0], persistenceStructureService);
284
285                                Object i = null;
286
287                                // If the next level is a Collection, look into the collection,
288                                // to find out what type its elements are.
289                                if (Collection.class.isAssignableFrom(c)) {
290                                        Map<String, Class> m = persistenceStructureService.listCollectionObjectTypes(o.getClass());
291                                        c = m.get(parts[0]);
292                                }
293
294                                // Look into the attribute class to see if it is writeable.
295                                try {
296                                        i = c.newInstance();
297                                        StringBuffer sb = new StringBuffer();
298                                        for (int x = 1; x < parts.length; x++) {
299                                                sb.append(1 == x ? "" : ".").append(parts[x]);
300                                        }
301                                        b = isWriteable(i, sb.toString());
302                                } catch (InstantiationException ie) {
303                                        LOG.info(ie);
304                                } catch (IllegalAccessException iae) {
305                                        LOG.info(iae);
306                                }
307                        }
308                } else {
309                        b = true;
310                }
311
312                return b;
313        }
314
315        public boolean createCriteria(Object example, String searchValue, String propertyName, Object criteria) {
316                return createCriteria(example, searchValue, propertyName, false, false, criteria);
317        }
318        
319    public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria) {
320        return createCriteria( example, searchValue, propertyName, caseInsensitive, treatWildcardsAndOperatorsAsLiteral, criteria, null );
321    }
322
323        public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Object criteria, Map searchValues) {
324                // if searchValue is empty and the key is not a valid property ignore
325                if (!(criteria instanceof Criteria) || StringUtils.isBlank(searchValue) || !isWriteable(example, propertyName)) {
326                        return false;
327                }
328
329                // get property type which is used to determine type of criteria
330                Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
331                if (propertyType == null) {
332                        return false;
333                }
334
335                // build criteria
336                if (example instanceof InactivatableFromTo) {
337                        if (KRADPropertyConstants.ACTIVE.equals(propertyName)) {
338                                addInactivateableFromToActiveCriteria(example, searchValue, (Criteria) criteria, searchValues);
339                        } else if (KRADPropertyConstants.CURRENT.equals(propertyName)) {
340                                addInactivateableFromToCurrentCriteria(example, searchValue, (Criteria) criteria, searchValues);
341                        } else if (!KRADPropertyConstants.ACTIVE_AS_OF_DATE.equals(propertyName)) {
342                                addCriteria(propertyName, searchValue, propertyType, caseInsensitive,
343                                                treatWildcardsAndOperatorsAsLiteral, (Criteria) criteria);
344                        }
345                } else {
346                        addCriteria(propertyName, searchValue, propertyType, caseInsensitive, treatWildcardsAndOperatorsAsLiteral,
347                                        (Criteria) criteria);
348                }
349                
350                return true;
351        }
352
353        /**
354         * @see org.kuali.rice.krad.dao.LookupDao#findObjectByMap(java.lang.Object,
355         *      java.util.Map)
356         */
357        public Object findObjectByMap(Object example, Map formProps) {
358                Criteria jpaCriteria = new Criteria(example.getClass().getName());
359
360                Iterator propsIter = formProps.keySet().iterator();
361                while (propsIter.hasNext()) {
362                        String propertyName = (String) propsIter.next();
363                        String searchValue = "";
364                        if (formProps.get(propertyName) != null) {
365                                searchValue = (formProps.get(propertyName)).toString();
366                        }
367
368                        if (StringUtils.isNotBlank(searchValue) & PropertyUtils.isWriteable(example, propertyName)) {
369                                Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
370                                if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType)) {
371                                        if (propertyType.equals(Long.class)) {
372                                                jpaCriteria.eq(propertyName, new Long(searchValue));
373                                        } else {
374                                                jpaCriteria.eq(propertyName, new Integer(searchValue));
375                                        }
376                                } else if (TypeUtils.isTemporalClass(propertyType)) {
377                                        jpaCriteria.eq(propertyName, parseDate(ObjectUtils.clean(searchValue)));
378                                } else {
379                                        jpaCriteria.eq(propertyName, searchValue);
380                                }
381                        }
382                }
383
384                return new QueryByCriteria(entityManager, jpaCriteria).toQuery().getSingleResult();
385        }
386
387        private void addCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
388                String alias = "";
389                String[] keySplit = propertyName.split("\\.");
390                if (keySplit.length > 1) {
391                        alias = keySplit[keySplit.length-2];
392                        String variableKey = keySplit[keySplit.length-1];
393                        for (int j = 0; j < keySplit.length - 1; j++)  {
394                                if (StringUtils.contains(keySplit[j], Criteria.JPA_ALIAS_PREFIX)) {
395                                        String tempKey = keySplit[j].substring(keySplit[j].indexOf('\'', keySplit[j].indexOf(Criteria.JPA_ALIAS_PREFIX)) + 1,
396                                                        keySplit[j].lastIndexOf('\'', keySplit[j].indexOf(Criteria.JPA_ALIAS_SUFFIX)));
397                                        if (criteria.getAliasIndex(tempKey) == -1) {
398                                                criteria.join(tempKey, tempKey, false, true);
399                                        }
400                                } else {
401                                        if (criteria.getAliasIndex(keySplit[j]) == -1) {
402                                                criteria.join(keySplit[j], keySplit[j], false, true);
403                                        }
404                                }
405                        }
406                        if (!StringUtils.contains(propertyName, "__JPA_ALIAS[[")) {
407                                propertyName = "__JPA_ALIAS[['" + alias + "']]__." + variableKey;
408                        }
409                }
410                if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.OR.op())) {
411                        addOrCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
412                        return;
413                }
414
415                if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.AND.op())) {
416                        addAndCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
417                        return;
418                }
419
420        if (StringUtils.containsIgnoreCase(propertyValue, SearchOperator.NULL.op())) {
421                if (StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
422                        criteria.notNull(propertyName);
423                }
424                else {
425                        criteria.isNull(propertyName);
426                }
427        }
428        else if (TypeUtils.isStringClass(propertyType)) {
429                        // KULRICE-85 : made string searches case insensitive - used new
430                        // DBPlatform function to force strings to upper case
431                        if (caseInsensitive) {
432                                // TODO: What to do here now that the JPA version does not extend platform aware?
433                                //propertyName = getDbPlatform().getUpperCaseFunction() + "(__JPA_ALIAS[[0]]__." + propertyName + ")";
434                                if (StringUtils.contains(propertyName, "__JPA_ALIAS[[")) {
435                                        propertyName = "UPPER(" + propertyName + ")";
436                                } else {
437                                        propertyName = "UPPER(__JPA_ALIAS[[0]]__." + propertyName + ")";
438                                }
439                                propertyValue = propertyValue.toUpperCase();
440                        }
441                        if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue,
442                                        SearchOperator.NOT.op())) {
443                                addNotCriteria(propertyName, propertyValue, propertyType,
444                                                caseInsensitive, criteria);
445            } else if (
446                        !treatWildcardsAndOperatorsAsLiteral && propertyValue != null && (
447                                        StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())
448                                        || propertyValue.startsWith(">")
449                                        || propertyValue.startsWith("<") ) ) {
450                                addStringRangeCriteria(propertyName, propertyValue, criteria);
451                        } else {
452                                if (treatWildcardsAndOperatorsAsLiteral) {
453                                        propertyValue = StringUtils.replace(propertyValue, "*", "\\*");
454                                }
455                                criteria.like(propertyName, propertyValue);
456                        }
457                } else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType)) {
458                        addNumericRangeCriteria(propertyName, propertyValue, criteria);
459                } else if (TypeUtils.isTemporalClass(propertyType)) {
460                        addDateRangeCriteria(propertyName, propertyValue, criteria);
461                } else if (TypeUtils.isBooleanClass(propertyType)) {
462                        String temp = ObjectUtils.clean(propertyValue);
463                        criteria.eq(propertyName, ("Y".equalsIgnoreCase(temp) || "T".equalsIgnoreCase(temp) || "1".equalsIgnoreCase(temp) || "true".equalsIgnoreCase(temp)) ? true : false);
464                } else {
465                        LOG.error("not adding criterion for: " + propertyName + "," + propertyType + "," + propertyValue);
466                }
467        }
468        
469    
470    /**
471     * Translates criteria for active status to criteria on the active from and to fields
472     * 
473     * @param example - business object being queried on
474     * @param activeSearchValue - value for the active search field, should convert to boolean
475     * @param criteria - Criteria object being built
476     * @param searchValues - Map containing all search keys and values
477     */
478    protected void addInactivateableFromToActiveCriteria(Object example, String activeSearchValue, Criteria criteria, Map searchValues) {
479        Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
480                
481        String activeBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(activeSearchValue);
482        if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
483                // (active from date <= date or active from date is null) and (date < active to date or active to date is null)
484                Criteria criteriaBeginDate = new Criteria(example.getClass().getName());
485                criteriaBeginDate.lte(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
486                
487                Criteria criteriaBeginDateNull = new Criteria(example.getClass().getName());
488                criteriaBeginDateNull.isNull(KRADPropertyConstants.ACTIVE_FROM_DATE);
489                criteriaBeginDate.or(criteriaBeginDateNull);
490                
491                criteria.and(criteriaBeginDate);
492                
493                Criteria criteriaEndDate = new Criteria(example.getClass().getName());
494                criteriaEndDate.gt(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
495        
496                Criteria criteriaEndDateNull = new Criteria(example.getClass().getName());
497                criteriaEndDateNull.isNull(KRADPropertyConstants.ACTIVE_TO_DATE);
498                criteriaEndDate.or(criteriaEndDateNull);
499                
500                criteria.and(criteriaEndDate);
501        }
502        else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
503                // (date < active from date) or (active from date is null) or (date >= active to date) 
504                Criteria criteriaNonActive = new Criteria(example.getClass().getName());
505                criteriaNonActive.gt(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);
506                
507                Criteria criteriaBeginDateNull = new Criteria(example.getClass().getName());
508                criteriaBeginDateNull.isNull(KRADPropertyConstants.ACTIVE_FROM_DATE);
509                criteriaNonActive.or(criteriaBeginDateNull);
510                
511                Criteria criteriaEndDate = new Criteria(example.getClass().getName());
512                criteriaEndDate.lte(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
513                criteriaNonActive.or(criteriaEndDate);
514                
515                criteria.and(criteriaNonActive);
516        }
517    }
518    
519    /**
520     * Translates criteria for current status to a sub-query on active begin date
521     * 
522     * @param example - business object being queried on
523     * @param currentSearchValue - value for the current search field, should convert to boolean
524     * @param criteria - Criteria object being built
525     */
526        protected void addInactivateableFromToCurrentCriteria(Object example, String currentSearchValue, Criteria criteria, Map searchValues) {
527                Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);
528                
529                List<String> groupByFieldList = dataDictionaryService.getGroupByAttributesForEffectiveDating(example
530                                .getClass());
531                if (groupByFieldList == null) {
532                        return;
533                }
534
535                String alias = "c";
536
537                String jpql = " (select max(" + alias + "." + KRADPropertyConstants.ACTIVE_FROM_DATE + ") from "
538                                + example.getClass().getName() + " as " + alias + " where ";
539                String activeDateDBStr = KRADServiceLocatorInternal.getDatabasePlatform().getDateSQL(dateTimeService.toDateTimeString(activeTimestamp), null);
540                jpql += alias + "." + KRADPropertyConstants.ACTIVE_FROM_DATE + " <= '" + activeDateDBStr + "'";
541
542                // join back to main query with the group by fields
543                boolean firstGroupBy = true;
544                String groupByJpql = "";
545                for (String groupByField : groupByFieldList) {
546                        if (!firstGroupBy) {
547                                groupByJpql += ", ";
548                        }
549
550                        jpql += " AND " + alias + "." + groupByField + " = " + criteria.getAlias() + "." + groupByField + " ";
551                        groupByJpql += alias + "." + groupByField;
552                        firstGroupBy = false;
553                }
554
555                jpql += " group by " + groupByJpql + " )";
556
557                String currentBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(currentSearchValue);
558                if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
559                        jpql = criteria.getAlias() + "." + KRADPropertyConstants.ACTIVE_FROM_DATE + " in " + jpql;
560                } else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
561                        jpql = criteria.getAlias() + "." + KRADPropertyConstants.ACTIVE_FROM_DATE + " not in " + jpql;
562                }
563
564                criteria.rawJpql(jpql);
565        }
566
567        private void addOrCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
568                addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.OR.op());
569        }
570
571        private void addAndCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
572                addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria, SearchOperator.AND.op());
573        }
574
575        private void addNotCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria) {
576                String[] splitPropVal = StringUtils.split(propertyValue, SearchOperator.NOT.op());
577
578                int strLength = splitPropVal.length;
579                // if more than one NOT operator assume an implicit and (i.e. !a!b = !a&!b)
580                if (strLength > 1) {
581                        String expandedNot = "!" + StringUtils.join(splitPropVal, SearchOperator.AND.op() + SearchOperator.NOT.op());
582                        // we know that since this method is called, treatWildcardsAndOperatorsAsLiteral is false
583                        addCriteria(propertyName, expandedNot, propertyType, caseInsensitive, false, criteria);
584                } else {
585                        // only one so add a not like
586                        criteria.notLike(propertyName, splitPropVal[0]);
587                }
588        }
589
590        private void addLogicalOperatorCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive, Criteria criteria, String splitValue) {
591                String[] splitPropVal = StringUtils.split(propertyValue, splitValue);
592
593                Criteria subCriteria = new Criteria("N/A");
594                for (int i = 0; i < splitPropVal.length; i++) {
595                        Criteria predicate = new Criteria("N/A");
596                        // we know that since this method is called, treatWildcardsAndOperatorsAsLiteral is false
597                        addCriteria(propertyName, splitPropVal[i], propertyType, caseInsensitive, false, predicate);
598                        if (splitValue == SearchOperator.OR.op()) {
599                                subCriteria.or(predicate);
600                        }
601                        if (splitValue == SearchOperator.AND.op()) {
602                                subCriteria.and(predicate);
603                        }
604                }
605
606                criteria.and(subCriteria);
607        }
608
609        private java.sql.Date parseDate(String dateString) {
610                dateString = dateString.trim();
611                try {
612                        return dateTimeService.convertToSqlDate(dateString);
613                } catch (ParseException ex) {
614                        return null;
615                }
616        }
617
618        private void addDateRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {
619
620                if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
621                        String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
622                        criteria.between(propertyName, parseDate(ObjectUtils.clean(rangeValues[0])), parseDate(ObjectUtils.clean(rangeValues[1])));
623                } else if (propertyValue.startsWith(">=")) {
624                        criteria.gte(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
625                } else if (propertyValue.startsWith("<=")) {
626                        criteria.lte(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
627                } else if (propertyValue.startsWith(">")) {
628                        criteria.gt(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
629                } else if (propertyValue.startsWith("<")) {
630                        criteria.lt(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
631                } else {
632                        criteria.eq(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
633                }
634        }
635
636        private BigDecimal cleanNumeric(String value) {
637                String cleanedValue = value.replaceAll("[^-0-9.]", "");
638                // ensure only one "minus" at the beginning, if any
639                if (cleanedValue.lastIndexOf('-') > 0) {
640                        if (cleanedValue.charAt(0) == '-') {
641                                cleanedValue = "-" + cleanedValue.replaceAll("-", "");
642                        } else {
643                                cleanedValue = cleanedValue.replaceAll("-", "");
644                        }
645                }
646                // ensure only one decimal in the string
647                int decimalLoc = cleanedValue.lastIndexOf('.');
648                if (cleanedValue.indexOf('.') != decimalLoc) {
649                        cleanedValue = cleanedValue.substring(0, decimalLoc).replaceAll("\\.", "") + cleanedValue.substring(decimalLoc);
650                }
651                try {
652                        return new BigDecimal(cleanedValue);
653                } catch (NumberFormatException ex) {
654                        GlobalVariables.getMessageMap().putError(KRADConstants.DOCUMENT_ERRORS, RiceKeyConstants.ERROR_CUSTOM, new String[] { "Invalid Numeric Input: " + value });
655                        return null;
656                }
657        }
658
659        private void addNumericRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {
660
661                if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
662                        String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
663                        criteria.between(propertyName, cleanNumeric(rangeValues[0]), cleanNumeric(rangeValues[1]));
664                } else if (propertyValue.startsWith(">=")) {
665                        criteria.gte(propertyName, cleanNumeric(propertyValue));
666                } else if (propertyValue.startsWith("<=")) {
667                        criteria.lte(propertyName, cleanNumeric(propertyValue));
668                } else if (propertyValue.startsWith(">")) {
669                        criteria.gt(propertyName, cleanNumeric(propertyValue));
670                } else if (propertyValue.startsWith("<")) {
671                        criteria.lt(propertyName, cleanNumeric(propertyValue));
672                } else {
673                        criteria.eq(propertyName, cleanNumeric(propertyValue));
674                }
675        }
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.between(propertyName, rangeValues[0], rangeValues[1]);
682                } else if (propertyValue.startsWith(">=")) {
683                        criteria.gte(propertyName, ObjectUtils.clean(propertyValue));
684                } else if (propertyValue.startsWith("<=")) {
685                        criteria.lte(propertyName, ObjectUtils.clean(propertyValue));
686                } else if (propertyValue.startsWith(">")) {
687                        criteria.gt(propertyName, ObjectUtils.clean(propertyValue));
688                } else if (propertyValue.startsWith("<")) {
689                        criteria.lt(propertyName, ObjectUtils.clean(propertyValue));
690                }
691        }
692
693        /**
694         * @param dateTimeService
695         *            the dateTimeService to set
696         */
697        public void setDateTimeService(DateTimeService dateTimeService) {
698                this.dateTimeService = dateTimeService;
699        }
700
701    /**
702     * @return the entityManager
703     */
704    public EntityManager getEntityManager() {
705        return this.entityManager;
706    }
707
708    /**
709     * @param entityManager the entityManager to set
710     */
711    public void setEntityManager(EntityManager entityManager) {
712        this.entityManager = entityManager;
713    }
714    
715        public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
716                this.persistenceStructureService = persistenceStructureService;
717        }
718
719        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
720        this.dataDictionaryService = dataDictionaryService;
721    }
722        
723}