/**
* 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.compliance;
import org.candlepin.audit.EventSink;
import org.candlepin.model.Consumer;
import org.candlepin.model.ConsumerCurator;
import org.candlepin.model.Entitlement;
import org.candlepin.model.EntitlementCurator;
import org.candlepin.policy.js.JsRunner;
import org.candlepin.policy.js.JsonJsContext;
import org.candlepin.policy.js.RuleExecutionException;
import org.candlepin.policy.js.RulesObjectMapper;
import org.candlepin.policy.js.compliance.hash.ComplianceStatusHasher;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.List;
/**
* ComplianceRules
*
* A class used to check consumer compliance status.
*/
public class ComplianceRules {
private EntitlementCurator entCurator;
private JsRunner jsRules;
private RulesObjectMapper mapper;
private static Logger log = LoggerFactory.getLogger(ComplianceRules.class);
private StatusReasonMessageGenerator generator;
private EventSink eventSink;
// Use the curator to update consumer entitlement status every time we run compliance (with null date)
private ConsumerCurator consumerCurator;
@Inject
public ComplianceRules(JsRunner jsRules, EntitlementCurator entCurator,
StatusReasonMessageGenerator generator, EventSink eventSink,
ConsumerCurator consumerCurator, RulesObjectMapper mapper) {
this.entCurator = entCurator;
this.jsRules = jsRules;
this.generator = generator;
this.eventSink = eventSink;
this.consumerCurator = consumerCurator;
this.mapper = mapper;
jsRules.init("compliance_name_space");
}
/**
* Check compliance status for a consumer on a specific date.
* This should NOT calculate compliantUntil.
*
* @param c Consumer to check.
* @return Compliance status.
*/
public ComplianceStatus getStatus(Consumer c) {
return getStatus(c, null, false);
}
/**
* Check compliance status for a consumer on a specific date.
*
* @param c Consumer to check.
* @param date Date to check compliance status for.
* @return Compliance status.
*/
public ComplianceStatus getStatus(Consumer c, Date date) {
return getStatus(c, date, true);
}
/**
* Check compliance status for a consumer on a specific date.
*
* @param c Consumer to check.
* @param date Date to check compliance status for.
* @param calculateCompliantUntil calculate how long the system will remain compliant (expensive)
* @return Compliance status.
*/
public ComplianceStatus getStatus(Consumer c, Date date, boolean calculateCompliantUntil) {
return getStatus(c, date, calculateCompliantUntil, true, false);
}
/**
* Check compliance status for a consumer on a specific date.
*
* @param c Consumer to check.
* @param date Date to check compliance status for.
* @param calculateCompliantUntil calculate how long the system will remain compliant (expensive)
* @param updateConsumer whether or not to use consumerCurator.update
* @return Compliance status.
*/
public ComplianceStatus getStatus(Consumer c, Date date, boolean calculateCompliantUntil,
boolean updateConsumer) {
return this.getStatus(c, date, calculateCompliantUntil, updateConsumer, false);
}
/**
* Check compliance status for a consumer on a specific date.
*
* @param c Consumer to check.
* @param date Date to check compliance status for.
* @param calculateCompliantUntil calculate how long the system will remain compliant (expensive)
* @param updateConsumer whether or not to use consumerCurator.update
* @param calculateProductComplianceDateRanges calculate the individual compliance ranges for each product
* (also expensive)
* @return Compliance status.
*/
public ComplianceStatus getStatus(Consumer c, Date date, boolean calculateCompliantUntil,
boolean updateConsumer, boolean calculateProductComplianceDateRanges) {
// If this is true, we send an updated compliance event
boolean currentCompliance = false;
if (date == null) {
date = new Date();
currentCompliance = true;
}
if (currentCompliance) {
for (Entitlement ent : c.getEntitlements()) {
if (!ent.isUpdatedOnStart() && ent.isValid()) {
ent.setUpdatedOnStart(true);
entCurator.merge(ent);
}
}
}
/*
* Do not calculate compliance status for distributors and shares. It is prohibitively
* expensive and meaningless
*/
if (c.isManifestDistributor() || c.isShare()) {
return new ComplianceStatus(new Date());
}
JsonJsContext args = new JsonJsContext(mapper);
args.put("consumer", c);
args.put("entitlements", c.getEntitlements());
args.put("ondate", date);
args.put("calculateCompliantUntil", calculateCompliantUntil);
args.put("calculateProductComplianceDateRanges", calculateProductComplianceDateRanges);
args.put("log", log, false);
args.put("guestIds", c.getGuestIds());
// Convert the JSON returned into a ComplianceStatus object:
String json = jsRules.runJsFunction(String.class, "get_status", args);
try {
ComplianceStatus result = mapper.toObject(json, ComplianceStatus.class);
for (ComplianceReason reason : result.getReasons()) {
generator.setMessage(c, reason, result.getDate());
}
if (currentCompliance) {
String newHash = getComplianceStatusHash(result, c);
boolean complianceChanged = !newHash.equals(c.getComplianceStatusHash());
if (complianceChanged) {
log.debug("Compliance has changed, sending Compliance event.");
c.setComplianceStatusHash(newHash);
eventSink.emitCompliance(c, c.getEntitlements(), result);
}
boolean entStatusChanged = !result.getStatus().equals(c.getEntitlementStatus());
if (entStatusChanged) {
c.setEntitlementStatus(result.getStatus());
}
if (updateConsumer && (complianceChanged || entStatusChanged)) {
// Merge might work better here, but we use update in other places for this
consumerCurator.update(c, false);
}
}
return result;
}
catch (Exception e) {
throw new RuleExecutionException(e);
}
}
public boolean isStackCompliant(Consumer consumer, String stackId, List<Entitlement> entsToConsider) {
JsonJsContext args = new JsonJsContext(mapper);
args.put("stack_id", stackId);
args.put("consumer", consumer);
args.put("entitlements", entsToConsider);
args.put("log", log, false);
args.put("guestIds", consumer.getGuestIds());
return jsRules.runJsFunction(Boolean.class, "is_stack_compliant", args);
}
public boolean isEntitlementCompliant(Consumer consumer, Entitlement ent, Date onDate) {
List<Entitlement> ents = entCurator.listByConsumerAndDate(consumer, onDate).list();
JsonJsContext args = new JsonJsContext(mapper);
args.put("consumer", consumer);
args.put("entitlement", ent);
args.put("entitlements", ents);
args.put("log", log, false);
args.put("guestIds", consumer.getGuestIds());
return jsRules.runJsFunction(Boolean.class, "is_ent_compliant", args);
}
private String getComplianceStatusHash(ComplianceStatus status, Consumer consumer) {
ComplianceStatusHasher hasher = new ComplianceStatusHasher(consumer, status);
return hasher.hash();
}
}