public interface CategoryDao {
    Category findByCategoryId(long categoryId);

    Collection<Category> findAll();
} public class CategoryDaoGuava implements CategoryDao {

    private CategoryDao origin;

    private LoadingCache<Long, Category> cache;

    @SuppressWarnings("ConstantConditions")
    public CategoryDaoGuava(CategoryDao originCategoryDao) {
        this.origin = originCategoryDao;
        cache = GuavaUtils.makeCache((k)->origin.findByCategoryId(k));
        bulkload();
    }

    public void bulkload() {
        // Bulk-fill the cache at startup time and periodically
        cache.putAll(Maps.uniqueIndex(origin.findAll(), Category::getCategoryId));
    }


    @Override
    public Category findByCategoryId(long categoryId) {
        try {
            return cache.get(categoryId);
        } catch (Exception e) {
            throw new SimpleAffableDbException("Encountered problem loading category id "+categoryId+" into cache", e);
        }
    }

    @Override
    public Collection<Category> findAll() {
        return cache.asMap().values();
    }

} public class CategoryDaoJdbc implements CategoryDao {

    private static final String FIND_ALL_SQL =
        "SELECT " +
            "c.category_id, " +
            "c.name " +
        "FROM " +
            "category c";

    private static final String FIND_BY_CATEGORY_ID_SQL =
        "SELECT " +
            "c.category_id, " +
            "c.name " +
        "FROM " +
            "category c " +
        "WHERE " +
            "c.category_id = ?";

    @Override
    public Category findByCategoryId(long categoryId) {
        Category result = null;
        try (Connection connection = getConnection();
             PreparedStatement statement = connection.prepareStatement(FIND_BY_CATEGORY_ID_SQL)) {

             statement.setLong(1, categoryId);

             try (ResultSet resultSet = statement.executeQuery()) {
                 if (resultSet.next()) {
                     result = readCategory(resultSet);
                 }
             }
        } catch (SQLException e) {
            throw new SimpleAffableQueryDbException("Encountered problem finding category " + categoryId, e);
        }
        return result;
    }

    @Override
    public Collection<Category> findAll() {
        List<Category> result = new ArrayList<>(16);
        try (Connection connection = getConnection();
             PreparedStatement statement = connection.prepareStatement(FIND_ALL_SQL);
             ResultSet resultSet = statement.executeQuery()) {
            while (resultSet.next()) {
                Category c = readCategory(resultSet);
                result.add(c);
            }
        } catch (SQLException e) {
            throw new SimpleAffableQueryDbException("Encountered problem finding all categories", e);
        }
        return result;
    }

    private Category readCategory(ResultSet resultSet) throws SQLException {
        Long categoryId = resultSet.getLong("category_id");
        String categoryName = resultSet.getString("name");
        return new Category(categoryId, categoryName);
    }

} public interface CategoryService {
    Category findByCategoryId(long categoryId);

    List<Category> findAll();

    Category getDefaultCategory();

} public class DefaultCategoryService implements CategoryService {
    private static final long DEFAULT_CATEGORY_ID = 1001L;

    private static final Logger logger =
        LoggerFactory.getLogger(DefaultCategoryService.class);


    private CategoryDao categoryDao;

    @Override
    public Category findByCategoryId(long categoryId) {
        try {
            return categoryDao.findByCategoryId(categoryId);
        } catch (Exception e) {
            logger.error("Trouble finding category {}", categoryId, e);
            throw e;
        }
    }

    @Override
    public List<Category> findAll() {
        try {
            // Sort by category id for consistency
            return categoryDao.findAll().stream()
                .sorted(Comparator.comparingLong(Category::getCategoryId))
                .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("Trouble finding all categories", e);
            throw e;
        }
    }

    @Override
    public Category getDefaultCategory() {
        try {
            return categoryDao.findByCategoryId(DEFAULT_CATEGORY_ID);
        } catch (Exception e) {
            logger.error("Trouble finding default category {}", DEFAULT_CATEGORY_ID, e);
            throw e;
        }
    }

    public void setCategoryDao(CategoryDao categoryDao) {
        this.categoryDao = categoryDao;
    }
} public interface CustomerDao {
    long create(Connection connection, CustomerForm customerForm);

    Customer findByCustomerId(long customerId);

    List<Customer> findAll();
} public class CustomerDaoJdbc implements CustomerDao {

    private static final String CREATE_CUSTOMER_SQL =
        "INSERT INTO `customer` (`name`, email, phone, address, city_region, cc_number) " +
            "VALUES (?, ?, ?, ?, ?, ?)";

    private static final String FIND_ALL_SQL =
        "SELECT " +
            "c.customer_id, c.name, c.email, " +
            "c.phone, c.address, c.city_region, c.cc_number " +
        "FROM " +
            "customer c";

    private static final String FIND_BY_CUSTOMER_ID_SQL =
        "SELECT " +
            "c.customer_id, c.name, c.email, " +
            "c.phone, c.address, c.city_region, c.cc_number " +
        "FROM " +
            "customer c " +
        "WHERE " +
            "c.customer_id = ?";


    @Override
    public long create(final Connection connection, CustomerForm customerForm) {
        try (PreparedStatement statement =
                 connection.prepareStatement(CREATE_CUSTOMER_SQL, Statement.RETURN_GENERATED_KEYS)) {
            statement.setString(1, customerForm.getName());
            statement.setString(2, customerForm.getEmail());
            statement.setString(3, customerForm.getPhone());
            statement.setString(4, customerForm.getAddress());
            statement.setString(5, customerForm.getCityRegion());
            statement.setString(6, customerForm.getCcNumber());
            int affected = statement.executeUpdate();
            if (affected != 1) {
                throw new SimpleAffableUpdateDbException("Failed to insert a customer, affected row count = "
                    + affected);
            }
            long customerId;
            ResultSet rs = statement.getGeneratedKeys();
            if (rs.next()) {
                customerId = rs.getLong(1);
            } else {
                throw new SimpleAffableQueryDbException("Failed to retrieve customerId auto-generated key");
            }

            return customerId;
        } catch (SQLException e) {
            throw new SimpleAffableUpdateDbException("Encountered problem creating a new customer ", e);
        }
    }

    @Override
    public Customer findByCustomerId(long customerId) {
        Customer result = null;
        try (Connection connection = getConnection();
             PreparedStatement statement = connection.prepareStatement(FIND_BY_CUSTOMER_ID_SQL)) {
            statement.setLong(1, customerId);

            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    result = readCustomer(resultSet);
                }
            }
        } catch (SQLException e) {
            throw new SimpleAffableQueryDbException("Encountered problem finding customer " + customerId, e);
        }
        return result;
    }

    @Override
    public List<Customer> findAll() {
        List<Customer> result = new ArrayList<>(16);
        try (Connection connection = getConnection();
             PreparedStatement statement = connection.prepareStatement(FIND_ALL_SQL);
             ResultSet resultSet = statement.executeQuery()) {
            while (resultSet.next()) {
                Customer c = readCustomer(resultSet);
                result.add(c);
            }
        } catch (SQLException e) {
            throw new SimpleAffableQueryDbException("Encountered problem finding all categories", e);
        }

        return result;
    }

    private Customer readCustomer(ResultSet resultSet) throws SQLException {
       Long customerId = resultSet.getLong("customer_id");
       String name = resultSet.getString("name");
       String email = resultSet.getString("email");
       String phone = resultSet.getString("phone");
       String address = resultSet.getString("address");
       String cityRegion = resultSet.getString("city_region");
       String cc_number = resultSet.getString("cc_number");
       return new Customer(customerId, name, email, phone, address, cityRegion, cc_number);
    }
} public interface CustomerService {
    long create(Connection connection, CustomerForm customerForm) throws ValidationException;

    Customer findByCustomerId(long customerId);

    List<Customer> findAll();
} public class DefaultCustomerService implements CustomerService {

    private CustomerDao customerDao;

    private static final Logger logger =
        LoggerFactory.getLogger(DefaultCustomerService.class);


    private void validateForm(CustomerForm customerForm)
        throws ValidationException {

        ValidationException e = new ValidationException();
        String name = customerForm.getName();
        String email = customerForm.getEmail();
        String phone = customerForm.getPhone();
        String address = customerForm.getAddress();
        String cityRegion = customerForm.getCityRegion();
        String ccNumber = customerForm.getCcNumber();

        if (name == null
            || name.equals("")
            || name.length() > 45) {
            e.fieldError("name");
        }
        if (email == null
            || email.equals("")
            || !email.contains("@")) {
            e.fieldError("email");
        }
        if (phone == null
            || phone.equals("")
            || phone.length() < 9) {
            e.fieldError("phone");
        }
        if (address == null
            || address.equals("")
            || address.length() > 45) {
            e.fieldError("address");
        }
        if (cityRegion == null
            || cityRegion.equals("")
            || cityRegion.length() > 2) {
            e.fieldError("cityRegion");
        }
        if (ccNumber == null
            || ccNumber.equals("")
            || ccNumber.length() > 19) {
            e.fieldError("ccNumber");
        }

        if (e.hasErrors()) {
            throw e;
        }

    }


    @Override
    public long create(Connection connection, CustomerForm customerForm) throws ValidationException {
        validateForm(customerForm);
        try {
            return customerDao.create(connection, customerForm);
        } catch (Exception e) {
            logger.error("Trouble creating a customer.", e);
            throw e;
        }
    }

    @Override
    public Customer findByCustomerId(long customerId) {
        try {
            return customerDao.findByCustomerId(customerId);
        } catch (Exception e) {
            logger.error("Trouble finding customer {}", customerId, e);
            throw e;
        }
    }

    @Override
    public List<Customer> findAll() {
        try {
            return customerDao.findAll();
        } catch (Exception e) {
            logger.error("Trouble finding all customers.", e);
            throw e;
        }
    }

    public void setCustomerDao(CustomerDao customerDao) {
        this.customerDao = customerDao;
    }
} public interface CustomerOrderDao {
    long create(Connection connection, long customerId, int amount, int confirmationNumber);

    CustomerOrder findByCustomerId(long customerId);
    CustomerOrder findByCustomerOrderId(long customerOrderId);

    Collection<CustomerOrder> findAll();
} public class CustomerOrderDaoJdbc implements CustomerOrderDao {

    private static final String CREATE_ORDER_SQL =
        "INSERT INTO customer_order (amount, customer_id, confirmation_number) " +
            "VALUES (?, ?, ?)";

    private static final String FIND_ALL_SQL =
        "SELECT " +
            "co.customer_order_id, co.customer_id, co.amount, co.date_created, co.confirmation_number " +
        "FROM " +
            "customer_order co";

    private static final String FIND_BY_CUSTOMER_ID_SQL =
        "SELECT " +
            "co.customer_order_id, co.customer_id, co.amount, co.date_created, co.confirmation_number " +
        "FROM " +
            "customer_order co " +
        "WHERE " +
            "co.customer_id = ?";

    private static final String FIND_BY_CUSTOMER_ORDER_ID_SQL =
        "SELECT " +
            "co.customer_order_id, co.customer_id, co.amount, co.date_created, co.confirmation_number " +
            "FROM " +
            "customer_order co " +
            "WHERE " +
            "co.customer_order_id = ?";

    @Override
    public long create(final Connection connection, long customerId, int amount, int confirmationNumber) {
        try (PreparedStatement statement =
                 connection.prepareStatement(CREATE_ORDER_SQL, Statement.RETURN_GENERATED_KEYS)) {
            statement.setInt(1, amount);
            statement.setLong(2, customerId);
            statement.setInt(3, confirmationNumber);
            int affected = statement.executeUpdate();
            if (affected != 1) {
                throw new SimpleAffableUpdateDbException("Failed to insert an order, affected row count = "
                    + affected);
            }
            long customerOrderId;
            ResultSet rs = statement.getGeneratedKeys();
            if (rs.next()) {
                customerOrderId = rs.getLong(1);
            } else {
                throw new SimpleAffableQueryDbException("Failed to retrieve customerOrderId auto-generated key");
            }

            return customerOrderId;
        } catch (SQLException e) {
            throw new SimpleAffableUpdateDbException("Encountered problem creating a new customer ", e);
        }
    }

    @Override
    public CustomerOrder findByCustomerId(long customerId) {
        return getCustomerOrderBy(FIND_BY_CUSTOMER_ID_SQL, customerId);
    }

    @Override
    public CustomerOrder findByCustomerOrderId(long customerOrderId) {
        return getCustomerOrderBy(FIND_BY_CUSTOMER_ORDER_ID_SQL, customerOrderId);
    }

    @Override
    public Collection<CustomerOrder> findAll() {
        List<CustomerOrder> result = new ArrayList<>(16);
        try (Connection connection = getConnection();
             PreparedStatement statement = connection.prepareStatement(FIND_ALL_SQL);
             ResultSet resultSet = statement.executeQuery()) {
            while (resultSet.next()) {
                CustomerOrder customerOrder = readCustomerOrder(resultSet);
                result.add(customerOrder);
            }
        } catch (SQLException e) {
            throw new SimpleAffableQueryDbException("Encountered problem finding all orders", e);
        }

        return result;
    }

    private CustomerOrder getCustomerOrderBy(String sql, long id) {
        CustomerOrder result = null;
        try (Connection connection = getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setLong(1, id);

            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    result = readCustomerOrder(resultSet);
                }
            }
        } catch (SQLException e) {
            throw new SimpleAffableQueryDbException("Encountered problem finding customer order id=" + id, e);
        }
        return result;
    }

    private CustomerOrder readCustomerOrder(ResultSet resultSet) throws SQLException {
        Long customerOrderId = resultSet.getLong("customer_order_id");
       Long customerId = resultSet.getLong("customer_id");
       int amount = resultSet.getInt("amount");
       Date dateCreated = resultSet.getTimestamp("date_created");
       int confirmationNumber = resultSet.getInt("confirmation_number");
       return new CustomerOrder(customerOrderId, customerId, amount, dateCreated, confirmationNumber);
    }

} public interface CustomerOrderLineItemDao {
    void create(Connection connection, long customerOrderId, long productId, short quantity);
    Collection<CustomerOrderLineItem> findByCustomerOrderId(long customerOrderId);
} public class CustomerOrderLineItemDaoJdbc implements CustomerOrderLineItemDao {

    private static final String CREATE_LINE_ITEM_SQL =
        "INSERT INTO customer_order_line_item (customer_order_id, product_id, quantity) " +
         "VALUES (?, ?, ?)";

    private static final String FIND_BY_CUSTOMER_ORDER_ID_SQL =
        "SELECT " +
            "li.customer_order_id, li.product_id, li.quantity " +
        "FROM " +
            "customer_order_line_item li " +
        "WHERE " +
            "li.customer_order_id = ?";


    @Override
    public void create(Connection connection, long customerOrderId, long productId, short quantity) {
        try (PreparedStatement statement = connection.prepareStatement(CREATE_LINE_ITEM_SQL)) {
            statement.setLong(1, customerOrderId);
            statement.setLong(2, productId);
            statement.setShort(3, quantity);
            int affected = statement.executeUpdate();
            if (affected != 1) {
                throw new SimpleAffableUpdateDbException("Failed to insert an order line item, affected row count = "
                    + affected);
            }
        } catch (SQLException e) {
            throw new SimpleAffableQueryDbException("Encountered problem creating a new customer ", e);
        }
    }

    @Override
    public Collection<CustomerOrderLineItem> findByCustomerOrderId(long customerOrderId) {
        List<CustomerOrderLineItem> result = new ArrayList<>(16);
        try (Connection connection = getConnection();
             PreparedStatement statement = connection.prepareStatement(FIND_BY_CUSTOMER_ORDER_ID_SQL)) {
            statement.setLong(1, customerOrderId);

            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    result.add(readCustomerOrderLineItem(resultSet));
                }
            }
        } catch (SQLException e) {
            throw new SimpleAffableQueryDbException("Encountered problem finding customer order line items " +
                "for customer order " + customerOrderId, e);
        }
        return result;
    }


    private CustomerOrderLineItem readCustomerOrderLineItem(ResultSet resultSet) throws SQLException {
       Long customerOrderId = resultSet.getLong("customer_order_id");
       Long productId = resultSet.getLong("product_id");
       Short quantity = resultSet.getShort("quantity");
       return new CustomerOrderLineItem(customerOrderId, productId, quantity);
    }

} public interface CustomerOrderService {

    long placeOrder(CustomerForm customerForm, ShoppingCart cart) throws ValidationException;

    CustomerOrderDetails getOrderDetails(long customerOrderId);

    List<CustomerOrder> findAll();

    CustomerOrder findByCustomerId(long customerId);

    CustomerOrder findByCustomerOrderId(long customerOrderId);
} public class DefaultCustomerOrderService implements CustomerOrderService {

    private CustomerOrderDao customerOrderDao;
    private CustomerOrderLineItemDao customerOrderLineItemDao;
    private CustomerService customerService;
    private ProductDao productDao;
    private Random random = new Random();


    private static final Logger logger =
        LoggerFactory.getLogger(DefaultCustomerOrderService.class);

    @Override
    public long placeOrder(CustomerForm customerForm, ShoppingCart cart) throws ValidationException {

        try {
            return performPlaceOrder(customerForm, cart);
        } catch (Exception e) {
            logger.error("Trouble placing an order.", e);
            throw e;
        }

    }

    private long performPlaceOrder(CustomerForm customerForm, ShoppingCart cart) throws ValidationException {
        try (Connection connection = JdbcUtils.getConnection()) {
            return performPlaceOrderTransaction(customerForm, cart, connection);
        } catch (SQLException e) {
            throw new SimpleAffableDbException("Error during close connection for customer order", e);
        }
    }

    private long performPlaceOrderTransaction(CustomerForm customerForm, ShoppingCart cart, Connection connection) throws SQLException, ValidationException {
        try {
            connection.setAutoCommit(false);

            long customerId = customerService.create(connection, customerForm);
            long customerOrderId = customerOrderDao.create(connection, customerId, cart.getTotal(), generateConfirmationNumber());


            cart.getItems().forEach((item) -> customerOrderLineItemDao.create(connection, customerOrderId, item.getProductId(), item.getQuantity()));

            connection.commit();
            return customerOrderId;
        } catch (ValidationException e) {
            throw e;
        } catch (Exception e) {
            try {
                connection.rollback();
            } catch (SQLException e1) {
                throw new SimpleAffableDbException("Failed to roll back transaction", e1);
            }
            throw e;
        }
    }

    private final Function<CustomerOrderLineItem, Product> LINE_ITEM_TO_PRODUCT =
        (lineItem) -> productDao.findByProductId(lineItem.getProductId());

    @Override
    public CustomerOrderDetails getOrderDetails(long customerOrderId) {

        try {
            CustomerOrder order = customerOrderDao.findByCustomerOrderId(customerOrderId);
            Customer customer = customerService.findByCustomerId(order.getCustomerId());
            List<CustomerOrderLineItem> lineItems = new ArrayList<>(customerOrderLineItemDao.findByCustomerOrderId(customerOrderId));
            List<Product> products = lineItems.stream().map(LINE_ITEM_TO_PRODUCT).collect(Collectors.toList());

            return new CustomerOrderDetails(order, customer, products, lineItems);
        } catch (Exception e) {
            logger.error("Trouble getting order details.", e);
            throw e;
        }
    }

    @Override
    public List<CustomerOrder> findAll() {
        return customerOrderDao.findAll().stream()
            .sorted(Comparator.comparing(CustomerOrder::getDateCreated))
            .collect(Collectors.toList());
    }

    @Override
    public CustomerOrder findByCustomerId(long customerId) {
        return customerOrderDao.findByCustomerId(customerId);
    }

    @Override
    public CustomerOrder findByCustomerOrderId(long customerOrderId) {
        return customerOrderDao.findByCustomerOrderId(customerOrderId);
    }

    private int generateConfirmationNumber() {
        return random.nextInt(999999999);
    }

    public void setCustomerOrderDao(CustomerOrderDao customerOrderDao) {
        this.customerOrderDao = customerOrderDao;
    }

    public void setCustomerOrderLineItemDao(CustomerOrderLineItemDao customerOrderLineItemDao) {
        this.customerOrderLineItemDao = customerOrderLineItemDao;
    }

    public void setCustomerService(CustomerService customerService) {
        this.customerService = customerService;
    }

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }
} public class DefaultProductService implements ProductService {

    private static final Logger logger =
        LoggerFactory.getLogger(DefaultProductService.class);

    private ProductDao productDao;

    @Override
    public List<Product> findByCategoryId(long categoryId) {
        try {
            return productDao.findByCategoryId(categoryId);
        } catch (Exception e) {
            logger.error("Trouble finding product by category id {}", categoryId, e);
            throw e;
        }
    }

    @Override
    public Product findByProductId(long productId) {
        try {
            return productDao.findByProductId(productId);
        } catch (Exception e) {
            logger.error("Trouble finding product by product id {}", productId, e);
            throw e;
        }
    }

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }
} public interface ProductDao {
    List<Product> findByCategoryId(long categoryId);

    Product findByProductId(long productId);
} public class ProductDaoGuava implements ProductDao {

    private ProductDao origin;

    private LoadingCache<Long, Product> productIdToProductCache;
    private LoadingCache<Long, List<Product>> categoryIdToProductsCache;

    @SuppressWarnings("ConstantConditions")
    public ProductDaoGuava(ProductDao origin) {
        this.origin = origin;
        productIdToProductCache = GuavaUtils.makeCache(origin::findByProductId);
        categoryIdToProductsCache = GuavaUtils.makeCache(origin::findByCategoryId);
    }

    public void clear() {
        // Clear the caches periodically
        productIdToProductCache.invalidateAll();
        categoryIdToProductsCache.invalidateAll();
    }


    @Override
    public Product findByProductId(long productId) {
        try {
            return productIdToProductCache.get(productId);
        } catch (Exception e) {
            throw new SimpleAffableDbException("Encountered problem loading product id "+productId+" into cache", e);
        }
    }

    @Override
    public List<Product> findByCategoryId(long categoryId) {
        try {
            return categoryIdToProductsCache.get(categoryId);
        } catch (Exception e) {
            throw new SimpleAffableDbException("Encountered problem loading products by category id "+categoryId+" into cache", e);
        }
    }

} public class ProductDaoJdbc implements ProductDao {

    private static final String FIND_BY_CATEGORY_SQL =
        "SELECT " +
            "p.product_id, " +
            "p.category_id, " +
            "p.name, " +
            "p.price, " +
            "p.last_update " +
        "FROM " +
            "product p " +
        "WHERE " +
            "p.category_id = ?";

    private static final String FIND_BY_PRODUCT_ID_SQL =
        "SELECT " +
            " p.product_id, " +
            " p.category_id, " +
            " p.name, " +
            " p.price, " +
            " p.last_update " +
        "FROM " +
            " product p " +
        "WHERE " +
            "p.product_id = ?";


    @Override
    public List<Product> findByCategoryId(long categoryId) {
        List<Product> result = new ArrayList<>(16);

        try (Connection connection = getConnection();
        PreparedStatement statement = connection.prepareStatement(FIND_BY_CATEGORY_SQL)) {
            statement.setLong(1, categoryId);
            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    result.add(readProduct(resultSet));
                }
            }
        } catch (SQLException e) {
            throw new SimpleAffableQueryDbException("Encountered problem reading products by category", e);
        }

        return result;
    }

    @Override
    public Product findByProductId(long productId) {
        Product result = null;

        try (Connection connection = getConnection();
             PreparedStatement statement = connection.prepareStatement(FIND_BY_PRODUCT_ID_SQL)) {
            statement.setLong(1, productId);
            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    result = readProduct(resultSet);
                }
            }
        } catch (SQLException e) {
            throw new SimpleAffableQueryDbException("Encountered problem reading products by product id", e);
        }

        return result;
    }

    private Product readProduct(ResultSet resultSet) throws SQLException {
        Product result;
        Long productIdFromDb = resultSet.getLong("product_id");
        String name = resultSet.getString("name");
        int price = resultSet.getInt("price");
        Date lastUpdate = resultSet.getTimestamp("last_update");

        result = new Product(productIdFromDb, name, price, lastUpdate);
        return result;
    }
} public interface ProductService {

    List<Product> findByCategoryId(long categoryId);

    Product findByProductId(long productId);
}
import business.category.CategoryDao;
import business.category.CategoryDaoGuava;
import business.category.CategoryDaoJdbc;
import business.category.CategoryService;
import business.category.DefaultCategoryService;
import business.customer.CustomerDao;
import business.customer.CustomerDaoJdbc;
import business.customer.CustomerService;
import business.customer.DefaultCustomerService;
import business.order.CustomerOrderDao;
import business.order.CustomerOrderDaoJdbc;
import business.order.CustomerOrderLineItemDao;
import business.order.CustomerOrderLineItemDaoJdbc;
import business.order.CustomerOrderService;
import business.order.DefaultCustomerOrderService;
import business.product.DefaultProductService;
import business.product.ProductDao;
import business.product.ProductDaoGuava;
import business.product.ProductDaoJdbc;
import business.product.ProductService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * BSD 3-Clause License
 *
 * Copyright (C) 2018 Steven Atkinson <steven@nowucca.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package business;


/**
 */
public final class ApplicationContext {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private ProductService productService;

    private CategoryService categoryService;

    private CustomerService customerService;

    private CustomerOrderService customerOrderService;

    private ScheduledExecutorService executorService;

    public static ApplicationContext INSTANCE = new ApplicationContext();

    private ApplicationContext() {

        executorService = new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors());

        // wire up the business.dao layer "by hand"
        ProductDao productDao = new ProductDaoJdbc();

        ProductDaoGuava cachedProductDao = new ProductDaoGuava(productDao);