org.candlepin.model.ConsumerCurator.java Source code

Java tutorial

Introduction

Here is the source code for org.candlepin.model.ConsumerCurator.java

Source

/**
 * Copyright (c) 2009 - 2012 Red Hat, Inc.
 *
 * This software is licensed to you under the GNU General Public License,
 * version 2 (GPLv2). There is NO WARRANTY for this software, express or
 * implied, including the implied warranties of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
 * along with this software; if not, see
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
 *
 * Red Hat trademarks are not licensed under GPLv2. No permission is
 * granted to use or replicate Red Hat trademarks that are incorporated
 * in this software or its documentation.
 */
package org.candlepin.model;

import org.candlepin.common.config.Configuration;
import org.candlepin.common.exceptions.BadRequestException;
import org.candlepin.common.exceptions.NotFoundException;
import org.candlepin.common.paging.Page;
import org.candlepin.common.paging.PageRequest;
import org.candlepin.config.ConfigProperties;
import org.candlepin.resteasy.parameter.KeyValueParameter;
import org.candlepin.util.Util;

import com.google.common.collect.Iterables;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;

import org.apache.commons.collections.CollectionUtils;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.ReplicationMode;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.persistence.LockModeType;

/**
 * ConsumerCurator
 */
public class ConsumerCurator extends AbstractHibernateCurator<Consumer> {

    @Inject
    private EntitlementCurator entitlementCurator;
    @Inject
    private ConsumerTypeCurator consumerTypeCurator;
    @Inject
    private DeletedConsumerCurator deletedConsumerCurator;
    @Inject
    private Configuration config;

    private static final int MAX_FACT_STR_LENGTH = 255;
    private static final int NAME_LENGTH = 250;
    private static final int MAX_IN_QUERY_LENGTH = 500;
    private static Logger log = LoggerFactory.getLogger(ConsumerCurator.class);

    public ConsumerCurator() {
        super(Consumer.class);
    }

    @Transactional
    @Override
    public Consumer create(Consumer entity) {
        entity.ensureUUID();
        if (entity.getFacts() != null) {
            entity.setFacts(filterAndVerifyFacts(entity));
        }

        return super.create(entity);
    }

    @Transactional
    public void delete(Consumer entity) {
        // save off the IDs before we delete
        DeletedConsumer dc = new DeletedConsumer(entity.getUuid(), entity.getOwner().getId(),
                entity.getOwner().getKey(), entity.getOwner().getDisplayName());

        super.delete(entity);

        DeletedConsumer existing = deletedConsumerCurator.findByConsumerUuid(dc.getConsumerUuid());
        if (existing != null) {
            // update the owner ID in case the same UUID was specified by two owners
            existing.setOwnerId(dc.getOwnerId());
            existing.setOwnerKey(dc.getOwnerKey());
            existing.setOwnerDisplayName(dc.getOwnerDisplayName());
            existing.setUpdated(new Date());
            deletedConsumerCurator.save(existing);
        } else {
            deletedConsumerCurator.create(dc);
        }
    }

    @Transactional
    public Consumer replicate(Consumer consumer) {
        for (Entitlement entitlement : consumer.getEntitlements()) {
            entitlement.setConsumer(consumer);
        }

        ConsumerType consumerType = consumerTypeCurator.lookupByLabel(consumer.getType().getLabel());
        consumer.setType(consumerType);

        IdentityCertificate idCert = consumer.getIdCert();
        this.currentSession().replicate(idCert.getSerial(), ReplicationMode.EXCEPTION);
        this.currentSession().replicate(idCert, ReplicationMode.EXCEPTION);

        this.currentSession().replicate(consumer, ReplicationMode.EXCEPTION);

        return consumer;
    }

    /**
     * Lookup consumer by its name
     *
     * @param name consumer name to find
     * @return Consumer whose name matches the given name, null otherwise.
     */
    @Transactional
    public Consumer findByName(Owner o, String name) {
        return (Consumer) createSecureCriteria().add(Restrictions.eq("name", name)).add(Restrictions.eq("owner", o))
                .uniqueResult();
    }

