Product.java

package org.example.customer;

/*
 * This is free and unencumbered software released into the public domain.
 * Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, 
 * either in source code form or as a compiled binary, for any purpose, commercial or 
 * non-commercial, and by any means.
 * 
 * In jurisdictions that recognize copyright laws, the author or authors of this 
 * software dedicate any and all copyright interest in the software to the public domain. 
 * We make this dedication for the benefit of the public at large and to the detriment of 
 * our heirs and successors. We intend this dedication to be an overt act of relinquishment in 
 * perpetuity of all present and future rights to this software under copyright law.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES 
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 * For more information, please refer to: https://unlicense.org/
*/

import java.io.Serializable;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.example.customer.utility.CustomerEntity;
import org.example.websecurity.XssSanitizer;
import org.example.websecurity.XssSanitizerImpl;

//CREATE TABLE PRODUCT (
//        ID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
//        PRODUCT_NAME VARCHAR(50) NOT NULL,
//        SUPPLIER_ID INTEGER NOT NULL,
//        UNIT_PRICE DECIMAL(12,2),
//        PACKAGE  VARCHAR(30),
//        IS_DISCONTINUED BOOLEAN,
//        CONSTRAINT PK_PRODUCT PRIMARY KEY (ID)
//     );

/**
 * The Product Entity for the Customer application.
 * <p>
 * This class represents the following DB Table:
 * 
 * <pre>
 * CREATE TABLE PRODUCT (
 *      ID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
 *      PRODUCT_NAME VARCHAR(50) NOT NULL,
 *      SUPPLIER_ID INTEGER NOT NULL,
 *      UNIT_PRICE DECIMAL(12,2),
 *      PACKAGE  VARCHAR(30),
 *      IS_DISCONTINUED BOOLEAN,
 *      CONSTRAINT PK_PRODUCT PRIMARY KEY (ID)
 *   );
 * </pre>
 * 
 * @author Jonathan Earl
 * @version 1.0  
 *
 */
