org.candlepin.policy.js.entitlement.AbstractEntitlementRules.java Source code

Java tutorial

Introduction

Here is the source code for org.candlepin.policy.js.entitlement.AbstractEntitlementRules.java

Source

/**
 * 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.policy.js.entitlement;

import org.candlepin.common.config.Configuration;
import org.candlepin.config.ConfigProperties;
import org.candlepin.controller.PoolManager;
import org.candlepin.model.Consumer;
import org.candlepin.model.ConsumerCurator;
import org.candlepin.model.Entitlement;
import org.candlepin.model.Pool;
import org.candlepin.model.PoolCurator;
import org.candlepin.model.Product;
import org.candlepin.policy.ValidationError;
import org.candlepin.policy.ValidationResult;
import org.candlepin.policy.ValidationWarning;
import org.candlepin.policy.js.JsRunner;
import org.candlepin.policy.js.RulesObjectMapper;
import org.candlepin.policy.js.pool.PoolHelper;
import org.candlepin.util.DateSource;

import org.apache.commons.collections.CollectionUtils;
import org.mozilla.javascript.RhinoException;
import org.slf4j.Logger;
import org.xnap.commons.i18n.I18n;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
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;

/**
 * Enforces the Javascript Rules definition.
 */
public abstract class AbstractEntitlementRules implements Enforcer {

    protected Logger log = null;
    protected DateSource dateSource;

    protected I18n i18n;
    protected Map<String, Set<Rule>> attributesToRules;
    protected JsRunner jsRules;
    protected Configuration config;
    protected ConsumerCurator consumerCurator;
    protected PoolCurator poolCurator;

    protected RulesObjectMapper objectMapper = RulesObjectMapper.instance();

    protected static final String POST_PREFIX = "post_";

