org.kuali.rice.krad.dao.impl.LookupDaoJpa.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.rice.krad.dao.impl.LookupDaoJpa.java

Source

/**
 * Copyright 2005-2013 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.rice.krad.dao.impl;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.search.SearchOperator;
import org.kuali.rice.core.api.util.RiceKeyConstants;
import org.kuali.rice.core.api.util.type.TypeUtils;
import org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria;
import org.kuali.rice.core.framework.persistence.jpa.criteria.QueryByCriteria;
import org.kuali.rice.core.framework.persistence.jpa.metadata.EntityDescriptor;
import org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor;
import org.kuali.rice.core.framework.persistence.jpa.metadata.MetadataManager;
import org.kuali.rice.core.framework.persistence.ojb.conversion.OjbCharBooleanConversion;
import org.kuali.rice.kns.service.KNSServiceLocator;
import org.kuali.rice.krad.bo.InactivatableFromTo;
import org.kuali.rice.krad.bo.PersistableBusinessObject;
import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
import org.kuali.rice.krad.dao.LookupDao;
import org.kuali.rice.krad.lookup.CollectionIncomplete;
import org.kuali.rice.krad.lookup.LookupUtils;
import org.kuali.rice.krad.service.DataDictionaryService;
import org.kuali.rice.krad.service.KRADServiceLocator;
import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
import org.kuali.rice.krad.service.PersistenceStructureService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.KRADConstants;
import org.kuali.rice.krad.util.KRADPropertyConstants;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.dao.DataIntegrityViolationException;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * JPA implementation of the LookupDao interface
 *
 * @author Kuali Rice Team (rice.collab@kuali.org)
 */
