package rewards.internal.restaurant; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.stereotype.Repository; import rewards.Dining; import rewards.internal.account.Account; import common.money.Percentage; /** * Loads restaurants from a data source using the JDBC API. */ @Repository public class JdbcRestaurantRepository implements RestaurantRepository { private DataSource dataSource; @Autowired public JdbcRestaurantRepository(DataSource dataSource){ this.dataSource = dataSource; } public Restaurant findByMerchantNumber(String merchantNumber) { String sql = "select MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE, BENEFIT_AVAILABILITY_POLICY from T_RESTAURANT where MERCHANT_NUMBER = ?"; Restaurant restaurant = null; Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = dataSource.getConnection(); ps = conn.prepareStatement(sql); ps.setString(1, merchantNumber); rs = ps.executeQuery(); advanceToNextRow(rs); restaurant = mapRestaurant(rs); } catch (SQLException e) { throw new RuntimeException("SQL exception occurred finding by merchant number", e); } finally { if (rs != null) { try { // Close to prevent database cursor exhaustion rs.close(); } catch (SQLException ex) { } } if (ps != null) { try { // Close to prevent database cursor exhaustion ps.close(); } catch (SQLException ex) { } } if (conn != null) { try { // Close to prevent database connection exhaustion conn.close(); } catch (SQLException ex) { } } } return restaurant; } /** * Maps a row returned from a query of T_RESTAURANT to a Restaurant object. * * @param rs the result set with its cursor positioned at the current row */ private Restaurant mapRestaurant(ResultSet rs) throws SQLException { // get the row column data String name = rs.getString("NAME"); String number = rs.getString("MERCHANT_NUMBER"); Percentage benefitPercentage = Percentage.valueOf(rs.getString("BENEFIT_PERCENTAGE")); // map to the object Restaurant restaurant = new Restaurant(number, name); restaurant.setBenefitPercentage(benefitPercentage); restaurant.setBenefitAvailabilityPolicy(mapBenefitAvailabilityPolicy(rs)); return restaurant; } /** * Advances a ResultSet to the next row and throws an exception if there are no rows. * @param rs the ResultSet to advance * @throws EmptyResultDataAccessException if there is no next row * @throws SQLException */ private void advanceToNextRow(ResultSet rs) throws EmptyResultDataAccessException, SQLException { if (!rs.next()) { throw new EmptyResultDataAccessException(1); } } /** * Helper method that maps benefit availability policy data in the ResultSet to a fully-configured * {@link BenefitAvailabilityPolicy} object. The key column is 'BENEFIT_AVAILABILITY_POLICY', which is a * discriminator column containing a string code that identifies the type of policy. Currently supported types are: * 'A' for 'always available' and 'N' for 'never available'. * * <p> * More types could be added easily by enhancing this method. For example, 'W' for 'Weekdays only' or 'M' for 'Max * Rewards per Month'. Some of these types might require additional database column values to be configured, for * example a 'MAX_REWARDS_PER_MONTH' data column. * * @param rs the result set used to map the policy object from database column values * @return the matching benefit availability policy * @throws IllegalArgumentException if the mapping could not be performed */ private BenefitAvailabilityPolicy mapBenefitAvailabilityPolicy(ResultSet rs) throws SQLException { String policyCode = rs.getString("BENEFIT_AVAILABILITY_POLICY"); if ("A".equals(policyCode)) { return AlwaysAvailable.INSTANCE; } else if ("N".equals(policyCode)) { return NeverAvailable.INSTANCE; } else { throw new IllegalArgumentException("Not a supported policy code " + policyCode); } } /** * Returns true indicating benefit is always available. */ static class AlwaysAvailable implements BenefitAvailabilityPolicy { static final BenefitAvailabilityPolicy INSTANCE = new AlwaysAvailable(); public boolean isBenefitAvailableFor(Account account, Dining dining) { return true; } public String toString() { return "alwaysAvailable"; } } /** * Returns false indicating benefit is never available. */ static class NeverAvailable implements BenefitAvailabilityPolicy { static final BenefitAvailabilityPolicy INSTANCE = new NeverAvailable(); public boolean isBenefitAvailableFor(Account account, Dining dining) { return false; } public String toString() { return "neverAvailable"; } } }