    protected void rulesInit() {
        String mappings;
        try {
            mappings = jsRules.invokeMethod("attribute_mappings");
            this.attributesToRules = parseAttributeMappings(mappings);
        } catch (RhinoException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public List<Rule> rulesForAttributes(Set<String> attributes, Map<String, Set<Rule>> rules) {
        Set<Rule> possibleMatches = new HashSet<Rule>();
        for (String attribute : attributes) {
            if (rules.containsKey(attribute)) {
                possibleMatches.addAll(rules.get(attribute));
            }
        }

        List<Rule> matches = new LinkedList<Rule>();
        for (Rule rule : possibleMatches) {
            if (attributes.containsAll(rule.getAttributes())) {
                matches.add(rule);
            }
        }

        // Always run the global rule, and run it first
        matches.add(new Rule("global", 0, new HashSet<String>()));

        Collections.sort(matches, new RuleOrderComparator());
        return matches;
    }

    public Map<String, Set<Rule>> parseAttributeMappings(String mappings) {
        Map<String, Set<Rule>> toReturn = new HashMap<String, Set<Rule>>();
        if (mappings.trim().isEmpty()) {
            return toReturn;
        }

        String[] separatedMappings = mappings.split(",");

        for (String mapping : separatedMappings) {
            Rule rule = parseRule(mapping);
            for (String attribute : rule.getAttributes()) {
                if (!toReturn.containsKey(attribute)) {
                    toReturn.put(attribute, new HashSet<Rule>(Collections.singletonList(rule)));
                }

                toReturn.get(attribute).add(rule);
            }
        }

        return toReturn;
    }

    public Rule parseRule(String toParse) {
        String[] tokens = toParse.split(":");

        if (tokens.length < 3) {
            throw new IllegalArgumentException(
                    i18n.tr("''{0}'' Should contain name, priority and at least one attribute", toParse));
        }

        Set<String> attributes = new HashSet<String>();
        for (int i = 2; i < tokens.length; i++) {
            attributes.add(tokens[i].trim());
        }

        try {
            return new Rule(tokens[0].trim(), Integer.parseInt(tokens[1]), attributes);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(i18n.tr("second parameter should be the priority number.", e));
        }
    }

    protected void callPostEntitlementRules(List<Rule> matchingRules) {
        for (Rule rule : matchingRules) {
            jsRules.invokeRule(POST_PREFIX + rule.getRuleName());
        }
    }

    protected void callPostUnbindRules(List<Rule> matchingRules) {
        for (Rule rule : matchingRules) {
            jsRules.invokeRule(POST_PREFIX + rule.getRuleName());
        }
    }

    // Always ensure that we do not over consume.
    // FIXME for auto sub stacking, we need to be able to pull across multiple
    // pools eventually, so this would need to go away in that case
    protected void validatePoolQuantity(ValidationResult result, Pool pool, int quantity) {
        if (!pool.entitlementsAvailable(quantity)) {
            result.addError("rulefailed.no.entitlements.available");
        }
    }

    /**
     * RuleOrderComparator
     */
    public static class RuleOrderComparator implements Comparator<Rule>, Serializable {
        private static final long serialVersionUID = 7602679645721757886L;

        @Override
        public int compare(Rule o1, Rule o2) {
            return Integer.valueOf(o2.getOrder()).compareTo(Integer.valueOf(o1.getOrder()));
        }
    }

    /**
     * Rule represents a core concept in Candlepin which is a business rule used
     * to determine system compliance as well as entitlement eligibility for a
     * particular consumer.
     */
    public static class Rule {
        private final String ruleName;
        private final int order;
        private final Set<String> attributes;

        public Rule(String ruleName, int order, Set<String> attributes) {
            this.ruleName = ruleName;
            this.order = order;
            this.attributes = attributes;
        }

        public String getRuleName() {
            return ruleName;
        }

        public int getOrder() {
            return order;
        }

        public Set<String> getAttributes() {
            return attributes;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((attributes == null) ? 0 : attributes.hashCode());
            result = prime * result + order;
            result = prime * result + ((ruleName == null) ? 0 : ruleName.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }

            Rule other = (Rule) obj;
            if (attributes == null) {
                if (other.attributes != null) {
                    return false;
                }
            } else if (!attributes.equals(other.attributes)) {
                return false;
            }
            if (order != other.order) {
                return false;
            }
            if (ruleName == null) {
                if (other.ruleName != null) {
                    return false;
                }
            } else if (!ruleName.equals(other.ruleName)) {
                return false;
            }
            return true;
        }

        public String toString() {
            return "'" + ruleName + "':" + order + ":" + attributes.toString();
        }
    }

    protected void runPostEntitlement(PoolManager poolManager, Consumer consumer,
            Map<String, Entitlement> entitlementMap, List<Pool> subPoolsForStackIds) {
        Map<String, Map<String, String>> flatAttributeMaps = new HashMap<String, Map<String, String>>();
        Map<String, Entitlement> virtLimitEntitlements = new HashMap<String, Entitlement>();
        for (Entry<String, Entitlement> entry : entitlementMap.entrySet()) {
            Entitlement entitlement = entry.getValue();
            Map<String, String> attributes = PoolHelper.getFlattenedAttributes(entitlement.getPool());
            if (attributes.containsKey("virt_limit")) {
                virtLimitEntitlements.put(entry.getKey(), entitlement);
                flatAttributeMaps.put(entry.getKey(), attributes);
            }
        }
        // Perform pool management based on the attributes of the pool:
        postBindVirtLimit(poolManager, consumer, virtLimitEntitlements, flatAttributeMaps, subPoolsForStackIds);
    }

    protected void runPostUnbind(PoolManager poolManager, Entitlement entitlement) {
        Pool pool = entitlement.getPool();

        // Can this attribute appear on pools?
        if (pool.hasAttribute(Product.Attributes.VIRT_LIMIT)
                || pool.getProduct().hasAttribute(Product.Attributes.VIRT_LIMIT)) {

            Map<String, String> attributes = PoolHelper.getFlattenedAttributes(pool);
            Consumer c = entitlement.getConsumer();
            postUnbindVirtLimit(poolManager, entitlement, pool, c, attributes);
        }
    }

    private void postUnbindVirtLimit(PoolManager poolManager, Entitlement entitlement, Pool pool, Consumer c,
            Map<String, String> attributes) {

        log.debug("Running virt_limit post unbind.");

        boolean hostLimited = "true".equals(attributes.get(Product.Attributes.HOST_LIMITED));

        if (!config.getBoolean(ConfigProperties.STANDALONE) && !hostLimited && c.getType().isManifest()) {
            // We're making an assumption that VIRT_LIMIT is defined the same way in every possible
            // source for the attributes map.
            String virtLimit = attributes.get(Product.Attributes.VIRT_LIMIT);

            if (!"unlimited".equals(virtLimit)) {
                // As we have unbound an entitlement from a physical pool that was previously
                // exported, we need to add back the reduced bonus pool quantity.
                int virtQuantity = Integer.parseInt(virtLimit) * entitlement.getQuantity();
                if (virtQuantity > 0) {
                    List<Pool> pools = poolManager.lookupBySubscriptionId(pool.getSubscriptionId());
                    for (int idex = 0; idex < pools.size(); idex++) {
                        Pool derivedPool = pools.get(idex);
                        if (derivedPool.getAttributeValue(Pool.Attributes.DERIVED_POOL) != null) {
                            poolManager.updatePoolQuantity(derivedPool, virtQuantity);
                        }
                    }
                }
            } else {
                // As we have unbound an entitlement from a physical pool that
                // was previously
                // exported, we need to set the unlimited bonus pool quantity to
                // -1.
                List<Pool> pools = poolManager.lookupBySubscriptionId(pool.getSubscriptionId());
                for (int idex = 0; idex < pools.size(); idex++) {
                    Pool derivedPool = pools.get(idex);
                    if (derivedPool.getAttributeValue(Pool.Attributes.DERIVED_POOL) != null
                            && derivedPool.getQuantity() == 0) {

                        poolManager.setPoolQuantity(derivedPool, -1);
                    }
                }
            }
        }
    }

    private void postBindVirtLimit(PoolManager poolManager, Consumer c, Map<String, Entitlement> entitlementMap,
            Map<String, Map<String, String>> attributeMaps, List<Pool> subPoolsForStackIds) {

        Set<String> stackIdsThathaveSubPools = new HashSet<String>();
        if (CollectionUtils.isNotEmpty(subPoolsForStackIds)) {
            for (Pool pool : subPoolsForStackIds) {
                stackIdsThathaveSubPools.add(pool.getSourceStackId());
            }
        }

        log.debug("Running virt_limit post-bind.");

        boolean consumerFactExpression = !c.getType().isManifest()
                && !"true".equalsIgnoreCase(c.getFact("virt.is_guest"));

        boolean isStandalone = config.getBoolean(ConfigProperties.STANDALONE);

        List<Pool> createHostRestrictedPoolFor = new ArrayList<Pool>();
        List<Entitlement> decrementHostedBonusPoolQuantityFor = new ArrayList<Entitlement>();

        for (Entitlement entitlement : entitlementMap.values()) {
            Pool pool = entitlement.getPool();
            Map<String, String> attributes = attributeMaps.get(pool.getId());
            boolean hostLimited = "true".equals(attributes.get(Product.Attributes.HOST_LIMITED));

            if (consumerFactExpression && (isStandalone || hostLimited)) {
                String virtLimit = attributes.get(Product.Attributes.VIRT_LIMIT);
                String stackId = attributes.get(Product.Attributes.STACKING_ID);

                if (stackId == null || !stackIdsThathaveSubPools.contains(stackId)) {
                    log.debug("Creating a new sub-pool for {}", pool);
                    try {
                        int virtQuantity = Integer.parseInt(virtLimit);
                        if (virtQuantity <= 0) {
                            continue;
                        }
                    } catch (NumberFormatException nfe) {
                        if (!"unlimited".equals(virtLimit)) {
                            continue;
                        }
                    }
                    createHostRestrictedPoolFor.add(pool);
                } else {
                    log.debug("Skipping sub-pool creation for: {}", pool);
                }
            } else {
                decrementHostedBonusPoolQuantityFor.add(entitlement);
            }
        }

        if (CollectionUtils.isNotEmpty(createHostRestrictedPoolFor)) {
            log.debug("creating host restricted pools for: {}", createHostRestrictedPoolFor);
            PoolHelper.createHostRestrictedPools(poolManager, c, createHostRestrictedPoolFor, entitlementMap,
                    attributeMaps);
        }
        if (CollectionUtils.isNotEmpty(decrementHostedBonusPoolQuantityFor)) {
            log.debug("decrementHostedBonusPoolQuantity for: {}", decrementHostedBonusPoolQuantityFor);
            decrementHostedBonusPoolQuantity(poolManager, c, decrementHostedBonusPoolQuantityFor, attributeMaps);
        }
    }

    /*
     * When distributors bind to virt_limit pools in hosted, we need to go adjust the
     * quantity on the bonus pool, as those entitlements have now been exported to on-site.
     */
    private void decrementHostedBonusPoolQuantity(PoolManager poolManager, Consumer c,
            List<Entitlement> entitlements, Map<String, Map<String, String>> attributesMaps) {
        boolean consumerFactExpression = c.getType().isManifest()
                && !config.getBoolean(ConfigProperties.STANDALONE);

        // pre-fetch subscription and respective pools in a batch
        Set<String> subscriptionIds = new HashSet<String>();
        for (Entitlement entitlement : entitlements) {
            subscriptionIds.add(entitlement.getPool().getSubscriptionId());
        }
        List<Pool> subscriptionPools = poolManager.lookupBySubscriptionIds(subscriptionIds);
        Map<String, List<Pool>> subscriptionPoolMap = new HashMap<String, List<Pool>>();
        for (Pool pool : subscriptionPools) {
            if (!subscriptionPoolMap.containsKey(pool.getSubscriptionId())) {
                subscriptionPoolMap.put(pool.getSubscriptionId(), new ArrayList<Pool>());
            }
            subscriptionPoolMap.get(pool.getSubscriptionId()).add(pool);
        }

        for (Entitlement entitlement : entitlements) {
            Pool pool = entitlement.getPool();
            Map<String, String> attributes = attributesMaps.get(pool.getId());

            boolean hostLimited = "true".equals(attributes.get(Product.Attributes.HOST_LIMITED));

            if (consumerFactExpression && !hostLimited) {
                String virtLimit = attributes.get(Product.Attributes.VIRT_LIMIT);
                if (!"unlimited".equals(virtLimit)) {
                    // if the bonus pool is not unlimited, then the bonus pool
                    // quantity needs to be adjusted based on the virt limit
                    int virtQuantity = Integer.parseInt(virtLimit) * entitlement.getQuantity();
                    if (virtQuantity > 0) {
                        List<Pool> pools = subscriptionPoolMap.get(pool.getSubscriptionId());
                        for (int idex = 0; idex < pools.size(); idex++) {
                            Pool derivedPool = pools.get(idex);
                            if (derivedPool.getAttributeValue(Pool.Attributes.DERIVED_POOL) != null) {
                                derivedPool = poolManager.updatePoolQuantity(derivedPool, -1 * virtQuantity);
                            }
                        }
                    }
                } else {
                    // if the bonus pool is unlimited, then the quantity needs
                    // to go to 0 when the physical pool is exhausted completely
                    // by export. A quantity of 0 will block future binds,
                    // whereas -1 does not.
                    if (pool.getQuantity().equals(pool.getExported())) {
                        // getting all pools matching the sub id. Filtering out
                        // the 'parent'.
                        List<Pool> pools = subscriptionPoolMap.get(pool.getSubscriptionId());
                        if (pools != null) {
                            for (int idex = 0; idex < pools.size(); idex++) {
                                Pool derivedPool = pools.get(idex);
                                if (derivedPool.getAttributeValue(Pool.Attributes.DERIVED_POOL) != null) {
                                    derivedPool = poolManager.setPoolQuantity(derivedPool, 0);
                                }
                            }
                        }
                    }
                }
            }
        }

    }

    public void postEntitlement(PoolManager poolManager, Consumer consumer, Map<String, Entitlement> entitlements,
            List<Pool> subPoolsForStackIds) {
        runPostEntitlement(poolManager, consumer, entitlements, subPoolsForStackIds);
    }

    public void postUnbind(Consumer c, PoolManager poolManager, Entitlement ent) {
        runPostUnbind(poolManager, ent);
    }

    @Override
    public ValidationResult preEntitlement(Consumer consumer, Pool entitlementPool, Integer quantity) {
        jsRules.reinitTo("entitlement_name_space");
        rulesInit();
        return new ValidationResult();
    }

    protected void logResult(ValidationResult result) {
        if (log.isDebugEnabled()) {
            for (ValidationError error : result.getErrors()) {
                log.debug("  Rule error: {}", error.getResourceKey());
            }

            for (ValidationWarning warning : result.getWarnings()) {
                log.debug("  Rule warning: {}", warning.getResourceKey());
            }
        }
    }

}