/**
* 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.exceptions.BadRequestException;
import org.candlepin.common.paging.Page;
import org.candlepin.common.paging.PageRequest;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.ReplicationMode;
import org.hibernate.criterion.CriteriaSpecification;
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.Property;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;
import org.hibernate.sql.JoinType;
import org.hibernate.type.StringType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Set;
import javax.persistence.EntityManager;
import javax.persistence.Query;
/**
* EntitlementCurator
*/
public class EntitlementCurator extends AbstractHibernateCurator<Entitlement> {
private static Logger log = LoggerFactory.getLogger(EntitlementCurator.class);
private CandlepinQueryFactory cpQueryFactory;
private OwnerProductCurator ownerProductCurator;
private ProductCurator productCurator;
/**
* default ctor
*/
@Inject
public EntitlementCurator(OwnerProductCurator ownerProductCurator, ProductCurator productCurator,
CandlepinQueryFactory cpQueryFactory) {
super(Entitlement.class);
this.cpQueryFactory = cpQueryFactory;
this.ownerProductCurator = ownerProductCurator;
this.productCurator = productCurator;
}
// TODO: handles addition of new entitlements only atm!
/**
* @param entitlements entitlements to update
* @return updated entitlements.
*/
@Transactional
public Set<Entitlement> bulkUpdate(Set<Entitlement> entitlements) {
Set<Entitlement> toReturn = new HashSet<Entitlement>();
for (Entitlement toUpdate : entitlements) {
Entitlement found = find(toUpdate.getId());
if (found != null) {
toReturn.add(found);
continue;
}
toReturn.add(create(toUpdate));
}
return toReturn;
}
@SuppressWarnings("checkstyle:indentation")
private Criteria createCriteriaFromFilters(EntitlementFilterBuilder filterBuilder) {
Criteria criteria = createSecureCriteria()
.createAlias("pool", "Pool")
.createAlias("Pool.product", "Product")
.setProjection(Projections.distinct(Projections.id()))
.add(Restrictions.ge("Pool.endDate", new Date()));
boolean joinedProvided = false;
if (filterBuilder != null) {
Collection<String> values = filterBuilder.getIdFilters();
if (values != null && !values.isEmpty()) {
criteria.add(CPRestrictions.in("Pool.id", values));
}
// Product ID filters
values = filterBuilder.getProductIdFilter();
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)
));
}
// Subscription ID filter
String value = filterBuilder.getSubscriptionIdFilter();
if (value != null && !value.isEmpty()) {
criteria.createAlias("Pool.sourceSubscription", "srcsub")
.add(Restrictions.eq("srcsub.subscriptionId", value));
}
// Matches stuff
values = filterBuilder.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 : filterBuilder.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));
}
}
}
}
return criteria;
}
@SuppressWarnings("checkstyle:indentation")
private Criterion addAttributeFilterSubquery(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
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 = pool1_.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 = pool1_.id AND LOWER(poolattr.name) LIKE LOWER(?) ESCAPE '!')",
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();
}
/**
* This must return a sorted list in order to avoid deadlocks
*
* @param consumer
* @return list of entitlements belonging to the consumer, ordered by pool id
*/
@SuppressWarnings("unchecked")
public List<Entitlement> listByConsumer(Consumer consumer) {
return listByConsumer(consumer, new EntitlementFilterBuilder());
}
@SuppressWarnings("unchecked")
public List<Entitlement> listByConsumer(Consumer consumer, EntitlementFilterBuilder filters) {
Criteria criteria = this.createCriteriaFromFilters(filters)
.add(Restrictions.eq("consumer", consumer));
List<String> entitlementIds = criteria.list();
if (entitlementIds != null && !entitlementIds.isEmpty()) {
criteria = this.currentSession()
.createCriteria(Entitlement.class)
.add(CPRestrictions.in("id", entitlementIds));
return criteria.list();
}
return Collections.<Entitlement>emptyList();
}
@SuppressWarnings("unchecked")
public List<Entitlement> listByConsumerAndPoolId(Consumer consumer, String poolId) {
Criteria query = currentSession().createCriteria(Entitlement.class)
.add(Restrictions.eq("pool.id", poolId));
query.add(Restrictions.eq("consumer", consumer));
return listByCriteria(query);
}
public Page<List<Entitlement>> listByConsumer(Consumer consumer, String productId,
EntitlementFilterBuilder filters, PageRequest pageRequest) {
return listFilteredPages(consumer, "consumer", productId, filters, pageRequest);
}
public Page<List<Entitlement>> listByOwner(Owner owner, String productId,
EntitlementFilterBuilder filters, PageRequest pageRequest) {
return listFilteredPages(owner, "owner", productId, filters, pageRequest);
}
public Page<List<Entitlement>> listAll(EntitlementFilterBuilder filters, PageRequest pageRequest) {
return listFilteredPages(null, null, null, filters, pageRequest);
}
private Page<List<Entitlement>> listFilteredPages(AbstractHibernateObject object, String objectType,
String productId, EntitlementFilterBuilder filters, PageRequest pageRequest) {
Page<List<Entitlement>> entitlementsPage;
Owner owner = null;
if (object != null) {
owner = (object instanceof Owner) ? (Owner) object : ((Consumer) object).getOwner();
}
// No need to add filters when matching by product.
if (object != null && productId != null) {
Product p = this.ownerProductCurator.getProductById(owner, productId);
if (p == null) {
throw new BadRequestException(i18n.tr(
"Product with ID ''{0}'' could not be found.", productId));
}
entitlementsPage = listByProduct(object, objectType, productId, pageRequest);
}
else {
// Build up any provided entitlement filters from query params.
Criteria criteria = this.createCriteriaFromFilters(filters);
if (object != null) {
criteria.add(Restrictions.eq(objectType, object));
}
List<String> entitlementIds = criteria.list();
if (entitlementIds != null && !entitlementIds.isEmpty()) {
criteria = this.currentSession()
.createCriteria(Entitlement.class)
.add(CPRestrictions.in("id", entitlementIds));
entitlementsPage = listByCriteria(criteria, pageRequest);
}
else {
entitlementsPage = new Page<List<Entitlement>>();
entitlementsPage.setPageData(Collections.<Entitlement>emptyList());
entitlementsPage.setMaxRecords(0);
}
}
return entitlementsPage;
}
public CandlepinQuery<Entitlement> listByOwner(Owner owner) {
DetachedCriteria criteria = DetachedCriteria.forClass(Entitlement.class)
.add(Restrictions.eq("owner", owner));
return this.cpQueryFactory.<Entitlement>buildQuery(this.currentSession(), criteria);
}
public CandlepinQuery<Entitlement> listByEnvironment(Environment environment) {
DetachedCriteria criteria = DetachedCriteria.forClass(Entitlement.class)
.createCriteria("consumer")
.add(Restrictions.eq("environment", environment));
return this.cpQueryFactory.<Entitlement>buildQuery(this.currentSession(), criteria);
}
/**
* List entitlements for a consumer which are valid for a specific date.
*
* @param consumer Consumer to list entitlements for.
* @param activeOn The date we want to see entitlements which are active on.
* @return List of entitlements.
*/
public CandlepinQuery<Entitlement> listByConsumerAndDate(Consumer consumer, Date activeOn) {
/*
* Essentially the opposite of the above query which searches for entitlement
* overlap with a "modifying" entitlement being granted. This query is used to
* search for modifying entitlements which overlap with a regular entitlement
* being granted. As such the logic is basically reversed.
*
*/
DetachedCriteria criteria = DetachedCriteria.forClass(Entitlement.class)
.add(Restrictions.eq("consumer", consumer))
.createCriteria("pool")
.add(Restrictions.le("startDate", activeOn))
.add(Restrictions.ge("endDate", activeOn));
return this.cpQueryFactory.<Entitlement>buildQuery(this.currentSession(), criteria);
}
/**
* Lists dirty entitlements for the given consumer. If the consumer does not have any dirty
* entitlements, this method returns an empty collection.
*
* @param consumer
* The consumer for which to find dirty entitlements
*
* @return
* a collection of dirty entitlements for the given consumer
*/
public List<Entitlement> listDirty(Consumer consumer) {
Criteria criteria = this.currentSession().createCriteria(Entitlement.class)
.add(Restrictions.eq("consumer", consumer))
.add(Restrictions.eq("dirty", true));
return criteria.list();
}
/**
* List all entitled product IDs from entitlements which overlap the given date range.
*
* i.e. given start date must be within the entitlements start/end dates, or
* the given end date must be within the entitlements start/end dates,
* or the given start date must be before the entitlement *and* the given end date
* must be after entitlement. (i.e. we are looking for *any* overlap)
*
* @param c
* @param startDate
* @param endDate
* @return entitled product IDs
*/
public Set<String> listEntitledProductIds(Consumer c, Date startDate, Date endDate) {
// FIXME Either address the TODO below, or move this method out of the curator.
// TODO: Swap this to a db query if we're worried about memory:
Set<String> entitledProductIds = new HashSet<String>();
for (Entitlement e : c.getEntitlements()) {
Pool p = e.getPool();
if (!poolOverlapsRange(p, startDate, endDate)) {
// Skip this entitlement:
continue;
}
entitledProductIds.add(p.getProduct().getId());
for (Product pp : productCurator.getPoolProvidedProductsCached(p)) {
entitledProductIds.add(pp.getId());
}
// A distributor should technically be entitled to derived products and
// will need to be able to sync content downstream.
if (c.isManifestDistributor() && p.getDerivedProduct() != null) {
entitledProductIds.add(p.getDerivedProduct().getId());
for (Product dpp : productCurator.getPoolDerivedProvidedProductsCached(p)) {
entitledProductIds.add(dpp.getId());
}
}
}
return entitledProductIds;
}
private boolean poolOverlapsRange(Pool p, Date startDate, Date endDate) {
Date poolStart = p.getStartDate();
Date poolEnd = p.getEndDate();
// If pool start is within the range we're looking for:
if (poolStart.compareTo(startDate) >= 0 && poolStart.compareTo(endDate) <= 0) {
return true;
}
// If pool end is within the range we're looking for:
if (poolEnd.compareTo(startDate) >= 0 && poolEnd.compareTo(endDate) <= 0) {
return true;
}
// If pool completely encapsulates the range we're looking for:
if (poolStart.compareTo(startDate) <= 0 && poolEnd.compareTo(endDate) >= 0) {
return true;
}
return false;
}
/**
* A version of list Modifying that finds Entitlements that modify
* input entitlements.
* When dealing with large amount of entitlements for which it is necessary
* to determine their modifier products.
* @param entitlement
* @return Entitlements that are being modified by the input entitlements
*/
public Collection<String> batchListModifying(Iterable<Entitlement> entitlements) {
List<String> eids = new LinkedList<String>();
if (entitlements != null && entitlements.iterator().hasNext()) {
String hql =
"SELECT DISTINCT eOut.id" +
" FROM Entitlement eOut" +
" JOIN eOut.pool outPool" +
" JOIN outPool.providedProducts outProvided" +
" JOIN outProvided.productContent outProvContent" +
" JOIN outProvContent.content outContent" +
" JOIN outContent.modifiedProductIds outModProdId" +
" WHERE" +
" outPool.endDate >= current_date AND" +
" eOut.owner = :owner AND" +
" eOut NOT IN (:ein) AND" +
" EXISTS (" +
" SELECT eIn" +
" FROM Entitlement eIn" +
" JOIN eIn.consumer inConsumer" +
" JOIN eIn.pool inPool" +
" JOIN inPool.product inMktProd" +
" LEFT JOIN inPool.providedProducts inProvidedProd" +
" WHERE eIn in (:ein) AND inConsumer = eOut.consumer AND" +
" inPool.endDate >= outPool.startDate AND" +
" inPool.startDate <= outPool.endDate AND" +
" (inProvidedProd.id = outModProdId OR inMktProd.id = outModProdId)" +
" )";
Query query = this.getEntityManager().createQuery(hql);
Iterable<List<Entitlement>> blocks = Iterables.partition(entitlements, getInBlockSize());
for (List<Entitlement> block : blocks) {
Owner sampleOwner = block.get(0).getOwner();
eids.addAll(query.setParameter("ein", block)
.setParameter("owner", sampleOwner).getResultList());
}
}
return eids;
}
public Collection<String> listModifying(Entitlement entitlement) {
return batchListModifying(java.util.Arrays.asList(entitlement));
}
public Collection<String> listModifying(Collection entitlements) {
return batchListModifying(entitlements);
}
public Map<Consumer, List<Entitlement>> getDistinctConsumers(List<Entitlement> entsToRevoke) {
Map<Consumer, List<Entitlement>> result = new HashMap<Consumer, List<Entitlement>>();
for (Entitlement ent : entsToRevoke) {
List<Entitlement> ents = result.get(ent.getConsumer());
if (ents == null) {
ents = new ArrayList<Entitlement>();
result.put(ent.getConsumer(), ents);
}
ents.add(ent);
}
return result;
}
public Page<List<Entitlement>> listByConsumerAndProduct(Consumer consumer,
String productId, PageRequest pageRequest) {
return listByProduct(consumer, "consumer", productId, pageRequest);
}
@Transactional
private Page<List<Entitlement>> listByProduct(AbstractHibernateObject object, String objectType,
String productId, PageRequest pageRequest) {
Criteria query = createSecureCriteria()
.add(Restrictions.eq(objectType, object))
.createAlias("pool", "p")
.createAlias("p.product", "prod")
.createAlias("p.providedProducts", "pp", CriteriaSpecification.LEFT_JOIN)
// Never show a consumer expired entitlements
.add(Restrictions.ge("p.endDate", new Date()))
.add(Restrictions.or(Restrictions.eq("prod.id", productId), Restrictions.eq("pp.id", productId)));
Page<List<Entitlement>> page = listByCriteria(query, pageRequest);
return page;
}
/**
* Deletes the given entitlement.
*
* @param entity
* The entitlement entity to delete
*/
@Transactional
public void delete(Entitlement entity) {
Entitlement toDelete = find(entity.getId());
if (toDelete != null) {
this.deleteImpl(toDelete);
// Maintain runtime consistency.
entity.getCertificates().clear();
entity.getConsumer().getEntitlements().remove(entity);
entity.getPool().getEntitlements().remove(entity);
}
}
/**
* Deletes the given collection of entitlements.
* <p/></p>
* Note: Unlike the standard delete method, this method does not perform a lookup on an entity
* before deleting it.
*
* @param entitlements
* The collection of entitlement entities to delete
*/
public void batchDelete(Collection<Entitlement> entitlements) {
for (Entitlement entitlement : entitlements) {
this.deleteImpl(entitlement);
// Maintain runtime consistency.
entitlement.getCertificates().clear();
if (Hibernate.isInitialized(entitlement.getConsumer().getEntitlements())) {
entitlement.getConsumer().getEntitlements().remove(entitlement);
}
if (Hibernate.isInitialized(entitlement.getPool().getEntitlements())) {
entitlement.getPool().getEntitlements().remove(entitlement);
}
}
}
private void deleteImpl(Entitlement entity) {
log.debug("Deleting entitlement: {}", entity);
EntityManager entityManager = this.getEntityManager();
if (entity.getCertificates() != null) {
log.debug("certs.size = {}", entity.getCertificates().size());
for (EntitlementCertificate cert : entity.getCertificates()) {
entityManager.remove(cert);
}
}
entityManager.remove(entity);
}
@Transactional
public Entitlement findByCertificateSerial(Long serial) {
return (Entitlement) currentSession().createCriteria(Entitlement.class)
.createCriteria("certificates")
.add(Restrictions.eq("serial.id", serial))
.uniqueResult();
}
@Transactional
public Entitlement replicate(Entitlement ent) {
for (EntitlementCertificate ec : ent.getCertificates()) {
ec.setEntitlement(ent);
CertificateSerial cs = ec.getSerial();
if (cs != null) {
this.currentSession().replicate(cs, ReplicationMode.EXCEPTION);
}
}
this.currentSession().replicate(ent, ReplicationMode.EXCEPTION);
return ent;
}
/**
* Find the entitlements for the given consumer that are part of the specified stack.
*
* @param consumer the consumer
* @param stackId the ID of the stack
* @return the list of entitlements for the consumer that are in the stack.
*/
@SuppressWarnings("unchecked")
public CandlepinQuery<Entitlement> findByStackId(Consumer consumer, String stackId) {
return findByStackIds(consumer, Arrays.asList(stackId));
}
/**
* Find the entitlements for the given consumer that are part of the
* specified stacks.
*
* @param consumer the consumer
* @param stackIds the IDs of the stacks
* @return the list of entitlements for the consumer that are in the stack.
*/
@SuppressWarnings("unchecked")
public CandlepinQuery<Entitlement> findByStackIds(Consumer consumer, Collection stackIds) {
DetachedCriteria criteria = DetachedCriteria.forClass(Entitlement.class)
.add(Restrictions.eq("consumer", consumer))
.createAlias("pool", "ent_pool")
.createAlias("ent_pool.product", "product")
.createAlias("product.attributes", "attrs")
.add(Restrictions.eq("attrs.indices", Product.Attributes.STACKING_ID))
.add(CPRestrictions.in("attrs.elements", stackIds))
.add(Restrictions.isNull("ent_pool.sourceEntitlement"))
.createAlias("ent_pool.sourceStack", "ss", JoinType.LEFT_OUTER_JOIN)
.add(Restrictions.isNull("ss.id"));
return this.cpQueryFactory.<Entitlement>buildQuery(this.currentSession(), criteria);
}
@SuppressWarnings("unchecked")
public CandlepinQuery<Entitlement> findByPoolAttribute(Consumer consumer, String attributeName,
String value) {
DetachedCriteria criteria = DetachedCriteria.forClass(Entitlement.class)
.createAlias("pool", "ent_pool")
.createAlias("ent_pool.attributes", "attrs")
.add(Restrictions.eq("attrs.indices", attributeName))
.add(Restrictions.eq("attrs.elements", value));
if (consumer != null) {
criteria.add(Restrictions.eq("consumer", consumer));
}
return this.cpQueryFactory.<Entitlement>buildQuery(this.currentSession(), criteria);
}
@SuppressWarnings("unchecked")
public CandlepinQuery<Entitlement> findByPoolAttribute(String attributeName, String value) {
return findByPoolAttribute(null, attributeName, value);
}
/**
* For a given stack, find the eldest active entitlement with a subscription ID.
* This is used to look up the upstream subscription certificate to use to talk to
* the CDN.
*
* @param consumer the consumer
* @param stackId the ID of the stack
* @return the eldest active entitlement with a subscription ID, or null if none can
* be found.
*/
public Entitlement findUpstreamEntitlementForStack(Consumer consumer, String stackId) {
Date currentDate = new Date();
Criteria activeNowQuery = currentSession().createCriteria(Entitlement.class)
.add(Restrictions.eq("consumer", consumer))
.createAlias("pool", "ent_pool")
.createAlias("ent_pool.product", "product")
.createAlias("product.attributes", "attrs")
.add(Restrictions.le("ent_pool.startDate", currentDate))
.add(Restrictions.ge("ent_pool.endDate", currentDate))
.add(Restrictions.eq("attrs.indices", Product.Attributes.STACKING_ID))
.add(Restrictions.eq("attrs.elements", stackId).ignoreCase())
.add(Restrictions.isNull("ent_pool.sourceEntitlement"))
.createAlias("ent_pool.sourceSubscription", "sourceSub")
.add(Restrictions.isNotNull("sourceSub.id"))
.addOrder(Order.asc("created")) // eldest entitlement
.setMaxResults(1);
return (Entitlement) activeNowQuery.uniqueResult();
}
/**
* Marks the given entitlements as dirty; forcing a regeneration the next time it is requested.
*
* @param entitlementIds
* A collection of IDs of the entitlements to mark dirty
*
* @return
* The number of certificates updated
*/
@Transactional
public int markEntitlementsDirty(Iterable<String> entitlementIds) {
int count = 0;
if (entitlementIds != null && entitlementIds.iterator().hasNext()) {
Iterable<List<String>> blocks = Iterables.partition(entitlementIds, getInBlockSize());
String hql = "UPDATE Entitlement SET dirty = true WHERE id IN (:entIds)";
Query query = this.getEntityManager().createQuery(hql);
for (List<String> block : blocks) {
count += query.setParameter("entIds", block).executeUpdate();
}
}
return count;
}
}