/** * 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.paging.Page; import org.candlepin.common.paging.PageRequest; import org.candlepin.model.Pool.PoolType; import org.candlepin.model.activationkeys.ActivationKey; import org.candlepin.model.activationkeys.ActivationKeyPool; import com.google.common.collect.Iterables; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.persist.Transactional; import org.hibernate.Criteria; import org.hibernate.FetchMode; import org.hibernate.Filter; 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.Junction; import org.hibernate.criterion.Order; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Property; import org.hibernate.criterion.Restrictions; import org.hibernate.criterion.SimpleExpression; import org.hibernate.criterion.Subqueries; import org.hibernate.internal.FilterImpl; import org.hibernate.sql.JoinType; import org.hibernate.type.StringType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; 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 java.util.TreeSet; import javax.persistence.TypedQuery; /** * PoolCurator */ public class PoolCurator extends AbstractHibernateCurator<Pool> { /** The recommended number of expired pools to fetch in a single call to listExpiredPools */ public static final int EXPIRED_POOL_BLOCK_SIZE = 2048; private static Logger log = LoggerFactory.getLogger(PoolCurator.class); private ConsumerCurator consumerCurator; @Inject protected Injector injector; @Inject public PoolCurator(ConsumerCurator consumerCurator) { super(Pool.class); this.consumerCurator = consumerCurator; } @Override @Transactional public Pool find(Serializable id) { Pool pool = super.find(id); return pool; } /** * Returns list of pools owned by the given Owner. * @param owner Owner to filter * @return pools owned by the given Owner. */ @Transactional public CandlepinQuery<Pool> listByOwner(Owner owner) { return listByOwner(owner, null); } /** * Returns list of pools owned by the given Owner. * @param owner Owner to filter * @param activeOn only include pools active on the given date. * @return pools owned by the given Owner. */ @Transactional public CandlepinQuery<Pool> listByOwner(Owner owner, Date activeOn) { DetachedCriteria criteria = DetachedCriteria.forClass(Pool.class) .add(Restrictions.eq("owner", owner)) .add(Restrictions.eq("activeSubscription", Boolean.TRUE)); if (activeOn != null) { criteria.add(Restrictions.le("startDate", activeOn)) .add(Restrictions.ge("endDate", activeOn)); } return this.cpQueryFactory.<Pool>buildQuery(this.currentSession(), criteria); } @Transactional public CandlepinQuery<Pool> listByOwnerAndType(Owner owner, PoolType type) { DetachedCriteria criteria = DetachedCriteria.forClass(Pool.class) .add(Restrictions.eq("owner", owner)) .add(Restrictions.eq("type", type)); return this.cpQueryFactory.<Pool>buildQuery(this.currentSession(), criteria); } /** * Return all pools referencing the given entitlement as their source entitlement. * * @param e Entitlement * @return Pools created as a result of this entitlement. */ public CandlepinQuery<Pool> listBySourceEntitlement(Entitlement e) { DetachedCriteria criteria = this.createSecureDetachedCriteria() .add(Restrictions.eq("sourceEntitlement", e)); return this.cpQueryFactory.<Pool>buildQuery(this.currentSession(), criteria); } /** * Return all pools referencing the given entitlements as their source entitlements. * Works recursively. The method always takes the result and return all source entitlements * of the pools. * This method finds all the pools that have been created as direct consequence of creating * some of ents. So for example bonus pools created as consequence of creating ents. * @param ents Entitlements for which we search the pools * @return Pools created as a result of creation of one of the ents. */ public List<Pool> listBySourceEntitlements(List<Entitlement> ents) { if (ents.size() == 0) { return new ArrayList<Pool>(); } List<Pool> results = createSecureCriteria() .add(CPRestrictions.in("sourceEntitlement", ents)) .setFetchMode("entitlements", FetchMode.JOIN) .list(); if (results == null) { results = new LinkedList<Pool>(); } if (results.size() > 0) { List<Pool> pools = listBySourceEntitlements(convertPoolsToEntitlements(results)); results.addAll(pools); } return results; } private List<Entitlement> convertPoolsToEntitlements(List<Pool> pools) { List<Entitlement> result = new ArrayList<Entitlement>(); for (Pool p : pools) { result.addAll(p.getEntitlements()); } return result; } /** * Returns list of pools available to the consumer. * * @param c Consumer to filter * @return pools available to the consumer. */ @Transactional public List<Pool> listByConsumer(Consumer c) { return listAvailableEntitlementPools(c, c.getOwner(), (Set<String>) null, null); } /** * List all entitlement pools for the given owner and product. * * @param owner owner of the entitlement pool * @param productId product filter. * @return list of EntitlementPools */ @Transactional public List<Pool> listByOwnerAndProduct(Owner owner, String productId) { return listAvailableEntitlementPools(null, owner, productId, null); } /** * Fetches the list of non-derived, expired pools * * @return * the list of non-derived, expired pools pools */ public List<Pool> listExpiredPools() { return this.listExpiredPools(-1); } /** * Fetches a block of non-derived, expired pools from the database, using the specified block * size. If the given block size is a non-positive integer, all non-derived, expired pools will * be retrieved. * <p></p> * <strong>Note:</strong> This method does not set the offset (first result) for the block. * Unless the pools are being deleted, this method will repeatedly return the same pools. To * fetch all expired pools in blocks, the calling method must ensure that the pools are deleted * between calls to this method. * * @param blockSize * The maximum number of pools to fetch; if block size is less than 1, no limit will be applied * * @return * a list of non-derived, expired pools no larger than the specified block size */ @Transactional @SuppressWarnings("checkstyle:indentation") public List<Pool> listExpiredPools(int blockSize) { Date now = new Date(); DetachedCriteria entCheck = DetachedCriteria.forClass(Pool.class, "entPool") .createAlias("entitlements", "ent", JoinType.INNER_JOIN) .add(Restrictions.eqProperty("entPool.id", "tgtPool.id")) .add(Restrictions.ge("ent.endDateOverride", now)) .setProjection(Projections.property("entPool.id")); Criteria criteria = this.createSecureCriteria("tgtPool") .add(Restrictions.lt("tgtPool.endDate", now)) .add(Subqueries.notExists(entCheck)); if (blockSize > 0) { criteria.setMaxResults(blockSize); } List<Pool> results = (List<Pool>) criteria.list(); return results != null ? results : new LinkedList<Pool>(); } @SuppressWarnings("unchecked") @Transactional public List<Pool> listAvailableEntitlementPools(Consumer c, Owner o, String productId, Date activeOn) { return listAvailableEntitlementPools(c, o, (productId != null ? Arrays.asList(productId) : (Collection<String>) null), null, activeOn, new PoolFilterBuilder(), null, false, false, false).getPageData(); } @SuppressWarnings("unchecked") @Transactional public List<Pool> listAvailableEntitlementPools(Consumer c, Owner o, Collection<String> productIds, Date activeOn) { return listAvailableEntitlementPools(c, o, productIds, null, activeOn, new PoolFilterBuilder(), null, false, false, false).getPageData(); } @Transactional public List<Pool> listByFilter(PoolFilterBuilder filters) { return listAvailableEntitlementPools( null, null, (Set<String>) null, null, null, filters, null, false, false, false).getPageData(); } @Transactional public Page<List<Pool>> listAvailableEntitlementPools(Consumer c, Owner o, String productId, String subscriptionId, Date activeOn, PoolFilterBuilder filters, PageRequest pageRequest, boolean postFilter, boolean addFuture, boolean onlyFuture) { return this.listAvailableEntitlementPools(c, o, (productId != null ? Arrays.asList(productId) : (Collection<String>) null), subscriptionId, activeOn, filters, pageRequest, postFilter, addFuture, onlyFuture); } /** * List entitlement pools. * * Pools will be refreshed from the underlying subscription service. * * @param consumer Consumer being entitled. * @param owner Owner whose subscriptions should be inspected. * @param productIds only entitlements which provide these products are included. * @param activeOn Indicates to return only pools valid on this date. * Set to null for no date filtering. * @param filters filter builder with set filters to apply to the criteria. * @param pageRequest used to specify paging criteria. * @param postFilter if you plan on filtering the list in java * @return List of entitlement pools. */ @Transactional @SuppressWarnings({"unchecked", "checkstyle:indentation", "checkstyle:methodlength"}) // TODO: Remove the methodlength suppression once this method is cleaned up public Page<List<Pool>> listAvailableEntitlementPools(Consumer consumer, Owner owner, Collection<String> productIds, String subscriptionId, Date activeOn, PoolFilterBuilder filters, PageRequest pageRequest, boolean postFilter, boolean addFuture, boolean onlyFuture) { if (log.isDebugEnabled()) { log.debug("Listing available pools for:"); log.debug(" consumer: {}", consumer); log.debug(" owner: {}", owner); log.debug(" products: {}", productIds); log.debug(" subscription: {}", subscriptionId); log.debug(" active on: {}", activeOn); } boolean joinedProvided = false; Criteria criteria = this.createSecureCriteria("Pool") .createAlias("product", "Product") .setProjection(Projections.distinct(Projections.id())); if (consumer != null) { // Impl note: This block was inherited from the current implementation of the // CriteriaRules.availableEntitlementCriteria method. if (owner != null && !owner.equals(consumer.getOwner())) { // Both a consumer and an owner were specified, but the consumer belongs to a different owner. // We can't possibly match a pool on two owners, so we can just abort immediately with an // empty page log.warn("Attempting to filter entitlement pools by owner and a consumer belonging to a " + "different owner: {}, {}", owner, consumer); Page<List<Pool>> output = new Page<List<Pool>>(); output.setPageData(Collections.<Pool>emptyList()); output.setMaxRecords(0); return output; } // We'll set the owner restriction later owner = consumer.getOwner(); if (consumer.isManifestDistributor()) { DetachedCriteria hostPoolSubquery = DetachedCriteria.forClass(Pool.class, "PoolI") .createAlias("PoolI.attributes", "attrib") .setProjection(Projections.id()) .add(Property.forName("Pool.id").eqProperty("PoolI.id")) .add(Restrictions.eq("attrib.indices", Pool.Attributes.REQUIRES_HOST)); criteria.add(Subqueries.notExists(hostPoolSubquery)); } else if (!consumer.isGuest()) { criteria.add(Restrictions.not( this.addAttributeFilterSubquery(Pool.Attributes.VIRT_ONLY, Arrays.asList("true")) )); } else if (consumer.hasFact("virt.uuid")) { Consumer host = null; String uuidFact = consumer.getFact("virt.uuid"); if (uuidFact != null) { host = this.consumerCurator.getHost(uuidFact, owner); } // Impl note: // This query matches pools with the "requires_host" attribute explicitly set to a // value other than the host we're looking for. We then negate the results of this // subquery, so our final result is: fetch pools which do not have a required host // or have a required host equal to our host. // TODO: If we don't have a host, should this be filtering at all? Seems strange to // be filtering pools which have a null/empty required host value. Probably just // wasted cycles. DetachedCriteria hostPoolSubquery = DetachedCriteria.forClass(Pool.class, "PoolI") .createAlias("PoolI.attributes", "attrib") .setProjection(Projections.id()) .add(Property.forName("Pool.id").eqProperty("PoolI.id")) .add(Restrictions.eq("attrib.indices", Pool.Attributes.REQUIRES_HOST)) .add(Restrictions.ne("attrib.elements", host != null ? host.getUuid() : "").ignoreCase()); criteria.add(Subqueries.notExists(hostPoolSubquery)); } } if (owner != null) { criteria.add(Restrictions.eq("Pool.owner", owner)); } if (activeOn != null) { if (onlyFuture) { criteria.add(Restrictions.ge("Pool.startDate", activeOn)); } else if (!addFuture) { criteria.add(Restrictions.le("Pool.startDate", activeOn)); criteria.add(Restrictions.ge("Pool.endDate", activeOn)); } else { criteria.add(Restrictions.ge("Pool.endDate", activeOn)); } } // TODO: This section is sloppy. If we're going to clobber the bits in the filter with our own input // parameters, why bother accepting a filter to begin with? Similarly, why bother accepting a filter // if the method takes the arguments directly? If we're going to abstract out the filtering bits, we // should go all-in, cut down on the massive argument list and simply take a single filter object. -C // Subscription ID filter String value = subscriptionId == null && filters != null ? filters.getSubscriptionIdFilter() : subscriptionId; if (value != null && !value.isEmpty()) { criteria.createAlias("Pool.sourceSubscription", "srcsub") .add(Restrictions.eq("srcsub.subscriptionId", value)); } // Product ID filters Collection<String> values = productIds == null && filters != null ? filters.getProductIdFilter() : productIds; if (values != null && !values.isEmpty()) { if (!joinedProvided) { criteria.createAlias("Pool.providedProducts", "Provided", JoinType.LEFT_OUTER_JOIN); joinedProvided = true; } criteria.add(Restrictions.or( CPRestrictions.in("Product.id", values), CPRestrictions.in("Provided.id", values) )); } if (filters != null) { // Pool ID filters values = filters.getIdFilters(); if (values != null && !values.isEmpty()) { criteria.add(CPRestrictions.in("Pool.id", values)); } // Matches stuff values = filters.getMatchesFilters(); if (values != null && !values.isEmpty()) { if (!joinedProvided) { // This was an inner join -- might end up being important later criteria.createAlias("Pool.providedProducts", "Provided", JoinType.LEFT_OUTER_JOIN); joinedProvided = true; } criteria.createAlias("Provided.productContent", "PPC", JoinType.LEFT_OUTER_JOIN); criteria.createAlias("PPC.content", "Content", JoinType.LEFT_OUTER_JOIN); for (String matches : values) { String sanitized = this.sanitizeMatchesFilter(matches); Disjunction matchesDisjunction = Restrictions.disjunction(); matchesDisjunction.add(CPRestrictions.ilike("Pool.contractNumber", sanitized, '!')) .add(CPRestrictions.ilike("Pool.orderNumber", sanitized, '!')) .add(CPRestrictions.ilike("Product.id", sanitized, '!')) .add(CPRestrictions.ilike("Product.name", sanitized, '!')) .add(CPRestrictions.ilike("Provided.id", sanitized, '!')) .add(CPRestrictions.ilike("Provided.name", sanitized, '!')) .add(CPRestrictions.ilike("Content.name", sanitized, '!')) .add(CPRestrictions.ilike("Content.label", sanitized, '!')) .add(this.addProductAttributeFilterSubquery(Product.Attributes.SUPPORT_LEVEL, Arrays.asList(matches))); criteria.add(matchesDisjunction); } } // Attribute filters for (Map.Entry<String, List<String>> entry : filters.getAttributeFilters().entrySet()) { String attrib = entry.getKey(); values = entry.getValue(); if (attrib != null && !attrib.isEmpty()) { // TODO: // Searching both pool and product attributes is likely an artifact from the days // when we copied SKU product attributes to the pool. I don't believe there's any // precedence for attribute lookups now that they're no longer being copied over. // If this is not the case, then the following logic is broken and will need to be // adjusted to account for one having priority over the other. if (values != null && !values.isEmpty()) { List<String> positives = new LinkedList<String>(); List<String> negatives = new LinkedList<String>(); for (String attrValue : values) { if (attrValue.startsWith("!")) { negatives.add(attrValue.substring(1)); } else { positives.add(attrValue); } } if (!positives.isEmpty()) { criteria.add(this.addAttributeFilterSubquery(attrib, positives)); } if (!negatives.isEmpty()) { criteria.add(Restrictions.not( this.addAttributeFilterSubquery(attrib, negatives))); } } else { criteria.add(this.addAttributeFilterSubquery(attrib, values)); } } } } // Impl note: // Hibernate has an issue with properly hydrating objects within collections of the pool // when only a subset of the collection matches the criteria. To work around this, we pull // the ID list from the main filtering query, then pull the pools again using the ID list. // This also makes it easier to eventually start using a cursor, since the distinct entity // functionality doesn't work with cursors. List<String> poolIds = criteria.list(); if (poolIds != null && !poolIds.isEmpty()) { criteria = this.currentSession() .createCriteria(Pool.class) .add(CPRestrictions.in("id", poolIds)); return this.listByCriteria(criteria, pageRequest, postFilter); } Page<List<Pool>> output = new Page<List<Pool>>(); output.setPageData(Collections.<Pool>emptyList()); output.setMaxRecords(0); return output; } @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(Restrictions.eq("attrib.indices", key)); // 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(Restrictions.eq("attrib.indices", key)) .add(Restrictions.sqlRestriction( "NOT EXISTS (SELECT poolattr.pool_id FROM cp_pool_attribute poolattr " + "WHERE poolattr.pool_id = this_.id AND poolattr.name = ?)", 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(CPRestrictions.ilike("attrib.elements", attrValue, '!')); prodAttrValueDisjunction.add(CPRestrictions.ilike("attrib.elements", attrValue, '!')); } } poolAttrSubquery.add(poolAttrValueDisjunction); prodAttrSubquery.add(prodAttrValueDisjunction); } return Restrictions.or( Subqueries.exists(poolAttrSubquery), Subqueries.exists(prodAttrSubquery) ); } @SuppressWarnings("checkstyle:indentation") private Criterion addProductAttributeFilterSubquery(String key, Collection<String> values) { // Find all pools which have the given attribute (and values) on a product, unless the pool // defines that same attribute // 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(Restrictions.eq("attrib.indices", key)) .add(Restrictions.sqlRestriction( "NOT EXISTS (SELECT poolattr.pool_id FROM cp_pool_attribute poolattr " + "WHERE poolattr.pool_id = this_.id AND poolattr.name = ?)", key, StringType.INSTANCE )); if (values != null && !values.isEmpty()) { Disjunction prodAttrValueDisjunction = Restrictions.disjunction(); for (String attrValue : values) { if (attrValue == null || attrValue.isEmpty()) { prodAttrValueDisjunction.add(Restrictions.isNull("attrib.elements")) .add(Restrictions.eq("attrib.elements", "")); } else { attrValue = this.sanitizeMatchesFilter(attrValue); prodAttrValueDisjunction.add( CPRestrictions.ilike("attrib.elements", attrValue, '!')); } } prodAttrSubquery.add(prodAttrValueDisjunction); } return 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(); } /** * Determine if owner has at least one active pool * * @param o Owner whose subscriptions should be inspected. * @param date The date to test the active state. * Set to null for current. * @return boolean is active on test date. */ @SuppressWarnings("unchecked") @Transactional public boolean hasActiveEntitlementPools(Owner o, Date date) { if (o == null) { return false; } if (date == null) { date = new Date(); } Criteria crit = createSecureCriteria(); crit.add(Restrictions.eq("activeSubscription", Boolean.TRUE)); crit.add(Restrictions.eq("owner", o)); crit.add(Restrictions.le("startDate", date)); crit.add(Restrictions.ge("endDate", date)); crit.setProjection(Projections.rowCount()); long count = (Long) crit.uniqueResult(); return count > 0; } @Transactional public List<Pool> listPoolsRestrictedToUser(String username) { return listByCriteria( currentSession().createCriteria(Pool.class) .add(Restrictions.eq("restrictedToUsername", username))); } @SuppressWarnings("unchecked") public List<Entitlement> retrieveFreeEntitlementsOfPools(List<Pool> existingPools, boolean lifo) { return criteriaToSelectEntitlementForPools(existingPools) .addOrder(lifo ? Order.desc("created") : Order.asc("created")) .list(); } @SuppressWarnings("unchecked") public List<String> retrieveFreeEntitlementIdsOfPool(Pool existingPool, boolean lifo) { return criteriaToSelectEntitlementForPool(existingPool) .addOrder(lifo ? Order.desc("created") : Order.asc("created")) .setProjection(Projections.id()) .list(); } public void deactivatePool(Pool pool) { if (log.isDebugEnabled()) { log.info("Subscription disappeared for pool: " + pool); } pool.setActiveSubscription(Boolean.FALSE); merge(pool); } private Criteria criteriaToSelectEntitlementForPool(Pool entitlementPool) { return this.currentSession().createCriteria(Entitlement.class) .add(Restrictions.eq("pool", entitlementPool)); } private Criteria criteriaToSelectEntitlementForPools(List<Pool> entitlementPools) { return this.currentSession().createCriteria(Entitlement.class) .add(CPRestrictions.in("pool", entitlementPools)); } /** * @param entitlementPool entitlement pool to search. * @return entitlements in the given pool. */ @Transactional @SuppressWarnings("unchecked") public List<Entitlement> entitlementsIn(Pool entitlementPool) { return criteriaToSelectEntitlementForPool(entitlementPool).list(); } /** * Query pools by the subscription that generated them. * * @param owner The owner of the subscriptions to query * @param subId Subscription to look up pools by * @return pools from the given subscription, sorted by pool.id to avoid deadlocks */ @SuppressWarnings("unchecked") public List<Pool> lookupBySubscriptionId(Owner owner, String subId) { return createSecureCriteria() .createAlias("sourceSubscription", "sourceSub") .add(Restrictions.eq("owner", owner)) .add(Restrictions.eq("sourceSub.subscriptionId", subId)) .addOrder(Order.asc("id")).list(); } /** * Query pools by the subscriptions that generated them. * * @param owner The owner of the subscriptions to query * @param subIds Subscriptions to look up pools by * @return pools from the given subscriptions, sorted by pool.id to avoid * deadlocks */ @SuppressWarnings("unchecked") public List<Pool> lookupBySubscriptionIds(Owner owner, Collection<String> subIds) { return createSecureCriteria() .createAlias("sourceSubscription", "sourceSub") .add(Restrictions.eq("owner", owner)) .add(CPRestrictions.in("sourceSub.subscriptionId", subIds)) .addOrder(Order.asc("id")) .list(); } /** * Attempts to find pools which are over subscribed after the creation or * modification of the given entitlement. To do this we search for only the * pools related to the subscription ID which could have changed, the two * cases where this can happen are: * 1. Bonus pool (not derived from any entitlement) after a bind. (in cases * such as exporting to downstream) * 2. A derived pool whose source entitlement just had it's quantity * reduced. * This has to be done carefully to avoid potential performance problems * with virt_bonus on-site subscriptions where one pool is created per * physical entitlement. * * @param owner Owner - The owner of the entitlements being passed in. Scoping * this to a single owner prevents performance problems in large datasets. * @param subIdMap Map where key is Subscription ID of the pool, and value * is the Entitlement just created or modified. * @return Pools with too many entitlements for their new quantity. */ @SuppressWarnings("unchecked") public List<Pool> lookupOversubscribedBySubscriptionIds(Owner owner, Map<String, Entitlement> subIdMap) { List<Criterion> subIdMapCriteria = new ArrayList<Criterion>(); Criterion[] exampleCriteria = new Criterion[0]; for (Entry<String, Entitlement> entry : subIdMap.entrySet()) { SimpleExpression subscriptionExpr = Restrictions.eq("sourceSub.subscriptionId", entry.getKey()); Junction subscriptionJunction = Restrictions.and(subscriptionExpr).add( Restrictions.or(Restrictions.isNull("sourceEntitlement"), Restrictions.eqOrIsNull("sourceEntitlement", entry.getValue()))); subIdMapCriteria.add(subscriptionJunction); } return currentSession() .createCriteria(Pool.class) .createAlias("sourceSubscription", "sourceSub") .add(Restrictions.eq("owner", owner)) .add(Restrictions.ge("quantity", 0L)) .add(Restrictions.gtProperty("consumed", "quantity")) .add(Restrictions.or(subIdMapCriteria.toArray(exampleCriteria))).list(); } @Transactional public Pool replicate(Pool pool) { pool.setSourceEntitlement(null); this.currentSession().replicate(pool, ReplicationMode.EXCEPTION); return pool; } @Transactional public Pool create(Pool entity) { /* Ensure all referenced PoolAttributes are correctly pointing to * this pool. This is useful for pools being created from * incoming json. */ return super.create(entity); } private static final String CONSUMER_FILTER = "Entitlement_CONSUMER_FILTER"; @SuppressWarnings("checkstyle:indentation") public int getNoOfDependentEntitlements(String entitlementId) { Filter consumerFilter = disableConsumerFilter(); Integer result = (Integer) currentSession() .createCriteria(Entitlement.class) .setProjection(Projections.rowCount()) .createCriteria("pool") .createCriteria("sourceEntitlement") .add(Restrictions.idEq(entitlementId)) .list() .get(0); enableIfPrevEnabled(consumerFilter); return result; } // TODO: watch out for performance. Should we limit the certificates // retrieved? @SuppressWarnings("unchecked") public List<EntitlementCertificate> retrieveEntCertsOfPoolsWithSourceEntitlement(String entId) { return currentSession().createCriteria(EntitlementCertificate.class) .createCriteria("entitlement") .createCriteria("pool") .createCriteria("sourceEntitlement") .add(Restrictions.idEq(entId)) .list(); } /** * @param session * @param consumerFilter */ private void enableIfPrevEnabled(Filter consumerFilter) { // if filter was previously enabled, restore it. if (consumerFilter != null) { FilterImpl filterImpl = (FilterImpl) consumerFilter; Filter filter = currentSession().enableFilter(CONSUMER_FILTER); filter.setParameter("consumer_id", filterImpl.getParameter("consumer_id")); } } public Filter disableConsumerFilter() { Filter consumerFilter = currentSession().getEnabledFilter(CONSUMER_FILTER); currentSession().disableFilter(CONSUMER_FILTER); return consumerFilter; } public List<ActivationKey> getActivationKeysForPool(Pool p) { List<ActivationKey> activationKeys = new ArrayList<ActivationKey>(); List<ActivationKeyPool> activationKeyPools = currentSession().createCriteria( ActivationKeyPool.class).add(Restrictions.eq("pool", p)).list(); for (ActivationKeyPool akp : activationKeyPools) { activationKeys.add(akp.getKey()); } return activationKeys; } /** * Method to compile service/support level lists. One is the available levels for * consumers for this owner. The second is the level names that are exempt. Exempt * means that a product pool with this level can be used with a consumer of any * service level. * * @param owner The owner that has the list of available service levels for * its consumers * @param exempt boolean to show if the desired list is the levels that are * explicitly marked with the support_level_exempt attribute. * @return Set of levels based on exempt flag. */ public Set<String> retrieveServiceLevelsForOwner(Owner owner, boolean exempt) { String stmt = "SELECT DISTINCT key(Attribute), value(Attribute), Product.id " + "FROM Pool AS Pool " + " INNER JOIN Pool.product AS Product " + " INNER JOIN Product.attributes AS Attribute " + " LEFT JOIN Pool.entitlements AS Entitlement " + "WHERE Pool.owner.id = :owner_id " + " AND (key(Attribute) = :sl_attr OR key(Attribute) = :sle_attr) " + " AND (Pool.endDate >= current_date() OR Entitlement.endDateOverride >= current_date()) " + // Needs to be ordered, because the code below assumes exempt levels are first "ORDER BY key(Attribute) DESC"; Query q = currentSession().createQuery(stmt) .setParameter("owner_id", owner.getId()) .setParameter("sl_attr", Product.Attributes.SUPPORT_LEVEL) .setParameter("sle_attr", Product.Attributes.SUPPORT_LEVEL_EXEMPT); List<Object[]> results = q.list(); // Use case insensitive comparison here, since we treat // Premium the same as PREMIUM or premium, to make it easier for users to specify // a level on the CLI. However, use the original case, since Premium is more // attractive than PREMIUM. Set<String> slaSet = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); Set<String> exemptSlaSet = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); Set<String> exemptProductIds = new HashSet<String>(); for (Object[] result : results) { String name = (String) result[0]; String value = (String) result[1]; String productId = (String) result[2]; if (Product.Attributes.SUPPORT_LEVEL_EXEMPT.equals(name) && "true".equalsIgnoreCase(value)) { exemptProductIds.add(productId); } else if (Product.Attributes.SUPPORT_LEVEL.equalsIgnoreCase(name) && (value != null && !value.trim().equals("")) && exemptProductIds.contains(productId)) { exemptSlaSet.add(value); } } for (Object[] result : results) { String name = (String) result[0]; String value = (String) result[1]; if (!Product.Attributes.SUPPORT_LEVEL_EXEMPT.equals(name) && !exemptSlaSet.contains(value)) { slaSet.add(value); } } if (exempt) { return exemptSlaSet; } return slaSet; } private void deleteImpl(Pool entity) { if (entity != null) { // Before we delete the pool, we need to hydrate the attributes collection. Unlike the // entitlements collection below, we can't just clear the set, since we use the // attributes post-deletion. Also, if we were to ever create a new pool from the now- // deleted pool, having its attributes would be useful. // Impl note: // isPropertyInitialized does not work on attributes, for some reason. Also, Hibernate // lacks a proper/generic way to initialize properties without getting the proxy // directly (which violates proper encapsulation), so we use this not-so-clever // workaround to trigger collection hydration. // TODO: Maybe move this to the places where we actually use the attributes after // deletion? Not sure how much it'll actually save vs the time wasted by future devs // trying to figure out what the random NPE in Hibernate's internals means. entity.getAttributes().size(); // Perform the actual deletion... this.currentSession().delete(entity); // Maintain runtime consistency. The entitlements for the pool have been deleted on the // database because delete is cascaded on Pool.entitlements relation // While it'd be nice to be able to skip this for every pool, we have no guarantee that // the pools came fresh from the DB with uninitialized entitlement collections. Since // it could be initialized, we should clear it so other bits using the pool don't // attempt to use the entitlements. if (Hibernate.isInitialized(entity.getEntitlements())) { entity.getEntitlements().clear(); } } } /** * There is an issue with attributes in the database. Duplicate attribute * name/value pairs were showing up from outside the Candlepin code. * Only one was getting pulled into the HashSet and the other ignored. * Uses explicit lookup and deletion instead of the Hibernate cascade which * relies on the data structure in memory. * * @param entity pool to be deleted. */ @Transactional public void delete(Pool entity) { Pool toDelete = find(entity.getId()); if (toDelete != null) { this.deleteImpl(toDelete); this.flush(); } else { log.debug("Pool {} not found; skipping deletion.", entity.getId()); } } /** * Batch deletes a list of pools. * * @param pools pools to delete * @param alreadyDeletedPools pools to skip, they have already been deleted. */ public void batchDelete(List<Pool> pools, Set<String> alreadyDeletedPools) { if (alreadyDeletedPools == null) { alreadyDeletedPools = new HashSet<String>(); } for (Pool pool : pools) { // As we batch pool operations, pools may be deleted at multiple places in the code path. // We may request to delete the same pool in multiple places too, for example if an expired // stack derived pool has no entitlements, ExpiredPoolsJob will request to delete it twice, // for each reason. if (alreadyDeletedPools.contains(pool.getId())) { continue; } alreadyDeletedPools.add(pool.getId()); this.deleteImpl(pool); } } /** * @param consumer * @param stackIds * @return Derived pools which exist for the given consumer and stack ids */ @SuppressWarnings("checkstyle:indentation") public List<Pool> getSubPoolForStackIds(Consumer consumer, Collection stackIds) { Criteria getPools = createSecureCriteria() .createAlias("sourceStack", "ss") .add(Restrictions.eq("ss.sourceConsumer", consumer)) .add(Restrictions.and( Restrictions.isNotNull("ss.sourceStackId"), CPRestrictions.in("ss.sourceStackId", stackIds)) ); return (List<Pool>) getPools.list(); } @SuppressWarnings("unchecked") public List<Pool> getOwnerSubPoolsForStackId(Owner owner, String stackId) { return createSecureCriteria() .createAlias("sourceStack", "ss") .add(Restrictions.eq("ss.sourceStackId", stackId)) .add(Restrictions.eq("owner", owner)) .list(); } /** * Lookup all pools for subscriptions which are not in the given list of subscription * IDs. Used for pool cleanup during refresh. * * @param owner * @param expectedSubIds Full list of all expected subscription IDs. * @return a list of pools for subscriptions not matching the specified subscription list */ @SuppressWarnings("unchecked") public List<Pool> getPoolsFromBadSubs(Owner owner, Collection<String> expectedSubIds) { Criteria crit = currentSession().createCriteria(Pool.class) .add(Restrictions.eq("owner", owner)); if (!expectedSubIds.isEmpty()) { crit.createAlias("sourceSubscription", "sourceSub"); crit.add(Restrictions.and( Restrictions.not(Restrictions.in("sourceSub.subscriptionId", expectedSubIds)), Restrictions.isNotNull("sourceSub.subscriptionId") )); } crit.addOrder(Order.asc("id")); return crit.list(); } @SuppressWarnings("unchecked") public List<Pool> getPoolsBySubscriptionId(String subId) { return currentSession().createCriteria(Pool.class) .createAlias("sourceSubscription", "sourceSub", JoinType.LEFT_OUTER_JOIN) .add(Restrictions.eq("sourceSub.subscriptionId", subId)) .addOrder(Order.asc("id")) .list(); } @SuppressWarnings("unchecked") public List<Pool> getPoolsBySubscriptionIds(Collection<String> subIds) { return currentSession().createCriteria(Pool.class) .createAlias("sourceSubscription", "sourceSub", JoinType.LEFT_OUTER_JOIN) .add(Restrictions.in("sourceSub.subscriptionId", subIds)) .addOrder(Order.asc("id")) .list(); } @SuppressWarnings("unchecked") public Pool getMasterPoolBySubscriptionId(String subscriptionId) { return (Pool) currentSession().createCriteria(Pool.class) .createAlias("sourceSubscription", "srcsub", JoinType.LEFT_OUTER_JOIN) .add(Restrictions.eq("srcsub.subscriptionId", subscriptionId)) .add(Restrictions.eq("srcsub.subscriptionSubKey", "master")) .uniqueResult(); } @SuppressWarnings("unchecked") public List<Pool> listMasterPools() { return this.currentSession().createCriteria(Pool.class) .createAlias("sourceSubscription", "srcsub", JoinType.LEFT_OUTER_JOIN) .add(Restrictions.eq("srcsub.subscriptionSubKey", "master")) .list(); } @SuppressWarnings("unchecked") public List<Pool> getOwnersFloatingPools(Owner owner) { return currentSession().createCriteria(Pool.class) .add(Restrictions.eq("owner", owner)) .createAlias("sourceSubscription", "sourceSub", JoinType.LEFT_OUTER_JOIN) .add(Restrictions.isNull("sourceSub.subscriptionId")) .addOrder(Order.asc("id")) .list(); } /** * Retrieves the set of all known product IDs, as determined by looking only at pool data. If * there are no known products, this method returns an empty set. * * @return * a set of all known product IDs. */ @SuppressWarnings("unchecked") public Set<String> getAllKnownProductIds() { // Impl note: // HQL does not (properly) support unions, so we have to do this query multiple times. Set<String> result = new HashSet<String>(); Query query = this.currentSession().createQuery( "SELECT DISTINCT P.product.id " + "FROM Pool P " + "WHERE NULLIF(P.product.id, '') IS NOT NULL" ); result.addAll(query.list()); query = this.currentSession().createQuery( "SELECT DISTINCT P.derivedProduct.id " + "FROM Pool P " + "WHERE NULLIF(P.derivedProduct.id, '') IS NOT NULL" ); result.addAll(query.list()); query = this.currentSession().createQuery( "SELECT DISTINCT PP.id " + "FROM Pool P INNER JOIN P.providedProducts AS PP " + "WHERE NULLIF(PP.id, '') IS NOT NULL" ); result.addAll(query.list()); query = this.currentSession().createQuery( "SELECT DISTINCT DPP.id " + "FROM Pool P INNER JOIN P.derivedProvidedProducts AS DPP " + "WHERE NULLIF(DPP.id, '') IS NOT NULL" ); result.addAll(query.list()); // Return! return result; } /** * Retrieves the set of all known product IDs for the specified owner, as determined by looking * only at pool data. If there are no known products for the given owner, this method returns an * empty set. * * @param owner * The owner for which to retrieve all known product IDs * * @return * a set of all known product IDs for the specified owner */ @SuppressWarnings("unchecked") public Set<String> getAllKnownProductIdsForOwner(Owner owner) { // Impl note: // HQL does not (properly) support unions, so we have to do this query multiple times. Set<String> result = new HashSet<String>(); Query query = this.currentSession().createQuery( "SELECT DISTINCT P.product.id " + "FROM Pool P " + "WHERE NULLIF(P.product.id, '') IS NOT NULL " + "AND P.owner = :owner" ); query.setParameter("owner", owner); result.addAll(query.list()); query = this.currentSession().createQuery( "SELECT DISTINCT P.derivedProduct.id " + "FROM Pool P " + "WHERE NULLIF(P.derivedProduct.id, '') IS NOT NULL " + "AND P.owner = :owner" ); query.setParameter("owner", owner); result.addAll(query.list()); query = this.currentSession().createQuery( "SELECT DISTINCT PP.id " + "FROM Pool P INNER JOIN P.providedProducts AS PP " + "WHERE NULLIF(PP.id, '') IS NOT NULL " + "AND P.owner = :owner" ); query.setParameter("owner", owner); result.addAll(query.list()); query = this.currentSession().createQuery( "SELECT DISTINCT DPP.id " + "FROM Pool P INNER JOIN P.derivedProvidedProducts AS DPP " + "WHERE NULLIF(DPP.id, '') IS NOT NULL " + "AND P.owner = :owner" ); query.setParameter("owner", owner); result.addAll(query.list()); // Return! return result; } @SuppressWarnings("checkstyle:indentation") public Pool findDevPool(Consumer consumer) { PoolFilterBuilder filters = new PoolFilterBuilder(); filters.addAttributeFilter(Pool.Attributes.DEVELOPMENT_POOL, "true"); filters.addAttributeFilter(Pool.Attributes.REQUIRES_CONSUMER, consumer.getUuid()); Criteria criteria = this.createSecureCriteria("Pool") .createAlias("product", "Product") .setProjection(Projections.distinct(Projections.id())); criteria.add(Restrictions.eq("owner", consumer.getOwner())) .add(this.addAttributeFilterSubquery( Pool.Attributes.DEVELOPMENT_POOL, Arrays.asList("true"))) .add(this.addAttributeFilterSubquery( Pool.Attributes.REQUIRES_CONSUMER, Arrays.asList(consumer.getUuid()))); // Impl note: // Hibernate has an issue with properly hydrating objects within collections of the pool // when only a subset of the collection matches the criteria. To work around this, we pull // the ID list from the main filtering query, then pull the pools again using the ID list. // This also makes it easier to eventually start using a cursor, since the distinct entity // functionality doesn't work with cursors. List<String> poolIds = criteria.list(); if (poolIds != null && !poolIds.isEmpty()) { if (poolIds.size() > 1) { // This is probably (see: definitely) bad. throw new IllegalStateException("More than one dev pool found for consumer: " + consumer); } criteria = this.currentSession() .createCriteria(Pool.class) .add(Restrictions.eq("id", poolIds.get(0))); return (Pool) criteria.uniqueResult(); } return null; } /** * Uses a database query to check if the pool is still * in the database. * @param pool pool to be searched in the database * @return true if and only if the pool is still in the database */ public boolean exists(Pool pool) { return getEntityManager() .createQuery("SELECT COUNT(p) FROM Pool p WHERE p=:pool", Long.class) .setParameter("pool", pool).getSingleResult() > 0; } public void calculateConsumedForOwnersPools(Owner owner) { String stmt = "update Pool p set p.consumed = coalesce(" + "(select sum(quantity) from Entitlement ent where ent.pool.id = p.id),0) " + "where p.owner = :owner"; Query q = currentSession().createQuery(stmt); q.setParameter("owner", owner); q.executeUpdate(); } public void calculateExportedForOwnersPools(Owner owner) { String stmt = "update Pool p set p.exported = coalesce(" + "(select sum(ent.quantity) FROM Entitlement ent, Consumer cons, ConsumerType ctype " + "where ent.pool.id = p.id and ent.consumer.id = cons.id and cons.type.id = ctype.id " + "and ctype.manifest = 'Y'),0) where p.owner = :owner"; Query q = currentSession().createQuery(stmt); q.setParameter("owner", owner); q.executeUpdate(); } public void markCertificatesDirtyForPoolsWithProducts(Owner owner, Collection<String> productIds) { for (List<String> batch : Iterables.partition(productIds, getInBlockSize())) { markCertificatesDirtyForPoolsWithNormalProducts(owner, batch); markCertificatesDirtyForPoolsWithProvidedProducts(owner, batch); } } private void markCertificatesDirtyForPoolsWithNormalProducts(Owner owner, Collection<String> productIds) { String statement = "update Entitlement e set e.dirty=true where e.pool.id in " + "(select p.id from Pool p where p.product.id in :productIds) and e.owner = :owner"; Query query = currentSession().createQuery(statement); query.setParameter("owner", owner); query.setParameterList("productIds", productIds); query.executeUpdate(); } private void markCertificatesDirtyForPoolsWithProvidedProducts(Owner owner, Collection<String> productIds) { String statement = "update Entitlement e set e.dirty=true where e.pool.id in " + "(select p.id from Pool p join p.providedProducts pp where pp.id in :productIds)" + "and e.owner = :owner"; Query query = currentSession().createQuery(statement); query.setParameter("owner", owner); query.setParameterList("productIds", productIds); query.executeUpdate(); } /** * Check if this pool provides the given product * * Figures out if the pool with poolId provides a product providedProductId. * 'provides' means that the product is either Pool product or is linked through * cp2_pool_provided_products table * @param poolId * @param providedProductId * @return True if and only if providedProductId is provided product or pool product */ public Boolean provides(Pool pool, String providedProductId) { TypedQuery<Long> query = getEntityManager().createQuery( "SELECT count(product.uuid) FROM Pool p " + "LEFT JOIN p.providedProducts pproduct " + "LEFT JOIN p.product product " + "WHERE p.id = :poolid and (pproduct.id = :providedProductId OR product.id = :providedProductId)", Long.class); query.setParameter("poolid", pool.getId()); query.setParameter("providedProductId", providedProductId); return query.getSingleResult() > 0; } /** * Check if this pool provides the given product ID as a derived provided product. * Used when we're looking for pools we could give to a host that will create * sub-pools for guest products. * * If derived product ID is not set, we just use the normal set of products. * * @param pool * @param derivedProvidedProductId * @return True if and only if derivedProvidedProductId is provided product or derived product */ public Boolean providesDerived(Pool pool, String derivedProvidedProductId) { if (pool.getDerivedProduct() != null) { TypedQuery<Long> query = getEntityManager().createQuery( "SELECT count(product.uuid) FROM Pool p " + "LEFT JOIN p.derivedProvidedProducts pproduct " + "LEFT JOIN p.derivedProduct product " + "WHERE p.id = :poolid and " + "(pproduct.id = :providedProductId OR product.id = :providedProductId)", Long.class); query.setParameter("poolid", pool.getId()); query.setParameter("providedProductId", derivedProvidedProductId); return query.getSingleResult() > 0; } else { return provides(pool, derivedProvidedProductId); } } /** * Fetches a mapping of pool IDs to sets of product IDs representing the provided products of * the given pool. The returned map will only contain mappings for pools specified in the given * collection of pool IDs. * * @param pools * A collection of pools for which to fetch provided product IDs * * @return * A mapping of pool IDs to provided product IDs */ public Map<String, Set<String>> getProvidedProductIds(Collection<Pool> pools) { Set<String> poolIds = new HashSet<String>(); if (pools != null && !pools.isEmpty()) { for (Pool pool : pools) { if (pool != null && pool.getId() != null) { poolIds.add(pool.getId()); } } } return this.getProvidedProductIdsByPoolIds(poolIds); } /** * Fetches a mapping of pool IDs to sets of product IDs representing the provided products of * the given pool. The returned map will only contain mappings for pools specified in the given * collection of pool IDs. * * @param poolIds * A collection of pool IDs for which to fetch provided product IDs * * @return * A mapping of pool IDs to provided product IDs */ public Map<String, Set<String>> getProvidedProductIdsByPoolIds(Collection<String> poolIds) { Map<String, Set<String>> providedProductMap = new HashMap<String, Set<String>>(); if (poolIds != null && !poolIds.isEmpty()) { StringBuilder builder = new StringBuilder("SELECT p.id, pp.id FROM Pool p JOIN p.providedProducts pp WHERE"); javax.persistence.Query query = null; int blockSize = getInBlockSize(); int blockCount = (int) Math.ceil(poolIds.size() / (float) blockSize); if (blockCount > 1) { Iterable<List<String>> blocks = Iterables.partition(poolIds, blockSize); for (int i = 0; i < blockCount; ++i) { if (i != 0) { builder.append(" OR"); } builder.append(" p.id IN (:block").append(i).append(')'); } query = this.getEntityManager().createQuery(builder.toString()); int i = -1; for (List<String> block : blocks) { query.setParameter("block" + ++i, block); } } else { builder.append(" p.id IN (:pids)"); query = this.getEntityManager().createQuery(builder.toString()) .setParameter("pids", poolIds); } for (Object[] cols : (List<Object[]>) query.getResultList()) { Set<String> providedProducts = providedProductMap.get((String) cols[0]); if (providedProducts == null) { providedProducts = new HashSet<String>(); providedProductMap.put((String) cols[0], providedProducts); } providedProducts.add((String) cols[1]); } } return providedProductMap; } /** * Fetches a mapping of pool IDs to sets of product IDs representing the provided products of * the given pool. The returned map will only contain mappings for pools specified in the given * collection of pool IDs. * * @param pools * A collection of pools for which to fetch provided product IDs * * @return * A mapping of pool IDs to provided product IDs */ public Map<String, Set<String>> getDerivedProvidedProductIds(Collection<Pool> pools) { Set<String> poolIds = new HashSet<String>(); if (pools != null && !pools.isEmpty()) { for (Pool pool : pools) { if (pool != null && pool.getId() != null) { poolIds.add(pool.getId()); } } } return this.getDerivedProvidedProductIdsByPoolIds(poolIds); } /** * Fetches a mapping of pool IDs to sets of product IDs representing the provided products of * the given pool. The returned map will only contain mappings for pools specified in the given * collection of pool IDs. * * @param poolIds * A collection of pool IDs for which to fetch provided product IDs * * @return * A mapping of pool IDs to provided product IDs */ public Map<String, Set<String>> getDerivedProvidedProductIdsByPoolIds(Collection<String> poolIds) { Map<String, Set<String>> providedProductMap = new HashMap<String, Set<String>>(); if (poolIds != null && !poolIds.isEmpty()) { StringBuilder builder = new StringBuilder("SELECT p.id, dpp.id FROM Pool p JOIN p.derivedProvidedProducts dpp WHERE"); javax.persistence.Query query = null; int blockSize = getInBlockSize(); int blockCount = (int) Math.ceil(poolIds.size() / (float) blockSize); if (blockCount > 1) { Iterable<List<String>> blocks = Iterables.partition(poolIds, blockSize); for (int i = 0; i < blockCount; ++i) { if (i != 0) { builder.append(" OR"); } builder.append(" p.id IN (:block").append(i).append(')'); } query = this.getEntityManager().createQuery(builder.toString()); int i = -1; for (List<String> block : blocks) { query.setParameter("block" + ++i, block); } } else { builder.append(" p.id IN (:pids)"); query = this.getEntityManager().createQuery(builder.toString()) .setParameter("pids", poolIds); } for (Object[] cols : (List<Object[]>) query.getResultList()) { Set<String> providedProducts = providedProductMap.get((String) cols[0]); if (providedProducts == null) { providedProducts = new HashSet<String>(); providedProductMap.put((String) cols[0], providedProducts); } providedProducts.add((String) cols[1]); } } return providedProductMap; } @Transactional public void removeCdn(Cdn cdn) { if (cdn == null) { throw new IllegalArgumentException("Attempt to remove pool's cdn with null cdn value."); } String hql = "UPDATE Pool p SET p.cdn = null WHERE p.cdn = :cdn"; int updated = this.currentSession() .createQuery(hql) .setParameter("cdn", cdn) .executeUpdate(); log.debug("CDN removed from {} pools: {}", updated, cdn); } @Transactional public void calculateSharedForOwnerPools(Owner owner) { String stmt = "update Pool p set p.shared = coalesce(" + "(select sum(ent.quantity) FROM Entitlement ent, Consumer cons, ConsumerType ctype " + "where ent.pool.id = p.id and ent.consumer.id = cons.id and cons.type.id = ctype.id " + "and ctype.label = 'share'), 0) where p.owner = :owner"; Query q = currentSession().createQuery(stmt); q.setParameter("owner", owner); q.executeUpdate(); } }