public final class Product extends CustomerEntity
    implements Serializable, Comparable<Product>
{
	private static final long serialVersionUID = 1L;

	private static final Logger LOG = LogManager.getLogger();

    private String myProductName;
    private int mySupplierId;
    private double myUnitPrice;
    private String myPackaging;
    private boolean myDiscontinued;

    private XssSanitizer mySanitizer;
    
    /**
     * The default constructor for the Product class.
     * <p>
     * The initial values are:
     * <ul>
     *   <li>id: 1</li>
     *   <li>product name: Paper Envelope</li>
     *   <li>supplier id: 1</li>
     *   <li>unit price: 0.01</li>
     *   <li>packaging: single</li>
     *   <li>discontinued: false</li>
     * </ul>
     */
    public Product()
    {
        this(new XssSanitizerImpl());
        LOG.debug("Finishing the default Constructor");
    }
    
    /**
     * The overloaded constructor for the Product class that takes an XssSanitizer as input.
     * <p>
     * The initial values are:
     * <ul>
     *   <li>id: 1</li>
     *   <li>product name: Paper Envelope</li>
     *   <li>supplier id: 1</li>
     *   <li>unit price: 0.01</li>
     *   <li>packaging: single</li>
     *   <li>discontinued: false</li>
     * </ul>
     * 
     * @param sanitizer the XssSanitizer used by this instance
     */
    public Product(final XssSanitizer sanitizer)
    {
        LOG.debug("Starting the overloaded Constructor");
        final int initialId = 1;
        final String initialProductName = "Paper Envelope";
        final int initialSupplierId = 1;
        final double initialUnitPrice = 0.01;
        final String initialPackaging = "single";
        final boolean initialDiscontinued = false;
        
        mySanitizer = sanitizer;
        
        setId(initialId);
        setProductName(initialProductName);
        setSupplierId(initialSupplierId);
        setUnitPrice(initialUnitPrice);
        setPackaging(initialPackaging);
        setDiscontinued(initialDiscontinued);
    }
    
    /**
     * This will enable sorting of Products by name.
     * <br>
     * <br>
     * @param other the Product object to compare with
     * @return the sort value of negative/zero/positive
     */
    @Override
    public int compareTo(Product other)
    {
        return this.getProductName().compareToIgnoreCase(other.getProductName());
    }

    /**
     * Returns the product name value for the Product.
     *  
     * @return the product name value for the Product
     */
    public String getProductName()
    {
        LOG.debug("returning the Product Name: " + myProductName);
        return myProductName;
    }

    /**
     * Sets the product name value for the Product.
     * <p>
     * The business rules are:
     * <ul>
     *   <li>the product name must <strong>not</strong> be null</li>
     *   <li>the product name must <strong>not</strong> be empty</li>
     *   <li>the product name must max length of 50 chars</li>
     *   <li>XSS strings within the product name will be removed</li>
     * </ul>
     * 
     * @param productName the value to set into the product name field
     * @throws IllegalArgumentException if the product name is invalid
     */
    public void setProductName(final String productName)
    {
        LOG.debug("setting the Product Name");
        final int max = 50;
        
        if (productName == null)
        {
            LOG.error("Product Name must not be null");
            throw new IllegalArgumentException("Product Name must not be null");
        }
        
        String safeProductName = mySanitizer.sanitizeInput(productName);
        if (safeProductName.isEmpty())
        {
            LOG.error("Product Name must not be empty");
            throw new IllegalArgumentException("Product Name must not be empty");
        }
        if (safeProductName.length() > max)
        {
            LOG.error("Product Name must be up to 50 chars in length");
            throw new IllegalArgumentException("Product Name must be up to 50 chars in length");
        }
        LOG.debug("setting the Product Name to: " + safeProductName);
        this.myProductName = safeProductName;
    }

    /**
     * Returns the SupplierId value for the Product.
     *  
     * @return the supplierId value for the Product
     */
    public int getSupplierId()
    {
        LOG.debug("returning the SupplierId: " + mySupplierId);
        return mySupplierId;
    }

    /**
     * Sets the SupplierId value for the Product.
     * <p>
     * The business rules are:
     * <ul>
     *   <li>the SupplierId must be 1 or greater</li>
     * </ul>
     * 
     * @param supplierId the value to set into the supplier id field
     * @throws IllegalArgumentException if the id is invalid
     */
    public void setSupplierId(final int supplierId)
    {
        LOG.debug("setting the SuppliierId: " + supplierId);
        final int min = 1;
        
        if (supplierId < min)
        {
            LOG.error("SupplierId must be greater then zero");
            throw new IllegalArgumentException("SupplierId must be greater then zero");
        }
        this.mySupplierId = supplierId;
    }

    /**
     * Returns the UnitPrice value for the Product.
     *  
     * @return the UnitPrice value for the Product
     */
    public double getUnitPrice()
    {
        LOG.debug("returning the UnitPrice: " + myUnitPrice);
        return myUnitPrice;
    }

    /**
     * Sets the UnitPrice value for the Product.
     * <p>
     * The business rules are:
     * <ul>
     *   <li>the UnitPrice must be 0.01 or greater</li>
     * </ul>
     * 
     * @param unitPrice the value to set into the unitPrice field
     * @throws IllegalArgumentException if the UnitPrice is invalid
     */
    public void setUnitPrice(final double unitPrice)
    {
        LOG.debug("setting the unitPrice: " + unitPrice);
        final double min = 0.01;
        
        if (unitPrice < min)
        {
            LOG.error("unitPrice must be greater then zero");
            throw new IllegalArgumentException("unitPrice must be greater then zero");
        }
        this.myUnitPrice = unitPrice;
    }

    /**
     * Returns the packaging value for the Product.
     *  
     * @return the packaging value for the Product
     */
    public String getPackaging()
    {
        LOG.debug("returning the Packaging: " + myPackaging);
        return myPackaging;
    }

    /**
     * Sets the packaging value for the Product.
     * <p>
     * The business rules are:
     * <ul>
     *   <li>the packaging <strong>may</strong> be null</li>
     *   <li>the packaging must <strong>not</strong> be empty</li>
     *   <li>the packaging must min length of 2 chars</li>
     *   <li>the packaging must max length of 30 chars</li>
     *   <li>XSS strings within the packaging will be removed</li>
     * </ul>
     * 
     * @param packaging the value to set into the Product packaging field
     * @throws IllegalArgumentException if the packaging is invalid
     */
    public void setPackaging(final String packaging)
    {
        LOG.debug("setting the Packaging");
        final int max = 30;
        final int min = 2;
        
        if (packaging == null)
        {
            LOG.debug("Packaging is set to null");
            this.myPackaging = null;
            return;
        }
        
        String safePackaging = mySanitizer.sanitizeInput(packaging);
        if (safePackaging.isEmpty())
        {
            LOG.error("Packaging must not be empty");
            throw new IllegalArgumentException("Packaging must not be empty");
        }
        if (safePackaging.length() > max || safePackaging.length() < min)
        {
            LOG.error("Packaging must be between 2 and 30 chars in length");
            throw new IllegalArgumentException("Packaging must be between 2 and 30 chars in length");
        }
        LOG.debug("setting the Packaging to: " + safePackaging);
        this.myPackaging = safePackaging;
    }

    /**
     * Returns the Discontinued value for the Product.
     *  
     * @return the discontinued value for the Product
     */
    public boolean isDiscontinued()
    {
        LOG.debug("returning the Discontinued: " + myDiscontinued);
        return myDiscontinued;
    }

    /**
     * Sets the Discontinued value for the Product.
     * 
     * @param discontinued the value to set field
      */
    public void setDiscontinued(final boolean discontinued)
    {
        LOG.debug("setting the discontinued value to: " + discontinued);
        this.myDiscontinued = discontinued;
    }
    
    /**
     * The hashCode() method of the Product class.
     * <p>
     * <strong>This method uses:</strong>
     * <ul>
     *  <li>id</li>
     *  <li>product name</li>
     *  <li>supplier id</li>
     *  <li>unit price</li>
     *  <li>packaging</li>
     * </ul>
     * 
     * @see java.lang.Object#hashCode()
     * @return the hashCode value for this Product object
     */
    @Override
    public int hashCode()
    {
        LOG.debug("building HashCode");
        return new HashCodeBuilder()
                .append(getId())
                .append(myProductName)
                .append(mySupplierId)
                .append(myUnitPrice)
                .append(myPackaging)
                .toHashCode();
    }
    
    /**
     * The equals() method of the Product class.
     * <p>
     * <strong>This method uses:</strong>
     * <ul>
     *  <li>id</li>
     *  <li>product name</li>
     *  <li>supplier id</li>
     *  <li>unit price</li>
     *  <li>packaging</li>
     * </ul>
     * 
     * @see java.lang.Object#equals(Object obj)
     * @param obj the incoming object to compare against
     * @return true if the fields being compared are equal
     */
    @Override
    public boolean equals(final Object obj)
    {
        LOG.debug("checking equals");
        if (obj instanceof Product)
        {
            final Product other = (Product) obj;
            return new EqualsBuilder()
                    .append(getId(), other.getId())
                    .append(myProductName, other.myProductName)
                    .append(mySupplierId, other.mySupplierId)
                    .append(myUnitPrice, other.myUnitPrice)
                    .append(myPackaging, other.myPackaging)
                    .isEquals();
        }
        else
        {
            return false;
        }
    }

    /**
     * The toString method for the Product class.
     * 
     * this method will return:<br>
     * Product [myId=xxx, myProductName=xxx, mySupplierId=xxx,
     *          myUnitPrice=xxx, myPackaging=xxx, myDiscontinued=xxx]
     */
    @Override
    public String toString()
    {
    	 LOG.debug("calling toString()");
        return "Product [Id=" + getId() + ", ProductName=" + myProductName 
                + ", SupplierId=" + mySupplierId
                + ", UnitPrice=" + myUnitPrice + ", Packaging=" + myPackaging 
                + ", Discontinued=" + myDiscontinued + "]";
    }
}