ProductDAOImpl.java

package org.example.customerdao;

/*
* 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.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.apache.commons.dbutils.DbUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.example.customer.Product;
import org.example.customer.Supplier;
import org.example.customer.utility.CustomerEntity;
import org.example.customerdao.utility.ErrorFormatter;
import org.example.customerdao.utility.NonDeleteableRecordException;
import org.example.websecurity.UserCredentials;

/**
 * This is the ProductDAO Implementation for the Customer DAO component of the
 * Customer Web Application. This will be the primary Product database exposure
 * for the Customer Web Layer.
 * 
 * @author Jonathan Earl
 * @since 1.0
 * 
 */
public class ProductDAOImpl 
    implements ProductDAO
{
    private static final Logger LOG = LogManager.getLogger();

    private DataSource myReadOnlyDS = null;
    private DataSource myReadWriteDS = null;

    private static final String FIND_ALL_SQL = "Select * from Product";
    private static final String FIND_ALL_ACTIVE_SQL = "Select * from Product where IS_DISCONTINUED = false ";
    private static final String FIND_BY_ID_SQL = "Select * from Product where ID = ?";
    private static final String FIND_BY_SUPPLIER_SQL = "Select * from Product where SUPPLIER_ID = ?";
    private static final String ADD_SQL = "Insert into Product(PRODUCT_NAME,SUPPLIER_ID,UNIT_PRICE,PACKAGE,IS_DISCONTINUED)"
            + " VALUES(?,?,?,?,?)";
    private static final String UPDATE_SQL = "Update Product set PRODUCT_NAME = ?, SUPPLIER_ID = ?, "
            + "UNIT_PRICE = ?, PACKAGE = ?, IS_DISCONTINUED = ?" + " WHERE ID = ?";
    private static final String IS_DELETEABLE_PRODUCT_SQL = "Select COUNT(*) from Order_Item where PRODUCT_ID = ?";
    private static final String DELETE_SQL = "Delete from Product where id = ?";

    /**
     * {@inheritDoc}
     */
    @Override
    public void setReadOnlyDS(final DataSource readOnlyDS)
    {
        LOG.debug("Setting the ReadOnly DataSource");
        this.myReadOnlyDS = readOnlyDS;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setReadWriteDS(final DataSource readWriteDS)
    {
        LOG.debug("Setting the ReadWrite DataSource");
        this.myReadWriteDS = readWriteDS;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Product> findAllProducts(final UserCredentials credentials)
    {
        LOG.debug("findAllProducts");
        
        if (credentials == null || !credentials.hasRole("worker"))
        {
            LOG.error("findAllProducts - Permission refused");
            throw new IllegalArgumentException("Permission refused for this operation");
        }
          
        PreparedStatement findAll = null;
        Connection conn = null;
        ResultSet results = null;
        List<Product> data = new ArrayList<Product>();
        try
        {
            conn = myReadOnlyDS.getConnection();
            findAll = conn.prepareStatement(FIND_ALL_SQL);

            results = findAll.executeQuery();

            while (results.next())
            {
                data.add(buildProduct(results));
            }
        }
        catch (SQLException sqle)
        {
            LOG.error("Error finding all Products");
            LOG.error(ErrorFormatter.extractError(sqle));
            throw new IllegalArgumentException(sqle);
        }
        finally
        {
            DbUtils.closeQuietly(conn, findAll, results);
        }
        return data;
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public List<Product> findAllActiveProducts(final UserCredentials credentials)
    {
        LOG.debug("findAllActiveProducts");
        
        if (credentials == null || !credentials.hasRole("worker"))
        {
            LOG.error("findAllActiveProducts - Permission refused");
            throw new IllegalArgumentException("Permission refused for this operation");
        }
          
        PreparedStatement findAll = null;
        Connection conn = null;
        ResultSet results = null;
        List<Product> data = new ArrayList<Product>();
        try
        {
            conn = myReadOnlyDS.getConnection();
            findAll = conn.prepareStatement(FIND_ALL_ACTIVE_SQL);

            results = findAll.executeQuery();

            while (results.next())
            {
                data.add(buildProduct(results));
            }
        }
        catch (SQLException sqle)
        {
            LOG.error("Error finding all active Products");
            LOG.error(ErrorFormatter.extractError(sqle));
            throw new IllegalArgumentException(sqle);
        }
        finally
        {
            DbUtils.closeQuietly(conn, findAll, results);
        }
        return data;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Product findProductById(final UserCredentials credentials, final int id)
    {
        LOG.debug("findProductById");

        if (credentials == null || !credentials.hasRole("worker"))
        {
            LOG.error("findProductById - Permission refused");
            throw new IllegalArgumentException("Permission refused for this operation");
        }
                 
        PreparedStatement findById = null;
        Connection conn = null;
        ResultSet results = null;
        Product product = null;
        try
        {
            conn = myReadOnlyDS.getConnection();
            findById = conn.prepareStatement(FIND_BY_ID_SQL);

            findById.setInt(1, id);
            results = findById.executeQuery();

            if (results.next())
            {
                product = buildProduct(results);
            }
        }
        catch (SQLException sqle)
        {
            LOG.error("Error finding Product by id");
            LOG.error(ErrorFormatter.extractError(sqle));
            throw new IllegalArgumentException(sqle);
        }
        finally
        {
            DbUtils.closeQuietly(conn, findById, results);
        }
        return product;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Product> findProductsBySupplier(final UserCredentials credentials, final Supplier supplier)
    {
       LOG.debug("findProductsBySupplier");
        
        if (credentials == null || !credentials.hasRole("worker"))
        {
            LOG.error("findSuppliersByComapanyName - Permission refused");
            throw new IllegalArgumentException("Permission refused for this operation");
        }
        if (supplier == null)
        {
            LOG.error("Supplier is required");
            throw new IllegalArgumentException("Supplier is required");
        }
          
        PreparedStatement findBySupplier = null;
        Connection conn = null;
        ResultSet results = null;
        List<Product> data = new ArrayList<Product>();
        try
        {
            conn = myReadOnlyDS.getConnection();
            findBySupplier = conn.prepareStatement(FIND_BY_SUPPLIER_SQL);
            findBySupplier.setInt(1, supplier.getId());

            results = findBySupplier.executeQuery();

            while (results.next())
            {
                data.add(buildProduct(results));
            }
        }
        catch (SQLException sqle)
        {
            LOG.error("Error finding Products by Supplier");
            LOG.error(ErrorFormatter.extractError(sqle));
            throw new IllegalArgumentException(sqle);
        }
        finally
        {
            DbUtils.closeQuietly(conn, findBySupplier, results);
        }
        return data;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int addProduct(final UserCredentials credentials, final Product product)
    {
        LOG.debug("addProduct");
        
        if (credentials == null || !credentials.hasRole("manager"))
        {
            LOG.error("addProduct - Permission refused");
            throw new IllegalArgumentException("Permission refused for this operation");
        }
          
        if (product == null)
        {
            LOG.error("addProduct - null Product was provided");
            throw new IllegalArgumentException("A Product must be provided");
        }

        PreparedStatement addProduct = null;
        Connection conn = null;
        ResultSet results = null;
        int generatedId = 0;
        try
        {
            conn = myReadWriteDS.getConnection();
            addProduct = conn.prepareStatement(ADD_SQL, PreparedStatement.RETURN_GENERATED_KEYS);   
                        
            String name = product.getProductName();
            int supplierId = product.getSupplierId();
            double unitPrice = product.getUnitPrice();
            String packaging = product.getPackaging();
            boolean discontinued = product.isDiscontinued();
                              
            addProduct.setString(1, name);
            addProduct.setInt(2, supplierId);
            addProduct.setDouble(3, unitPrice);
            addProduct.setString(4, packaging);
            addProduct.setBoolean(5, discontinued);
            addProduct.executeUpdate();
            results = addProduct.getGeneratedKeys();
            if (results.next())
            {   
                generatedId = results.getInt(1);
            }
        }
        catch (SQLException sqle)
        {
            LOG.error("Error adding Product");
            LOG.error(ErrorFormatter.extractError(sqle));
            throw new IllegalArgumentException(sqle);
        }
        finally
        {
            DbUtils.closeQuietly(conn, addProduct, results);
        }
        return generatedId;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void updateProduct(final UserCredentials credentials, final Product product)
    {
      LOG.debug("updateProduct");
        
        if (credentials == null || !credentials.hasRole("manager"))
        {
            LOG.error("updateProduct - Permission refused");
            throw new IllegalArgumentException("Permission refused for this operation");
        }
          
        if (product == null)
        {
            LOG.error("updateProduct - null Product was provided");
            throw new IllegalArgumentException("A Product must be provided");
        }

        PreparedStatement updateProduct = null;
        Connection conn = null;
        try
        {
            conn = myReadWriteDS.getConnection();
            updateProduct = conn.prepareStatement(UPDATE_SQL);           
            
            int id = product.getId();
            String name = product.getProductName();
            int supplierId = product.getSupplierId();
            double unitPrice = product.getUnitPrice();
            String packaging = product.getPackaging();
            boolean discontinued = product.isDiscontinued();
                              
            updateProduct.setString(1, name);
            updateProduct.setInt(2, supplierId);
            updateProduct.setDouble(3, unitPrice);
            updateProduct.setString(4, packaging);
            updateProduct.setBoolean(5, discontinued);
            updateProduct.setInt(6, id);

            updateProduct.executeUpdate();
        }
        catch (SQLException sqle)
        {
            LOG.error("Error updating Product");
            LOG.error(ErrorFormatter.extractError(sqle));
            throw new IllegalArgumentException(sqle);
        }
        finally
        {
            DbUtils.closeQuietly(updateProduct);
            DbUtils.closeQuietly(conn);
        } 
    }

    /**
     * {@inheritDoc}
     * <p>
     * Products with Orders are not deleteable.
     */
    @Override
    public boolean isDeleteable(final UserCredentials credentials, final CustomerEntity entity)
    {
        LOG.debug("isDeleteable");
        
        if (credentials == null || entity == null)
        {
            LOG.error("isDeleteable - null Product was provided");
            throw new IllegalArgumentException("A Product must be provided");
        }
       return isDeleteable(credentials, entity.getId());
    }

    /**
     * {@inheritDoc}
     * <p>
     * Products with Orders are not deleteable.
     */
    @Override
    public boolean isDeleteable(final UserCredentials credentials, final int id)
    {
       LOG.debug("isDeleteable");
        
        if (credentials == null || !credentials.hasRole("worker"))
        {
            LOG.error("isDeleteable - Permission refused");
            throw new IllegalArgumentException("Permission refused for this operation");
        }
          
        PreparedStatement isDeleteable = null;
        Connection conn = null;
        ResultSet results = null;
        boolean isDeleteableResult = false;
        try
        {
            conn = myReadOnlyDS.getConnection();
            isDeleteable = conn.prepareStatement(IS_DELETEABLE_PRODUCT_SQL);
 
            isDeleteable.setInt(1, id);
            results = isDeleteable.executeQuery();

            if (results.next())
            {
                int count = results.getInt(1);
                if (count == 0)
                {
                    isDeleteableResult = true;
                }
            }
        }
        catch (SQLException sqle)
        {
            LOG.error("Error finding Orders by Product");
            LOG.error(ErrorFormatter.extractError(sqle));
            throw new IllegalArgumentException(sqle);
        }
        finally
        {
            DbUtils.closeQuietly(conn, isDeleteable, results);
        }
        return isDeleteableResult;    
    }

    /**
     * {@inheritDoc}
     * <p>
     * Products with Orders are not deleteable.
     */
    @Override
    public void deleteEntity(final UserCredentials credentials, final CustomerEntity entity) 
            throws NonDeleteableRecordException
    {
        LOG.debug("deleteProduct");
        
        if (entity == null)
        {
            LOG.error("deleteProduct - null Product was provided");
            throw new IllegalArgumentException("A Product must be provided");
        }
        if (!(entity instanceof Product))
        {
            LOG.error("deleteProduct - invalid CustomerEntity was provided");
            throw new IllegalArgumentException("A Product must be provided");
        } 
        deleteEntity(credentials, entity.getId());     
    }

    /**
     * {@inheritDoc}
     * <p>
     * Products with Orders are not deleteable.
     */
    @Override
    public void deleteEntity(final UserCredentials credentials, final int id) 
            throws NonDeleteableRecordException
    {
        LOG.debug("deleteProduct");
        
        if (credentials == null || !credentials.hasRole("manager"))
        {
            LOG.error("deleteProduct - Permission refused");
            throw new IllegalArgumentException("Permission refused for this operation");
        }

        if (!isDeleteable(credentials, id))
        {
            LOG.error("Product not deleteable");
            throw new NonDeleteableRecordException("This Product is not deletable");
        }

        PreparedStatement deleteProduct = null;
        Connection conn = null;
        try
        {
            conn = myReadWriteDS.getConnection();
            deleteProduct = conn.prepareStatement(DELETE_SQL);           
         
            deleteProduct.setInt(1, id);

            deleteProduct.executeUpdate();
        }
        catch (SQLException sqle)
        {
            LOG.error("Error updating Product");
            LOG.error(ErrorFormatter.extractError(sqle));
            throw new IllegalArgumentException(sqle);
        }
        finally
        {
            DbUtils.closeQuietly(deleteProduct);
            DbUtils.closeQuietly(conn);
        }  
    }
    
    private Product buildProduct(final ResultSet results) throws SQLException
    {
        Product current = new Product();

        current.setId(results.getInt("ID"));
        current.setProductName(results.getString("PRODUCT_NAME").trim());
        current.setSupplierId(results.getInt("SUPPLIER_ID"));
        current.setUnitPrice(results.getDouble("UNIT_PRICE"));
        current.setPackaging(results.getString("PACKAGE"));
        current.setDiscontinued(results.getBoolean("IS_DISCONTINUED"));
        
        LOG.trace("Found Product: " + current.toString());
        
        return current;
    }
}