/**
* 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 com.google.inject.Inject;
import com.google.inject.Provider;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.LikeExpression;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;
import org.hibernate.type.StringType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.persistence.EntityManager;
/**
* OwnerInfoCurator
*/
public class OwnerInfoCurator {
private static Logger log = LoggerFactory.getLogger(OwnerInfoCurator.class);
private Provider<EntityManager> entityManager;
private ConsumerTypeCurator consumerTypeCurator;
private ConsumerCurator consumerCurator;
private PoolCurator poolCurator;
@Inject
public OwnerInfoCurator(Provider<EntityManager> entityManager,
ConsumerCurator consumerCurator, ConsumerTypeCurator consumerTypeCurator,
PoolCurator poolCurator) {
this.entityManager = entityManager;
this.consumerCurator = consumerCurator;
this.consumerTypeCurator = consumerTypeCurator;
this.poolCurator = poolCurator;
}
public OwnerInfo lookupByOwner(Owner owner) {
OwnerInfo info = new OwnerInfo();
Date now = new Date();
// TODO:
// Make sure this doesn't choke on MySQL, since we're doing queries with the cursor open.
List<ConsumerType> types = consumerTypeCurator.listAll().list();
HashMap<String, ConsumerType> typeHash = new HashMap<String, ConsumerType>();
for (ConsumerType type : types) {
// Store off the type for later use
typeHash.put(type.getLabel(), type);
// Do the real work
int consumers = consumerCurator.getConsumerCount(owner, type);
int entCount = consumerCurator.getConsumerEntitlementCount(owner, type);
info.addTypeTotal(type, consumers, entCount);
int count = getRequiresConsumerTypeCount(type, owner, now);
info.addToConsumerTypeCountByPool(type, count);
count = getEnabledConsumerTypeCount(type, owner, now);
if (count > 0) {
info.addToEnabledConsumerTypeCountByPool(type, count);
}
}
int activePools = getActivePoolCount(owner, now);
info.addDefaultEnabledConsumerTypeCount(activePools);
Collection<String> families = getProductFamilies(owner, now);
for (String family : families) {
int virtualCount = getProductFamilyCount(owner, now, family, true);
int totalCount = getProductFamilyCount(owner, now, family, false);
info.addToEntitlementsConsumedByFamily(family, totalCount - virtualCount, virtualCount);
}
int virtTotalEntitlements = getProductFamilyCount(owner, now, null, true);
int totalEntitlements = getProductFamilyCount(owner, now, null, false);
info.addDefaultEntitlementsConsumedByFamily(
totalEntitlements - virtTotalEntitlements,
virtTotalEntitlements);
setConsumerGuestCounts(owner, info);
setConsumerCountsByComplianceStatus(owner, info);
return info;
}
@SuppressWarnings("unchecked")
private void setConsumerGuestCounts(Owner owner, OwnerInfo info) {
Criteria cr = consumerCurator.createSecureCriteria()
.createAlias("facts", "f")
.add(Restrictions.eq("owner", owner))
.add(Restrictions.ilike("f.indices", "virt.is_guest"))
.add(Restrictions.ilike("f.elements", "true"))
.setProjection(Projections.count("id"));
int guestCount = ((Long) cr.uniqueResult()).intValue();
Criteria totalConsumersCriteria = consumerCurator.createSecureCriteria()
.add(Restrictions.eq("owner", owner))
.setProjection(Projections.count("id"));
int totalConsumers = ((Long) totalConsumersCriteria.uniqueResult()).intValue();
int physicalCount = totalConsumers - guestCount;
info.setGuestCount(guestCount);
info.setPhysicalCount(physicalCount);
}
@SuppressWarnings("checkstyle:indentation")
private void setConsumerCountsByComplianceStatus(Owner owner, OwnerInfo info) {
Criteria countCriteria = consumerCurator.createSecureCriteria()
.createAlias("type", "t")
.add(Restrictions.eq("owner", owner))
.add(Restrictions.isNotNull("entitlementStatus"))
.setProjection(Projections.projectionList()
.add(Projections.groupProperty("entitlementStatus"))
.add(Projections.count("id")));
List<Object[]> results = countCriteria.list();
for (Object[] row : results) {
String status = (String) row[0];
Integer count = ((Long) row[1]).intValue();
info.setConsumerCountByComplianceStatus(status, count);
}
}
private int getActivePoolCount(Owner owner, Date date) {
Criteria activePoolCountCrit = poolCurator.createSecureCriteria()
.add(Restrictions.eq("owner", owner))
.add(Restrictions.le("startDate", date))
.add(Restrictions.ge("endDate", date))
.setProjection(Projections.count("id"));
return ((Long) activePoolCountCrit.uniqueResult()).intValue();
}
@SuppressWarnings("checkstyle:indentation")
private int getRequiresConsumerTypeCount(ConsumerType type, Owner owner, Date date) {
Criteria criteria = poolCurator.createSecureCriteria("Pool")
.createAlias("product", "Product")
.setProjection(Projections.countDistinct("Pool.id"));
criteria.add(Restrictions.eq("owner", owner))
.add(Restrictions.le("startDate", date))
.add(Restrictions.ge("endDate", date))
.add(this.addAttributeFilterSubquery(Pool.Attributes.REQUIRES_CONSUMER_TYPE, Arrays.asList(
type.getLabel()
)));
return ((Long) criteria.uniqueResult()).intValue();
}
@SuppressWarnings("checkstyle:indentation")
private int getEnabledConsumerTypeCount(ConsumerType type, Owner owner, Date date) {
Criteria criteria = poolCurator.createSecureCriteria("Pool")
.createAlias("product", "Product")
.setProjection(Projections.countDistinct("Pool.id"));
criteria.add(Restrictions.eq("owner", owner))
.add(Restrictions.le("startDate", date))
.add(Restrictions.ge("endDate", date))
.add(this.addAttributeFilterSubquery(Pool.Attributes.ENABLED_CONSUMER_TYPES, Arrays.asList(
type.getLabel() + ",*", "*," + type.getLabel(), "*," + type.getLabel() + ",*", type.getLabel()
)));
return ((Long) criteria.uniqueResult()).intValue();
}
@SuppressWarnings("checkstyle:indentation")
private Criterion addAttributeFilterSubquery(String key, Collection<String> values) {
key = this.sanitizeMatchesFilter(key);
// Find all pools which have the given attribute (and values) on a product, unless the pool
// defines that same attribute
DetachedCriteria poolAttrSubquery = DetachedCriteria.forClass(Pool.class, "PoolI")
.createAlias("PoolI.attributes", "attrib")
.setProjection(Projections.id())
.add(Property.forName("Pool.id").eqProperty("PoolI.id"))
.add(new CPLikeExpression("attrib.indices", key, '!', false));
// Impl note:
// The SQL restriction below uses some Hibernate magic value to get the pool ID from the
// outer-most query. We can't use {alias} here, since we're basing the subquery on Product
// instead of Pool to save ourselves an unnecessary join. Similarly, we use an SQL
// restriction here because we can query the information we need, hitting only one table
// with direct SQL, whereas the matching criteria query would end up requiring a minimum of
// one join to get from pool to pool attributes.
DetachedCriteria prodAttrSubquery = DetachedCriteria.forClass(Product.class, "ProdI")
.createAlias("ProdI.attributes", "attrib")
.setProjection(Projections.id())
.add(Property.forName("Product.uuid").eqProperty("ProdI.uuid"))
.add(new CPLikeExpression("attrib.indices", key, '!', false))
.add(Restrictions.sqlRestriction(
"NOT EXISTS (SELECT poolattr.pool_id FROM cp_pool_attribute poolattr " +
"WHERE poolattr.pool_id = this_.id AND LOWER(poolattr.name) LIKE LOWER(?) ESCAPE '!')",
key, StringType.INSTANCE
));
if (values != null && !values.isEmpty()) {
Disjunction poolAttrValueDisjunction = Restrictions.disjunction();
Disjunction prodAttrValueDisjunction = Restrictions.disjunction();
for (String attrValue : values) {
if (attrValue == null || attrValue.isEmpty()) {
poolAttrValueDisjunction.add(Restrictions.isNull("attrib.elements"))
.add(Restrictions.eq("attrib.elements", ""));
prodAttrValueDisjunction.add(Restrictions.isNull("attrib.elements"))
.add(Restrictions.eq("attrib.elements", ""));
}
else {
attrValue = this.sanitizeMatchesFilter(attrValue);
poolAttrValueDisjunction.add(
new CPLikeExpression("attrib.elements", attrValue, '!', true));
prodAttrValueDisjunction.add(
new CPLikeExpression("attrib.elements", attrValue, '!', true));
}
}
poolAttrSubquery.add(poolAttrValueDisjunction);
prodAttrSubquery.add(prodAttrValueDisjunction);
}
return Restrictions.or(
Subqueries.exists(poolAttrSubquery),
Subqueries.exists(prodAttrSubquery)
);
}
private String sanitizeMatchesFilter(String matches) {
StringBuilder output = new StringBuilder();
boolean escaped = false;
for (int index = 0; index < matches.length(); ++index) {
char c = matches.charAt(index);
switch (c) {
case '!':
case '_':
case '%':
output.append('!').append(c);
break;
case '\\':
if (escaped) {
output.append(c);
}
escaped = !escaped;
break;
case '*':
case '?':
if (!escaped) {
output.append(c == '*' ? '%' : '_');
break;
}
default:
output.append(c);
escaped = false;
}
}
return output.toString();
}
// TODO: Move this to the CPRestrictions class (as a pair of .like and .ilike methods) once
// this branch and the branch that contain it are merged together.
private static class CPLikeExpression extends LikeExpression {
public CPLikeExpression(String property, String value, char escape, boolean ignoreCase) {
super(property, value, escape, ignoreCase);
}
}
private Collection<String> getProductFamilies(Owner owner, Date date) {
Set<String> families = new HashSet<String>();
String queryStr = "select distinct value(attr) from Pool p " +
"join p.attributes as attr " +
"where p.owner = :owner " +
"and p.startDate < :date and p.endDate > :date " +
"and key(attr) = :attribute";
Query query = currentSession().createQuery(queryStr)
.setEntity("owner", owner)
.setParameter("date", date)
.setParameter("attribute", Pool.Attributes.PRODUCT_FAMILY);
Iterator iter = query.iterate();
while (iter.hasNext()) {
String family = (String) iter.next();
families.add(family);
}
queryStr = "select distinct value(prod) from Pool p " +
"join p.product.attributes as prod " +
"where p.owner = :owner " +
"and p.startDate < :date and p.endDate > :date " +
"and key(prod) = :attribute " +
"and p not in (SELECT DISTINCT p2 FROM Pool p2 JOIN p2.attributes AS attr2 " +
" WHERE key(attr2) = :attribute)";
query = currentSession().createQuery(queryStr)
.setEntity("owner", owner)
.setParameter("date", date)
.setParameter("attribute", Pool.Attributes.PRODUCT_FAMILY);
iter = query.iterate();
while (iter.hasNext()) {
String family = (String) iter.next();
families.add(family);
}
return families;
}
/*
* If called with virt = false, returns the total number of all entitlements in
* that family for that owner. So you probably want to subtract the result of
* virt=true from that.
*
* use family = null to get counts for all pools.
*/
private int getProductFamilyCount(Owner owner, Date date, String family, boolean virt) {
Criteria criteria = poolCurator.createSecureCriteria("Pool")
.createAlias("entitlements", "Ent")
.createAlias("product", "Product")
.setProjection(Projections.sum("Ent.quantity"));
criteria.add(Restrictions.eq("owner", owner))
.add(Restrictions.le("startDate", date))
.add(Restrictions.ge("endDate", date));
if (family != null) {
criteria.add(this.addAttributeFilterSubquery(
Pool.Attributes.PRODUCT_FAMILY, Arrays.asList(family)
));
}
if (virt) {
criteria.add(this.addAttributeFilterSubquery(Pool.Attributes.VIRT_ONLY, Arrays.asList("true")));
}
Long res = (Long) criteria.uniqueResult();
if (res == null) {
return 0;
}
return res != null ? res.intValue() : 0;
}
protected Session currentSession() {
Session sess = (Session) entityManager.get().getDelegate();
return sess;
}
}