    /**
     * Lookup consumer by its virt.uuid.
     *
     * In some cases the hypervisor will report UUIDs with uppercase, while the guest will
     * report lowercase. As such we do case insensitive comparison when looking these up.
     *
     * @param uuid consumer virt.uuid to find
     * @return Consumer whose name matches the given virt.uuid, null otherwise.
     */
    @Transactional
    public Consumer findByVirtUuid(String uuid, String ownerId) {
        Consumer result = null;
        List<String> possibleGuestIds = Util.getPossibleUuids(uuid);

        String sql = "select cp_consumer.id from cp_consumer " + "inner join cp_consumer_facts_lower "
                + "on cp_consumer.id = cp_consumer_facts_lower.cp_consumer_id "
                + "where cp_consumer_facts_lower.mapkey = 'virt.uuid' and "
                + "cp_consumer_facts_lower.element in (:guestids) " + "and cp_consumer.owner_id = :ownerid "
                + "order by cp_consumer.updated desc";

        Query q = currentSession().createSQLQuery(sql);
        q.setParameterList("guestids", possibleGuestIds);
        q.setParameter("ownerid", ownerId);
        List<String> options = q.list();

        if (options != null && options.size() != 0) {
            result = this.find(options.get(0));
        }

        return result;
    }

    /**
     * Lookup all consumers matching the given guest IDs.
     *
     * Maps guest ID to the most recent registered consumer that matches it.
     * Any guest ID not found will not return null.
     *
     * If multiple registered consumers report this guest ID (re-registraiton), only the
     * most recently updated will be returned.
     *
     * @param guestId
     *
     * @return VirtConsumerMap of guest ID to it's registered guest consumer, or null if
     * none exists.
     */
    @Transactional
    public VirtConsumerMap getGuestConsumersMap(Owner owner, Set<String> guestIds) {
        VirtConsumerMap guestConsumersMap = new VirtConsumerMap();

        if (guestIds.size() == 0) {
            return guestConsumersMap;
        }

        List<String> possibleGuestIds = Util.getPossibleUuids(guestIds.toArray(new String[guestIds.size()]));

        String sql = "select cp_consumer.uuid from cp_consumer " + "inner join cp_consumer_facts_lower "
                + "on cp_consumer.id = cp_consumer_facts_lower.cp_consumer_id "
                + "where cp_consumer_facts_lower.mapkey = 'virt.uuid' and "
                + "cp_consumer_facts_lower.element in (:guestids) " + "and cp_consumer.owner_id = :ownerid "
                + "order by cp_consumer.updated desc";

        // We need to filter down to only the most recently registered consumer with
        // each guest ID.

        List<String> consumerUuids = new LinkedList<String>();

        Iterable<List<String>> blocks = Iterables.partition(possibleGuestIds,
                AbstractHibernateCurator.IN_OPERATOR_BLOCK_SIZE);

        Query query = this.currentSession().createSQLQuery(sql).setParameter("ownerid", owner.getId());

        for (List<String> block : blocks) {
            query.setParameterList("guestids", block);
            consumerUuids.addAll(query.list());
        }

        if (consumerUuids.isEmpty()) {
            return guestConsumersMap;
        }

        List<Consumer> guestConsumersWithDupes = findByUuidsAndOwner(consumerUuids, owner);
        // At this point we might have duplicates for re-registered consumers:
        for (Consumer c : guestConsumersWithDupes) {
            String virtUuid = c.getFact("virt.uuid").toLowerCase();
            if (guestConsumersMap.get(virtUuid) == null) {
                // Store both big and little endian forms in the result:
                guestConsumersMap.add(virtUuid, c);
            }

            // Can safely ignore if already in the map, this would be another consumer
            // reporting the same guest ID (re-registration), but we already sorted by
            // last update time.
        }

        return guestConsumersMap;
    }

    /**
     * Candlepin supports the notion of a user being a consumer. When in effect
     * a consumer will exist in the system who is tied to a particular user.
     *
     * @param user User
     * @return Consumer for this user if one exists, null otherwise.
     */
    @Transactional
    public Consumer findByUser(User user) {
        ConsumerType person = consumerTypeCurator.lookupByLabel(ConsumerType.ConsumerTypeEnum.PERSON.getLabel());

        return (Consumer) createSecureCriteria().add(Restrictions.eq("username", user.getUsername()))
                .add(Restrictions.eq("type", person)).uniqueResult();
    }

