org.finra.herd.dao.impl.BusinessObjectDataDaoImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.finra.herd.dao.impl.BusinessObjectDataDaoImpl.java

Source

/*
 * Copyright 2015 herd contributors
 *
 * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
 *
 * 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.finra.herd.dao.impl;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import javax.persistence.metamodel.SingularAttribute;

import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import org.finra.herd.core.HerdDateUtils;
import org.finra.herd.dao.BusinessObjectDataDao;
import org.finra.herd.dao.BusinessObjectFormatDao;
import org.finra.herd.dao.FileTypeDao;
import org.finra.herd.dao.NamespaceDao;
import org.finra.herd.model.api.xml.Attribute;
import org.finra.herd.model.api.xml.AttributeValueFilter;
import org.finra.herd.model.api.xml.BusinessObjectData;
import org.finra.herd.model.api.xml.BusinessObjectDataKey;
import org.finra.herd.model.api.xml.BusinessObjectDataSearchKey;
import org.finra.herd.model.api.xml.BusinessObjectDefinitionKey;
import org.finra.herd.model.api.xml.BusinessObjectFormatKey;
import org.finra.herd.model.api.xml.PartitionValueFilter;
import org.finra.herd.model.api.xml.PartitionValueRange;
import org.finra.herd.model.api.xml.RegistrationDateRangeFilter;
import org.finra.herd.model.dto.StoragePolicyPriorityLevel;
import org.finra.herd.model.jpa.BusinessObjectDataAttributeEntity;
import org.finra.herd.model.jpa.BusinessObjectDataAttributeEntity_;
import org.finra.herd.model.jpa.BusinessObjectDataEntity;
import org.finra.herd.model.jpa.BusinessObjectDataEntity_;
import org.finra.herd.model.jpa.BusinessObjectDataStatusEntity;
import org.finra.herd.model.jpa.BusinessObjectDataStatusEntity_;
import org.finra.herd.model.jpa.BusinessObjectDefinitionEntity;
import org.finra.herd.model.jpa.BusinessObjectDefinitionEntity_;
import org.finra.herd.model.jpa.BusinessObjectFormatEntity;
import org.finra.herd.model.jpa.BusinessObjectFormatEntity_;
import org.finra.herd.model.jpa.FileTypeEntity;
import org.finra.herd.model.jpa.FileTypeEntity_;
import org.finra.herd.model.jpa.NamespaceEntity;
import org.finra.herd.model.jpa.NamespaceEntity_;
import org.finra.herd.model.jpa.RetentionTypeEntity;
import org.finra.herd.model.jpa.SchemaColumnEntity;
import org.finra.herd.model.jpa.SchemaColumnEntity_;
import org.finra.herd.model.jpa.StorageEntity;
import org.finra.herd.model.jpa.StorageEntity_;
import org.finra.herd.model.jpa.StoragePlatformEntity;
import org.finra.herd.model.jpa.StoragePolicyEntity;
import org.finra.herd.model.jpa.StoragePolicyEntity_;
import org.finra.herd.model.jpa.StoragePolicyStatusEntity;
import org.finra.herd.model.jpa.StoragePolicyTransitionTypeEntity;
import org.finra.herd.model.jpa.StorageUnitEntity;
import org.finra.herd.model.jpa.StorageUnitEntity_;
import org.finra.herd.model.jpa.StorageUnitStatusEntity;
import org.finra.herd.model.jpa.StorageUnitStatusEntity_;

@Repository
public class BusinessObjectDataDaoImpl extends AbstractHerdDao implements BusinessObjectDataDao {
    @Autowired
    private BusinessObjectFormatDao businessObjectFormatDao;

    @Autowired
    private FileTypeDao fileTypeDao;

    @Autowired
    private NamespaceDao namespaceDao;

    @Override
    public BusinessObjectDataEntity getBusinessObjectDataByAltKey(BusinessObjectDataKey businessObjectDataKey) {
        return getBusinessObjectDataByAltKeyAndStatus(businessObjectDataKey, null);
    }

    @Override
    public BusinessObjectDataEntity getBusinessObjectDataByAltKeyAndStatus(
            BusinessObjectDataKey businessObjectDataKey, String businessObjectDataStatus) {
        // Create the criteria builder and the criteria.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<BusinessObjectDataEntity> criteria = builder.createQuery(BusinessObjectDataEntity.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> businessObjectDataEntity = criteria.from(BusinessObjectDataEntity.class);

        // Join to other tables that we need to filter on.
        Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> businessObjectFormatEntity = businessObjectDataEntity
                .join(BusinessObjectDataEntity_.businessObjectFormat);
        Join<BusinessObjectFormatEntity, FileTypeEntity> fileTypeEntity = businessObjectFormatEntity
                .join(BusinessObjectFormatEntity_.fileType);
        Join<BusinessObjectFormatEntity, BusinessObjectDefinitionEntity> businessObjectDefinitionEntity = businessObjectFormatEntity
                .join(BusinessObjectFormatEntity_.businessObjectDefinition);

        // Create the standard restrictions (i.e. the standard where clauses).
        Predicate mainQueryRestriction = getQueryRestriction(builder, businessObjectDataEntity,
                businessObjectFormatEntity, fileTypeEntity, businessObjectDefinitionEntity, businessObjectDataKey);

        // If a format version was specified, use the latest available for this partition value.
        if (businessObjectDataKey.getBusinessObjectFormatVersion() == null) {
            // Business object format version is not specified, so just use the latest available for this set of partition values.
            Subquery<Integer> subQuery = criteria.subquery(Integer.class);

            // The criteria root is the business object data.
            Root<BusinessObjectDataEntity> subBusinessObjectDataEntity = subQuery
                    .from(BusinessObjectDataEntity.class);

            // Join to the other tables we can filter on.
            Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> subBusinessObjectFormatEntity = subBusinessObjectDataEntity
                    .join(BusinessObjectDataEntity_.businessObjectFormat);
            Join<BusinessObjectFormatEntity, BusinessObjectDefinitionEntity> subBusinessObjectDefinitionEntity = subBusinessObjectFormatEntity
                    .join(BusinessObjectFormatEntity_.businessObjectDefinition);
            Join<BusinessObjectFormatEntity, FileTypeEntity> subBusinessObjectFormatFileTypeEntity = subBusinessObjectFormatEntity
                    .join(BusinessObjectFormatEntity_.fileType);
            Join<BusinessObjectDataEntity, BusinessObjectDataStatusEntity> subBusinessObjectDataStatusEntity = subBusinessObjectDataEntity
                    .join(BusinessObjectDataEntity_.status);

            // Create the standard restrictions (i.e. the standard where clauses).
            Predicate subQueryRestriction = builder.equal(subBusinessObjectDefinitionEntity,
                    businessObjectDefinitionEntity);
            subQueryRestriction = builder.and(subQueryRestriction,
                    builder.equal(subBusinessObjectFormatEntity.get(BusinessObjectFormatEntity_.usage),
                            businessObjectFormatEntity.get(BusinessObjectFormatEntity_.usage)));
            subQueryRestriction = builder.and(subQueryRestriction,
                    builder.equal(subBusinessObjectFormatFileTypeEntity, fileTypeEntity));

            // Create and add standard restrictions on primary and sub-partition values.
            subQueryRestriction = builder.and(subQueryRestriction, getQueryRestrictionOnPartitionValues(builder,
                    subBusinessObjectDataEntity, businessObjectDataEntity));

            // Add restrictions on business object data version and business object data status.
            Predicate subQueryRestrictionOnBusinessObjectDataVersionAndStatus = getQueryRestrictionOnBusinessObjectDataVersionAndStatus(
                    builder, subBusinessObjectDataEntity, subBusinessObjectDataStatusEntity,
                    businessObjectDataKey.getBusinessObjectDataVersion(), businessObjectDataStatus);
            if (subQueryRestrictionOnBusinessObjectDataVersionAndStatus != null) {
                subQueryRestriction = builder.and(subQueryRestriction,
                        subQueryRestrictionOnBusinessObjectDataVersionAndStatus);
            }

            subQuery.select(builder.max(
                    subBusinessObjectFormatEntity.get(BusinessObjectFormatEntity_.businessObjectFormatVersion)))
                    .where(subQueryRestriction);

            mainQueryRestriction = builder.and(mainQueryRestriction,
                    builder.in(
                            businessObjectFormatEntity.get(BusinessObjectFormatEntity_.businessObjectFormatVersion))
                            .value(subQuery));
        }

        // If a data version was not specified, use the latest one as per specified business object data status.
        if (businessObjectDataKey.getBusinessObjectDataVersion() == null) {
            // Since business object data version is not specified, just use the latest one as per specified business object data status.
            if (businessObjectDataStatus != null) {
                // Business object data version is not specified, so get the latest one as per specified business object data status.
                Subquery<Integer> subQuery = criteria.subquery(Integer.class);

                // The criteria root is the business object data.
                Root<BusinessObjectDataEntity> subBusinessObjectDataEntity = subQuery
                        .from(BusinessObjectDataEntity.class);

                // Join to the other tables we can filter on.
                Join<BusinessObjectDataEntity, BusinessObjectDataStatusEntity> subBusinessObjectDataStatusEntity = subBusinessObjectDataEntity
                        .join(BusinessObjectDataEntity_.status);
                Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> subBusinessObjectFormatEntity = subBusinessObjectDataEntity
                        .join(BusinessObjectDataEntity_.businessObjectFormat);

                // Create the standard restrictions (i.e. the standard where clauses).
                Predicate subQueryRestriction = builder.equal(subBusinessObjectFormatEntity,
                        businessObjectFormatEntity);

                // Create and add standard restrictions on primary and sub-partition values.
                subQueryRestriction = builder.and(subQueryRestriction, getQueryRestrictionOnPartitionValues(builder,
                        subBusinessObjectDataEntity, businessObjectDataEntity));

                // Create and add standard restrictions on business object data status.
                subQueryRestriction = builder.and(subQueryRestriction, builder.equal(
                        builder.upper(subBusinessObjectDataStatusEntity.get(BusinessObjectDataStatusEntity_.code)),
                        businessObjectDataStatus.toUpperCase()));

                subQuery.select(builder.max(subBusinessObjectDataEntity.get(BusinessObjectDataEntity_.version)))
                        .where(subQueryRestriction);

                mainQueryRestriction = builder.and(mainQueryRestriction, builder
                        .in(businessObjectDataEntity.get(BusinessObjectDataEntity_.version)).value(subQuery));
            } else {
                // Both business object data version and business object data status are not specified, so just use the latest business object data version.
                mainQueryRestriction = builder.and(mainQueryRestriction,
                        builder.equal(businessObjectDataEntity.get(BusinessObjectDataEntity_.latestVersion), true));
            }
        }

        criteria.select(businessObjectDataEntity).where(mainQueryRestriction);

        return executeSingleResultQuery(criteria, String.format(
                "Found more than one business object data instance with parameters {namespace=\"%s\", businessObjectDefinitionName=\"%s\","
                        + " businessObjectFormatUsage=\"%s\", businessObjectFormatFileType=\"%s\", businessObjectFormatVersion=\"%d\","
                        + " businessObjectDataPartitionValue=\"%s\", businessObjectDataSubPartitionValues=\"%s\", businessObjectDataVersion=\"%d\","
                        + " businessObjectDataStatus=\"%s\"}.",
                businessObjectDataKey.getNamespace(), businessObjectDataKey.getBusinessObjectDefinitionName(),
                businessObjectDataKey.getBusinessObjectFormatUsage(),
                businessObjectDataKey.getBusinessObjectFormatFileType(),
                businessObjectDataKey.getBusinessObjectFormatVersion(), businessObjectDataKey.getPartitionValue(),
                CollectionUtils.isEmpty(businessObjectDataKey.getSubPartitionValues()) ? ""
                        : StringUtils.join(businessObjectDataKey.getSubPartitionValues(), ","),
                businessObjectDataKey.getBusinessObjectDataVersion(), businessObjectDataStatus));
    }

    @Override
    public Integer getBusinessObjectDataMaxVersion(BusinessObjectDataKey businessObjectDataKey) {
        // Create the criteria builder and the criteria.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Integer> criteria = builder.createQuery(Integer.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> businessObjectDataEntity = criteria.from(BusinessObjectDataEntity.class);

        // Join to the other tables we can filter on.
        Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> businessObjectFormatEntity = businessObjectDataEntity
                .join(BusinessObjectDataEntity_.businessObjectFormat);
        Join<BusinessObjectFormatEntity, FileTypeEntity> fileTypeEntity = businessObjectFormatEntity
                .join(BusinessObjectFormatEntity_.fileType);
        Join<BusinessObjectFormatEntity, BusinessObjectDefinitionEntity> businessObjectDefinitionEntity = businessObjectFormatEntity
                .join(BusinessObjectFormatEntity_.businessObjectDefinition);
        Join<BusinessObjectDefinitionEntity, NamespaceEntity> namespaceEntity = businessObjectDefinitionEntity
                .join(BusinessObjectDefinitionEntity_.namespace);

        // Create the path.
        Expression<Integer> maxBusinessObjectDataVersion = builder
                .max(businessObjectDataEntity.get(BusinessObjectDataEntity_.version));

        // Create the standard restrictions (i.e. the standard where clauses).
        Predicate queryRestriction = builder.equal(builder.upper(namespaceEntity.get(NamespaceEntity_.code)),
                businessObjectDataKey.getNamespace().toUpperCase());
        queryRestriction = builder.and(queryRestriction,
                builder.equal(
                        builder.upper(businessObjectDefinitionEntity.get(BusinessObjectDefinitionEntity_.name)),
                        businessObjectDataKey.getBusinessObjectDefinitionName().toUpperCase()));
        queryRestriction = builder.and(queryRestriction,
                builder.equal(builder.upper(businessObjectFormatEntity.get(BusinessObjectFormatEntity_.usage)),
                        businessObjectDataKey.getBusinessObjectFormatUsage().toUpperCase()));
        queryRestriction = builder.and(queryRestriction,
                builder.equal(builder.upper(fileTypeEntity.get(FileTypeEntity_.code)),
                        businessObjectDataKey.getBusinessObjectFormatFileType().toUpperCase()));
        queryRestriction = builder.and(queryRestriction,
                builder.equal(
                        businessObjectFormatEntity.get(BusinessObjectFormatEntity_.businessObjectFormatVersion),
                        businessObjectDataKey.getBusinessObjectFormatVersion()));
        queryRestriction = builder.and(queryRestriction,
                builder.equal(businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue),
                        businessObjectDataKey.getPartitionValue()));

        for (int i = 0; i < BusinessObjectDataEntity.MAX_SUBPARTITIONS; i++) {
            queryRestriction = builder.and(queryRestriction,
                    i < businessObjectDataKey.getSubPartitionValues().size()
                            ? builder.equal(businessObjectDataEntity.get(BUSINESS_OBJECT_DATA_SUBPARTITIONS.get(i)),
                                    businessObjectDataKey.getSubPartitionValues().get(i))
                            : builder.isNull(
                                    businessObjectDataEntity.get(BUSINESS_OBJECT_DATA_SUBPARTITIONS.get(i))));
        }

        criteria.select(maxBusinessObjectDataVersion).where(queryRestriction);

        return entityManager.createQuery(criteria).getSingleResult();
    }

    @Override
    public String getBusinessObjectDataMaxPartitionValue(int partitionColumnPosition,
            BusinessObjectFormatKey businessObjectFormatKey, Integer businessObjectDataVersion,
            String businessObjectDataStatus, List<String> storageNames, String storagePlatformType,
            String excludedStoragePlatformType, String upperBoundPartitionValue, String lowerBoundPartitionValue) {
        return getBusinessObjectDataPartitionValue(partitionColumnPosition, businessObjectFormatKey,
                businessObjectDataVersion, businessObjectDataStatus, storageNames, storagePlatformType,
                excludedStoragePlatformType, AggregateFunction.GREATEST, upperBoundPartitionValue,
                lowerBoundPartitionValue);
    }

    @Override
    public String getBusinessObjectDataMinPartitionValue(int partitionColumnPosition,
            BusinessObjectFormatKey businessObjectFormatKey, Integer businessObjectDataVersion,
            String businessObjectDataStatus, List<String> storageNames, String storagePlatformType,
            String excludedStoragePlatformType) {
        return getBusinessObjectDataPartitionValue(partitionColumnPosition, businessObjectFormatKey,
                businessObjectDataVersion, businessObjectDataStatus, storageNames, storagePlatformType,
                excludedStoragePlatformType, AggregateFunction.LEAST, null, null);
    }

    @Override
    public Long getBusinessObjectDataCount(BusinessObjectFormatKey businessObjectFormatKey) {
        // Create the criteria builder and the criteria.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Long> criteria = builder.createQuery(Long.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> businessObjectDataEntity = criteria.from(BusinessObjectDataEntity.class);

        // Join to the other tables we can filter on.
        Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> businessObjectFormatEntity = businessObjectDataEntity
                .join(BusinessObjectDataEntity_.businessObjectFormat);
        Join<BusinessObjectFormatEntity, FileTypeEntity> fileTypeEntity = businessObjectFormatEntity
                .join(BusinessObjectFormatEntity_.fileType);
        Join<BusinessObjectFormatEntity, BusinessObjectDefinitionEntity> businessObjectDefinitionEntity = businessObjectFormatEntity
                .join(BusinessObjectFormatEntity_.businessObjectDefinition);
        Join<BusinessObjectDefinitionEntity, NamespaceEntity> namespaceEntity = businessObjectDefinitionEntity
                .join(BusinessObjectDefinitionEntity_.namespace);

        // Create path.
        Expression<Long> businessObjectDataCount = builder
                .count(businessObjectDataEntity.get(BusinessObjectDataEntity_.id));

        // Create the standard restrictions (i.e. the standard where clauses).
        Predicate queryRestriction = builder.equal(builder.upper(namespaceEntity.get(NamespaceEntity_.code)),
                businessObjectFormatKey.getNamespace().toUpperCase());
        queryRestriction = builder.and(queryRestriction,
                builder.equal(
                        builder.upper(businessObjectDefinitionEntity.get(BusinessObjectDefinitionEntity_.name)),
                        businessObjectFormatKey.getBusinessObjectDefinitionName().toUpperCase()));
        queryRestriction = builder.and(queryRestriction,
                builder.equal(builder.upper(businessObjectFormatEntity.get(BusinessObjectFormatEntity_.usage)),
                        businessObjectFormatKey.getBusinessObjectFormatUsage().toUpperCase()));
        queryRestriction = builder.and(queryRestriction,
                builder.equal(builder.upper(fileTypeEntity.get(FileTypeEntity_.code)),
                        businessObjectFormatKey.getBusinessObjectFormatFileType().toUpperCase()));
        queryRestriction = builder.and(queryRestriction,
                builder.equal(
                        businessObjectFormatEntity.get(BusinessObjectFormatEntity_.businessObjectFormatVersion),
                        businessObjectFormatKey.getBusinessObjectFormatVersion()));

        criteria.select(businessObjectDataCount).where(queryRestriction);

        return entityManager.createQuery(criteria).getSingleResult();
    }

    @Override
    public List<BusinessObjectDataEntity> getBusinessObjectDataEntities(
            BusinessObjectDataKey businessObjectDataKey) {
        // Create the criteria builder and the criteria.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<BusinessObjectDataEntity> criteria = builder.createQuery(BusinessObjectDataEntity.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> businessObjectDataEntity = criteria.from(BusinessObjectDataEntity.class);

        // Join to the other tables we can filter on.
        Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> businessObjectFormatEntity = businessObjectDataEntity
                .join(BusinessObjectDataEntity_.businessObjectFormat);
        Join<BusinessObjectFormatEntity, FileTypeEntity> fileTypeEntity = businessObjectFormatEntity
                .join(BusinessObjectFormatEntity_.fileType);
        Join<BusinessObjectFormatEntity, BusinessObjectDefinitionEntity> businessObjectDefinitionEntity = businessObjectFormatEntity
                .join(BusinessObjectFormatEntity_.businessObjectDefinition);

        // Create the standard restrictions (i.e. the standard where clauses).
        Predicate queryRestriction = getQueryRestriction(builder, businessObjectDataEntity,
                businessObjectFormatEntity, fileTypeEntity, businessObjectDefinitionEntity, businessObjectDataKey);

        // Add the clauses for the query.
        criteria.select(businessObjectDataEntity).where(queryRestriction);

        // Order by business object format and data versions.
        criteria.orderBy(
                builder.asc(
                        businessObjectFormatEntity.get(BusinessObjectFormatEntity_.businessObjectFormatVersion)),
                builder.asc(businessObjectDataEntity.get(BusinessObjectDataEntity_.version)));

        return entityManager.createQuery(criteria).getResultList();
    }

    @Override
    public List<BusinessObjectDataEntity> getBusinessObjectDataEntities(
            BusinessObjectFormatKey businessObjectFormatKey, List<List<String>> partitionFilters,
            Integer businessObjectDataVersion, String businessObjectDataStatus, String storageName) {
        List<BusinessObjectDataEntity> resultBusinessObjectDataEntities = new ArrayList<>();

        // Loop through each chunk of partition filters until we have reached the end of the list.
        for (int i = 0; i < partitionFilters.size(); i += MAX_PARTITION_FILTERS_PER_REQUEST) {
            // Get a sub-list for the current chunk of partition filters.
            List<BusinessObjectDataEntity> chunkBusinessObjectDataEntities = getBusinessObjectDataEntities(
                    businessObjectFormatKey, partitionFilters, businessObjectDataVersion, businessObjectDataStatus,
                    storageName, i,
                    (i + MAX_PARTITION_FILTERS_PER_REQUEST) > partitionFilters.size() ? partitionFilters.size() - i
                            : MAX_PARTITION_FILTERS_PER_REQUEST);

            // Add the sub-list to the result.
            resultBusinessObjectDataEntities.addAll(chunkBusinessObjectDataEntities);
        }

        return resultBusinessObjectDataEntities;
    }

    @Override
    public List<BusinessObjectDataEntity> getBusinessObjectDataFromStorageOlderThan(StorageEntity storageEntity,
            int thresholdMinutes, List<String> businessObjectDataStatuses) {
        // Create the criteria builder and the criteria.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<BusinessObjectDataEntity> criteria = builder.createQuery(BusinessObjectDataEntity.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> businessObjectDataEntityRoot = criteria.from(BusinessObjectDataEntity.class);

        // Join to the other tables we can filter on.
        Join<BusinessObjectDataEntity, StorageUnitEntity> storageUnitEntityJoin = businessObjectDataEntityRoot
                .join(BusinessObjectDataEntity_.storageUnits);

        // Compute threshold timestamp based on the current database timestamp and threshold minutes.
        Timestamp thresholdTimestamp = HerdDateUtils.addMinutes(getCurrentTimestamp(), -thresholdMinutes);

        // Create the standard restrictions (i.e. the standard where clauses).
        List<Predicate> predicates = new ArrayList<>();
        predicates.add(
                builder.equal(storageUnitEntityJoin.get(StorageUnitEntity_.storageName), storageEntity.getName()));
        predicates.add(businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.statusCode)
                .in(businessObjectDataStatuses));
        predicates.add(builder.lessThanOrEqualTo(
                businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.createdOn), thresholdTimestamp));

        // Order results by "created on" timestamp.
        Order orderBy = builder.asc(businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.createdOn));

        // Add all clauses to the query.
        criteria.select(businessObjectDataEntityRoot)
                .where(builder.and(predicates.toArray(new Predicate[predicates.size()]))).orderBy(orderBy);

        // Execute the query and return the results.
        return entityManager.createQuery(criteria).getResultList();
    }

    @Override
    public Map<BusinessObjectDataEntity, StoragePolicyEntity> getBusinessObjectDataEntitiesMatchingStoragePolicies(
            StoragePolicyPriorityLevel storagePolicyPriorityLevel, List<String> supportedBusinessObjectDataStatuses,
            int storagePolicyTransitionMaxAllowedAttempts, int startPosition, int maxResult) {
        // Create the criteria builder and a tuple style criteria query.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Tuple> criteria = builder.createTupleQuery();

        // The criteria root is the business object data along with the storage policy.
        Root<BusinessObjectDataEntity> businessObjectDataEntityRoot = criteria.from(BusinessObjectDataEntity.class);
        Root<StoragePolicyEntity> storagePolicyEntityRoot = criteria.from(StoragePolicyEntity.class);

        // Join to the other tables we can filter on.
        Join<BusinessObjectDataEntity, StorageUnitEntity> storageUnitEntityJoin = businessObjectDataEntityRoot
                .join(BusinessObjectDataEntity_.storageUnits);
        Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> businessObjectFormatEntityJoin = businessObjectDataEntityRoot
                .join(BusinessObjectDataEntity_.businessObjectFormat);

        // Create main query restrictions based on the specified parameters.
        List<Predicate> predicates = new ArrayList<>();

        // Add restriction on business object definition.
        predicates.add(storagePolicyPriorityLevel.isBusinessObjectDefinitionIsNull()
                ? builder.isNull(storagePolicyEntityRoot.get(StoragePolicyEntity_.businessObjectDefinitionId))
                : builder.equal(
                        businessObjectFormatEntityJoin.get(BusinessObjectFormatEntity_.businessObjectDefinitionId),
                        storagePolicyEntityRoot.get(StoragePolicyEntity_.businessObjectDefinitionId)));

        // Add restriction on business object format usage.
        predicates.add(storagePolicyPriorityLevel.isUsageIsNull()
                ? builder.isNull(storagePolicyEntityRoot.get(StoragePolicyEntity_.usage))
                : builder.equal(
                        builder.upper(businessObjectFormatEntityJoin.get(BusinessObjectFormatEntity_.usage)),
                        builder.upper(storagePolicyEntityRoot.get(StoragePolicyEntity_.usage))));

        // Add restriction on business object format file type.
        predicates.add(storagePolicyPriorityLevel.isFileTypeIsNull()
                ? builder.isNull(storagePolicyEntityRoot.get(StoragePolicyEntity_.fileType))
                : builder.equal(businessObjectFormatEntityJoin.get(BusinessObjectFormatEntity_.fileTypeCode),
                        storagePolicyEntityRoot.get(StoragePolicyEntity_.fileTypeCode)));

        // Add restriction on storage policy filter storage.
        predicates.add(builder.equal(storageUnitEntityJoin.get(StorageUnitEntity_.storageName),
                storagePolicyEntityRoot.get(StoragePolicyEntity_.storageName)));

        // Add restriction on storage policy latest version flag.
        predicates.add(builder.isTrue(storagePolicyEntityRoot.get(StoragePolicyEntity_.latestVersion)));

        // Add restriction on storage policy status.
        predicates.add(builder.equal(storagePolicyEntityRoot.get(StoragePolicyEntity_.statusCode),
                StoragePolicyStatusEntity.ENABLED));

        // Add restriction on supported business object data statuses.
        predicates.add(businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.statusCode)
                .in(supportedBusinessObjectDataStatuses));

        // Add restrictions as per storage policy transition type.
        predicates.add(
                builder.equal(storagePolicyEntityRoot.get(StoragePolicyEntity_.storagePolicyTransitionTypeCode),
                        StoragePolicyTransitionTypeEntity.GLACIER));
        predicates.add(storageUnitEntityJoin.get(StorageUnitEntity_.statusCode)
                .in(Lists.newArrayList(StorageUnitStatusEntity.ENABLED, StorageUnitStatusEntity.ARCHIVING)));

        // If specified, add restriction on maximum allowed attempts for a storage policy transition.
        if (storagePolicyTransitionMaxAllowedAttempts > 0) {
            predicates.add(builder.or(
                    builder.isNull(
                            storageUnitEntityJoin.get(StorageUnitEntity_.storagePolicyTransitionFailedAttempts)),
                    builder.lessThan(
                            storageUnitEntityJoin.get(StorageUnitEntity_.storagePolicyTransitionFailedAttempts),
                            storagePolicyTransitionMaxAllowedAttempts)));
        }

        // Order the results by business object data "created on" value.
        Order orderByCreatedOn = builder.asc(businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.createdOn));

        // Add the select clause to the main query.
        criteria.multiselect(businessObjectDataEntityRoot, storagePolicyEntityRoot);

        // Add the where clause to the main query.
        criteria.where(predicates.toArray(new Predicate[] {}));

        // Add the order by clause to the main query.
        criteria.orderBy(orderByCreatedOn);

        // Run the query to get a list of tuples back.
        List<Tuple> tuples = entityManager.createQuery(criteria).setFirstResult(startPosition)
                .setMaxResults(maxResult).getResultList();

        // Populate the result map from the returned tuples (i.e. 1 tuple for each row).
        Map<BusinessObjectDataEntity, StoragePolicyEntity> result = new LinkedHashMap<>();
        for (Tuple tuple : tuples) {
            // Since multiple storage policies can contain identical filters, we add the below check to select each business object data instance only once.
            if (!result.containsKey(tuple.get(businessObjectDataEntityRoot))) {
                result.put(tuple.get(businessObjectDataEntityRoot), tuple.get(storagePolicyEntityRoot));
            }
        }

        return result;
    }

    /**
     * Retrieves partition value per specified parameters that includes the aggregate function.
     * <p>
     * Returns null if the business object format key does not exist.
     *
     * @param partitionColumnPosition the partition column position (1-based numbering)
     * @param businessObjectFormatKey the business object format key (case-insensitive). If a business object format version isn't specified, the latest
     * available format version for each partition value will be used.
     * @param businessObjectDataVersion the business object data version. If a business object data version isn't specified, the latest data version based on
     * the specified business object data status will be used for each partition value.
     * @param businessObjectDataStatus the business object data status. This parameter is ignored when the business object data version is specified.
     * @param storageNames the optional list of storage names (case-insensitive)
     * @param storagePlatformType the optional storage platform type, e.g. S3 for Hive DDL. It is ignored when the list of storages is not empty
     * @param excludedStoragePlatformType the optional storage platform type to be excluded from search. It is ignored when the list of storages is not empty or
     * the storage platform type is specified
     * @param aggregateFunction the aggregate function to use against partition values
     * @param upperBoundPartitionValue the optional inclusive upper bound for the maximum available partition value
     * @param lowerBoundPartitionValue the optional inclusive lower bound for the maximum available partition value
     *
     * @return the partition value
     */
    private String getBusinessObjectDataPartitionValue(int partitionColumnPosition,
            final BusinessObjectFormatKey businessObjectFormatKey, final Integer businessObjectDataVersion,
            String businessObjectDataStatus, List<String> storageNames, String storagePlatformType,
            String excludedStoragePlatformType, final AggregateFunction aggregateFunction,
            String upperBoundPartitionValue, String lowerBoundPartitionValue) {
        // We cannot use businessObjectFormatKey passed in since it is case-insensitive. Case-insensitive values requires upper() function in the SQL query, and
        // it has caused performance problems. So we need to extract case-sensitive business object format key from database so we can eliminate the upper()
        // function.
        BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDao
                .getBusinessObjectFormatByAltKey(businessObjectFormatKey);

        BusinessObjectFormatKey businessObjectFormatKeyCaseSensitive = (BusinessObjectFormatKey) businessObjectFormatKey
                .clone();
        if (businessObjectFormatEntity == null) {
            // Returns null if business object format key does not exist.
            return null;
        } else {
            // Sets the exact values for business object format key from the database. Note that usage type is still case-insensitive.
            businessObjectFormatKeyCaseSensitive.setNamespace(
                    businessObjectFormatEntity.getBusinessObjectDefinition().getNamespace().getCode());
            businessObjectFormatKeyCaseSensitive.setBusinessObjectDefinitionName(
                    businessObjectFormatEntity.getBusinessObjectDefinition().getName());
            businessObjectFormatKeyCaseSensitive
                    .setBusinessObjectFormatFileType(businessObjectFormatEntity.getFileType().getCode());
        }

        // Create the criteria builder and the criteria.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<String> criteria = builder.createQuery(String.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> businessObjectDataEntityRoot = criteria.from(BusinessObjectDataEntity.class);

        // Join to the other tables we can filter on.
        Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> businessObjectFormatEntityJoin = businessObjectDataEntityRoot
                .join(BusinessObjectDataEntity_.businessObjectFormat);
        Join<BusinessObjectFormatEntity, FileTypeEntity> fileTypeEntityJoin = businessObjectFormatEntityJoin
                .join(BusinessObjectFormatEntity_.fileType);
        Join<BusinessObjectFormatEntity, BusinessObjectDefinitionEntity> businessObjectDefinitionEntityJoin = businessObjectFormatEntityJoin
                .join(BusinessObjectFormatEntity_.businessObjectDefinition);
        Join<BusinessObjectDefinitionEntity, NamespaceEntity> namespaceEntityJoin = businessObjectDefinitionEntityJoin
                .join(BusinessObjectDefinitionEntity_.namespace);
        Join<BusinessObjectDataEntity, StorageUnitEntity> storageUnitEntityJoin = businessObjectDataEntityRoot
                .join(BusinessObjectDataEntity_.storageUnits);
        Join<StorageUnitEntity, StorageUnitStatusEntity> storageUnitStatusEntityJoin = storageUnitEntityJoin
                .join(StorageUnitEntity_.status);
        Join<StorageUnitEntity, StorageEntity> storageEntityJoin = storageUnitEntityJoin
                .join(StorageUnitEntity_.storage);
        Join<StorageEntity, StoragePlatformEntity> storagePlatformEntityJoin = storageEntityJoin
                .join(StorageEntity_.storagePlatform);

        // Create the path.
        Expression<String> partitionValue;
        SingularAttribute<BusinessObjectDataEntity, String> singleValuedAttribute = BUSINESS_OBJECT_DATA_PARTITIONS
                .get(partitionColumnPosition - 1);
        switch (aggregateFunction) {
        case GREATEST:
            partitionValue = builder.greatest(businessObjectDataEntityRoot.get(singleValuedAttribute));
            break;
        case LEAST:
            partitionValue = builder.least(businessObjectDataEntityRoot.get(singleValuedAttribute));
            break;
        default:
            throw new IllegalArgumentException("Invalid aggregate function found: \"" + aggregateFunction + "\".");
        }

        // Create the standard restrictions (i.e. the standard where clauses).
        Predicate mainQueryRestriction = builder.equal(namespaceEntityJoin.get(NamespaceEntity_.code),
                businessObjectFormatKeyCaseSensitive.getNamespace());
        mainQueryRestriction = builder.and(mainQueryRestriction,
                builder.equal(businessObjectDefinitionEntityJoin.get(BusinessObjectDefinitionEntity_.name),
                        businessObjectFormatKeyCaseSensitive.getBusinessObjectDefinitionName()));
        mainQueryRestriction = builder.and(mainQueryRestriction,
                builder.equal(builder.upper(businessObjectFormatEntityJoin.get(BusinessObjectFormatEntity_.usage)),
                        businessObjectFormatKeyCaseSensitive.getBusinessObjectFormatUsage().toUpperCase()));
        mainQueryRestriction = builder.and(mainQueryRestriction,
                builder.equal(fileTypeEntityJoin.get(FileTypeEntity_.code),
                        businessObjectFormatKeyCaseSensitive.getBusinessObjectFormatFileType()));

        // If a business object format version was specified, use it.
        if (businessObjectFormatKey.getBusinessObjectFormatVersion() != null) {
            mainQueryRestriction = builder.and(mainQueryRestriction,
                    builder.equal(
                            businessObjectFormatEntityJoin
                                    .get(BusinessObjectFormatEntity_.businessObjectFormatVersion),
                            businessObjectFormatKey.getBusinessObjectFormatVersion()));
        }

        // If a data version was specified, use it.
        if (businessObjectDataVersion != null) {
            mainQueryRestriction = builder.and(mainQueryRestriction,
                    builder.equal(businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.version),
                            businessObjectDataVersion));
        }
        // Business object data version is not specified, so get the latest one as per specified business object data status in the specified storage.
        else {
            Subquery<Integer> subQuery = getMaximumBusinessObjectDataVersionSubQuery(builder, criteria,
                    businessObjectDataEntityRoot, businessObjectFormatEntityJoin, businessObjectDataStatus,
                    storageNames, storagePlatformType, excludedStoragePlatformType, false);

            mainQueryRestriction = builder.and(mainQueryRestriction, builder
                    .in(businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.version)).value(subQuery));
        }

        // Add an inclusive upper bound partition value restriction if specified.
        if (upperBoundPartitionValue != null) {
            mainQueryRestriction = builder.and(mainQueryRestriction, builder.lessThanOrEqualTo(
                    businessObjectDataEntityRoot.get(singleValuedAttribute), upperBoundPartitionValue));
        }

        // Add an inclusive lower bound partition value restriction if specified.
        if (lowerBoundPartitionValue != null) {
            mainQueryRestriction = builder.and(mainQueryRestriction, builder.greaterThanOrEqualTo(
                    businessObjectDataEntityRoot.get(singleValuedAttribute), lowerBoundPartitionValue));
        }

        // If specified, add restriction on storage.
        mainQueryRestriction = builder.and(mainQueryRestriction,
                getQueryRestrictionOnStorage(builder, storageEntityJoin, storagePlatformEntityJoin, storageNames,
                        storagePlatformType, excludedStoragePlatformType));

        // Search across only "available" storage units.
        mainQueryRestriction = builder.and(mainQueryRestriction,
                builder.isTrue(storageUnitStatusEntityJoin.get(StorageUnitStatusEntity_.available)));

        criteria.select(partitionValue).where(mainQueryRestriction);

        return entityManager.createQuery(criteria).getSingleResult();
    }

    /**
     * Retrieves a list of business object data entities per specified parameters. This method processes a sublist of partition filters specified by
     * partitionFilterSubListFromIndex and partitionFilterSubListSize parameters.
     *
     * @param businessObjectFormatKey the business object format key (case-insensitive). If a business object format version isn't specified, the latest
     * available format version for each partition value will be used.
     * @param partitionFilters the list of partition filter to be used to select business object data instances. Each partition filter contains a list of
     * primary and sub-partition values in the right order up to the maximum partition levels allowed by business object data registration - with partition
     * values for the relative partitions not to be used for selection passed as nulls.
     * @param businessObjectDataVersion the business object data version. If a business object data version isn't specified, the latest data version based on
     * the specified business object data status is returned.
     * @param businessObjectDataStatus the business object data status. This parameter is ignored when the business object data version is specified. When
     * business object data version and business object data status both are not specified, the latest data version for each set of partition values will be
     * used regardless of the status.
     * @param storageName the name of the storage where the business object data storage unit is located (case-insensitive)
     * @param partitionFilterSubListFromIndex the index of the first element in the partition filter sublist
     * @param partitionFilterSubListSize the size of the partition filter sublist
     *
     * @return the list of business object data entities sorted by partition values
     */
    private List<BusinessObjectDataEntity> getBusinessObjectDataEntities(
            BusinessObjectFormatKey businessObjectFormatKey, List<List<String>> partitionFilters,
            Integer businessObjectDataVersion, String businessObjectDataStatus, String storageName,
            int partitionFilterSubListFromIndex, int partitionFilterSubListSize) {
        // Create the criteria builder and the criteria.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<BusinessObjectDataEntity> criteria = builder.createQuery(BusinessObjectDataEntity.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> businessObjectDataEntity = criteria.from(BusinessObjectDataEntity.class);

        // Join to the other tables we can filter on.
        Join<BusinessObjectDataEntity, StorageUnitEntity> storageUnitEntity = businessObjectDataEntity
                .join(BusinessObjectDataEntity_.storageUnits);
        Join<StorageUnitEntity, StorageEntity> storageEntity = storageUnitEntity.join(StorageUnitEntity_.storage);
        Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> businessObjectFormatEntity = businessObjectDataEntity
                .join(BusinessObjectDataEntity_.businessObjectFormat);
        Join<BusinessObjectFormatEntity, FileTypeEntity> fileTypeEntity = businessObjectFormatEntity
                .join(BusinessObjectFormatEntity_.fileType);
        Join<BusinessObjectFormatEntity, BusinessObjectDefinitionEntity> businessObjectDefinitionEntity = businessObjectFormatEntity
                .join(BusinessObjectFormatEntity_.businessObjectDefinition);

        // Create the standard restrictions (i.e. the standard where clauses).

        // Create a standard restriction based on the business object format key values.
        // Please note that we specify not to ignore the business object format version.
        Predicate mainQueryRestriction = getQueryRestriction(builder, businessObjectFormatEntity, fileTypeEntity,
                businessObjectDefinitionEntity, businessObjectFormatKey, false);

        // If a format version was not specified, use the latest available for this set of partition values.
        if (businessObjectFormatKey.getBusinessObjectFormatVersion() == null) {
            // Business object format version is not specified, so just use the latest available for this set of partition values.
            Subquery<Integer> subQuery = criteria.subquery(Integer.class);

            // The criteria root is the business object data.
            Root<BusinessObjectDataEntity> subBusinessObjectDataEntity = subQuery
                    .from(BusinessObjectDataEntity.class);

            // Join to the other tables we can filter on.
            Join<BusinessObjectDataEntity, StorageUnitEntity> subStorageUnitEntity = subBusinessObjectDataEntity
                    .join(BusinessObjectDataEntity_.storageUnits);
            Join<StorageUnitEntity, StorageEntity> subStorageEntity = subStorageUnitEntity
                    .join(StorageUnitEntity_.storage);
            Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> subBusinessObjectFormatEntity = subBusinessObjectDataEntity
                    .join(BusinessObjectDataEntity_.businessObjectFormat);
            Join<BusinessObjectFormatEntity, BusinessObjectDefinitionEntity> subBusinessObjectDefinitionEntity = subBusinessObjectFormatEntity
                    .join(BusinessObjectFormatEntity_.businessObjectDefinition);
            Join<BusinessObjectFormatEntity, FileTypeEntity> subBusinessObjectFormatFileTypeEntity = subBusinessObjectFormatEntity
                    .join(BusinessObjectFormatEntity_.fileType);
            Join<BusinessObjectDataEntity, BusinessObjectDataStatusEntity> subBusinessObjectDataStatusEntity = subBusinessObjectDataEntity
                    .join(BusinessObjectDataEntity_.status);

            // Create the standard restrictions (i.e. the standard where clauses).
            Predicate subQueryRestriction = builder.equal(subBusinessObjectDefinitionEntity,
                    businessObjectDefinitionEntity);
            subQueryRestriction = builder.and(subQueryRestriction,
                    builder.equal(subBusinessObjectFormatEntity.get(BusinessObjectFormatEntity_.usage),
                            businessObjectFormatEntity.get(BusinessObjectFormatEntity_.usage)));
            subQueryRestriction = builder.and(subQueryRestriction,
                    builder.equal(subBusinessObjectFormatFileTypeEntity, fileTypeEntity));

            // Create and add standard restrictions on primary and sub-partition values.
            subQueryRestriction = builder.and(subQueryRestriction, getQueryRestrictionOnPartitionValues(builder,
                    subBusinessObjectDataEntity, businessObjectDataEntity));

            // Add restrictions on business object data version and business object data status.
            Predicate subQueryRestrictionOnBusinessObjectDataVersionAndStatus = getQueryRestrictionOnBusinessObjectDataVersionAndStatus(
                    builder, subBusinessObjectDataEntity, subBusinessObjectDataStatusEntity,
                    businessObjectDataVersion, businessObjectDataStatus);
            if (subQueryRestrictionOnBusinessObjectDataVersionAndStatus != null) {
                subQueryRestriction = builder.and(subQueryRestriction,
                        subQueryRestrictionOnBusinessObjectDataVersionAndStatus);
            }

            // Create and add a standard restriction on storage.
            subQueryRestriction = builder.and(subQueryRestriction, builder.equal(subStorageEntity, storageEntity));

            subQuery.select(builder.max(
                    subBusinessObjectFormatEntity.get(BusinessObjectFormatEntity_.businessObjectFormatVersion)))
                    .where(subQueryRestriction);

            mainQueryRestriction = builder.and(mainQueryRestriction,
                    builder.in(
                            businessObjectFormatEntity.get(BusinessObjectFormatEntity_.businessObjectFormatVersion))
                            .value(subQuery));
        }

        // Add restriction as per specified primary and/or sub-partition values.
        mainQueryRestriction = builder.and(mainQueryRestriction,
                getQueryRestrictionOnPartitionValues(builder, businessObjectDataEntity,
                        partitionFilters.subList(partitionFilterSubListFromIndex,
                                partitionFilterSubListFromIndex + partitionFilterSubListSize)));

        // If a data version was specified, use it. Otherwise, use the latest one as per specified business object data status.
        if (businessObjectDataVersion != null) {
            mainQueryRestriction = builder.and(mainQueryRestriction, builder.equal(
                    businessObjectDataEntity.get(BusinessObjectDataEntity_.version), businessObjectDataVersion));
        } else {
            // Business object data version is not specified, so get the latest one as per specified business object data status, if any.
            // Meaning, when both business object data version and business object data status are not specified, we just return
            // the latest business object data version in the specified storage.
            Subquery<Integer> subQuery = getMaximumBusinessObjectDataVersionSubQuery(builder, criteria,
                    businessObjectDataEntity, businessObjectFormatEntity, businessObjectDataStatus,
                    Collections.singletonList(storageName), null, null, false);

            mainQueryRestriction = builder.and(mainQueryRestriction,
                    builder.in(businessObjectDataEntity.get(BusinessObjectDataEntity_.version)).value(subQuery));
        }

        // Add a storage name restriction to the main query where clause.
        mainQueryRestriction = builder.and(mainQueryRestriction,
                builder.equal(builder.upper(storageEntity.get(StorageEntity_.name)), storageName.toUpperCase()));

        // Add the clauses for the query.
        criteria.select(businessObjectDataEntity).where(mainQueryRestriction);

        // Order by partitions.
        List<Order> orderBy = new ArrayList<>();
        for (SingularAttribute<BusinessObjectDataEntity, String> businessObjectDataPartition : BUSINESS_OBJECT_DATA_PARTITIONS) {
            orderBy.add(builder.asc(businessObjectDataEntity.get(businessObjectDataPartition)));
        }
        criteria.orderBy(orderBy);

        return entityManager.createQuery(criteria).getResultList();
    }

    @Override
    public List<BusinessObjectDataEntity> getBusinessObjectDataEntitiesByPartitionValue(String partitionValue) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<BusinessObjectDataEntity> query = criteriaBuilder.createQuery(BusinessObjectDataEntity.class);

        Root<BusinessObjectDataEntity> businessObjectDataEntity = query.from(BusinessObjectDataEntity.class);
        Predicate partitionValueEquals = criteriaBuilder
                .equal(businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue), partitionValue);
        query.select(businessObjectDataEntity).where(partitionValueEquals);

        return entityManager.createQuery(query).getResultList();
    }

    /**
     * Create search restrictions.
     *
     * @param builder the criteria builder
     * @param criteria the criteria
     * @param businessObjectDataEntity the root business object data entity
     * @param businessObjectDataSearchKey the business object data search key
     * @param namespaceEntity the namespace entity
     * @param fileTypeEntity the file type entity
     * @param isCountQuery specifies if this is a count query
     *
     * @return the search restrictions
     */
    private Predicate getPredict(CriteriaBuilder builder, CriteriaQuery<?> criteria,
            Root<BusinessObjectDataEntity> businessObjectDataEntity,
            BusinessObjectDataSearchKey businessObjectDataSearchKey, NamespaceEntity namespaceEntity,
            FileTypeEntity fileTypeEntity, boolean isCountQuery) {
        // Join to the other tables we can filter on.
        Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> businessObjectFormatEntity = businessObjectDataEntity
                .join(BusinessObjectDataEntity_.businessObjectFormat);
        Join<BusinessObjectFormatEntity, BusinessObjectDefinitionEntity> businessObjectDefinitionEntity = businessObjectFormatEntity
                .join(BusinessObjectFormatEntity_.businessObjectDefinition);

        if (!isCountQuery) {
            List<Order> orderList = new ArrayList<>();
            orderList.add(
                    builder.asc(businessObjectDefinitionEntity.get(BusinessObjectDefinitionEntity_.namespace)));
            orderList.add(builder.asc(businessObjectDefinitionEntity.get(BusinessObjectDefinitionEntity_.name)));
            orderList.add(builder.asc(businessObjectFormatEntity.get(BusinessObjectFormatEntity_.usage)));
            orderList.add(builder.asc(businessObjectFormatEntity.get(BusinessObjectFormatEntity_.fileType)));
            orderList.add(builder
                    .desc(businessObjectFormatEntity.get(BusinessObjectFormatEntity_.businessObjectFormatVersion)));
            orderList.add(builder.desc(businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue)));
            orderList.add(builder.desc(businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue2)));
            orderList.add(builder.desc(businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue3)));
            orderList.add(builder.desc(businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue4)));
            orderList.add(builder.desc(businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue5)));
            orderList.add(builder.desc(businessObjectDataEntity.get(BusinessObjectDataEntity_.version)));
            criteria.orderBy(orderList);
        }

        // Create the standard restrictions based on the business object search key values (i.e. the standard where clauses).

        // Create a restriction on namespace code.
        Predicate predicate = builder.equal(
                businessObjectDefinitionEntity.get(BusinessObjectDefinitionEntity_.namespace), namespaceEntity);

        // Create and append a restriction on business object definition name.
        predicate = builder.and(predicate,
                builder.equal(
                        builder.upper(businessObjectDefinitionEntity.get(BusinessObjectDefinitionEntity_.name)),
                        businessObjectDataSearchKey.getBusinessObjectDefinitionName().toUpperCase()));

        // Create and append a restriction on business object format usage.
        if (!StringUtils.isEmpty(businessObjectDataSearchKey.getBusinessObjectFormatUsage())) {
            predicate = builder.and(predicate,
                    builder.equal(builder.upper(businessObjectFormatEntity.get(BusinessObjectFormatEntity_.usage)),
                            businessObjectDataSearchKey.getBusinessObjectFormatUsage().toUpperCase()));
        }

        // If specified, create and append a restriction on business object format file type.
        if (fileTypeEntity != null) {
            predicate = builder.and(predicate, builder
                    .equal(businessObjectFormatEntity.get(BusinessObjectFormatEntity_.fileType), fileTypeEntity));
        }

        // If specified, create and append a restriction on business object format version.
        if (businessObjectDataSearchKey.getBusinessObjectFormatVersion() != null) {
            predicate = builder.and(predicate,
                    builder.equal(
                            businessObjectFormatEntity.get(BusinessObjectFormatEntity_.businessObjectFormatVersion),
                            businessObjectDataSearchKey.getBusinessObjectFormatVersion()));
        }

        predicate = createPartitionValueFilters(businessObjectDataSearchKey, businessObjectDataEntity,
                businessObjectFormatEntity, builder, predicate);

        List<AttributeValueFilter> attributeValueFilters = businessObjectDataSearchKey.getAttributeValueFilters();

        if (attributeValueFilters != null && !attributeValueFilters.isEmpty()) {
            predicate = applyAttributeValueFilters(businessObjectDataSearchKey, businessObjectDataEntity, builder,
                    predicate);
        }

        // Apply registration date range filter, if specified.
        if (businessObjectDataSearchKey.getRegistrationDateRangeFilter() != null) {
            predicate = applyRegistrationDateRangeFilter(
                    businessObjectDataSearchKey.getRegistrationDateRangeFilter(), businessObjectDataEntity, builder,
                    predicate);
        }

        if (BooleanUtils.isTrue(businessObjectDataSearchKey.isFilterOnLatestValidVersion())) {
            String validStatus = BusinessObjectDataStatusEntity.VALID;
            Subquery<Integer> subQuery = getMaximumBusinessObjectDataVersionSubQuery(builder, criteria,
                    businessObjectDataEntity, businessObjectFormatEntity, validStatus);
            predicate = builder.and(predicate,
                    builder.in(businessObjectDataEntity.get(BusinessObjectDataEntity_.version)).value(subQuery));
        }

        if (BooleanUtils.isTrue(businessObjectDataSearchKey.isFilterOnRetentionExpiration())) {
            predicate = applyRetentionExpirationFilter(businessObjectDataSearchKey, businessObjectDataEntity,
                    businessObjectFormatEntity, builder, predicate);
        }

        return predicate;
    }

    @Override
    public Integer getBusinessObjectDataLimitedCountBySearchKey(
            BusinessObjectDataSearchKey businessObjectDataSearchKey, Integer recordCountLimit) {
        // Create the criteria builder and the criteria.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Integer> criteria = builder.createQuery(Integer.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> businessObjectDataEntityRoot = criteria.from(BusinessObjectDataEntity.class);

        // Create path. We use business object data id column here, since this is enough to check the record count.
        Path<Integer> businessObjectDataIdColumn = businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.id);

        // Namespace is a required parameter, so fetch the relative entity to optimize the main query.
        NamespaceEntity namespaceEntity = namespaceDao.getNamespaceByCd(businessObjectDataSearchKey.getNamespace());

        // If specified namespace does not exist, then return a zero record count.
        if (namespaceEntity == null) {
            return 0;
        }

        // If file type is specified, fetch the relative entity to optimize the main query.
        FileTypeEntity fileTypeEntity = null;
        if (StringUtils.isNotBlank(businessObjectDataSearchKey.getBusinessObjectFormatFileType())) {
            fileTypeEntity = fileTypeDao
                    .getFileTypeByCode(businessObjectDataSearchKey.getBusinessObjectFormatFileType());

            // If specified file type does not exist, then return a zero record count.
            if (fileTypeEntity == null) {
                return 0;
            }
        }

        // Create the standard restrictions (i.e. the standard where clauses).
        Predicate predicate;
        try {
            predicate = getPredict(builder, criteria, businessObjectDataEntityRoot, businessObjectDataSearchKey,
                    namespaceEntity, fileTypeEntity, true);
        } catch (IllegalArgumentException ex) {
            // This exception means that there are no records found for the query, thus return 0 record count.
            return 0;
        }

        // Add all clauses for the query.
        criteria.select(businessObjectDataIdColumn).where(predicate);

        // Execute the query.
        List<Integer> businessObjectDataIds = entityManager.createQuery(criteria).setMaxResults(recordCountLimit)
                .getResultList();

        // Return the size of the result list.
        return CollectionUtils.size(businessObjectDataIds);
    }

    @Override
    public List<BusinessObjectData> searchBusinessObjectData(
            BusinessObjectDataSearchKey businessObjectDataSearchKey, Integer pageNum, Integer pageSize) {
        // Create the criteria builder and the criteria.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<BusinessObjectDataEntity> criteria = builder.createQuery(BusinessObjectDataEntity.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> businessObjectDataEntityRoot = criteria.from(BusinessObjectDataEntity.class);

        // Namespace is a required parameter, so fetch the relative entity to optimize the main query.
        NamespaceEntity namespaceEntity = namespaceDao.getNamespaceByCd(businessObjectDataSearchKey.getNamespace());

        // If specified namespace does not exist, then return an empty result list.
        if (namespaceEntity == null) {
            return Collections.emptyList();
        }

        // If file type is specified, fetch the relative entity to optimize the main query.
        FileTypeEntity fileTypeEntity = null;
        if (StringUtils.isNotBlank(businessObjectDataSearchKey.getBusinessObjectFormatFileType())) {
            fileTypeEntity = fileTypeDao
                    .getFileTypeByCode(businessObjectDataSearchKey.getBusinessObjectFormatFileType());

            // If specified file type does not exist, then return an empty result list.
            if (fileTypeEntity == null) {
                return Collections.emptyList();
            }
        }

        // Create the standard restrictions (i.e. the standard where clauses).
        Predicate predicate;
        try {
            predicate = getPredict(builder, criteria, businessObjectDataEntityRoot, businessObjectDataSearchKey,
                    namespaceEntity, fileTypeEntity, false);
        } catch (IllegalArgumentException ex) {
            // This exception means that there are no records found for the query, thus return an empty result list.
            return Collections.emptyList();
        }

        // Add all clauses for the query.
        criteria.select(businessObjectDataEntityRoot).where(predicate);

        // Execute the query.
        List<BusinessObjectDataEntity> entityArray = entityManager.createQuery(criteria)
                .setFirstResult(pageSize * (pageNum - 1)).setMaxResults(pageSize).getResultList();

        // Crete the result list of business object data.
        return getQueryResultListFromEntityList(entityArray,
                businessObjectDataSearchKey.getAttributeValueFilters());
    }

    /**
     * Creates a predicate for partition value filters.
     *
     * @param businessDataSearchKey businessDataSearchKey
     * @param businessObjectDataEntity businessObjectDataEntity
     * @param businessObjectFormatEntity businessObjectFormatEntity
     * @param builder builder
     * @param predicatePram predicate parameter
     *
     * @return the predicate
     */
    private Predicate createPartitionValueFilters(BusinessObjectDataSearchKey businessDataSearchKey,
            Root<BusinessObjectDataEntity> businessObjectDataEntity,
            Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> businessObjectFormatEntity,
            CriteriaBuilder builder, Predicate predicatePram) {
        Predicate predicate = predicatePram;

        if (businessDataSearchKey.getPartitionValueFilters() != null
                && !businessDataSearchKey.getPartitionValueFilters().isEmpty()) {
            for (PartitionValueFilter partitionFilter : businessDataSearchKey.getPartitionValueFilters()) {
                Join<BusinessObjectFormatEntity, SchemaColumnEntity> schemaEntity = businessObjectFormatEntity
                        .join(BusinessObjectFormatEntity_.schemaColumns);

                List<String> partitionValues = partitionFilter.getPartitionValues();

                predicate = builder.and(predicate,
                        builder.equal(builder.upper(schemaEntity.get(SchemaColumnEntity_.name)),
                                partitionFilter.getPartitionKey().toUpperCase()));
                predicate = builder.and(predicate,
                        builder.isNotNull(schemaEntity.get(SchemaColumnEntity_.partitionLevel)));

                if (partitionValues != null && !partitionValues.isEmpty()) {
                    predicate = builder.and(predicate, builder.or(
                            builder.and(builder.equal(schemaEntity.get(SchemaColumnEntity_.partitionLevel), 1),
                                    businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue)
                                            .in(partitionValues)),
                            builder.and(builder.equal(schemaEntity.get(SchemaColumnEntity_.partitionLevel), 2),
                                    businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue2)
                                            .in(partitionValues)),
                            builder.and(builder.equal(schemaEntity.get(SchemaColumnEntity_.partitionLevel), 3),
                                    businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue3)
                                            .in(partitionValues)),
                            builder.and(builder.equal(schemaEntity.get(SchemaColumnEntity_.partitionLevel), 4),
                                    businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue4)
                                            .in(partitionValues)),
                            builder.and(builder.equal(schemaEntity.get(SchemaColumnEntity_.partitionLevel), 5),
                                    businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue5)
                                            .in(partitionValues))));
                } else if (partitionFilter.getPartitionValueRange() != null) {
                    PartitionValueRange partitionRange = partitionFilter.getPartitionValueRange();
                    String startPartitionValue = partitionRange.getStartPartitionValue();
                    String endPartitionValue = partitionRange.getEndPartitionValue();

                    predicate = builder.and(predicate, builder.or(
                            builder.and(builder.equal(schemaEntity.get(SchemaColumnEntity_.partitionLevel), 1),
                                    builder.greaterThanOrEqualTo(
                                            businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue),
                                            startPartitionValue),
                                    builder.lessThanOrEqualTo(
                                            businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue),
                                            endPartitionValue)),
                            builder.and(builder.equal(schemaEntity.get(SchemaColumnEntity_.partitionLevel), 2),
                                    builder.greaterThanOrEqualTo(
                                            businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue2),
                                            startPartitionValue),
                                    builder.lessThanOrEqualTo(
                                            businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue2),
                                            endPartitionValue)),
                            builder.and(builder.equal(schemaEntity.get(SchemaColumnEntity_.partitionLevel), 3),
                                    builder.greaterThanOrEqualTo(
                                            businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue3),
                                            startPartitionValue),
                                    builder.lessThanOrEqualTo(
                                            businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue3),
                                            endPartitionValue)),
                            builder.and(builder.equal(schemaEntity.get(SchemaColumnEntity_.partitionLevel), 4),
                                    builder.greaterThanOrEqualTo(
                                            businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue4),
                                            startPartitionValue),
                                    builder.lessThanOrEqualTo(
                                            businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue4),
                                            endPartitionValue)),
                            builder.and(builder.equal(schemaEntity.get(SchemaColumnEntity_.partitionLevel), 5),
                                    builder.greaterThanOrEqualTo(
                                            businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue5),
                                            startPartitionValue),
                                    builder.lessThanOrEqualTo(
                                            businessObjectDataEntity.get(BusinessObjectDataEntity_.partitionValue5),
                                            endPartitionValue))));
                }
            }
        }

        return predicate;
    }

    /**
     * Apply retention expiration filter to the main query predicate.
     *
     * @param businessObjectDataSearchKey the business object data search key
     * @param businessObjectDataEntityRoot the criteria root which is a business object data entity
     * @param businessObjectFormatEntityJoin the join with the business object format table
     * @param builder the criteria builder
     * @param mainQueryPredicate the main query predicate to be updated
     *
     * @return the updated main query predicate
     */
    private Predicate applyRetentionExpirationFilter(BusinessObjectDataSearchKey businessObjectDataSearchKey,
            Root<BusinessObjectDataEntity> businessObjectDataEntityRoot,
            Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> businessObjectFormatEntityJoin,
            CriteriaBuilder builder, Predicate mainQueryPredicate) {
        // Create a business object definition key per specified search key.
        BusinessObjectDefinitionKey businessObjectDefinitionKey = new BusinessObjectDefinitionKey(
                businessObjectDataSearchKey.getNamespace(),
                businessObjectDataSearchKey.getBusinessObjectDefinitionName());

        // Get latest versions of all business object formats that registered with the business object definition.
        List<BusinessObjectFormatEntity> businessObjectFormatEntities = businessObjectFormatDao
                .getLatestVersionBusinessObjectFormatsByBusinessObjectDefinition(businessObjectDefinitionKey);

        // Create a result predicate to join all retention expiration predicates created per selected business object formats.
        Predicate businessObjectDefinitionRetentionExpirationPredicate = null;

        // Get the current database timestamp to be used to select expired business object data per BDATA_RETENTION_DATE retention type.
        Timestamp currentTimestamp = getCurrentTimestamp();

        // Create a predicate for each business object format with the retention information.
        for (BusinessObjectFormatEntity businessObjectFormatEntity : businessObjectFormatEntities) {
            if (businessObjectFormatEntity.getRetentionType() != null) {
                // Create a retention expiration predicate for this business object format.
                Predicate businessObjectFormatRetentionExpirationPredicate = null;

                if (StringUtils.equals(businessObjectFormatEntity.getRetentionType().getCode(),
                        RetentionTypeEntity.BDATA_RETENTION_DATE)) {
                    // Select business object data that has expired per its explicitly configured retention expiration date.
                    businessObjectFormatRetentionExpirationPredicate = builder.lessThan(
                            businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.retentionExpiration),
                            currentTimestamp);
                } else if (StringUtils.equals(businessObjectFormatEntity.getRetentionType().getCode(),
                        RetentionTypeEntity.PARTITION_VALUE)
                        && businessObjectFormatEntity.getRetentionPeriodInDays() != null) {
                    // Compute the retention expiration date and convert it to the date format to match against partition values.
                    String retentionExpirationDate = DateFormatUtils.format(
                            DateUtils.addDays(new Date(),
                                    -1 * businessObjectFormatEntity.getRetentionPeriodInDays()),
                            DEFAULT_SINGLE_DAY_DATE_MASK);

                    // Create a predicate to compare business object data primary partition value against the retention expiration date.
                    businessObjectFormatRetentionExpirationPredicate = builder.lessThan(
                            businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.partitionValue),
                            retentionExpirationDate);
                }

                // If it was initialize, complete processing of retention expiration predicate for this business object format.
                if (businessObjectFormatRetentionExpirationPredicate != null) {
                    // Update the predicate to match this business object format w/o version.
                    businessObjectFormatRetentionExpirationPredicate = builder.and(
                            businessObjectFormatRetentionExpirationPredicate,
                            builder.equal(
                                    builder.upper(
                                            businessObjectFormatEntityJoin.get(BusinessObjectFormatEntity_.usage)),
                                    businessObjectFormatEntity.getUsage().toUpperCase()));
                    businessObjectFormatRetentionExpirationPredicate = builder.and(
                            businessObjectFormatRetentionExpirationPredicate,
                            builder.equal(businessObjectFormatEntityJoin.get(BusinessObjectFormatEntity_.fileType),
                                    businessObjectFormatEntity.getFileType()));

                    // Add this business object format specific retention expiration predicate to other
                    // retention expiration predicates created for the specified business object definition.
                    if (businessObjectDefinitionRetentionExpirationPredicate == null) {
                        businessObjectDefinitionRetentionExpirationPredicate = businessObjectFormatRetentionExpirationPredicate;
                    } else {
                        businessObjectDefinitionRetentionExpirationPredicate = builder.or(
                                businessObjectDefinitionRetentionExpirationPredicate,
                                businessObjectFormatRetentionExpirationPredicate);
                    }
                }
            }
        }

        // Fail if no retention expiration predicates got created per specified business objject definition.
        Assert.notNull(businessObjectDefinitionRetentionExpirationPredicate, String.format(
                "Business object definition with name \"%s\" and namespace \"%s\" has no business object formats with supported retention type.",
                businessObjectDefinitionKey.getBusinessObjectDefinitionName(),
                businessObjectDefinitionKey.getNamespace()));

        // Add created business object definition retention expiration predicate to the main query predicate passed to this method and return the result.
        return builder.and(mainQueryPredicate, businessObjectDefinitionRetentionExpirationPredicate);
    }

    /**
     * Apply attribute value filters to the main query predicate.
     *
     * @param businessDataSearchKey the business object data search key
     * @param businessObjectDataEntityRoot the criteria root which is a business object data entity
     * @param builder the criteria builder
     * @param mainQueryPredicate the main query predicate to be updated
     *
     * @return the updated main query predicate
     */
    private Predicate applyAttributeValueFilters(final BusinessObjectDataSearchKey businessDataSearchKey,
            final Root<BusinessObjectDataEntity> businessObjectDataEntityRoot, final CriteriaBuilder builder,
            Predicate mainQueryPredicate) {
        if (!CollectionUtils.isEmpty(businessDataSearchKey.getAttributeValueFilters())) {
            for (AttributeValueFilter attributeValueFilter : businessDataSearchKey.getAttributeValueFilters()) {
                Join<BusinessObjectDataEntity, BusinessObjectDataAttributeEntity> dataAttributeEntity = businessObjectDataEntityRoot
                        .join(BusinessObjectDataEntity_.attributes);

                if (!StringUtils.isEmpty(attributeValueFilter.getAttributeName())) {
                    mainQueryPredicate = builder.and(mainQueryPredicate,
                            builder.equal(dataAttributeEntity.get(BusinessObjectDataAttributeEntity_.name),
                                    attributeValueFilter.getAttributeName()));
                }

                if (!StringUtils.isEmpty(attributeValueFilter.getAttributeValue())) {
                    mainQueryPredicate = builder.and(mainQueryPredicate,
                            builder.equal(dataAttributeEntity.get(BusinessObjectDataAttributeEntity_.value),
                                    attributeValueFilter.getAttributeValue()));
                }
            }
        }

        return mainQueryPredicate;
    }

    /**
     * Apply a predicate for registration date range filter.
     *
     * @param registrationDateRangeFilter the registration date range filter, not null
     * @param businessObjectDataEntity the business object data entity
     * @param builder the query builder
     * @param predicate the predicate to be updated
     *
     * @return the predicate with added registration date range filter
     */
    private Predicate applyRegistrationDateRangeFilter(RegistrationDateRangeFilter registrationDateRangeFilter,
            Root<BusinessObjectDataEntity> businessObjectDataEntity, CriteriaBuilder builder, Predicate predicate) {
        // Apply predicate for registration start date and removed the time portion of the date.
        if (registrationDateRangeFilter.getStartRegistrationDate() != null) {
            predicate = builder.and(predicate, builder.greaterThanOrEqualTo(
                    businessObjectDataEntity.get(BusinessObjectDataEntity_.createdOn),
                    HerdDateUtils.resetTimeToMidnight(registrationDateRangeFilter.getStartRegistrationDate())));
        }

        // Apply predicate for registration end date. Removed time portion of the date and added one day to get the result till the end of the day
        if (registrationDateRangeFilter.getEndRegistrationDate() != null) {
            predicate = builder.and(predicate, builder.lessThan(
                    businessObjectDataEntity.get(BusinessObjectDataEntity_.createdOn),
                    HerdDateUtils.addDays(
                            HerdDateUtils.resetTimeToMidnight(registrationDateRangeFilter.getEndRegistrationDate()),
                            1)));
        }

        return predicate;
    }

    /**
     * Gets a query result list from an entity list.
     *
     * @param entityArray entity array from query
     * @param attributeValueList attribute value list
     *
     * @return the list of business object data
     */
    private List<BusinessObjectData> getQueryResultListFromEntityList(List<BusinessObjectDataEntity> entityArray,
            List<AttributeValueFilter> attributeValueList) {
        List<BusinessObjectData> businessObjectDataList = new ArrayList<>();
        Set<Integer> businessObjectIdSet = new HashSet<>();
        for (BusinessObjectDataEntity dataEntity : entityArray) {
            //need to skip the same data entity
            if (businessObjectIdSet.contains(dataEntity.getId())) {
                continue;
            }
            BusinessObjectData businessObjectData = new BusinessObjectData();
            businessObjectIdSet.add(dataEntity.getId());
            businessObjectData.setId(dataEntity.getId());
            businessObjectData.setPartitionValue(dataEntity.getPartitionValue());
            businessObjectData.setVersion(dataEntity.getVersion());
            businessObjectData.setLatestVersion(dataEntity.getLatestVersion());
            BusinessObjectFormatEntity formatEntity = dataEntity.getBusinessObjectFormat();
            businessObjectData.setNamespace(formatEntity.getBusinessObjectDefinition().getNamespace().getCode());
            businessObjectData
                    .setBusinessObjectDefinitionName(formatEntity.getBusinessObjectDefinition().getName());
            businessObjectData.setBusinessObjectFormatUsage(formatEntity.getUsage());
            businessObjectData.setBusinessObjectFormatFileType(formatEntity.getFileType().getCode());
            businessObjectData.setBusinessObjectFormatVersion(formatEntity.getBusinessObjectFormatVersion());
            businessObjectData.setPartitionKey(formatEntity.getPartitionKey());
            businessObjectData.setStatus(dataEntity.getStatus().getCode());

            List<String> subpartitions = new ArrayList<>();
            if (dataEntity.getPartitionValue2() != null) {
                subpartitions.add(dataEntity.getPartitionValue2());
            }
            if (dataEntity.getPartitionValue3() != null) {
                subpartitions.add(dataEntity.getPartitionValue3());
            }
            if (dataEntity.getPartitionValue4() != null) {
                subpartitions.add(dataEntity.getPartitionValue4());
            }
            if (dataEntity.getPartitionValue5() != null) {
                subpartitions.add(dataEntity.getPartitionValue5());
            }
            if (subpartitions.size() > 0) {
                businessObjectData.setSubPartitionValues(subpartitions);
            }

            //add attribute name and values in the request to the response
            if (attributeValueList != null && !attributeValueList.isEmpty()) {
                Collection<BusinessObjectDataAttributeEntity> dataAttributeCollection = dataEntity.getAttributes();
                List<Attribute> attributeList = new ArrayList<>();
                for (BusinessObjectDataAttributeEntity attributeEntity : dataAttributeCollection) {
                    Attribute attribute = new Attribute(attributeEntity.getName(), attributeEntity.getValue());
                    if (shouldIncludeAttributeInResponse(attributeEntity, attributeValueList)
                            && !attributeList.contains(attribute)) {
                        attributeList.add(attribute);
                    }
                }
                businessObjectData.setAttributes(attributeList);
            }
            businessObjectDataList.add(businessObjectData);
        }

        return businessObjectDataList;
    }

    /**
     * Checks if the attribute should be returned based on the attribute value query list if attribute name supplied, match attribute name case in sensitive if
     * attribute value supplied, match attribute value case sensitive with contain logic if both attribute name and value supplied, match both.
     *
     * @param attributeEntity the database attribute entity
     * @param attributeQueryList the attribute query list
     *
     * @return true for returning in response; false for not
     */
    private boolean shouldIncludeAttributeInResponse(BusinessObjectDataAttributeEntity attributeEntity,
            List<AttributeValueFilter> attributeQueryList) {
        String attributeName = attributeEntity.getName();
        String attributeValue = attributeEntity.getValue();

        for (AttributeValueFilter valueFiler : attributeQueryList) {
            String queryAttributeName = valueFiler.getAttributeName();
            String queryAttributeValue = valueFiler.getAttributeValue();
            Boolean matchAttributeName = false;
            Boolean matchAttributeValue = false;

            if (attributeName != null && attributeName.equalsIgnoreCase(queryAttributeName)) {
                matchAttributeName = true;
            }

            if (attributeValue != null && queryAttributeValue != null
                    && attributeValue.contains(queryAttributeValue)) {
                matchAttributeValue = true;
            }

            if (!StringUtils.isEmpty(queryAttributeName) && !StringUtils.isEmpty(queryAttributeValue)) {
                if (matchAttributeName && matchAttributeValue) {
                    return true;
                }
            } else if (!StringUtils.isEmpty(queryAttributeName) && matchAttributeName) {
                return true;
            } else if (!StringUtils.isEmpty(queryAttributeValue) && matchAttributeValue) {
                return true;
            }
        }

        return false;
    }

    /**
     * Creates a sub query for the maximum business object data version.
     *
     * @param builder criteria builder
     * @param criteria criteria query
     * @param businessObjectDataEntity business object data entity
     * @param businessObjectFormatEntity business object format entity
     * @param businessObjectDataStatus business object status, case insensitive
     *
     * @return the sub query to select the maximum business object data version
     */
    private Subquery<Integer> getMaximumBusinessObjectDataVersionSubQuery(CriteriaBuilder builder,
            CriteriaQuery<?> criteria, From<?, BusinessObjectDataEntity> businessObjectDataEntity,
            From<?, BusinessObjectFormatEntity> businessObjectFormatEntity, String businessObjectDataStatus) {
        // Create a sub query for the business object data version.
        Subquery<Integer> subQuery = criteria.subquery(Integer.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> subBusinessObjectDataEntity = subQuery.from(BusinessObjectDataEntity.class);

        // Join to the other tables we can filter on.
        Join<BusinessObjectDataEntity, BusinessObjectFormatEntity> subBusinessObjectFormatEntity = subBusinessObjectDataEntity
                .join(BusinessObjectDataEntity_.businessObjectFormat);

        // Add a standard restriction on business object format.
        Predicate subQueryRestriction = builder.equal(subBusinessObjectFormatEntity, businessObjectFormatEntity);

        // Create and add standard restrictions on primary and sub-partition values.
        subQueryRestriction = builder.and(subQueryRestriction, getQueryRestrictionOnPartitionValues(builder,
                subBusinessObjectDataEntity, businessObjectDataEntity));

        // If specified, create and add a standard restriction on business object data status.
        if (businessObjectDataStatus != null) {
            Join<BusinessObjectDataEntity, BusinessObjectDataStatusEntity> subBusinessObjectDataStatusEntity = subBusinessObjectDataEntity
                    .join(BusinessObjectDataEntity_.status);

            subQueryRestriction = builder.and(subQueryRestriction,
                    builder.equal(
                            builder.upper(
                                    subBusinessObjectDataStatusEntity.get(BusinessObjectDataStatusEntity_.code)),
                            businessObjectDataStatus.toUpperCase()));
        }

        // Add all clauses to the sub query.
        subQuery.select(builder.max(subBusinessObjectDataEntity.get(BusinessObjectDataEntity_.version)))
                .where(subQueryRestriction);

        return subQuery;
    }

    @Override
    public List<BusinessObjectDataKey> getBusinessObjectDataByBusinessObjectDefinition(
            BusinessObjectDefinitionEntity businessObjectDefinitionEntity, Integer maxResults) {
        // Get ids for all business object formats registered with the specified business object definition.
        List<Integer> businessObjectFormatIds = businessObjectFormatDao
                .getBusinessObjectFormatIdsByBusinessObjectDefinition(businessObjectDefinitionEntity);

        // Return no results if the business object definition has no business object formats registered with it.
        if (CollectionUtils.isEmpty(businessObjectFormatIds)) {
            return new ArrayList<>();
        }

        // Create the criteria builder and the criteria.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<BusinessObjectDataEntity> criteria = builder.createQuery(BusinessObjectDataEntity.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> businessObjectDataEntityRoot = criteria.from(BusinessObjectDataEntity.class);

        // Create the standard restrictions (i.e. the standard where clauses).
        Predicate predicate = getPredicateForInClause(builder,
                businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.businessObjectFormatId),
                businessObjectFormatIds);

        // Build an order by clause.
        List<Order> orderBy = new ArrayList<>();
        for (SingularAttribute<BusinessObjectDataEntity, String> businessObjectDataPartition : BUSINESS_OBJECT_DATA_PARTITIONS) {
            orderBy.add(builder.desc(businessObjectDataEntityRoot.get(businessObjectDataPartition)));
        }
        orderBy.add(builder.desc(businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.version)));

        // Add the clauses for the query.
        criteria.select(businessObjectDataEntityRoot).where(predicate).orderBy(orderBy);

        // Create a query.
        TypedQuery<BusinessObjectDataEntity> query = entityManager.createQuery(criteria);

        // If specified, set the maximum number of results for query to return.
        if (maxResults != null) {
            query.setMaxResults(maxResults);
        }

        // Run the query to get a list of entities back.
        List<BusinessObjectDataEntity> businessObjectDataEntities = query.getResultList();

        // Populate the "keys" objects from the returned entities.
        List<BusinessObjectDataKey> businessObjectDataKeys = new ArrayList<>();
        for (BusinessObjectDataEntity businessObjectDataEntity : businessObjectDataEntities) {
            BusinessObjectDataKey businessObjectDataKey = new BusinessObjectDataKey();
            businessObjectDataKeys.add(businessObjectDataKey);
            businessObjectDataKey.setNamespace(businessObjectDataEntity.getBusinessObjectFormat()
                    .getBusinessObjectDefinition().getNamespace().getCode());
            businessObjectDataKey.setBusinessObjectDefinitionName(
                    businessObjectDataEntity.getBusinessObjectFormat().getBusinessObjectDefinition().getName());
            businessObjectDataKey
                    .setBusinessObjectFormatUsage(businessObjectDataEntity.getBusinessObjectFormat().getUsage());
            businessObjectDataKey.setBusinessObjectFormatFileType(
                    businessObjectDataEntity.getBusinessObjectFormat().getFileType().getCode());
            businessObjectDataKey.setBusinessObjectFormatVersion(
                    businessObjectDataEntity.getBusinessObjectFormat().getBusinessObjectFormatVersion());
            businessObjectDataKey.setPartitionValue(businessObjectDataEntity.getPartitionValue());
            businessObjectDataKey.setSubPartitionValues(getSubPartitionValues(businessObjectDataEntity));
            businessObjectDataKey.setBusinessObjectDataVersion(businessObjectDataEntity.getVersion());
        }

        return businessObjectDataKeys;
    }

    @Override
    public List<BusinessObjectDataKey> getBusinessObjectDataByBusinessObjectFormat(
            BusinessObjectFormatEntity businessObjectFormatEntity, Integer maxResults) {
        // Create the criteria builder and the criteria.
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<BusinessObjectDataEntity> criteria = builder.createQuery(BusinessObjectDataEntity.class);

        // The criteria root is the business object data.
        Root<BusinessObjectDataEntity> businessObjectDataEntityRoot = criteria.from(BusinessObjectDataEntity.class);

        // Create the standard restrictions (i.e. the standard where clauses).
        Predicate predicate = builder.equal(
                businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.businessObjectFormat),
                businessObjectFormatEntity);

        // Build the order by clause. The sort order is consistent with the search business object data implementation.
        List<Order> orderBy = new ArrayList<>();
        for (SingularAttribute<BusinessObjectDataEntity, String> businessObjectDataPartition : BUSINESS_OBJECT_DATA_PARTITIONS) {
            orderBy.add(builder.desc(businessObjectDataEntityRoot.get(businessObjectDataPartition)));
        }
        orderBy.add(builder.desc(businessObjectDataEntityRoot.get(BusinessObjectDataEntity_.version)));

        // Add the clauses for the query.
        criteria.select(businessObjectDataEntityRoot).where(predicate).orderBy(orderBy);

        // Create a query.
        TypedQuery<BusinessObjectDataEntity> query = entityManager.createQuery(criteria);

        // If specified, set the maximum number of results for query to return.
        if (maxResults != null) {
            query.setMaxResults(maxResults);
        }

        // Run the query to get a list of entities back.
        List<BusinessObjectDataEntity> businessObjectDataEntities = query.getResultList();

        // Populate the "keys" objects from the returned entities.
        List<BusinessObjectDataKey> businessObjectDataKeys = new ArrayList<>();
        for (BusinessObjectDataEntity businessObjectDataEntity : businessObjectDataEntities) {
            BusinessObjectDataKey businessObjectDataKey = new BusinessObjectDataKey();
            businessObjectDataKeys.add(businessObjectDataKey);
            businessObjectDataKey.setNamespace(businessObjectDataEntity.getBusinessObjectFormat()
                    .getBusinessObjectDefinition().getNamespace().getCode());
            businessObjectDataKey.setBusinessObjectDefinitionName(
                    businessObjectDataEntity.getBusinessObjectFormat().getBusinessObjectDefinition().getName());
            businessObjectDataKey
                    .setBusinessObjectFormatUsage(businessObjectDataEntity.getBusinessObjectFormat().getUsage());
            businessObjectDataKey.setBusinessObjectFormatFileType(
                    businessObjectDataEntity.getBusinessObjectFormat().getFileType().getCode());
            businessObjectDataKey.setBusinessObjectFormatVersion(
                    businessObjectDataEntity.getBusinessObjectFormat().getBusinessObjectFormatVersion());
            businessObjectDataKey.setPartitionValue(businessObjectDataEntity.getPartitionValue());
            businessObjectDataKey.setSubPartitionValues(getSubPartitionValues(businessObjectDataEntity));
            businessObjectDataKey.setBusinessObjectDataVersion(businessObjectDataEntity.getVersion());
        }

        return businessObjectDataKeys;
    }

    /**
     * Gets the sub-partition values for the specified business object data entity.
     *
     * @param businessObjectDataEntity the business object data entity
     *
     * @return the list of sub-partition values
     */
    private List<String> getSubPartitionValues(BusinessObjectDataEntity businessObjectDataEntity) {
        List<String> subPartitionValues = new ArrayList<>();

        List<String> rawSubPartitionValues = new ArrayList<>();
        rawSubPartitionValues.add(businessObjectDataEntity.getPartitionValue2());
        rawSubPartitionValues.add(businessObjectDataEntity.getPartitionValue3());
        rawSubPartitionValues.add(businessObjectDataEntity.getPartitionValue4());
        rawSubPartitionValues.add(businessObjectDataEntity.getPartitionValue5());

        for (String rawSubPartitionValue : rawSubPartitionValues) {
            if (rawSubPartitionValue != null) {
                subPartitionValues.add(rawSubPartitionValue);
            } else {
                break;
            }
        }

        return subPartitionValues;
    }
}