public class LookupDaoJpa implements LookupDao {
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LookupDao.class);

    private DateTimeService dateTimeService;
    private PersistenceStructureService persistenceStructureService;
    private DataDictionaryService dataDictionaryService;

    @PersistenceContext
    private EntityManager entityManager;

    public Long findCountByMap(Object example, Map formProps) {
        Criteria criteria = new Criteria(example.getClass().getName());

        // iterate through the parameter map for key values search criteria
        Iterator propsIter = formProps.keySet().iterator();
        while (propsIter.hasNext()) {
            String propertyName = (String) propsIter.next();
            String searchValue = (String) formProps.get(propertyName);

            // if searchValue is empty and the key is not a valid property ignore
            if (StringUtils.isBlank(searchValue) || !(PropertyUtils.isWriteable(example, propertyName))) {
                continue;
            }

            // get property type which is used to determine type of criteria
            Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
            if (propertyType == null) {
                continue;
            }
            Boolean caseInsensitive = Boolean.TRUE;
            if (KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined(example.getClass(),
                    propertyName)) {
                // 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 
                // 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 
                // could be mixed case.  Thus, caseInsensitive will be the opposite of forceUppercase. 
                caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService()
                        .getAttributeForceUppercase(example.getClass(), propertyName);
            }
            if (caseInsensitive == null) {
                caseInsensitive = Boolean.TRUE;
            }

            boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.getBusinessObjectDictionaryService()
                    .isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
            // build criteria
            if (!caseInsensitive) {
                // Verify that the searchValue is uppercased if caseInsensitive is false 
                searchValue = searchValue.toUpperCase();
            }
            addCriteria(propertyName, searchValue, propertyType, caseInsensitive,
                    treatWildcardsAndOperatorsAsLiteral, criteria);
        }

        // execute query and return result list
        return (Long) new QueryByCriteria(entityManager, criteria).toCountQuery().getSingleResult();
    }

    /**
     * Since 2.3
     * This version of findCollectionBySearchHelper is needed for version compatibility.   It allows executeSearch
     * to behave the same way as it did prior to 2.3. The value for searchResultsLimit will be retrieved from the
     * KNS version of LookupUtils.
     *
     * @see org.kuali.rice.krad.dao.LookupDao#findCollectionBySearchHelper(java.lang.Class, java.util.Map, boolean,
     *      boolean)
     */
    public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded,
            boolean usePrimaryKeyValuesOnly) {
        Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils
                .getSearchResultsLimit(businessObjectClass);
        return findCollectionBySearchHelper(businessObjectClass, formProps, unbounded, usePrimaryKeyValuesOnly,
                searchResultsLimit);
    }

    /**
     * @see org.kuali.rice.krad.dao.LookupDao#findCollectionBySearchHelper(java.lang.Class, java.util.Map, boolean,
     *      boolean, Integer)
     *
     * If searchResultsLimit is null, the search results will not be limited by any other means.
     */
    public Collection findCollectionBySearchHelper(Class businessObjectClass, Map formProps, boolean unbounded,
            boolean usePrimaryKeyValuesOnly, Integer searchResultsLimit) {
        PersistableBusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
        if (usePrimaryKeyValuesOnly) {
            return executeSearch(businessObjectClass,
                    getCollectionCriteriaFromMapUsingPrimaryKeysOnly(businessObjectClass, formProps), unbounded,
                    searchResultsLimit);
        } else {
            Criteria crit = getCollectionCriteriaFromMap(businessObject, formProps);
            return executeSearch(businessObjectClass, crit, unbounded, searchResultsLimit);
        }
    }

    public Criteria getCollectionCriteriaFromMap(PersistableBusinessObject example, Map formProps) {
        Criteria criteria = new Criteria(example.getClass().getName());
        Iterator propsIter = formProps.keySet().iterator();
        while (propsIter.hasNext()) {
            String propertyName = (String) propsIter.next();
            Boolean caseInsensitive = Boolean.TRUE;
            if (KRADServiceLocatorWeb.getDataDictionaryService().isAttributeDefined(example.getClass(),
                    propertyName)) {
                caseInsensitive = !KRADServiceLocatorWeb.getDataDictionaryService()
                        .getAttributeForceUppercase(example.getClass(), propertyName);
            }
            if (caseInsensitive == null) {
                caseInsensitive = Boolean.TRUE;
            }
            boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.getBusinessObjectDictionaryService()
                    .isLookupFieldTreatWildcardsAndOperatorsAsLiteral(example.getClass(), propertyName);
            if (formProps.get(propertyName) instanceof Collection) {
                Iterator iter = ((Collection) formProps.get(propertyName)).iterator();
                while (iter.hasNext()) {
                    String searchValue = (String) iter.next();
                    if (!caseInsensitive) {
                        // Verify that the searchValue is uppercased if caseInsensitive is false 
                        searchValue = searchValue.toUpperCase();
                    }
                    if (!createCriteria(example, searchValue, propertyName, caseInsensitive,
                            treatWildcardsAndOperatorsAsLiteral, criteria, formProps)) {
                        throw new RuntimeException("Invalid value in Collection");
                    }
                }
            } else {
                String searchValue = (String) formProps.get(propertyName);
                if (!caseInsensitive) {
                    // Verify that the searchValue is uppercased if caseInsensitive is false 
                    searchValue = searchValue.toUpperCase();
                }
                if (!createCriteria(example, searchValue, propertyName, caseInsensitive,
                        treatWildcardsAndOperatorsAsLiteral, criteria, formProps)) {
                    continue;
                }
            }
        }
        return criteria;
    }

    public Criteria getCollectionCriteriaFromMapUsingPrimaryKeysOnly(Class businessObjectClass, Map formProps) {
        PersistableBusinessObject businessObject = checkBusinessObjectClass(businessObjectClass);
        Criteria criteria = new Criteria(businessObjectClass.getName());
        List pkFields = persistenceStructureService.listPrimaryKeyFieldNames(businessObjectClass);
        Iterator pkIter = pkFields.iterator();
        while (pkIter.hasNext()) {
            String pkFieldName = (String) pkIter.next();
            String pkValue = (String) formProps.get(pkFieldName);

            if (StringUtils.isBlank(pkValue)) {
                throw new RuntimeException("Missing pk value for field " + pkFieldName
                        + " when a search based on PK values only is performed.");
            } else {
                for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) {
                    if (pkValue.contains(op.op())) {
                        throw new RuntimeException("Value \"" + pkValue + "\" for PK field " + pkFieldName
                                + " contains wildcard/operator characters.");
                    }
                }
            }
            boolean treatWildcardsAndOperatorsAsLiteral = KNSServiceLocator.getBusinessObjectDictionaryService()
                    .isLookupFieldTreatWildcardsAndOperatorsAsLiteral(businessObjectClass, pkFieldName);
            createCriteria(businessObject, pkValue, pkFieldName, false, treatWildcardsAndOperatorsAsLiteral,
                    criteria);
        }
        return criteria;
    }

    private PersistableBusinessObject checkBusinessObjectClass(Class businessObjectClass) {
        if (businessObjectClass == null) {
            throw new IllegalArgumentException(
                    "BusinessObject class passed to LookupDao findCollectionBySearchHelper... method was null");
        }
        PersistableBusinessObject businessObject = null;
        try {
            businessObject = (PersistableBusinessObject) businessObjectClass.newInstance();
        } catch (IllegalAccessException e) {
            throw new RuntimeException("LookupDao could not get instance of " + businessObjectClass.getName(), e);
        } catch (InstantiationException e) {
            throw new RuntimeException("LookupDao could not get instance of " + businessObjectClass.getName(), e);
        }
        return businessObject;
    }

    private Collection executeSearch(Class businessObjectClass, Criteria criteria, boolean unbounded,
            Integer searchResultsLimit) {
        Collection<PersistableBusinessObject> searchResults = new ArrayList<PersistableBusinessObject>();
        Long matchingResultsCount = null;
        try {
            if (!unbounded && (searchResultsLimit != null)) {
                matchingResultsCount = (Long) new QueryByCriteria(entityManager, criteria).toCountQuery()
                        .getSingleResult();
                searchResults = new QueryByCriteria(entityManager, criteria).toQuery()
                        .setMaxResults(searchResultsLimit).getResultList();
            } else {
                searchResults = new QueryByCriteria(entityManager, criteria).toQuery().getResultList();
            }
            if ((matchingResultsCount == null)
                    || (matchingResultsCount.intValue() <= searchResultsLimit.intValue())) {
                matchingResultsCount = new Long(0);
            }
            // Temp solution for loading extension objects - need to find a
            // better way
            // Should look for a JOIN query, for the above query, that will grab
            // the PBOEs as well (1+n query problem)
            for (PersistableBusinessObject bo : searchResults) {
                if (bo.getExtension() != null) {
                    PersistableBusinessObjectExtension boe = bo.getExtension();
                    EntityDescriptor entity = MetadataManager.getEntityDescriptor(bo.getExtension().getClass());
                    Criteria extensionCriteria = new Criteria(boe.getClass().getName());
                    for (FieldDescriptor fieldDescriptor : entity.getPrimaryKeys()) {
                        try {
                            Field field = bo.getClass().getDeclaredField(fieldDescriptor.getName());
                            field.setAccessible(true);
                            extensionCriteria.eq(fieldDescriptor.getName(), field.get(bo));
                        } catch (Exception e) {
                            LOG.error(e.getMessage(), e);
                        }
                    }
                    try {
                        boe = (PersistableBusinessObjectExtension) new QueryByCriteria(entityManager,
                                extensionCriteria).toQuery().getSingleResult();
                    } catch (PersistenceException e) {
                    }
                    bo.setExtension(boe);
                }
            }
            // populate Person objects in business objects
            List bos = new ArrayList();
            bos.addAll(searchResults);
            searchResults = bos;
        } catch (DataIntegrityViolationException e) {
            throw new RuntimeException("LookupDao encountered exception during executeSearch", e);
        }
        return new CollectionIncomplete(searchResults, matchingResultsCount);
    }

    /**
     * Return whether or not an attribute is writeable. This method is aware
     * that that Collections may be involved and handles them consistently with
     * the way in which OJB handles specifying the attributes of elements of a
     * Collection.
     * 
     * @param o
     * @param p
     * @return
     * @throws IllegalArgumentException
     */
    private boolean isWriteable(Object o, String p) throws IllegalArgumentException {
        if (null == o || null == p) {
            throw new IllegalArgumentException("Cannot check writeable status with null arguments.");
        }

        boolean b = false;

        // Try the easy way.
        if (!(PropertyUtils.isWriteable(o, p))) {

            // If that fails lets try to be a bit smarter, understanding that
            // Collections may be involved.
            if (-1 != p.indexOf('.')) {

                String[] parts = p.split("\\.");

                // Get the type of the attribute.
                Class c = ObjectUtils.getPropertyType(o, parts[0], persistenceStructureService);

                Object i = null;

                // If the next level is a Collection, look into the collection,
                // to find out what type its elements are.
                if (Collection.class.isAssignableFrom(c)) {
                    Map<String, Class> m = persistenceStructureService.listCollectionObjectTypes(o.getClass());
                    c = m.get(parts[0]);
                }

                // Look into the attribute class to see if it is writeable.
                try {
                    i = c.newInstance();
                    StringBuffer sb = new StringBuffer();
                    for (int x = 1; x < parts.length; x++) {
                        sb.append(1 == x ? "" : ".").append(parts[x]);
                    }
                    b = isWriteable(i, sb.toString());
                } catch (InstantiationException ie) {
                    LOG.info(ie);
                } catch (IllegalAccessException iae) {
                    LOG.info(iae);
                }
            }
        } else {
            b = true;
        }

        return b;
    }

    public boolean createCriteria(Object example, String searchValue, String propertyName, Object criteria) {
        return createCriteria(example, searchValue, propertyName, false, false, criteria);
    }

    public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive,
            boolean treatWildcardsAndOperatorsAsLiteral, Object criteria) {
        return createCriteria(example, searchValue, propertyName, caseInsensitive,
                treatWildcardsAndOperatorsAsLiteral, criteria, null);
    }

    public boolean createCriteria(Object example, String searchValue, String propertyName, boolean caseInsensitive,
            boolean treatWildcardsAndOperatorsAsLiteral, Object criteria, Map searchValues) {
        // if searchValue is empty and the key is not a valid property ignore
        if (!(criteria instanceof Criteria) || StringUtils.isBlank(searchValue)
                || !isWriteable(example, propertyName)) {
            return false;
        }

        // get property type which is used to determine type of criteria
        Class propertyType = ObjectUtils.getPropertyType(example, propertyName, persistenceStructureService);
        if (propertyType == null) {
            return false;
        }

        // build criteria
        if (example instanceof InactivatableFromTo) {
            if (KRADPropertyConstants.ACTIVE.equals(propertyName)) {
                addInactivateableFromToActiveCriteria(example, searchValue, (Criteria) criteria, searchValues);
            } else if (KRADPropertyConstants.CURRENT.equals(propertyName)) {
                addInactivateableFromToCurrentCriteria(example, searchValue, (Criteria) criteria, searchValues);
            } else if (!KRADPropertyConstants.ACTIVE_AS_OF_DATE.equals(propertyName)) {
                addCriteria(propertyName, searchValue, propertyType, caseInsensitive,
                        treatWildcardsAndOperatorsAsLiteral, (Criteria) criteria);
            }
        } else {
            addCriteria(propertyName, searchValue, propertyType, caseInsensitive,
                    treatWildcardsAndOperatorsAsLiteral, (Criteria) criteria);
        }

        return true;
    }

    /**
     * @see org.kuali.rice.krad.dao.LookupDao#findObjectByMap(java.lang.Object,
     *      java.util.Map)
     */
    public Object findObjectByMap(Object example, Map formProps) {
        Criteria jpaCriteria = new Criteria(example.getClass().getName());

        Iterator propsIter = formProps.keySet().iterator();
        while (propsIter.hasNext()) {
            String propertyName = (String) propsIter.next();
            String searchValue = "";
            if (formProps.get(propertyName) != null) {
                searchValue = (formProps.get(propertyName)).toString();
            }

            if (StringUtils.isNotBlank(searchValue) & PropertyUtils.isWriteable(example, propertyName)) {
                Class propertyType = ObjectUtils.getPropertyType(example, propertyName,
                        persistenceStructureService);
                if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType)) {
                    if (propertyType.equals(Long.class)) {
                        jpaCriteria.eq(propertyName, new Long(searchValue));
                    } else {
                        jpaCriteria.eq(propertyName, new Integer(searchValue));
                    }
                } else if (TypeUtils.isTemporalClass(propertyType)) {
                    jpaCriteria.eq(propertyName, parseDate(ObjectUtils.clean(searchValue)));
                } else {
                    jpaCriteria.eq(propertyName, searchValue);
                }
            }
        }

        return new QueryByCriteria(entityManager, jpaCriteria).toQuery().getSingleResult();
    }

    private void addCriteria(String propertyName, String propertyValue, Class propertyType, boolean caseInsensitive,
            boolean treatWildcardsAndOperatorsAsLiteral, Criteria criteria) {
        String alias = "";
        String[] keySplit = propertyName.split("\\.");
        if (keySplit.length > 1) {
            alias = keySplit[keySplit.length - 2];
            String variableKey = keySplit[keySplit.length - 1];
            for (int j = 0; j < keySplit.length - 1; j++) {
                if (StringUtils.contains(keySplit[j], Criteria.JPA_ALIAS_PREFIX)) {
                    String tempKey = keySplit[j].substring(
                            keySplit[j].indexOf('\'', keySplit[j].indexOf(Criteria.JPA_ALIAS_PREFIX)) + 1,
                            keySplit[j].lastIndexOf('\'', keySplit[j].indexOf(Criteria.JPA_ALIAS_SUFFIX)));
                    if (criteria.getAliasIndex(tempKey) == -1) {
                        criteria.join(tempKey, tempKey, false, true);
                    }
                } else {
                    if (criteria.getAliasIndex(keySplit[j]) == -1) {
                        criteria.join(keySplit[j], keySplit[j], false, true);
                    }
                }
            }
            if (!StringUtils.contains(propertyName, "__JPA_ALIAS[[")) {
                propertyName = "__JPA_ALIAS[['" + alias + "']]__." + variableKey;
            }
        }
        if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.OR.op())) {
            addOrCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
            return;
        }

        if (!treatWildcardsAndOperatorsAsLiteral && StringUtils.contains(propertyValue, SearchOperator.AND.op())) {
            addAndCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
            return;
        }

        if (StringUtils.containsIgnoreCase(propertyValue, SearchOperator.NULL.op())) {
            if (StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
                criteria.notNull(propertyName);
            } else {
                criteria.isNull(propertyName);
            }
        } else if (TypeUtils.isStringClass(propertyType)) {
            // KULRICE-85 : made string searches case insensitive - used new
            // DBPlatform function to force strings to upper case
            if (caseInsensitive) {
                // TODO: What to do here now that the JPA version does not extend platform aware?
                //propertyName = getDbPlatform().getUpperCaseFunction() + "(__JPA_ALIAS[[0]]__." + propertyName + ")";
                if (StringUtils.contains(propertyName, "__JPA_ALIAS[[")) {
                    propertyName = "UPPER(" + propertyName + ")";
                } else {
                    propertyName = "UPPER(__JPA_ALIAS[[0]]__." + propertyName + ")";
                }
                propertyValue = propertyValue.toUpperCase();
            }
            if (!treatWildcardsAndOperatorsAsLiteral
                    && StringUtils.contains(propertyValue, SearchOperator.NOT.op())) {
                addNotCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria);
            } else if (!treatWildcardsAndOperatorsAsLiteral && propertyValue != null
                    && (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())
                            || propertyValue.startsWith(">") || propertyValue.startsWith("<"))) {
                addStringRangeCriteria(propertyName, propertyValue, criteria);
            } else {
                if (treatWildcardsAndOperatorsAsLiteral) {
                    propertyValue = StringUtils.replace(propertyValue, "*", "\\*");
                }
                criteria.like(propertyName, propertyValue);
            }
        } else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType)) {
            addNumericRangeCriteria(propertyName, propertyValue, criteria);
        } else if (TypeUtils.isTemporalClass(propertyType)) {
            addDateRangeCriteria(propertyName, propertyValue, criteria);
        } else if (TypeUtils.isBooleanClass(propertyType)) {
            String temp = ObjectUtils.clean(propertyValue);
            criteria.eq(propertyName, ("Y".equalsIgnoreCase(temp) || "T".equalsIgnoreCase(temp)
                    || "1".equalsIgnoreCase(temp) || "true".equalsIgnoreCase(temp)) ? true : false);
        } else {
            LOG.error("not adding criterion for: " + propertyName + "," + propertyType + "," + propertyValue);
        }
    }

    /**
     * Translates criteria for active status to criteria on the active from and to fields
     * 
     * @param example - business object being queried on
     * @param activeSearchValue - value for the active search field, should convert to boolean
     * @param criteria - Criteria object being built
     * @param searchValues - Map containing all search keys and values
     */
    protected void addInactivateableFromToActiveCriteria(Object example, String activeSearchValue,
            Criteria criteria, Map searchValues) {
        Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);

        String activeBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(activeSearchValue);
        if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
            // (active from date <= date or active from date is null) and (date < active to date or active to date is null)
            Criteria criteriaBeginDate = new Criteria(example.getClass().getName());
            criteriaBeginDate.lte(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);

            Criteria criteriaBeginDateNull = new Criteria(example.getClass().getName());
            criteriaBeginDateNull.isNull(KRADPropertyConstants.ACTIVE_FROM_DATE);
            criteriaBeginDate.or(criteriaBeginDateNull);

            criteria.and(criteriaBeginDate);

            Criteria criteriaEndDate = new Criteria(example.getClass().getName());
            criteriaEndDate.gt(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);

            Criteria criteriaEndDateNull = new Criteria(example.getClass().getName());
            criteriaEndDateNull.isNull(KRADPropertyConstants.ACTIVE_TO_DATE);
            criteriaEndDate.or(criteriaEndDateNull);

            criteria.and(criteriaEndDate);
        } else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION.equals(activeBooleanStr)) {
            // (date < active from date) or (active from date is null) or (date >= active to date) 
            Criteria criteriaNonActive = new Criteria(example.getClass().getName());
            criteriaNonActive.gt(KRADPropertyConstants.ACTIVE_FROM_DATE, activeTimestamp);

            Criteria criteriaBeginDateNull = new Criteria(example.getClass().getName());
            criteriaBeginDateNull.isNull(KRADPropertyConstants.ACTIVE_FROM_DATE);
            criteriaNonActive.or(criteriaBeginDateNull);

            Criteria criteriaEndDate = new Criteria(example.getClass().getName());
            criteriaEndDate.lte(KRADPropertyConstants.ACTIVE_TO_DATE, activeTimestamp);
            criteriaNonActive.or(criteriaEndDate);

            criteria.and(criteriaNonActive);
        }
    }

    /**
     * Translates criteria for current status to a sub-query on active begin date
     * 
     * @param example - business object being queried on
     * @param currentSearchValue - value for the current search field, should convert to boolean
     * @param criteria - Criteria object being built
     */
    protected void addInactivateableFromToCurrentCriteria(Object example, String currentSearchValue,
            Criteria criteria, Map searchValues) {
        Timestamp activeTimestamp = LookupUtils.getActiveDateTimestampForCriteria(searchValues);

        List<String> groupByFieldList = dataDictionaryService
                .getGroupByAttributesForEffectiveDating(example.getClass());
        if (groupByFieldList == null) {
            return;
        }

        String alias = "c";

        String jpql = " (select max(" + alias + "." + KRADPropertyConstants.ACTIVE_FROM_DATE + ") from "
                + example.getClass().getName() + " as " + alias + " where ";
        String activeDateDBStr = KRADServiceLocator.getDatabasePlatform()
                .getDateSQL(dateTimeService.toDateTimeString(activeTimestamp), null);
        jpql += alias + "." + KRADPropertyConstants.ACTIVE_FROM_DATE + " <= '" + activeDateDBStr + "'";

        // join back to main query with the group by fields
        boolean firstGroupBy = true;
        String groupByJpql = "";
        for (String groupByField : groupByFieldList) {
            if (!firstGroupBy) {
                groupByJpql += ", ";
            }

            jpql += " AND " + alias + "." + groupByField + " = " + criteria.getAlias() + "." + groupByField + " ";
            groupByJpql += alias + "." + groupByField;
            firstGroupBy = false;
        }

        jpql += " group by " + groupByJpql + " )";

        String currentBooleanStr = (String) (new OjbCharBooleanConversion()).javaToSql(currentSearchValue);
        if (OjbCharBooleanConversion.DATABASE_BOOLEAN_TRUE_STRING_REPRESENTATION.equals(currentBooleanStr)) {
            jpql = criteria.getAlias() + "." + KRADPropertyConstants.ACTIVE_FROM_DATE + " in " + jpql;
        } else if (OjbCharBooleanConversion.DATABASE_BOOLEAN_FALSE_STRING_REPRESENTATION
                .equals(currentBooleanStr)) {
            jpql = criteria.getAlias() + "." + KRADPropertyConstants.ACTIVE_FROM_DATE + " not in " + jpql;
        }

        criteria.rawJpql(jpql);
    }

    private void addOrCriteria(String propertyName, String propertyValue, Class propertyType,
            boolean caseInsensitive, Criteria criteria) {
        addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria,
                SearchOperator.OR.op());
    }

    private void addAndCriteria(String propertyName, String propertyValue, Class propertyType,
            boolean caseInsensitive, Criteria criteria) {
        addLogicalOperatorCriteria(propertyName, propertyValue, propertyType, caseInsensitive, criteria,
                SearchOperator.AND.op());
    }

    private void addNotCriteria(String propertyName, String propertyValue, Class propertyType,
            boolean caseInsensitive, Criteria criteria) {
        String[] splitPropVal = StringUtils.split(propertyValue, SearchOperator.NOT.op());

        int strLength = splitPropVal.length;
        // if more than one NOT operator assume an implicit and (i.e. !a!b = !a&!b)
        if (strLength > 1) {
            String expandedNot = "!"
                    + StringUtils.join(splitPropVal, SearchOperator.AND.op() + SearchOperator.NOT.op());
            // we know that since this method is called, treatWildcardsAndOperatorsAsLiteral is false
            addCriteria(propertyName, expandedNot, propertyType, caseInsensitive, false, criteria);
        } else {
            // only one so add a not like
            criteria.notLike(propertyName, splitPropVal[0]);
        }
    }

    private void addLogicalOperatorCriteria(String propertyName, String propertyValue, Class propertyType,
            boolean caseInsensitive, Criteria criteria, String splitValue) {
        String[] splitPropVal = StringUtils.split(propertyValue, splitValue);

        Criteria subCriteria = new Criteria("N/A");
        for (int i = 0; i < splitPropVal.length; i++) {
            Criteria predicate = new Criteria("N/A");
            // we know that since this method is called, treatWildcardsAndOperatorsAsLiteral is false
            addCriteria(propertyName, splitPropVal[i], propertyType, caseInsensitive, false, predicate);
            if (splitValue == SearchOperator.OR.op()) {
                subCriteria.or(predicate);
            }
            if (splitValue == SearchOperator.AND.op()) {
                subCriteria.and(predicate);
            }
        }

        criteria.and(subCriteria);
    }

    private java.sql.Date parseDate(String dateString) {
        dateString = dateString.trim();
        try {
            return dateTimeService.convertToSqlDate(dateString);
        } catch (ParseException ex) {
            return null;
        }
    }

    private void addDateRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {

        if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
            String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
            criteria.between(propertyName, parseDate(ObjectUtils.clean(rangeValues[0])),
                    parseDate(ObjectUtils.clean(rangeValues[1])));
        } else if (propertyValue.startsWith(">=")) {
            criteria.gte(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
        } else if (propertyValue.startsWith("<=")) {
            criteria.lte(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
        } else if (propertyValue.startsWith(">")) {
            criteria.gt(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
        } else if (propertyValue.startsWith("<")) {
            criteria.lt(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
        } else {
            criteria.eq(propertyName, parseDate(ObjectUtils.clean(propertyValue)));
        }
    }

    private BigDecimal cleanNumeric(String value) {
        String cleanedValue = value.replaceAll("[^-0-9.]", "");
        // ensure only one "minus" at the beginning, if any
        if (cleanedValue.lastIndexOf('-') > 0) {
            if (cleanedValue.charAt(0) == '-') {
                cleanedValue = "-" + cleanedValue.replaceAll("-", "");
            } else {
                cleanedValue = cleanedValue.replaceAll("-", "");
            }
        }
        // ensure only one decimal in the string
        int decimalLoc = cleanedValue.lastIndexOf('.');
        if (cleanedValue.indexOf('.') != decimalLoc) {
            cleanedValue = cleanedValue.substring(0, decimalLoc).replaceAll("\\.", "")
                    + cleanedValue.substring(decimalLoc);
        }
        try {
            return new BigDecimal(cleanedValue);
        } catch (NumberFormatException ex) {
            GlobalVariables.getMessageMap().putError(KRADConstants.DOCUMENT_ERRORS, RiceKeyConstants.ERROR_CUSTOM,
                    new String[] { "Invalid Numeric Input: " + value });
            return null;
        }
    }

    private void addNumericRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {

        if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
            String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
            criteria.between(propertyName, cleanNumeric(rangeValues[0]), cleanNumeric(rangeValues[1]));
        } else if (propertyValue.startsWith(">=")) {
            criteria.gte(propertyName, cleanNumeric(propertyValue));
        } else if (propertyValue.startsWith("<=")) {
            criteria.lte(propertyName, cleanNumeric(propertyValue));
        } else if (propertyValue.startsWith(">")) {
            criteria.gt(propertyName, cleanNumeric(propertyValue));
        } else if (propertyValue.startsWith("<")) {
            criteria.lt(propertyName, cleanNumeric(propertyValue));
        } else {
            criteria.eq(propertyName, cleanNumeric(propertyValue));
        }
    }

    private void addStringRangeCriteria(String propertyName, String propertyValue, Criteria criteria) {

        if (StringUtils.contains(propertyValue, SearchOperator.BETWEEN.op())) {
            String[] rangeValues = StringUtils.split(propertyValue, SearchOperator.BETWEEN.op());
            criteria.between(propertyName, rangeValues[0], rangeValues[1]);
        } else if (propertyValue.startsWith(">=")) {
            criteria.gte(propertyName, ObjectUtils.clean(propertyValue));
        } else if (propertyValue.startsWith("<=")) {
            criteria.lte(propertyName, ObjectUtils.clean(propertyValue));
        } else if (propertyValue.startsWith(">")) {
            criteria.gt(propertyName, ObjectUtils.clean(propertyValue));
        } else if (propertyValue.startsWith("<")) {
            criteria.lt(propertyName, ObjectUtils.clean(propertyValue));
        }
    }

    /**
     * @param dateTimeService
     *            the dateTimeService to set
     */
    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    /**
     * @return the entityManager
     */
    public EntityManager getEntityManager() {
        return this.entityManager;
    }

    /**
     * @param entityManager the entityManager to set
     */
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
        this.persistenceStructureService = persistenceStructureService;
    }

    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
        this.dataDictionaryService = dataDictionaryService;
    }

}