    /**
     * Lookup the Consumer by its UUID.
     *
     * @param uuid Consumer UUID sought.
     * @return Consumer whose UUID matches the given value, or null otherwise.
     */
    @Transactional
    public Consumer findByUuid(String uuid) {
        return getConsumer(uuid);
    }

    /**
     * Apply a SELECT FOR UPDATE on a consumer.
     *
     * Note this method is not transactional.  It is meant to be used within
     * a larger transaction.  Starting a transaction, running a select for update,
     * and then ending the transaction is pointless.
     *
     * @return A consumer locked in the database
     */
    public Consumer lockAndLoad(Consumer c) {
        getEntityManager().lock(c, LockModeType.PESSIMISTIC_WRITE);
        return c;
    }

    /**
     * Find a consumer by uuid and immediately lock it.
     *
     * @param consumerUuid the uuid of the target consumer.
     *
     * @return the Consumer matching the given uuid, null if the consumer was not found.
     */
    public Consumer lockAndLoadByUuid(String consumerUuid) {
        List<Consumer> consumerList = lockAndLoadBatch(Arrays.asList(consumerUuid));
        return (CollectionUtils.isEmpty(consumerList)) ? null : consumerList.get(0);
    }

    public List<Consumer> lockAndLoadBatch(Collection<String> uuids) {
        return lockAndLoadBatch(uuids, "Consumer", "uuid");
    }

    @Transactional
    public List<Consumer> findByUuids(Collection<String> uuids) {
        return listByCriteria(createSecureCriteria().add(Restrictions.in("uuid", uuids)));
    }

    @Transactional
    public List<Consumer> findByUuidsAndOwner(Collection<String> uuids, Owner owner) {
        Criteria criteria = currentSession().createCriteria(Consumer.class);
        criteria.add(Restrictions.eq("owner", owner));
        criteria.add(Restrictions.in("uuid", uuids));
        return listByCriteria(criteria);
    }

    // NOTE: This is a giant hack that is for use *only* by SSLAuth in order
    // to bypass the authentication. Do not call it!
    // TODO: Come up with a better way to do this!
    public Consumer getConsumer(String uuid) {
        Criteria criteria = createSecureCriteria().add(Restrictions.eq("uuid", uuid));

        return (Consumer) criteria.uniqueResult();
    }

    @SuppressWarnings("unchecked")
    @Transactional
    public List<Consumer> listByOwner(Owner owner) {
        return createSecureCriteria().add(Restrictions.eq("owner", owner)).list();
    }

    /**
     * Search for Consumers with fields matching those provided.
     *
     * @param userName the username to match, or null to ignore
     * @param types the types to match, or null/empty to ignore
     * @param owner Optional owner to filter on, pass null to skip.
     * @return a list of matching Consumers
     */
    @SuppressWarnings("unchecked")
    @Transactional
    public Page<List<Consumer>> listByUsernameAndType(String userName, List<ConsumerType> types, Owner owner,
            PageRequest pageRequest) {

        Criteria criteria = createSecureCriteria();

        if (userName != null) {
            criteria.add(Restrictions.eq("username", userName));
        }
        if (types != null && !types.isEmpty()) {
            criteria.add(Restrictions.in("type", types));
        }
        if (owner != null) {
            criteria.add(Restrictions.eq("owner", owner));
        }

        return listByCriteria(criteria, pageRequest);
    }

    /**
     * @param updatedConsumer updated Consumer values.
     * @return Updated consumers
     */
    @Transactional
    public Consumer update(Consumer updatedConsumer) {
        return updateWithOptionalFlush(updatedConsumer, true);
    }

