Order.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 java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

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;

/**
 * The Order Entity for the Customer application.
 * <br>
 * <br>
 * This class represents the following DB Table:
 * 
 * <pre>
 * CREATE TABLE ORDERS (
 *      ID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
 *      ORDER_DATE DATE NOT NULL,
 *      CUSTOMER_ID  INTEGER,
 *      TOTAL_AMOUNT DECIMAL(12,2),
 *      ORDER_NUMBER VARCHAR(10),
 *      CONSTRAINT PK_ORDER PRIMARY KEY (ID)
 *   );
 * </pre>
 * 
 * @author Jonathan Earl
 * @version 1.0
 *
 */
public final class Order extends CustomerEntity
   implements Serializable, Comparable<Order>
{
    private static final long serialVersionUID = 1L;
    
    private static final Logger LOG = LogManager.getLogger();

    private LocalDate myOrderDate;
    private int myCustomerId;
    private double myTotalAmount;
    private String myOrderNumber;

    private XssSanitizer mySanitizer;
    
    private List<OrderItem> myOrderItems;

    /**
     * The default constructor for the Order class.
     * <br>
     * <br>
     * The initial values are:
     * <ul>
     *   <li>id: 1</li>
     *   <li>orderDate: Current Date</li>
     *   <li>customerId: 1</li>
     *   <li>totalAmount: 0.00</li>
     *   <li>orderNumber: null</li>
     *   <li>orderItems: empty</li>
     * </ul>
     */
    public Order()
    {
        this(new XssSanitizerImpl());
        LOG.debug("Finishing the default Constructor");
    }

    /**
     * The overloaded constructor for the order class that takes an XssSanitizer as input.
     * <p>
     * The initial values are:
     * <ul>
     *   <li>id: 1</li>
     *   <li>orderDate: Current Date</li>
     *   <li>customerId: 1</li>
     *   <li>totalAmount: 0.00</li>
     *   <li>orderNumber: null</li>
     *   <li>orderItems: empty</li>
     * </ul>
     * 
     * @param sanitizer the XssSanitizer used by this instance
     */
    public Order(final XssSanitizer sanitizer)
    {
        LOG.debug("Starting the overloaded Constructor");
        final int initialId = 1;
        final LocalDate initialOrderDate = LocalDate.now();
        final int initialCustomerId = 1;
        final String initialOrderNumber = null;
        final List<OrderItem> initalOrderItems = new ArrayList<OrderItem>();
        
        mySanitizer = sanitizer;
        
        setId(initialId);
        setOrderDate(initialOrderDate);
        setCustomerId(initialCustomerId);
        setOrderNumber(initialOrderNumber);
        setOrderItems(initalOrderItems);
    }
    
    /**
     * This will enable sorting of Order by Order number concatenated with Order Date.
     * <br>
     * <br>
     * @param other the Order object to compare with
     * @return the sort value of negative/zero/positive
     */
    @Override
    public int compareTo(Order other)
    {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd-yyyy");
        String thisSortValue = this.getOrderNumber() + " " 
                + this.getOrderDate().format(formatter);
        String otherSortValue = other.getOrderNumber() + " " 
                + other.getOrderDate().format(formatter);
        return thisSortValue.compareToIgnoreCase(otherSortValue);
    }

    /**
     * Returns the orderDate value for the Order.
     *  
     * @return the orderDate value for the order
     */
    public LocalDate getOrderDate()
    {
        LOG.debug("returning the Oder Date: " + myOrderDate);
        return myOrderDate;
    }

    /**
     * Sets the order date value for the Customer.
     * <p>
     * The business rules are:
     * <ul>
     *   <li>the order date must <strong>not</strong> be null</li>
     *   <li>the order date must <strong>today or earlier</strong></li>
     * </ul>
     * 
     * @param orderDate the value to set into the orderDate field
     * @throws IllegalArgumentException if the first name is invalid
     */
    public void setOrderDate(final LocalDate orderDate)
    {
        LOG.debug("setting the order Date");
        
        if (orderDate == null)
        {
            LOG.error("Order Date must not be null");
            throw new IllegalArgumentException("Order Date must not be null");
        }
        LocalDate tomorrow = LocalDate.now().plusDays(1);
        if (orderDate.isBefore(tomorrow))
        {
            this.myOrderDate = orderDate;
        }
        else
        {
            LOG.error("OrderDate must be today or earlier");
            throw new IllegalArgumentException("OrderDate must be today or earlier");
        }
    }

    /**
     * Returns the id value for the Customer Id.
     *  
     * @return the id value for the customer id
     */
    public int getCustomerId()
    {
        LOG.debug("returning the CustomerId: " + myCustomerId);
        return myCustomerId;
    }

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


    /**
     * Returns the total amount for the order.
     *  
     * @return the totalAmount value for the order
     */
    public double getTotalAmount()
    {
        LOG.debug("returning the totalAmount: " + myTotalAmount);
        return myTotalAmount;
    }
    
    /**
     * Sets the total amount value for the Orde.
     * <p>
     * The business rules are:
     * <ul>
     *   <li>the TotalAmount must be 0.01 or greater</li>
     * </ul>
     * 
     * @param totalAmount the value to set into the totalAmount field
     * @throws IllegalArgumentException if the totalAmount is invalid
     */
    public void setTotalAmount(final double totalAmount)
    {
        LOG.debug("setting the totalAmount: " + totalAmount);
        final double min = 0.01;
        
        if (totalAmount < min)
        {
            LOG.error("totalAmount must be greater then zero");
            throw new IllegalArgumentException("totalAmount must be greater then zero");
        }
        this.myTotalAmount = totalAmount;
    }

    /**
     * Returns the orderNumber value for the Order.
     *  
     * @return the orderNumber value for the order
     */
    public String getOrderNumber()
    {
        LOG.debug("returning the OrderNumber: " + myOrderNumber);
        return myOrderNumber;
    }

    /**
     * Sets the orderNumber value for the Order.
     * <p>
     * The business rules are:
     * <ul>
     *   <li>the orderNumber <strong>may</strong> be null</li>
     *   <li>the orderNumber must <strong>not</strong> be empty</li>
     *   <li>the orderNumber must min length of 2 chars</li>
     *   <li>the orderNumber must max length of 10 chars</li>
     *   <li>XSS strings within the orderNumber will be removed</li>
     * </ul>
     * 
     * @param orderNumber the value to set into the Order Number field
     * @throws IllegalArgumentException if the orderNumber is invalid
     */
    public void setOrderNumber(final String orderNumber)
    {
        LOG.debug("setting the OrderNumber");
        final int max = 10;
        final int min = 2;
        
        if (orderNumber == null)
        {
            LOG.debug("orderNumber is set to null");
            this.myOrderNumber = null;
            return;
        }
        
        String safeOrderNumber = mySanitizer.sanitizeInput(orderNumber);
        if (safeOrderNumber.isEmpty())
        {
            LOG.error("OrderNumber must not be empty");
            throw new IllegalArgumentException("OrderNumber must not be empty");
        }
        if (safeOrderNumber.length() > max || safeOrderNumber.length() < min)
        {
            LOG.error("OrderNumber must be between 2 and 10 chars in length");
            throw new IllegalArgumentException("OrderNumber must be between 2 and 10 chars in length");
        }
        LOG.debug("setting the OrderNumber to: " + safeOrderNumber);
        this.myOrderNumber = safeOrderNumber;
    }
        
    /**
     * Returns the orderItem list value for the Order.
     *  
     * @return the myOrderItems for the order
     */
    public List<OrderItem> getOrderItems()
    {
        LOG.debug("returning the OrderItems: " + myOrderItems);
        return myOrderItems;
    }

    /**
     * Sets the orderItem list value for the Order.
     * <p>
     * The business rules are:
     * <ul>
     *   <li>the orderItems must <strong>not</strong> be null</li>
     *   <li>the orderItems may be empty</li>
     * </ul>
     * 
     * @param orderItems the list to set into the orderItems
     * @throws IllegalArgumentException if the orderItems is invalid
     */
	public void setOrderItems(final List<OrderItem> orderItems)
	{
        LOG.debug("setting the Orderitems");
        if (orderItems == null)
        {
            LOG.error("Orderitems must not be null");
            throw new IllegalArgumentException("Orderitems must not be null");
        }
        double total = 0.0;
        for (OrderItem current : orderItems)
        {
            total += current.getSubTotal();
        }
        this.myTotalAmount = total;
        this.myOrderItems = orderItems;
    }

    /**
     * The hashCode() method of the Order class.
     * <p>
     * <strong>This method uses:</strong>
     * <ul>
     *  <li>id</li>
     *  <li>order date</li>
     *  <li>customer id</li>
     *  <li>order number</li>
     * </ul>
     * 
     * @see java.lang.Object#hashCode()
     * @return the hashCode value for this Order object
     */
    @Override
    public int hashCode()
    {
        LOG.debug("building HashCode");
        return new HashCodeBuilder()
                .append(getId())
                .append(myOrderDate)
                .append(myCustomerId)
                .append(myOrderNumber)
                .toHashCode();
    }

    /**
     * The equals() method of the order class.
     * <p>
     * <strong>This method uses:</strong>
     * <ul>
     *  <li>id</li>
     *  <li>order date</li>
     *  <li>customer id</li>
     *  <li>order number</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 Order)
        {
            final Order other = (Order) obj;
            return new EqualsBuilder()
                    .append(getId(), other.getId())
                    .append(myOrderDate, other.myOrderDate)
                    .append(myCustomerId, other.myCustomerId)
                    .append(myOrderNumber, other.myOrderNumber)
                    .isEquals();
        }
        else
        {
            return false;
        }
    }
    
    

    /**
     * The toString method for the Order class.
     * 
     * this method will return:<br>
     * Order [Id=xxx, OrderDate=xx/xx/xx, CustomerId=xxx, 
     *   TotalAmount=xxx, OrderNumber=xxx]
     */
    @Override
    public String toString()
    {
        return "Order [Id=" + getId() + ", OrderDate=" + myOrderDate + ", CustomerId=" + myCustomerId
                + ", TotalAmount=" + myTotalAmount + ", OrderNumber=" + myOrderNumber 
                + ", OrderItems=" + myOrderItems.size() + "]";
    }
}