/**
* 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 com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.candlepin.common.jackson.HateoasArrayExclude;
import org.candlepin.common.jackson.HateoasInclude;
import org.candlepin.jackson.StringTrimmingConverter;
import org.candlepin.model.ConsumerType.ConsumerTypeEnum;
import org.candlepin.util.Util;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.apache.commons.lang.StringUtils;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.ForeignKey;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Index;
import org.hibernate.annotations.Type;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
/**
* A Consumer is the entity that uses a given Entitlement. It can be a user,
* system, or anything else we want to track as using the Entitlement.
*
* Every Consumer has an Owner which may or may not own the Entitlement. The
* Consumer's attributes or metadata is stored in a ConsumerInfo object which
* boils down to a series of name/value pairs.
*/
@XmlRootElement
@XmlAccessorType(XmlAccessType.PROPERTY)
@Entity
@Table(name = Consumer.DB_TABLE)
@JsonFilter("ConsumerFilter")
public class Consumer extends AbstractHibernateObject implements Linkable, Owned, Named, ConsumerProperty,
Eventful {
/** Name of the table backing this object in the database */
public static final String DB_TABLE = "cp_consumer";
public static final int MAX_LENGTH_OF_CONSUMER_NAME = 255;
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
@Column(length = 32)
@NotNull
private String id;
@Column(nullable = false, unique = true)
@Size(max = 255)
@NotNull
private String uuid;
@Column(nullable = false)
@Size(max = MAX_LENGTH_OF_CONSUMER_NAME)
@NotNull
private String name;
// Represents the username used to register this consumer
@Column
@Size(max = 255)
private String username;
@Column(length = 32)
@Size(max = 32)
private String entitlementStatus;
/**
* Represents an SHA256 hex string of the last calculated ComplianceStatus that was
* generated by ComplianceRules.
*/
@Column
@Size(max = 64)
private String complianceStatusHash;
@Column(length = 255, nullable = true)
@Type(type = "org.candlepin.hibernate.EmptyStringUserType")
@Size(max = 255)
private String serviceLevel;
// for selecting Y/Z stream
@Column(length = 255, nullable = true)
@Size(max = 255)
private String releaseVer;
/*
* Because this object is used both as a Hibernate object, as well as a DTO to be
* serialized and sent to callers, we do some magic with these two cert related
* fields. The idCert is a database certificated that carries bytes, the identity
* field is a DTO for transmission to the client carrying PEM in plain text, and is
* not stored in the database.
*/
@OneToOne
@JoinColumn(name = "consumer_idcert_id")
private IdentityCertificate idCert;
@OneToOne (fetch = FetchType.LAZY)
@JoinColumn(name = "cont_acc_cert_id")
private ContentAccessCertificate contentAccessCert;
@ManyToOne
@JoinColumn(nullable = false)
@ForeignKey(name = "fk_consumer_consumer_type")
private ConsumerType type;
@ManyToOne
@ForeignKey(name = "fk_consumer_owner")
@JoinColumn(nullable = false)
@Index(name = "cp_consumer_owner_fk_idx")
private Owner owner;
@ManyToOne
@ForeignKey(name = "fk_consumer_env")
@JoinColumn(nullable = true)
@Index(name = "cp_consumer_env_fk_idx")
private Environment environment;
@Column(name = "entitlement_count")
@NotNull
private Long entitlementCount;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "consumer", fetch = FetchType.LAZY)
private Set<Entitlement> entitlements;
@ElementCollection
@CollectionTable(name = "cp_consumer_facts", joinColumns = @JoinColumn(name = "cp_consumer_id"))
@MapKeyColumn(name = "mapkey")
@Column(name = "element")
//FIXME A cascade shouldn't be necessary here as ElementCollections cascade by default
//See http://stackoverflow.com/a/7696147
@Cascade({org.hibernate.annotations.CascadeType.ALL})
@JsonDeserialize(contentConverter = StringTrimmingConverter.class)
private Map<String, String> facts;
@OneToOne(cascade = CascadeType.ALL)
private KeyPair keyPair;
private Date lastCheckin;
@OneToMany(mappedBy = "consumer", orphanRemoval = true, cascade = { CascadeType.ALL })
private Set<ConsumerInstalledProduct> installedProducts;
@Transient
private boolean canActivate;
@BatchSize(size = 32)
@OneToMany(mappedBy = "consumer",
orphanRemoval = true, cascade = { CascadeType.ALL })
private List<GuestId> guestIds;
@OneToMany(mappedBy = "consumer",
orphanRemoval = true, cascade = { CascadeType.ALL })
private Set<ConsumerCapability> capabilities;
@OneToOne(mappedBy = "consumer",
orphanRemoval = true, cascade = { CascadeType.ALL })
private HypervisorId hypervisorId;
@Valid // Enable validation. See http://stackoverflow.com/a/13992948
@ElementCollection
@CollectionTable(name = "cp_consumer_content_tags", joinColumns = @JoinColumn(name = "consumer_id"))
@Column(name = "content_tag")
private Set<String> contentTags;
// An instruction for the client to initiate an autoheal request.
// WARNING: can't initialize to a default value here, we need to be able to see
// if it was specified on an incoming update, so it must be null if no value came in.
private Boolean autoheal;
// This is normally used from the owner setting. If this consumer is manifest then it
// can have a different setting as long as it exists in the owner's mode list.
@Column(name = "content_access_mode")
private String contentAccessMode;
/**
* Identifies the share recipient owner
*/
@Column(name = "recipient_owner_key")
private String recipientOwnerKey;
/**
* Length of field is required by hypersonic in the unit tests only
*
* 4194304 bytes = 4 MB
*/
@Basic(fetch = FetchType.LAZY)
@Column(name = "annotations", length = 4194304)
private String annotations;
public Consumer(String name, String userName, Owner owner, ConsumerType type) {
this();
this.name = name;
this.username = userName;
this.owner = owner;
this.type = type;
this.facts = new HashMap<String, String>();
this.installedProducts = new HashSet<ConsumerInstalledProduct>();
this.guestIds = new ArrayList<GuestId>();
this.autoheal = true;
this.serviceLevel = "";
this.entitlementCount = 0L;
}
public Consumer() {
// This constructor is for creating a new Consumer in the DB, so we'll
// generate a UUID at this point.
this.ensureUUID();
this.entitlements = new HashSet<Entitlement>();
this.setEntitlementCount(0L);
}
/**
* @return the Consumer's UUID
*/
@HateoasInclude
public String getUuid() {
return uuid;
}
public void ensureUUID() {
if (uuid == null || uuid.length() == 0) {
this.uuid = Util.generateUUID();
}
}
/**
* @param uuid the UUID of this consumer.
*/
public void setUuid(String uuid) {
this.uuid = uuid;
}
/**
* {@inheritDoc}
*/
@Override
@HateoasInclude
public String getId() {
return id;
}
/**
* @param id the db id.
*/
public void setId(String id) {
this.id = id;
}
@HateoasArrayExclude
public IdentityCertificate getIdCert() {
return idCert;
}
public void setIdCert(IdentityCertificate idCert) {
this.idCert = idCert;
}
@HateoasArrayExclude
@XmlTransient
public ContentAccessCertificate getContentAccessCert() {
return contentAccessCert;
}
public void setContentAccessCert(ContentAccessCertificate contentAccessCert) {
this.contentAccessCert = contentAccessCert;
}
/**
* @return the name of this consumer.
*/
@HateoasInclude
public String getName() {
return name;
}
/**
* @param name the name of this consumer.
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the userName
*/
public String getUsername() {
return username;
}
/**
* @param userName the userName to set
*/
public void setUsername(String userName) {
this.username = userName;
}
/**
* @return this consumers type.
*/
public ConsumerType getType() {
return type;
}
/**
* @param typeIn consumer type
*/
public void setType(ConsumerType typeIn) {
type = typeIn;
}
/**
* @return the owner of this Consumer.
*/
@Override
public Owner getOwner() {
return owner;
}
/**
* Associates an owner to this Consumer.
* @param owner owner to associate to this Consumer.
*/
public void setOwner(Owner owner) {
this.owner = owner;
}
@Override
public String toString() {
String consumerType = (this.getType() != null) ? this.getType().getLabel() : "null";
return String.format("Consumer [id: %s, uuid: %s, consumerType: %s, name: %s]",
this.getId(), this.getUuid(), consumerType, this.getName());
}
/**
* @return all facts about this consumer.
*/
@HateoasArrayExclude
public Map<String, String> getFacts() {
return facts;
}
public boolean hasFact(String fact) {
return facts.containsKey(fact);
}
/**
* Returns the value of the fact with the given key.
* @param factKey specific fact to retrieve.
* @return the value of the fact with the given key.
*/
public String getFact(String factKey) {
if (facts != null) {
return facts.get(factKey);
}
return null;
}
/**
* @param factsIn facts about this consumer.
*/
public void setFacts(Map<String, String> factsIn) {
facts = factsIn;
}
/**
* Returns if the <code>other</code> consumer's facts are
* the same as the facts of this consumer.
*
* @param other the Consumer whose facts to compare
* @return <code>true</code> if the facts are the same, <code>false</code> otherwise
*/
public boolean factsAreEqual(Consumer other) {
Map<String, String> myFacts = getFacts();
Map<String, String> otherFacts = other.getFacts();
if (myFacts == null && otherFacts == null) {
return true;
}
if (myFacts == null || otherFacts == null) {
return false;
}
if (myFacts.size() != otherFacts.size()) {
return false;
}
for (Entry<String, String> entry : myFacts.entrySet()) {
String myVal = entry.getValue();
String otherVal = otherFacts.get(entry.getKey());
if (myVal == null) {
if (otherVal != null) {
return false;
}
}
else if (!myVal.equals(otherVal)) {
return false;
}
}
return true;
}
/**
* Set a fact
* @param name to set
* @param value to set
*/
public void setFact(String name, String value) {
if (facts == null) {
facts = new HashMap<String, String>();
}
this.facts.put(name, value);
}
public long getEntitlementCount() {
if (entitlementCount == null) {
return 0;
}
return entitlementCount.longValue();
}
public void setEntitlementCount(long count) {
this.entitlementCount = count;
}
/**
* @return Returns the entitlements.
*/
@XmlTransient
public Set<Entitlement> getEntitlements() {
return entitlements;
}
/**
* @param entitlementsIn The entitlements to set.
*/
public void setEntitlements(Set<Entitlement> entitlementsIn) {
entitlements = entitlementsIn;
}
/**
* Add an Entitlement to this Consumer
* @param entitlementIn to add to this consumer
*
*/
public void addEntitlement(Entitlement entitlementIn) {
entitlementIn.setConsumer(this);
this.entitlements.add(entitlementIn);
}
public void removeEntitlement(Entitlement entitlement) {
this.entitlements.remove(entitlement);
}
/*
* Only for internal use as a pojo for resource update.
*/
public void setLastCheckin(Date lastCheckin) {
this.lastCheckin = lastCheckin;
}
@XmlTransient
public KeyPair getKeyPair() {
return keyPair;
}
public void setKeyPair(KeyPair keyPair) {
this.keyPair = keyPair;
}
@Override
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (!(anObject instanceof Consumer)) {
return false;
}
Consumer another = (Consumer) anObject;
return uuid.equals(another.getUuid());
}
@Override
public int hashCode() {
return uuid.hashCode();
}
@Override
@HateoasInclude
public String getHref() {
return "/consumers/" + getUuid();
}
public void setHref(String href) {
/*
* No-op, here to aid with updating objects which have nested objects that were
* originally sent down to the client in HATEOAS form.
*/
}
public Date getLastCheckin() {
return lastCheckin;
}
public boolean isCanActivate() {
return canActivate;
}
public void setCanActivate(boolean canActivate) {
this.canActivate = canActivate;
}
public Set<ConsumerInstalledProduct> getInstalledProducts() {
return installedProducts;
}
public void setInstalledProducts(Set<ConsumerInstalledProduct> installedProducts) {
this.installedProducts = installedProducts;
}
public void addInstalledProduct(ConsumerInstalledProduct installed) {
if (installedProducts == null) {
installedProducts = new HashSet<ConsumerInstalledProduct>();
}
installed.setConsumer(this);
installedProducts.add(installed);
}
public Boolean isAutoheal() {
return autoheal;
}
public void setAutoheal(Boolean autoheal) {
this.autoheal = autoheal;
}
/**
* @param guests the GuestIds to set
*/
@JsonProperty
public void setGuestIds(List<GuestId> guests) {
this.guestIds = guests;
}
/**
* @return the guestIds
*/
@JsonIgnore
public List<GuestId> getGuestIds() {
return guestIds;
}
public void addGuestId(GuestId guestId) {
if (guestIds == null) {
guestIds = new ArrayList<GuestId>();
}
guestId.setConsumer(this);
guestIds.add(guestId);
}
public String getEntitlementStatus() {
return entitlementStatus;
}
public void setEntitlementStatus(String status) {
this.entitlementStatus = status;
}
public String getServiceLevel() {
return serviceLevel;
}
public void setServiceLevel(String level) {
this.serviceLevel = level;
}
public Environment getEnvironment() {
return environment;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* @param releaseVer the releaseVer to set
*/
public void setReleaseVer(Release releaseVer) {
if (releaseVer == null) {
releaseVer = new Release();
}
this.releaseVer = releaseVer.getReleaseVer();
}
/**
* @return the releaseVer
*/
public Release getReleaseVer() {
return new Release(releaseVer);
}
/**
* @return the capabilities
*/
public Set<ConsumerCapability> getCapabilities() {
return capabilities;
}
/**
* @param capabilities the capabilities to set
*/
public void setCapabilities(Set<ConsumerCapability> capabilities) {
if (capabilities == null) { return; }
if (this.capabilities == null) {
this.capabilities = new HashSet<ConsumerCapability>();
}
if (!this.capabilities.equals(capabilities)) {
this.capabilities.clear();
this.capabilities.addAll(capabilities);
this.setUpdated(new Date());
for (ConsumerCapability cc : this.capabilities) {
cc.setConsumer(this);
}
}
}
/**
* @return the hypervisorId
*/
public HypervisorId getHypervisorId() {
return hypervisorId;
}
/**
* @param hypervisorId the hypervisorId to set
*/
public void setHypervisorId(HypervisorId hypervisorId) {
if (hypervisorId != null) {
hypervisorId.setConsumer(this);
}
this.hypervisorId = hypervisorId;
}
@XmlTransient
public String getComplianceStatusHash() {
return complianceStatusHash;
}
public void setComplianceStatusHash(String complianceStatusHash) {
this.complianceStatusHash = complianceStatusHash;
}
public Set<String> getContentTags() {
return contentTags;
}
public void setContentTags(Set<String> contentTags) {
this.contentTags = contentTags;
}
@Override
@XmlTransient
public Consumer getConsumer() {
return this;
}
/**
* Logic to determine if this consumer can handle a V3 or greater certificate.
* Used to determine what type of certificate to generate, as well as to guard access
* to subscription that provide too much content to fit in a V1 certificate.
*
* @return true is the consumer can handle V3 (or greater) certificates.
*/
@XmlTransient
public boolean isCertV3Capable() {
if (isManifestDistributor()) {
for (ConsumerCapability capability : getCapabilities()) {
if ("cert_v3".equals(capability.getName())) {
return true;
}
}
return false;
}
else if (getType().getLabel().equals(
ConsumerTypeEnum.HYPERVISOR.getLabel())) {
// Hypervisors in this context don't use content, so V3 is allowed:
return true;
}
else {
// NOTE: in future this might need to change to accomodate v4 certificates.
String entitlementVersion = getFact("system.certificate_version");
return entitlementVersion != null && entitlementVersion.startsWith("3.");
}
}
public String getAnnotations() {
return this.annotations;
}
public void setAnnotations(String annotations) {
this.annotations = annotations;
}
public boolean isDev() {
return !StringUtils.isEmpty(getFact("dev_sku"));
}
@JsonIgnore
public boolean isShare() {
return getType() != null && getType().isType(ConsumerTypeEnum.SHARE);
}
@JsonIgnore
public boolean isManifestDistributor() {
return getType() != null && getType().isManifest();
}
@JsonIgnore
public boolean isGuest() {
return "true".equalsIgnoreCase(this.getFact("virt.is_guest"));
}
public String getContentAccessMode() {
return this.contentAccessMode;
}
public void setContentAccessMode(String contentAccessMode) {
this.contentAccessMode = contentAccessMode;
}
public String getRecipientOwnerKey() {
return recipientOwnerKey; }
public void setRecipientOwnerKey(String recipientOwnerKey) {
this.recipientOwnerKey = recipientOwnerKey;
}
}