Search in sources :

Example 1 with OwnerProduct

use of org.candlepin.model.OwnerProduct in project candlepin by candlepin.

the class ProductManager method importProducts.

/**
 * Creates or updates products from the given products DTOs, using the provided content for
 * content lookup and resolution.
 * <p></p>
 * The product DTOs provided in the given map should be mapped by the product's Red Hat ID. If
 * the mappings are incorrect or inconsistent, the result of this method is undefined.
 *
 * @param owner
 *  The owner for which to import the given product
 *
 * @param productData
 *  A mapping of Red Hat product ID to product DTOs to import
 *
 * @param importedContent
 *  A mapping of Red Hat content ID to content instances to use to lookup and resolve content
 *  references on the provided product DTOs.
 *
 * @return
 *  A mapping of Red Hat content ID to content entities representing the imported content
 */
@Transactional
@Traceable
public ImportResult<Product> importProducts(@TraceableParam("owner") Owner owner, Map<String, ProductData> productData, Map<String, Content> importedContent) {
    if (owner == null) {
        throw new IllegalArgumentException("owner is null");
    }
    ImportResult<Product> importResult = new ImportResult<>();
    if (productData == null || productData.isEmpty()) {
        // Nothing to import
        return importResult;
    }
    Map<String, Product> skippedProducts = importResult.getSkippedEntities();
    Map<String, Product> createdProducts = importResult.getCreatedEntities();
    Map<String, Product> updatedProducts = importResult.getUpdatedEntities();
    Map<String, Integer> productVersions = new HashMap<>();
    Map<String, Product> sourceProducts = new HashMap<>();
    Map<String, List<Product>> existingVersions = new HashMap<>();
    List<OwnerProduct> ownerProductBuffer = new LinkedList<>();
    // - Divide imported products into sets of updates and creates
    log.debug("Fetching existing products for update...");
    for (Product product : this.ownerProductCurator.getProductsByIds(owner, productData.keySet())) {
        ProductData update = productData.get(product.getId());
        if (!this.isChangedBy(product, update)) {
            // This product won't be changing, so we'll just pretend it's not being imported at all
            skippedProducts.put(product.getId(), product);
            continue;
        }
        sourceProducts.put(product.getId(), product);
        product = this.applyProductChanges((Product) product.clone(), update, importedContent);
        updatedProducts.put(product.getId(), product);
        productVersions.put(product.getId(), product.getEntityVersion());
    }
    log.debug("Validating new products...");
    for (ProductData update : productData.values()) {
        if (!skippedProducts.containsKey(update.getId()) && !updatedProducts.containsKey(update.getId())) {
            // Ensure the product is minimally populated
            if (update.getId() == null || update.getName() == null) {
                throw new IllegalStateException("Product data is incomplete: " + update);
            }
            Product product = new Product(update.getId(), update.getName());
            // TODO: Remove this shim and stop using DTOs in this class
            product = this.applyProductChanges(product, update, importedContent);
            createdProducts.put(product.getId(), product);
            productVersions.put(product.getId(), product.getEntityVersion());
        }
    }
    log.debug("Checking for existing product versions...");
    for (Product alt : this.ownerProductCurator.getProductsByVersions(owner, productVersions)) {
        List<Product> alternates = existingVersions.get(alt.getId());
        if (alternates == null) {
            alternates = new LinkedList<>();
            existingVersions.put(alt.getId(), alternates);
        }
        alternates.add(alt);
    }
    productVersions.clear();
    productVersions = null;
    // We're about to start modifying the maps, so we need to clone the created set before we
    // start adding the update forks to it.
    Map<String, Product> stagedEntities = new HashMap<>(createdProducts);
    // Process the created group...
    // Check our created set for existing versions:
    // - If there's an existing version, we'll remove the staged entity from the creation
    // set, and stage an owner-product mapping for the existing version
    // - Otherwise, we'll stage the new entity for persistence by leaving it in the created
    // set, and stage an owner-product mapping to the new entity
    Iterator<Product> iterator = stagedEntities.values().iterator();
    createdProductLoop: while (iterator.hasNext()) {
        Product created = iterator.next();
        List<Product> alternates = existingVersions.get(created.getId());
        if (alternates != null) {
            for (Product alt : alternates) {
                if (created.equals(alt)) {
                    ownerProductBuffer.add(new OwnerProduct(owner, alt));
                    createdProducts.put(alt.getId(), alt);
                    iterator.remove();
                    continue createdProductLoop;
                }
            }
        }
        ownerProductBuffer.add(new OwnerProduct(owner, created));
    }
    // - Otherwise, we need to stage the updated entity for persistence
    updatedProductLoop: for (Map.Entry<String, Product> entry : updatedProducts.entrySet()) {
        Product updated = entry.getValue();
        List<Product> alternates = existingVersions.get(updated.getId());
        if (alternates != null) {
            for (Product alt : alternates) {
                if (updated.equals(alt)) {
                    updated = alt;
                    entry.setValue(alt);
                    continue updatedProductLoop;
                }
            }
        }
        // We need to stage the updated entity for persistence. We'll reuse the now-empty
        // createdProducts map for this.
        updated.setUuid(null);
        stagedEntities.put(updated.getId(), updated);
    }
    // Persist our staged entities
    // We probably don't want to evict the products yet, as they'll appear as unmanaged if
    // they're used later. However, the join objects can be evicted safely since they're only
    // really used here.
    log.debug("Persisting product changes...");
    this.productCurator.saveAll(stagedEntities.values(), true, false);
    this.ownerProductCurator.saveAll(ownerProductBuffer, true, true);
    // Perform bulk reference update
    Map<String, String> productUuidMap = new HashMap<>();
    for (Product update : updatedProducts.values()) {
        Product source = sourceProducts.get(update.getId());
        productUuidMap.put(source.getUuid(), update.getUuid());
    }
    this.ownerProductCurator.updateOwnerProductReferences(owner, productUuidMap);
    // Return
    return importResult;
}
Also used : ProductData(org.candlepin.model.dto.ProductData) HashMap(java.util.HashMap) OwnerProduct(org.candlepin.model.OwnerProduct) Product(org.candlepin.model.Product) OwnerProduct(org.candlepin.model.OwnerProduct) LinkedList(java.util.LinkedList) LinkedList(java.util.LinkedList) List(java.util.List) Traceable(org.candlepin.util.Traceable) Transactional(com.google.inject.persist.Transactional)

Aggregations

Transactional (com.google.inject.persist.Transactional)1 HashMap (java.util.HashMap)1 LinkedList (java.util.LinkedList)1 List (java.util.List)1 OwnerProduct (org.candlepin.model.OwnerProduct)1 Product (org.candlepin.model.Product)1 ProductData (org.candlepin.model.dto.ProductData)1 Traceable (org.candlepin.util.Traceable)1