/**
* 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.sync;
import org.candlepin.audit.Event;
import org.candlepin.audit.EventSink;
import org.candlepin.model.Branding;
import org.candlepin.model.Cdn;
import org.candlepin.model.CdnCurator;
import org.candlepin.model.CertificateSerial;
import org.candlepin.model.CertificateSerialCurator;
import org.candlepin.model.DerivedProvidedProduct;
import org.candlepin.model.Entitlement;
import org.candlepin.model.EntitlementCertificate;
import org.candlepin.model.Owner;
import org.candlepin.model.Product;
import org.candlepin.model.ProvidedProduct;
import org.candlepin.model.Subscription;
import org.candlepin.model.SubscriptionCurator;
import org.candlepin.model.SubscriptionsCertificate;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnap.commons.i18n.I18n;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* EntitlementImporter - turn an upstream Entitlement into a local subscription
*/
public class EntitlementImporter {
private static Logger log = LoggerFactory.getLogger(EntitlementImporter.class);
private SubscriptionCurator subscriptionCurator;
private CertificateSerialCurator csCurator;
private CdnCurator cdnCurator;
private EventSink sink;
private I18n i18n;
public EntitlementImporter(SubscriptionCurator subscriptionCurator,
CertificateSerialCurator csCurator, CdnCurator cdnCurator,
EventSink sink, I18n i18n) {
this.subscriptionCurator = subscriptionCurator;
this.csCurator = csCurator;
this.cdnCurator = cdnCurator;
this.sink = sink;
this.i18n = i18n;
}
public Subscription importObject(ObjectMapper mapper, Reader reader, Owner owner,
Map<String, Product> productsById, ConsumerDto consumer, Meta meta)
throws IOException, SyncDataFormatException {
Entitlement entitlement = mapper.readValue(reader, Entitlement.class);
Subscription subscription = new Subscription();
subscription.setUpstreamPoolId(entitlement.getPool().getId());
subscription.setUpstreamEntitlementId(entitlement.getId());
subscription.setUpstreamConsumerId(consumer.getUuid());
subscription.setOwner(owner);
subscription.setStartDate(entitlement.getStartDate());
subscription.setEndDate(entitlement.getEndDate());
subscription.setAccountNumber(entitlement.getPool().getAccountNumber());
subscription.setContractNumber(entitlement.getPool().getContractNumber());
subscription.setOrderNumber(entitlement.getPool().getOrderNumber());
subscription.setQuantity(entitlement.getQuantity().longValue());
for (Branding b : entitlement.getPool().getBranding()) {
subscription.getBranding().add(new Branding(b.getProductId(), b.getType(),
b.getName()));
}
subscription.setProduct(findProduct(productsById, entitlement.getProductId()));
String cdnLabel = meta.getCdnLabel();
if (!StringUtils.isBlank(cdnLabel)) {
Cdn cdn = cdnCurator.lookupByLabel(cdnLabel);
if (cdn != null) {
subscription.setCdn(cdn);
}
}
Set<Product> products = new HashSet<Product>();
for (ProvidedProduct providedProduct : entitlement.getPool().
getProvidedProducts()) {
products.add(findProduct(productsById, providedProduct.getProductId()));
}
subscription.setProvidedProducts(products);
// Add any sub product data to the subscription.
if (entitlement.getPool().getDerivedProductId() != null) {
subscription.setDerivedProduct(findProduct(productsById,
entitlement.getPool().getDerivedProductId()));
}
Set<Product> subProvProds = new HashSet<Product>();
for (DerivedProvidedProduct subProvProd : entitlement.getPool().
getDerivedProvidedProducts()) {
subProvProds.add(findProduct(productsById, subProvProd.getProductId()));
}
subscription.setDerivedProvidedProducts(subProvProds);
Set<EntitlementCertificate> certs = entitlement.getCertificates();
// subscriptions have one cert
int entcnt = 0;
for (EntitlementCertificate cert : certs) {
entcnt++;
CertificateSerial cs = new CertificateSerial();
cs.setCollected(cert.getSerial().isCollected());
cs.setExpiration(cert.getSerial().getExpiration());
cs.setUpdated(cert.getSerial().getUpdated());
cs.setCreated(cert.getSerial().getCreated());
csCurator.create(cs);
SubscriptionsCertificate sc = new SubscriptionsCertificate();
sc.setKey(cert.getKey());
sc.setCertAsBytes(cert.getCertAsBytes());
sc.setSerial(cs);
subscription.setCertificate(sc);
}
if (entcnt > 1) {
log.error("More than one entitlement cert found for subscription");
}
return subscription;
}
private Product findProduct(Map<String, Product> productsById,
String productId) throws SyncDataFormatException {
Product product = productsById.get(productId);
if (product == null) {
throw new SyncDataFormatException(i18n.tr("Unable to find product with ID: " +
productId));
}
return product;
}
/**
* @param subsToImport
*
* Reconciles incoming entitlements to existing subscriptions.
* Each set is mapped against the upstream pool id.
* First match attempt will use entitlement id from incoming
* entitlements for comparison to existing subscriptions.
* Next attempt will use the exact quantity for comparison. This is to
* cover scenarios where the intent is to re-establish the distributor
* from the host.
* The final attempt will use ordering of the remaining incoming entitlements
* and of remaining existing subscriptions in descending order by quantity.
* Either the remaining subscriptions will be deleted, or the unmatched incoming
* entitlements will be turned into new subscriptions.
*/
public void store(Owner owner, Set<Subscription> subsToImport) {
Map<String, Map<String, Subscription>> existingSubsByUpstreamPool =
mapSubsByUpstreamPool(owner);
// if we can match to the entitlement id do it.
// we need a new list to hold the ones that are left
Set<Subscription> subscriptionsStillToImport = new HashSet<Subscription>();
for (Subscription subscription : subsToImport) {
Subscription local = null;
Map<String, Subscription> map = existingSubsByUpstreamPool.get(
subscription.getUpstreamPoolId());
if (map == null || map.isEmpty()) {
createSubscription(subscription);
log.info("Creating new subscription for incoming entitlement with id [" +
subscription.getUpstreamEntitlementId() +
"]");
continue;
}
local = map.get(subscription.getUpstreamEntitlementId());
if (local != null) {
mergeSubscription(subscription, local, map);
log.info("Merging subscription for incoming entitlement id [" +
subscription.getUpstreamEntitlementId() +
"] into subscription with existing entitlement id [" +
local.getUpstreamEntitlementId() +
"]. Entitlement id match.");
}
else {
subscriptionsStillToImport.add(subscription);
log.warn("Subscription for incoming entitlement id [" +
subscription.getUpstreamEntitlementId() +
"] does not have an entitlement id match " +
"in the current subscriptions for the upstream pool id [" +
subscription.getUpstreamPoolId() +
"]");
}
}
// matches will be made against the upstream pool id and quantity.
// we need a new list to hold the ones that are left
List<Subscription> subscriptionsNeedQuantityMatch = new ArrayList<Subscription>();
for (Subscription subscription : subscriptionsStillToImport) {
Subscription local = null;
Map<String, Subscription> map = existingSubsByUpstreamPool.get(
subscription.getUpstreamPoolId());
if (map == null) {
map = new HashMap<String, Subscription>();
}
for (Subscription localSub : map.values()) {
if (localSub.getQuantity().equals(subscription.getQuantity())) {
local = localSub;
break;
}
}
if (local != null) {
mergeSubscription(subscription, local, map);
log.info("Merging subscription for incoming entitlement id [" +
subscription.getUpstreamEntitlementId() +
"] into subscription with existing entitlement id [" +
local.getUpstreamEntitlementId() +
"]. Exact quantity match.");
}
else {
subscriptionsNeedQuantityMatch.add(subscription);
log.warn("Subscription for incoming entitlement id [" +
subscription.getUpstreamEntitlementId() +
"] does not have an exact quantity match " +
"in the current subscriptions for the upstream pool id [" +
subscription.getUpstreamPoolId() +
"]");
}
}
// matches will be made against the upstream pool id and quantity.
// quantities will just match by position from highest to lowest
// we need a new list to hold the ones that are left
Subscription[] inNeed = subscriptionsNeedQuantityMatch.toArray(
new Subscription[0]);
Arrays.sort(inNeed, new QuantityComparator());
for (Subscription subscription : inNeed) {
Subscription local = null;
Map<String, Subscription> map = existingSubsByUpstreamPool.get(
subscription.getUpstreamPoolId());
if (map == null || map.isEmpty()) {
createSubscription(subscription);
log.info("Creating new subscription for incoming entitlement with id [" +
subscription.getUpstreamEntitlementId() +
"]");
continue;
}
Subscription[] locals = map.values().toArray(new Subscription[0]);
Arrays.sort(locals, new QuantityComparator());
local = locals[0];
mergeSubscription(subscription, local, map);
log.info("Merging subscription for incoming entitlement id [" +
subscription.getUpstreamEntitlementId() +
"] into subscription with existing entitlement id [" +
local.getUpstreamEntitlementId() +
"]. Ordered quantity match.");
}
deleteRemainingLocalSubscriptions(existingSubsByUpstreamPool);
}
private Map<String, Map<String, Subscription>> mapSubsByUpstreamPool(Owner owner) {
Map<String, Map<String, Subscription>> existingSubsByUpstreamPool =
new HashMap<String, Map<String, Subscription>>();
int idx = 0;
for (Subscription subscription : subscriptionCurator.listByOwner(owner)) {
// if the upstream pool id is null,
// this must be a locally controlled sub.
if (subscription.getUpstreamPoolId() != null) {
// if the existing sub does not have the entitlement id yet,
// just assign a placeholder to differentiate.
if (subscription.getUpstreamEntitlementId() == null ||
subscription.getUpstreamEntitlementId().trim().equals("")) {
subscription.setUpstreamEntitlementId("" + idx++);
}
Map<String, Subscription> subs = existingSubsByUpstreamPool.get(
subscription.getUpstreamPoolId());
if (subs == null) {
subs = new HashMap<String, Subscription>();
}
subs.put(subscription.getUpstreamEntitlementId(), subscription);
existingSubsByUpstreamPool.put(subscription.getUpstreamPoolId(),
subs);
}
}
return existingSubsByUpstreamPool;
}
private void deleteRemainingLocalSubscriptions(
Map<String, Map<String, Subscription>> existingSubsByUpstreamPool) {
for (Map<String, Subscription> map : existingSubsByUpstreamPool.values()) {
for (Subscription subscription : map.values()) {
Event e = sink.createSubscriptionDeleted(subscription);
subscriptionCurator.delete(subscription);
sink.queueEvent(e);
log.info("Delete subscription with entitlement id [" +
subscription.getUpstreamEntitlementId() +
"]");
}
}
}
private void mergeSubscription(Subscription subscription, Subscription local,
Map<String, Subscription> map) {
subscription.setId(local.getId());
map.remove(local.getUpstreamEntitlementId());
subscriptionCurator.merge(subscription);
// send updated event
sink.emitSubscriptionModified(local, subscription);
}
private void createSubscription(Subscription subscription) {
subscriptionCurator.create(subscription);
// send out created event
log.debug("emitting subscription event");
sink.emitSubscriptionCreated(subscription);
}
/**
*
* QuantityComparator
*
* descending quantity sort on Subscriptions
*/
public static class QuantityComparator implements
Comparator<Subscription>, Serializable {
@Override
public int compare(Subscription s1, Subscription s2) {
return s2.getQuantity().compareTo(s1.getQuantity());
}
}
}