    /**
     * @param updatedConsumer updated Consumer values.
     * @param saveConsumer to flush or not to flush, that is the question.
     * @return Updated consumers
     */
    @Transactional
    public Consumer updateWithOptionalFlush(Consumer updatedConsumer, boolean saveConsumer) {
        Consumer existingConsumer = find(updatedConsumer.getId());
        if (existingConsumer == null) {
            return create(updatedConsumer);
        }

        // TODO: Are any of these read-only?
        existingConsumer.setEntitlements(entitlementCurator.bulkUpdate(updatedConsumer.getEntitlements()));
        Map<String, String> newFacts = filterAndVerifyFacts(updatedConsumer);
        if (factsChanged(newFacts, existingConsumer.getFacts())) {
            existingConsumer.setFacts(newFacts);
        }
        existingConsumer.setName(updatedConsumer.getName());
        existingConsumer.setOwner(updatedConsumer.getOwner());
        existingConsumer.setType(updatedConsumer.getType());
        existingConsumer.setUuid(updatedConsumer.getUuid());

        if (saveConsumer) {
            save(existingConsumer);
        }

        return existingConsumer;
    }

    /**
     * Modifies the last check in and persists the entity. Make sure that the data
     * is refreshed before using this method.
     * @param consumer the consumer to update
     */
    public void updateLastCheckin(Consumer consumer) {
        this.updateLastCheckin(consumer, new Date());
    }

    @Transactional
    public void updateLastCheckin(Consumer consumer, Date checkinDate) {
        currentSession()
                .createQuery("update Consumer c " + "set c.lastCheckin = :date, " + "c.updated = :date "
                        + "where c.id = :consumerid")
                .setTimestamp("date", checkinDate).setParameter("consumerid", consumer.getId()).executeUpdate();
    }

    private boolean factsChanged(Map<String, String> updatedFacts, Map<String, String> existingFacts) {
        return !existingFacts.equals(updatedFacts);
    }

    /**
     * @param facts
     * @return the list of facts filtered by the fact filter regex config
     */
    private Map<String, String> filterAndVerifyFacts(Consumer consumer) {
        Map<String, String> factsIn = consumer.getFacts();
        Map<String, String> facts = new HashMap<String, String>();
        String factMatch = config.getString(ConfigProperties.CONSUMER_FACTS_MATCHER);
        Set<String> intFacts = config.getSet(ConfigProperties.INTEGER_FACTS);
        Set<String> posFacts = config.getSet(ConfigProperties.NON_NEG_INTEGER_FACTS);

        for (Entry<String, String> entry : factsIn.entrySet()) {
            if (entry.getKey().matches(factMatch)) {
                if (intFacts != null && intFacts.contains(entry.getKey())
                        || posFacts != null && posFacts.contains(entry.getKey())) {
                    int value = -1;
                    try {
                        value = Integer.parseInt(entry.getValue());
                    } catch (NumberFormatException nfe) {
                        log.error("The fact " + entry.getKey() + " for consumer " + consumer.getUuid()
                                + " must be an integer instead of " + entry.getValue() + ". "
                                + "No value will exist for that fact.");
                        continue;
                    }
                    if (posFacts != null && posFacts.contains(entry.getKey()) && value < 0) {
                        log.error("The fact " + entry.getKey() + " must have a positive integer value instead of "
                                + entry.getValue() + ". No value will exist for that fact.");
                        continue;
                    }
                }
                facts.put(sanitizeFact(entry.getKey()), sanitizeFact(entry.getValue()));
            }
        }
        return facts;
    }

    private String sanitizeFact(String value) {
        if (value != null && value.length() > MAX_FACT_STR_LENGTH) {
            return value.substring(0, MAX_FACT_STR_LENGTH - 3) + "...";
        }
        return value;
    }

    /**
     * @param consumers consumers to update
     * @return updated consumers
     */
    @Transactional
    public Set<Consumer> bulkUpdate(Set<Consumer> consumers) {
        Set<Consumer> toReturn = new HashSet<Consumer>();
        for (Consumer toUpdate : consumers) {
            toReturn.add(update(toUpdate));
        }
        return toReturn;
    }

    /**
     * Get host consumer for a guest system id.
     *
     * As multiple hosts could have reported the same guest ID, we find the newest
     * and assume this is the authoritative host for the guest.
     *
     * This search needs to be case insensitive as some hypervisors report uppercase
     * guest UUIDs, when the guest itself will report lowercase.
     *
     * @param guestId a virtual guest ID (not a consumer UUID)
     * @return host consumer who most recently reported the given guestId (if any)
     */
    @Transactional
    public Consumer getHost(String guestId, Owner owner) {
        Disjunction guestIdCrit = Restrictions.disjunction();
        for (String possibleId : Util.getPossibleUuids(guestId)) {
            guestIdCrit.add(Restrictions.eq("guestIdLower", possibleId.toLowerCase()));
        }
        Criteria crit = currentSession().createCriteria(GuestId.class).createAlias("consumer", "gconsumer")
                .add(Restrictions.eq("gconsumer.owner", owner)).addOrder(Order.desc("updated")).setMaxResults(1)
                .setProjection(Projections.property("consumer"));
        return (Consumer) crit.add(guestIdCrit).uniqueResult();
    }

    /**
     * Get guest consumers for a host consumer.
     *
     * @param consumer host consumer to find the guests for
     * @return list of registered guest consumers for this host
     */
    @Transactional
    public List<Consumer> getGuests(Consumer consumer) {
        if (consumer.getFact("virt.uuid") != null && !consumer.getFact("virt.uuid").trim().equals("")) {
            throw new BadRequestException(
                    i18n.tr("The system with UUID {0} is a virtual guest. " + "It does not have guests.",
                            consumer.getUuid()));
        }
        List<Consumer> guests = new ArrayList<Consumer>();
        List<GuestId> consumerGuests = consumer.getGuestIds();
        if (consumerGuests != null) {
            for (GuestId cg : consumerGuests) {
                // Check if this is the most recent host to report the guest by asking
                // for the consumer's current host and comparing it to ourselves.
                if (consumer.equals(getHost(cg.getGuestId(), consumer.getOwner()))) {
                    Consumer guest = findByVirtUuid(cg.getGuestId(), consumer.getOwner().getId());
                    if (guest != null) {
                        guests.add(guest);
                    }
                }
            }
        }
        return guests;
    }

    /**
     * This is an insecure query, because we need to know whether or not the
     * consumer exists
     *
     * We do not require that the hypervisor be consumerType hypervisor
     * because we need to allow regular consumers to be given
     * HypervisorIds to be updated via hypervisorResource
     *
     * @param hypervisorId Unique identifier of the hypervisor
     * @param owner Org namespace to search
     * @return Consumer that matches the given
     */
    @Transactional
    public Consumer getHypervisor(String hypervisorId, Owner owner) {
        return (Consumer) currentSession().createCriteria(Consumer.class).add(Restrictions.eq("owner", owner))
                .createAlias("hypervisorId", "hvsr")
                .add(Restrictions.eq("hvsr.hypervisorId", hypervisorId.toLowerCase())).setMaxResults(1)
                .uniqueResult();
    }

    /**
     * Lookup all registered consumers matching one of the given hypervisor IDs.
     *
     * Results are returned as a map of hypervisor ID to the consumer record created.
     * If a hypervisor ID is not in the map, this indicates the hypervisor consumer does
     * not exist, i.e. it is new and needs to be created.
     *
     * This is an unsecured query, manually limited to an owner by the parameter given.
     * @param owner Owner to limit results to.
     * @param hypervisorIds Collection of hypervisor IDs as reported by the virt fabric.
     *
     * @return VirtConsumerMap of hypervisor ID to it's consumer, or null if none exists.
     */
    @Transactional
    public VirtConsumerMap getHostConsumersMap(Owner owner, Iterable<String> hypervisorIds) {
        VirtConsumerMap hypervisorMap = new VirtConsumerMap();

        // TODO: Replace this with cursor bits when they're available!
        for (Consumer consumer : this.getHypervisorsBulk(hypervisorIds, owner.getKey())) {
            hypervisorMap.add(consumer.getHypervisorId().getHypervisorId(), consumer);
        }

        return hypervisorMap;
    }

    /**
     * @param hypervisorIds list of unique hypervisor identifiers
     * @param ownerKey Org namespace to search
     * @return Consumer that matches the given
     */
    @SuppressWarnings("unchecked")
    @Transactional
    public List<Consumer> getHypervisorsBulk(Iterable<String> hypervisorIds, String ownerKey) {
        if (hypervisorIds == null || !hypervisorIds.iterator().hasNext()) {
            return new LinkedList<Consumer>();
        }

        return this.currentSession().createCriteria(Consumer.class).createAlias("owner", "o")
                .createAlias("hypervisorId", "hvsr").add(Restrictions.eq("o.key", ownerKey))
                .add(this.getHypervisorIdRestriction(hypervisorIds)).list();
    }

    private Criterion getHypervisorIdRestriction(Iterable<String> hypervisorIds) {
        List<Criterion> ors = new LinkedList<Criterion>();
        for (String hypervisorId : hypervisorIds) {
            ors.add(Restrictions.eq("hvsr.hypervisorId", hypervisorId.toLowerCase()));
        }
        return Restrictions.or(ors.toArray(new Criterion[0]));
    }

    @SuppressWarnings("unchecked")
    @Transactional
    public List<Consumer> getHypervisorsForOwner(String ownerKey) {
        return createSecureCriteria().createAlias("owner", "o").createAlias("hypervisorId", "hvsr")
                .add(Restrictions.eq("o.key", ownerKey)).add(Restrictions.isNotNull("hvsr.hypervisorId")).list();
    }

    public boolean doesConsumerExist(String uuid) {
        long result = (Long) createSecureCriteria().add(Restrictions.eq("uuid", uuid))
                .setProjection(Projections.count("id")).uniqueResult();

        return result != 0;
    }

    public Consumer verifyAndLookupConsumer(String consumerUuid) {
        Consumer consumer = this.findByUuid(consumerUuid);

        if (consumer == null) {
            throw new NotFoundException(i18n.tr("Unit with ID ''{0}'' could not be found.", consumerUuid));
        }

        return consumer;
    }

    public Consumer verifyAndLookupConsumerWithEntitlements(String consumerUuid) {
        Consumer consumer = this.findByUuid(consumerUuid);
        if (consumer == null) {
            throw new NotFoundException(i18n.tr("Unit with ID ''{0}'' could not be found.", consumerUuid));
        }

        for (Entitlement e : consumer.getEntitlements()) {
            Hibernate.initialize(e.getCertificates());

            if (e.getPool() != null) {
                Hibernate.initialize(e.getPool().getBranding());
                Hibernate.initialize(e.getPool().getDerivedProvidedProducts());
                Hibernate.initialize(e.getPool().getProvidedProducts());
                Hibernate.initialize(e.getPool().getProductAttributes());
                Hibernate.initialize(e.getPool().getAttributes());
                Hibernate.initialize(e.getPool().getDerivedProductAttributes());
            }
        }

        return consumer;
    }

    @SuppressWarnings("checkstyle:indentation")
    public Page<List<Consumer>> searchOwnerConsumers(Owner owner, String userName, Collection<ConsumerType> types,
            List<String> uuids, List<String> hypervisorIds, List<KeyValueParameter> factFilters, List<String> skus,
            List<String> subscriptionIds, List<String> contracts, PageRequest pageRequest) {
        Criteria crit = super.createSecureCriteria();
        if (owner != null) {
            crit.add(Restrictions.eq("owner", owner));
        }
        if (userName != null && !userName.isEmpty()) {
            crit.add(Restrictions.eq("username", userName));
        }
        if (types != null && !types.isEmpty()) {
            crit.add(Restrictions.in("type", types));
        }
        if (uuids != null && !uuids.isEmpty()) {
            crit.add(Restrictions.in("uuid", uuids));
        }
        if (hypervisorIds != null && !hypervisorIds.isEmpty()) {
            // Cannot use Restrictions.in here because hypervisorId is case insensitive
            Set<Criterion> ors = new HashSet<Criterion>();
            for (String hypervisorId : hypervisorIds) {
                ors.add(Restrictions.eq("hvsr.hypervisorId", hypervisorId.toLowerCase()));
            }
            crit.createAlias("hypervisorId", "hvsr");
            crit.add(Restrictions.or(ors.toArray(new Criterion[ors.size()])));
        }
        if (factFilters != null && !factFilters.isEmpty()) {
            // Process the filters passed for the attributes
            FilterBuilder factFilter = new FactFilterBuilder();
            for (KeyValueParameter filterParam : factFilters) {
                factFilter.addAttributeFilter(filterParam.key(), filterParam.value());
            }
            factFilter.applyTo(crit);
        }

        boolean hasSkus = (skus != null && !skus.isEmpty());
        boolean hasSubscriptionIds = (subscriptionIds != null && !subscriptionIds.isEmpty());
        boolean hasContractNumbers = (contracts != null && !contracts.isEmpty());

        if (hasSkus || hasSubscriptionIds || hasContractNumbers) {
            if (hasSkus) {
                for (String sku : skus) {
                    DetachedCriteria subCrit = DetachedCriteria.forClass(Consumer.class, "subquery_consumer");

                    if (owner != null) {
                        subCrit.add(Restrictions.eq("owner", owner));
                    }

                    subCrit.createCriteria("entitlements").createCriteria("pool").createCriteria("product")
                            .add(Restrictions.eq("id", sku)).createCriteria("attributes").add(Restrictions
                                    .and(Restrictions.eq("name", "type"), Restrictions.eq("value", "MKT")));

                    subCrit.add(Restrictions.eqProperty("this.id", "subquery_consumer.id"));

                    crit.add(Subqueries
                            .exists(subCrit.setProjection(Projections.property("subquery_consumer.name"))));
                }
            }
            if (hasSubscriptionIds) {
                for (String subId : subscriptionIds) {
                    DetachedCriteria subCrit = DetachedCriteria.forClass(Consumer.class, "subquery_consumer");

                    if (owner != null) {
                        subCrit.add(Restrictions.eq("owner", owner));
                    }

                    subCrit.createCriteria("entitlements").createCriteria("pool")
                            .createCriteria("sourceSubscription").add(Restrictions.eq("subscriptionId", subId));
                    subCrit.add(Restrictions.eqProperty("this.id", "subquery_consumer.id"));

                    crit.add(Subqueries
                            .exists(subCrit.setProjection(Projections.property("subquery_consumer.name"))));
                }
            }
            if (hasContractNumbers) {
                for (String contract : contracts) {
                    DetachedCriteria subCrit = DetachedCriteria.forClass(Consumer.class, "subquery_consumer");

                    if (owner != null) {
                        subCrit.add(Restrictions.eq("owner", owner));
                    }

                    subCrit.createCriteria("entitlements").createCriteria("pool")
                            .add(Restrictions.eq("contractNumber", contract));
                    subCrit.add(Restrictions.eqProperty("this.id", "subquery_consumer.id"));

                    crit.add(Subqueries
                            .exists(subCrit.setProjection(Projections.property("subquery_consumer.name"))));
                }
            }
        }

        return listByCriteria(crit, pageRequest);
    }

    /**
     * Finds the consumer count for an Owner based on type.
     *
     * @param owner the owner to count consumers for
     * @param type the type of the Consumer to filter on.
     * @return the number of consumers based on the type.
     */
    public int getConsumerCount(Owner owner, ConsumerType type) {
        Criteria c = createSecureCriteria().add(Restrictions.eq("owner", owner)).add(Restrictions.eq("type", type));
        c.setProjection(Projections.rowCount());
        return ((Long) c.uniqueResult()).intValue();
    }

    public int getConsumerEntitlementCount(Owner owner, ConsumerType type) {
        Criteria c = createSecureCriteria().add(Restrictions.eq("owner", owner)).add(Restrictions.eq("type", type))
                .createAlias("entitlements", "ent").setMaxResults(0).setProjection(Projections.sum("ent.quantity"));
        Long result = (Long) c.uniqueResult();
        return result == null ? 0 : result.intValue();
    }

    @SuppressWarnings("unchecked")
    public List<String> getConsumerIdsWithStartedEnts() {
        Date now = new Date();
        return currentSession().createCriteria(Entitlement.class).createAlias("pool", "p")
                .add(Restrictions.eq("updatedOnStart", false)).add(Restrictions.lt("p.startDate", now))
                .setProjection(Projections.property("consumer.id"))
                .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
    }

}