/**
* Copyright (c) 2009 - 2016 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.dto;
import org.candlepin.jackson.CandlepinAttributeDeserializer;
import org.candlepin.jackson.CandlepinLegacyAttributeSerializer;
import org.candlepin.model.Content;
import org.candlepin.model.Product;
import org.candlepin.model.ProductContent;
import org.candlepin.util.MapView;
import org.candlepin.util.SetView;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
/**
* DTO representing the product data exposed to the API and adapter layers.
*
* <pre>
* {
* "uuid" : "ff808081554a3e4101554a3e9033005d",
* "id" : "5051",
* "name" : "Admin OS Developer Bits",
* "multiplier" : 1,
* "attributes" : [ ... ],
* "productContent" : [ ... ],
* "dependentProductIds" : [ ... ],
* "href" : "/products/ff808081554a3e4101554a3e9033005d",
* "created" : "2016-06-13T14:51:02+0000",
* "updated" : "2016-06-13T14:51:02+0000"
* }
* </pre>
*/
@ApiModel(parent = CandlepinDTO.class, description = "Product information for a given sku or product")
@XmlRootElement
public class ProductData extends CandlepinDTO {
public static final long serialVersionUID = 1L;
@ApiModelProperty(example = "ff808081554a3e4101554a3e9033005d")
protected String uuid;
@ApiModelProperty(required = true, example = "5051")
protected String id;
@ApiModelProperty(example = "Admin OS Developer Bits")
protected String name;
@ApiModelProperty(example = "1")
protected Long multiplier;
@JsonSerialize(using = CandlepinLegacyAttributeSerializer.class)
@JsonDeserialize(using = CandlepinAttributeDeserializer.class)
protected Map<String, String> attributes;
protected Map<String, ProductContentData> content;
protected Set<String> dependentProductIds;
@ApiModelProperty(example = "/products/ff808081554a3e4101554a3e9033005d")
protected String href;
@ApiModelProperty(hidden = true)
protected Boolean locked;
/**
* Initializes a new ProductData instance with null values.
*/
public ProductData() {
super();
}
/**
* Initializes a new ProductData instance with the specified Red Hat ID and name.
* <p/></p>
* <strong>Note</strong>: This constructor passes the provided values to their respective
* mutator methods, and does not capture any exceptions they may throw as due to malformed
* values.
*
* @param id
* The ID of the product to be represented by this DTO; cannot be null
*
* @param name
* The name of the product to be represented by this DTO
*/
public ProductData(String id, String name) {
super();
this.setId(id);
this.setName(name);
}
/**
* Initializes a new ProductData instance using the data contained by the given DTO.
*
* @param source
* The source DTO from which to copy data
*
* @throws IllegalArgumentException
* if source is null
*/
public ProductData(ProductData source) {
if (source == null) {
throw new IllegalArgumentException("source is null");
}
this.populate(source);
}
/**
* Initializes a new ProductData instance using the data contained by the given entiy.
*
* @param source
* The source entity from which to copy data
*
* @throws IllegalArgumentException
* if source is null
*/
public ProductData(Product source) {
if (source == null) {
throw new IllegalArgumentException("source is null");
}
this.populate(source);
}
/**
* Retrieves the UUID of the product represented by this DTO. If the UUID has not yet been
* defined, this method returns null.
*
* @return
* the UUID of the product, or null if the UUID has not yet been defined
*/
public String getUuid() {
return this.uuid;
}
/**
* Sets the UUID of the product represented by this DTO.
*
* @param uuid
* The UUID of the product represented by this DTO, or null to clear the UUID
*
* @return
* a reference to this DTO
*/
public ProductData setUuid(String uuid) {
this.uuid = uuid;
return this;
}
/**
* Retrieves the ID of the product represented by this DTO. If the ID has not yet been
* defined, this method returns null.
*
* @return
* the ID of the product, or null if the ID has not yet been defined
*/
public String getId() {
return this.id;
}
/**
* Sets the ID of the product represented by this DTO.
*
* @param id
* The ID of the product represented by this DTO
*
* @throws IllegalArgumentException
* if id is null or empty
*
* @return
* a reference to this DTO
*/
public ProductData setId(String id) {
if (id == null || id.length() == 0) {
throw new IllegalArgumentException("id is null or empty");
}
this.id = id;
return this;
}
/**
* Retrieves the UUID of the product represented by this DTO. If the UUID has not yet been
* defined, this method returns null.
*
* @return
* the UUID of the product, or null if the UUID has not yet been defined
*/
public String getName() {
return this.name;
}
/**
* Sets the name of the product represented by this DTO.
*
* @param name
* The name of the product represented by this DTO, or null to clear the name
*
* @return
* a reference to this DTO
*/
public ProductData setName(String name) {
this.name = name;
return this;
}
/**
* Retrieves the multiplier of the product represented by this DTO. If the multiplier has not
* yet been defined, this method returns null.
*
* @return
* the multiplier of the product, or null if the multiplier has not yet been defined
*/
public Long getMultiplier() {
return this.multiplier;
}
/**
* Sets the multiplier of the product represented by this DTO.
*
* @param multiplier
* The multiplier of the product represented by this DTO, or null to clear the multiplier
*
* @return
* a reference to this DTO
*/
public ProductData setMultiplier(Long multiplier) {
this.multiplier = multiplier;
return this;
}
/**
* Retrieves a view of the attributes for the product represented by this DTO. If the product
* attributes have not yet been defined, this method returns null.
* <p></p>
* Note that the collection returned by this method is a view of the collection backing this
* product data. Elements cannot be added to the collection, but elements may be removed.
* Changes made to the collection will be reflected by this product data instance.
*
* @return
* the attributes of the product, or null if the attributes have not yet been defined
*/
public Map<String, String> getAttributes() {
return this.attributes != null ? new MapView(this.attributes) : null;
}
/**
* Retrieves the value associated with the given attribute. If the attribute is not set, this
* method returns null.
*
* @param key
* The key (name) of the attribute to lookup
*
* @throws IllegalArgumentException
* if key is null
*
* @return
* the value set for the given attribute, or null if the attribute is not set
*/
@XmlTransient
public String getAttributeValue(String key) {
if (key == null) {
throw new IllegalArgumentException("key is null");
}
return this.attributes != null ? this.attributes.get(key) : null;
}
/**
* Checks if the given attribute has been defined on this product DTO.
*
* @param key
* The key (name) of the attribute to lookup
*
* @throws IllegalArgumentException
* if key is null
*
* @return
* true if the attribute is defined for this product; false otherwise
*/
@XmlTransient
public boolean hasAttribute(String key) {
if (key == null) {
throw new IllegalArgumentException("key is null");
}
return this.attributes != null && this.attributes.containsKey(key);
}
/**
* Sets the specified attribute for this product DTO. If the attribute has already been set for
* this product, the existing value will be overwritten.
*
* @param key
* The name or key of the attribute to set
*
* @param value
* The value to assign to the attribute
*
* @throws IllegalArgumentException
* if key is null
*
* @return
* a reference to this DTO
*/
public ProductData setAttribute(String key, String value) {
if (key == null) {
throw new IllegalArgumentException("key is null");
}
if (this.attributes == null) {
this.attributes = new HashMap<String, String>();
}
// Impl note:
// We can't standardize the value at all here; some attributes allow null, some expect
// empty strings, and others have their own sential values. Unless we make a concerted
// effort to fix all of these inconsistencies with a massive database update, we can't
// perform any input sanitation/massaging.
this.attributes.put(key, value);
return this;
}
/**
* Removes the product attribute with the given attribute key from this product DTO.
*
* @param key
* The key (name) of the attribute to remove
*
* @throws IllegalArgumentException
* if key is null
*
* @return
* true if the attribute was removed successfully; false otherwise
*/
public boolean removeAttribute(String key) {
if (key == null) {
throw new IllegalArgumentException("key is null");
}
if (this.attributes != null && this.attributes.containsKey(key)) {
this.attributes.remove(key);
return true;
}
return false;
}
/**
* Sets the attributes of the product represented by this DTO.
*
* @param attributes
* A map of product attributes to attach to this DTO, or null to clear the attributes
*
* @return
* a reference to this DTO
*/
public ProductData setAttributes(Map<String, String> attributes) {
if (attributes != null) {
if (this.attributes == null) {
this.attributes = new HashMap<String, String>();
}
else {
this.attributes.clear();
}
this.attributes.putAll(attributes);
}
else {
this.attributes = null;
}
return this;
}
/**
* Retrieves the content of the product represented by this DTO. If the product content has not
* yet been defined, this method returns null.
* <p></p>
* Note that the collection returned by this method is a view of the collection backing this
* product data. Elements cannot be added to the collection, but elements may be removed.
* Changes made to the collection will be reflected by this product data instance.
*
* @return
* the content of the product, or null if the content not yet been defined
*/
public Collection<ProductContentData> getProductContent() {
return this.content != null ? this.content.values() : null;
}
/**
* Retrieves the product content for the specified content ID. If no such content has been
* assocaited with this product DTO, this method returns null.
*
* @param contentId
* The ID of the content to retrieve
*
* @throws IllegalArgumentException
* if contentId is null
*
* @return
* the content associated with this DTO using the given ID, or null if such content does not
* exist
*/
public ProductContentData getProductContent(String contentId) {
if (contentId == null) {
throw new IllegalArgumentException("contentId is null");
}
return this.content != null ? this.content.get(contentId) : null;
}
/**
* Checks if any content with the given content ID has been associated with this product.
*
* @param contentId
* The ID of the content to check
*
* @throws IllegalArgumentException
* if contentId is null
*
* @return
* true if any content with the given content ID has been associated with this product; false
* otherwise
*/
public boolean hasContent(String contentId) {
if (contentId == null) {
throw new IllegalArgumentException("contentId is null");
}
return this.content != null ? this.content.containsKey(contentId) : false;
}
/**
* Adds the given content to this product DTO. If a matching content has already been added to
* this product, it will be overwritten by the specified content.
*
* @param contentData
* The product content DTO to add to this product
*
* @throws IllegalArgumentException
* if content is null or incomplete
*
* @return
* true if adding the content resulted in a change to this product; false otherwise
*/
public boolean addProductContent(ProductContentData contentData) {
if (contentData == null) {
throw new IllegalArgumentException("contentData is null");
}
if (contentData.getContent() == null || contentData.getContent().getId() == null) {
throw new IllegalArgumentException("contentData or incomplete");
}
// We're operating under the assumption that we won't be doing janky things like
// adding product content, then changing it. It's too bad this isn't all immutable...
boolean changed = false;
if (this.content == null) {
this.content = new HashMap<String, ProductContentData>();
changed = true;
}
else {
ProductContentData existing = this.content.get(contentData.getContent().getId());
changed = (existing == null || !existing.equals(contentData));
}
if (changed) {
this.content.put(contentData.getContent().getId(), contentData);
}
return changed;
}
/**
* Adds the given content to this product DTO. If a matching content has already been added to
* this product, it will be overwritten by the specified content.
*
* @param productContent
* The product content DTO to add to this product
*
* @throws IllegalArgumentException
* if productContent is null
*
* @return
* true if adding the content resulted in a change to this product; false otherwise
*/
public boolean addProductContent(ProductContent productContent) {
if (productContent == null) {
throw new IllegalArgumentException("productContent is null");
}
return this.addProductContent(productContent.toDTO());
}
/**
* Adds the given content to this product DTO. If a matching content has already been added to
* this product, it will be overwritten by the specified content.
*
* @param contentData
* The product content DTO to add to this product
*
* @throws IllegalArgumentException
* if content is null
*
* @return
* true if adding the content resulted in a change to this product; false otherwise
*/
public boolean addContent(ContentData contentData, boolean enabled) {
if (contentData == null) {
throw new IllegalArgumentException("contentData is null");
}
return this.addProductContent(new ProductContentData(contentData, enabled));
}
/**
* Adds the given content to this product DTO. If a matching content has already been added to
* this product, it will be overwritten by the specified content.
*
* @param content
* The product content DTO to add to this product
*
* @throws IllegalArgumentException
* if content is null
*
* @return
* true if adding the content resulted in a change to this product; false otherwise
*/
public boolean addContent(Content content, boolean enabled) {
if (content == null) {
throw new IllegalArgumentException("content is null");
}
return this.addProductContent(new ProductContentData(content.toDTO(), enabled));
}
/**
* Removes the content with the given content ID from this product DTO.
*
* @param contentId
* The ID of the content to remove
*
* @throws IllegalArgumentException
* if contentId is null
*
* @return
* true if the content was removed successfully; false otherwise
*/
public boolean removeContent(String contentId) {
if (contentId == null) {
throw new IllegalArgumentException("contentId is null");
}
boolean updated = false;
if (this.content != null) {
ProductContentData existing = this.content.remove(contentId);
updated = (existing != null);
}
return updated;
}
/**
* Removes the content represented by the given content entity from this product. Any content
* with the same ID as the ID of the given content entity will be removed.
*
* @param content
* The content entity representing the content to remove from this product
*
* @throws IllegalArgumentException
* if content is null or incomplete
*
* @return
* true if the content was removed successfully; false otherwise
*/
public boolean removeContent(Content content) {
if (content == null) {
throw new IllegalArgumentException("content is null");
}
if (content.getId() == null) {
throw new IllegalArgumentException("content is incomplete");
}
return this.removeContent(content.getId());
}
/**
* Removes the content represented by the given content DTO from this product. Any content with
* the same ID as the ID of the given content DTO will be removed.
*
* @param contentData
* The product content DTO representing the content to remove from this product
*
* @throws IllegalArgumentException
* if content is null or incomplete
*
* @return
* true if the content was removed successfully; false otherwise
*/
public boolean removeContent(ContentData contentData) {
if (contentData == null) {
throw new IllegalArgumentException("contentData is null");
}
if (contentData.getId() == null) {
throw new IllegalArgumentException("contentData is incomplete");
}
return this.removeContent(contentData.getId());
}
/**
* Removes the content represented by the given content entity from this product. Any content
* with the same ID as the ID of the given content entity will be removed.
*
* @param content
* The product content entity representing the content to remove from this product
*
* @throws IllegalArgumentException
* if content is null or incomplete
*
* @return
* true if the content was removed successfully; false otherwise
*/
public boolean removeProductContent(ProductContent content) {
if (content == null) {
throw new IllegalArgumentException("content is null");
}
if (content.getContent() == null || content.getContent().getId() == null) {
throw new IllegalArgumentException("content is incomplete");
}
return this.removeContent(content.getContent().getId());
}
/**
* Removes the content represented by the given content DTO from this product. Any content with
* the same ID as the ID of the given content DTO will be removed.
*
* @param contentData
* The product content DTO representing the content to remove from this product
*
* @throws IllegalArgumentException
* if contentData is null or incomplete
*
* @return
* true if the content was removed successfully; false otherwise
*/
public boolean removeProductContent(ProductContentData contentData) {
if (contentData == null) {
throw new IllegalArgumentException("contentData is null");
}
if (contentData.getContent() == null || contentData.getContent().getId() == null) {
throw new IllegalArgumentException("contentData is incomplete");
}
return this.removeContent(contentData.getContent().getId());
}
/**
* Sets the content of the product represented by this DTO.
*
* @param content
* A collection of product content DTO to attach to this DTO, or null to clear the content
*
* @return
* a reference to this DTO
*/
public ProductData setProductContent(Collection<ProductContentData> content) {
if (content != null) {
if (this.content == null) {
this.content = new HashMap<String, ProductContentData>();
}
else {
this.content.clear();
}
for (ProductContentData pcd : content) {
this.addProductContent(pcd);
}
}
else {
this.content = null;
}
return this;
}
/**
* Retrieves the dependent product IDs of the product represented by this DTO. If the product
* dependent product IDs have not yet been defined, this method returns null.
* <p></p>
* Note that the collection returned by this method is a view of the collection backing this
* product data. Elements cannot be added to the collection, but elements may be removed.
* Changes made to the collection will be reflected by this product data instance.
*
* @return
* the dependent product IDs of the product, or null if the dependent product IDs have not yet
* been defined
*/
public Collection<String> getDependentProductIds() {
return this.dependentProductIds != null ? new SetView(this.dependentProductIds) : null;
}
/**
* Adds the ID of the specified product as a dependent product of this product. If the product
* is already a dependent product, it will not be added again.
*
* @param productId
* The ID of the product to add as a dependent product
*
* @throws IllegalArgumentException
* if productId is null
*
* @return
* true if the dependent product was added successfully; false otherwise
*/
public boolean addDependentProductId(String productId) {
if (productId == null) {
throw new IllegalArgumentException("productId is null");
}
if (this.dependentProductIds == null) {
this.dependentProductIds = new HashSet<String>();
}
return this.dependentProductIds.add(productId);
}
/**
* Removes the specified product as a dependent product of this product. If the product is not
* dependent on this product, this method does nothing.
*
* @param productId
* The ID of the product to add as a dependent product
*
* @throws IllegalArgumentException
* if productId is null
*
* @return
* true if the dependent product was removed successfully; false otherwise
*/
public boolean removeDependentProductId(String productId) {
return this.dependentProductIds != null ? this.dependentProductIds.remove(productId) : false;
}
/**
* Sets the dependent product IDs of the product represented by this DTO.
*
* @param dependentProductIds
* A collection of dependent product IDs to attach to this DTO, or null to clear the
* dependent products
*
* @return
* a reference to this DTO
*/
public ProductData setDependentProductIds(Collection<String> dependentProductIds) {
if (dependentProductIds != null) {
if (this.dependentProductIds == null) {
this.dependentProductIds = new HashSet<String>();
}
else {
this.dependentProductIds.clear();
}
for (String pid : dependentProductIds) {
this.addDependentProductId(pid);
}
}
else {
this.dependentProductIds = null;
}
return this;
}
/**
* Retrieves the link of the product represented by this DTO. If the product hyperlink has not
* yet been defined, this method returns null.
*
* @return
* the link of the product, or null if the link have not yet been defined
*/
public String getHref() {
return this.href;
}
/**
* Sets the hyperlink of the product represented by this DTO.
*
* @param href
* The hyperlink of the product represented by this DTO, or null to clear the hyperlink
*
* @return
* a reference to this DTO
*/
public ProductData setHref(String href) {
this.href = href;
return this;
}
/**
* Retrieves the lock state of the product represented by this DTO. If the lock state has not
* yet been defined, this method returns null.
*
* @return
* the lock state of the product, or null if the lock state has not yet been defined
*/
@XmlTransient
public Boolean isLocked() {
return this.locked;
}
/**
* Sets the lock state of the product represented by this DTO.
*
* @param locked
* The lock state of the product represented by this DTO, or null to clear the state
*
* @return
* a reference to this DTO
*/
public ProductData setLocked(Boolean locked) {
this.locked = locked;
return this;
}
@Override
public String toString() {
return String.format("ProductData [id = %s, name = %s]", this.id, this.name);
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof ProductData)) {
return false;
}
if (obj == this) {
return true;
}
ProductData that = (ProductData) obj;
EqualsBuilder builder = new EqualsBuilder()
.append(this.uuid, that.uuid)
.append(this.id, that.id)
.append(this.name, that.name)
.append(this.multiplier, that.multiplier)
.append(this.attributes, that.attributes)
.append(this.content, that.content)
.append(this.dependentProductIds, that.dependentProductIds)
.append(this.href, that.href)
.append(this.locked, that.locked);
return super.equals(obj) && builder.isEquals();
}
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder(37, 7)
.append(super.hashCode())
.append(this.uuid)
.append(this.id)
.append(this.name)
.append(this.multiplier)
.append(this.href)
.append(this.attributes)
.append(this.content)
.append(this.dependentProductIds)
.append(this.locked);
return builder.toHashCode();
}
@Override
public Object clone() {
ProductData copy = (ProductData) super.clone();
if (this.attributes != null) {
copy.attributes = new HashMap<String, String>();
copy.attributes.putAll(this.attributes);
}
if (this.content != null) {
copy.content = new HashMap<String, ProductContentData>();
for (Map.Entry<String, ProductContentData> entry : this.content.entrySet()) {
copy.content.put(entry.getKey(), (ProductContentData) entry.getValue().clone());
}
}
if (this.dependentProductIds != null) {
copy.dependentProductIds = new HashSet<String>();
copy.dependentProductIds.addAll(this.dependentProductIds);
}
return copy;
}
/**
* Populates this DTO with the data from the given source DTO.
*
* @param source
* The source DTO from which to copy data
*
* @throws IllegalArgumentException
* if source is null
*
* @return
* a reference to this DTO
*/
public ProductData populate(ProductData source) {
if (source == null) {
throw new IllegalArgumentException("source is null");
}
super.populate(source);
this.uuid = source.getUuid();
this.id = source.getId();
this.name = source.getName();
this.multiplier = source.getMultiplier();
this.href = source.getHref();
this.locked = source.isLocked();
this.setAttributes(source.getAttributes());
this.setProductContent(source.getProductContent());
this.setDependentProductIds(source.getDependentProductIds());
return this;
}
/**
* Populates this DTO with data from the given source entity.
*
* @param source
* The source entity from which to copy data
*
* @throws IllegalArgumentException
* if source is null
*
* @return
* a reference to this DTO
*/
public ProductData populate(Product source) {
if (source == null) {
throw new IllegalArgumentException("source is null");
}
super.populate(source);
this.uuid = source.getUuid();
this.id = source.getId();
this.name = source.getName();
this.multiplier = source.getMultiplier();
this.href = source.getHref();
this.locked = source.isLocked();
this.setAttributes(source.getAttributes());
if (source.getProductContent() != null) {
if (this.content == null) {
this.content = new HashMap<String, ProductContentData>();
}
else {
this.content.clear();
}
for (ProductContent entity : source.getProductContent()) {
this.addProductContent(entity.toDTO());
}
}
else {
this.setProductContent(null);
}
this.setDependentProductIds(source.getDependentProductIds());
return this;
}
}