diff --git a/lab/00-rewards-common/build.gradle b/lab/00-rewards-common/build.gradle index 2a7dd7dc..38c72750 100644 --- a/lab/00-rewards-common/build.gradle +++ b/lab/00-rewards-common/build.gradle @@ -1,6 +1,6 @@ apply plugin: 'java-library' dependencies { - api "org.hibernate:hibernate-entitymanager" + api "org.hibernate.orm:hibernate-core" api "com.fasterxml.jackson.core:jackson-annotations" } diff --git a/lab/00-rewards-common/src/main/java/common/datetime/DateInterval.java b/lab/00-rewards-common/src/main/java/common/datetime/DateInterval.java index bab4956e..ddf7fcbb 100644 --- a/lab/00-rewards-common/src/main/java/common/datetime/DateInterval.java +++ b/lab/00-rewards-common/src/main/java/common/datetime/DateInterval.java @@ -1,21 +1,4 @@ package common.datetime; -public class DateInterval { - - private SimpleDate start; - - private SimpleDate end; - - public DateInterval(SimpleDate start, SimpleDate end) { - this.start = start; - this.end = end; - } - - public SimpleDate getStart() { - return start; - } - - public SimpleDate getEnd() { - return end; - } +public record DateInterval(SimpleDate start, SimpleDate end) { } diff --git a/lab/00-rewards-common/src/main/java/common/datetime/SimpleDate.java b/lab/00-rewards-common/src/main/java/common/datetime/SimpleDate.java index c453e7db..cd553299 100644 --- a/lab/00-rewards-common/src/main/java/common/datetime/SimpleDate.java +++ b/lab/00-rewards-common/src/main/java/common/datetime/SimpleDate.java @@ -1,5 +1,6 @@ package common.datetime; +import java.io.Serial; import java.io.Serializable; import java.util.Calendar; import java.util.Date; @@ -11,6 +12,7 @@ */ public class SimpleDate implements Serializable { + @Serial private static final long serialVersionUID = 2285962420279644602L; private GregorianCalendar base; @@ -57,7 +59,7 @@ public Date asDate() { /** * Returns this date in milliseconds since 1970. - * @return + * @return this date in milliseconds since 1970 */ public long inMilliseconds() { return asDate().getTime(); @@ -69,11 +71,10 @@ public int compareTo(Object date) { } public boolean equals(Object day) { - if (!(day instanceof SimpleDate)) { + if (!(day instanceof SimpleDate other)) { return false; } - SimpleDate other = (SimpleDate) day; - return (base.equals(other.base)); + return (base.equals(other.base)); } public int hashCode() { diff --git a/lab/00-rewards-common/src/main/java/common/datetime/SimpleDateEditor.java b/lab/00-rewards-common/src/main/java/common/datetime/SimpleDateEditor.java index dac2d94b..dc1b438c 100644 --- a/lab/00-rewards-common/src/main/java/common/datetime/SimpleDateEditor.java +++ b/lab/00-rewards-common/src/main/java/common/datetime/SimpleDateEditor.java @@ -11,7 +11,7 @@ */ public class SimpleDateEditor extends PropertyEditorSupport { - private DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG, Locale.ENGLISH); + private final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG, Locale.ENGLISH); @Override public String getAsText() { diff --git a/lab/00-rewards-common/src/main/java/common/money/MonetaryAmount.java b/lab/00-rewards-common/src/main/java/common/money/MonetaryAmount.java index 0894cf42..91e1e48c 100644 --- a/lab/00-rewards-common/src/main/java/common/money/MonetaryAmount.java +++ b/lab/00-rewards-common/src/main/java/common/money/MonetaryAmount.java @@ -3,19 +3,21 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import javax.persistence.Embeddable; +import jakarta.persistence.Embeddable; + +import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.math.RoundingMode; /** * A representation of money. - * * A value object. Immutable. */ @Embeddable public class MonetaryAmount implements Serializable { + @Serial private static final long serialVersionUID = -3734467432803577280L; private BigDecimal value; @@ -38,7 +40,7 @@ public MonetaryAmount(double value) { } @SuppressWarnings("unused") - private MonetaryAmount() { + public MonetaryAmount() { } private void initValue(BigDecimal value) { @@ -51,7 +53,7 @@ private void initValue(BigDecimal value) { * @return the monetary amount object */ public static MonetaryAmount valueOf(String string) { - if (string == null || string.length() == 0) { + if (string == null || string.isEmpty()) { throw new IllegalArgumentException("The monetary amount value is required"); } if (string.startsWith("$")) { diff --git a/lab/00-rewards-common/src/main/java/common/money/Percentage.java b/lab/00-rewards-common/src/main/java/common/money/Percentage.java index d233e02d..ed60c957 100644 --- a/lab/00-rewards-common/src/main/java/common/money/Percentage.java +++ b/lab/00-rewards-common/src/main/java/common/money/Percentage.java @@ -3,19 +3,20 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import javax.persistence.Embeddable; +import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.math.RoundingMode; +import jakarta.persistence.Embeddable; /** * A percentage. Represented as a decimal value with scale 2 between 0.00 and 1.00. - * * A value object. Immutable. */ @Embeddable public class Percentage implements Serializable { + @Serial private static final long serialVersionUID = 8077279865855620752L; private BigDecimal value; @@ -24,7 +25,7 @@ public class Percentage implements Serializable { * Create a new percentage from the specified value. Value must be between 0 and 1. For example, value .45 * represents 45%. If the value has more than two digits past the decimal point it will be rounded up. For example, * value .24555 rounds up to .25. - * @param the percentage value + * @param value the percentage value * @throws IllegalArgumentException if the value is not between 0 and 1 */ @JsonCreator @@ -36,7 +37,7 @@ public Percentage(BigDecimal value) { * Create a new percentage from the specified double value. Converts it to a BigDecimal with exact precision. Value * must be between 0 and 1. For example, value .45 represents 45%. If the value has more than two digits past the * decimal point it will be rounded up. For example, value .24555 rounds up to .25. - * @param the percentage value as a double + * @param value the percentage value as a double * @throws IllegalArgumentException if the value is not between 0 and 1 */ public Percentage(double value) { @@ -44,12 +45,12 @@ public Percentage(double value) { } @SuppressWarnings("unused") - private Percentage() { + public Percentage() { } private void initValue(BigDecimal value) { value = value.setScale(2, RoundingMode.HALF_UP); - if (value.compareTo(BigDecimal.ZERO) == -1 || value.compareTo(BigDecimal.ONE) == 1) { + if (value.compareTo(BigDecimal.ZERO) < 0 || value.compareTo(BigDecimal.ONE) > 0) { throw new IllegalArgumentException("Percentage value must be between 0 and 1; your value was " + value); } this.value = value; @@ -61,7 +62,7 @@ private void initValue(BigDecimal value) { * @return the percentage object */ public static Percentage valueOf(String string) { - if (string == null || string.length() == 0) { + if (string == null || string.isEmpty()) { throw new IllegalArgumentException("The percentage value is required"); } boolean percent = string.endsWith("%"); diff --git a/lab/00-rewards-common/src/main/resources/rewards/testdb/data.sql b/lab/00-rewards-common/src/main/resources/rewards/testdb/data.sql index 28a87ccd..af707235 100644 --- a/lab/00-rewards-common/src/main/resources/rewards/testdb/data.sql +++ b/lab/00-rewards-common/src/main/resources/rewards/testdb/data.sql @@ -1,78 +1,119 @@ +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456789', 'Keith and Keri Donald'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456001', 'Dollie R. Adams'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456002', 'Cornelia J. Andresen'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456003', 'Coral Villareal Betancourt'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456004', 'Chad I. Cobbs'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456005', 'Michael C. Feller'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456006', 'Michael J. Grover'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456007', 'John C. Howard'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456008', 'Ida Ketterer'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456009', 'Laina Ochoa Lucero'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456010', 'Wesley M. Mayo'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456011', 'Leslie F. Mcclary'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456012', 'John D. Mudra'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456013', 'Pietronella J. Nielsen'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456014', 'John S. Oleary'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456015', 'Glenda D. Smith'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456016', 'Willemina O. Thygesen'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456017', 'Antje Vogt'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456018', 'Julia Weber'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456019', 'Mark T. Williams'); +insert into T_ACCOUNT (NUMBER, NAME) +values ('123456020', 'Christine J. Wilson'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456789', 'Keith and Keri Donald'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456001', 'Dollie R. Adams'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456002', 'Cornelia J. Andresen'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456003', 'Coral Villareal Betancourt'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456004', 'Chad I. Cobbs'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456005', 'Michael C. Feller'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456006', 'Michael J. Grover'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456007', 'John C. Howard'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456008', 'Ida Ketterer'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456009', 'Laina Ochoa Lucero'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456010', 'Wesley M. Mayo'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456011', 'Leslie F. Mcclary'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456012', 'John D. Mudra'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456013', 'Pietronella J. Nielsen'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456014', 'John S. Oleary'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456015', 'Glenda D. Smith'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456016', 'Willemina O. Thygesen'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456017', 'Antje Vogt'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456018', 'Julia Weber'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456019', 'Mark T. Williams'); -insert into T_ACCOUNT (NUMBER, NAME) values ('123456020', 'Christine J. Wilson'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (0, '1234123412341234'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (1, '1234123412340001'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (2, '1234123412340002'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (3, '1234123412340003'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (4, '1234123412340004'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (5, '1234123412340005'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (6, '1234123412340006'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (7, '1234123412340007'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (8, '1234123412340008'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (9, '1234123412340009'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (10, '1234123412340010'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (11, '1234123412340011'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (12, '1234123412340012'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (13, '1234123412340013'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (14, '1234123412340014'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (15, '1234123412340015'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (16, '1234123412340016'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (17, '1234123412340017'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (18, '1234123412340018'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (19, '1234123412340019'); +insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) +values (20, '1234123412340020'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (0, '1234123412341234'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (1, '1234123412340001'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (2, '1234123412340002'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (3, '1234123412340003'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (4, '1234123412340004'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (5, '1234123412340005'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (6, '1234123412340006'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (7, '1234123412340007'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (8, '1234123412340008'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (9, '1234123412340009'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (10, '1234123412340010'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (11, '1234123412340011'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (12, '1234123412340012'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (13, '1234123412340013'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (14, '1234123412340014'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (15, '1234123412340015'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (16, '1234123412340016'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (17, '1234123412340017'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (18, '1234123412340018'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (19, '1234123412340019'); -insert into T_ACCOUNT_CREDIT_CARD (ACCOUNT_ID, NUMBER) values (20, '1234123412340020'); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (0, 'Annabelle', .5, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (0, 'Corgan', .5, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (3, 'Antolin', .25, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (3, 'Argus', .25, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (3, 'Gian', .25, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (3, 'Argeo', .25, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (8, 'Kai', .33, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (8, 'Kasper', .33, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (8, 'Ernst', .34, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (12, 'Brian', .75, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (12, 'Shelby', .25, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (15, 'Charles', .50, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (15, 'Thomas', .25, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (15, 'Neil', .25, 0.00); +insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) +values (17, 'Daniel', 1.0, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (0, 'Annabelle', .5, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (0, 'Corgan', .5, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (3, 'Antolin', .25, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (3, 'Argus', .25, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (3, 'Gian', .25, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (3, 'Argeo', .25, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (8, 'Kai', .33, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (8, 'Kasper', .33, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (8, 'Ernst', .34, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (12, 'Brian', .75, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (12, 'Shelby', .25, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (15, 'Charles', .50, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (15, 'Thomas', .25, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (15, 'Neil', .25, 0.00); -insert into T_ACCOUNT_BENEFICIARY (ACCOUNT_ID, NAME, ALLOCATION_PERCENTAGE, SAVINGS) - values (17, 'Daniel', 1.0, 0.00); - -insert into T_RESTAURANT (MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE, BENEFIT_AVAILABILITY_POLICY) - values ('1234567890', 'AppleBees', .08, 'A'); +insert into T_RESTAURANT (MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE, BENEFIT_AVAILABILITY_POLICY) +values ('1234567890', 'AppleBees', .08, 'A'); diff --git a/lab/00-rewards-common/src/main/resources/rewards/testdb/schema.sql b/lab/00-rewards-common/src/main/resources/rewards/testdb/schema.sql index b0324fae..acce945e 100644 --- a/lab/00-rewards-common/src/main/resources/rewards/testdb/schema.sql +++ b/lab/00-rewards-common/src/main/resources/rewards/testdb/schema.sql @@ -6,15 +6,60 @@ drop table T_REWARD if exists; drop sequence S_REWARD_CONFIRMATION_NUMBER if exists; drop table DUAL_REWARD_CONFIRMATION_NUMBER if exists; -create table T_ACCOUNT (ID integer identity primary key, NUMBER varchar(9), NAME varchar(50) not null, unique(NUMBER)); -create table T_ACCOUNT_CREDIT_CARD (ID integer identity primary key, ACCOUNT_ID integer, NUMBER varchar(16), unique(ACCOUNT_ID, NUMBER)); -create table T_ACCOUNT_BENEFICIARY (ID integer identity primary key, ACCOUNT_ID integer, NAME varchar(50), ALLOCATION_PERCENTAGE decimal(3,2) not null, SAVINGS decimal(8,2) not null, unique(ACCOUNT_ID, NAME)); -create table T_RESTAURANT (ID integer identity primary key, MERCHANT_NUMBER varchar(10) not null, NAME varchar(80) not null, BENEFIT_PERCENTAGE decimal(3,2) not null, BENEFIT_AVAILABILITY_POLICY varchar(1) not null, unique(MERCHANT_NUMBER)); -create table T_REWARD (ID integer identity primary key, CONFIRMATION_NUMBER varchar(25) not null, REWARD_AMOUNT decimal(8,2) not null, REWARD_DATE date not null, ACCOUNT_NUMBER varchar(9) not null, DINING_AMOUNT decimal not null, DINING_MERCHANT_NUMBER varchar(10) not null, DINING_DATE date not null, unique(CONFIRMATION_NUMBER)); +create table T_ACCOUNT +( + ID integer identity primary key, + NUMBER varchar(9), + NAME varchar(50) not null, + unique (NUMBER) +); +create table T_ACCOUNT_CREDIT_CARD +( + ID integer identity primary key, + ACCOUNT_ID integer, + NUMBER varchar(16), + unique (ACCOUNT_ID, NUMBER) +); +create table T_ACCOUNT_BENEFICIARY +( + ID integer identity primary key, + ACCOUNT_ID integer, + NAME varchar(50), + ALLOCATION_PERCENTAGE decimal(3, 2) not null, + SAVINGS decimal(8, 2) not null, + unique (ACCOUNT_ID, NAME) +); +create table T_RESTAURANT +( + ID integer identity primary key, + MERCHANT_NUMBER varchar(10) not null, + NAME varchar(80) not null, + BENEFIT_PERCENTAGE decimal(3, 2) not null, + BENEFIT_AVAILABILITY_POLICY varchar(1) not null, + unique (MERCHANT_NUMBER) +); +create table T_REWARD +( + ID integer identity primary key, + CONFIRMATION_NUMBER varchar(25) not null, + REWARD_AMOUNT decimal(8, 2) not null, + REWARD_DATE date not null, + ACCOUNT_NUMBER varchar(9) not null, + DINING_AMOUNT decimal not null, + DINING_MERCHANT_NUMBER varchar(10) not null, + DINING_DATE date not null, + unique (CONFIRMATION_NUMBER) +); create sequence S_REWARD_CONFIRMATION_NUMBER start with 1; -create table DUAL_REWARD_CONFIRMATION_NUMBER (ZERO integer); -insert into DUAL_REWARD_CONFIRMATION_NUMBER values (0); - -alter table T_ACCOUNT_CREDIT_CARD add constraint FK_ACCOUNT_CREDIT_CARD foreign key (ACCOUNT_ID) references T_ACCOUNT(ID) on delete cascade; -alter table T_ACCOUNT_BENEFICIARY add constraint FK_ACCOUNT_BENEFICIARY foreign key (ACCOUNT_ID) references T_ACCOUNT(ID) on delete cascade; \ No newline at end of file +create table DUAL_REWARD_CONFIRMATION_NUMBER +( + ZERO integer +); +insert into DUAL_REWARD_CONFIRMATION_NUMBER +values (0); + +alter table T_ACCOUNT_CREDIT_CARD + add constraint FK_ACCOUNT_CREDIT_CARD foreign key (ACCOUNT_ID) references T_ACCOUNT (ID) on delete cascade; +alter table T_ACCOUNT_BENEFICIARY + add constraint FK_ACCOUNT_BENEFICIARY foreign key (ACCOUNT_ID) references T_ACCOUNT (ID) on delete cascade; \ No newline at end of file diff --git a/lab/00-rewards-common/src/test/java/common/datetime/SimpleDateEditorTests.java b/lab/00-rewards-common/src/test/java/common/datetime/SimpleDateEditorTests.java index b2c7fb9e..42dc0e85 100644 --- a/lab/00-rewards-common/src/test/java/common/datetime/SimpleDateEditorTests.java +++ b/lab/00-rewards-common/src/test/java/common/datetime/SimpleDateEditorTests.java @@ -5,28 +5,28 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -public class SimpleDateEditorTests { +class SimpleDateEditorTests { - private SimpleDateEditor editor = new SimpleDateEditor(); + SimpleDateEditor editor = new SimpleDateEditor(); - @Test - public void testGetAsText() { - SimpleDate date = new SimpleDate(12, 29, 1977); - editor.setValue(date); - assertEquals("December 29, 1977", editor.getAsText()); - } + @Test + void testGetAsText() { + SimpleDate date = new SimpleDate(12, 29, 1977); + editor.setValue(date); + assertEquals("December 29, 1977", editor.getAsText()); + } - @Test - public void testSetAsText() { - editor.setAsText("December 29, 1977"); - SimpleDate date = (SimpleDate) editor.getValue(); - assertEquals(new SimpleDate(12, 29, 1977), date); - } + @Test + void testSetAsText() { + editor.setAsText("December 29, 1977"); + SimpleDate date = (SimpleDate) editor.getValue(); + assertEquals(new SimpleDate(12, 29, 1977), date); + } - @Test - public void testSetAsTextBogus() { - assertThrows(IllegalArgumentException.class, () -> { - editor.setAsText("December 29th, 1977"); - }); - } + @Test + void testSetAsTextBogus() { + assertThrows(IllegalArgumentException.class, () -> + editor.setAsText("December 29th, 1977") + ); + } } diff --git a/lab/00-rewards-common/src/test/java/common/datetime/SimpleDateTests.java b/lab/00-rewards-common/src/test/java/common/datetime/SimpleDateTests.java index c006ba11..77dd8422 100644 --- a/lab/00-rewards-common/src/test/java/common/datetime/SimpleDateTests.java +++ b/lab/00-rewards-common/src/test/java/common/datetime/SimpleDateTests.java @@ -11,10 +11,10 @@ * Unit tests for the "Simple Date" wrapper around a Calendar that tracks * month/date/year only, with no provision for tracking time. */ -public class SimpleDateTests { +class SimpleDateTests { @Test - public void testToday() { + void testToday() { SimpleDate today = SimpleDate.today(); Calendar cal = new GregorianCalendar(); cal.set(Calendar.HOUR_OF_DAY, 0); @@ -25,7 +25,7 @@ public void testToday() { } @Test - public void testValueOfDate() { + void testValueOfDate() { SimpleDate today = SimpleDate.today(); Date date = today.asDate(); SimpleDate today2 = SimpleDate.valueOf(date); @@ -33,7 +33,7 @@ public void testValueOfDate() { } @Test - public void testValueOfTime() { + void testValueOfTime() { SimpleDate today = SimpleDate.today(); long time = today.inMilliseconds(); SimpleDate today2 = SimpleDate.valueOf(time); diff --git a/lab/00-rewards-common/src/test/java/common/money/MonetaryAmountTests.java b/lab/00-rewards-common/src/test/java/common/money/MonetaryAmountTests.java index 837a00aa..64a94b29 100644 --- a/lab/00-rewards-common/src/test/java/common/money/MonetaryAmountTests.java +++ b/lab/00-rewards-common/src/test/java/common/money/MonetaryAmountTests.java @@ -8,21 +8,21 @@ /** * Unit tests that make sure the MonetaryAmount class works in isolation. */ -public class MonetaryAmountTests { +class MonetaryAmountTests { @Test - public void testMonetaryAmountValueOfString() { + void testMonetaryAmountValueOfString() { MonetaryAmount amount = MonetaryAmount.valueOf("$100"); assertEquals("$100.00", amount.toString()); } @Test - public void testMonetaryCreation() { + void testMonetaryCreation() { MonetaryAmount amt = MonetaryAmount.valueOf("100.00"); assertEquals("$100.00", amt.toString()); } @Test - public void testMonetaryAdd() { + void testMonetaryAdd() { MonetaryAmount amt1 = MonetaryAmount.valueOf("100.00"); MonetaryAmount amt2 = MonetaryAmount.valueOf("100.00"); assertEquals(MonetaryAmount.valueOf("200.00"), amt1.add(amt2)); @@ -30,31 +30,31 @@ public void testMonetaryAdd() { } @Test - public void testMultiplyByPercentage() { + void testMultiplyByPercentage() { MonetaryAmount amt = MonetaryAmount.valueOf("100.005"); assertEquals(MonetaryAmount.valueOf("8.00"), amt.multiplyBy(Percentage.valueOf("8%"))); } @Test - public void testMultiplyByDecimal() { + void testMultiplyByDecimal() { MonetaryAmount amt = MonetaryAmount.valueOf("100.005"); - assertEquals(MonetaryAmount.valueOf("8.00"), amt.multiplyBy(new BigDecimal(0.08))); + assertEquals(MonetaryAmount.valueOf("8.00"), amt.multiplyBy(new BigDecimal("0.08"))); } @Test - public void testDivideByMonetaryAmount() { + void testDivideByMonetaryAmount() { MonetaryAmount amt = MonetaryAmount.valueOf("100.005"); - assertEquals(new BigDecimal(12.5), amt.divide(MonetaryAmount.valueOf("8.00"))); + assertEquals(new BigDecimal("12.5"), amt.divide(MonetaryAmount.valueOf("8.00"))); } @Test - public void testDivideByDecimal() { + void testDivideByDecimal() { MonetaryAmount amt = MonetaryAmount.valueOf("100.005"); - assertEquals(MonetaryAmount.valueOf("8.00"), amt.divideBy(new BigDecimal(12.5))); + assertEquals(MonetaryAmount.valueOf("8.00"), amt.divideBy(new BigDecimal("12.5"))); } @Test - public void testDoubleEquality() { + void testDoubleEquality() { MonetaryAmount amt = MonetaryAmount.valueOf(".1"); assertEquals(new BigDecimal(".10"), amt.asBigDecimal()); } diff --git a/lab/00-rewards-common/src/test/java/common/money/PercentageTests.java b/lab/00-rewards-common/src/test/java/common/money/PercentageTests.java index 6564896d..511199c5 100644 --- a/lab/00-rewards-common/src/test/java/common/money/PercentageTests.java +++ b/lab/00-rewards-common/src/test/java/common/money/PercentageTests.java @@ -6,35 +6,35 @@ /** * Unit tests that make sure the Percentage class works in isolation. */ -public class PercentageTests { +class PercentageTests { @Test - public void testPercentageValueOfString() { + void testPercentageValueOfString() { Percentage percentage = Percentage.valueOf("100%"); assertEquals("100%", percentage.toString()); } @Test - public void testPercentage() { + void testPercentage() { assertEquals(Percentage.valueOf("0.01"), Percentage.valueOf("1%")); } @Test - public void testPercentageEquality() { + void testPercentageEquality() { Percentage percentage1 = Percentage.valueOf("25%"); Percentage percentage2 = Percentage.valueOf("25%"); assertEquals(percentage1, percentage2); } @Test - public void testNewPercentage() { + void testNewPercentage() { Percentage p = new Percentage(.25); assertEquals("25%", p.toString()); } @Test - public void testNewPercentageWithRounding() { + void testNewPercentageWithRounding() { Percentage p = new Percentage(.255555); assertEquals("26%", p.toString()); } diff --git a/lab/01-rewards-db/build.gradle b/lab/01-rewards-db/build.gradle index 4d5f0360..85b986b6 100644 --- a/lab/01-rewards-db/build.gradle +++ b/lab/01-rewards-db/build.gradle @@ -3,5 +3,4 @@ apply plugin: 'java-library' dependencies { api project(':00-rewards-common') api "org.springframework:spring-orm" - api "org.hibernate:hibernate-entitymanager" } diff --git a/lab/01-rewards-db/src/main/java/accounts/AccountManager.java b/lab/01-rewards-db/src/main/java/accounts/AccountManager.java index e278d0b5..b3cdcf8d 100644 --- a/lab/01-rewards-db/src/main/java/accounts/AccountManager.java +++ b/lab/01-rewards-db/src/main/java/accounts/AccountManager.java @@ -19,14 +19,14 @@ public interface AccountManager { * * @return Implementation information. */ - public String getInfo(); + String getInfo(); /** * Get all accounts in the system * * @return all accounts */ - public List getAllAccounts(); + List getAllAccounts(); /** * Find an account by its number. @@ -35,7 +35,7 @@ public interface AccountManager { * the account id * @return the account */ - public Account getAccount(Long id); + Account getAccount(Long id); /** * Takes a transient account and persists it. @@ -45,7 +45,7 @@ public interface AccountManager { * @return The persistent account - this may or may not be the same object * as the method argument. */ - public Account save(Account account); + Account save(Account account); /** * Takes a changed account and persists any changes made to it. @@ -53,7 +53,7 @@ public interface AccountManager { * @param account * The account with changes */ - public void update(Account account); + void update(Account account); /** * Updates the allocation percentages for the entire collection of @@ -65,7 +65,7 @@ public interface AccountManager { * A map of beneficiary names and allocation percentages, keyed * by beneficiary name */ - public void updateBeneficiaryAllocationPercentages(Long accountId, + void updateBeneficiaryAllocationPercentages(Long accountId, Map allocationPercentages); /** @@ -77,7 +77,7 @@ public void updateBeneficiaryAllocationPercentages(Long accountId, * @param beneficiaryName * the name of the beneficiary to remove */ - public void addBeneficiary(Long accountId, String beneficiaryName); + void addBeneficiary(Long accountId, String beneficiaryName); /** * Removes a beneficiary from an account. @@ -89,6 +89,6 @@ public void updateBeneficiaryAllocationPercentages(Long accountId, * @param allocationPercentages * new allocation percentages, keyed by beneficiary name */ - public void removeBeneficiary(Long accountId, String beneficiaryName, + void removeBeneficiary(Long accountId, String beneficiaryName, Map allocationPercentages); } diff --git a/lab/01-rewards-db/src/main/java/accounts/internal/AbstractAccountManager.java b/lab/01-rewards-db/src/main/java/accounts/internal/AbstractAccountManager.java index abc14962..30abc7c2 100644 --- a/lab/01-rewards-db/src/main/java/accounts/internal/AbstractAccountManager.java +++ b/lab/01-rewards-db/src/main/java/accounts/internal/AbstractAccountManager.java @@ -9,9 +9,9 @@ public abstract class AbstractAccountManager implements AccountManager { protected final Logger logger; - public AbstractAccountManager() { + protected AbstractAccountManager() { logger = LoggerFactory.getLogger(getClass()); - logger.info("Created " + getInfo() + " account-manager"); + logger.info("Created {} account-manager", getInfo()); } @Override diff --git a/lab/01-rewards-db/src/main/java/accounts/internal/JpaAccountManager.java b/lab/01-rewards-db/src/main/java/accounts/internal/JpaAccountManager.java index 3f466b27..4c34ace2 100644 --- a/lab/01-rewards-db/src/main/java/accounts/internal/JpaAccountManager.java +++ b/lab/01-rewards-db/src/main/java/accounts/internal/JpaAccountManager.java @@ -5,8 +5,8 @@ import java.util.Map; import java.util.Map.Entry; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -45,7 +45,7 @@ public List getAllAccounts() { // Use of "JOIN FETCH" produces duplicate accounts, and DISTINCT does // not address this. So we have to filter it manually. - List result = new ArrayList(); + List result = new ArrayList<>(); for (Account a : l) { if (!result.contains(a)) @@ -58,7 +58,7 @@ public List getAllAccounts() { @Override @Transactional(readOnly = true) public Account getAccount(Long id) { - Account account = (Account) entityManager.find(Account.class, id); + Account account = entityManager.find(Account.class, id); if (account != null) { // Force beneficiaries to load too - avoid Hibernate lazy loading error diff --git a/lab/01-rewards-db/src/main/java/accounts/internal/StubAccountManager.java b/lab/01-rewards-db/src/main/java/accounts/internal/StubAccountManager.java index 557c8ab5..7f49f86e 100644 --- a/lab/01-rewards-db/src/main/java/accounts/internal/StubAccountManager.java +++ b/lab/01-rewards-db/src/main/java/accounts/internal/StubAccountManager.java @@ -1,5 +1,9 @@ package accounts.internal; +import common.money.Percentage; +import rewards.internal.account.Account; +import rewards.internal.account.Beneficiary; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -7,13 +11,6 @@ import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicLong; -import org.springframework.orm.ObjectRetrievalFailureException; - -import rewards.internal.account.Account; -import rewards.internal.account.Beneficiary; - -import common.money.Percentage; - /** * IMPORTANT: Per best practices, this class shouldn't be in 'src/main/java' * but rather in 'src/test/java'. However, it is used by numerous Test classes @@ -37,9 +34,9 @@ public class StubAccountManager extends AbstractAccountManager { public static final String TEST_BEN1_NAME = "Corgan"; public static final String BENEFICIARY_SHARE = "50%"; - private Map accountsById = new HashMap(); + private final Map accountsById = new HashMap<>(); - private AtomicLong nextEntityId = new AtomicLong(3); + private final AtomicLong nextEntityId = new AtomicLong(3); public StubAccountManager() { // One test account @@ -60,7 +57,7 @@ public StubAccountManager() { @Override public List getAllAccounts() { - return new ArrayList(accountsById.values()); + return new ArrayList<>(accountsById.values()); } @Override diff --git a/lab/01-rewards-db/src/main/java/config/DbConfig.java b/lab/01-rewards-db/src/main/java/config/DbConfig.java index a8b60275..ae8f7294 100644 --- a/lab/01-rewards-db/src/main/java/config/DbConfig.java +++ b/lab/01-rewards-db/src/main/java/config/DbConfig.java @@ -52,7 +52,7 @@ public DataSource dataSource() { * Transaction Manager For JPA */ @Bean - public PlatformTransactionManager transactionManager() throws Exception { + public PlatformTransactionManager transactionManager() { return new JpaTransactionManager(); } @@ -64,21 +64,21 @@ public LocalContainerEntityManagerFactoryBean entityManagerFactory(JpaVendorAdap // Tell the underlying implementation what type of database we are using - a // hint to generate better SQL - if (adapter instanceof AbstractJpaVendorAdapter) { - ((AbstractJpaVendorAdapter) adapter).setDatabase(Database.HSQL); + if (adapter instanceof AbstractJpaVendorAdapter jpaVendorAdapter) { + jpaVendorAdapter.setDatabase(Database.HSQL); } // Setup configuration properties Properties props = new Properties(); - boolean showSql = "TRUE".equalsIgnoreCase(this.showSql); + boolean shouldShowSql = "TRUE".equalsIgnoreCase(this.showSql); Logger.getLogger("config").info("JPA Show generated SQL? " + this.showSql); if (adapter instanceof EclipseLinkJpaVendorAdapter) { - props.setProperty("eclipselink.logging.level", showSql ? "FINE" : "WARN"); - props.setProperty("eclipselink.logging.parameters", String.valueOf(showSql)); + props.setProperty("eclipselink.logging.level", shouldShowSql ? "FINE" : "WARN"); + props.setProperty("eclipselink.logging.parameters", String.valueOf(shouldShowSql)); props.setProperty("eclipselink.weaving", "false"); } else { - props.setProperty("hibernate.show_sql", String.valueOf(showSql)); + props.setProperty("hibernate.show_sql", String.valueOf(shouldShowSql)); props.setProperty("hibernate.format_sql", "true"); } diff --git a/lab/01-rewards-db/src/main/java/rewards/AccountContribution.java b/lab/01-rewards-db/src/main/java/rewards/AccountContribution.java index 94774dd2..04b5bb8e 100644 --- a/lab/01-rewards-db/src/main/java/rewards/AccountContribution.java +++ b/lab/01-rewards-db/src/main/java/rewards/AccountContribution.java @@ -8,56 +8,14 @@ /** * A summary of a monetary contribution made to an account that was distributed among the account's beneficiaries. - * * A value object. Immutable. */ -@SuppressWarnings("serial") -public class AccountContribution implements Serializable { - - private String accountNumber; - - private MonetaryAmount amount; - - private Set distributions; - - /** - * Creates a new account contribution. - * @param accountNumber the number of the account the contribution was made - * @param amount the total contribution amount - * @param distributions how the contribution was distributed among the account's beneficiaries - */ - public AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { - this.accountNumber = accountNumber; - this.amount = amount; - this.distributions = distributions; - } - - /** - * Returns the number of the account this contribution was made to. - * @return the account number - */ - public String getAccountNumber() { - return accountNumber; - } - - /** - * Returns the total amount of the contribution. - * @return the contribution amount - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns how this contribution was distributed among the account's beneficiaries. - * @return the contribution distributions - */ - public Set getDistributions() { - return distributions; - } +public record AccountContribution(String accountNumber, MonetaryAmount amount, + Set distributions) implements Serializable { /** * Returns how this contribution was distributed to a single account beneficiary. + * * @param beneficiary the name of the beneficiary e.g "Annabelle" * @return a summary of how the contribution amount was distributed to the beneficiary */ @@ -73,66 +31,16 @@ public Distribution getDistribution(String beneficiary) { /** * A single distribution made to a beneficiary as part of an account contribution, summarizing the distribution * amount and resulting total beneficiary savings. - * * A value object. */ - public static class Distribution implements Serializable { - - private String beneficiary; - - private MonetaryAmount amount; - - private Percentage percentage; + public record Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, + MonetaryAmount totalSavings) implements Serializable { - private MonetaryAmount totalSavings; - - /** - * Creates a new distribution. - * @param beneficiary the name of the account beneficiary that received a distribution - * @param amount the distribution amount - * @param percentage this distribution's percentage of the total account contribution - * @param totalSavings the beneficiary's total savings amount after the distribution was made - */ - public Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, - MonetaryAmount totalSavings) { - this.beneficiary = beneficiary; - this.percentage = percentage; - this.amount = amount; - this.totalSavings = totalSavings; - } - - /** - * Returns the name of the beneficiary. - */ - public String getBeneficiary() { - return beneficiary; - } - /** - * Returns the amount of this distribution. - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns the percentage of this distribution relative to others in the contribution. - */ - public Percentage getPercentage() { - return percentage; - } - - /** - * Returns the total savings of the beneficiary after this distribution. - */ - public MonetaryAmount getTotalSavings() { - return totalSavings; - } - - public String toString() { - return amount + " to '" + beneficiary + "' (" + percentage + ")"; + public String toString() { + return amount + " to '" + beneficiary + "' (" + percentage + ")"; + } } - } public String toString() { return "Contribution of " + amount + " to account '" + accountNumber + "' distributed " + distributions; diff --git a/lab/01-rewards-db/src/main/java/rewards/Dining.java b/lab/01-rewards-db/src/main/java/rewards/Dining.java index 017652d3..54e68a76 100644 --- a/lab/01-rewards-db/src/main/java/rewards/Dining.java +++ b/lab/01-rewards-db/src/main/java/rewards/Dining.java @@ -7,43 +7,21 @@ /** * A dining event that occurred, representing a charge made to an credit card by a merchant on a specific date. - * * For a dining to be eligible for reward, the credit card number should map to an account in the reward network. In * addition, the merchant number should map to a restaurant in the network. - * * A value object. Immutable. */ -@SuppressWarnings("serial") -public class Dining implements Serializable { +public record Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, + SimpleDate date) implements Serializable { - private MonetaryAmount amount; - - private String creditCardNumber; - - private String merchantNumber; - - private SimpleDate date; - - /** - * Creates a new dining, reflecting an amount that was charged to a card by a merchant on the date specified. - * @param amount the total amount of the dining bill - * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param date the date of the dining event - */ - public Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, SimpleDate date) { - this.amount = amount; - this.creditCardNumber = creditCardNumber; - this.merchantNumber = merchantNumber; - this.date = date; - } /** * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on today's date. A * convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param merchantNumber the merchant number of the restaurant where the dining occurred * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber) { @@ -53,65 +31,34 @@ public static Dining createDining(String amount, String creditCardNumber, String /** * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on the date specified. * A convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param month the month of the dining event - * @param day the day of the dining event - * @param year the year of the dining event + * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param month the month of the dining event + * @param day the day of the dining event + * @param year the year of the dining event * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber, int month, - int day, int year) { + int day, int year) { return new Dining(MonetaryAmount.valueOf(amount), creditCardNumber, merchantNumber, new SimpleDate(month, day, year)); } - /** - * Returns the amount of this dining--the total amount of the bill that was charged to the credit card. - */ - public MonetaryAmount getAmount() { - return amount; + public String toString() { + return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; } - /** - * Returns the number of the credit card used to pay for this dining. For this dining to be eligible for reward, - * this credit card number should be associated with a valid account in the reward network. - */ public String getCreditCardNumber() { return creditCardNumber; } - /** - * Returns the merchant number of the restaurant where this dining occurred. For this dining to be eligible for - * reward, this merchant number should be associated with a valid restaurant in the reward network. - */ public String getMerchantNumber() { return merchantNumber; } - /** - * Returns the date this dining occurred on. - */ - public SimpleDate getDate() { - return date; - } - - public boolean equals(Object o) { - if (!(o instanceof Dining)) { - return false; - } - Dining other = (Dining) o; - // value objects are equal if their attributes are equal - return amount.equals(other.amount) && creditCardNumber.equals(other.creditCardNumber) - && merchantNumber.equals(other.merchantNumber) && date.equals(other.date); - } - - public int hashCode() { - return amount.hashCode() + creditCardNumber.hashCode() + merchantNumber.hashCode() + date.hashCode(); - } - - public String toString() { - return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; + public MonetaryAmount getAmount() { + return amount; } } \ No newline at end of file diff --git a/lab/01-rewards-db/src/main/java/rewards/RewardConfirmation.java b/lab/01-rewards-db/src/main/java/rewards/RewardConfirmation.java index d04184df..43ec595a 100644 --- a/lab/01-rewards-db/src/main/java/rewards/RewardConfirmation.java +++ b/lab/01-rewards-db/src/main/java/rewards/RewardConfirmation.java @@ -6,37 +6,8 @@ * A summary of a confirmed reward transaction describing a contribution made to an account that was distributed among * the account's beneficiaries. */ -@SuppressWarnings("serial") -public class RewardConfirmation implements Serializable { - - private String confirmationNumber; - - private AccountContribution accountContribution; - - /** - * Creates a new reward confirmation. - * @param confirmationNumber the unique confirmation number - * @param accountContribution a summary of the account contribution that was made - */ - public RewardConfirmation(String confirmationNumber, AccountContribution accountContribution) { - this.confirmationNumber = confirmationNumber; - this.accountContribution = accountContribution; - } - - /** - * Returns the confirmation number of the reward transaction. Can be used later to lookup the transaction record. - */ - public String getConfirmationNumber() { - return confirmationNumber; - } - - /** - * Returns a summary of the monetary contribution that was made to an account. - * @return the account contribution (the details of this reward) - */ - public AccountContribution getAccountContribution() { - return accountContribution; - } +public record RewardConfirmation(String confirmationNumber, + AccountContribution accountContribution) implements Serializable { public String toString() { return confirmationNumber; diff --git a/lab/01-rewards-db/src/main/java/rewards/internal/account/Account.java b/lab/01-rewards-db/src/main/java/rewards/internal/account/Account.java index 852ad742..04ecc3e4 100644 --- a/lab/01-rewards-db/src/main/java/rewards/internal/account/Account.java +++ b/lab/01-rewards-db/src/main/java/rewards/internal/account/Account.java @@ -1,26 +1,24 @@ package rewards.internal.account; +import common.money.MonetaryAmount; +import common.money.Percentage; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import rewards.AccountContribution; +import rewards.AccountContribution.Distribution; + import java.util.Collections; import java.util.HashSet; import java.util.Objects; import java.util.Set; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.OneToMany; -import javax.persistence.Table; - -import rewards.AccountContribution; -import rewards.AccountContribution.Distribution; - -import common.money.MonetaryAmount; -import common.money.Percentage; - /** * An account for a member of the reward network. An account has one or more * beneficiaries whose allocations must add up to 100%. @@ -47,7 +45,7 @@ public class Account { @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "ACCOUNT_ID") - private Set beneficiaries = new HashSet(); + private Set beneficiaries = new HashSet<>(); protected Account() { } @@ -204,11 +202,7 @@ public boolean isValid() { return false; } } - if (totalPercentage.equals(Percentage.oneHundred())) { - return true; - } else { - return false; - } + return totalPercentage.equals(Percentage.oneHundred()); } public void setValid(boolean valid) { @@ -222,8 +216,6 @@ public void setValid(boolean valid) { * * @param amount * the total amount to contribute - * @param contribution - * the contribution summary */ public AccountContribution makeContribution(MonetaryAmount amount) { if (!isValid()) { @@ -242,8 +234,7 @@ public AccountContribution makeContribution(MonetaryAmount amount) { * @return the individual beneficiary distributions */ private Set distribute(MonetaryAmount amount) { - Set distributions = new HashSet( - beneficiaries.size()); + Set distributions = HashSet.newHashSet(beneficiaries.size()); for (Beneficiary beneficiary : beneficiaries) { MonetaryAmount distributionAmount = amount.multiplyBy(beneficiary .getAllocationPercentage()); diff --git a/lab/01-rewards-db/src/main/java/rewards/internal/account/Beneficiary.java b/lab/01-rewards-db/src/main/java/rewards/internal/account/Beneficiary.java index 2a16a0b4..9b761be0 100644 --- a/lab/01-rewards-db/src/main/java/rewards/internal/account/Beneficiary.java +++ b/lab/01-rewards-db/src/main/java/rewards/internal/account/Beneficiary.java @@ -1,13 +1,13 @@ package rewards.internal.account; -import javax.persistence.AttributeOverride; -import javax.persistence.Column; -import javax.persistence.Embedded; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import common.money.MonetaryAmount; import common.money.Percentage; diff --git a/lab/01-rewards-db/src/main/java/rewards/internal/account/JpaAccountRepository.java b/lab/01-rewards-db/src/main/java/rewards/internal/account/JpaAccountRepository.java index 70a68cc7..dcc49143 100644 --- a/lab/01-rewards-db/src/main/java/rewards/internal/account/JpaAccountRepository.java +++ b/lab/01-rewards-db/src/main/java/rewards/internal/account/JpaAccountRepository.java @@ -1,7 +1,7 @@ package rewards.internal.account; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +40,7 @@ public Account findByCreditCard(String creditCardNumber) { .createNativeQuery(ACCOUNT_BY_CC_QUERY) .setParameter("ccn", creditCardNumber).getSingleResult(); - Account account = (Account) entityManager.find(Account.class, accountId.longValue()); + Account account = entityManager.find(Account.class, accountId.longValue()); // Force beneficiaries to load too - avoid Hibernate lazy loading error account.getBeneficiaries().size(); diff --git a/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/BenefitAvailabilityPolicy.java b/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/BenefitAvailabilityPolicy.java index c2dc79db..ad838529 100644 --- a/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/BenefitAvailabilityPolicy.java +++ b/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/BenefitAvailabilityPolicy.java @@ -5,7 +5,6 @@ /** * Determines if benefit is available for an account for dining. - * * A value object. A strategy. Scoped by the Resturant aggregate. */ public interface BenefitAvailabilityPolicy { @@ -16,5 +15,5 @@ public interface BenefitAvailabilityPolicy { * @param dining the dining event * @return benefit availability status */ - public boolean isBenefitAvailableFor(Account account, Dining dining); + boolean isBenefitAvailableFor(Account account, Dining dining); } diff --git a/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/JpaRestaurantRepository.java b/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/JpaRestaurantRepository.java index f5c5b67e..835c58cd 100644 --- a/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/JpaRestaurantRepository.java +++ b/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/JpaRestaurantRepository.java @@ -3,10 +3,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; /** * Loads restaurants from a data source using JPA. diff --git a/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/Restaurant.java b/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/Restaurant.java index cfb239aa..0f15b800 100644 --- a/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/Restaurant.java +++ b/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/Restaurant.java @@ -1,14 +1,14 @@ package rewards.internal.restaurant; -import javax.persistence.Access; -import javax.persistence.AccessType; -import javax.persistence.AttributeOverride; -import javax.persistence.Column; -import javax.persistence.Embedded; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import rewards.Dining; import rewards.internal.account.Account; @@ -18,7 +18,6 @@ /** * A restaurant establishment in the network. Like AppleBee's. - * * Restaurants calculate how much benefit may be awarded to an account for * dining based on an availability policy and a benefit percentage. */ @@ -156,7 +155,7 @@ public BenefitAvailabilityPolicy getBenefitAvailabilityPolicy() { */ public MonetaryAmount calculateBenefitFor(Account account, Dining dining) { if (benefitAvailabilityPolicy.isBenefitAvailableFor(account, dining)) { - return dining.getAmount().multiplyBy(benefitPercentage); + return dining.amount().multiplyBy(benefitPercentage); } else { return MonetaryAmount.zero(); } diff --git a/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/RestaurantRepository.java b/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/RestaurantRepository.java index 6d35b956..429badd1 100644 --- a/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/RestaurantRepository.java +++ b/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/RestaurantRepository.java @@ -4,7 +4,6 @@ * Loads restaurant aggregates. Called by the reward network to find and * reconstitute Restaurant entities from an external form such as a set of RDMS * rows. - * * Objects returned by this repository are guaranteed to be fully-initialized * and ready to use. */ @@ -16,7 +15,7 @@ public interface RestaurantRepository { * * @return Implementation information. */ - public String getInfo(); + String getInfo(); /** * Load a Restaurant entity by its merchant number. @@ -25,12 +24,12 @@ public interface RestaurantRepository { * the merchant number * @return the restaurant */ - public Restaurant findByMerchantNumber(String merchantNumber); + Restaurant findByMerchantNumber(String merchantNumber); /** * Find the number of restaurants in the repository. * * @return The number of restaurants - zero or more. */ - public Long getRestaurantCount(); + Long getRestaurantCount(); } diff --git a/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/StubRestaurantRepository.java b/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/StubRestaurantRepository.java index 5cfdf47f..ee6976f1 100644 --- a/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/StubRestaurantRepository.java +++ b/lab/01-rewards-db/src/main/java/rewards/internal/restaurant/StubRestaurantRepository.java @@ -13,7 +13,6 @@ /** * A dummy restaurant repository implementation. Has a single restaurant "Apple Bees" with a 8% benefit availability * percentage that's always available. - * * Stubs facilitate unit testing. An object needing a RestaurantRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. @@ -22,7 +21,7 @@ public class StubRestaurantRepository implements RestaurantRepository { public static final String TYPE = "Stub"; - private Map restaurantsByMerchantNumber = new HashMap(); + private final Map restaurantsByMerchantNumber = new HashMap<>(); public StubRestaurantRepository() { Restaurant restaurant = new Restaurant("1234567890", "Apple Bees"); @@ -38,7 +37,7 @@ public String getInfo() { @Override public Restaurant findByMerchantNumber(String merchantNumber) { - Restaurant restaurant = (Restaurant) restaurantsByMerchantNumber.get(merchantNumber); + Restaurant restaurant = restaurantsByMerchantNumber.get(merchantNumber); if (restaurant == null) { throw new ObjectRetrievalFailureException(Restaurant.class, merchantNumber); } diff --git a/lab/01-rewards-db/src/main/java/rewards/internal/reward/JdbcRewardRepository.java b/lab/01-rewards-db/src/main/java/rewards/internal/reward/JdbcRewardRepository.java index 4bd35fa5..e669e0bf 100644 --- a/lab/01-rewards-db/src/main/java/rewards/internal/reward/JdbcRewardRepository.java +++ b/lab/01-rewards-db/src/main/java/rewards/internal/reward/JdbcRewardRepository.java @@ -8,6 +8,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import common.datetime.SimpleDate; +import org.springframework.stereotype.Repository; import rewards.AccountContribution; import rewards.Dining; import rewards.RewardConfirmation; @@ -16,13 +17,14 @@ * JDBC implementation of a reward repository that records the result of a * reward transaction by inserting a reward confirmation record. */ +@Repository public class JdbcRewardRepository implements RewardRepository { public static final String TYPE = "jdbc"; private static final Logger logger = LoggerFactory.getLogger("config"); - private JdbcTemplate jdbcTemplate; + private final JdbcTemplate jdbcTemplate; @Autowired public JdbcRewardRepository(DataSource dataSource) { @@ -39,9 +41,9 @@ public String getInfo() { public RewardConfirmation confirmReward(AccountContribution contribution, Dining dining) { String sql = "insert into T_REWARD (CONFIRMATION_NUMBER, REWARD_AMOUNT, REWARD_DATE, ACCOUNT_NUMBER, DINING_MERCHANT_NUMBER, DINING_DATE, DINING_AMOUNT) values (?, ?, ?, ?, ?, ?, ?)"; String confirmationNumber = nextConfirmationNumber(); - jdbcTemplate.update(sql, confirmationNumber, contribution.getAmount().asBigDecimal(), - SimpleDate.today().asDate(), contribution.getAccountNumber(), dining.getMerchantNumber(), - dining.getDate().asDate(), dining.getAmount().asBigDecimal()); + jdbcTemplate.update(sql, confirmationNumber, contribution.amount().asBigDecimal(), + SimpleDate.today().asDate(), contribution.accountNumber(), dining.merchantNumber(), + dining.date().asDate(), dining.amount().asBigDecimal()); return new RewardConfirmation(confirmationNumber, contribution); } diff --git a/lab/01-rewards-db/src/main/java/rewards/internal/reward/RewardRepository.java b/lab/01-rewards-db/src/main/java/rewards/internal/reward/RewardRepository.java index f7df5d70..1dd257c3 100644 --- a/lab/01-rewards-db/src/main/java/rewards/internal/reward/RewardRepository.java +++ b/lab/01-rewards-db/src/main/java/rewards/internal/reward/RewardRepository.java @@ -15,7 +15,7 @@ public interface RewardRepository { * * @return Implementation information. */ - public String getInfo(); + String getInfo(); /** * Create a record of a reward that will track a contribution made to an account for dining. @@ -24,5 +24,5 @@ public interface RewardRepository { * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later * date */ - public RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); + RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); } \ No newline at end of file diff --git a/lab/01-rewards-db/src/main/java/rewards/internal/reward/StubRewardRepository.java b/lab/01-rewards-db/src/main/java/rewards/internal/reward/StubRewardRepository.java index 793c05e0..f4986876 100644 --- a/lab/01-rewards-db/src/main/java/rewards/internal/reward/StubRewardRepository.java +++ b/lab/01-rewards-db/src/main/java/rewards/internal/reward/StubRewardRepository.java @@ -1,19 +1,15 @@ package rewards.internal.reward; -import java.util.Random; - import rewards.AccountContribution; import rewards.Dining; import rewards.RewardConfirmation; /** * A dummy reward repository implementation. - * * IMPORTANT!!! Per best practices, this class shouldn't be in 'src/main/java' * but rather in 'src/test/java'. However, it is used by numerous Test classes * inside multiple projects. Maven does not provide an easy way to access a * class that is inside another project's 'src/test/java' folder. - * * Rather than using some complex Maven configuration, we decided it is * acceptable to place this test class inside 'src/main/java'. */ diff --git a/lab/01-rewards-db/src/test/java/accounts/internal/AbstractAccountManagerTests.java b/lab/01-rewards-db/src/test/java/accounts/internal/AbstractAccountManagerTests.java index 0c1aa7c0..f27cf0b2 100644 --- a/lab/01-rewards-db/src/test/java/accounts/internal/AbstractAccountManagerTests.java +++ b/lab/01-rewards-db/src/test/java/accounts/internal/AbstractAccountManagerTests.java @@ -74,7 +74,7 @@ public void getAccount() { // assert the returned account contains what you expect given the state // of the database assertNotNull(account, "account should never be null"); - assertEquals(account.getEntityId().longValue(), 0L, "wrong entity id"); + assertEquals(0L, account.getEntityId().longValue(), "wrong entity id"); assertEquals("123456789", account.getNumber(), "wrong account number"); assertEquals("Keith and Keri Donald", account.getName(), "wrong name"); assertEquals(2, account.getBeneficiaries().size(), "wrong beneficiary collection size"); @@ -128,7 +128,7 @@ public void updateAccount() { @Test @Transactional public void updateAccountBeneficiaries() { - Map allocationPercentages = new HashMap(); + Map allocationPercentages = new HashMap<>(); allocationPercentages.put("Annabelle", Percentage.valueOf("25%")); allocationPercentages.put("Corgan", Percentage.valueOf("75%")); accountManager.updateBeneficiaryAllocationPercentages(0L, allocationPercentages); @@ -150,7 +150,7 @@ public void addBeneficiary() { @Test @Transactional public void removeBeneficiary() { - Map allocationPercentages = new HashMap(); + Map allocationPercentages = new HashMap<>(); allocationPercentages.put("Corgan", Percentage.oneHundred()); accountManager.removeBeneficiary(0L, "Annabelle", allocationPercentages); Account account = accountManager.getAccount(0L); diff --git a/lab/01-rewards-db/src/test/java/accounts/internal/AbstractDatabaseAccountManagerTests.java b/lab/01-rewards-db/src/test/java/accounts/internal/AbstractDatabaseAccountManagerTests.java index 0d88a27a..ba590f98 100644 --- a/lab/01-rewards-db/src/test/java/accounts/internal/AbstractDatabaseAccountManagerTests.java +++ b/lab/01-rewards-db/src/test/java/accounts/internal/AbstractDatabaseAccountManagerTests.java @@ -33,7 +33,7 @@ public abstract class AbstractDatabaseAccountManagerTests extends AbstractAccoun @Override protected void showStatus() { - logger.info("TRANSACTION IS : " + transactionUtils.getCurrentTransaction()); + logger.info("TRANSACTION IS : {}", transactionUtils.getCurrentTransaction()); } @BeforeEach diff --git a/lab/01-rewards-db/src/test/java/accounts/internal/JpaAccountManagerIntegrationTests.java b/lab/01-rewards-db/src/test/java/accounts/internal/JpaAccountManagerIntegrationTests.java index 0474763f..c3229e54 100644 --- a/lab/01-rewards-db/src/test/java/accounts/internal/JpaAccountManagerIntegrationTests.java +++ b/lab/01-rewards-db/src/test/java/accounts/internal/JpaAccountManagerIntegrationTests.java @@ -8,7 +8,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Spring-driven integration test for the JPA-based account manager @@ -23,7 +23,7 @@ public class JpaAccountManagerIntegrationTests extends AbstractDatabaseAccountMa @Test @Override public void testProfile() { - assertTrue(accountManager.getInfo().equals("JPA"), "JPA expected but found " + accountManager.getInfo()); + assertEquals("JPA", accountManager.getInfo(), "JPA expected but found " + accountManager.getInfo()); } } diff --git a/lab/01-rewards-db/src/test/java/accounts/internal/JpaAccountManagerManualIntegrationTests.java b/lab/01-rewards-db/src/test/java/accounts/internal/JpaAccountManagerManualIntegrationTests.java index bfa0fabf..69871fc3 100644 --- a/lab/01-rewards-db/src/test/java/accounts/internal/JpaAccountManagerManualIntegrationTests.java +++ b/lab/01-rewards-db/src/test/java/accounts/internal/JpaAccountManagerManualIntegrationTests.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; import utils.DataManagementSetup; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; /** * Manually configured integration test (not using Spring) for the JPA-based @@ -23,7 +23,7 @@ public JpaAccountManagerManualIntegrationTests() { @Test @Override public void testProfile() { - assertTrue(accountManager instanceof JpaAccountManager, "JPA expected"); + assertInstanceOf(JpaAccountManager.class, accountManager, "JPA expected"); logger.info("JPA with Hibernate"); } @@ -35,7 +35,7 @@ public void setUp() throws Exception { } @AfterEach - public void tearDown() throws Exception { + public void tearDown() { transactionUtils.rollbackTransaction(); } diff --git a/lab/01-rewards-db/src/test/java/rewards/internal/account/AccountTests.java b/lab/01-rewards-db/src/test/java/rewards/internal/account/AccountTests.java index 0bd9b637..49648630 100644 --- a/lab/01-rewards-db/src/test/java/rewards/internal/account/AccountTests.java +++ b/lab/01-rewards-db/src/test/java/rewards/internal/account/AccountTests.java @@ -10,12 +10,12 @@ /** * Unit tests for the Account class that verify Account behavior works in isolation. */ -public class AccountTests { +class AccountTests { - private Account account = new Account("1", "Keith and Keri Donald"); + Account account = new Account("1", "Keith and Keri Donald"); @Test - public void accountIsValid() { + void accountIsValid() { // setup account with a valid set of beneficiaries to prepare for testing account.addBeneficiary("Annabelle", Percentage.valueOf("50%")); account.addBeneficiary("Corgan", Percentage.valueOf("50%")); @@ -23,31 +23,31 @@ public void accountIsValid() { } @Test - public void accountIsInvalidWithNoBeneficiaries() { + void accountIsInvalidWithNoBeneficiaries() { assertFalse(account.isValid()); } @Test - public void accountIsInvalidWhenBeneficiaryAllocationsAreOver100() { + void accountIsInvalidWhenBeneficiaryAllocationsAreOver100() { account.addBeneficiary("Annabelle", Percentage.valueOf("50%")); account.addBeneficiary("Corgan", Percentage.valueOf("100%")); assertFalse(account.isValid()); } @Test - public void accountIsInvalidWhenBeneficiaryAllocationsAreUnder100() { + void accountIsInvalidWhenBeneficiaryAllocationsAreUnder100() { account.addBeneficiary("Annabelle", Percentage.valueOf("50%")); account.addBeneficiary("Corgan", Percentage.valueOf("25%")); assertFalse(account.isValid()); } @Test - public void makeContribution() { + void makeContribution() { account.addBeneficiary("Annabelle", Percentage.valueOf("50%")); account.addBeneficiary("Corgan", Percentage.valueOf("50%")); AccountContribution contribution = account.makeContribution(MonetaryAmount.valueOf("100.00")); - assertEquals(contribution.getAmount(), MonetaryAmount.valueOf("100.00")); - assertEquals(MonetaryAmount.valueOf("50.00"), contribution.getDistribution("Annabelle").getAmount()); - assertEquals(MonetaryAmount.valueOf("50.00"), contribution.getDistribution("Corgan").getAmount()); + assertEquals(contribution.amount(), MonetaryAmount.valueOf("100.00")); + assertEquals(MonetaryAmount.valueOf("50.00"), contribution.getDistribution("Annabelle").amount()); + assertEquals(MonetaryAmount.valueOf("50.00"), contribution.getDistribution("Corgan").amount()); } } \ No newline at end of file diff --git a/lab/01-rewards-db/src/test/java/rewards/internal/account/JpaAccountRepositoryIntegrationTests.java b/lab/01-rewards-db/src/test/java/rewards/internal/account/JpaAccountRepositoryIntegrationTests.java index d183140a..f2b40935 100644 --- a/lab/01-rewards-db/src/test/java/rewards/internal/account/JpaAccountRepositoryIntegrationTests.java +++ b/lab/01-rewards-db/src/test/java/rewards/internal/account/JpaAccountRepositoryIntegrationTests.java @@ -24,7 +24,7 @@ public class JpaAccountRepositoryIntegrationTests extends AbstractAccountReposit @Test @Override public void testProfile() { - assertTrue(accountRepository.getInfo().equals(JpaAccountRepository.INFO), "JPA expected but found " + accountRepository.getInfo()); + assertEquals(JpaAccountRepository.INFO, accountRepository.getInfo(), "JPA expected but found " + accountRepository.getInfo()); } } diff --git a/lab/01-rewards-db/src/test/java/rewards/internal/account/JpaAccountRepositoryTests.java b/lab/01-rewards-db/src/test/java/rewards/internal/account/JpaAccountRepositoryTests.java index 4dfc3240..e5dc227a 100644 --- a/lab/01-rewards-db/src/test/java/rewards/internal/account/JpaAccountRepositoryTests.java +++ b/lab/01-rewards-db/src/test/java/rewards/internal/account/JpaAccountRepositoryTests.java @@ -8,7 +8,7 @@ import org.springframework.transaction.support.DefaultTransactionDefinition; import utils.DataManagementSetup; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; /** * Manually configured integration test for the JPA based account repository @@ -22,7 +22,7 @@ public class JpaAccountRepositoryTests extends AbstractAccountRepositoryTests { private TransactionStatus transactionStatus; @BeforeEach - public void setUp() throws Exception { + public void setUp() { DataManagementSetup dataManagementSetup = new DataManagementSetup(); JpaAccountRepository accountRepository = new JpaAccountRepository(); @@ -37,11 +37,11 @@ public void setUp() throws Exception { @Test @Override public void testProfile() { - assertTrue(accountRepository instanceof JpaAccountRepository, "JPA expected"); + assertInstanceOf(JpaAccountRepository.class, accountRepository, "JPA expected"); } @AfterEach - public void tearDown() throws Exception { + public void tearDown() { // rollback the transaction to avoid corrupting other tests if (transactionManager != null) transactionManager.rollback(transactionStatus); diff --git a/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/AbstractRestaurantRepositoryTests.java b/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/AbstractRestaurantRepositoryTests.java index 5e2b3b9f..651b7e8a 100644 --- a/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/AbstractRestaurantRepositoryTests.java +++ b/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/AbstractRestaurantRepositoryTests.java @@ -26,7 +26,7 @@ public abstract class AbstractRestaurantRepositoryTests { @Test @Transactional - public final void findRestaurantByMerchantNumber() { + public void findRestaurantByMerchantNumber() { Restaurant restaurant = restaurantRepository .findByMerchantNumber("1234567890"); assertNotNull(restaurant, "the restaurant should never be null"); diff --git a/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/JpaRestaurantRepositoryIntegrationTests.java b/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/JpaRestaurantRepositoryIntegrationTests.java index 0b3ccebe..f95a75bf 100644 --- a/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/JpaRestaurantRepositoryIntegrationTests.java +++ b/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/JpaRestaurantRepositoryIntegrationTests.java @@ -8,7 +8,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Integration test for the JPA-based restaurant repository implementation. @@ -23,8 +23,7 @@ public class JpaRestaurantRepositoryIntegrationTests extends AbstractRestaurantR @Test @Override public void testProfile() { - assertTrue(restaurantRepository.getInfo().equals(JpaRestaurantRepository.INFO) - , "JPA expected but found " + restaurantRepository.getInfo()); + assertEquals(JpaRestaurantRepository.INFO, restaurantRepository.getInfo(), "JPA expected but found " + restaurantRepository.getInfo()); } } diff --git a/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/JpaRestaurantRepositoryTests.java b/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/JpaRestaurantRepositoryTests.java index fe0a7ad5..e026f9c7 100644 --- a/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/JpaRestaurantRepositoryTests.java +++ b/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/JpaRestaurantRepositoryTests.java @@ -8,7 +8,7 @@ import org.springframework.transaction.support.DefaultTransactionDefinition; import utils.DataManagementSetup; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; /** * Manually configured integration test for the JPA based restaurant repository @@ -22,7 +22,7 @@ public class JpaRestaurantRepositoryTests extends AbstractRestaurantRepositoryTe private TransactionStatus transactionStatus; @BeforeEach - public void setUp() throws Exception { + public void setUp() { DataManagementSetup dataManagementSetup = new DataManagementSetup(); JpaRestaurantRepository restaurantRepository = new JpaRestaurantRepository(); @@ -37,11 +37,11 @@ public void setUp() throws Exception { @Test @Override public void testProfile() { - assertTrue(restaurantRepository instanceof JpaRestaurantRepository, "JPA expected"); + assertInstanceOf(JpaRestaurantRepository.class, restaurantRepository, "JPA expected"); } @AfterEach - public void tearDown() throws Exception { + public void tearDown() { // rollback the transaction to avoid corrupting other tests if (transactionManager != null) transactionManager.rollback(transactionStatus); diff --git a/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/RestaurantTests.java b/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/RestaurantTests.java index fd8ee56b..bc1290ad 100644 --- a/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/RestaurantTests.java +++ b/lab/01-rewards-db/src/test/java/rewards/internal/restaurant/RestaurantTests.java @@ -13,7 +13,7 @@ * Unit tests for exercising the behavior of the Restaurant aggregate entity. A restaurant calculates a benefit to award * to an account for dining based on an availability policy and benefit percentage. */ -public class RestaurantTests { +class RestaurantTests { private Restaurant restaurant; @@ -34,14 +34,14 @@ public void setUp() { } @Test - public void testCalcuateBenefitFor() { + void testCalcuateBenefitFor() { MonetaryAmount benefit = restaurant.calculateBenefitFor(account, dining); // assert 8.00 eligible for reward assertEquals(MonetaryAmount.valueOf("8.00"), benefit); } @Test - public void testNoBenefitAvailable() { + void testNoBenefitAvailable() { // configure stub that always returns false restaurant.setBenefitAvailabilityPolicy(new StubBenefitAvailibilityPolicy(false)); MonetaryAmount benefit = restaurant.calculateBenefitFor(account, dining); @@ -54,16 +54,10 @@ public void testNoBenefitAvailable() { * Only useful for testing--a real availability policy might consider many factors such as the day of week of the * dining, or the account's reward history for the current month. */ - private static class StubBenefitAvailibilityPolicy implements BenefitAvailabilityPolicy { - - private boolean isBenefitAvailable; - - public StubBenefitAvailibilityPolicy(boolean isBenefitAvailable) { - this.isBenefitAvailable = isBenefitAvailable; - } + private record StubBenefitAvailibilityPolicy(boolean isBenefitAvailable) implements BenefitAvailabilityPolicy { public boolean isBenefitAvailableFor(Account account, Dining dining) { - return isBenefitAvailable; + return isBenefitAvailable; + } } - } } \ No newline at end of file diff --git a/lab/01-rewards-db/src/test/java/rewards/internal/reward/AbstractRewardRepositoryTests.java b/lab/01-rewards-db/src/test/java/rewards/internal/reward/AbstractRewardRepositoryTests.java index a9229e4d..5a7b79e0 100644 --- a/lab/01-rewards-db/src/test/java/rewards/internal/reward/AbstractRewardRepositoryTests.java +++ b/lab/01-rewards-db/src/test/java/rewards/internal/reward/AbstractRewardRepositoryTests.java @@ -26,69 +26,70 @@ */ public abstract class AbstractRewardRepositoryTests { - @Autowired - protected JdbcRewardRepository rewardRepository; + @Autowired + protected JdbcRewardRepository rewardRepository; - @Autowired - protected DataSource dataSource; + @Autowired + protected DataSource dataSource; - @Test - public abstract void testProfile(); + @Test + public abstract void testProfile(); - @Test - @Transactional - public void createReward() throws SQLException { - Dining dining = Dining.createDining("100.00", "1234123412341234", - "0123456789"); + @Test + @Transactional + public void createReward() throws SQLException { + Dining dining = Dining.createDining("100.00", "1234123412341234", + "0123456789"); - Account account = new Account("1", "Keith and Keri Donald"); - account.setEntityId(0L); - account.addBeneficiary("Annabelle", Percentage.valueOf("50%")); - account.addBeneficiary("Corgan", Percentage.valueOf("50%")); + Account account = new Account("1", "Keith and Keri Donald"); + account.setEntityId(0L); + account.addBeneficiary("Annabelle", Percentage.valueOf("50%")); + account.addBeneficiary("Corgan", Percentage.valueOf("50%")); - AccountContribution contribution = account - .makeContribution(MonetaryAmount.valueOf("8.00")); - RewardConfirmation confirmation = rewardRepository.confirmReward( - contribution, dining); - assertNotNull(confirmation, "confirmation should not be null"); - assertNotNull("confirmation number should not be null", - confirmation.getConfirmationNumber()); - assertEquals(contribution, - confirmation.getAccountContribution(), "wrong contribution object"); - verifyRewardInserted(confirmation, dining); - } + AccountContribution contribution = account + .makeContribution(MonetaryAmount.valueOf("8.00")); + RewardConfirmation confirmation = rewardRepository.confirmReward( + contribution, dining); + assertNotNull(confirmation, "confirmation should not be null"); + assertNotNull(confirmation.confirmationNumber(), + "confirmation number should not be null"); + assertEquals(contribution, + confirmation.accountContribution(), "wrong contribution object"); + verifyRewardInserted(confirmation); + } - private void verifyRewardInserted(RewardConfirmation confirmation, - Dining dining) throws SQLException { - assertEquals(1, getRewardCount()); - Statement stmt = getCurrentConnection().createStatement(); - ResultSet rs = stmt - .executeQuery("select REWARD_AMOUNT from T_REWARD where CONFIRMATION_NUMBER = '" - + confirmation.getConfirmationNumber() + "'"); - rs.next(); - assertEquals(confirmation.getAccountContribution().getAmount(), - MonetaryAmount.valueOf(rs.getString(1))); - } + private void verifyRewardInserted(RewardConfirmation confirmation) throws SQLException { + assertEquals(1, getRewardCount()); + try (Statement stmt = getCurrentConnection().createStatement()) { + ResultSet rs = stmt + .executeQuery("select REWARD_AMOUNT from T_REWARD where CONFIRMATION_NUMBER = '" + + confirmation.confirmationNumber() + "'"); + rs.next(); + assertEquals(confirmation.accountContribution().amount(), + MonetaryAmount.valueOf(rs.getString(1))); + } + } - private int getRewardCount() throws SQLException { - Statement stmt = getCurrentConnection().createStatement(); - ResultSet rs = stmt.executeQuery("select count(*) from T_REWARD"); - rs.next(); - return rs.getInt(1); - } + private int getRewardCount() throws SQLException { + try (Statement stmt = getCurrentConnection().createStatement()) { + ResultSet rs = stmt.executeQuery("select count(*) from T_REWARD"); + rs.next(); + return rs.getInt(1); + } + } - /** - * Gets the connection behind the current transaction - this allows the - * tests to use the transaction created for the @Transactional test and see - * the changes made. - *

- * Using a different (new) connection would fail because the T_REWARD table - * is locked by any updates and the queries in verifyRewardInserted - * and getRewardCount can never return. - * - * @return The current connection - */ - private Connection getCurrentConnection() { - return DataSourceUtils.getConnection(dataSource); - } + /** + * Gets the connection behind the current transaction - this allows the + * tests to use the transaction created for the @Transactional test and see + * the changes made. + *

+ * Using a different (new) connection would fail because the T_REWARD table + * is locked by any updates and the queries in verifyRewardInserted + * and getRewardCount can never return. + * + * @return The current connection + */ + private Connection getCurrentConnection() { + return DataSourceUtils.getConnection(dataSource); + } } diff --git a/lab/01-rewards-db/src/test/java/rewards/internal/reward/JdbcRewardRepositoryIntegrationTests.java b/lab/01-rewards-db/src/test/java/rewards/internal/reward/JdbcRewardRepositoryIntegrationTests.java index f164c0a2..c9df4b3d 100644 --- a/lab/01-rewards-db/src/test/java/rewards/internal/reward/JdbcRewardRepositoryIntegrationTests.java +++ b/lab/01-rewards-db/src/test/java/rewards/internal/reward/JdbcRewardRepositoryIntegrationTests.java @@ -26,9 +26,7 @@ public class JdbcRewardRepositoryIntegrationTests extends @Test @Override public void testProfile() { - assertTrue( - rewardRepository.getInfo().equals(JdbcRewardRepository.TYPE), - "JDBC expected but found " + rewardRepository.getInfo()); + assertEquals(JdbcRewardRepository.TYPE, rewardRepository.getInfo(), "JDBC expected but found " + rewardRepository.getInfo()); } } diff --git a/lab/01-rewards-db/src/test/java/rewards/internal/reward/JdbcRewardRepositoryTests.java b/lab/01-rewards-db/src/test/java/rewards/internal/reward/JdbcRewardRepositoryTests.java index ef430a1e..e5f59558 100644 --- a/lab/01-rewards-db/src/test/java/rewards/internal/reward/JdbcRewardRepositoryTests.java +++ b/lab/01-rewards-db/src/test/java/rewards/internal/reward/JdbcRewardRepositoryTests.java @@ -6,7 +6,7 @@ import javax.sql.DataSource; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; /** * Tests the JDBC reward repository with a test data source to test repository @@ -15,7 +15,7 @@ public class JdbcRewardRepositoryTests extends AbstractRewardRepositoryTests { @BeforeEach - public void setUp() throws Exception { + public void setUp() { dataSource = createTestDataSource(); rewardRepository = new JdbcRewardRepository(dataSource); } @@ -23,8 +23,7 @@ public void setUp() throws Exception { @Test @Override public void testProfile() { - assertTrue( - rewardRepository instanceof JdbcRewardRepository, "JDBC expected"); + assertInstanceOf(JdbcRewardRepository.class, rewardRepository, "JDBC expected"); } private DataSource createTestDataSource() { diff --git a/lab/01-rewards-db/src/test/java/utils/DataManagementSetup.java b/lab/01-rewards-db/src/test/java/utils/DataManagementSetup.java index 207cb509..4f3621a5 100644 --- a/lab/01-rewards-db/src/test/java/utils/DataManagementSetup.java +++ b/lab/01-rewards-db/src/test/java/utils/DataManagementSetup.java @@ -2,8 +2,8 @@ import java.util.Properties; -import javax.persistence.EntityManager; -import javax.persistence.EntityManagerFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; @@ -95,7 +95,7 @@ protected final EntityManagerFactory createEntityManagerFactory() { factoryBean.afterPropertiesSet(); // get the created session factory - return (EntityManagerFactory) factoryBean.getObject(); + return factoryBean.getObject(); } } diff --git a/lab/01-rewards-db/src/test/java/utils/TransactionUtils.java b/lab/01-rewards-db/src/test/java/utils/TransactionUtils.java index 62890d6e..e9f23084 100644 --- a/lab/01-rewards-db/src/test/java/utils/TransactionUtils.java +++ b/lab/01-rewards-db/src/test/java/utils/TransactionUtils.java @@ -43,11 +43,9 @@ public TransactionUtils(PlatformTransactionManager transactionManager) { /** * Begin a new transaction, ensuring one is not running already - * - * @throws Exception - * A transaction is already running. - */ - public void beginTransaction() throws Exception { + * + */ + public void beginTransaction() { // Make sure no transaction is running try { transactionStatus = transactionManager @@ -61,18 +59,15 @@ public void beginTransaction() throws Exception { transactionStatus = transactionManager .getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED)); - assert (transactionStatus != null); assert (transactionStatus.isNewTransaction()); - logger.info("NEW " + transactionStatus + " - completed = " + transactionStatus.isCompleted()); + logger.info("NEW {} - completed = {}", transactionStatus, transactionStatus.isCompleted()); } /** * Rollback the current transaction - there must be one. - * - * @throws Exception - * A transaction is NOT already running. - */ - public void rollbackTransaction() throws Exception { + * + */ + public void rollbackTransaction() { // Make sure an exception is running try { transactionManager @@ -83,20 +78,20 @@ public void rollbackTransaction() throws Exception { } // Rollback the transaction to avoid corrupting other tests - logger.info("ROLLBACKK " + transactionStatus); + logger.info("ROLLBACK {}", transactionStatus); transactionManager.rollback(transactionStatus); } /** * Get the current transaction - there should be one. * - * @return + * @return transaction The current transaction. */ public TransactionStatus getCurrentTransaction() { TransactionDefinition definition = new DefaultTransactionDefinition( DefaultTransactionDefinition.PROPAGATION_MANDATORY); TransactionStatus transaction = transactionManager.getTransaction(definition); - logger.info("TRANSACTION = " + transaction); + logger.info("TRANSACTION = {}", transaction); return transaction; } @@ -109,7 +104,7 @@ public TransactionStatus getTransaction() { TransactionDefinition definition = new DefaultTransactionDefinition( DefaultTransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus transaction = transactionManager.getTransaction(definition); - logger.info("TRANSACTION = " + transaction); + logger.info("TRANSACTION = {}", transaction); return transaction; } @@ -117,13 +112,13 @@ public TransactionStatus getTransaction() { * Start a brand new transaction - forcing a new one if one exists already * (using {@link TransactionDefinition#PROPAGATION_REQUIRES_NEW}). * - * @return + * @return The new transaction. */ public TransactionStatus getNewTransaction() { TransactionDefinition definition = new DefaultTransactionDefinition( DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW); TransactionStatus transaction = transactionManager.getTransaction(definition); - logger.info("TRANSACTION = " + transaction); + logger.info("TRANSACTION = {}", transaction); return transaction; } @@ -139,11 +134,10 @@ public boolean transactionExists() { if (transaction == null) throw new IllegalStateException("No transaction in progress"); - logger.info("TRANSACTION EXISTS - new ? " + transaction.isNewTransaction()); + logger.info("TRANSACTION EXISTS - new ? {}", transaction.isNewTransaction()); return true; } catch (Exception e) { - System.out.println(e); - logger.error("NO TRANSACTION: " + e); + logger.error("NO TRANSACTION: ", e); return false; } } diff --git a/lab/10-spring-intro-solution/src/main/java/rewards/AccountContribution.java b/lab/10-spring-intro-solution/src/main/java/rewards/AccountContribution.java index 5cad1918..b7cd5254 100644 --- a/lab/10-spring-intro-solution/src/main/java/rewards/AccountContribution.java +++ b/lab/10-spring-intro-solution/src/main/java/rewards/AccountContribution.java @@ -7,55 +7,14 @@ /** * A summary of a monetary contribution made to an account that was distributed among the account's beneficiaries. - * + *

* A value object. Immutable. */ -public class AccountContribution { - - private String accountNumber; - - private MonetaryAmount amount; - - private Set distributions; - - /** - * Creates a new account contribution. - * @param accountNumber the number of the account the contribution was made - * @param amount the total contribution amount - * @param distributions how the contribution was distributed among the account's beneficiaries - */ - public AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { - this.accountNumber = accountNumber; - this.amount = amount; - this.distributions = distributions; - } - - /** - * Returns the number of the account this contribution was made to. - * @return the account number - */ - public String getAccountNumber() { - return accountNumber; - } - - /** - * Returns the total amount of the contribution. - * @return the contribution amount - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns how this contribution was distributed among the account's beneficiaries. - * @return the contribution distributions - */ - public Set getDistributions() { - return distributions; - } +public record AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { /** * Returns how this contribution was distributed to a single account beneficiary. + * * @param beneficiary the name of the beneficiary e.g "Annabelle" * @return a summary of how the contribution amount was distributed to the beneficiary */ @@ -71,61 +30,11 @@ public Distribution getDistribution(String beneficiary) { /** * A single distribution made to a beneficiary as part of an account contribution, summarizing the distribution * amount and resulting total beneficiary savings. - * + *

* A value object. */ - public static class Distribution { - - private String beneficiary; - - private MonetaryAmount amount; - - private Percentage percentage; - - private MonetaryAmount totalSavings; - - /** - * Creates a new distribution. - * @param beneficiary the name of the account beneficiary that received a distribution - * @param amount the distribution amount - * @param percentage this distribution's percentage of the total account contribution - * @param totalSavings the beneficiary's total savings amount after the distribution was made - */ - public Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, - MonetaryAmount totalSavings) { - this.beneficiary = beneficiary; - this.percentage = percentage; - this.amount = amount; - this.totalSavings = totalSavings; - } - - /** - * Returns the name of the beneficiary. - */ - public String getBeneficiary() { - return beneficiary; - } - - /** - * Returns the amount of this distribution. - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns the percentage of this distribution relative to others in the contribution. - */ - public Percentage getPercentage() { - return percentage; - } - - /** - * Returns the total savings of the beneficiary after this distribution. - */ - public MonetaryAmount getTotalSavings() { - return totalSavings; - } + public record Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, + MonetaryAmount totalSavings) { public String toString() { return amount + " to '" + beneficiary + "' (" + percentage + ")"; diff --git a/lab/10-spring-intro-solution/src/main/java/rewards/Dining.java b/lab/10-spring-intro-solution/src/main/java/rewards/Dining.java index 478463c3..54e68a76 100644 --- a/lab/10-spring-intro-solution/src/main/java/rewards/Dining.java +++ b/lab/10-spring-intro-solution/src/main/java/rewards/Dining.java @@ -1,46 +1,27 @@ package rewards; +import java.io.Serializable; + import common.datetime.SimpleDate; import common.money.MonetaryAmount; /** * A dining event that occurred, representing a charge made to an credit card by a merchant on a specific date. - * * For a dining to be eligible for reward, the credit card number should map to an account in the reward network. In * addition, the merchant number should map to a restaurant in the network. - * * A value object. Immutable. */ -public class Dining { - - private MonetaryAmount amount; - - private String creditCardNumber; - - private String merchantNumber; +public record Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, + SimpleDate date) implements Serializable { - private SimpleDate date; - - /** - * Creates a new dining, reflecting an amount that was charged to a card by a merchant on the date specified. - * @param amount the total amount of the dining bill - * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param date the date of the dining event - */ - public Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, SimpleDate date) { - this.amount = amount; - this.creditCardNumber = creditCardNumber; - this.merchantNumber = merchantNumber; - this.date = date; - } /** * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on today's date. A * convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param merchantNumber the merchant number of the restaurant where the dining occurred * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber) { @@ -50,65 +31,34 @@ public static Dining createDining(String amount, String creditCardNumber, String /** * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on the date specified. * A convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param month the month of the dining event - * @param day the day of the dining event - * @param year the year of the dining event + * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param month the month of the dining event + * @param day the day of the dining event + * @param year the year of the dining event * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber, int month, - int day, int year) { + int day, int year) { return new Dining(MonetaryAmount.valueOf(amount), creditCardNumber, merchantNumber, new SimpleDate(month, day, year)); } - /** - * Returns the amount of this dining--the total amount of the bill that was charged to the credit card. - */ - public MonetaryAmount getAmount() { - return amount; + public String toString() { + return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; } - /** - * Returns the number of the credit card used to pay for this dining. For this dining to be eligible for reward, - * this credit card number should be associated with a valid account in the reward network. - */ public String getCreditCardNumber() { return creditCardNumber; } - /** - * Returns the merchant number of the restaurant where this dining occurred. For this dining to be eligible for - * reward, this merchant number should be associated with a valid restaurant in the reward network. - */ public String getMerchantNumber() { return merchantNumber; } - /** - * Returns the date this dining occurred on. - */ - public SimpleDate getDate() { - return date; - } - - public boolean equals(Object o) { - if (!(o instanceof Dining)) { - return false; - } - Dining other = (Dining) o; - // value objects are equal if their attributes are equal - return amount.equals(other.amount) && creditCardNumber.equals(other.creditCardNumber) - && merchantNumber.equals(other.merchantNumber) && date.equals(other.date); - } - - public int hashCode() { - return amount.hashCode() + creditCardNumber.hashCode() + merchantNumber.hashCode() + date.hashCode(); - } - - public String toString() { - return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; + public MonetaryAmount getAmount() { + return amount; } } \ No newline at end of file diff --git a/lab/10-spring-intro-solution/src/main/java/rewards/RewardConfirmation.java b/lab/10-spring-intro-solution/src/main/java/rewards/RewardConfirmation.java index c6984dcd..43ec595a 100644 --- a/lab/10-spring-intro-solution/src/main/java/rewards/RewardConfirmation.java +++ b/lab/10-spring-intro-solution/src/main/java/rewards/RewardConfirmation.java @@ -1,39 +1,13 @@ package rewards; +import java.io.Serializable; + /** * A summary of a confirmed reward transaction describing a contribution made to an account that was distributed among * the account's beneficiaries. */ -public class RewardConfirmation { - - private String confirmationNumber; - - private AccountContribution accountContribution; - - /** - * Creates a new reward confirmation. - * @param confirmationNumber the unique confirmation number - * @param accountContribution a summary of the account contribution that was made - */ - public RewardConfirmation(String confirmationNumber, AccountContribution accountContribution) { - this.confirmationNumber = confirmationNumber; - this.accountContribution = accountContribution; - } - - /** - * Returns the confirmation number of the reward transaction. Can be used later to lookup the transaction record. - */ - public String getConfirmationNumber() { - return confirmationNumber; - } - - /** - * Returns a summary of the monetary contribution that was made to an account. - * @return the account contribution (the details of this reward) - */ - public AccountContribution getAccountContribution() { - return accountContribution; - } +public record RewardConfirmation(String confirmationNumber, + AccountContribution accountContribution) implements Serializable { public String toString() { return confirmationNumber; diff --git a/lab/10-spring-intro-solution/src/main/java/rewards/RewardNetwork.java b/lab/10-spring-intro-solution/src/main/java/rewards/RewardNetwork.java index 252fe280..1cb8d831 100644 --- a/lab/10-spring-intro-solution/src/main/java/rewards/RewardNetwork.java +++ b/lab/10-spring-intro-solution/src/main/java/rewards/RewardNetwork.java @@ -2,14 +2,14 @@ /** * Rewards a member account for dining at a restaurant. - * + *

* A reward takes the form of a monetary contribution made to an account that is distributed among the account's * beneficiaries. The contribution amount is typically a function of several factors such as the dining amount and * restaurant where the dining occurred. - * + *

* Example: Papa Keith spends $100.00 at Apple Bee's resulting in a $8.00 contribution to his account that is * distributed evenly among his beneficiaries Annabelle and Corgan. - * + *

* This is the central application-boundary for the "rewards" application. This is the public interface users call to * invoke the application. This is the entry-point into the Application Layer. */ @@ -17,12 +17,12 @@ public interface RewardNetwork { /** * Reward an account for dining. - * + *

* For a dining to be eligible for reward: - It must have been paid for by a registered credit card of a valid * member account in the network. - It must have taken place at a restaurant participating in the network. - * + * * @param dining a charge made to a credit card for dining at a restaurant * @return confirmation of the reward */ - public RewardConfirmation rewardAccountFor(Dining dining); + RewardConfirmation rewardAccountFor(Dining dining); } \ No newline at end of file diff --git a/lab/10-spring-intro-solution/src/main/java/rewards/internal/RewardNetworkImpl.java b/lab/10-spring-intro-solution/src/main/java/rewards/internal/RewardNetworkImpl.java index a02a23f5..0ec27ac5 100644 --- a/lab/10-spring-intro-solution/src/main/java/rewards/internal/RewardNetworkImpl.java +++ b/lab/10-spring-intro-solution/src/main/java/rewards/internal/RewardNetworkImpl.java @@ -14,39 +14,40 @@ /** * Rewards an Account for Dining at a Restaurant. - * + *

* The sole Reward Network implementation. This object is an application-layer service responsible for coordinating with * the domain-layer to carry out the process of rewarding benefits to accounts for dining. - * + *

* Said in other words, this class implements the "reward account for dining" use case. */ public class RewardNetworkImpl implements RewardNetwork { - private AccountRepository accountRepository; - - private RestaurantRepository restaurantRepository; - - private RewardRepository rewardRepository; - - /** - * Creates a new reward network. - * @param accountRepository the repository for loading accounts to reward - * @param restaurantRepository the repository for loading restaurants that determine how much to reward - * @param rewardRepository the repository for recording a record of successful reward transactions - */ - public RewardNetworkImpl(AccountRepository accountRepository, RestaurantRepository restaurantRepository, - RewardRepository rewardRepository) { - this.accountRepository = accountRepository; - this.restaurantRepository = restaurantRepository; - this.rewardRepository = rewardRepository; - } - - public RewardConfirmation rewardAccountFor(Dining dining) { - Account account = accountRepository.findByCreditCard(dining.getCreditCardNumber()); - Restaurant restaurant = restaurantRepository.findByMerchantNumber(dining.getMerchantNumber()); - MonetaryAmount amount = restaurant.calculateBenefitFor(account, dining); - AccountContribution contribution = account.makeContribution(amount); - accountRepository.updateBeneficiaries(account); - return rewardRepository.confirmReward(contribution, dining); - } + private final AccountRepository accountRepository; + + private final RestaurantRepository restaurantRepository; + + private final RewardRepository rewardRepository; + + /** + * Creates a new reward network. + * + * @param accountRepository the repository for loading accounts to reward + * @param restaurantRepository the repository for loading restaurants that determine how much to reward + * @param rewardRepository the repository for recording a record of successful reward transactions + */ + public RewardNetworkImpl(AccountRepository accountRepository, RestaurantRepository restaurantRepository, + RewardRepository rewardRepository) { + this.accountRepository = accountRepository; + this.restaurantRepository = restaurantRepository; + this.rewardRepository = rewardRepository; + } + + public RewardConfirmation rewardAccountFor(Dining dining) { + Account account = accountRepository.findByCreditCard(dining.getCreditCardNumber()); + Restaurant restaurant = restaurantRepository.findByMerchantNumber(dining.getMerchantNumber()); + MonetaryAmount amount = restaurant.calculateBenefitFor(account, dining); + AccountContribution contribution = account.makeContribution(amount); + accountRepository.updateBeneficiaries(account); + return rewardRepository.confirmReward(contribution, dining); + } } \ No newline at end of file diff --git a/lab/10-spring-intro-solution/src/main/java/rewards/internal/account/Account.java b/lab/10-spring-intro-solution/src/main/java/rewards/internal/account/Account.java index eb685069..8930d5a2 100644 --- a/lab/10-spring-intro-solution/src/main/java/rewards/internal/account/Account.java +++ b/lab/10-spring-intro-solution/src/main/java/rewards/internal/account/Account.java @@ -14,132 +14,135 @@ /** * An account for a member of the reward network. An account has one or more beneficiaries whose allocations must add up * to 100%. - * + *

* An account can make contributions to its beneficiaries. Each contribution is distributed among the beneficiaries * based on an allocation. - * + *

* An entity. An aggregate. */ public class Account extends Entity { - private String number; - - private String name; - - private Set beneficiaries = new HashSet(); - - @SuppressWarnings("unused") - private Account() { - } - - /** - * Create a new account. - * @param number the account number - * @param name the name on the account - */ - public Account(String number, String name) { - this.number = number; - this.name = name; - } - - /** - * Returns the number used to uniquely identify this account. - */ - public String getNumber() { - return number; - } - - /** - * Returns the name on file for this account. - */ - public String getName() { - return name; - } - - /** - * Add a single beneficiary with a 100% allocation percentage. - * @param beneficiaryName the name of the beneficiary (should be unique) - */ - public void addBeneficiary(String beneficiaryName) { - addBeneficiary(beneficiaryName, Percentage.oneHundred()); - } - - /** - * Add a single beneficiary with the specified allocation percentage. - * @param beneficiaryName the name of the beneficiary (should be unique) - * @param allocationPercentage the beneficiary's allocation percentage within this account - */ - public void addBeneficiary(String beneficiaryName, Percentage allocationPercentage) { - beneficiaries.add(new Beneficiary(beneficiaryName, allocationPercentage)); - } - - /** - * Validation check that returns true only if the total beneficiary allocation adds up to 100%. - */ - public boolean isValid() { - Percentage totalPercentage = Percentage.zero(); - for (Beneficiary b : beneficiaries) { - totalPercentage = totalPercentage.add(b.getAllocationPercentage()); - } - if (totalPercentage.equals(Percentage.oneHundred())) { - return true; - } else { - return false; - } - } - - /** - * Make a monetary contribution to this account. The contribution amount is distributed among the account's - * beneficiaries based on each beneficiary's allocation percentage. - * @param amount the total amount to contribute - */ - public AccountContribution makeContribution(MonetaryAmount amount) { - if (!isValid()) { - throw new IllegalStateException( - "Cannot make contributions to this account: it has invalid beneficiary allocations"); - } - Set distributions = distribute(amount); - return new AccountContribution(getNumber(), amount, distributions); - } - - /** - * Distribute the contribution amount among this account's beneficiaries. - * @param amount the total contribution amount - * @return the individual beneficiary distributions - */ - private Set distribute(MonetaryAmount amount) { - Set distributions = new HashSet(beneficiaries.size()); - for (Beneficiary beneficiary : beneficiaries) { - MonetaryAmount distributionAmount = amount.multiplyBy(beneficiary.getAllocationPercentage()); - beneficiary.credit(distributionAmount); - Distribution distribution = new Distribution(beneficiary.getName(), distributionAmount, beneficiary - .getAllocationPercentage(), beneficiary.getSavings()); - distributions.add(distribution); - } - return distributions; - } - - /** - * Returns the beneficiaries for this account. - *

- * Callers should not attempt to hold on or modify the returned set. This method should only be used transitively; - * for example, called to facilitate account reporting. - * @return the beneficiaries of this account - */ - public Set getBeneficiaries() { - return Collections.unmodifiableSet(beneficiaries); - } - - /** - * Used to restore an allocated beneficiary. Should only be called by the repository responsible for reconstituting - * this account. - * @param beneficiary the beneficiary - */ - void restoreBeneficiary(Beneficiary beneficiary) { - beneficiaries.add(beneficiary); - } - - public String toString() { - return "Number = '" + number + "', name = " + name + "', beneficiaries = " + beneficiaries; - } + private String number; + + private String name; + + private final Set beneficiaries = new HashSet<>(); + + @SuppressWarnings("unused") + private Account() { + } + + /** + * Create a new account. + * + * @param number the account number + * @param name the name on the account + */ + public Account(String number, String name) { + this.number = number; + this.name = name; + } + + /** + * Returns the number used to uniquely identify this account. + */ + public String getNumber() { + return number; + } + + /** + * Returns the name on file for this account. + */ + public String getName() { + return name; + } + + /** + * Add a single beneficiary with a 100% allocation percentage. + * + * @param beneficiaryName the name of the beneficiary (should be unique) + */ + public void addBeneficiary(String beneficiaryName) { + addBeneficiary(beneficiaryName, Percentage.oneHundred()); + } + + /** + * Add a single beneficiary with the specified allocation percentage. + * + * @param beneficiaryName the name of the beneficiary (should be unique) + * @param allocationPercentage the beneficiary's allocation percentage within this account + */ + public void addBeneficiary(String beneficiaryName, Percentage allocationPercentage) { + beneficiaries.add(new Beneficiary(beneficiaryName, allocationPercentage)); + } + + /** + * Validation check that returns true only if the total beneficiary allocation adds up to 100%. + */ + public boolean isValid() { + Percentage totalPercentage = Percentage.zero(); + for (Beneficiary b : beneficiaries) { + totalPercentage = totalPercentage.add(b.getAllocationPercentage()); + } + return totalPercentage.equals(Percentage.oneHundred()); + } + + /** + * Make a monetary contribution to this account. The contribution amount is distributed among the account's + * beneficiaries based on each beneficiary's allocation percentage. + * + * @param amount the total amount to contribute + */ + public AccountContribution makeContribution(MonetaryAmount amount) { + if (!isValid()) { + throw new IllegalStateException( + "Cannot make contributions to this account: it has invalid beneficiary allocations"); + } + Set distributions = distribute(amount); + return new AccountContribution(getNumber(), amount, distributions); + } + + /** + * Distribute the contribution amount among this account's beneficiaries. + * + * @param amount the total contribution amount + * @return the individual beneficiary distributions + */ + private Set distribute(MonetaryAmount amount) { + Set distributions = HashSet.newHashSet(beneficiaries.size()); + for (Beneficiary beneficiary : beneficiaries) { + MonetaryAmount distributionAmount = amount.multiplyBy(beneficiary.getAllocationPercentage()); + beneficiary.credit(distributionAmount); + Distribution distribution = new Distribution(beneficiary.getName(), distributionAmount, beneficiary + .getAllocationPercentage(), beneficiary.getSavings()); + distributions.add(distribution); + } + return distributions; + } + + /** + * Returns the beneficiaries for this account. + *

+ * Callers should not attempt to hold on or modify the returned set. This method should only be used transitively; + * for example, called to facilitate account reporting. + * + * @return the beneficiaries of this account + */ + public Set getBeneficiaries() { + return Collections.unmodifiableSet(beneficiaries); + } + + /** + * Used to restore an allocated beneficiary. Should only be called by the repository responsible for reconstituting + * this account. + * + * @param beneficiary the beneficiary + */ + void restoreBeneficiary(Beneficiary beneficiary) { + beneficiaries.add(beneficiary); + } + + public String toString() { + return "Number = '" + number + "', name = " + name + "', beneficiaries = " + beneficiaries; + } } \ No newline at end of file diff --git a/lab/10-spring-intro-solution/src/main/java/rewards/internal/account/AccountRepository.java b/lab/10-spring-intro-solution/src/main/java/rewards/internal/account/AccountRepository.java index 5ba489ec..4def8258 100644 --- a/lab/10-spring-intro-solution/src/main/java/rewards/internal/account/AccountRepository.java +++ b/lab/10-spring-intro-solution/src/main/java/rewards/internal/account/AccountRepository.java @@ -3,27 +3,29 @@ /** * Loads account aggregates. Called by the reward network to find and reconstitute Account entities from an external * form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. */ public interface AccountRepository { - /** - * Load an account by its credit card. - * @param creditCardNumber the credit card number - * @return the account object - */ - public Account findByCreditCard(String creditCardNumber); + /** + * Load an account by its credit card. + * + * @param creditCardNumber the credit card number + * @return the account object + */ + Account findByCreditCard(String creditCardNumber); - /** - * Updates the 'savings' of each account beneficiary. The new savings balance contains the amount distributed for a - * contribution made during a reward transaction. - *

- * Note: use of an object-relational mapper (ORM) with support for transparent-persistence like Hibernate (or the - * new Java Persistence API (JPA)) would remove the need for this explicit update operation as the ORM would take - * care of applying relational updates to a modified Account entity automatically. - * @param account the account whose beneficiary savings have changed - */ - public void updateBeneficiaries(Account account); + /** + * Updates the 'savings' of each account beneficiary. The new savings balance contains the amount distributed for a + * contribution made during a reward transaction. + *

+ * Note: use of an object-relational mapper (ORM) with support for transparent-persistence like Hibernate (or the + * new Java Persistence API (JPA)) would remove the need for this explicit update operation as the ORM would take + * care of applying relational updates to a modified Account entity automatically. + * + * @param account the account whose beneficiary savings have changed + */ + void updateBeneficiaries(Account account); } \ No newline at end of file diff --git a/lab/10-spring-intro-solution/src/main/java/rewards/internal/restaurant/Restaurant.java b/lab/10-spring-intro-solution/src/main/java/rewards/internal/restaurant/Restaurant.java index aa642ae5..029fb4cc 100644 --- a/lab/10-spring-intro-solution/src/main/java/rewards/internal/restaurant/Restaurant.java +++ b/lab/10-spring-intro-solution/src/main/java/rewards/internal/restaurant/Restaurant.java @@ -9,7 +9,6 @@ /** * A restaurant establishment in the network. Like AppleBee's. - * * Restaurants calculate how much benefit may be awarded to an account for dining based on a benefit percentage. */ public class Restaurant extends Entity { diff --git a/lab/10-spring-intro-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java b/lab/10-spring-intro-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java index a632fcc1..04968ac5 100644 --- a/lab/10-spring-intro-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java +++ b/lab/10-spring-intro-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java @@ -3,15 +3,16 @@ /** * Loads restaurant aggregates. Called by the reward network to find and reconstitute Restaurant entities from an * external form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. */ public interface RestaurantRepository { /** * Load a Restaurant entity by its merchant number. + * * @param merchantNumber the merchant number * @return the restaurant */ - public Restaurant findByMerchantNumber(String merchantNumber); + Restaurant findByMerchantNumber(String merchantNumber); } diff --git a/lab/10-spring-intro-solution/src/main/java/rewards/internal/reward/RewardRepository.java b/lab/10-spring-intro-solution/src/main/java/rewards/internal/reward/RewardRepository.java index c8189e52..81209d64 100644 --- a/lab/10-spring-intro-solution/src/main/java/rewards/internal/reward/RewardRepository.java +++ b/lab/10-spring-intro-solution/src/main/java/rewards/internal/reward/RewardRepository.java @@ -11,10 +11,11 @@ public interface RewardRepository { /** * Create a record of a reward that will track a contribution made to an account for dining. + * * @param contribution the account contribution that was made - * @param dining the dining event that resulted in the account contribution + * @param dining the dining event that resulted in the account contribution * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later * date */ - public RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); + RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); } \ No newline at end of file diff --git a/lab/10-spring-intro-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java b/lab/10-spring-intro-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java index c1f9af1f..bd8706e5 100644 --- a/lab/10-spring-intro-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java +++ b/lab/10-spring-intro-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java @@ -18,55 +18,55 @@ /** * Unit tests for the RewardNetworkImpl application logic. Configures the implementation with stub repositories * containing dummy data for fast in-memory testing without the overhead of an external data source. - * + *

* Besides helping catch bugs early, tests are a great way for a new developer to learn an API as he or she can see the * API in action. Tests also help validate a design as they are a measure for how easy it is to use your code. */ -public class RewardNetworkImplTests { +class RewardNetworkImplTests { - /** - * The object being tested. - */ - private RewardNetworkImpl rewardNetwork; + /** + * The object being tested. + */ + private RewardNetworkImpl rewardNetwork; - @BeforeEach - public void setUp() throws Exception { - // create stubs to facilitate fast in-memory testing with dummy data and no external dependencies - AccountRepository accountRepo = new StubAccountRepository(); - RestaurantRepository restaurantRepo = new StubRestaurantRepository(); - RewardRepository rewardRepo = new StubRewardRepository(); + @BeforeEach + void setUp() { + // create stubs to facilitate fast in-memory testing with dummy data and no external dependencies + AccountRepository accountRepo = new StubAccountRepository(); + RestaurantRepository restaurantRepo = new StubRestaurantRepository(); + RewardRepository rewardRepo = new StubRewardRepository(); - // setup the object being tested by handing what it needs to work - rewardNetwork = new RewardNetworkImpl(accountRepo, restaurantRepo, rewardRepo); - } + // setup the object being tested by handing what it needs to work + rewardNetwork = new RewardNetworkImpl(accountRepo, restaurantRepo, rewardRepo); + } - @Test - public void testRewardForDining() { - // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input - Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); + @Test + void testRewardForDining() { + // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input + Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); - // call the 'rewardNetwork' to test its rewardAccountFor(Dining) method - RewardConfirmation confirmation = rewardNetwork.rewardAccountFor(dining); + // call the 'rewardNetwork' to test its rewardAccountFor(Dining) method + RewardConfirmation confirmation = rewardNetwork.rewardAccountFor(dining); - // assert the expected reward confirmation results - assertNotNull(confirmation); - assertNotNull(confirmation.getConfirmationNumber()); + // assert the expected reward confirmation results + assertNotNull(confirmation); + assertNotNull(confirmation.confirmationNumber()); - // assert an account contribution was made - AccountContribution contribution = confirmation.getAccountContribution(); - assertNotNull(contribution); + // assert an account contribution was made + AccountContribution contribution = confirmation.accountContribution(); + assertNotNull(contribution); - // the account number should be '123456789' - assertEquals("123456789", contribution.getAccountNumber()); + // the account number should be '123456789' + assertEquals("123456789", contribution.accountNumber()); - // the total contribution amount should be 8.00 (8% of 100.00) - assertEquals(MonetaryAmount.valueOf("8.00"), contribution.getAmount()); + // the total contribution amount should be 8.00 (8% of 100.00) + assertEquals(MonetaryAmount.valueOf("8.00"), contribution.amount()); - // the total contribution amount should have been split into 2 distributions - assertEquals(2, contribution.getDistributions().size()); + // the total contribution amount should have been split into 2 distributions + assertEquals(2, contribution.distributions().size()); - // each distribution should be 4.00 (as both have a 50% allocation) - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").getAmount()); - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").getAmount()); - } + // each distribution should be 4.00 (as both have a 50% allocation) + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").amount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").amount()); + } } diff --git a/lab/10-spring-intro-solution/src/test/java/rewards/internal/StubAccountRepository.java b/lab/10-spring-intro-solution/src/test/java/rewards/internal/StubAccountRepository.java index 5d229021..ff91a312 100644 --- a/lab/10-spring-intro-solution/src/test/java/rewards/internal/StubAccountRepository.java +++ b/lab/10-spring-intro-solution/src/test/java/rewards/internal/StubAccountRepository.java @@ -11,14 +11,14 @@ /** * A dummy account repository implementation. Has a single Account "Keith and Keri Donald" with two beneficiaries * "Annabelle" (50% allocation) and "Corgan" (50% allocation) associated with credit card "1234123412341234". - * + *

* Stubs facilitate unit testing. An object needing an AccountRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubAccountRepository implements AccountRepository { - private Map accountsByCreditCard = new HashMap(); + Map accountsByCreditCard = new HashMap<>(); public StubAccountRepository() { Account account = new Account("123456789", "Keith and Keri Donald"); diff --git a/lab/10-spring-intro-solution/src/test/java/rewards/internal/StubRestaurantRepository.java b/lab/10-spring-intro-solution/src/test/java/rewards/internal/StubRestaurantRepository.java index 6a5873fe..0bc47be6 100644 --- a/lab/10-spring-intro-solution/src/test/java/rewards/internal/StubRestaurantRepository.java +++ b/lab/10-spring-intro-solution/src/test/java/rewards/internal/StubRestaurantRepository.java @@ -11,14 +11,14 @@ /** * A dummy restaurant repository implementation. Has a single restaurant "Apple Bees" with a 8% benefit availability * percentage that's always available. - * + *

* Stubs facilitate unit testing. An object needing a RestaurantRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubRestaurantRepository implements RestaurantRepository { - private Map restaurantsByMerchantNumber = new HashMap(); + Map restaurantsByMerchantNumber = new HashMap<>(); public StubRestaurantRepository() { Restaurant restaurant = new Restaurant("1234567890", "Apple Bees"); @@ -27,7 +27,7 @@ public StubRestaurantRepository() { } public Restaurant findByMerchantNumber(String merchantNumber) { - Restaurant restaurant = (Restaurant) restaurantsByMerchantNumber.get(merchantNumber); + Restaurant restaurant = restaurantsByMerchantNumber.get(merchantNumber); if (restaurant == null) { throw new RuntimeException("no restaurant has been found for merchant number " + merchantNumber); } diff --git a/lab/10-spring-intro/src/main/java/rewards/AccountContribution.java b/lab/10-spring-intro/src/main/java/rewards/AccountContribution.java index 5cad1918..852a512b 100644 --- a/lab/10-spring-intro/src/main/java/rewards/AccountContribution.java +++ b/lab/10-spring-intro/src/main/java/rewards/AccountContribution.java @@ -7,132 +7,41 @@ /** * A summary of a monetary contribution made to an account that was distributed among the account's beneficiaries. - * + *

* A value object. Immutable. */ -public class AccountContribution { - - private String accountNumber; - - private MonetaryAmount amount; - - private Set distributions; - - /** - * Creates a new account contribution. - * @param accountNumber the number of the account the contribution was made - * @param amount the total contribution amount - * @param distributions how the contribution was distributed among the account's beneficiaries - */ - public AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { - this.accountNumber = accountNumber; - this.amount = amount; - this.distributions = distributions; - } - - /** - * Returns the number of the account this contribution was made to. - * @return the account number - */ - public String getAccountNumber() { - return accountNumber; - } - - /** - * Returns the total amount of the contribution. - * @return the contribution amount - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns how this contribution was distributed among the account's beneficiaries. - * @return the contribution distributions - */ - public Set getDistributions() { - return distributions; - } - - /** - * Returns how this contribution was distributed to a single account beneficiary. - * @param beneficiary the name of the beneficiary e.g "Annabelle" - * @return a summary of how the contribution amount was distributed to the beneficiary - */ - public Distribution getDistribution(String beneficiary) { - for (Distribution d : distributions) { - if (d.beneficiary.equals(beneficiary)) { - return d; - } - } - throw new IllegalArgumentException("No such distribution for '" + beneficiary + "'"); - } - - /** - * A single distribution made to a beneficiary as part of an account contribution, summarizing the distribution - * amount and resulting total beneficiary savings. - * - * A value object. - */ - public static class Distribution { - - private String beneficiary; - - private MonetaryAmount amount; - - private Percentage percentage; - - private MonetaryAmount totalSavings; - - /** - * Creates a new distribution. - * @param beneficiary the name of the account beneficiary that received a distribution - * @param amount the distribution amount - * @param percentage this distribution's percentage of the total account contribution - * @param totalSavings the beneficiary's total savings amount after the distribution was made - */ - public Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, - MonetaryAmount totalSavings) { - this.beneficiary = beneficiary; - this.percentage = percentage; - this.amount = amount; - this.totalSavings = totalSavings; - } - - /** - * Returns the name of the beneficiary. - */ - public String getBeneficiary() { - return beneficiary; - } - - /** - * Returns the amount of this distribution. - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns the percentage of this distribution relative to others in the contribution. - */ - public Percentage getPercentage() { - return percentage; - } - - /** - * Returns the total savings of the beneficiary after this distribution. - */ - public MonetaryAmount getTotalSavings() { - return totalSavings; - } - - public String toString() { - return amount + " to '" + beneficiary + "' (" + percentage + ")"; - } - } - - public String toString() { - return "Contribution of " + amount + " to account '" + accountNumber + "' distributed " + distributions; - } +public record AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { + + /** + * Returns how this contribution was distributed to a single account beneficiary. + * + * @param beneficiary the name of the beneficiary e.g "Annabelle" + * @return a summary of how the contribution amount was distributed to the beneficiary + */ + public Distribution getDistribution(String beneficiary) { + for (Distribution d : distributions) { + if (d.beneficiary.equals(beneficiary)) { + return d; + } + } + throw new IllegalArgumentException("No such distribution for '" + beneficiary + "'"); + } + + /** + * A single distribution made to a beneficiary as part of an account contribution, summarizing the distribution + * amount and resulting total beneficiary savings. + *

+ * A value object. + */ + public record Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, + MonetaryAmount totalSavings) { + + public String toString() { + return amount + " to '" + beneficiary + "' (" + percentage + ")"; + } + } + + public String toString() { + return "Contribution of " + amount + " to account '" + accountNumber + "' distributed " + distributions; + } } \ No newline at end of file diff --git a/lab/10-spring-intro/src/main/java/rewards/Dining.java b/lab/10-spring-intro/src/main/java/rewards/Dining.java index c9b8dcf2..54e68a76 100644 --- a/lab/10-spring-intro/src/main/java/rewards/Dining.java +++ b/lab/10-spring-intro/src/main/java/rewards/Dining.java @@ -1,46 +1,27 @@ package rewards; +import java.io.Serializable; + import common.datetime.SimpleDate; import common.money.MonetaryAmount; /** * A dining event that occurred, representing a charge made to an credit card by a merchant on a specific date. - * * For a dining to be eligible for reward, the credit card number should map to an account in the reward network. In * addition, the merchant number should map to a restaurant in the network. - * * A value object. Immutable. */ -public class Dining { - - private MonetaryAmount amount; - - private String creditCardNumber; - - private String merchantNumber; +public record Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, + SimpleDate date) implements Serializable { - private SimpleDate date; - - /** - * Creates a new dining, reflecting an amount that was charged to a card by a restaurant on the date specified. - * @param amount the total amount of the dining bill - * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param date the date of the dining event - */ - public Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, SimpleDate date) { - this.amount = amount; - this.creditCardNumber = creditCardNumber; - this.merchantNumber = merchantNumber; - this.date = date; - } /** - * Creates a new dining, reflecting an amount that was charged to a credit card by a restaurant on today's date. A + * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on today's date. A * convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param merchantNumber the merchant number of the restaurant where the dining occurred * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber) { @@ -48,67 +29,36 @@ public static Dining createDining(String amount, String creditCardNumber, String } /** - * Creates a new dining, reflecting an amount that was charged to a credit card by a restaurant on the date - * specified. A convenient static factory method. - * @param amount the total amount of the dining bill as a string + * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on the date specified. + * A convenient static factory method. + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param month the month of the dining event - * @param day the day of the dining event - * @param year the year of the dining event + * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param month the month of the dining event + * @param day the day of the dining event + * @param year the year of the dining event * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber, int month, - int day, int year) { + int day, int year) { return new Dining(MonetaryAmount.valueOf(amount), creditCardNumber, merchantNumber, new SimpleDate(month, day, year)); } - /** - * Returns the amount of this dining--the total amount of the bill that was charged to the credit card. - */ - public MonetaryAmount getAmount() { - return amount; + public String toString() { + return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; } - /** - * Returns the number of the credit card used to pay for this dining. For this dining to be eligible for reward, - * this credit card number should be associated with a valid account in the reward network. - */ public String getCreditCardNumber() { return creditCardNumber; } - /** - * Returns the merchant number of the restaurant where this dining occurred. For this dining to be eligible for - * reward, this merchant number should be associated with a valid restaurant in the reward network. - */ public String getMerchantNumber() { return merchantNumber; } - /** - * Returns the date this dining occurred on. - */ - public SimpleDate getDate() { - return date; - } - - public boolean equals(Object o) { - if (!(o instanceof Dining)) { - return false; - } - Dining other = (Dining) o; - // value objects are equal if their attributes are equal - return amount.equals(other.amount) && creditCardNumber.equals(other.creditCardNumber) - && merchantNumber.equals(other.merchantNumber) && date.equals(other.date); - } - - public int hashCode() { - return amount.hashCode() + creditCardNumber.hashCode() + merchantNumber.hashCode() + date.hashCode(); - } - - public String toString() { - return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; + public MonetaryAmount getAmount() { + return amount; } } \ No newline at end of file diff --git a/lab/10-spring-intro/src/main/java/rewards/RewardConfirmation.java b/lab/10-spring-intro/src/main/java/rewards/RewardConfirmation.java index c6984dcd..43ec595a 100644 --- a/lab/10-spring-intro/src/main/java/rewards/RewardConfirmation.java +++ b/lab/10-spring-intro/src/main/java/rewards/RewardConfirmation.java @@ -1,39 +1,13 @@ package rewards; +import java.io.Serializable; + /** * A summary of a confirmed reward transaction describing a contribution made to an account that was distributed among * the account's beneficiaries. */ -public class RewardConfirmation { - - private String confirmationNumber; - - private AccountContribution accountContribution; - - /** - * Creates a new reward confirmation. - * @param confirmationNumber the unique confirmation number - * @param accountContribution a summary of the account contribution that was made - */ - public RewardConfirmation(String confirmationNumber, AccountContribution accountContribution) { - this.confirmationNumber = confirmationNumber; - this.accountContribution = accountContribution; - } - - /** - * Returns the confirmation number of the reward transaction. Can be used later to lookup the transaction record. - */ - public String getConfirmationNumber() { - return confirmationNumber; - } - - /** - * Returns a summary of the monetary contribution that was made to an account. - * @return the account contribution (the details of this reward) - */ - public AccountContribution getAccountContribution() { - return accountContribution; - } +public record RewardConfirmation(String confirmationNumber, + AccountContribution accountContribution) implements Serializable { public String toString() { return confirmationNumber; diff --git a/lab/10-spring-intro/src/main/java/rewards/RewardNetwork.java b/lab/10-spring-intro/src/main/java/rewards/RewardNetwork.java index 252fe280..699e404e 100644 --- a/lab/10-spring-intro/src/main/java/rewards/RewardNetwork.java +++ b/lab/10-spring-intro/src/main/java/rewards/RewardNetwork.java @@ -2,27 +2,27 @@ /** * Rewards a member account for dining at a restaurant. - * + *

* A reward takes the form of a monetary contribution made to an account that is distributed among the account's * beneficiaries. The contribution amount is typically a function of several factors such as the dining amount and * restaurant where the dining occurred. - * + *

* Example: Papa Keith spends $100.00 at Apple Bee's resulting in a $8.00 contribution to his account that is * distributed evenly among his beneficiaries Annabelle and Corgan. - * + *

* This is the central application-boundary for the "rewards" application. This is the public interface users call to * invoke the application. This is the entry-point into the Application Layer. */ public interface RewardNetwork { - /** - * Reward an account for dining. - * - * For a dining to be eligible for reward: - It must have been paid for by a registered credit card of a valid - * member account in the network. - It must have taken place at a restaurant participating in the network. - * - * @param dining a charge made to a credit card for dining at a restaurant - * @return confirmation of the reward - */ - public RewardConfirmation rewardAccountFor(Dining dining); + /** + * Reward an account for dining. + *

+ * For a dining to be eligible for reward: - It must have been paid for by a registered credit card of a valid + * member account in the network. - It must have taken place at a restaurant participating in the network. + * + * @param dining a charge made to a credit card for dining at a restaurant + * @return confirmation of the reward + */ + RewardConfirmation rewardAccountFor(Dining dining); } \ No newline at end of file diff --git a/lab/10-spring-intro/src/main/java/rewards/internal/RewardNetworkImpl.java b/lab/10-spring-intro/src/main/java/rewards/internal/RewardNetworkImpl.java index e13b0b5e..43cff4b8 100644 --- a/lab/10-spring-intro/src/main/java/rewards/internal/RewardNetworkImpl.java +++ b/lab/10-spring-intro/src/main/java/rewards/internal/RewardNetworkImpl.java @@ -9,18 +9,18 @@ /** * Rewards an Account for Dining at a Restaurant. - * + *

* The sole Reward Network implementation. This object is an application-layer service responsible for coordinating with * the domain-layer to carry out the process of rewarding benefits to accounts for dining. - * + *

* Said in other words, this class implements the "reward account for dining" use case. - * + *

* TODO-00: In this lab, you are going to exercise the following: * - Understanding internal operations that need to be performed to implement * "rewardAccountFor" method of the "RewardNetworkImpl" class * - Writing test code using stub implementations of dependencies * - Writing both target code and test code without using Spring framework - * + *

* TODO-01: Review the Rewards Application document (Refer to the lab document) * TODO-02: Review project dependencies (Refer to the lab document) * TODO-03: Review Rewards Commons project (Refer to the lab document) @@ -30,29 +30,30 @@ */ public class RewardNetworkImpl implements RewardNetwork { - private AccountRepository accountRepository; + private final AccountRepository accountRepository; - private RestaurantRepository restaurantRepository; + private final RestaurantRepository restaurantRepository; - private RewardRepository rewardRepository; + private final RewardRepository rewardRepository; - /** - * Creates a new reward network. - * @param accountRepository the repository for loading accounts to reward - * @param restaurantRepository the repository for loading restaurants that determine how much to reward - * @param rewardRepository the repository for recording a record of successful reward transactions - */ - public RewardNetworkImpl(AccountRepository accountRepository, RestaurantRepository restaurantRepository, - RewardRepository rewardRepository) { - this.accountRepository = accountRepository; - this.restaurantRepository = restaurantRepository; - this.rewardRepository = rewardRepository; - } + /** + * Creates a new reward network. + * + * @param accountRepository the repository for loading accounts to reward + * @param restaurantRepository the repository for loading restaurants that determine how much to reward + * @param rewardRepository the repository for recording a record of successful reward transactions + */ + public RewardNetworkImpl(AccountRepository accountRepository, RestaurantRepository restaurantRepository, + RewardRepository rewardRepository) { + this.accountRepository = accountRepository; + this.restaurantRepository = restaurantRepository; + this.rewardRepository = rewardRepository; + } - public RewardConfirmation rewardAccountFor(Dining dining) { - // TODO-07: Write code here for rewarding an account according to - // the sequence diagram in the lab document - // TODO-08: Return the corresponding reward confirmation - return null; - } + public RewardConfirmation rewardAccountFor(Dining dining) { + // TODO-07: Write code here for rewarding an account according to + // the sequence diagram in the lab document + // TODO-08: Return the corresponding reward confirmation + return null; + } } \ No newline at end of file diff --git a/lab/10-spring-intro/src/main/java/rewards/internal/account/Account.java b/lab/10-spring-intro/src/main/java/rewards/internal/account/Account.java index eb685069..8930d5a2 100644 --- a/lab/10-spring-intro/src/main/java/rewards/internal/account/Account.java +++ b/lab/10-spring-intro/src/main/java/rewards/internal/account/Account.java @@ -14,132 +14,135 @@ /** * An account for a member of the reward network. An account has one or more beneficiaries whose allocations must add up * to 100%. - * + *

* An account can make contributions to its beneficiaries. Each contribution is distributed among the beneficiaries * based on an allocation. - * + *

* An entity. An aggregate. */ public class Account extends Entity { - private String number; - - private String name; - - private Set beneficiaries = new HashSet(); - - @SuppressWarnings("unused") - private Account() { - } - - /** - * Create a new account. - * @param number the account number - * @param name the name on the account - */ - public Account(String number, String name) { - this.number = number; - this.name = name; - } - - /** - * Returns the number used to uniquely identify this account. - */ - public String getNumber() { - return number; - } - - /** - * Returns the name on file for this account. - */ - public String getName() { - return name; - } - - /** - * Add a single beneficiary with a 100% allocation percentage. - * @param beneficiaryName the name of the beneficiary (should be unique) - */ - public void addBeneficiary(String beneficiaryName) { - addBeneficiary(beneficiaryName, Percentage.oneHundred()); - } - - /** - * Add a single beneficiary with the specified allocation percentage. - * @param beneficiaryName the name of the beneficiary (should be unique) - * @param allocationPercentage the beneficiary's allocation percentage within this account - */ - public void addBeneficiary(String beneficiaryName, Percentage allocationPercentage) { - beneficiaries.add(new Beneficiary(beneficiaryName, allocationPercentage)); - } - - /** - * Validation check that returns true only if the total beneficiary allocation adds up to 100%. - */ - public boolean isValid() { - Percentage totalPercentage = Percentage.zero(); - for (Beneficiary b : beneficiaries) { - totalPercentage = totalPercentage.add(b.getAllocationPercentage()); - } - if (totalPercentage.equals(Percentage.oneHundred())) { - return true; - } else { - return false; - } - } - - /** - * Make a monetary contribution to this account. The contribution amount is distributed among the account's - * beneficiaries based on each beneficiary's allocation percentage. - * @param amount the total amount to contribute - */ - public AccountContribution makeContribution(MonetaryAmount amount) { - if (!isValid()) { - throw new IllegalStateException( - "Cannot make contributions to this account: it has invalid beneficiary allocations"); - } - Set distributions = distribute(amount); - return new AccountContribution(getNumber(), amount, distributions); - } - - /** - * Distribute the contribution amount among this account's beneficiaries. - * @param amount the total contribution amount - * @return the individual beneficiary distributions - */ - private Set distribute(MonetaryAmount amount) { - Set distributions = new HashSet(beneficiaries.size()); - for (Beneficiary beneficiary : beneficiaries) { - MonetaryAmount distributionAmount = amount.multiplyBy(beneficiary.getAllocationPercentage()); - beneficiary.credit(distributionAmount); - Distribution distribution = new Distribution(beneficiary.getName(), distributionAmount, beneficiary - .getAllocationPercentage(), beneficiary.getSavings()); - distributions.add(distribution); - } - return distributions; - } - - /** - * Returns the beneficiaries for this account. - *

- * Callers should not attempt to hold on or modify the returned set. This method should only be used transitively; - * for example, called to facilitate account reporting. - * @return the beneficiaries of this account - */ - public Set getBeneficiaries() { - return Collections.unmodifiableSet(beneficiaries); - } - - /** - * Used to restore an allocated beneficiary. Should only be called by the repository responsible for reconstituting - * this account. - * @param beneficiary the beneficiary - */ - void restoreBeneficiary(Beneficiary beneficiary) { - beneficiaries.add(beneficiary); - } - - public String toString() { - return "Number = '" + number + "', name = " + name + "', beneficiaries = " + beneficiaries; - } + private String number; + + private String name; + + private final Set beneficiaries = new HashSet<>(); + + @SuppressWarnings("unused") + private Account() { + } + + /** + * Create a new account. + * + * @param number the account number + * @param name the name on the account + */ + public Account(String number, String name) { + this.number = number; + this.name = name; + } + + /** + * Returns the number used to uniquely identify this account. + */ + public String getNumber() { + return number; + } + + /** + * Returns the name on file for this account. + */ + public String getName() { + return name; + } + + /** + * Add a single beneficiary with a 100% allocation percentage. + * + * @param beneficiaryName the name of the beneficiary (should be unique) + */ + public void addBeneficiary(String beneficiaryName) { + addBeneficiary(beneficiaryName, Percentage.oneHundred()); + } + + /** + * Add a single beneficiary with the specified allocation percentage. + * + * @param beneficiaryName the name of the beneficiary (should be unique) + * @param allocationPercentage the beneficiary's allocation percentage within this account + */ + public void addBeneficiary(String beneficiaryName, Percentage allocationPercentage) { + beneficiaries.add(new Beneficiary(beneficiaryName, allocationPercentage)); + } + + /** + * Validation check that returns true only if the total beneficiary allocation adds up to 100%. + */ + public boolean isValid() { + Percentage totalPercentage = Percentage.zero(); + for (Beneficiary b : beneficiaries) { + totalPercentage = totalPercentage.add(b.getAllocationPercentage()); + } + return totalPercentage.equals(Percentage.oneHundred()); + } + + /** + * Make a monetary contribution to this account. The contribution amount is distributed among the account's + * beneficiaries based on each beneficiary's allocation percentage. + * + * @param amount the total amount to contribute + */ + public AccountContribution makeContribution(MonetaryAmount amount) { + if (!isValid()) { + throw new IllegalStateException( + "Cannot make contributions to this account: it has invalid beneficiary allocations"); + } + Set distributions = distribute(amount); + return new AccountContribution(getNumber(), amount, distributions); + } + + /** + * Distribute the contribution amount among this account's beneficiaries. + * + * @param amount the total contribution amount + * @return the individual beneficiary distributions + */ + private Set distribute(MonetaryAmount amount) { + Set distributions = HashSet.newHashSet(beneficiaries.size()); + for (Beneficiary beneficiary : beneficiaries) { + MonetaryAmount distributionAmount = amount.multiplyBy(beneficiary.getAllocationPercentage()); + beneficiary.credit(distributionAmount); + Distribution distribution = new Distribution(beneficiary.getName(), distributionAmount, beneficiary + .getAllocationPercentage(), beneficiary.getSavings()); + distributions.add(distribution); + } + return distributions; + } + + /** + * Returns the beneficiaries for this account. + *

+ * Callers should not attempt to hold on or modify the returned set. This method should only be used transitively; + * for example, called to facilitate account reporting. + * + * @return the beneficiaries of this account + */ + public Set getBeneficiaries() { + return Collections.unmodifiableSet(beneficiaries); + } + + /** + * Used to restore an allocated beneficiary. Should only be called by the repository responsible for reconstituting + * this account. + * + * @param beneficiary the beneficiary + */ + void restoreBeneficiary(Beneficiary beneficiary) { + beneficiaries.add(beneficiary); + } + + public String toString() { + return "Number = '" + number + "', name = " + name + "', beneficiaries = " + beneficiaries; + } } \ No newline at end of file diff --git a/lab/10-spring-intro/src/main/java/rewards/internal/account/AccountRepository.java b/lab/10-spring-intro/src/main/java/rewards/internal/account/AccountRepository.java index 5ba489ec..4def8258 100644 --- a/lab/10-spring-intro/src/main/java/rewards/internal/account/AccountRepository.java +++ b/lab/10-spring-intro/src/main/java/rewards/internal/account/AccountRepository.java @@ -3,27 +3,29 @@ /** * Loads account aggregates. Called by the reward network to find and reconstitute Account entities from an external * form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. */ public interface AccountRepository { - /** - * Load an account by its credit card. - * @param creditCardNumber the credit card number - * @return the account object - */ - public Account findByCreditCard(String creditCardNumber); + /** + * Load an account by its credit card. + * + * @param creditCardNumber the credit card number + * @return the account object + */ + Account findByCreditCard(String creditCardNumber); - /** - * Updates the 'savings' of each account beneficiary. The new savings balance contains the amount distributed for a - * contribution made during a reward transaction. - *

- * Note: use of an object-relational mapper (ORM) with support for transparent-persistence like Hibernate (or the - * new Java Persistence API (JPA)) would remove the need for this explicit update operation as the ORM would take - * care of applying relational updates to a modified Account entity automatically. - * @param account the account whose beneficiary savings have changed - */ - public void updateBeneficiaries(Account account); + /** + * Updates the 'savings' of each account beneficiary. The new savings balance contains the amount distributed for a + * contribution made during a reward transaction. + *

+ * Note: use of an object-relational mapper (ORM) with support for transparent-persistence like Hibernate (or the + * new Java Persistence API (JPA)) would remove the need for this explicit update operation as the ORM would take + * care of applying relational updates to a modified Account entity automatically. + * + * @param account the account whose beneficiary savings have changed + */ + void updateBeneficiaries(Account account); } \ No newline at end of file diff --git a/lab/10-spring-intro/src/main/java/rewards/internal/restaurant/Restaurant.java b/lab/10-spring-intro/src/main/java/rewards/internal/restaurant/Restaurant.java index aa642ae5..029fb4cc 100644 --- a/lab/10-spring-intro/src/main/java/rewards/internal/restaurant/Restaurant.java +++ b/lab/10-spring-intro/src/main/java/rewards/internal/restaurant/Restaurant.java @@ -9,7 +9,6 @@ /** * A restaurant establishment in the network. Like AppleBee's. - * * Restaurants calculate how much benefit may be awarded to an account for dining based on a benefit percentage. */ public class Restaurant extends Entity { diff --git a/lab/10-spring-intro/src/main/java/rewards/internal/restaurant/RestaurantRepository.java b/lab/10-spring-intro/src/main/java/rewards/internal/restaurant/RestaurantRepository.java index a632fcc1..c57e0afe 100644 --- a/lab/10-spring-intro/src/main/java/rewards/internal/restaurant/RestaurantRepository.java +++ b/lab/10-spring-intro/src/main/java/rewards/internal/restaurant/RestaurantRepository.java @@ -3,15 +3,16 @@ /** * Loads restaurant aggregates. Called by the reward network to find and reconstitute Restaurant entities from an * external form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. */ public interface RestaurantRepository { - /** - * Load a Restaurant entity by its merchant number. - * @param merchantNumber the merchant number - * @return the restaurant - */ - public Restaurant findByMerchantNumber(String merchantNumber); + /** + * Load a Restaurant entity by its merchant number. + * + * @param merchantNumber the merchant number + * @return the restaurant + */ + Restaurant findByMerchantNumber(String merchantNumber); } diff --git a/lab/10-spring-intro/src/main/java/rewards/internal/reward/RewardRepository.java b/lab/10-spring-intro/src/main/java/rewards/internal/reward/RewardRepository.java index c8189e52..e32368d2 100644 --- a/lab/10-spring-intro/src/main/java/rewards/internal/reward/RewardRepository.java +++ b/lab/10-spring-intro/src/main/java/rewards/internal/reward/RewardRepository.java @@ -9,12 +9,13 @@ */ public interface RewardRepository { - /** - * Create a record of a reward that will track a contribution made to an account for dining. - * @param contribution the account contribution that was made - * @param dining the dining event that resulted in the account contribution - * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later - * date - */ - public RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); + /** + * Create a record of a reward that will track a contribution made to an account for dining. + * + * @param contribution the account contribution that was made + * @param dining the dining event that resulted in the account contribution + * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later + * date + */ + RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); } \ No newline at end of file diff --git a/lab/10-spring-intro/src/test/java/rewards/internal/RewardNetworkImplTests.java b/lab/10-spring-intro/src/test/java/rewards/internal/RewardNetworkImplTests.java index bf860715..3cd76965 100644 --- a/lab/10-spring-intro/src/test/java/rewards/internal/RewardNetworkImplTests.java +++ b/lab/10-spring-intro/src/test/java/rewards/internal/RewardNetworkImplTests.java @@ -19,63 +19,63 @@ * Configures the implementation with stub repositories * containing dummy data for fast in-memory testing without * the overhead of an external data source. - * + *

* Besides helping catch bugs early, tests are a great way * for a new developer to learn an API as he or she can see the * API in action. Tests also help validate a design as they * are a measure for how easy it is to use your code. */ -public class RewardNetworkImplTests { +class RewardNetworkImplTests { - /** - * The object being tested. - */ - private RewardNetworkImpl rewardNetwork; + /** + * The object being tested. + */ + RewardNetworkImpl rewardNetwork; - // TODO-09: Review the test setup - @BeforeEach - public void setUp() throws Exception { - // Create stubs to facilitate fast in-memory testing with - // dummy data and no external dependencies - AccountRepository accountRepo = new StubAccountRepository(); - RestaurantRepository restaurantRepo = new StubRestaurantRepository(); - RewardRepository rewardRepo = new StubRewardRepository(); + // TODO-09: Review the test setup + @BeforeEach + void setUp() { + // Create stubs to facilitate fast in-memory testing with + // dummy data and no external dependencies + AccountRepository accountRepo = new StubAccountRepository(); + RestaurantRepository restaurantRepo = new StubRestaurantRepository(); + RewardRepository rewardRepo = new StubRewardRepository(); - // Setup the object being tested by handing what it needs to work - rewardNetwork = new RewardNetworkImpl(accountRepo, restaurantRepo, rewardRepo); - } + // Setup the object being tested by handing what it needs to work + rewardNetwork = new RewardNetworkImpl(accountRepo, restaurantRepo, rewardRepo); + } - // TODO-10: Test RewardNetworkImpl class - // - Remove the @Disabled annotation below. - // - Run this JUnit test. Verify it passes. - @Test - @Disabled - public void testRewardForDining() { - // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input - Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); + // TODO-10: Test RewardNetworkImpl class + // - Remove the @Disabled annotation below. + // - Run this JUnit test. Verify it passes. + @Test + @Disabled + void testRewardForDining() { + // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input + Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); - // call the 'rewardNetwork' to test its rewardAccountFor(Dining) method - RewardConfirmation confirmation = rewardNetwork.rewardAccountFor(dining); + // call the 'rewardNetwork' to test its rewardAccountFor(Dining) method + RewardConfirmation confirmation = rewardNetwork.rewardAccountFor(dining); - // assert the expected reward confirmation results - assertNotNull(confirmation); - assertNotNull(confirmation.getConfirmationNumber()); + // assert the expected reward confirmation results + assertNotNull(confirmation); + assertNotNull(confirmation.confirmationNumber()); - // assert an account contribution was made - AccountContribution contribution = confirmation.getAccountContribution(); - assertNotNull(contribution); + // assert an account contribution was made + AccountContribution contribution = confirmation.accountContribution(); + assertNotNull(contribution); - // the account number should be '123456789' - assertEquals("123456789", contribution.getAccountNumber()); + // the account number should be '123456789' + assertEquals("123456789", contribution.accountNumber()); - // the total contribution amount should be 8.00 (8% of 100.00) - assertEquals(MonetaryAmount.valueOf("8.00"), contribution.getAmount()); + // the total contribution amount should be 8.00 (8% of 100.00) + assertEquals(MonetaryAmount.valueOf("8.00"), contribution.amount()); - // the total contribution amount should have been split into 2 distributions - assertEquals(2, contribution.getDistributions().size()); + // the total contribution amount should have been split into 2 distributions + assertEquals(2, contribution.distributions().size()); - // each distribution should be 4.00 (as both have a 50% allocation) - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").getAmount()); - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").getAmount()); - } + // each distribution should be 4.00 (as both have a 50% allocation) + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").amount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").amount()); + } } \ No newline at end of file diff --git a/lab/10-spring-intro/src/test/java/rewards/internal/StubAccountRepository.java b/lab/10-spring-intro/src/test/java/rewards/internal/StubAccountRepository.java index 5d229021..8aac7c37 100644 --- a/lab/10-spring-intro/src/test/java/rewards/internal/StubAccountRepository.java +++ b/lab/10-spring-intro/src/test/java/rewards/internal/StubAccountRepository.java @@ -11,31 +11,31 @@ /** * A dummy account repository implementation. Has a single Account "Keith and Keri Donald" with two beneficiaries * "Annabelle" (50% allocation) and "Corgan" (50% allocation) associated with credit card "1234123412341234". - * + *

* Stubs facilitate unit testing. An object needing an AccountRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubAccountRepository implements AccountRepository { - private Map accountsByCreditCard = new HashMap(); - - public StubAccountRepository() { - Account account = new Account("123456789", "Keith and Keri Donald"); - account.addBeneficiary("Annabelle", Percentage.valueOf("50%")); - account.addBeneficiary("Corgan", Percentage.valueOf("50%")); - accountsByCreditCard.put("1234123412341234", account); - } - - public Account findByCreditCard(String creditCardNumber) { - Account account = accountsByCreditCard.get(creditCardNumber); - if (account == null) { - throw new RuntimeException("no account has been found for credit card number " + creditCardNumber); - } - return account; - } - - public void updateBeneficiaries(Account account) { - // nothing to do, everything is in memory - } + Map accountsByCreditCard = new HashMap<>(); + + public StubAccountRepository() { + Account account = new Account("123456789", "Keith and Keri Donald"); + account.addBeneficiary("Annabelle", Percentage.valueOf("50%")); + account.addBeneficiary("Corgan", Percentage.valueOf("50%")); + accountsByCreditCard.put("1234123412341234", account); + } + + public Account findByCreditCard(String creditCardNumber) { + Account account = accountsByCreditCard.get(creditCardNumber); + if (account == null) { + throw new RuntimeException("no account has been found for credit card number " + creditCardNumber); + } + return account; + } + + public void updateBeneficiaries(Account account) { + // nothing to do, everything is in memory + } } \ No newline at end of file diff --git a/lab/10-spring-intro/src/test/java/rewards/internal/StubRestaurantRepository.java b/lab/10-spring-intro/src/test/java/rewards/internal/StubRestaurantRepository.java index 6a5873fe..f1704fa1 100644 --- a/lab/10-spring-intro/src/test/java/rewards/internal/StubRestaurantRepository.java +++ b/lab/10-spring-intro/src/test/java/rewards/internal/StubRestaurantRepository.java @@ -11,26 +11,26 @@ /** * A dummy restaurant repository implementation. Has a single restaurant "Apple Bees" with a 8% benefit availability * percentage that's always available. - * + *

* Stubs facilitate unit testing. An object needing a RestaurantRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubRestaurantRepository implements RestaurantRepository { - private Map restaurantsByMerchantNumber = new HashMap(); + Map restaurantsByMerchantNumber = new HashMap<>(); - public StubRestaurantRepository() { - Restaurant restaurant = new Restaurant("1234567890", "Apple Bees"); - restaurant.setBenefitPercentage(Percentage.valueOf("8%")); - restaurantsByMerchantNumber.put(restaurant.getNumber(), restaurant); - } + public StubRestaurantRepository() { + Restaurant restaurant = new Restaurant("1234567890", "Apple Bees"); + restaurant.setBenefitPercentage(Percentage.valueOf("8%")); + restaurantsByMerchantNumber.put(restaurant.getNumber(), restaurant); + } - public Restaurant findByMerchantNumber(String merchantNumber) { - Restaurant restaurant = (Restaurant) restaurantsByMerchantNumber.get(merchantNumber); - if (restaurant == null) { - throw new RuntimeException("no restaurant has been found for merchant number " + merchantNumber); - } - return restaurant; - } + public Restaurant findByMerchantNumber(String merchantNumber) { + Restaurant restaurant = restaurantsByMerchantNumber.get(merchantNumber); + if (restaurant == null) { + throw new RuntimeException("no restaurant has been found for merchant number " + merchantNumber); + } + return restaurant; + } } \ No newline at end of file diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/config/RewardsConfig.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/config/RewardsConfig.java index 2a935f73..4520df3f 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/config/RewardsConfig.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/config/RewardsConfig.java @@ -1,10 +1,7 @@ package config; -import javax.sql.DataSource; - import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - import rewards.RewardNetwork; import rewards.internal.RewardNetworkImpl; import rewards.internal.account.AccountRepository; @@ -14,42 +11,44 @@ import rewards.internal.reward.JdbcRewardRepository; import rewards.internal.reward.RewardRepository; +import javax.sql.DataSource; + @Configuration public class RewardsConfig { - private DataSource dataSource; - - // As this is the only constructor, @Autowired is not needed. - public RewardsConfig(DataSource dataSource) { - this.dataSource = dataSource; - } - - @Bean - public RewardNetwork rewardNetwork(){ - return new RewardNetworkImpl( - accountRepository(), - restaurantRepository(), - rewardRepository()); - } - - @Bean - public AccountRepository accountRepository(){ - JdbcAccountRepository repository = new JdbcAccountRepository(); - repository.setDataSource(dataSource); - return repository; - } - - @Bean - public RestaurantRepository restaurantRepository(){ - JdbcRestaurantRepository repository = new JdbcRestaurantRepository(); - repository.setDataSource(dataSource); - return repository; - } - - @Bean - public RewardRepository rewardRepository(){ - JdbcRewardRepository repository = new JdbcRewardRepository(); - repository.setDataSource(dataSource); - return repository; - } - + private final DataSource dataSource; + + // As this is the only constructor, @Autowired is not needed. + public RewardsConfig(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Bean + RewardNetwork rewardNetwork() { + return new RewardNetworkImpl( + accountRepository(), + restaurantRepository(), + rewardRepository()); + } + + @Bean + AccountRepository accountRepository() { + JdbcAccountRepository repository = new JdbcAccountRepository(); + repository.setDataSource(dataSource); + return repository; + } + + @Bean + RestaurantRepository restaurantRepository() { + JdbcRestaurantRepository repository = new JdbcRestaurantRepository(); + repository.setDataSource(dataSource); + return repository; + } + + @Bean + RewardRepository rewardRepository() { + JdbcRewardRepository repository = new JdbcRewardRepository(); + repository.setDataSource(dataSource); + return repository; + } + } diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/AccountContribution.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/AccountContribution.java index 5cad1918..b68c7512 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/AccountContribution.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/AccountContribution.java @@ -1,61 +1,20 @@ package rewards; -import java.util.Set; - import common.money.MonetaryAmount; import common.money.Percentage; +import java.util.Set; + /** * A summary of a monetary contribution made to an account that was distributed among the account's beneficiaries. - * + *

* A value object. Immutable. */ -public class AccountContribution { - - private String accountNumber; - - private MonetaryAmount amount; - - private Set distributions; - - /** - * Creates a new account contribution. - * @param accountNumber the number of the account the contribution was made - * @param amount the total contribution amount - * @param distributions how the contribution was distributed among the account's beneficiaries - */ - public AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { - this.accountNumber = accountNumber; - this.amount = amount; - this.distributions = distributions; - } - - /** - * Returns the number of the account this contribution was made to. - * @return the account number - */ - public String getAccountNumber() { - return accountNumber; - } - - /** - * Returns the total amount of the contribution. - * @return the contribution amount - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns how this contribution was distributed among the account's beneficiaries. - * @return the contribution distributions - */ - public Set getDistributions() { - return distributions; - } +public record AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { /** * Returns how this contribution was distributed to a single account beneficiary. + * * @param beneficiary the name of the beneficiary e.g "Annabelle" * @return a summary of how the contribution amount was distributed to the beneficiary */ @@ -71,61 +30,11 @@ public Distribution getDistribution(String beneficiary) { /** * A single distribution made to a beneficiary as part of an account contribution, summarizing the distribution * amount and resulting total beneficiary savings. - * + *

* A value object. */ - public static class Distribution { - - private String beneficiary; - - private MonetaryAmount amount; - - private Percentage percentage; - - private MonetaryAmount totalSavings; - - /** - * Creates a new distribution. - * @param beneficiary the name of the account beneficiary that received a distribution - * @param amount the distribution amount - * @param percentage this distribution's percentage of the total account contribution - * @param totalSavings the beneficiary's total savings amount after the distribution was made - */ - public Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, - MonetaryAmount totalSavings) { - this.beneficiary = beneficiary; - this.percentage = percentage; - this.amount = amount; - this.totalSavings = totalSavings; - } - - /** - * Returns the name of the beneficiary. - */ - public String getBeneficiary() { - return beneficiary; - } - - /** - * Returns the amount of this distribution. - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns the percentage of this distribution relative to others in the contribution. - */ - public Percentage getPercentage() { - return percentage; - } - - /** - * Returns the total savings of the beneficiary after this distribution. - */ - public MonetaryAmount getTotalSavings() { - return totalSavings; - } + public record Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, + MonetaryAmount totalSavings) { public String toString() { return amount + " to '" + beneficiary + "' (" + percentage + ")"; diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/Dining.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/Dining.java index 478463c3..379762bf 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/Dining.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/Dining.java @@ -3,44 +3,25 @@ import common.datetime.SimpleDate; import common.money.MonetaryAmount; +import java.io.Serializable; + /** * A dining event that occurred, representing a charge made to an credit card by a merchant on a specific date. - * * For a dining to be eligible for reward, the credit card number should map to an account in the reward network. In * addition, the merchant number should map to a restaurant in the network. - * * A value object. Immutable. */ -public class Dining { - - private MonetaryAmount amount; - - private String creditCardNumber; - - private String merchantNumber; +public record Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, + SimpleDate date) implements Serializable { - private SimpleDate date; - - /** - * Creates a new dining, reflecting an amount that was charged to a card by a merchant on the date specified. - * @param amount the total amount of the dining bill - * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param date the date of the dining event - */ - public Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, SimpleDate date) { - this.amount = amount; - this.creditCardNumber = creditCardNumber; - this.merchantNumber = merchantNumber; - this.date = date; - } /** * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on today's date. A * convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param merchantNumber the merchant number of the restaurant where the dining occurred * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber) { @@ -50,65 +31,34 @@ public static Dining createDining(String amount, String creditCardNumber, String /** * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on the date specified. * A convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param month the month of the dining event - * @param day the day of the dining event - * @param year the year of the dining event + * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param month the month of the dining event + * @param day the day of the dining event + * @param year the year of the dining event * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber, int month, - int day, int year) { + int day, int year) { return new Dining(MonetaryAmount.valueOf(amount), creditCardNumber, merchantNumber, new SimpleDate(month, day, year)); } - /** - * Returns the amount of this dining--the total amount of the bill that was charged to the credit card. - */ - public MonetaryAmount getAmount() { - return amount; + public String toString() { + return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; } - /** - * Returns the number of the credit card used to pay for this dining. For this dining to be eligible for reward, - * this credit card number should be associated with a valid account in the reward network. - */ public String getCreditCardNumber() { return creditCardNumber; } - /** - * Returns the merchant number of the restaurant where this dining occurred. For this dining to be eligible for - * reward, this merchant number should be associated with a valid restaurant in the reward network. - */ public String getMerchantNumber() { return merchantNumber; } - /** - * Returns the date this dining occurred on. - */ - public SimpleDate getDate() { - return date; - } - - public boolean equals(Object o) { - if (!(o instanceof Dining)) { - return false; - } - Dining other = (Dining) o; - // value objects are equal if their attributes are equal - return amount.equals(other.amount) && creditCardNumber.equals(other.creditCardNumber) - && merchantNumber.equals(other.merchantNumber) && date.equals(other.date); - } - - public int hashCode() { - return amount.hashCode() + creditCardNumber.hashCode() + merchantNumber.hashCode() + date.hashCode(); - } - - public String toString() { - return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; + public MonetaryAmount getAmount() { + return amount; } } \ No newline at end of file diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/RewardConfirmation.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/RewardConfirmation.java index c6984dcd..43ec595a 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/RewardConfirmation.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/RewardConfirmation.java @@ -1,39 +1,13 @@ package rewards; +import java.io.Serializable; + /** * A summary of a confirmed reward transaction describing a contribution made to an account that was distributed among * the account's beneficiaries. */ -public class RewardConfirmation { - - private String confirmationNumber; - - private AccountContribution accountContribution; - - /** - * Creates a new reward confirmation. - * @param confirmationNumber the unique confirmation number - * @param accountContribution a summary of the account contribution that was made - */ - public RewardConfirmation(String confirmationNumber, AccountContribution accountContribution) { - this.confirmationNumber = confirmationNumber; - this.accountContribution = accountContribution; - } - - /** - * Returns the confirmation number of the reward transaction. Can be used later to lookup the transaction record. - */ - public String getConfirmationNumber() { - return confirmationNumber; - } - - /** - * Returns a summary of the monetary contribution that was made to an account. - * @return the account contribution (the details of this reward) - */ - public AccountContribution getAccountContribution() { - return accountContribution; - } +public record RewardConfirmation(String confirmationNumber, + AccountContribution accountContribution) implements Serializable { public String toString() { return confirmationNumber; diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/RewardNetwork.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/RewardNetwork.java index 252fe280..1cb8d831 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/RewardNetwork.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/RewardNetwork.java @@ -2,14 +2,14 @@ /** * Rewards a member account for dining at a restaurant. - * + *

* A reward takes the form of a monetary contribution made to an account that is distributed among the account's * beneficiaries. The contribution amount is typically a function of several factors such as the dining amount and * restaurant where the dining occurred. - * + *

* Example: Papa Keith spends $100.00 at Apple Bee's resulting in a $8.00 contribution to his account that is * distributed evenly among his beneficiaries Annabelle and Corgan. - * + *

* This is the central application-boundary for the "rewards" application. This is the public interface users call to * invoke the application. This is the entry-point into the Application Layer. */ @@ -17,12 +17,12 @@ public interface RewardNetwork { /** * Reward an account for dining. - * + *

* For a dining to be eligible for reward: - It must have been paid for by a registered credit card of a valid * member account in the network. - It must have taken place at a restaurant participating in the network. - * + * * @param dining a charge made to a credit card for dining at a restaurant * @return confirmation of the reward */ - public RewardConfirmation rewardAccountFor(Dining dining); + RewardConfirmation rewardAccountFor(Dining dining); } \ No newline at end of file diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/RewardNetworkImpl.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/RewardNetworkImpl.java index a02a23f5..4166aa90 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/RewardNetworkImpl.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/RewardNetworkImpl.java @@ -1,5 +1,6 @@ package rewards.internal; +import common.money.MonetaryAmount; import rewards.AccountContribution; import rewards.Dining; import rewards.RewardConfirmation; @@ -10,32 +11,31 @@ import rewards.internal.restaurant.RestaurantRepository; import rewards.internal.reward.RewardRepository; -import common.money.MonetaryAmount; - /** * Rewards an Account for Dining at a Restaurant. - * + *

* The sole Reward Network implementation. This object is an application-layer service responsible for coordinating with * the domain-layer to carry out the process of rewarding benefits to accounts for dining. - * + *

* Said in other words, this class implements the "reward account for dining" use case. */ public class RewardNetworkImpl implements RewardNetwork { - private AccountRepository accountRepository; + private final AccountRepository accountRepository; - private RestaurantRepository restaurantRepository; + private final RestaurantRepository restaurantRepository; - private RewardRepository rewardRepository; + private final RewardRepository rewardRepository; /** * Creates a new reward network. - * @param accountRepository the repository for loading accounts to reward + * + * @param accountRepository the repository for loading accounts to reward * @param restaurantRepository the repository for loading restaurants that determine how much to reward - * @param rewardRepository the repository for recording a record of successful reward transactions + * @param rewardRepository the repository for recording a record of successful reward transactions */ public RewardNetworkImpl(AccountRepository accountRepository, RestaurantRepository restaurantRepository, - RewardRepository rewardRepository) { + RewardRepository rewardRepository) { this.accountRepository = accountRepository; this.restaurantRepository = restaurantRepository; this.rewardRepository = rewardRepository; diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/Account.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/Account.java index eb685069..9b48c01f 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/Account.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/Account.java @@ -1,23 +1,22 @@ package rewards.internal.account; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import rewards.AccountContribution; -import rewards.AccountContribution.Distribution; - import common.money.MonetaryAmount; import common.money.Percentage; import common.repository.Entity; +import rewards.AccountContribution; +import rewards.AccountContribution.Distribution; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; /** * An account for a member of the reward network. An account has one or more beneficiaries whose allocations must add up * to 100%. - * + *

* An account can make contributions to its beneficiaries. Each contribution is distributed among the beneficiaries * based on an allocation. - * + *

* An entity. An aggregate. */ public class Account extends Entity { @@ -26,7 +25,7 @@ public class Account extends Entity { private String name; - private Set beneficiaries = new HashSet(); + private final Set beneficiaries = new HashSet<>(); @SuppressWarnings("unused") private Account() { @@ -34,8 +33,9 @@ private Account() { /** * Create a new account. + * * @param number the account number - * @param name the name on the account + * @param name the name on the account */ public Account(String number, String name) { this.number = number; @@ -58,6 +58,7 @@ public String getName() { /** * Add a single beneficiary with a 100% allocation percentage. + * * @param beneficiaryName the name of the beneficiary (should be unique) */ public void addBeneficiary(String beneficiaryName) { @@ -66,7 +67,8 @@ public void addBeneficiary(String beneficiaryName) { /** * Add a single beneficiary with the specified allocation percentage. - * @param beneficiaryName the name of the beneficiary (should be unique) + * + * @param beneficiaryName the name of the beneficiary (should be unique) * @param allocationPercentage the beneficiary's allocation percentage within this account */ public void addBeneficiary(String beneficiaryName, Percentage allocationPercentage) { @@ -81,16 +83,13 @@ public boolean isValid() { for (Beneficiary b : beneficiaries) { totalPercentage = totalPercentage.add(b.getAllocationPercentage()); } - if (totalPercentage.equals(Percentage.oneHundred())) { - return true; - } else { - return false; - } + return totalPercentage.equals(Percentage.oneHundred()); } /** * Make a monetary contribution to this account. The contribution amount is distributed among the account's * beneficiaries based on each beneficiary's allocation percentage. + * * @param amount the total amount to contribute */ public AccountContribution makeContribution(MonetaryAmount amount) { @@ -104,11 +103,12 @@ public AccountContribution makeContribution(MonetaryAmount amount) { /** * Distribute the contribution amount among this account's beneficiaries. + * * @param amount the total contribution amount * @return the individual beneficiary distributions */ private Set distribute(MonetaryAmount amount) { - Set distributions = new HashSet(beneficiaries.size()); + Set distributions = HashSet.newHashSet(beneficiaries.size()); for (Beneficiary beneficiary : beneficiaries) { MonetaryAmount distributionAmount = amount.multiplyBy(beneficiary.getAllocationPercentage()); beneficiary.credit(distributionAmount); @@ -124,6 +124,7 @@ private Set distribute(MonetaryAmount amount) { *

* Callers should not attempt to hold on or modify the returned set. This method should only be used transitively; * for example, called to facilitate account reporting. + * * @return the beneficiaries of this account */ public Set getBeneficiaries() { @@ -133,6 +134,7 @@ public Set getBeneficiaries() { /** * Used to restore an allocated beneficiary. Should only be called by the repository responsible for reconstituting * this account. + * * @param beneficiary the beneficiary */ void restoreBeneficiary(Beneficiary beneficiary) { diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/AccountRepository.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/AccountRepository.java index e9f6b8fd..7c6bba59 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/AccountRepository.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/AccountRepository.java @@ -3,18 +3,18 @@ /** * Loads account aggregates. Called by the reward network to find and reconstitute Account entities from an external * form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. - * */ public interface AccountRepository { /** * Load an account by its credit card. + * * @param creditCardNumber the credit card number * @return the account object */ - public Account findByCreditCard(String creditCardNumber); + Account findByCreditCard(String creditCardNumber); /** * Updates the 'savings' of each account beneficiary. The new savings balance contains the amount distributed for a @@ -23,8 +23,9 @@ public interface AccountRepository { * Note: use of an object-relational mapper (ORM) with support for transparent-persistence like Hibernate (or the * new Java Persistence API (JPA)) would remove the need for this explicit update operation as the ORM would take * care of applying relational updates to a modified Account entity automatically. + * * @param account the account whose beneficiary savings have changed */ - public void updateBeneficiaries(Account account); + void updateBeneficiaries(Account account); } \ No newline at end of file diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/JdbcAccountRepository.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/JdbcAccountRepository.java index 13be1fe8..4605a1f8 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/JdbcAccountRepository.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/account/JdbcAccountRepository.java @@ -1,17 +1,15 @@ package rewards.internal.account; +import common.money.MonetaryAmount; +import common.money.Percentage; +import org.springframework.dao.EmptyResultDataAccessException; + import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; - import javax.sql.DataSource; -import org.springframework.dao.EmptyResultDataAccessException; - -import common.money.MonetaryAmount; -import common.money.Percentage; - /** * Loads accounts from a data source using the JDBC API. */ @@ -30,7 +28,7 @@ public void setDataSource(DataSource dataSource) { public Account findByCreditCard(String creditCardNumber) { String sql = "select a.ID as ID, a.NUMBER as ACCOUNT_NUMBER, a.NAME as ACCOUNT_NAME, c.NUMBER as CREDIT_CARD_NUMBER, " + - " b.NAME as BENEFICIARY_NAME, b.ALLOCATION_PERCENTAGE as BENEFICIARY_ALLOCATION_PERCENTAGE, b.SAVINGS as BENEFICIARY_SAVINGS " + + "b.NAME as BENEFICIARY_NAME, b.ALLOCATION_PERCENTAGE as BENEFICIARY_ALLOCATION_PERCENTAGE, b.SAVINGS as BENEFICIARY_SAVINGS " + "from T_ACCOUNT a, T_ACCOUNT_CREDIT_CARD c " + "left outer join T_ACCOUNT_BENEFICIARY b " + "on a.ID = b.ACCOUNT_ID " + diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java index 6e658157..597a08c8 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java @@ -1,16 +1,14 @@ package rewards.internal.restaurant; +import common.money.Percentage; +import org.springframework.dao.EmptyResultDataAccessException; + import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; - import javax.sql.DataSource; -import org.springframework.dao.EmptyResultDataAccessException; - -import common.money.Percentage; - /** * Loads restaurants from a data source using the JDBC API. */ @@ -28,7 +26,7 @@ public void setDataSource(DataSource dataSource) { public Restaurant findByMerchantNumber(String merchantNumber) { String sql = "select MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE from T_RESTAURANT where MERCHANT_NUMBER = ?"; - Restaurant restaurant = null; + Restaurant restaurant; Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; @@ -86,7 +84,7 @@ private Restaurant mapRestaurant(ResultSet rs) throws SQLException { * 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 + * @throws SQLException if there is a problem advancing the ResultSet */ private void advanceToNextRow(ResultSet rs) throws EmptyResultDataAccessException, SQLException { if (!rs.next()) { diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java index a632fcc1..04968ac5 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java @@ -3,15 +3,16 @@ /** * Loads restaurant aggregates. Called by the reward network to find and reconstitute Restaurant entities from an * external form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. */ public interface RestaurantRepository { /** * Load a Restaurant entity by its merchant number. + * * @param merchantNumber the merchant number * @return the restaurant */ - public Restaurant findByMerchantNumber(String merchantNumber); + Restaurant findByMerchantNumber(String merchantNumber); } diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/reward/JdbcRewardRepository.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/reward/JdbcRewardRepository.java index c20a0128..35ae1cfb 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/reward/JdbcRewardRepository.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/reward/JdbcRewardRepository.java @@ -5,8 +5,12 @@ import rewards.Dining; import rewards.RewardConfirmation; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import javax.sql.DataSource; -import java.sql.*; /** * JDBC implementation of a reward repository that records the result of a reward transaction by inserting a reward @@ -33,11 +37,11 @@ public RewardConfirmation confirmReward(AccountContribution contribution, Dining ps = conn.prepareStatement(sql); String confirmationNumber = nextConfirmationNumber(); ps.setString(1, confirmationNumber); - ps.setBigDecimal(2, contribution.getAmount().asBigDecimal()); + ps.setBigDecimal(2, contribution.amount().asBigDecimal()); ps.setDate(3, new Date(SimpleDate.today().inMilliseconds())); - ps.setString(4, contribution.getAccountNumber()); + ps.setString(4, contribution.accountNumber()); ps.setString(5, dining.getMerchantNumber()); - ps.setDate(6, new Date(dining.getDate().inMilliseconds())); + ps.setDate(6, new Date(dining.date().inMilliseconds())); ps.setBigDecimal(7, dining.getAmount().asBigDecimal()); ps.execute(); return new RewardConfirmation(confirmationNumber, contribution); diff --git a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/reward/RewardRepository.java b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/reward/RewardRepository.java index c8189e52..e32368d2 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/reward/RewardRepository.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/main/java/rewards/internal/reward/RewardRepository.java @@ -9,12 +9,13 @@ */ public interface RewardRepository { - /** - * Create a record of a reward that will track a contribution made to an account for dining. - * @param contribution the account contribution that was made - * @param dining the dining event that resulted in the account contribution - * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later - * date - */ - public RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); + /** + * Create a record of a reward that will track a contribution made to an account for dining. + * + * @param contribution the account contribution that was made + * @param dining the dining event that resulted in the account contribution + * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later + * date + */ + RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); } \ No newline at end of file diff --git a/lab/12-javaconfig-dependency-injection-solution/src/test/java/config/RewardsConfigTests.java b/lab/12-javaconfig-dependency-injection-solution/src/test/java/config/RewardsConfigTests.java index 8128d89e..8796af0f 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/test/java/config/RewardsConfigTests.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/test/java/config/RewardsConfigTests.java @@ -1,16 +1,8 @@ package config; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.lang.reflect.Field; - -import javax.sql.DataSource; - import org.assertj.core.api.Fail; import org.junit.jupiter.api.Test; import org.mockito.Mockito; - import rewards.RewardNetwork; import rewards.internal.RewardNetworkImpl; import rewards.internal.account.AccountRepository; @@ -20,31 +12,37 @@ import rewards.internal.reward.JdbcRewardRepository; import rewards.internal.reward.RewardRepository; +import java.lang.reflect.Field; +import javax.sql.DataSource; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + /** * Unit test the Spring configuration class to ensure it is creating the right * beans. */ -public class RewardsConfigTests { +class RewardsConfigTests { // Provide a mock for testing - private DataSource dataSource = Mockito.mock(DataSource.class); + DataSource dataSource = Mockito.mock(DataSource.class); - private RewardsConfig rewardsConfig = new RewardsConfig(dataSource); + RewardsConfig rewardsConfig = new RewardsConfig(dataSource); @Test - public void getBeans() { + void getBeans() { RewardNetwork rewardNetwork = rewardsConfig.rewardNetwork(); - assertTrue(rewardNetwork instanceof RewardNetworkImpl); + assertInstanceOf(RewardNetworkImpl.class, rewardNetwork); AccountRepository accountRepository = rewardsConfig.accountRepository(); - assertTrue(accountRepository instanceof JdbcAccountRepository); + assertInstanceOf(JdbcAccountRepository.class, accountRepository); checkDataSource(accountRepository); RestaurantRepository restaurantRepository = rewardsConfig.restaurantRepository(); - assertTrue(restaurantRepository instanceof JdbcRestaurantRepository); + assertInstanceOf(JdbcRestaurantRepository.class, restaurantRepository); checkDataSource(restaurantRepository); RewardRepository rewardsRepository = rewardsConfig.rewardRepository(); - assertTrue(rewardsRepository instanceof JdbcRewardRepository); + assertInstanceOf(JdbcRewardRepository.class, rewardsRepository); checkDataSource(rewardsRepository); } @@ -52,15 +50,15 @@ public void getBeans() { * Ensure the data-source is set for the repository. Uses reflection as we do * not wish to provide a getDataSource() method. * - * @param repository + * @param repository One of our three repositories. */ private void checkDataSource(Object repository) { - Class repositoryClass = repository.getClass(); + Class repositoryClass = repository.getClass(); try { - Field dataSource = repositoryClass.getDeclaredField("dataSource"); - dataSource.setAccessible(true); - assertNotNull(dataSource.get(repository)); + Field source = repositoryClass.getDeclaredField("dataSource"); + source.setAccessible(true); + assertNotNull(source.get(repository)); } catch (Exception e) { String failureMessage = "Unable to validate dataSource in " + repositoryClass.getSimpleName(); System.out.println(failureMessage); diff --git a/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/RewardNetworkTests.java b/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/RewardNetworkTests.java index fd100772..28a007f5 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/RewardNetworkTests.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/RewardNetworkTests.java @@ -1,29 +1,27 @@ package rewards; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - +import common.money.MonetaryAmount; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; -import common.money.MonetaryAmount; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * A system test that verifies the components of the RewardNetwork application work together to reward for dining * successfully. Uses Spring to bootstrap the application for use in a test environment. */ -public class RewardNetworkTests { +class RewardNetworkTests { /** * The object being tested. */ - private RewardNetwork rewardNetwork; + RewardNetwork rewardNetwork; @BeforeEach - public void setUp() { + void setUp() { // Create the test configuration for the application: ApplicationContext context = SpringApplication.run(TestInfrastructureConfig.class); @@ -33,7 +31,7 @@ public void setUp() { } @Test - public void testRewardForDining() { + void testRewardForDining() { // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); @@ -43,23 +41,23 @@ public void testRewardForDining() { // assert the expected reward confirmation results assertNotNull(confirmation); - assertNotNull(confirmation.getConfirmationNumber()); + assertNotNull(confirmation.confirmationNumber()); // assert an account contribution was made - AccountContribution contribution = confirmation.getAccountContribution(); + AccountContribution contribution = confirmation.accountContribution(); assertNotNull(contribution); // the contribution account number should be '123456789' - assertEquals("123456789", contribution.getAccountNumber()); + assertEquals("123456789", contribution.accountNumber()); // the total contribution amount should be 8.00 (8% of 100.00) - assertEquals(MonetaryAmount.valueOf("8.00"), contribution.getAmount()); + assertEquals(MonetaryAmount.valueOf("8.00"), contribution.amount()); // the total contribution amount should have been split into 2 distributions - assertEquals(2, contribution.getDistributions().size()); + assertEquals(2, contribution.distributions().size()); // each distribution should be 4.00 (as both have a 50% allocation) - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").getAmount()); - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").getAmount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").amount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").amount()); } } diff --git a/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/TestInfrastructureConfig.java b/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/TestInfrastructureConfig.java index bd29e01b..41523b1b 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/TestInfrastructureConfig.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/TestInfrastructureConfig.java @@ -11,18 +11,17 @@ @Configuration @Import(RewardsConfig.class) -public class TestInfrastructureConfig { +class TestInfrastructureConfig { - /** - * Creates an in-memory "rewards" database populated - * with test data for fast testing - */ - @Bean - public DataSource dataSource(){ - return - (new EmbeddedDatabaseBuilder()) - .addScript("classpath:rewards/testdb/schema.sql") - .addScript("classpath:rewards/testdb/data.sql") - .build(); - } + /** + * Creates an in-memory "rewards" database populated + * with test data for fast testing + */ + @Bean + DataSource dataSource() { + return new EmbeddedDatabaseBuilder() + .addScript("classpath:rewards/testdb/schema.sql") + .addScript("classpath:rewards/testdb/data.sql") + .build(); + } } diff --git a/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java b/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java index 847bfafc..82ff840a 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java @@ -1,11 +1,8 @@ package rewards.internal; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - +import common.money.MonetaryAmount; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import rewards.AccountContribution; import rewards.Dining; import rewards.RewardConfirmation; @@ -13,16 +10,17 @@ import rewards.internal.restaurant.RestaurantRepository; import rewards.internal.reward.RewardRepository; -import common.money.MonetaryAmount; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Unit tests for the RewardNetworkImpl application logic. Configures the implementation with stub repositories * containing dummy data for fast in-memory testing without the overhead of an external data source. - * + *

* Besides helping catch bugs early, tests are a great way for a new developer to learn an API as he or she can see the * API in action. Tests also help validate a design as they are a measure for how easy it is to use your code. */ -public class RewardNetworkImplTests { +class RewardNetworkImplTests { /** * The object being tested. @@ -30,7 +28,7 @@ public class RewardNetworkImplTests { private RewardNetworkImpl rewardNetwork; @BeforeEach - public void setUp() throws Exception { + void setUp() { // create stubs to facilitate fast in-memory testing with dummy data and no external dependencies AccountRepository accountRepo = new StubAccountRepository(); RestaurantRepository restaurantRepo = new StubRestaurantRepository(); @@ -41,7 +39,7 @@ public void setUp() throws Exception { } @Test - public void testRewardForDining() { + void testRewardForDining() { // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); @@ -50,23 +48,23 @@ public void testRewardForDining() { // assert the expected reward confirmation results assertNotNull(confirmation); - assertNotNull(confirmation.getConfirmationNumber()); + assertNotNull(confirmation.confirmationNumber()); // assert an account contribution was made - AccountContribution contribution = confirmation.getAccountContribution(); + AccountContribution contribution = confirmation.accountContribution(); assertNotNull(contribution); // the account number should be '123456789' - assertEquals("123456789", contribution.getAccountNumber()); + assertEquals("123456789", contribution.accountNumber()); // the total contribution amount should be 8.00 (8% of 100.00) - assertEquals(MonetaryAmount.valueOf("8.00"), contribution.getAmount()); + assertEquals(MonetaryAmount.valueOf("8.00"), contribution.amount()); // the total contribution amount should have been split into 2 distributions - assertEquals(2, contribution.getDistributions().size()); + assertEquals(2, contribution.distributions().size()); // each distribution should be 4.00 (as both have a 50% allocation) - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").getAmount()); - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").getAmount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").amount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").amount()); } -} \ No newline at end of file +} diff --git a/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/StubAccountRepository.java b/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/StubAccountRepository.java index 66ca1dcd..4931f4fc 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/StubAccountRepository.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/StubAccountRepository.java @@ -1,26 +1,23 @@ package rewards.internal; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.dao.EmptyResultDataAccessException; - +import common.money.Percentage; import rewards.internal.account.Account; import rewards.internal.account.AccountRepository; -import common.money.Percentage; +import java.util.HashMap; +import java.util.Map; /** * A dummy account repository implementation. Has a single Account "Keith and Keri Donald" with two beneficiaries * "Annabelle" (50% allocation) and "Corgan" (50% allocation) associated with credit card "1234123412341234". - * + *

* Stubs facilitate unit testing. An object needing an AccountRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubAccountRepository implements AccountRepository { - private Map accountsByCreditCard = new HashMap(); + Map accountsByCreditCard = new HashMap<>(); public StubAccountRepository() { Account account = new Account("123456789", "Keith and Keri Donald"); @@ -32,7 +29,7 @@ public StubAccountRepository() { public Account findByCreditCard(String creditCardNumber) { Account account = accountsByCreditCard.get(creditCardNumber); if (account == null) { - throw new EmptyResultDataAccessException(1); + throw new RuntimeException("no account has been found for credit card number " + creditCardNumber); } return account; } diff --git a/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/StubRestaurantRepository.java b/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/StubRestaurantRepository.java index 92375baa..e568a405 100644 --- a/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/StubRestaurantRepository.java +++ b/lab/12-javaconfig-dependency-injection-solution/src/test/java/rewards/internal/StubRestaurantRepository.java @@ -1,26 +1,23 @@ package rewards.internal; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.dao.EmptyResultDataAccessException; - +import common.money.Percentage; import rewards.internal.restaurant.Restaurant; import rewards.internal.restaurant.RestaurantRepository; -import common.money.Percentage; +import java.util.HashMap; +import java.util.Map; /** * A dummy restaurant repository implementation. Has a single restaurant "Apple Bees" with a 8% benefit availability * percentage that's always available. - * + *

* Stubs facilitate unit testing. An object needing a RestaurantRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubRestaurantRepository implements RestaurantRepository { - private Map restaurantsByMerchantNumber = new HashMap(); + Map restaurantsByMerchantNumber = new HashMap<>(); public StubRestaurantRepository() { Restaurant restaurant = new Restaurant("1234567890", "Apple Bees"); @@ -29,9 +26,9 @@ public StubRestaurantRepository() { } public Restaurant findByMerchantNumber(String merchantNumber) { - Restaurant restaurant = (Restaurant) restaurantsByMerchantNumber.get(merchantNumber); + Restaurant restaurant = restaurantsByMerchantNumber.get(merchantNumber); if (restaurant == null) { - throw new EmptyResultDataAccessException(1); + throw new RuntimeException("no restaurant has been found for merchant number " + merchantNumber); } return restaurant; } diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/AccountContribution.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/AccountContribution.java index 5cad1918..b7cd5254 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/AccountContribution.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/AccountContribution.java @@ -7,55 +7,14 @@ /** * A summary of a monetary contribution made to an account that was distributed among the account's beneficiaries. - * + *

* A value object. Immutable. */ -public class AccountContribution { - - private String accountNumber; - - private MonetaryAmount amount; - - private Set distributions; - - /** - * Creates a new account contribution. - * @param accountNumber the number of the account the contribution was made - * @param amount the total contribution amount - * @param distributions how the contribution was distributed among the account's beneficiaries - */ - public AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { - this.accountNumber = accountNumber; - this.amount = amount; - this.distributions = distributions; - } - - /** - * Returns the number of the account this contribution was made to. - * @return the account number - */ - public String getAccountNumber() { - return accountNumber; - } - - /** - * Returns the total amount of the contribution. - * @return the contribution amount - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns how this contribution was distributed among the account's beneficiaries. - * @return the contribution distributions - */ - public Set getDistributions() { - return distributions; - } +public record AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { /** * Returns how this contribution was distributed to a single account beneficiary. + * * @param beneficiary the name of the beneficiary e.g "Annabelle" * @return a summary of how the contribution amount was distributed to the beneficiary */ @@ -71,61 +30,11 @@ public Distribution getDistribution(String beneficiary) { /** * A single distribution made to a beneficiary as part of an account contribution, summarizing the distribution * amount and resulting total beneficiary savings. - * + *

* A value object. */ - public static class Distribution { - - private String beneficiary; - - private MonetaryAmount amount; - - private Percentage percentage; - - private MonetaryAmount totalSavings; - - /** - * Creates a new distribution. - * @param beneficiary the name of the account beneficiary that received a distribution - * @param amount the distribution amount - * @param percentage this distribution's percentage of the total account contribution - * @param totalSavings the beneficiary's total savings amount after the distribution was made - */ - public Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, - MonetaryAmount totalSavings) { - this.beneficiary = beneficiary; - this.percentage = percentage; - this.amount = amount; - this.totalSavings = totalSavings; - } - - /** - * Returns the name of the beneficiary. - */ - public String getBeneficiary() { - return beneficiary; - } - - /** - * Returns the amount of this distribution. - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns the percentage of this distribution relative to others in the contribution. - */ - public Percentage getPercentage() { - return percentage; - } - - /** - * Returns the total savings of the beneficiary after this distribution. - */ - public MonetaryAmount getTotalSavings() { - return totalSavings; - } + public record Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, + MonetaryAmount totalSavings) { public String toString() { return amount + " to '" + beneficiary + "' (" + percentage + ")"; diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/Dining.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/Dining.java index c9b8dcf2..54e68a76 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/Dining.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/Dining.java @@ -1,46 +1,27 @@ package rewards; +import java.io.Serializable; + import common.datetime.SimpleDate; import common.money.MonetaryAmount; /** * A dining event that occurred, representing a charge made to an credit card by a merchant on a specific date. - * * For a dining to be eligible for reward, the credit card number should map to an account in the reward network. In * addition, the merchant number should map to a restaurant in the network. - * * A value object. Immutable. */ -public class Dining { - - private MonetaryAmount amount; - - private String creditCardNumber; - - private String merchantNumber; +public record Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, + SimpleDate date) implements Serializable { - private SimpleDate date; - - /** - * Creates a new dining, reflecting an amount that was charged to a card by a restaurant on the date specified. - * @param amount the total amount of the dining bill - * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param date the date of the dining event - */ - public Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, SimpleDate date) { - this.amount = amount; - this.creditCardNumber = creditCardNumber; - this.merchantNumber = merchantNumber; - this.date = date; - } /** - * Creates a new dining, reflecting an amount that was charged to a credit card by a restaurant on today's date. A + * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on today's date. A * convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param merchantNumber the merchant number of the restaurant where the dining occurred * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber) { @@ -48,67 +29,36 @@ public static Dining createDining(String amount, String creditCardNumber, String } /** - * Creates a new dining, reflecting an amount that was charged to a credit card by a restaurant on the date - * specified. A convenient static factory method. - * @param amount the total amount of the dining bill as a string + * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on the date specified. + * A convenient static factory method. + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param month the month of the dining event - * @param day the day of the dining event - * @param year the year of the dining event + * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param month the month of the dining event + * @param day the day of the dining event + * @param year the year of the dining event * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber, int month, - int day, int year) { + int day, int year) { return new Dining(MonetaryAmount.valueOf(amount), creditCardNumber, merchantNumber, new SimpleDate(month, day, year)); } - /** - * Returns the amount of this dining--the total amount of the bill that was charged to the credit card. - */ - public MonetaryAmount getAmount() { - return amount; + public String toString() { + return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; } - /** - * Returns the number of the credit card used to pay for this dining. For this dining to be eligible for reward, - * this credit card number should be associated with a valid account in the reward network. - */ public String getCreditCardNumber() { return creditCardNumber; } - /** - * Returns the merchant number of the restaurant where this dining occurred. For this dining to be eligible for - * reward, this merchant number should be associated with a valid restaurant in the reward network. - */ public String getMerchantNumber() { return merchantNumber; } - /** - * Returns the date this dining occurred on. - */ - public SimpleDate getDate() { - return date; - } - - public boolean equals(Object o) { - if (!(o instanceof Dining)) { - return false; - } - Dining other = (Dining) o; - // value objects are equal if their attributes are equal - return amount.equals(other.amount) && creditCardNumber.equals(other.creditCardNumber) - && merchantNumber.equals(other.merchantNumber) && date.equals(other.date); - } - - public int hashCode() { - return amount.hashCode() + creditCardNumber.hashCode() + merchantNumber.hashCode() + date.hashCode(); - } - - public String toString() { - return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; + public MonetaryAmount getAmount() { + return amount; } } \ No newline at end of file diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/RewardConfirmation.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/RewardConfirmation.java index c6984dcd..43ec595a 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/RewardConfirmation.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/RewardConfirmation.java @@ -1,39 +1,13 @@ package rewards; +import java.io.Serializable; + /** * A summary of a confirmed reward transaction describing a contribution made to an account that was distributed among * the account's beneficiaries. */ -public class RewardConfirmation { - - private String confirmationNumber; - - private AccountContribution accountContribution; - - /** - * Creates a new reward confirmation. - * @param confirmationNumber the unique confirmation number - * @param accountContribution a summary of the account contribution that was made - */ - public RewardConfirmation(String confirmationNumber, AccountContribution accountContribution) { - this.confirmationNumber = confirmationNumber; - this.accountContribution = accountContribution; - } - - /** - * Returns the confirmation number of the reward transaction. Can be used later to lookup the transaction record. - */ - public String getConfirmationNumber() { - return confirmationNumber; - } - - /** - * Returns a summary of the monetary contribution that was made to an account. - * @return the account contribution (the details of this reward) - */ - public AccountContribution getAccountContribution() { - return accountContribution; - } +public record RewardConfirmation(String confirmationNumber, + AccountContribution accountContribution) implements Serializable { public String toString() { return confirmationNumber; diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/RewardNetwork.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/RewardNetwork.java index 252fe280..1cb8d831 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/RewardNetwork.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/RewardNetwork.java @@ -2,14 +2,14 @@ /** * Rewards a member account for dining at a restaurant. - * + *

* A reward takes the form of a monetary contribution made to an account that is distributed among the account's * beneficiaries. The contribution amount is typically a function of several factors such as the dining amount and * restaurant where the dining occurred. - * + *

* Example: Papa Keith spends $100.00 at Apple Bee's resulting in a $8.00 contribution to his account that is * distributed evenly among his beneficiaries Annabelle and Corgan. - * + *

* This is the central application-boundary for the "rewards" application. This is the public interface users call to * invoke the application. This is the entry-point into the Application Layer. */ @@ -17,12 +17,12 @@ public interface RewardNetwork { /** * Reward an account for dining. - * + *

* For a dining to be eligible for reward: - It must have been paid for by a registered credit card of a valid * member account in the network. - It must have taken place at a restaurant participating in the network. - * + * * @param dining a charge made to a credit card for dining at a restaurant * @return confirmation of the reward */ - public RewardConfirmation rewardAccountFor(Dining dining); + RewardConfirmation rewardAccountFor(Dining dining); } \ No newline at end of file diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/RewardNetworkImpl.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/RewardNetworkImpl.java index a02a23f5..b100917f 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/RewardNetworkImpl.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/RewardNetworkImpl.java @@ -14,28 +14,29 @@ /** * Rewards an Account for Dining at a Restaurant. - * + *

* The sole Reward Network implementation. This object is an application-layer service responsible for coordinating with * the domain-layer to carry out the process of rewarding benefits to accounts for dining. - * + *

* Said in other words, this class implements the "reward account for dining" use case. */ public class RewardNetworkImpl implements RewardNetwork { - private AccountRepository accountRepository; + private final AccountRepository accountRepository; - private RestaurantRepository restaurantRepository; + private final RestaurantRepository restaurantRepository; - private RewardRepository rewardRepository; + private final RewardRepository rewardRepository; /** * Creates a new reward network. - * @param accountRepository the repository for loading accounts to reward + * + * @param accountRepository the repository for loading accounts to reward * @param restaurantRepository the repository for loading restaurants that determine how much to reward - * @param rewardRepository the repository for recording a record of successful reward transactions + * @param rewardRepository the repository for recording a record of successful reward transactions */ public RewardNetworkImpl(AccountRepository accountRepository, RestaurantRepository restaurantRepository, - RewardRepository rewardRepository) { + RewardRepository rewardRepository) { this.accountRepository = accountRepository; this.restaurantRepository = restaurantRepository; this.rewardRepository = rewardRepository; diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/Account.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/Account.java index eb685069..f6899a72 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/Account.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/Account.java @@ -14,10 +14,10 @@ /** * An account for a member of the reward network. An account has one or more beneficiaries whose allocations must add up * to 100%. - * + *

* An account can make contributions to its beneficiaries. Each contribution is distributed among the beneficiaries * based on an allocation. - * + *

* An entity. An aggregate. */ public class Account extends Entity { @@ -26,7 +26,7 @@ public class Account extends Entity { private String name; - private Set beneficiaries = new HashSet(); + private final Set beneficiaries = new HashSet<>(); @SuppressWarnings("unused") private Account() { @@ -34,8 +34,9 @@ private Account() { /** * Create a new account. + * * @param number the account number - * @param name the name on the account + * @param name the name on the account */ public Account(String number, String name) { this.number = number; @@ -58,6 +59,7 @@ public String getName() { /** * Add a single beneficiary with a 100% allocation percentage. + * * @param beneficiaryName the name of the beneficiary (should be unique) */ public void addBeneficiary(String beneficiaryName) { @@ -66,7 +68,8 @@ public void addBeneficiary(String beneficiaryName) { /** * Add a single beneficiary with the specified allocation percentage. - * @param beneficiaryName the name of the beneficiary (should be unique) + * + * @param beneficiaryName the name of the beneficiary (should be unique) * @param allocationPercentage the beneficiary's allocation percentage within this account */ public void addBeneficiary(String beneficiaryName, Percentage allocationPercentage) { @@ -81,16 +84,13 @@ public boolean isValid() { for (Beneficiary b : beneficiaries) { totalPercentage = totalPercentage.add(b.getAllocationPercentage()); } - if (totalPercentage.equals(Percentage.oneHundred())) { - return true; - } else { - return false; - } + return totalPercentage.equals(Percentage.oneHundred()); } /** * Make a monetary contribution to this account. The contribution amount is distributed among the account's * beneficiaries based on each beneficiary's allocation percentage. + * * @param amount the total amount to contribute */ public AccountContribution makeContribution(MonetaryAmount amount) { @@ -104,11 +104,12 @@ public AccountContribution makeContribution(MonetaryAmount amount) { /** * Distribute the contribution amount among this account's beneficiaries. + * * @param amount the total contribution amount * @return the individual beneficiary distributions */ private Set distribute(MonetaryAmount amount) { - Set distributions = new HashSet(beneficiaries.size()); + Set distributions = HashSet.newHashSet(beneficiaries.size()); for (Beneficiary beneficiary : beneficiaries) { MonetaryAmount distributionAmount = amount.multiplyBy(beneficiary.getAllocationPercentage()); beneficiary.credit(distributionAmount); @@ -124,6 +125,7 @@ private Set distribute(MonetaryAmount amount) { *

* Callers should not attempt to hold on or modify the returned set. This method should only be used transitively; * for example, called to facilitate account reporting. + * * @return the beneficiaries of this account */ public Set getBeneficiaries() { @@ -133,6 +135,7 @@ public Set getBeneficiaries() { /** * Used to restore an allocated beneficiary. Should only be called by the repository responsible for reconstituting * this account. + * * @param beneficiary the beneficiary */ void restoreBeneficiary(Beneficiary beneficiary) { diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/AccountRepository.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/AccountRepository.java index 5ba489ec..7c6bba59 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/AccountRepository.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/AccountRepository.java @@ -3,17 +3,18 @@ /** * Loads account aggregates. Called by the reward network to find and reconstitute Account entities from an external * form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. */ public interface AccountRepository { /** * Load an account by its credit card. + * * @param creditCardNumber the credit card number * @return the account object */ - public Account findByCreditCard(String creditCardNumber); + Account findByCreditCard(String creditCardNumber); /** * Updates the 'savings' of each account beneficiary. The new savings balance contains the amount distributed for a @@ -22,8 +23,9 @@ public interface AccountRepository { * Note: use of an object-relational mapper (ORM) with support for transparent-persistence like Hibernate (or the * new Java Persistence API (JPA)) would remove the need for this explicit update operation as the ORM would take * care of applying relational updates to a modified Account entity automatically. + * * @param account the account whose beneficiary savings have changed */ - public void updateBeneficiaries(Account account); + void updateBeneficiaries(Account account); } \ No newline at end of file diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/JdbcAccountRepository.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/JdbcAccountRepository.java index 13be1fe8..323acf6d 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/JdbcAccountRepository.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/account/JdbcAccountRepository.java @@ -30,7 +30,7 @@ public void setDataSource(DataSource dataSource) { public Account findByCreditCard(String creditCardNumber) { String sql = "select a.ID as ID, a.NUMBER as ACCOUNT_NUMBER, a.NAME as ACCOUNT_NAME, c.NUMBER as CREDIT_CARD_NUMBER, " + - " b.NAME as BENEFICIARY_NAME, b.ALLOCATION_PERCENTAGE as BENEFICIARY_ALLOCATION_PERCENTAGE, b.SAVINGS as BENEFICIARY_SAVINGS " + + "b.NAME as BENEFICIARY_NAME, b.ALLOCATION_PERCENTAGE as BENEFICIARY_ALLOCATION_PERCENTAGE, b.SAVINGS as BENEFICIARY_SAVINGS " + "from T_ACCOUNT a, T_ACCOUNT_CREDIT_CARD c " + "left outer join T_ACCOUNT_BENEFICIARY b " + "on a.ID = b.ACCOUNT_ID " + diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java index 6e658157..f0e2a51e 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java @@ -28,7 +28,7 @@ public void setDataSource(DataSource dataSource) { public Restaurant findByMerchantNumber(String merchantNumber) { String sql = "select MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE from T_RESTAURANT where MERCHANT_NUMBER = ?"; - Restaurant restaurant = null; + Restaurant restaurant; Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; @@ -86,7 +86,7 @@ private Restaurant mapRestaurant(ResultSet rs) throws SQLException { * 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 + * @throws SQLException if there is a problem advancing the ResultSet */ private void advanceToNextRow(ResultSet rs) throws EmptyResultDataAccessException, SQLException { if (!rs.next()) { diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/restaurant/RestaurantRepository.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/restaurant/RestaurantRepository.java index a632fcc1..04968ac5 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/restaurant/RestaurantRepository.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/restaurant/RestaurantRepository.java @@ -3,15 +3,16 @@ /** * Loads restaurant aggregates. Called by the reward network to find and reconstitute Restaurant entities from an * external form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. */ public interface RestaurantRepository { /** * Load a Restaurant entity by its merchant number. + * * @param merchantNumber the merchant number * @return the restaurant */ - public Restaurant findByMerchantNumber(String merchantNumber); + Restaurant findByMerchantNumber(String merchantNumber); } diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/reward/JdbcRewardRepository.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/reward/JdbcRewardRepository.java index c20a0128..67df3e06 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/reward/JdbcRewardRepository.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/reward/JdbcRewardRepository.java @@ -33,11 +33,11 @@ public RewardConfirmation confirmReward(AccountContribution contribution, Dining ps = conn.prepareStatement(sql); String confirmationNumber = nextConfirmationNumber(); ps.setString(1, confirmationNumber); - ps.setBigDecimal(2, contribution.getAmount().asBigDecimal()); + ps.setBigDecimal(2, contribution.amount().asBigDecimal()); ps.setDate(3, new Date(SimpleDate.today().inMilliseconds())); - ps.setString(4, contribution.getAccountNumber()); + ps.setString(4, contribution.accountNumber()); ps.setString(5, dining.getMerchantNumber()); - ps.setDate(6, new Date(dining.getDate().inMilliseconds())); + ps.setDate(6, new Date(dining.date().inMilliseconds())); ps.setBigDecimal(7, dining.getAmount().asBigDecimal()); ps.execute(); return new RewardConfirmation(confirmationNumber, contribution); diff --git a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/reward/RewardRepository.java b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/reward/RewardRepository.java index c8189e52..e32368d2 100644 --- a/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/reward/RewardRepository.java +++ b/lab/12-javaconfig-dependency-injection/src/main/java/rewards/internal/reward/RewardRepository.java @@ -9,12 +9,13 @@ */ public interface RewardRepository { - /** - * Create a record of a reward that will track a contribution made to an account for dining. - * @param contribution the account contribution that was made - * @param dining the dining event that resulted in the account contribution - * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later - * date - */ - public RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); + /** + * Create a record of a reward that will track a contribution made to an account for dining. + * + * @param contribution the account contribution that was made + * @param dining the dining event that resulted in the account contribution + * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later + * date + */ + RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); } \ No newline at end of file diff --git a/lab/12-javaconfig-dependency-injection/src/test/java/config/RewardsConfigTests.java b/lab/12-javaconfig-dependency-injection/src/test/java/config/RewardsConfigTests.java index fe34b746..e1c8c8f5 100644 --- a/lab/12-javaconfig-dependency-injection/src/test/java/config/RewardsConfigTests.java +++ b/lab/12-javaconfig-dependency-injection/src/test/java/config/RewardsConfigTests.java @@ -13,9 +13,9 @@ * beans. */ @SuppressWarnings("unused") -public class RewardsConfigTests { +class RewardsConfigTests { // Provide a mock object for testing - private DataSource dataSource = Mockito.mock(DataSource.class); + DataSource dataSource = Mockito.mock(DataSource.class); // TODO-05: Run the test // - Uncomment the code below between /* and */ @@ -24,23 +24,23 @@ public class RewardsConfigTests { // - Now run the test, it should pass. /* - private RewardsConfig rewardsConfig = new RewardsConfig(dataSource); + RewardsConfig rewardsConfig = new RewardsConfig(dataSource); @Test - public void getBeans() { + void getBeans() { RewardNetwork rewardNetwork = rewardsConfig.rewardNetwork(); - assertTrue(rewardNetwork instanceof RewardNetworkImpl); + assertInstanceOf(RewardNetworkImpl.class, rewardNetwork); AccountRepository accountRepository = rewardsConfig.accountRepository(); - assertTrue(accountRepository instanceof JdbcAccountRepository); + assertInstanceOf(JdbcAccountRepository.class, accountRepository); checkDataSource(accountRepository); RestaurantRepository restaurantRepository = rewardsConfig.restaurantRepository(); - assertTrue(restaurantRepository instanceof JdbcRestaurantRepository); + assertInstanceOf(JdbcRestaurantRepository.class, restaurantRepository); checkDataSource(restaurantRepository); RewardRepository rewardsRepository = rewardsConfig.rewardRepository(); - assertTrue(rewardsRepository instanceof JdbcRewardRepository); + assertInstanceOf(JdbcRewardRepository.class, rewardsRepository); checkDataSource(rewardsRepository); } */ @@ -53,12 +53,12 @@ public void getBeans() { * */ private void checkDataSource(Object repository) { - Class repositoryClass = repository.getClass(); + Class repositoryClass = repository.getClass(); try { - Field dataSource = repositoryClass.getDeclaredField("dataSource"); - dataSource.setAccessible(true); - assertNotNull(dataSource.get(repository)); + Field source = repositoryClass.getDeclaredField("dataSource"); + source.setAccessible(true); + assertNotNull(source.get(repository)); } catch (Exception e) { String failureMessage = "Unable to validate dataSource in " + repositoryClass.getSimpleName(); System.out.println(failureMessage); diff --git a/lab/12-javaconfig-dependency-injection/src/test/java/rewards/TestInfrastructureConfig.java b/lab/12-javaconfig-dependency-injection/src/test/java/rewards/TestInfrastructureConfig.java index 90f3b4bd..bf721f7c 100644 --- a/lab/12-javaconfig-dependency-injection/src/test/java/rewards/TestInfrastructureConfig.java +++ b/lab/12-javaconfig-dependency-injection/src/test/java/rewards/TestInfrastructureConfig.java @@ -15,33 +15,33 @@ * 'src/main/resources/rewards/testdb' directory of * the '00-rewards-common' project * - Do not modify this method. - * + *

* TODO-07: Import your application configuration file (RewardsConfig) * - Now the test code should have access to all the beans defined in * the RewardsConfig configuration class - * + *

* TODO-08: Create a new JUnit 5 test class * - Call it RewardNetworkTests * - Create it in the same package this configuration class is located. * - Ask for a setUp() method to be generated within your IDE. - * + *

* NOTE: The appendices at the bottom of the course Home Page includes * a section on creating JUnit tests in an IDE. - * + *

* TODO-09: Make sure the setUp() method in the RewardNetworkTests class is annotated with @BeforeEach. * - In the setUp() method, create an application context using * this configuration class - use run(..) static method of * the SpringApplication class * - Then get the 'rewardNetwork' bean from the application context * and assign it to a private field for use later. - * + *

* TODO-10: We can test the setup by running an empty test. * - If your IDE automatically generated a @Test method, rename it * testRewardForDining. Delete any code in the method body. * - Otherwise add a testRewardForDining method & annotate it with - * @Test (make sure the @Test is from org.junit.jupiter.api.Test ). + * @Test (make sure the @ Test is from org.junit.jupiter.api.Test). * - Run the test. If your setup() is working, you get a green bar. - * + *

* TODO-11: Finally run a real test. * - Copy the unit test (the @Test method) from * RewardNetworkImplTests#testRewardForDining() under @@ -50,20 +50,19 @@ * - Run the test - it should pass if you have configured everything * correctly. Congratulations, you are done. * - If your test fails - did you miss the import in TO DO 7 above? - * */ @Configuration -public class TestInfrastructureConfig { +class TestInfrastructureConfig { - /** - * Creates an in-memory "rewards" database populated - * with test data for fast testing - */ - @Bean - public DataSource dataSource() { - return (new EmbeddedDatabaseBuilder()) // - .addScript("classpath:rewards/testdb/schema.sql") // - .addScript("classpath:rewards/testdb/data.sql") // - .build(); - } + /** + * Creates an in-memory "rewards" database populated + * with test data for fast testing + */ + @Bean + DataSource dataSource() { + return (new EmbeddedDatabaseBuilder()) // + .addScript("classpath:rewards/testdb/schema.sql") // + .addScript("classpath:rewards/testdb/data.sql") // + .build(); + } } diff --git a/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/RewardNetworkImplTests.java b/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/RewardNetworkImplTests.java index 060d70c1..fed86e86 100644 --- a/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/RewardNetworkImplTests.java +++ b/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/RewardNetworkImplTests.java @@ -18,12 +18,11 @@ /** * Unit tests for the RewardNetworkImpl application logic. Configures the implementation with stub repositories * containing dummy data for fast in-memory testing without the overhead of an external data source. - * + *

* Besides helping catch bugs early, tests are a great way for a new developer to learn an API as he or she can see the * API in action. Tests also help validate a design as they are a measure for how easy it is to use your code. */ - -public class RewardNetworkImplTests { +class RewardNetworkImplTests { /** * The object being tested. @@ -31,7 +30,7 @@ public class RewardNetworkImplTests { private RewardNetworkImpl rewardNetwork; @BeforeEach - public void setUp() throws Exception { + void setUp() { // create stubs to facilitate fast in-memory testing with dummy data and no external dependencies AccountRepository accountRepo = new StubAccountRepository(); RestaurantRepository restaurantRepo = new StubRestaurantRepository(); @@ -42,7 +41,7 @@ public void setUp() throws Exception { } @Test - public void testRewardForDining() { + void testRewardForDining() { // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); @@ -51,23 +50,23 @@ public void testRewardForDining() { // assert the expected reward confirmation results assertNotNull(confirmation); - assertNotNull(confirmation.getConfirmationNumber()); + assertNotNull(confirmation.confirmationNumber()); // assert an account contribution was made - AccountContribution contribution = confirmation.getAccountContribution(); + AccountContribution contribution = confirmation.accountContribution(); assertNotNull(contribution); // the account number should be '123456789' - assertEquals("123456789", contribution.getAccountNumber()); + assertEquals("123456789", contribution.accountNumber()); // the total contribution amount should be 8.00 (8% of 100.00) - assertEquals(MonetaryAmount.valueOf("8.00"), contribution.getAmount()); + assertEquals(MonetaryAmount.valueOf("8.00"), contribution.amount()); // the total contribution amount should have been split into 2 distributions - assertEquals(2, contribution.getDistributions().size()); + assertEquals(2, contribution.distributions().size()); // each distribution should be 4.00 (as both have a 50% allocation) - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").getAmount()); - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").getAmount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").amount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").amount()); } -} \ No newline at end of file +} diff --git a/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/StubAccountRepository.java b/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/StubAccountRepository.java index 66ca1dcd..ff91a312 100644 --- a/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/StubAccountRepository.java +++ b/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/StubAccountRepository.java @@ -3,8 +3,6 @@ import java.util.HashMap; import java.util.Map; -import org.springframework.dao.EmptyResultDataAccessException; - import rewards.internal.account.Account; import rewards.internal.account.AccountRepository; @@ -13,14 +11,14 @@ /** * A dummy account repository implementation. Has a single Account "Keith and Keri Donald" with two beneficiaries * "Annabelle" (50% allocation) and "Corgan" (50% allocation) associated with credit card "1234123412341234". - * + *

* Stubs facilitate unit testing. An object needing an AccountRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubAccountRepository implements AccountRepository { - private Map accountsByCreditCard = new HashMap(); + Map accountsByCreditCard = new HashMap<>(); public StubAccountRepository() { Account account = new Account("123456789", "Keith and Keri Donald"); @@ -32,7 +30,7 @@ public StubAccountRepository() { public Account findByCreditCard(String creditCardNumber) { Account account = accountsByCreditCard.get(creditCardNumber); if (account == null) { - throw new EmptyResultDataAccessException(1); + throw new RuntimeException("no account has been found for credit card number " + creditCardNumber); } return account; } diff --git a/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/StubRestaurantRepository.java b/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/StubRestaurantRepository.java index 92375baa..0bc47be6 100644 --- a/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/StubRestaurantRepository.java +++ b/lab/12-javaconfig-dependency-injection/src/test/java/rewards/internal/StubRestaurantRepository.java @@ -3,8 +3,6 @@ import java.util.HashMap; import java.util.Map; -import org.springframework.dao.EmptyResultDataAccessException; - import rewards.internal.restaurant.Restaurant; import rewards.internal.restaurant.RestaurantRepository; @@ -13,14 +11,14 @@ /** * A dummy restaurant repository implementation. Has a single restaurant "Apple Bees" with a 8% benefit availability * percentage that's always available. - * + *

* Stubs facilitate unit testing. An object needing a RestaurantRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubRestaurantRepository implements RestaurantRepository { - private Map restaurantsByMerchantNumber = new HashMap(); + Map restaurantsByMerchantNumber = new HashMap<>(); public StubRestaurantRepository() { Restaurant restaurant = new Restaurant("1234567890", "Apple Bees"); @@ -29,9 +27,9 @@ public StubRestaurantRepository() { } public Restaurant findByMerchantNumber(String merchantNumber) { - Restaurant restaurant = (Restaurant) restaurantsByMerchantNumber.get(merchantNumber); + Restaurant restaurant = restaurantsByMerchantNumber.get(merchantNumber); if (restaurant == null) { - throw new EmptyResultDataAccessException(1); + throw new RuntimeException("no restaurant has been found for merchant number " + merchantNumber); } return restaurant; } diff --git a/lab/16-annotations-solution/src/main/java/rewards/AccountContribution.java b/lab/16-annotations-solution/src/main/java/rewards/AccountContribution.java index 5cad1918..b68c7512 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/AccountContribution.java +++ b/lab/16-annotations-solution/src/main/java/rewards/AccountContribution.java @@ -1,61 +1,20 @@ package rewards; -import java.util.Set; - import common.money.MonetaryAmount; import common.money.Percentage; +import java.util.Set; + /** * A summary of a monetary contribution made to an account that was distributed among the account's beneficiaries. - * + *

* A value object. Immutable. */ -public class AccountContribution { - - private String accountNumber; - - private MonetaryAmount amount; - - private Set distributions; - - /** - * Creates a new account contribution. - * @param accountNumber the number of the account the contribution was made - * @param amount the total contribution amount - * @param distributions how the contribution was distributed among the account's beneficiaries - */ - public AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { - this.accountNumber = accountNumber; - this.amount = amount; - this.distributions = distributions; - } - - /** - * Returns the number of the account this contribution was made to. - * @return the account number - */ - public String getAccountNumber() { - return accountNumber; - } - - /** - * Returns the total amount of the contribution. - * @return the contribution amount - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns how this contribution was distributed among the account's beneficiaries. - * @return the contribution distributions - */ - public Set getDistributions() { - return distributions; - } +public record AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { /** * Returns how this contribution was distributed to a single account beneficiary. + * * @param beneficiary the name of the beneficiary e.g "Annabelle" * @return a summary of how the contribution amount was distributed to the beneficiary */ @@ -71,61 +30,11 @@ public Distribution getDistribution(String beneficiary) { /** * A single distribution made to a beneficiary as part of an account contribution, summarizing the distribution * amount and resulting total beneficiary savings. - * + *

* A value object. */ - public static class Distribution { - - private String beneficiary; - - private MonetaryAmount amount; - - private Percentage percentage; - - private MonetaryAmount totalSavings; - - /** - * Creates a new distribution. - * @param beneficiary the name of the account beneficiary that received a distribution - * @param amount the distribution amount - * @param percentage this distribution's percentage of the total account contribution - * @param totalSavings the beneficiary's total savings amount after the distribution was made - */ - public Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, - MonetaryAmount totalSavings) { - this.beneficiary = beneficiary; - this.percentage = percentage; - this.amount = amount; - this.totalSavings = totalSavings; - } - - /** - * Returns the name of the beneficiary. - */ - public String getBeneficiary() { - return beneficiary; - } - - /** - * Returns the amount of this distribution. - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns the percentage of this distribution relative to others in the contribution. - */ - public Percentage getPercentage() { - return percentage; - } - - /** - * Returns the total savings of the beneficiary after this distribution. - */ - public MonetaryAmount getTotalSavings() { - return totalSavings; - } + public record Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, + MonetaryAmount totalSavings) { public String toString() { return amount + " to '" + beneficiary + "' (" + percentage + ")"; diff --git a/lab/16-annotations-solution/src/main/java/rewards/Dining.java b/lab/16-annotations-solution/src/main/java/rewards/Dining.java index 478463c3..379762bf 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/Dining.java +++ b/lab/16-annotations-solution/src/main/java/rewards/Dining.java @@ -3,44 +3,25 @@ import common.datetime.SimpleDate; import common.money.MonetaryAmount; +import java.io.Serializable; + /** * A dining event that occurred, representing a charge made to an credit card by a merchant on a specific date. - * * For a dining to be eligible for reward, the credit card number should map to an account in the reward network. In * addition, the merchant number should map to a restaurant in the network. - * * A value object. Immutable. */ -public class Dining { - - private MonetaryAmount amount; - - private String creditCardNumber; - - private String merchantNumber; +public record Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, + SimpleDate date) implements Serializable { - private SimpleDate date; - - /** - * Creates a new dining, reflecting an amount that was charged to a card by a merchant on the date specified. - * @param amount the total amount of the dining bill - * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param date the date of the dining event - */ - public Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, SimpleDate date) { - this.amount = amount; - this.creditCardNumber = creditCardNumber; - this.merchantNumber = merchantNumber; - this.date = date; - } /** * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on today's date. A * convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param merchantNumber the merchant number of the restaurant where the dining occurred * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber) { @@ -50,65 +31,34 @@ public static Dining createDining(String amount, String creditCardNumber, String /** * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on the date specified. * A convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param month the month of the dining event - * @param day the day of the dining event - * @param year the year of the dining event + * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param month the month of the dining event + * @param day the day of the dining event + * @param year the year of the dining event * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber, int month, - int day, int year) { + int day, int year) { return new Dining(MonetaryAmount.valueOf(amount), creditCardNumber, merchantNumber, new SimpleDate(month, day, year)); } - /** - * Returns the amount of this dining--the total amount of the bill that was charged to the credit card. - */ - public MonetaryAmount getAmount() { - return amount; + public String toString() { + return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; } - /** - * Returns the number of the credit card used to pay for this dining. For this dining to be eligible for reward, - * this credit card number should be associated with a valid account in the reward network. - */ public String getCreditCardNumber() { return creditCardNumber; } - /** - * Returns the merchant number of the restaurant where this dining occurred. For this dining to be eligible for - * reward, this merchant number should be associated with a valid restaurant in the reward network. - */ public String getMerchantNumber() { return merchantNumber; } - /** - * Returns the date this dining occurred on. - */ - public SimpleDate getDate() { - return date; - } - - public boolean equals(Object o) { - if (!(o instanceof Dining)) { - return false; - } - Dining other = (Dining) o; - // value objects are equal if their attributes are equal - return amount.equals(other.amount) && creditCardNumber.equals(other.creditCardNumber) - && merchantNumber.equals(other.merchantNumber) && date.equals(other.date); - } - - public int hashCode() { - return amount.hashCode() + creditCardNumber.hashCode() + merchantNumber.hashCode() + date.hashCode(); - } - - public String toString() { - return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; + public MonetaryAmount getAmount() { + return amount; } } \ No newline at end of file diff --git a/lab/16-annotations-solution/src/main/java/rewards/RewardConfirmation.java b/lab/16-annotations-solution/src/main/java/rewards/RewardConfirmation.java index c6984dcd..43ec595a 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/RewardConfirmation.java +++ b/lab/16-annotations-solution/src/main/java/rewards/RewardConfirmation.java @@ -1,39 +1,13 @@ package rewards; +import java.io.Serializable; + /** * A summary of a confirmed reward transaction describing a contribution made to an account that was distributed among * the account's beneficiaries. */ -public class RewardConfirmation { - - private String confirmationNumber; - - private AccountContribution accountContribution; - - /** - * Creates a new reward confirmation. - * @param confirmationNumber the unique confirmation number - * @param accountContribution a summary of the account contribution that was made - */ - public RewardConfirmation(String confirmationNumber, AccountContribution accountContribution) { - this.confirmationNumber = confirmationNumber; - this.accountContribution = accountContribution; - } - - /** - * Returns the confirmation number of the reward transaction. Can be used later to lookup the transaction record. - */ - public String getConfirmationNumber() { - return confirmationNumber; - } - - /** - * Returns a summary of the monetary contribution that was made to an account. - * @return the account contribution (the details of this reward) - */ - public AccountContribution getAccountContribution() { - return accountContribution; - } +public record RewardConfirmation(String confirmationNumber, + AccountContribution accountContribution) implements Serializable { public String toString() { return confirmationNumber; diff --git a/lab/16-annotations-solution/src/main/java/rewards/RewardNetwork.java b/lab/16-annotations-solution/src/main/java/rewards/RewardNetwork.java index 252fe280..1cb8d831 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/RewardNetwork.java +++ b/lab/16-annotations-solution/src/main/java/rewards/RewardNetwork.java @@ -2,14 +2,14 @@ /** * Rewards a member account for dining at a restaurant. - * + *

* A reward takes the form of a monetary contribution made to an account that is distributed among the account's * beneficiaries. The contribution amount is typically a function of several factors such as the dining amount and * restaurant where the dining occurred. - * + *

* Example: Papa Keith spends $100.00 at Apple Bee's resulting in a $8.00 contribution to his account that is * distributed evenly among his beneficiaries Annabelle and Corgan. - * + *

* This is the central application-boundary for the "rewards" application. This is the public interface users call to * invoke the application. This is the entry-point into the Application Layer. */ @@ -17,12 +17,12 @@ public interface RewardNetwork { /** * Reward an account for dining. - * + *

* For a dining to be eligible for reward: - It must have been paid for by a registered credit card of a valid * member account in the network. - It must have taken place at a restaurant participating in the network. - * + * * @param dining a charge made to a credit card for dining at a restaurant * @return confirmation of the reward */ - public RewardConfirmation rewardAccountFor(Dining dining); + RewardConfirmation rewardAccountFor(Dining dining); } \ No newline at end of file diff --git a/lab/16-annotations-solution/src/main/java/rewards/internal/RewardNetworkImpl.java b/lab/16-annotations-solution/src/main/java/rewards/internal/RewardNetworkImpl.java index 70600de2..92a04c5c 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/internal/RewardNetworkImpl.java +++ b/lab/16-annotations-solution/src/main/java/rewards/internal/RewardNetworkImpl.java @@ -1,8 +1,7 @@ package rewards.internal; -import org.springframework.beans.factory.annotation.Autowired; +import common.money.MonetaryAmount; import org.springframework.stereotype.Service; - import rewards.AccountContribution; import rewards.Dining; import rewards.RewardConfirmation; @@ -13,45 +12,43 @@ import rewards.internal.restaurant.RestaurantRepository; import rewards.internal.reward.RewardRepository; -import common.money.MonetaryAmount; - /** * Rewards an Account for Dining at a Restaurant. - * + *

* The sole Reward Network implementation. This object is an application-layer service responsible for coordinating with * the domain-layer to carry out the process of rewarding benefits to accounts for dining. - * + *

* Said in other words, this class implements the "reward account for dining" use case. */ @Service("rewardNetwork") public class RewardNetworkImpl implements RewardNetwork { - private AccountRepository accountRepository; - - private RestaurantRepository restaurantRepository; - - private RewardRepository rewardRepository; - - /** - * Creates a new reward network. - * @param accountRepository the repository for loading accounts to reward - * @param restaurantRepository the repository for loading restaurants that determine how much to reward - * @param rewardRepository the repository for recording a record of successful reward transactions - */ - @Autowired - public RewardNetworkImpl(AccountRepository accountRepository, RestaurantRepository restaurantRepository, - RewardRepository rewardRepository) { - this.accountRepository = accountRepository; - this.restaurantRepository = restaurantRepository; - this.rewardRepository = rewardRepository; - } - - public RewardConfirmation rewardAccountFor(Dining dining) { - Account account = accountRepository.findByCreditCard(dining.getCreditCardNumber()); - Restaurant restaurant = restaurantRepository.findByMerchantNumber(dining.getMerchantNumber()); - MonetaryAmount amount = restaurant.calculateBenefitFor(account, dining); - AccountContribution contribution = account.makeContribution(amount); - accountRepository.updateBeneficiaries(account); - return rewardRepository.confirmReward(contribution, dining); - } + private final AccountRepository accountRepository; + + private final RestaurantRepository restaurantRepository; + + private final RewardRepository rewardRepository; + + /** + * Creates a new reward network. + * + * @param accountRepository the repository for loading accounts to reward + * @param restaurantRepository the repository for loading restaurants that determine how much to reward + * @param rewardRepository the repository for recording a record of successful reward transactions + */ + public RewardNetworkImpl(AccountRepository accountRepository, RestaurantRepository restaurantRepository, + RewardRepository rewardRepository) { + this.accountRepository = accountRepository; + this.restaurantRepository = restaurantRepository; + this.rewardRepository = rewardRepository; + } + + public RewardConfirmation rewardAccountFor(Dining dining) { + Account account = accountRepository.findByCreditCard(dining.getCreditCardNumber()); + Restaurant restaurant = restaurantRepository.findByMerchantNumber(dining.getMerchantNumber()); + MonetaryAmount amount = restaurant.calculateBenefitFor(account, dining); + AccountContribution contribution = account.makeContribution(amount); + accountRepository.updateBeneficiaries(account); + return rewardRepository.confirmReward(contribution, dining); + } } \ No newline at end of file diff --git a/lab/16-annotations-solution/src/main/java/rewards/internal/account/Account.java b/lab/16-annotations-solution/src/main/java/rewards/internal/account/Account.java index eb685069..9b48c01f 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/internal/account/Account.java +++ b/lab/16-annotations-solution/src/main/java/rewards/internal/account/Account.java @@ -1,23 +1,22 @@ package rewards.internal.account; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import rewards.AccountContribution; -import rewards.AccountContribution.Distribution; - import common.money.MonetaryAmount; import common.money.Percentage; import common.repository.Entity; +import rewards.AccountContribution; +import rewards.AccountContribution.Distribution; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; /** * An account for a member of the reward network. An account has one or more beneficiaries whose allocations must add up * to 100%. - * + *

* An account can make contributions to its beneficiaries. Each contribution is distributed among the beneficiaries * based on an allocation. - * + *

* An entity. An aggregate. */ public class Account extends Entity { @@ -26,7 +25,7 @@ public class Account extends Entity { private String name; - private Set beneficiaries = new HashSet(); + private final Set beneficiaries = new HashSet<>(); @SuppressWarnings("unused") private Account() { @@ -34,8 +33,9 @@ private Account() { /** * Create a new account. + * * @param number the account number - * @param name the name on the account + * @param name the name on the account */ public Account(String number, String name) { this.number = number; @@ -58,6 +58,7 @@ public String getName() { /** * Add a single beneficiary with a 100% allocation percentage. + * * @param beneficiaryName the name of the beneficiary (should be unique) */ public void addBeneficiary(String beneficiaryName) { @@ -66,7 +67,8 @@ public void addBeneficiary(String beneficiaryName) { /** * Add a single beneficiary with the specified allocation percentage. - * @param beneficiaryName the name of the beneficiary (should be unique) + * + * @param beneficiaryName the name of the beneficiary (should be unique) * @param allocationPercentage the beneficiary's allocation percentage within this account */ public void addBeneficiary(String beneficiaryName, Percentage allocationPercentage) { @@ -81,16 +83,13 @@ public boolean isValid() { for (Beneficiary b : beneficiaries) { totalPercentage = totalPercentage.add(b.getAllocationPercentage()); } - if (totalPercentage.equals(Percentage.oneHundred())) { - return true; - } else { - return false; - } + return totalPercentage.equals(Percentage.oneHundred()); } /** * Make a monetary contribution to this account. The contribution amount is distributed among the account's * beneficiaries based on each beneficiary's allocation percentage. + * * @param amount the total amount to contribute */ public AccountContribution makeContribution(MonetaryAmount amount) { @@ -104,11 +103,12 @@ public AccountContribution makeContribution(MonetaryAmount amount) { /** * Distribute the contribution amount among this account's beneficiaries. + * * @param amount the total contribution amount * @return the individual beneficiary distributions */ private Set distribute(MonetaryAmount amount) { - Set distributions = new HashSet(beneficiaries.size()); + Set distributions = HashSet.newHashSet(beneficiaries.size()); for (Beneficiary beneficiary : beneficiaries) { MonetaryAmount distributionAmount = amount.multiplyBy(beneficiary.getAllocationPercentage()); beneficiary.credit(distributionAmount); @@ -124,6 +124,7 @@ private Set distribute(MonetaryAmount amount) { *

* Callers should not attempt to hold on or modify the returned set. This method should only be used transitively; * for example, called to facilitate account reporting. + * * @return the beneficiaries of this account */ public Set getBeneficiaries() { @@ -133,6 +134,7 @@ public Set getBeneficiaries() { /** * Used to restore an allocated beneficiary. Should only be called by the repository responsible for reconstituting * this account. + * * @param beneficiary the beneficiary */ void restoreBeneficiary(Beneficiary beneficiary) { diff --git a/lab/16-annotations-solution/src/main/java/rewards/internal/account/AccountRepository.java b/lab/16-annotations-solution/src/main/java/rewards/internal/account/AccountRepository.java index 5ba489ec..7c6bba59 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/internal/account/AccountRepository.java +++ b/lab/16-annotations-solution/src/main/java/rewards/internal/account/AccountRepository.java @@ -3,17 +3,18 @@ /** * Loads account aggregates. Called by the reward network to find and reconstitute Account entities from an external * form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. */ public interface AccountRepository { /** * Load an account by its credit card. + * * @param creditCardNumber the credit card number * @return the account object */ - public Account findByCreditCard(String creditCardNumber); + Account findByCreditCard(String creditCardNumber); /** * Updates the 'savings' of each account beneficiary. The new savings balance contains the amount distributed for a @@ -22,8 +23,9 @@ public interface AccountRepository { * Note: use of an object-relational mapper (ORM) with support for transparent-persistence like Hibernate (or the * new Java Persistence API (JPA)) would remove the need for this explicit update operation as the ORM would take * care of applying relational updates to a modified Account entity automatically. + * * @param account the account whose beneficiary savings have changed */ - public void updateBeneficiaries(Account account); + void updateBeneficiaries(Account account); } \ No newline at end of file diff --git a/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java b/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java index 5c5ef46e..e2743418 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java +++ b/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java @@ -1,144 +1,145 @@ package rewards.internal.restaurant; import common.money.Percentage; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.stereotype.Repository; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; +import javax.sql.DataSource; /** * Loads restaurants from a data source using the JDBC API. - * + *

* This implementation caches restaurants to improve performance. * The cache is populated on initialization and cleared on destruction. */ @Repository("restaurantRepository") public class JdbcRestaurantRepository implements RestaurantRepository { - private DataSource dataSource; + private DataSource dataSource; + + /** + * The Restaurant object cache. Cached restaurants are indexed by their merchant numbers. + */ + private Map restaurantCache; + + /** + * The constructor sets the data source this repository will use to load restaurants. + * When the instance of JdbcRestaurantRepository is created, a Restaurant cache is + * populated for read only access + * + * @param dataSource the data source + */ - /** - * The Restaurant object cache. Cached restaurants are indexed by their merchant numbers. - */ - private Map restaurantCache; + public JdbcRestaurantRepository(DataSource dataSource) { + this.dataSource = dataSource; + this.populateRestaurantCache(); + } - /** - * The constructor sets the data source this repository will use to load restaurants. - * When the instance of JdbcRestaurantRepository is created, a Restaurant cache is - * populated for read only access - * - * @param dataSource the data source - */ + public JdbcRestaurantRepository() { + } - public JdbcRestaurantRepository(DataSource dataSource){ - this.dataSource = dataSource; - this.populateRestaurantCache(); - } - - public JdbcRestaurantRepository(){} - - @Autowired - public void setDataSource(DataSource dataSource) { - this.dataSource = dataSource; - } + @Autowired + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } - public Restaurant findByMerchantNumber(String merchantNumber) { - return queryRestaurantCache(merchantNumber); - } + public Restaurant findByMerchantNumber(String merchantNumber) { + return queryRestaurantCache(merchantNumber); + } - /** - * Helper method that populates the {@link #restaurantCache restaurant object cache} from rows in the T_RESTAURANT - * table. Cached restaurants are indexed by their merchant numbers. This method is called on initialization. - */ - @PostConstruct - void populateRestaurantCache() { - restaurantCache = new HashMap(); - String sql = "select MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE from T_RESTAURANT"; - Connection conn = null; - PreparedStatement ps = null; - ResultSet rs = null; - try { - conn = dataSource.getConnection(); - ps = conn.prepareStatement(sql); - rs = ps.executeQuery(); - while (rs.next()) { - Restaurant restaurant = mapRestaurant(rs); - // index the restaurant by its merchant number - restaurantCache.put(restaurant.getNumber(), restaurant); - } - } 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) { - } - } - } - } + /** + * Helper method that populates the {@link #restaurantCache restaurant object cache} from rows in the T_RESTAURANT + * table. Cached restaurants are indexed by their merchant numbers. This method is called on initialization. + */ + @PostConstruct + void populateRestaurantCache() { + restaurantCache = new HashMap<>(); + String sql = "select MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE from T_RESTAURANT"; + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = dataSource.getConnection(); + ps = conn.prepareStatement(sql); + rs = ps.executeQuery(); + while (rs.next()) { + Restaurant restaurant = mapRestaurant(rs); + // index the restaurant by its merchant number + restaurantCache.put(restaurant.getNumber(), restaurant); + } + } 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) { + } + } + } + } - /** - * Helper method that simply queries the cache of restaurants. - * - * @param merchantNumber the restaurant's merchant number - * @return the restaurant - * @throws EmptyResultDataAccessException if no restaurant was found with that merchant number - */ - private Restaurant queryRestaurantCache(String merchantNumber) { - Restaurant restaurant = restaurantCache.get(merchantNumber); - if (restaurant == null) { - throw new EmptyResultDataAccessException(1); - } - return restaurant; - } + /** + * Helper method that simply queries the cache of restaurants. + * + * @param merchantNumber the restaurant's merchant number + * @return the restaurant + * @throws EmptyResultDataAccessException if no restaurant was found with that merchant number + */ + private Restaurant queryRestaurantCache(String merchantNumber) { + Restaurant restaurant = restaurantCache.get(merchantNumber); + if (restaurant == null) { + throw new EmptyResultDataAccessException(1); + } + return restaurant; + } - /** - * Helper method that clears the cache of restaurants. This method is called on destruction - */ - @PreDestroy - void clearRestaurantCache() { - restaurantCache.clear(); - } + /** + * Helper method that clears the cache of restaurants. This method is called on destruction + */ + @PreDestroy + void clearRestaurantCache() { + restaurantCache.clear(); + } - /** - * 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); - 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); + return restaurant; + } } \ No newline at end of file diff --git a/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/Restaurant.java b/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/Restaurant.java index aa642ae5..2ccf47e8 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/Restaurant.java +++ b/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/Restaurant.java @@ -1,79 +1,81 @@ package rewards.internal.restaurant; -import rewards.Dining; -import rewards.internal.account.Account; - import common.money.MonetaryAmount; import common.money.Percentage; import common.repository.Entity; +import rewards.Dining; +import rewards.internal.account.Account; /** * A restaurant establishment in the network. Like AppleBee's. - * + *

* Restaurants calculate how much benefit may be awarded to an account for dining based on a benefit percentage. */ public class Restaurant extends Entity { - private String number; + private String number; - private String name; + private String name; - private Percentage benefitPercentage; + private Percentage benefitPercentage; - @SuppressWarnings("unused") - private Restaurant() { - } + @SuppressWarnings("unused") + private Restaurant() { + } - /** - * Creates a new restaurant. - * @param number the restaurant's merchant number - * @param name the name of the restaurant - */ - public Restaurant(String number, String name) { - this.number = number; - this.name = name; - } + /** + * Creates a new restaurant. + * + * @param number the restaurant's merchant number + * @param name the name of the restaurant + */ + public Restaurant(String number, String name) { + this.number = number; + this.name = name; + } - /** - * Sets the percentage benefit to be awarded for eligible dining transactions. - * @param benefitPercentage the benefit percentage - */ - public void setBenefitPercentage(Percentage benefitPercentage) { - this.benefitPercentage = benefitPercentage; - } + /** + * Sets the percentage benefit to be awarded for eligible dining transactions. + * + * @param benefitPercentage the benefit percentage + */ + public void setBenefitPercentage(Percentage benefitPercentage) { + this.benefitPercentage = benefitPercentage; + } - /** - * Returns the name of this restaurant. - */ - public String getName() { - return name; - } + /** + * Returns the name of this restaurant. + */ + public String getName() { + return name; + } - /** - * Returns the merchant number of this restaurant. - */ - public String getNumber() { - return number; - } + /** + * Returns the merchant number of this restaurant. + */ + public String getNumber() { + return number; + } - /** - * Returns this restaurant's benefit percentage. - */ - public Percentage getBenefitPercentage() { - return benefitPercentage; - } + /** + * Returns this restaurant's benefit percentage. + */ + public Percentage getBenefitPercentage() { + return benefitPercentage; + } - /** - * Calculate the benefit eligible to this account for dining at this restaurant. - * @param account the account that dined at this restaurant - * @param dining a dining event that occurred - * @return the benefit amount eligible for reward - */ - public MonetaryAmount calculateBenefitFor(Account account, Dining dining) { - return dining.getAmount().multiplyBy(benefitPercentage); - } + /** + * Calculate the benefit eligible to this account for dining at this restaurant. + * + * @param account the account that dined at this restaurant + * @param dining a dining event that occurred + * @return the benefit amount eligible for reward + */ + public MonetaryAmount calculateBenefitFor(Account account, Dining dining) { + return dining.getAmount().multiplyBy(benefitPercentage); + } - public String toString() { - return "Number = '" + number + "', name = '" + name + "', benefitPercentage = " + benefitPercentage; - } + public String toString() { + return "Number = '" + number + "', name = '" + name + "', benefitPercentage = " + benefitPercentage; + } } \ No newline at end of file diff --git a/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java b/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java index a632fcc1..04968ac5 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java +++ b/lab/16-annotations-solution/src/main/java/rewards/internal/restaurant/RestaurantRepository.java @@ -3,15 +3,16 @@ /** * Loads restaurant aggregates. Called by the reward network to find and reconstitute Restaurant entities from an * external form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. */ public interface RestaurantRepository { /** * Load a Restaurant entity by its merchant number. + * * @param merchantNumber the merchant number * @return the restaurant */ - public Restaurant findByMerchantNumber(String merchantNumber); + Restaurant findByMerchantNumber(String merchantNumber); } diff --git a/lab/16-annotations-solution/src/main/java/rewards/internal/reward/JdbcRewardRepository.java b/lab/16-annotations-solution/src/main/java/rewards/internal/reward/JdbcRewardRepository.java index 7cf26003..b2740b4f 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/internal/reward/JdbcRewardRepository.java +++ b/lab/16-annotations-solution/src/main/java/rewards/internal/reward/JdbcRewardRepository.java @@ -7,8 +7,12 @@ import rewards.Dining; import rewards.RewardConfirmation; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import javax.sql.DataSource; -import java.sql.*; /** * JDBC implementation of a reward repository that records the result of a reward transaction by inserting a reward @@ -37,11 +41,11 @@ public RewardConfirmation confirmReward(AccountContribution contribution, Dining ps = conn.prepareStatement(sql); String confirmationNumber = nextConfirmationNumber(); ps.setString(1, confirmationNumber); - ps.setBigDecimal(2, contribution.getAmount().asBigDecimal()); + ps.setBigDecimal(2, contribution.amount().asBigDecimal()); ps.setDate(3, new Date(SimpleDate.today().inMilliseconds())); - ps.setString(4, contribution.getAccountNumber()); + ps.setString(4, contribution.accountNumber()); ps.setString(5, dining.getMerchantNumber()); - ps.setDate(6, new Date(dining.getDate().inMilliseconds())); + ps.setDate(6, new Date(dining.date().inMilliseconds())); ps.setBigDecimal(7, dining.getAmount().asBigDecimal()); ps.execute(); return new RewardConfirmation(confirmationNumber, contribution); diff --git a/lab/16-annotations-solution/src/main/java/rewards/internal/reward/RewardRepository.java b/lab/16-annotations-solution/src/main/java/rewards/internal/reward/RewardRepository.java index c8189e52..e32368d2 100644 --- a/lab/16-annotations-solution/src/main/java/rewards/internal/reward/RewardRepository.java +++ b/lab/16-annotations-solution/src/main/java/rewards/internal/reward/RewardRepository.java @@ -9,12 +9,13 @@ */ public interface RewardRepository { - /** - * Create a record of a reward that will track a contribution made to an account for dining. - * @param contribution the account contribution that was made - * @param dining the dining event that resulted in the account contribution - * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later - * date - */ - public RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); + /** + * Create a record of a reward that will track a contribution made to an account for dining. + * + * @param contribution the account contribution that was made + * @param dining the dining event that resulted in the account contribution + * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later + * date + */ + RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); } \ No newline at end of file diff --git a/lab/16-annotations-solution/src/test/java/rewards/RewardNetworkTests.java b/lab/16-annotations-solution/src/test/java/rewards/RewardNetworkTests.java index 8fa51cea..a5ea3c4c 100644 --- a/lab/16-annotations-solution/src/test/java/rewards/RewardNetworkTests.java +++ b/lab/16-annotations-solution/src/test/java/rewards/RewardNetworkTests.java @@ -1,67 +1,63 @@ package rewards; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - +import common.money.MonetaryAmount; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; -import common.money.MonetaryAmount; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * A system test that verifies the components of the RewardNetwork application work together to reward for dining * successfully. Uses Spring to bootstrap the application for use in a test environment. */ -public class RewardNetworkTests { +class RewardNetworkTests { /** * The object being tested. */ - private RewardNetwork rewardNetwork; + RewardNetwork rewardNetwork; - @BeforeEach - public void setUp() { - // Create the test configuration for the application from two classes: + void setUp() { + // Create application context from TestInfrastructureConfig, + // which also imports RewardsConfig ApplicationContext context = SpringApplication.run(TestInfrastructureConfig.class); - // Get the bean to use to invoke the application + // Get rewardNetwork bean from the application context rewardNetwork = context.getBean(RewardNetwork.class); - } - - @Test - public void rewardForDining() { + void testRewardForDining() { // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); // call the 'rewardNetwork' to test its rewardAccountFor(Dining) method + // this fails if you have selected an account without beneficiaries! RewardConfirmation confirmation = rewardNetwork.rewardAccountFor(dining); // assert the expected reward confirmation results assertNotNull(confirmation); - assertNotNull(confirmation.getConfirmationNumber()); + assertNotNull(confirmation.confirmationNumber()); // assert an account contribution was made - AccountContribution contribution = confirmation.getAccountContribution(); + AccountContribution contribution = confirmation.accountContribution(); assertNotNull(contribution); // the contribution account number should be '123456789' - assertEquals("123456789", contribution.getAccountNumber()); + assertEquals("123456789", contribution.accountNumber()); // the total contribution amount should be 8.00 (8% of 100.00) - assertEquals(MonetaryAmount.valueOf("8.00"), contribution.getAmount()); + assertEquals(MonetaryAmount.valueOf("8.00"), contribution.amount()); // the total contribution amount should have been split into 2 distributions - assertEquals(2, contribution.getDistributions().size()); + assertEquals(2, contribution.distributions().size()); // each distribution should be 4.00 (as both have a 50% allocation) - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").getAmount()); - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").getAmount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").amount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").amount()); } } diff --git a/lab/16-annotations-solution/src/test/java/rewards/TestInfrastructureConfig.java b/lab/16-annotations-solution/src/test/java/rewards/TestInfrastructureConfig.java index bd29e01b..04a22e62 100644 --- a/lab/16-annotations-solution/src/test/java/rewards/TestInfrastructureConfig.java +++ b/lab/16-annotations-solution/src/test/java/rewards/TestInfrastructureConfig.java @@ -1,28 +1,26 @@ package rewards; -import javax.sql.DataSource; - +import config.RewardsConfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import config.RewardsConfig; +import javax.sql.DataSource; @Configuration @Import(RewardsConfig.class) -public class TestInfrastructureConfig { +class TestInfrastructureConfig { - /** - * Creates an in-memory "rewards" database populated - * with test data for fast testing - */ - @Bean - public DataSource dataSource(){ - return - (new EmbeddedDatabaseBuilder()) - .addScript("classpath:rewards/testdb/schema.sql") - .addScript("classpath:rewards/testdb/data.sql") - .build(); - } + /** + * Creates an in-memory "rewards" database populated + * with test data for fast testing + */ + @Bean + DataSource dataSource() { + return new EmbeddedDatabaseBuilder() + .addScript("classpath:rewards/testdb/schema.sql") + .addScript("classpath:rewards/testdb/data.sql") + .build(); + } } diff --git a/lab/16-annotations-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java b/lab/16-annotations-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java index 847bfafc..82ff840a 100644 --- a/lab/16-annotations-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java +++ b/lab/16-annotations-solution/src/test/java/rewards/internal/RewardNetworkImplTests.java @@ -1,11 +1,8 @@ package rewards.internal; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - +import common.money.MonetaryAmount; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import rewards.AccountContribution; import rewards.Dining; import rewards.RewardConfirmation; @@ -13,16 +10,17 @@ import rewards.internal.restaurant.RestaurantRepository; import rewards.internal.reward.RewardRepository; -import common.money.MonetaryAmount; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Unit tests for the RewardNetworkImpl application logic. Configures the implementation with stub repositories * containing dummy data for fast in-memory testing without the overhead of an external data source. - * + *

* Besides helping catch bugs early, tests are a great way for a new developer to learn an API as he or she can see the * API in action. Tests also help validate a design as they are a measure for how easy it is to use your code. */ -public class RewardNetworkImplTests { +class RewardNetworkImplTests { /** * The object being tested. @@ -30,7 +28,7 @@ public class RewardNetworkImplTests { private RewardNetworkImpl rewardNetwork; @BeforeEach - public void setUp() throws Exception { + void setUp() { // create stubs to facilitate fast in-memory testing with dummy data and no external dependencies AccountRepository accountRepo = new StubAccountRepository(); RestaurantRepository restaurantRepo = new StubRestaurantRepository(); @@ -41,7 +39,7 @@ public void setUp() throws Exception { } @Test - public void testRewardForDining() { + void testRewardForDining() { // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); @@ -50,23 +48,23 @@ public void testRewardForDining() { // assert the expected reward confirmation results assertNotNull(confirmation); - assertNotNull(confirmation.getConfirmationNumber()); + assertNotNull(confirmation.confirmationNumber()); // assert an account contribution was made - AccountContribution contribution = confirmation.getAccountContribution(); + AccountContribution contribution = confirmation.accountContribution(); assertNotNull(contribution); // the account number should be '123456789' - assertEquals("123456789", contribution.getAccountNumber()); + assertEquals("123456789", contribution.accountNumber()); // the total contribution amount should be 8.00 (8% of 100.00) - assertEquals(MonetaryAmount.valueOf("8.00"), contribution.getAmount()); + assertEquals(MonetaryAmount.valueOf("8.00"), contribution.amount()); // the total contribution amount should have been split into 2 distributions - assertEquals(2, contribution.getDistributions().size()); + assertEquals(2, contribution.distributions().size()); // each distribution should be 4.00 (as both have a 50% allocation) - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").getAmount()); - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").getAmount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").amount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").amount()); } -} \ No newline at end of file +} diff --git a/lab/16-annotations-solution/src/test/java/rewards/internal/StubAccountRepository.java b/lab/16-annotations-solution/src/test/java/rewards/internal/StubAccountRepository.java index 66ca1dcd..4931f4fc 100644 --- a/lab/16-annotations-solution/src/test/java/rewards/internal/StubAccountRepository.java +++ b/lab/16-annotations-solution/src/test/java/rewards/internal/StubAccountRepository.java @@ -1,26 +1,23 @@ package rewards.internal; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.dao.EmptyResultDataAccessException; - +import common.money.Percentage; import rewards.internal.account.Account; import rewards.internal.account.AccountRepository; -import common.money.Percentage; +import java.util.HashMap; +import java.util.Map; /** * A dummy account repository implementation. Has a single Account "Keith and Keri Donald" with two beneficiaries * "Annabelle" (50% allocation) and "Corgan" (50% allocation) associated with credit card "1234123412341234". - * + *

* Stubs facilitate unit testing. An object needing an AccountRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubAccountRepository implements AccountRepository { - private Map accountsByCreditCard = new HashMap(); + Map accountsByCreditCard = new HashMap<>(); public StubAccountRepository() { Account account = new Account("123456789", "Keith and Keri Donald"); @@ -32,7 +29,7 @@ public StubAccountRepository() { public Account findByCreditCard(String creditCardNumber) { Account account = accountsByCreditCard.get(creditCardNumber); if (account == null) { - throw new EmptyResultDataAccessException(1); + throw new RuntimeException("no account has been found for credit card number " + creditCardNumber); } return account; } diff --git a/lab/16-annotations-solution/src/test/java/rewards/internal/StubRestaurantRepository.java b/lab/16-annotations-solution/src/test/java/rewards/internal/StubRestaurantRepository.java index 92375baa..e568a405 100644 --- a/lab/16-annotations-solution/src/test/java/rewards/internal/StubRestaurantRepository.java +++ b/lab/16-annotations-solution/src/test/java/rewards/internal/StubRestaurantRepository.java @@ -1,26 +1,23 @@ package rewards.internal; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.dao.EmptyResultDataAccessException; - +import common.money.Percentage; import rewards.internal.restaurant.Restaurant; import rewards.internal.restaurant.RestaurantRepository; -import common.money.Percentage; +import java.util.HashMap; +import java.util.Map; /** * A dummy restaurant repository implementation. Has a single restaurant "Apple Bees" with a 8% benefit availability * percentage that's always available. - * + *

* Stubs facilitate unit testing. An object needing a RestaurantRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubRestaurantRepository implements RestaurantRepository { - private Map restaurantsByMerchantNumber = new HashMap(); + Map restaurantsByMerchantNumber = new HashMap<>(); public StubRestaurantRepository() { Restaurant restaurant = new Restaurant("1234567890", "Apple Bees"); @@ -29,9 +26,9 @@ public StubRestaurantRepository() { } public Restaurant findByMerchantNumber(String merchantNumber) { - Restaurant restaurant = (Restaurant) restaurantsByMerchantNumber.get(merchantNumber); + Restaurant restaurant = restaurantsByMerchantNumber.get(merchantNumber); if (restaurant == null) { - throw new EmptyResultDataAccessException(1); + throw new RuntimeException("no restaurant has been found for merchant number " + merchantNumber); } return restaurant; } diff --git a/lab/16-annotations-solution/src/test/java/rewards/internal/StubRewardRepository.java b/lab/16-annotations-solution/src/test/java/rewards/internal/StubRewardRepository.java index 2487aca0..d149acc7 100644 --- a/lab/16-annotations-solution/src/test/java/rewards/internal/StubRewardRepository.java +++ b/lab/16-annotations-solution/src/test/java/rewards/internal/StubRewardRepository.java @@ -1,12 +1,12 @@ package rewards.internal; -import java.util.Random; - import rewards.AccountContribution; import rewards.Dining; import rewards.RewardConfirmation; import rewards.internal.reward.RewardRepository; +import java.util.Random; + /** * A dummy reward repository implementation. */ diff --git a/lab/16-annotations-solution/src/test/java/rewards/internal/restaurant/JdbcRestaurantRepositoryTests.java b/lab/16-annotations-solution/src/test/java/rewards/internal/restaurant/JdbcRestaurantRepositoryTests.java index 57993f7c..39a605c7 100644 --- a/lab/16-annotations-solution/src/test/java/rewards/internal/restaurant/JdbcRestaurantRepositoryTests.java +++ b/lab/16-annotations-solution/src/test/java/rewards/internal/restaurant/JdbcRestaurantRepositoryTests.java @@ -9,20 +9,21 @@ import javax.sql.DataSource; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Tests the JDBC restaurant repository with a test data source to verify data access and relational-to-object mapping * behavior works as expected. */ -public class JdbcRestaurantRepositoryTests { +class JdbcRestaurantRepositoryTests { - private JdbcRestaurantRepository repository; + JdbcRestaurantRepository repository; @BeforeEach - public void setUp() throws Exception { + void setUp() { // simulate the Spring bean initialization lifecycle: - // first, construct the bean repository = new JdbcRestaurantRepository(); @@ -34,36 +35,30 @@ public void setUp() throws Exception { } @AfterEach - public void tearDown() throws Exception { + void tearDown() { // simulate the Spring bean destruction lifecycle: - - // destroy the bean repository.clearRestaurantCache(); } @Test - public void findRestaurantByMerchantNumber() { + void findRestaurantByMerchantNumber() { Restaurant restaurant = repository.findByMerchantNumber("1234567890"); - assertNotNull(restaurant, "restaurant is null - repository cache not likely initialized"); - assertEquals("1234567890", restaurant.getNumber(), "number is wrong"); + assertNotNull(restaurant, "restaurant is null - check your repositories cache"); + assertEquals("1234567890", restaurant.getNumber(),"number is wrong"); assertEquals("AppleBees", restaurant.getName(), "name is wrong"); assertEquals(Percentage.valueOf("8%"), restaurant.getBenefitPercentage(), "benefitPercentage is wrong"); } @Test - public void findRestaurantByBogusMerchantNumber() { - assertThrows(EmptyResultDataAccessException.class, ()-> { - repository.findByMerchantNumber("bogus"); - }); + void findRestaurantByBogusMerchantNumber() { + assertThrows(EmptyResultDataAccessException.class, ()-> repository.findByMerchantNumber("bogus")); } @Test - public void restaurantCacheClearedAfterDestroy() throws Exception { + void restaurantCacheClearedAfterDestroy() { // force early tear down tearDown(); - assertThrows(EmptyResultDataAccessException.class, ()-> { - repository.findByMerchantNumber("1234567890"); - }); + assertThrows(EmptyResultDataAccessException.class, ()-> repository.findByMerchantNumber("1234567890")); } private DataSource createTestDataSource() { diff --git a/lab/16-annotations/src/main/java/config/RewardsConfig.java b/lab/16-annotations/src/main/java/config/RewardsConfig.java index 1248efe9..0772d69a 100644 --- a/lab/16-annotations/src/main/java/config/RewardsConfig.java +++ b/lab/16-annotations/src/main/java/config/RewardsConfig.java @@ -47,8 +47,7 @@ public AccountRepository accountRepository(){ @Bean public RestaurantRepository restaurantRepository(){ - JdbcRestaurantRepository repository = new JdbcRestaurantRepository(dataSource); - return repository; + return new JdbcRestaurantRepository(dataSource); } @Bean diff --git a/lab/16-annotations/src/main/java/rewards/AccountContribution.java b/lab/16-annotations/src/main/java/rewards/AccountContribution.java index 5cad1918..b68c7512 100644 --- a/lab/16-annotations/src/main/java/rewards/AccountContribution.java +++ b/lab/16-annotations/src/main/java/rewards/AccountContribution.java @@ -1,61 +1,20 @@ package rewards; -import java.util.Set; - import common.money.MonetaryAmount; import common.money.Percentage; +import java.util.Set; + /** * A summary of a monetary contribution made to an account that was distributed among the account's beneficiaries. - * + *

* A value object. Immutable. */ -public class AccountContribution { - - private String accountNumber; - - private MonetaryAmount amount; - - private Set distributions; - - /** - * Creates a new account contribution. - * @param accountNumber the number of the account the contribution was made - * @param amount the total contribution amount - * @param distributions how the contribution was distributed among the account's beneficiaries - */ - public AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { - this.accountNumber = accountNumber; - this.amount = amount; - this.distributions = distributions; - } - - /** - * Returns the number of the account this contribution was made to. - * @return the account number - */ - public String getAccountNumber() { - return accountNumber; - } - - /** - * Returns the total amount of the contribution. - * @return the contribution amount - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns how this contribution was distributed among the account's beneficiaries. - * @return the contribution distributions - */ - public Set getDistributions() { - return distributions; - } +public record AccountContribution(String accountNumber, MonetaryAmount amount, Set distributions) { /** * Returns how this contribution was distributed to a single account beneficiary. + * * @param beneficiary the name of the beneficiary e.g "Annabelle" * @return a summary of how the contribution amount was distributed to the beneficiary */ @@ -71,61 +30,11 @@ public Distribution getDistribution(String beneficiary) { /** * A single distribution made to a beneficiary as part of an account contribution, summarizing the distribution * amount and resulting total beneficiary savings. - * + *

* A value object. */ - public static class Distribution { - - private String beneficiary; - - private MonetaryAmount amount; - - private Percentage percentage; - - private MonetaryAmount totalSavings; - - /** - * Creates a new distribution. - * @param beneficiary the name of the account beneficiary that received a distribution - * @param amount the distribution amount - * @param percentage this distribution's percentage of the total account contribution - * @param totalSavings the beneficiary's total savings amount after the distribution was made - */ - public Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, - MonetaryAmount totalSavings) { - this.beneficiary = beneficiary; - this.percentage = percentage; - this.amount = amount; - this.totalSavings = totalSavings; - } - - /** - * Returns the name of the beneficiary. - */ - public String getBeneficiary() { - return beneficiary; - } - - /** - * Returns the amount of this distribution. - */ - public MonetaryAmount getAmount() { - return amount; - } - - /** - * Returns the percentage of this distribution relative to others in the contribution. - */ - public Percentage getPercentage() { - return percentage; - } - - /** - * Returns the total savings of the beneficiary after this distribution. - */ - public MonetaryAmount getTotalSavings() { - return totalSavings; - } + public record Distribution(String beneficiary, MonetaryAmount amount, Percentage percentage, + MonetaryAmount totalSavings) { public String toString() { return amount + " to '" + beneficiary + "' (" + percentage + ")"; diff --git a/lab/16-annotations/src/main/java/rewards/Dining.java b/lab/16-annotations/src/main/java/rewards/Dining.java index 478463c3..379762bf 100644 --- a/lab/16-annotations/src/main/java/rewards/Dining.java +++ b/lab/16-annotations/src/main/java/rewards/Dining.java @@ -3,44 +3,25 @@ import common.datetime.SimpleDate; import common.money.MonetaryAmount; +import java.io.Serializable; + /** * A dining event that occurred, representing a charge made to an credit card by a merchant on a specific date. - * * For a dining to be eligible for reward, the credit card number should map to an account in the reward network. In * addition, the merchant number should map to a restaurant in the network. - * * A value object. Immutable. */ -public class Dining { - - private MonetaryAmount amount; - - private String creditCardNumber; - - private String merchantNumber; +public record Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, + SimpleDate date) implements Serializable { - private SimpleDate date; - - /** - * Creates a new dining, reflecting an amount that was charged to a card by a merchant on the date specified. - * @param amount the total amount of the dining bill - * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param date the date of the dining event - */ - public Dining(MonetaryAmount amount, String creditCardNumber, String merchantNumber, SimpleDate date) { - this.amount = amount; - this.creditCardNumber = creditCardNumber; - this.merchantNumber = merchantNumber; - this.date = date; - } /** * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on today's date. A * convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param merchantNumber the merchant number of the restaurant where the dining occurred * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber) { @@ -50,65 +31,34 @@ public static Dining createDining(String amount, String creditCardNumber, String /** * Creates a new dining, reflecting an amount that was charged to a credit card by a merchant on the date specified. * A convenient static factory method. - * @param amount the total amount of the dining bill as a string + * + * @param amount the total amount of the dining bill as a string * @param creditCardNumber the number of the credit card used to pay for the dining bill - * @param merchantNumber the merchant number of the restaurant where the dining occurred - * @param month the month of the dining event - * @param day the day of the dining event - * @param year the year of the dining event + * @param merchantNumber the merchant number of the restaurant where the dining occurred + * @param month the month of the dining event + * @param day the day of the dining event + * @param year the year of the dining event * @return the dining event */ public static Dining createDining(String amount, String creditCardNumber, String merchantNumber, int month, - int day, int year) { + int day, int year) { return new Dining(MonetaryAmount.valueOf(amount), creditCardNumber, merchantNumber, new SimpleDate(month, day, year)); } - /** - * Returns the amount of this dining--the total amount of the bill that was charged to the credit card. - */ - public MonetaryAmount getAmount() { - return amount; + public String toString() { + return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; } - /** - * Returns the number of the credit card used to pay for this dining. For this dining to be eligible for reward, - * this credit card number should be associated with a valid account in the reward network. - */ public String getCreditCardNumber() { return creditCardNumber; } - /** - * Returns the merchant number of the restaurant where this dining occurred. For this dining to be eligible for - * reward, this merchant number should be associated with a valid restaurant in the reward network. - */ public String getMerchantNumber() { return merchantNumber; } - /** - * Returns the date this dining occurred on. - */ - public SimpleDate getDate() { - return date; - } - - public boolean equals(Object o) { - if (!(o instanceof Dining)) { - return false; - } - Dining other = (Dining) o; - // value objects are equal if their attributes are equal - return amount.equals(other.amount) && creditCardNumber.equals(other.creditCardNumber) - && merchantNumber.equals(other.merchantNumber) && date.equals(other.date); - } - - public int hashCode() { - return amount.hashCode() + creditCardNumber.hashCode() + merchantNumber.hashCode() + date.hashCode(); - } - - public String toString() { - return "Dining of " + amount + " charged to '" + creditCardNumber + "' by '" + merchantNumber + "' on " + date; + public MonetaryAmount getAmount() { + return amount; } } \ No newline at end of file diff --git a/lab/16-annotations/src/main/java/rewards/RewardConfirmation.java b/lab/16-annotations/src/main/java/rewards/RewardConfirmation.java index c6984dcd..43ec595a 100644 --- a/lab/16-annotations/src/main/java/rewards/RewardConfirmation.java +++ b/lab/16-annotations/src/main/java/rewards/RewardConfirmation.java @@ -1,39 +1,13 @@ package rewards; +import java.io.Serializable; + /** * A summary of a confirmed reward transaction describing a contribution made to an account that was distributed among * the account's beneficiaries. */ -public class RewardConfirmation { - - private String confirmationNumber; - - private AccountContribution accountContribution; - - /** - * Creates a new reward confirmation. - * @param confirmationNumber the unique confirmation number - * @param accountContribution a summary of the account contribution that was made - */ - public RewardConfirmation(String confirmationNumber, AccountContribution accountContribution) { - this.confirmationNumber = confirmationNumber; - this.accountContribution = accountContribution; - } - - /** - * Returns the confirmation number of the reward transaction. Can be used later to lookup the transaction record. - */ - public String getConfirmationNumber() { - return confirmationNumber; - } - - /** - * Returns a summary of the monetary contribution that was made to an account. - * @return the account contribution (the details of this reward) - */ - public AccountContribution getAccountContribution() { - return accountContribution; - } +public record RewardConfirmation(String confirmationNumber, + AccountContribution accountContribution) implements Serializable { public String toString() { return confirmationNumber; diff --git a/lab/16-annotations/src/main/java/rewards/RewardNetwork.java b/lab/16-annotations/src/main/java/rewards/RewardNetwork.java index 252fe280..1cb8d831 100644 --- a/lab/16-annotations/src/main/java/rewards/RewardNetwork.java +++ b/lab/16-annotations/src/main/java/rewards/RewardNetwork.java @@ -2,14 +2,14 @@ /** * Rewards a member account for dining at a restaurant. - * + *

* A reward takes the form of a monetary contribution made to an account that is distributed among the account's * beneficiaries. The contribution amount is typically a function of several factors such as the dining amount and * restaurant where the dining occurred. - * + *

* Example: Papa Keith spends $100.00 at Apple Bee's resulting in a $8.00 contribution to his account that is * distributed evenly among his beneficiaries Annabelle and Corgan. - * + *

* This is the central application-boundary for the "rewards" application. This is the public interface users call to * invoke the application. This is the entry-point into the Application Layer. */ @@ -17,12 +17,12 @@ public interface RewardNetwork { /** * Reward an account for dining. - * + *

* For a dining to be eligible for reward: - It must have been paid for by a registered credit card of a valid * member account in the network. - It must have taken place at a restaurant participating in the network. - * + * * @param dining a charge made to a credit card for dining at a restaurant * @return confirmation of the reward */ - public RewardConfirmation rewardAccountFor(Dining dining); + RewardConfirmation rewardAccountFor(Dining dining); } \ No newline at end of file diff --git a/lab/16-annotations/src/main/java/rewards/internal/RewardNetworkImpl.java b/lab/16-annotations/src/main/java/rewards/internal/RewardNetworkImpl.java index 4db6047e..7b98be91 100644 --- a/lab/16-annotations/src/main/java/rewards/internal/RewardNetworkImpl.java +++ b/lab/16-annotations/src/main/java/rewards/internal/RewardNetworkImpl.java @@ -14,12 +14,12 @@ /** * Rewards an Account for Dining at a Restaurant. - * + *

* The sole Reward Network implementation. This class is an * application-layer service responsible for coordinating with * the domain-layer to carry out the process of rewarding benefits * to accounts for dining. - * + *

* Said in other words, this class implements the "reward account * for dining" use case. */ @@ -33,32 +33,32 @@ public class RewardNetworkImpl implements RewardNetwork { - private AccountRepository accountRepository; + private final AccountRepository accountRepository; - private RestaurantRepository restaurantRepository; + private final RestaurantRepository restaurantRepository; - private RewardRepository rewardRepository; + private final RewardRepository rewardRepository; - /** - * Creates a new reward network. - * @param accountRepository the repository for loading accounts to reward - * @param restaurantRepository the repository for loading restaurants that determine how much to reward - * @param rewardRepository the repository for recording a record of successful reward transactions - */ - - public RewardNetworkImpl(AccountRepository accountRepository, RestaurantRepository restaurantRepository, - RewardRepository rewardRepository) { - this.accountRepository = accountRepository; - this.restaurantRepository = restaurantRepository; - this.rewardRepository = rewardRepository; - } + /** + * Creates a new reward network. + * + * @param accountRepository the repository for loading accounts to reward + * @param restaurantRepository the repository for loading restaurants that determine how much to reward + * @param rewardRepository the repository for recording a record of successful reward transactions + */ + public RewardNetworkImpl(AccountRepository accountRepository, RestaurantRepository restaurantRepository, + RewardRepository rewardRepository) { + this.accountRepository = accountRepository; + this.restaurantRepository = restaurantRepository; + this.rewardRepository = rewardRepository; + } - public RewardConfirmation rewardAccountFor(Dining dining) { - Account account = accountRepository.findByCreditCard(dining.getCreditCardNumber()); - Restaurant restaurant = restaurantRepository.findByMerchantNumber(dining.getMerchantNumber()); - MonetaryAmount amount = restaurant.calculateBenefitFor(account, dining); - AccountContribution contribution = account.makeContribution(amount); - accountRepository.updateBeneficiaries(account); - return rewardRepository.confirmReward(contribution, dining); - } + public RewardConfirmation rewardAccountFor(Dining dining) { + Account account = accountRepository.findByCreditCard(dining.getCreditCardNumber()); + Restaurant restaurant = restaurantRepository.findByMerchantNumber(dining.getMerchantNumber()); + MonetaryAmount amount = restaurant.calculateBenefitFor(account, dining); + AccountContribution contribution = account.makeContribution(amount); + accountRepository.updateBeneficiaries(account); + return rewardRepository.confirmReward(contribution, dining); + } } \ No newline at end of file diff --git a/lab/16-annotations/src/main/java/rewards/internal/account/Account.java b/lab/16-annotations/src/main/java/rewards/internal/account/Account.java index eb685069..9b48c01f 100644 --- a/lab/16-annotations/src/main/java/rewards/internal/account/Account.java +++ b/lab/16-annotations/src/main/java/rewards/internal/account/Account.java @@ -1,23 +1,22 @@ package rewards.internal.account; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import rewards.AccountContribution; -import rewards.AccountContribution.Distribution; - import common.money.MonetaryAmount; import common.money.Percentage; import common.repository.Entity; +import rewards.AccountContribution; +import rewards.AccountContribution.Distribution; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; /** * An account for a member of the reward network. An account has one or more beneficiaries whose allocations must add up * to 100%. - * + *

* An account can make contributions to its beneficiaries. Each contribution is distributed among the beneficiaries * based on an allocation. - * + *

* An entity. An aggregate. */ public class Account extends Entity { @@ -26,7 +25,7 @@ public class Account extends Entity { private String name; - private Set beneficiaries = new HashSet(); + private final Set beneficiaries = new HashSet<>(); @SuppressWarnings("unused") private Account() { @@ -34,8 +33,9 @@ private Account() { /** * Create a new account. + * * @param number the account number - * @param name the name on the account + * @param name the name on the account */ public Account(String number, String name) { this.number = number; @@ -58,6 +58,7 @@ public String getName() { /** * Add a single beneficiary with a 100% allocation percentage. + * * @param beneficiaryName the name of the beneficiary (should be unique) */ public void addBeneficiary(String beneficiaryName) { @@ -66,7 +67,8 @@ public void addBeneficiary(String beneficiaryName) { /** * Add a single beneficiary with the specified allocation percentage. - * @param beneficiaryName the name of the beneficiary (should be unique) + * + * @param beneficiaryName the name of the beneficiary (should be unique) * @param allocationPercentage the beneficiary's allocation percentage within this account */ public void addBeneficiary(String beneficiaryName, Percentage allocationPercentage) { @@ -81,16 +83,13 @@ public boolean isValid() { for (Beneficiary b : beneficiaries) { totalPercentage = totalPercentage.add(b.getAllocationPercentage()); } - if (totalPercentage.equals(Percentage.oneHundred())) { - return true; - } else { - return false; - } + return totalPercentage.equals(Percentage.oneHundred()); } /** * Make a monetary contribution to this account. The contribution amount is distributed among the account's * beneficiaries based on each beneficiary's allocation percentage. + * * @param amount the total amount to contribute */ public AccountContribution makeContribution(MonetaryAmount amount) { @@ -104,11 +103,12 @@ public AccountContribution makeContribution(MonetaryAmount amount) { /** * Distribute the contribution amount among this account's beneficiaries. + * * @param amount the total contribution amount * @return the individual beneficiary distributions */ private Set distribute(MonetaryAmount amount) { - Set distributions = new HashSet(beneficiaries.size()); + Set distributions = HashSet.newHashSet(beneficiaries.size()); for (Beneficiary beneficiary : beneficiaries) { MonetaryAmount distributionAmount = amount.multiplyBy(beneficiary.getAllocationPercentage()); beneficiary.credit(distributionAmount); @@ -124,6 +124,7 @@ private Set distribute(MonetaryAmount amount) { *

* Callers should not attempt to hold on or modify the returned set. This method should only be used transitively; * for example, called to facilitate account reporting. + * * @return the beneficiaries of this account */ public Set getBeneficiaries() { @@ -133,6 +134,7 @@ public Set getBeneficiaries() { /** * Used to restore an allocated beneficiary. Should only be called by the repository responsible for reconstituting * this account. + * * @param beneficiary the beneficiary */ void restoreBeneficiary(Beneficiary beneficiary) { diff --git a/lab/16-annotations/src/main/java/rewards/internal/account/AccountRepository.java b/lab/16-annotations/src/main/java/rewards/internal/account/AccountRepository.java index 5ba489ec..7c6bba59 100644 --- a/lab/16-annotations/src/main/java/rewards/internal/account/AccountRepository.java +++ b/lab/16-annotations/src/main/java/rewards/internal/account/AccountRepository.java @@ -3,17 +3,18 @@ /** * Loads account aggregates. Called by the reward network to find and reconstitute Account entities from an external * form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. */ public interface AccountRepository { /** * Load an account by its credit card. + * * @param creditCardNumber the credit card number * @return the account object */ - public Account findByCreditCard(String creditCardNumber); + Account findByCreditCard(String creditCardNumber); /** * Updates the 'savings' of each account beneficiary. The new savings balance contains the amount distributed for a @@ -22,8 +23,9 @@ public interface AccountRepository { * Note: use of an object-relational mapper (ORM) with support for transparent-persistence like Hibernate (or the * new Java Persistence API (JPA)) would remove the need for this explicit update operation as the ORM would take * care of applying relational updates to a modified Account entity automatically. + * * @param account the account whose beneficiary savings have changed */ - public void updateBeneficiaries(Account account); + void updateBeneficiaries(Account account); } \ No newline at end of file diff --git a/lab/16-annotations/src/main/java/rewards/internal/account/JdbcAccountRepository.java b/lab/16-annotations/src/main/java/rewards/internal/account/JdbcAccountRepository.java index 29247845..a6f404cd 100644 --- a/lab/16-annotations/src/main/java/rewards/internal/account/JdbcAccountRepository.java +++ b/lab/16-annotations/src/main/java/rewards/internal/account/JdbcAccountRepository.java @@ -4,11 +4,11 @@ import common.money.Percentage; import org.springframework.dao.EmptyResultDataAccessException; -import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import javax.sql.DataSource; /** * Loads accounts from a data source using the JDBC API. @@ -23,128 +23,128 @@ public class JdbcAccountRepository implements AccountRepository { - private DataSource dataSource; + private DataSource dataSource; - /** - * Sets the data source this repository will use to load accounts. - * - * @param dataSource the data source - */ - public void setDataSource(DataSource dataSource) { - this.dataSource = dataSource; - } + /** + * Sets the data source this repository will use to load accounts. + * + * @param dataSource the data source + */ + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } - public Account findByCreditCard(String creditCardNumber) { - String sql = "select a.ID as ID, a.NUMBER as ACCOUNT_NUMBER, a.NAME as ACCOUNT_NAME, c.NUMBER as CREDIT_CARD_NUMBER, b.NAME as BENEFICIARY_NAME, b.ALLOCATION_PERCENTAGE as BENEFICIARY_ALLOCATION_PERCENTAGE, b.SAVINGS as BENEFICIARY_SAVINGS from T_ACCOUNT a, T_ACCOUNT_BENEFICIARY b, T_ACCOUNT_CREDIT_CARD c where ID = b.ACCOUNT_ID and ID = c.ACCOUNT_ID and c.NUMBER = ?"; - Account account = null; - Connection conn = null; - PreparedStatement ps = null; - ResultSet rs = null; - try { - conn = dataSource.getConnection(); - ps = conn.prepareStatement(sql); - ps.setString(1, creditCardNumber); - rs = ps.executeQuery(); - account = mapAccount(rs); - } catch (SQLException e) { - throw new RuntimeException("SQL exception occurred finding by credit card 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 account; - } + public Account findByCreditCard(String creditCardNumber) { + String sql = "select a.ID as ID, a.NUMBER as ACCOUNT_NUMBER, a.NAME as ACCOUNT_NAME, c.NUMBER as CREDIT_CARD_NUMBER, b.NAME as BENEFICIARY_NAME, b.ALLOCATION_PERCENTAGE as BENEFICIARY_ALLOCATION_PERCENTAGE, b.SAVINGS as BENEFICIARY_SAVINGS from T_ACCOUNT a, T_ACCOUNT_BENEFICIARY b, T_ACCOUNT_CREDIT_CARD c where ID = b.ACCOUNT_ID and ID = c.ACCOUNT_ID and c.NUMBER = ?"; + Account account; + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = dataSource.getConnection(); + ps = conn.prepareStatement(sql); + ps.setString(1, creditCardNumber); + rs = ps.executeQuery(); + account = mapAccount(rs); + } catch (SQLException e) { + throw new RuntimeException("SQL exception occurred finding by credit card 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 account; + } - public void updateBeneficiaries(Account account) { - String sql = "update T_ACCOUNT_BENEFICIARY SET SAVINGS = ? where ACCOUNT_ID = ? and NAME = ?"; - Connection conn = null; - PreparedStatement ps = null; - try { - conn = dataSource.getConnection(); - ps = conn.prepareStatement(sql); - for (Beneficiary beneficiary : account.getBeneficiaries()) { - ps.setBigDecimal(1, beneficiary.getSavings().asBigDecimal()); - ps.setLong(2, account.getEntityId()); - ps.setString(3, beneficiary.getName()); - ps.executeUpdate(); - } - } catch (SQLException e) { - throw new RuntimeException("SQL exception occurred updating beneficiary savings", e); - } finally { - 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) { - } - } - } - } + public void updateBeneficiaries(Account account) { + String sql = "update T_ACCOUNT_BENEFICIARY SET SAVINGS = ? where ACCOUNT_ID = ? and NAME = ?"; + Connection conn = null; + PreparedStatement ps = null; + try { + conn = dataSource.getConnection(); + ps = conn.prepareStatement(sql); + for (Beneficiary beneficiary : account.getBeneficiaries()) { + ps.setBigDecimal(1, beneficiary.getSavings().asBigDecimal()); + ps.setLong(2, account.getEntityId()); + ps.setString(3, beneficiary.getName()); + ps.executeUpdate(); + } + } catch (SQLException e) { + throw new RuntimeException("SQL exception occurred updating beneficiary savings", e); + } finally { + 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) { + } + } + } + } - /** - * Map the rows returned from the join of T_ACCOUNT and T_ACCOUNT_BENEFICIARY - * to an fully-reconstituted Account aggregate. - * - * @param rs the set of rows returned from the query - * @return the mapped Account aggregate - * @throws SQLException an exception occurred extracting data from the result set - */ - private Account mapAccount(ResultSet rs) throws SQLException { - Account account = null; - while (rs.next()) { - if (account == null) { - String number = rs.getString("ACCOUNT_NUMBER"); - String name = rs.getString("ACCOUNT_NAME"); - account = new Account(number, name); - // set internal entity identifier (primary key) - account.setEntityId(rs.getLong("ID")); - } - account.restoreBeneficiary(mapBeneficiary(rs)); - } - if (account == null) { - // no rows returned - throw an empty result exception - throw new EmptyResultDataAccessException(1); - } - return account; - } + /** + * Map the rows returned from the join of T_ACCOUNT and T_ACCOUNT_BENEFICIARY + * to an fully-reconstituted Account aggregate. + * + * @param rs the set of rows returned from the query + * @return the mapped Account aggregate + * @throws SQLException an exception occurred extracting data from the result set + */ + private Account mapAccount(ResultSet rs) throws SQLException { + Account account = null; + while (rs.next()) { + if (account == null) { + String number = rs.getString("ACCOUNT_NUMBER"); + String name = rs.getString("ACCOUNT_NAME"); + account = new Account(number, name); + // set internal entity identifier (primary key) + account.setEntityId(rs.getLong("ID")); + } + account.restoreBeneficiary(mapBeneficiary(rs)); + } + if (account == null) { + // no rows returned - throw an empty result exception + throw new EmptyResultDataAccessException(1); + } + return account; + } - /** - * Maps the beneficiary columns in a single row to an AllocatedBeneficiary object. - * - * @param rs the result set with its cursor positioned at the current row - * @return an allocated beneficiary - * @throws SQLException an exception occurred extracting data from the result set - */ - private Beneficiary mapBeneficiary(ResultSet rs) throws SQLException { - String name = rs.getString("BENEFICIARY_NAME"); - MonetaryAmount savings = MonetaryAmount.valueOf(rs.getString("BENEFICIARY_SAVINGS")); - Percentage allocationPercentage = Percentage.valueOf(rs.getString("BENEFICIARY_ALLOCATION_PERCENTAGE")); - return new Beneficiary(name, allocationPercentage, savings); - } + /** + * Maps the beneficiary columns in a single row to an AllocatedBeneficiary object. + * + * @param rs the result set with its cursor positioned at the current row + * @return an allocated beneficiary + * @throws SQLException an exception occurred extracting data from the result set + */ + private Beneficiary mapBeneficiary(ResultSet rs) throws SQLException { + String name = rs.getString("BENEFICIARY_NAME"); + MonetaryAmount savings = MonetaryAmount.valueOf(rs.getString("BENEFICIARY_SAVINGS")); + Percentage allocationPercentage = Percentage.valueOf(rs.getString("BENEFICIARY_ALLOCATION_PERCENTAGE")); + return new Beneficiary(name, allocationPercentage, savings); + } } diff --git a/lab/16-annotations/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java b/lab/16-annotations/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java index eb44d161..4f9da475 100644 --- a/lab/16-annotations/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java +++ b/lab/16-annotations/src/main/java/rewards/internal/restaurant/JdbcRestaurantRepository.java @@ -3,17 +3,17 @@ import common.money.Percentage; import org.springframework.dao.EmptyResultDataAccessException; -import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; +import javax.sql.DataSource; /** * Loads restaurants from a data source using the JDBC API. - * + *

* This implementation should cache restaurants to improve performance. The * cache should be populated on initialization and cleared on destruction. */ @@ -46,144 +46,141 @@ public class JdbcRestaurantRepository implements RestaurantRepository { - private DataSource dataSource; - - /** - * The Restaurant object cache. Cached restaurants are indexed - * by their merchant numbers. - */ - private Map restaurantCache; - - /** - * The constructor sets the data source this repository will use to load - * restaurants. When the instance of JdbcRestaurantRepository is created, a - * Restaurant cache is populated for read only access - */ - - public JdbcRestaurantRepository(DataSource dataSource) { - this.dataSource = dataSource; - this.populateRestaurantCache(); - } - - public JdbcRestaurantRepository() { - } - - public void setDataSource(DataSource dataSource) { - this.dataSource = dataSource; - } - - public Restaurant findByMerchantNumber(String merchantNumber) { - return queryRestaurantCache(merchantNumber); - } - - /** - * Helper method that populates the restaurantCache restaurant object - * caches from the rows in the T_RESTAURANT table. Cached restaurants are indexed - * by their merchant numbers. This method should be called on initialization. - */ - - /* - * TODO-09: Make this method to be invoked after a bean gets created - * - Mark this method with an annotation that will cause it to be - * executed by Spring after constructor & setter initialization has occurred. - * - Re-run the RewardNetworkTests test. You should see the test succeeds. - * - Note that populating the cache is not really a valid - * construction activity, so using a post-construct, rather than - * the constructor, is a better practice. - */ - - void populateRestaurantCache() { - restaurantCache = new HashMap(); - String sql = "select MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE from T_RESTAURANT"; - Connection conn = null; - PreparedStatement ps = null; - ResultSet rs = null; - try { - conn = dataSource.getConnection(); - ps = conn.prepareStatement(sql); - rs = ps.executeQuery(); - while (rs.next()) { - Restaurant restaurant = mapRestaurant(rs); - // index the restaurant by its merchant number - restaurantCache.put(restaurant.getNumber(), restaurant); - } - } 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) { - } - } - } - } - - /** - * Helper method that simply queries the cache of restaurants. - * - * @param merchantNumber - * the restaurant's merchant number - * @return the restaurant - * @throws EmptyResultDataAccessException - * if no restaurant was found with that merchant number - */ - private Restaurant queryRestaurantCache(String merchantNumber) { - Restaurant restaurant = restaurantCache.get(merchantNumber); - if (restaurant == null) { - throw new EmptyResultDataAccessException(1); - } - return restaurant; - } - - /** - * Helper method that clears the cache of restaurants. - * This method should be called when a bean is destroyed. - * - * TODO-10: Add a scheme to check if this method is being invoked - * - Add System.out.println to this method. - * - * TODO-11: Have this method to be invoked before a bean gets destroyed - * - Re-run RewardNetworkTests. - * - Observe this method is not called. - * - Use an appropriate annotation to register this method for a - * destruction lifecycle callback. - * - Re-run the test and you should be able to see - * that this method is now being called. - */ - public void clearRestaurantCache() { - restaurantCache.clear(); - } - - /** - * 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); - return restaurant; - } + private DataSource dataSource; + + /** + * The Restaurant object cache. Cached restaurants are indexed + * by their merchant numbers. + */ + private Map restaurantCache; + + /** + * The constructor sets the data source this repository will use to load + * restaurants. When the instance of JdbcRestaurantRepository is created, a + * Restaurant cache is populated for read only access + */ + + public JdbcRestaurantRepository(DataSource dataSource) { + this.dataSource = dataSource; + this.populateRestaurantCache(); + } + + public JdbcRestaurantRepository() { + } + + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + + public Restaurant findByMerchantNumber(String merchantNumber) { + return queryRestaurantCache(merchantNumber); + } + + /** + * Helper method that populates the restaurantCache restaurant object + * caches from the rows in the T_RESTAURANT table. Cached restaurants are indexed + * by their merchant numbers. This method should be called on initialization. + */ + + /* + * TODO-09: Make this method to be invoked after a bean gets created + * - Mark this method with an annotation that will cause it to be + * executed by Spring after constructor & setter initialization has occurred. + * - Re-run the RewardNetworkTests test. You should see the test succeeds. + * - Note that populating the cache is not really a valid + * construction activity, so using a post-construct, rather than + * the constructor, is a better practice. + */ + + void populateRestaurantCache() { + restaurantCache = new HashMap<>(); + String sql = "select MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE from T_RESTAURANT"; + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + try { + conn = dataSource.getConnection(); + ps = conn.prepareStatement(sql); + rs = ps.executeQuery(); + while (rs.next()) { + Restaurant restaurant = mapRestaurant(rs); + // index the restaurant by its merchant number + restaurantCache.put(restaurant.getNumber(), restaurant); + } + } 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) { + } + } + } + } + + /** + * Helper method that simply queries the cache of restaurants. + * + * @param merchantNumber the restaurant's merchant number + * @return the restaurant + * @throws EmptyResultDataAccessException if no restaurant was found with that merchant number + */ + private Restaurant queryRestaurantCache(String merchantNumber) { + Restaurant restaurant = restaurantCache.get(merchantNumber); + if (restaurant == null) { + throw new EmptyResultDataAccessException(1); + } + return restaurant; + } + + /** + * Helper method that clears the cache of restaurants. + * This method should be called when a bean is destroyed. + *

+ * TODO-10: Add a scheme to check if this method is being invoked + * - Add System.out.println to this method. + *

+ * TODO-11: Have this method to be invoked before a bean gets destroyed + * - Re-run RewardNetworkTests. + * - Observe this method is not called. + * - Use an appropriate annotation to register this method for a + * destruction lifecycle callback. + * - Re-run the test and you should be able to see + * that this method is now being called. + */ + public void clearRestaurantCache() { + restaurantCache.clear(); + } + + /** + * 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); + return restaurant; + } } \ No newline at end of file diff --git a/lab/16-annotations/src/main/java/rewards/internal/restaurant/Restaurant.java b/lab/16-annotations/src/main/java/rewards/internal/restaurant/Restaurant.java index aa642ae5..2ccf47e8 100644 --- a/lab/16-annotations/src/main/java/rewards/internal/restaurant/Restaurant.java +++ b/lab/16-annotations/src/main/java/rewards/internal/restaurant/Restaurant.java @@ -1,79 +1,81 @@ package rewards.internal.restaurant; -import rewards.Dining; -import rewards.internal.account.Account; - import common.money.MonetaryAmount; import common.money.Percentage; import common.repository.Entity; +import rewards.Dining; +import rewards.internal.account.Account; /** * A restaurant establishment in the network. Like AppleBee's. - * + *

* Restaurants calculate how much benefit may be awarded to an account for dining based on a benefit percentage. */ public class Restaurant extends Entity { - private String number; + private String number; - private String name; + private String name; - private Percentage benefitPercentage; + private Percentage benefitPercentage; - @SuppressWarnings("unused") - private Restaurant() { - } + @SuppressWarnings("unused") + private Restaurant() { + } - /** - * Creates a new restaurant. - * @param number the restaurant's merchant number - * @param name the name of the restaurant - */ - public Restaurant(String number, String name) { - this.number = number; - this.name = name; - } + /** + * Creates a new restaurant. + * + * @param number the restaurant's merchant number + * @param name the name of the restaurant + */ + public Restaurant(String number, String name) { + this.number = number; + this.name = name; + } - /** - * Sets the percentage benefit to be awarded for eligible dining transactions. - * @param benefitPercentage the benefit percentage - */ - public void setBenefitPercentage(Percentage benefitPercentage) { - this.benefitPercentage = benefitPercentage; - } + /** + * Sets the percentage benefit to be awarded for eligible dining transactions. + * + * @param benefitPercentage the benefit percentage + */ + public void setBenefitPercentage(Percentage benefitPercentage) { + this.benefitPercentage = benefitPercentage; + } - /** - * Returns the name of this restaurant. - */ - public String getName() { - return name; - } + /** + * Returns the name of this restaurant. + */ + public String getName() { + return name; + } - /** - * Returns the merchant number of this restaurant. - */ - public String getNumber() { - return number; - } + /** + * Returns the merchant number of this restaurant. + */ + public String getNumber() { + return number; + } - /** - * Returns this restaurant's benefit percentage. - */ - public Percentage getBenefitPercentage() { - return benefitPercentage; - } + /** + * Returns this restaurant's benefit percentage. + */ + public Percentage getBenefitPercentage() { + return benefitPercentage; + } - /** - * Calculate the benefit eligible to this account for dining at this restaurant. - * @param account the account that dined at this restaurant - * @param dining a dining event that occurred - * @return the benefit amount eligible for reward - */ - public MonetaryAmount calculateBenefitFor(Account account, Dining dining) { - return dining.getAmount().multiplyBy(benefitPercentage); - } + /** + * Calculate the benefit eligible to this account for dining at this restaurant. + * + * @param account the account that dined at this restaurant + * @param dining a dining event that occurred + * @return the benefit amount eligible for reward + */ + public MonetaryAmount calculateBenefitFor(Account account, Dining dining) { + return dining.getAmount().multiplyBy(benefitPercentage); + } - public String toString() { - return "Number = '" + number + "', name = '" + name + "', benefitPercentage = " + benefitPercentage; - } + public String toString() { + return "Number = '" + number + "', name = '" + name + "', benefitPercentage = " + benefitPercentage; + } } \ No newline at end of file diff --git a/lab/16-annotations/src/main/java/rewards/internal/restaurant/RestaurantRepository.java b/lab/16-annotations/src/main/java/rewards/internal/restaurant/RestaurantRepository.java index a632fcc1..04968ac5 100644 --- a/lab/16-annotations/src/main/java/rewards/internal/restaurant/RestaurantRepository.java +++ b/lab/16-annotations/src/main/java/rewards/internal/restaurant/RestaurantRepository.java @@ -3,15 +3,16 @@ /** * Loads restaurant aggregates. Called by the reward network to find and reconstitute Restaurant entities from an * external form such as a set of RDMS rows. - * + *

* Objects returned by this repository are guaranteed to be fully-initialized and ready to use. */ public interface RestaurantRepository { /** * Load a Restaurant entity by its merchant number. + * * @param merchantNumber the merchant number * @return the restaurant */ - public Restaurant findByMerchantNumber(String merchantNumber); + Restaurant findByMerchantNumber(String merchantNumber); } diff --git a/lab/16-annotations/src/main/java/rewards/internal/reward/JdbcRewardRepository.java b/lab/16-annotations/src/main/java/rewards/internal/reward/JdbcRewardRepository.java index 37df1421..6324136e 100644 --- a/lab/16-annotations/src/main/java/rewards/internal/reward/JdbcRewardRepository.java +++ b/lab/16-annotations/src/main/java/rewards/internal/reward/JdbcRewardRepository.java @@ -5,8 +5,12 @@ import rewards.Dining; import rewards.RewardConfirmation; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import javax.sql.DataSource; -import java.sql.*; /** * JDBC implementation of a reward repository that @@ -42,11 +46,11 @@ public RewardConfirmation confirmReward(AccountContribution contribution, Dining ps = conn.prepareStatement(sql); String confirmationNumber = nextConfirmationNumber(); ps.setString(1, confirmationNumber); - ps.setBigDecimal(2, contribution.getAmount().asBigDecimal()); + ps.setBigDecimal(2, contribution.amount().asBigDecimal()); ps.setDate(3, new Date(SimpleDate.today().inMilliseconds())); - ps.setString(4, contribution.getAccountNumber()); + ps.setString(4, contribution.accountNumber()); ps.setString(5, dining.getMerchantNumber()); - ps.setDate(6, new Date(dining.getDate().inMilliseconds())); + ps.setDate(6, new Date(dining.date().inMilliseconds())); ps.setBigDecimal(7, dining.getAmount().asBigDecimal()); ps.execute(); return new RewardConfirmation(confirmationNumber, contribution); diff --git a/lab/16-annotations/src/main/java/rewards/internal/reward/RewardRepository.java b/lab/16-annotations/src/main/java/rewards/internal/reward/RewardRepository.java index c8189e52..e32368d2 100644 --- a/lab/16-annotations/src/main/java/rewards/internal/reward/RewardRepository.java +++ b/lab/16-annotations/src/main/java/rewards/internal/reward/RewardRepository.java @@ -9,12 +9,13 @@ */ public interface RewardRepository { - /** - * Create a record of a reward that will track a contribution made to an account for dining. - * @param contribution the account contribution that was made - * @param dining the dining event that resulted in the account contribution - * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later - * date - */ - public RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); + /** + * Create a record of a reward that will track a contribution made to an account for dining. + * + * @param contribution the account contribution that was made + * @param dining the dining event that resulted in the account contribution + * @return a reward confirmation object that can be used for reporting and to lookup the reward details at a later + * date + */ + RewardConfirmation confirmReward(AccountContribution contribution, Dining dining); } \ No newline at end of file diff --git a/lab/16-annotations/src/test/java/rewards/RewardNetworkTests.java b/lab/16-annotations/src/test/java/rewards/RewardNetworkTests.java index e1f85a2b..eea6953f 100644 --- a/lab/16-annotations/src/test/java/rewards/RewardNetworkTests.java +++ b/lab/16-annotations/src/test/java/rewards/RewardNetworkTests.java @@ -13,63 +13,63 @@ * A system test that verifies the components of the RewardNetwork * application work together to reward for dining successfully. * It uses Spring to bootstrap the application for use in a test environment. - * + *

* TODO-00: In this lab, you are going to exercise the following: * - Refactoring the current code that uses Spring configuration with * @Bean methods so that it uses annotation and component-scanning instead * - Using constructor injection and setter injection * - Using @PostConstruct and @PreDestroy - * + *

* TODO-01: Run this test before making any changes. * - It should pass. * Note that this test passes only when all the required * beans are correctly configured. */ -public class RewardNetworkTests { +class RewardNetworkTests { - /** - * The object being tested. - */ - private RewardNetwork rewardNetwork; + /** + * The object being tested. + */ + RewardNetwork rewardNetwork; - @BeforeEach - public void setUp() { - // Create application context from TestInfrastructureConfig, - // which also imports RewardsConfig - ApplicationContext context = SpringApplication.run(TestInfrastructureConfig.class); - - // Get rewardNetwork bean from the application context - rewardNetwork = context.getBean(RewardNetwork.class); - } + @BeforeEach + void setUp() { + // Create application context from TestInfrastructureConfig, + // which also imports RewardsConfig + ApplicationContext context = SpringApplication.run(TestInfrastructureConfig.class); - @Test - public void testRewardForDining() { - // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input - Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); + // Get rewardNetwork bean from the application context + rewardNetwork = context.getBean(RewardNetwork.class); + } - // call the 'rewardNetwork' to test its rewardAccountFor(Dining) method - // this fails if you have selected an account without beneficiaries! - RewardConfirmation confirmation = rewardNetwork.rewardAccountFor(dining); + @Test + void testRewardForDining() { + // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input + Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); - // assert the expected reward confirmation results - assertNotNull(confirmation); - assertNotNull(confirmation.getConfirmationNumber()); + // call the 'rewardNetwork' to test its rewardAccountFor(Dining) method + // this fails if you have selected an account without beneficiaries! + RewardConfirmation confirmation = rewardNetwork.rewardAccountFor(dining); - // assert an account contribution was made - AccountContribution contribution = confirmation.getAccountContribution(); - assertNotNull(contribution); + // assert the expected reward confirmation results + assertNotNull(confirmation); + assertNotNull(confirmation.confirmationNumber()); - // the contribution account number should be '123456789' - assertEquals("123456789", contribution.getAccountNumber()); + // assert an account contribution was made + AccountContribution contribution = confirmation.accountContribution(); + assertNotNull(contribution); - // the total contribution amount should be 8.00 (8% of 100.00) - assertEquals(MonetaryAmount.valueOf("8.00"), contribution.getAmount()); + // the contribution account number should be '123456789' + assertEquals("123456789", contribution.accountNumber()); - // the total contribution amount should have been split into 2 distributions - assertEquals(2, contribution.getDistributions().size()); + // the total contribution amount should be 8.00 (8% of 100.00) + assertEquals(MonetaryAmount.valueOf("8.00"), contribution.amount()); - // each distribution should be 4.00 (as both have a 50% allocation) - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").getAmount()); - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").getAmount()); - } + // the total contribution amount should have been split into 2 distributions + assertEquals(2, contribution.distributions().size()); + + // each distribution should be 4.00 (as both have a 50% allocation) + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").amount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").amount()); + } } diff --git a/lab/16-annotations/src/test/java/rewards/TestInfrastructureConfig.java b/lab/16-annotations/src/test/java/rewards/TestInfrastructureConfig.java index bd29e01b..04a22e62 100644 --- a/lab/16-annotations/src/test/java/rewards/TestInfrastructureConfig.java +++ b/lab/16-annotations/src/test/java/rewards/TestInfrastructureConfig.java @@ -1,28 +1,26 @@ package rewards; -import javax.sql.DataSource; - +import config.RewardsConfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import config.RewardsConfig; +import javax.sql.DataSource; @Configuration @Import(RewardsConfig.class) -public class TestInfrastructureConfig { +class TestInfrastructureConfig { - /** - * Creates an in-memory "rewards" database populated - * with test data for fast testing - */ - @Bean - public DataSource dataSource(){ - return - (new EmbeddedDatabaseBuilder()) - .addScript("classpath:rewards/testdb/schema.sql") - .addScript("classpath:rewards/testdb/data.sql") - .build(); - } + /** + * Creates an in-memory "rewards" database populated + * with test data for fast testing + */ + @Bean + DataSource dataSource() { + return new EmbeddedDatabaseBuilder() + .addScript("classpath:rewards/testdb/schema.sql") + .addScript("classpath:rewards/testdb/data.sql") + .build(); + } } diff --git a/lab/16-annotations/src/test/java/rewards/internal/RewardNetworkImplTests.java b/lab/16-annotations/src/test/java/rewards/internal/RewardNetworkImplTests.java index 0d3bace8..82ff840a 100644 --- a/lab/16-annotations/src/test/java/rewards/internal/RewardNetworkImplTests.java +++ b/lab/16-annotations/src/test/java/rewards/internal/RewardNetworkImplTests.java @@ -1,12 +1,8 @@ package rewards.internal; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - +import common.money.MonetaryAmount; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -import common.money.MonetaryAmount; import rewards.AccountContribution; import rewards.Dining; import rewards.RewardConfirmation; @@ -14,14 +10,17 @@ import rewards.internal.restaurant.RestaurantRepository; import rewards.internal.reward.RewardRepository; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + /** * Unit tests for the RewardNetworkImpl application logic. Configures the implementation with stub repositories * containing dummy data for fast in-memory testing without the overhead of an external data source. - * + *

* Besides helping catch bugs early, tests are a great way for a new developer to learn an API as he or she can see the * API in action. Tests also help validate a design as they are a measure for how easy it is to use your code. */ -public class RewardNetworkImplTests { +class RewardNetworkImplTests { /** * The object being tested. @@ -29,7 +28,7 @@ public class RewardNetworkImplTests { private RewardNetworkImpl rewardNetwork; @BeforeEach - public void setUp() throws Exception { + void setUp() { // create stubs to facilitate fast in-memory testing with dummy data and no external dependencies AccountRepository accountRepo = new StubAccountRepository(); RestaurantRepository restaurantRepo = new StubRestaurantRepository(); @@ -40,7 +39,7 @@ public void setUp() throws Exception { } @Test - public void testRewardForDining() { + void testRewardForDining() { // create a new dining of 100.00 charged to credit card '1234123412341234' by merchant '123457890' as test input Dining dining = Dining.createDining("100.00", "1234123412341234", "1234567890"); @@ -49,23 +48,23 @@ public void testRewardForDining() { // assert the expected reward confirmation results assertNotNull(confirmation); - assertNotNull(confirmation.getConfirmationNumber()); + assertNotNull(confirmation.confirmationNumber()); // assert an account contribution was made - AccountContribution contribution = confirmation.getAccountContribution(); + AccountContribution contribution = confirmation.accountContribution(); assertNotNull(contribution); // the account number should be '123456789' - assertEquals("123456789", contribution.getAccountNumber()); + assertEquals("123456789", contribution.accountNumber()); // the total contribution amount should be 8.00 (8% of 100.00) - assertEquals(MonetaryAmount.valueOf("8.00"), contribution.getAmount()); + assertEquals(MonetaryAmount.valueOf("8.00"), contribution.amount()); // the total contribution amount should have been split into 2 distributions - assertEquals(2, contribution.getDistributions().size()); + assertEquals(2, contribution.distributions().size()); // each distribution should be 4.00 (as both have a 50% allocation) - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").getAmount()); - assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").getAmount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Annabelle").amount()); + assertEquals(MonetaryAmount.valueOf("4.00"), contribution.getDistribution("Corgan").amount()); } -} \ No newline at end of file +} diff --git a/lab/16-annotations/src/test/java/rewards/internal/StubAccountRepository.java b/lab/16-annotations/src/test/java/rewards/internal/StubAccountRepository.java index 66ca1dcd..4931f4fc 100644 --- a/lab/16-annotations/src/test/java/rewards/internal/StubAccountRepository.java +++ b/lab/16-annotations/src/test/java/rewards/internal/StubAccountRepository.java @@ -1,26 +1,23 @@ package rewards.internal; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.dao.EmptyResultDataAccessException; - +import common.money.Percentage; import rewards.internal.account.Account; import rewards.internal.account.AccountRepository; -import common.money.Percentage; +import java.util.HashMap; +import java.util.Map; /** * A dummy account repository implementation. Has a single Account "Keith and Keri Donald" with two beneficiaries * "Annabelle" (50% allocation) and "Corgan" (50% allocation) associated with credit card "1234123412341234". - * + *

* Stubs facilitate unit testing. An object needing an AccountRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubAccountRepository implements AccountRepository { - private Map accountsByCreditCard = new HashMap(); + Map accountsByCreditCard = new HashMap<>(); public StubAccountRepository() { Account account = new Account("123456789", "Keith and Keri Donald"); @@ -32,7 +29,7 @@ public StubAccountRepository() { public Account findByCreditCard(String creditCardNumber) { Account account = accountsByCreditCard.get(creditCardNumber); if (account == null) { - throw new EmptyResultDataAccessException(1); + throw new RuntimeException("no account has been found for credit card number " + creditCardNumber); } return account; } diff --git a/lab/16-annotations/src/test/java/rewards/internal/StubRestaurantRepository.java b/lab/16-annotations/src/test/java/rewards/internal/StubRestaurantRepository.java index 92375baa..e568a405 100644 --- a/lab/16-annotations/src/test/java/rewards/internal/StubRestaurantRepository.java +++ b/lab/16-annotations/src/test/java/rewards/internal/StubRestaurantRepository.java @@ -1,26 +1,23 @@ package rewards.internal; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.dao.EmptyResultDataAccessException; - +import common.money.Percentage; import rewards.internal.restaurant.Restaurant; import rewards.internal.restaurant.RestaurantRepository; -import common.money.Percentage; +import java.util.HashMap; +import java.util.Map; /** * A dummy restaurant repository implementation. Has a single restaurant "Apple Bees" with a 8% benefit availability * percentage that's always available. - * + *

* Stubs facilitate unit testing. An object needing a RestaurantRepository can work with this stub and not have to bring * in expensive and/or complex dependencies such as a Database. Simple unit tests can then verify object behavior by * considering the state of this stub. */ public class StubRestaurantRepository implements RestaurantRepository { - private Map restaurantsByMerchantNumber = new HashMap(); + Map restaurantsByMerchantNumber = new HashMap<>(); public StubRestaurantRepository() { Restaurant restaurant = new Restaurant("1234567890", "Apple Bees"); @@ -29,9 +26,9 @@ public StubRestaurantRepository() { } public Restaurant findByMerchantNumber(String merchantNumber) { - Restaurant restaurant = (Restaurant) restaurantsByMerchantNumber.get(merchantNumber); + Restaurant restaurant = restaurantsByMerchantNumber.get(merchantNumber); if (restaurant == null) { - throw new EmptyResultDataAccessException(1); + throw new RuntimeException("no restaurant has been found for merchant number " + merchantNumber); } return restaurant; } diff --git a/lab/16-annotations/src/test/java/rewards/internal/StubRewardRepository.java b/lab/16-annotations/src/test/java/rewards/internal/StubRewardRepository.java index 2487aca0..d149acc7 100644 --- a/lab/16-annotations/src/test/java/rewards/internal/StubRewardRepository.java +++ b/lab/16-annotations/src/test/java/rewards/internal/StubRewardRepository.java @@ -1,12 +1,12 @@ package rewards.internal; -import java.util.Random; - import rewards.AccountContribution; import rewards.Dining; import rewards.RewardConfirmation; import rewards.internal.reward.RewardRepository; +import java.util.Random; + /** * A dummy reward repository implementation. */ diff --git a/lab/16-annotations/src/test/java/rewards/internal/restaurant/JdbcRestaurantRepositoryTests.java b/lab/16-annotations/src/test/java/rewards/internal/restaurant/JdbcRestaurantRepositoryTests.java index a35fd974..01dc26cd 100644 --- a/lab/16-annotations/src/test/java/rewards/internal/restaurant/JdbcRestaurantRepositoryTests.java +++ b/lab/16-annotations/src/test/java/rewards/internal/restaurant/JdbcRestaurantRepositoryTests.java @@ -15,12 +15,12 @@ * Tests the JDBC restaurant repository with a test data source to verify data access and relational-to-object mapping * behavior works as expected. */ -public class JdbcRestaurantRepositoryTests { +class JdbcRestaurantRepositoryTests { - private JdbcRestaurantRepository repository; + JdbcRestaurantRepository repository; @BeforeEach - public void setUp() throws Exception { + void setUp() { // simulate the Spring bean initialization lifecycle: // first, construct the bean repository = new JdbcRestaurantRepository(); @@ -33,13 +33,13 @@ public void setUp() throws Exception { } @AfterEach - public void tearDown() throws Exception { + void tearDown() { // simulate the Spring bean destruction lifecycle: repository.clearRestaurantCache(); } @Test - public void findRestaurantByMerchantNumber() { + void findRestaurantByMerchantNumber() { Restaurant restaurant = repository.findByMerchantNumber("1234567890"); assertNotNull(restaurant, "restaurant is null - check your repositories cache"); assertEquals("1234567890", restaurant.getNumber(),"number is wrong"); @@ -48,19 +48,15 @@ public void findRestaurantByMerchantNumber() { } @Test - public void findRestaurantByBogusMerchantNumber() { - assertThrows(EmptyResultDataAccessException.class, ()-> { - repository.findByMerchantNumber("bogus"); - }); + void findRestaurantByBogusMerchantNumber() { + assertThrows(EmptyResultDataAccessException.class, ()-> repository.findByMerchantNumber("bogus")); } @Test - public void restaurantCacheClearedAfterDestroy() throws Exception { + void restaurantCacheClearedAfterDestroy() { // force early tear down tearDown(); - assertThrows(EmptyResultDataAccessException.class, ()-> { - repository.findByMerchantNumber("1234567890"); - }); + assertThrows(EmptyResultDataAccessException.class, ()-> repository.findByMerchantNumber("1234567890")); } private DataSource createTestDataSource() { diff --git a/lab/build.gradle b/lab/build.gradle index 3d76bf77..60ff228b 100644 --- a/lab/build.gradle +++ b/lab/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { - springBootVersion = "2.7.5" - easyMockVersion = "4.3" + springBootVersion = "3.3.0" + easyMockVersion = "5.2.0" jmonVersion = "2.82" } @@ -36,8 +36,6 @@ subprojects { } } - ext['spring-security.version'] = '5.8.0' - dependencies { implementation "org.springframework.boot:spring-boot-starter" implementation "org.springframework.boot:spring-boot-starter-jdbc" @@ -46,9 +44,9 @@ subprojects { testImplementation "org.springframework.boot:spring-boot-starter-test" } - sourceCompatibility = '11' + sourceCompatibility = '21' - tasks.withType(JavaCompile) { + tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } diff --git a/lab/gradle/wrapper/gradle-wrapper.properties b/lab/gradle/wrapper/gradle-wrapper.properties index ae04661e..48c0a02c 100644 --- a/lab/gradle/wrapper/gradle-wrapper.properties +++ b/lab/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists