/**
* 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.resteasy.parameter.KeyValueParameter;
import org.candlepin.util.FactValidator;
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.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> {
private static Logger log = LoggerFactory.getLogger(ConsumerCurator.class);
@Inject private EntitlementCurator entitlementCurator;
@Inject private ConsumerTypeCurator consumerTypeCurator;
@Inject private DeletedConsumerCurator deletedConsumerCurator;
@Inject private Configuration config;
@Inject private FactValidator factValidator;
private Map<String, Consumer> cachedHosts = new HashMap<String, Consumer>();
public ConsumerCurator() {
super(Consumer.class);
}
@Transactional
@Override
public Consumer create(Consumer entity) {
entity.ensureUUID();
this.validateFacts(entity);
return super.create(entity);
}
@Override
@Transactional
public void delete(Consumer entity) {
log.debug("Deleting 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 " +
"on cp_consumer.id = cp_consumer_facts.cp_consumer_id " +
"where cp_consumer_facts.mapkey = 'virt.uuid' and " +
"lower(cp_consumer_facts.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 " +
"on cp_consumer.id = cp_consumer_facts.cp_consumer_id " +
"where cp_consumer_facts.mapkey = 'virt.uuid' and " +
"lower(cp_consumer_facts.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, getInBlockSize());
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;
}
// At this point we might have duplicates for re-registered consumers:
for (Consumer c : this.findByUuidsAndOwner(consumerUuids, owner)) {
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);
}
@Transactional
public CandlepinQuery<Consumer> findByUuids(Collection<String> uuids) {
DetachedCriteria criteria = this.createSecureDetachedCriteria()
.add(Restrictions.in("uuid", uuids));
return this.cpQueryFactory.<Consumer>buildQuery(this.currentSession(), criteria);
}
@Transactional
public CandlepinQuery<Consumer> findByUuidsAndOwner(Collection<String> uuids, Owner owner) {
DetachedCriteria criteria = DetachedCriteria.forClass(Consumer.class)
.add(Restrictions.eq("owner", owner))
.add(Restrictions.in("uuid", uuids));
return this.cpQueryFactory.<Consumer>buildQuery(this.currentSession(), 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 = this.createSecureCriteria()
.add(Restrictions.eq("uuid", uuid));
return (Consumer) criteria.uniqueResult();
}
@SuppressWarnings("unchecked")
@Transactional
public CandlepinQuery<Consumer> listByOwner(Owner owner) {
DetachedCriteria criteria = this.createSecureDetachedCriteria()
.add(Restrictions.eq("owner", owner));
return this.cpQueryFactory.<Consumer>buildQuery(this.currentSession(), criteria);
}
/**
* 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 CandlepinQuery<Consumer> listByUsernameAndType(String userName, List<ConsumerType> types,
Owner owner) {
DetachedCriteria criteria = this.createSecureDetachedCriteria();
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 this.cpQueryFactory.<Consumer>buildQuery(this.currentSession(), criteria);
}
/**
* @param updatedConsumer updated Consumer values.
* @return Updated consumers
*/
@Transactional
public Consumer update(Consumer updatedConsumer) {
return update(updatedConsumer, true);
}
/**
* Updates an existing consumer with the state specified by the given Consumer instance. If the
* consumer has not yet been created, it will be created.
* <p></p>
* <strong>Warning:</strong> Using an pre-existing and persisted Consumer entity as the update
* to apply may cause issues, as Hibernate may opt to save changes to nested collections
* (facts, guestIds, tags, etc.) when any other database operation is performed. To avoid this
* issue, it is advised to use only detached or otherwise unmanaged entities for the updated
* consumer to pass to this method.
*
* @param updatedConsumer
* A Consumer instance representing the updated state of a consumer
*
* @param flush
* Whether or not to flush pending database operations after creating or updating the given
* consumer
*
* @return
* The persisted, updated consumer
*/
@Transactional
public Consumer update(Consumer updatedConsumer, boolean flush) {
// TODO: FIXME:
// We really need to use a DTO here. Hibernate has so many pitfalls with this approach that
// can and will lead to odd, broken or out-of-order behavior.
// Validate inbound facts before even attempting to apply the update
this.validateFacts(updatedConsumer);
Consumer existingConsumer = find(updatedConsumer.getId());
if (existingConsumer == null) {
return this.create(updatedConsumer);
}
// TODO: Are any of these read-only?
existingConsumer.setEntitlements(entitlementCurator.bulkUpdate(updatedConsumer.getEntitlements()));
// This set of updates is strange. We're ignoring the "null-as-no-change" semantics we use
// everywhere else, and just blindly copying everything over.
existingConsumer.setFacts(updatedConsumer.getFacts());
existingConsumer.setName(updatedConsumer.getName());
existingConsumer.setOwner(updatedConsumer.getOwner());
existingConsumer.setType(updatedConsumer.getType());
existingConsumer.setUuid(updatedConsumer.getUuid());
if (flush) {
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) {
String hql = "UPDATE Consumer c SET c.lastCheckin = :date, c.updated = :date WHERE c.id = :cid";
this.currentSession().createQuery(hql)
.setTimestamp("date", checkinDate)
.setParameter("cid", consumer.getId())
.executeUpdate();
}
private boolean factsChanged(Map<String, String> updatedFacts, Map<String, String> existingFacts) {
return !existingFacts.equals(updatedFacts);
}
/**
* Validates the facts associated with the given consumer. If any fact fails validation a
* PropertyValidationException will be thrown.
*
* @param consumer
* The consumer containing the facts to validate
*/
private void validateFacts(Consumer consumer) {
// Impl note:
// Unlike the previous implementation, we are no longer attempting to "fix" anything here;
// if it's broken at this point, we're in trouble, so we're going to throw an exception
// instead of waiting for CP to die with a DB exception sometime in the very near future.
//
// Also, we're no longer using ConfigProperties.CONSUMER_FACTS_MATCHER at this point, as
// it's something that belongs with the other input validation and filtering.
Map<String, String> facts = consumer.getFacts();
if (facts != null) {
for (Entry<String, String> fact : facts.entrySet()) {
this.factValidator.validate(fact.getKey(), fact.getValue());
}
}
}
/**
* @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.
*
* The first lookup will retrieve the host and then place it in the map. This
* will save from reloading the host from the database if it is asked for again
* during the session. An auto-bind can call this method up to 50 times and this
* will cut the database calls significantly.
*
* @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) {
String guestLower = guestId.toLowerCase();
if (cachedHosts.containsKey(guestLower)) {
return cachedHosts.get(guestLower);
}
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))
.add(guestIdCrit)
.addOrder(Order.desc("updated"))
.setMaxResults(1)
.setProjection(Projections.property("consumer"));
Consumer host = (Consumer) crit.uniqueResult();
cachedHosts.put(guestLower, host);
return host;
}
/**
* 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();
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 CandlepinQuery<Consumer> getHypervisorsBulk(Iterable<String> hypervisorIds, String ownerKey) {
if (hypervisorIds == null || !hypervisorIds.iterator().hasNext()) {
return this.cpQueryFactory.<Consumer>buildQuery();
}
DetachedCriteria criteria = DetachedCriteria.forClass(Consumer.class)
.createAlias("owner", "o")
.createAlias("hypervisorId", "hvsr")
.add(Restrictions.eq("o.key", ownerKey))
.add(this.getHypervisorIdRestriction(hypervisorIds))
.addOrder(Order.asc("hvsr.hypervisorId"));
return this.cpQueryFactory.<Consumer>buildQuery(this.currentSession(), criteria)
.setLockMode(LockModeType.PESSIMISTIC_WRITE);
}
private Criterion getHypervisorIdRestriction(Iterable<String> hypervisorIds) {
Disjunction disjunction = Restrictions.disjunction();
for (String hid : hypervisorIds) {
disjunction.add(Restrictions.eq("hvsr.hypervisorId", hid.toLowerCase()));
}
return disjunction;
}
@SuppressWarnings("unchecked")
@Transactional
public CandlepinQuery<Consumer> getHypervisorsForOwner(String ownerKey) {
DetachedCriteria criteria = this.createSecureDetachedCriteria()
.createAlias("owner", "o")
.createAlias("hypervisorId", "hvsr")
.add(Restrictions.eq("o.key", ownerKey))
.add(Restrictions.isNotNull("hvsr.hypervisorId"));
return this.cpQueryFactory.<Consumer>buildQuery(this.currentSession(), criteria);
}
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().getProductAttributes());
Hibernate.initialize(e.getPool().getAttributes());
Hibernate.initialize(e.getPool().getDerivedProductAttributes());
}
}
return consumer;
}
@SuppressWarnings("checkstyle:indentation")
public CandlepinQuery<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) {
DetachedCriteria crit = super.createSecureDetachedCriteria();
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")
.createAlias("attributes", "attrib")
.add(Restrictions.eq("id", sku))
.add(Restrictions.eq("attrib.indices", "type"))
.add(Restrictions.eq("attrib.elements", "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 this.cpQueryFactory.<Consumer>buildQuery(this.currentSession(), crit);
}
/*
* JPQL of below criteria can look like this.
* If all parameters aren't passed then only sub-parts of it are returned.
*
* select count(distinct c.id) from Consumer c join c.owner o
* join c.type ct
* join c.entitlements e
* join e.pool po
* join po.product pr
* join pr.attributes pa
* join po.sourceSubscription ss
* where o.key = :key
* and ct.label in (...)
* and pr.id in (...)
* and pa.name = 'type'
* and pa.value = 'MKT'
* and ss.subscriptionId in (...)
* and po.contractNumber in (...)
*
*/
public int countConsumers(String ownerKey,
Collection<String> typeLabels, Collection<String> skus,
Collection<String> subscriptionIds, Collection<String> contracts) {
if (ownerKey == null || ownerKey.isEmpty()) {
throw new IllegalArgumentException("Owner key can't be null or empty");
}
Criteria crit = super.createSecureCriteria("c");
crit.createAlias("c.owner", "o")
.add(Restrictions.eq("o.key", ownerKey));
if (!CollectionUtils.isEmpty(typeLabels)) {
crit.createAlias("c.type", "ct")
.add(Restrictions.in("ct.label", typeLabels));
}
boolean hasSkus = !CollectionUtils.isEmpty(skus);
boolean hasSubscriptionIds = !CollectionUtils.isEmpty(subscriptionIds);
boolean hasContracts = !CollectionUtils.isEmpty(contracts);
if (hasSkus || hasSubscriptionIds || hasContracts) {
crit.createAlias("c.entitlements", "e").createAlias("e.pool", "po");
}
if (hasSkus) {
crit.createAlias("po.product", "pr")
.createAlias("pr.attributes", "pa")
.add(Restrictions.in("pr.id", skus))
.add(Restrictions.eq("pa.indices", "type"))
.add(Restrictions.eq("pa.elements", "MKT"));
}
if (hasSubscriptionIds) {
crit.createAlias("po.sourceSubscription", "ss")
.add(Restrictions.in("ss.subscriptionId", subscriptionIds));
}
if (hasContracts) {
crit.add(Restrictions.in("po.contractNumber", contracts));
}
crit.setProjection(Projections.countDistinct("c.id"));
return ((Long) crit.uniqueResult()).intValue();
}
/**
* 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 = this.createSecureCriteria()
.add(Restrictions.eq("owner", owner))
.add(Restrictions.eq("type", type))
.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();
}
}