From 831c96f0c7b839f3481b04faa665e667e1bf1694 Mon Sep 17 00:00:00 2001 From: seregaivanov Date: Sun, 26 Jul 2020 14:58:48 +0300 Subject: [PATCH 1/8] Resolve task one Added the implementation of the "equals" and "hashCode" methods to the "Account" and "AccountKey" classes, for correct element comparison. Added necessary checks to the "createAccount" method. Optimized "getAccount" method. Added tests. --- .../java/com/devexperts/account/Account.java | 17 ++++++ .../com/devexperts/account/AccountKey.java | 15 +++++ .../devexperts/service/AccountService.java | 2 + .../service/AccountServiceImpl.java | 29 +++++++--- .../service/AccountServiceTaskOneTest.java | 57 +++++++++++++++++++ 5 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 src/test/java/com/devexperts/service/AccountServiceTaskOneTest.java diff --git a/src/main/java/com/devexperts/account/Account.java b/src/main/java/com/devexperts/account/Account.java index fb2a3af..0649409 100644 --- a/src/main/java/com/devexperts/account/Account.java +++ b/src/main/java/com/devexperts/account/Account.java @@ -1,5 +1,7 @@ package com.devexperts.account; +import java.util.Objects; + public class Account { private final AccountKey accountKey; private final String firstName; @@ -32,4 +34,19 @@ public Double getBalance() { public void setBalance(Double balance) { this.balance = balance; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Account account = (Account) o; + return Objects.equals(accountKey, account.accountKey) && + Objects.equals(firstName, account.firstName) && + Objects.equals(lastName, account.lastName); + } + + @Override + public int hashCode() { + return Objects.hash(accountKey, firstName, lastName); + } } diff --git a/src/main/java/com/devexperts/account/AccountKey.java b/src/main/java/com/devexperts/account/AccountKey.java index 1b0a233..95213ea 100644 --- a/src/main/java/com/devexperts/account/AccountKey.java +++ b/src/main/java/com/devexperts/account/AccountKey.java @@ -1,5 +1,7 @@ package com.devexperts.account; +import java.util.Objects; + /** * Unique Account identifier * @@ -17,4 +19,17 @@ private AccountKey(long accountId) { public static AccountKey valueOf(long accountId) { return new AccountKey(accountId); } + + @Override + public boolean equals( Object o ) { + if ( this == o ) return true; + if ( o == null || getClass() != o.getClass() ) return false; + AccountKey that = ( AccountKey ) o; + return accountId == that.accountId; + } + + @Override + public int hashCode() { + return Objects.hash( accountId ); + } } diff --git a/src/main/java/com/devexperts/service/AccountService.java b/src/main/java/com/devexperts/service/AccountService.java index f287597..310fc6a 100644 --- a/src/main/java/com/devexperts/service/AccountService.java +++ b/src/main/java/com/devexperts/service/AccountService.java @@ -12,6 +12,8 @@ public interface AccountService { /** * Creates a new account + * Todo update??? + * Todo if null??? * * @param account account entity to add or update * @throws IllegalArgumentException if account is already present diff --git a/src/main/java/com/devexperts/service/AccountServiceImpl.java b/src/main/java/com/devexperts/service/AccountServiceImpl.java index 91261ba..7b131a7 100644 --- a/src/main/java/com/devexperts/service/AccountServiceImpl.java +++ b/src/main/java/com/devexperts/service/AccountServiceImpl.java @@ -5,33 +5,44 @@ import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Service public class AccountServiceImpl implements AccountService { - private final List accounts = new ArrayList<>(); + //Changed the collection, because the speed of searching for an element in HashMap is constant. + private final Map accounts = new HashMap<>(); @Override public void clear() { accounts.clear(); } + /** + * @throws NullPointerException if account null + */ @Override - public void createAccount(Account account) { - accounts.add(account); + public void createAccount( Account account ) throws IllegalArgumentException, NullPointerException { + if ( account == null ) { + throw new NullPointerException( "account is null" ); + } + + if ( accounts.containsKey( account.getAccountKey() ) ) { + throw new IllegalArgumentException( "account is already present" ); + } + + accounts.put( account.getAccountKey(), account ); } @Override - public Account getAccount(long id) { - return accounts.stream() - .filter(account -> account.getAccountKey() == AccountKey.valueOf(id)) - .findAny() - .orElse(null); + public Account getAccount( long id ) { + return accounts.get( AccountKey.valueOf( id ) ); } @Override - public void transfer(Account source, Account target, double amount) { + public void transfer( Account source, Account target, double amount ) { //do nothing for now } } diff --git a/src/test/java/com/devexperts/service/AccountServiceTaskOneTest.java b/src/test/java/com/devexperts/service/AccountServiceTaskOneTest.java new file mode 100644 index 0000000..4573d92 --- /dev/null +++ b/src/test/java/com/devexperts/service/AccountServiceTaskOneTest.java @@ -0,0 +1,57 @@ +package com.devexperts.service; + +import com.devexperts.account.Account; +import com.devexperts.account.AccountKey; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Tests for checking the first task") +class AccountServiceTaskOneTest { + private static Account account; + private static long key = 1123456; + private static AccountService accountService = new AccountServiceImpl(); + + static { + account = new Account( + AccountKey.valueOf( key ), "Ivanov", "Sergey", 100d + ); + } + + @Test + @DisplayName("Check 'createAccount', normal work") + void testCreateAccount() { + accountService.clear(); + assertDoesNotThrow( () -> accountService.createAccount( account ) ); + } + + @Test + @DisplayName("Check 'createAccount', that an 'NullPointerException' was thrown") + void testCreateAccountThrowNullPointerException() { + assertThrows( NullPointerException.class, () -> accountService.createAccount( null ) ); + } + + @Test + @DisplayName("Check 'createAccount', that an 'IllegalArgumentException' was thrown") + void testCreateAccountThrowIllegalArgumentException() { + accountService.clear(); + accountService.createAccount( account ); + assertThrows( IllegalArgumentException.class, () -> accountService.createAccount( account ) ); + } + + @Test + @DisplayName("Check 'getAccount', normal work") + void testGetAccount() { + accountService.clear(); + accountService.createAccount( account ); + assertEquals( account, accountService.getAccount( key ) ); + } + + @Test + @DisplayName("Check 'getAccount', account is not found") + void testGetAccountIsNotFound() { + accountService.clear(); + assertNull( accountService.getAccount( key ) ); + } +} \ No newline at end of file From a3eb4e4d98746dfdb7f210dd1206e9ce8c11c11b Mon Sep 17 00:00:00 2001 From: seregaivanov Date: Sun, 26 Jul 2020 16:36:10 +0300 Subject: [PATCH 2/8] Resolve task two Added the implementation of the "transfer", for single-threaded environment. Added tests. --- .../service/AccountServiceImpl.java | 28 +++++- .../exceptions/NegativeBalanceException.java | 20 ++++ .../service/AccountServiceTaskTwoTest.java | 93 +++++++++++++++++++ 3 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/devexperts/service/exceptions/NegativeBalanceException.java create mode 100644 src/test/java/com/devexperts/service/AccountServiceTaskTwoTest.java diff --git a/src/main/java/com/devexperts/service/AccountServiceImpl.java b/src/main/java/com/devexperts/service/AccountServiceImpl.java index 7b131a7..6141936 100644 --- a/src/main/java/com/devexperts/service/AccountServiceImpl.java +++ b/src/main/java/com/devexperts/service/AccountServiceImpl.java @@ -2,11 +2,10 @@ import com.devexperts.account.Account; import com.devexperts.account.AccountKey; +import com.devexperts.service.exceptions.NegativeBalanceException; import org.springframework.stereotype.Service; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; @Service @@ -24,7 +23,7 @@ public void clear() { * @throws NullPointerException if account null */ @Override - public void createAccount( Account account ) throws IllegalArgumentException, NullPointerException { + public void createAccount( Account account ) { if ( account == null ) { throw new NullPointerException( "account is null" ); } @@ -43,6 +42,27 @@ public Account getAccount( long id ) { @Override public void transfer( Account source, Account target, double amount ) { - //do nothing for now + if ( amount <= 0 ) { + throw new IllegalArgumentException ("amount is negative or zero"); + } + + if ( source == null || target == null ) { + throw new IllegalArgumentException ("source or target is null"); + } + + if (source.getAccountKey().equals( target.getAccountKey() )) { + throw new IllegalArgumentException ("source and target is one account"); + } + + if ( source.getBalance() - amount < 0 ) { + throw new NegativeBalanceException( "source has insufficient funds on his account" ); + } + + transferAmount( source, target, amount ); + } + + private void transferAmount( Account source, Account target, double amount ) { + source.setBalance( source.getBalance() - amount ); + target.setBalance( target.getBalance() + amount ); } } diff --git a/src/main/java/com/devexperts/service/exceptions/NegativeBalanceException.java b/src/main/java/com/devexperts/service/exceptions/NegativeBalanceException.java new file mode 100644 index 0000000..a12ac6c --- /dev/null +++ b/src/main/java/com/devexperts/service/exceptions/NegativeBalanceException.java @@ -0,0 +1,20 @@ +package com.devexperts.service.exceptions; + +public class NegativeBalanceException extends IllegalArgumentException { + + public NegativeBalanceException() { + super(); + } + + public NegativeBalanceException(String s) { + super(s); + } + + public NegativeBalanceException(String message, Throwable cause) { + super(message, cause); + } + + public NegativeBalanceException(Throwable cause) { + super(cause); + } +} diff --git a/src/test/java/com/devexperts/service/AccountServiceTaskTwoTest.java b/src/test/java/com/devexperts/service/AccountServiceTaskTwoTest.java new file mode 100644 index 0000000..7bba9c1 --- /dev/null +++ b/src/test/java/com/devexperts/service/AccountServiceTaskTwoTest.java @@ -0,0 +1,93 @@ +package com.devexperts.service; + +import com.devexperts.account.Account; +import com.devexperts.account.AccountKey; +import com.devexperts.service.exceptions.NegativeBalanceException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Tests for checking the second task") +class AccountServiceTaskTwoTest { + private static Account source; + private static Account target; + private static AccountService accountService = new AccountServiceImpl(); + + static { + source = new Account( + AccountKey.valueOf( 1123456 ), "Sergey", "Ivanov", 100d + ); + + target = new Account( + AccountKey.valueOf( 12332343 ), "Vika", "Okulist", 0d + ); + } + + @Test + @DisplayName("Check 'transfer', normal work") + void testTransfer() { + double amount = 99.12d; + double balanceTarget = target.getBalance(); + double balanceSource = source.getBalance(); + + accountService.transfer( source, target, amount ); + + assertEquals( target.getBalance(), balanceTarget + amount ); + assertEquals( source.getBalance(), balanceSource - amount ); + } + + @Test + @DisplayName("Check 'transfer', that an 'NegativeBalanceException' was thrown") + void testTransferThrowNegativeBalanceException() { + double amount = 100.12d; + assertThrows( NegativeBalanceException.class, () -> accountService.transfer( source, target, amount ) ); + } + + @Test + @DisplayName( + "Check 'transfer', that an 'IllegalArgumentException' was thrown, because 'source' or 'target' is null" + ) + void testTransferSourceIsNull() { + double amount = 100.12d; + Throwable thrown = assertThrows( IllegalArgumentException.class, () -> + accountService.transfer( null, target, amount ) + ); + + assertEquals( thrown.getMessage(), "source or target is null" ); + } + + @Test + @DisplayName( + "Check 'transfer', that an 'IllegalArgumentException' was thrown, " + + "because 'source' and 'target' is one account" + ) + void testTransferSourceAndTargetIsOneAccount() { + double amount = 82.12d; + Throwable thrown = assertThrows( IllegalArgumentException.class, () -> + accountService.transfer( source, source, amount ) + ); + + assertEquals( thrown.getMessage(), "source and target is one account" ); + } + + @Test + @DisplayName( + "Check 'transfer', that an 'IllegalArgumentException' was thrown, " + + "because 'source' and 'target' is one account" + ) + void testTransferAmountIsNegativeOrZero() { + double amountIsZero = 0.0d; + double amountIsNegative = -0.1d; + Throwable thrownIsZero = assertThrows( IllegalArgumentException.class, () -> + accountService.transfer( source, target, amountIsZero ) + ); + assertEquals( thrownIsZero.getMessage(), "amount is negative or zero" ); + + Throwable thrownIsNegative = assertThrows( IllegalArgumentException.class, () -> + accountService.transfer( source, target, amountIsNegative ) + ); + assertEquals( thrownIsNegative.getMessage(), "amount is negative or zero" ); + } +} \ No newline at end of file From 6ffada7658a0ba70daa7f39510b15b4393164891 Mon Sep 17 00:00:00 2001 From: seregaivanov Date: Sun, 26 Jul 2020 16:39:09 +0300 Subject: [PATCH 3/8] Fix bug task one --- .../java/com/devexperts/service/AccountServiceTaskOneTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/devexperts/service/AccountServiceTaskOneTest.java b/src/test/java/com/devexperts/service/AccountServiceTaskOneTest.java index 4573d92..142626e 100644 --- a/src/test/java/com/devexperts/service/AccountServiceTaskOneTest.java +++ b/src/test/java/com/devexperts/service/AccountServiceTaskOneTest.java @@ -15,7 +15,7 @@ class AccountServiceTaskOneTest { static { account = new Account( - AccountKey.valueOf( key ), "Ivanov", "Sergey", 100d + AccountKey.valueOf( key ), "Sergey", "Ivanov", 100d ); } From 80470d4f7e88aa6a40d273214f1212cef9c9a369 Mon Sep 17 00:00:00 2001 From: seregaivanov Date: Sun, 26 Jul 2020 17:23:54 +0300 Subject: [PATCH 4/8] Resolve task three and refactor task two Added the implementation of the "transfer", for multi-threaded environment. Refactor task two, due to duplicate code in task three. --- .../service/AccountServiceImpl.java | 24 ++++++++----- .../AccountTransferParallelService.java | 34 +++++++++++++++++++ 2 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/devexperts/service/AccountTransferParallelService.java diff --git a/src/main/java/com/devexperts/service/AccountServiceImpl.java b/src/main/java/com/devexperts/service/AccountServiceImpl.java index 6141936..91e848e 100644 --- a/src/main/java/com/devexperts/service/AccountServiceImpl.java +++ b/src/main/java/com/devexperts/service/AccountServiceImpl.java @@ -42,27 +42,33 @@ public Account getAccount( long id ) { @Override public void transfer( Account source, Account target, double amount ) { + checkArgumentForTransfer( source, target, amount ); + transferAmount( source, target, amount ); + } + + protected void checkArgumentForTransfer( Account source, Account target, double amount ) { if ( amount <= 0 ) { - throw new IllegalArgumentException ("amount is negative or zero"); + throw new IllegalArgumentException( "amount is negative or zero" ); } if ( source == null || target == null ) { - throw new IllegalArgumentException ("source or target is null"); + throw new IllegalArgumentException( "source or target is null" ); } - if (source.getAccountKey().equals( target.getAccountKey() )) { - throw new IllegalArgumentException ("source and target is one account"); + if ( source.getAccountKey().equals( target.getAccountKey() ) ) { + throw new IllegalArgumentException( "source and target is one account" ); } + } + private void transferAmount( Account source, Account target, double amount ) { if ( source.getBalance() - amount < 0 ) { throw new NegativeBalanceException( "source has insufficient funds on his account" ); } - - transferAmount( source, target, amount ); - } - - private void transferAmount( Account source, Account target, double amount ) { source.setBalance( source.getBalance() - amount ); target.setBalance( target.getBalance() + amount ); } + + protected Map getAccounts() { + return accounts; + } } diff --git a/src/main/java/com/devexperts/service/AccountTransferParallelService.java b/src/main/java/com/devexperts/service/AccountTransferParallelService.java new file mode 100644 index 0000000..47ab7b1 --- /dev/null +++ b/src/main/java/com/devexperts/service/AccountTransferParallelService.java @@ -0,0 +1,34 @@ +package com.devexperts.service; + +import com.devexperts.account.Account; +import com.devexperts.account.AccountKey; +import com.devexperts.service.exceptions.NegativeBalanceException; + +public class AccountTransferParallelService extends AccountServiceImpl { + + private Account getAccount( AccountKey accountKey ) { + return getAccounts().get( accountKey ); + } + + @Override + public void transfer( Account source, Account target, double amount ) { + checkArgumentForTransfer( source, target, amount ); + + //get from the cache (to be sure) + Account sourceCash = getAccount( source.getAccountKey() ); + Account targetCash = getAccount( target.getAccountKey() ); + + //used an optimistic approach to blocking resources + synchronized ( sourceCash ) { + if ( sourceCash.getBalance() - amount < 0 ) { + throw new NegativeBalanceException( "source has insufficient funds on his account" ); + } + + source.setBalance( source.getBalance() - amount ); + } + + synchronized ( targetCash ) { + target.setBalance( target.getBalance() + amount ); + } + } +} From 0773ea4850c2296ab55c59cf268c499402941a6b Mon Sep 17 00:00:00 2001 From: seregaivanov Date: Sun, 26 Jul 2020 18:03:52 +0300 Subject: [PATCH 5/8] Resolve task four --- .../devexperts/rest/AccountController.java | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/devexperts/rest/AccountController.java b/src/main/java/com/devexperts/rest/AccountController.java index b300282..998ac1a 100644 --- a/src/main/java/com/devexperts/rest/AccountController.java +++ b/src/main/java/com/devexperts/rest/AccountController.java @@ -1,14 +1,59 @@ package com.devexperts.rest; +import com.devexperts.account.Account; +import com.devexperts.account.AccountKey; +import com.devexperts.service.AccountService; +import com.devexperts.service.exceptions.NegativeBalanceException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api") public class AccountController extends AbstractAccountController { - public ResponseEntity transfer(long sourceId, long targetId, double amount) { - return null; + private AccountService accountService; + + @Autowired + public void setAccountService(AccountService accountService) { + this.accountService = accountService; + } + + //For test + @Deprecated + @GetMapping("test/init") + public ResponseEntity testInit () { + Account account1 = new Account( AccountKey.valueOf( 1 ), "Sergey", "Ivanov", 12d ); + Account account2 = new Account( AccountKey.valueOf( 2 ), "Vika", "Okulist", 125.0 ); + Account account3 = new Account( AccountKey.valueOf( 3 ), "Nikoly", "Frolov", 0.3d ); + accountService.createAccount( account1 ); + accountService.createAccount( account2 ); + accountService.createAccount( account3 ); + + return ResponseEntity.ok().build(); + } + + @PostMapping("operations/transfer") + public ResponseEntity transfer( + @RequestParam("source_id") long sourceId, + @RequestParam("target_id") long targetId, + @RequestParam("amount") double amount + ) { + Account source = accountService.getAccount(sourceId); + Account target = accountService.getAccount(targetId); + + if ( source == null || target == null ) { + return ResponseEntity.notFound().build(); + } + + try { + accountService.transfer(source, target, amount); + } catch ( NegativeBalanceException e ) { + return ResponseEntity.status( 500 ).build(); + } catch ( IllegalArgumentException e ) { + return ResponseEntity.badRequest().build(); + } + + return ResponseEntity.ok().build(); } } From df30f7bbd6373c145a0bf3f4ca63d7f9162719a3 Mon Sep 17 00:00:00 2001 From: seregaivanov Date: Sun, 26 Jul 2020 19:33:15 +0300 Subject: [PATCH 6/8] Resolve task five --- src/main/resources/sql/data.sql | 11 +++++++++++ src/main/resources/sql/data/accounts.sql | 12 ++++++++++++ src/main/resources/sql/data/transfers.sql | 12 ++++++++++++ src/main/resources/sql/select.sql | 10 ++++++++++ 4 files changed, 45 insertions(+) create mode 100644 src/main/resources/sql/data.sql create mode 100644 src/main/resources/sql/data/accounts.sql create mode 100644 src/main/resources/sql/data/transfers.sql create mode 100644 src/main/resources/sql/select.sql diff --git a/src/main/resources/sql/data.sql b/src/main/resources/sql/data.sql new file mode 100644 index 0000000..fcc204f --- /dev/null +++ b/src/main/resources/sql/data.sql @@ -0,0 +1,11 @@ +INSERT INTO ACCOUNTS (FIRST_NAME, LAST_NAME, BALANCE) +VALUES ('Сергей', 'Иванов', 170000.34), + ('Николай', 'Гумилёв', 20000), + ('Дарт', 'Вейдер', 7000000.07), + ('Сергей', 'Иванов', 4500000); + +INSERT INTO TRANSFERS (SOURCE_ID, TARGET_ID, AMOUNT, TRANSFER_TIME) +VALUES (1, 3, 999, '2019-02-23'), + (1, 4, 2000, '2020-07-04'), + (1, 2, 760, '208-01-08'), + (4, 3, 10000, '2020-07-25') \ No newline at end of file diff --git a/src/main/resources/sql/data/accounts.sql b/src/main/resources/sql/data/accounts.sql new file mode 100644 index 0000000..7459452 --- /dev/null +++ b/src/main/resources/sql/data/accounts.sql @@ -0,0 +1,12 @@ +DROP TABLE IF EXISTS ACCOUNTS; + +CREATE TABLE ACCOUNTS +( + ID LONG NOT NULL AUTO_INCREMENT, + FIRST_NAME VARCHAR(100) NOT NULL, + LAST_NAME VARCHAR(100) NOT NULL, + BALANCE DOUBLE, + CONSTRAINT ACCOUNTS_PK + PRIMARY KEY (ID) +); + diff --git a/src/main/resources/sql/data/transfers.sql b/src/main/resources/sql/data/transfers.sql new file mode 100644 index 0000000..bf4eefc --- /dev/null +++ b/src/main/resources/sql/data/transfers.sql @@ -0,0 +1,12 @@ +DROP TABLE IF EXISTS TRANSFERS; + +CREATE TABLE TRANSFERS +( + ID LONG NOT NULL AUTO_INCREMENT, + SOURCE_ID LONG NOT NULL, + TARGET_ID LONG NOT NULL, + AMOUNT DOUBLE NOT NULL, + TRANSFER_TIME TIMESTAMP NOT NULL, + CONSTRAINT TRANSFERS_PK + PRIMARY KEY (ID) +); \ No newline at end of file diff --git a/src/main/resources/sql/select.sql b/src/main/resources/sql/select.sql new file mode 100644 index 0000000..8fc5245 --- /dev/null +++ b/src/main/resources/sql/select.sql @@ -0,0 +1,10 @@ +SELECT SOURCE_ID +FROM TRANSFERS t + JOIN ACCOUNTS a_source + ON t.SOURCE_ID = a_source.ID + JOIN ACCOUNTS a_target + ON t.TARGET_ID = a_target.ID +WHERE (a_source.FIRST_NAME <> a_target.FIRST_NAME OR a_source.LAST_NAME <> a_target.LAST_NAME) + AND TRANSFER_TIME >= '2019-01-01' +GROUP BY SOURCE_ID +HAVING SUM(AMOUNT) > 1000 \ No newline at end of file From f45b05e88f99aa3625023062361233a22549c8c4 Mon Sep 17 00:00:00 2001 From: seregaivanov Date: Mon, 27 Jul 2020 01:03:11 +0300 Subject: [PATCH 7/8] Add error handling to task four Add error handling to task four, for order to separate the error handling logic from the request handling logic. --- .../devexperts/rest/AccountController.java | 44 +++++++++++++------ .../devexperts/rest/data/ResponseMessage.java | 41 +++++++++++++++++ 2 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/devexperts/rest/data/ResponseMessage.java diff --git a/src/main/java/com/devexperts/rest/AccountController.java b/src/main/java/com/devexperts/rest/AccountController.java index 998ac1a..cf890e4 100644 --- a/src/main/java/com/devexperts/rest/AccountController.java +++ b/src/main/java/com/devexperts/rest/AccountController.java @@ -2,6 +2,7 @@ import com.devexperts.account.Account; import com.devexperts.account.AccountKey; +import com.devexperts.rest.data.ResponseMessage; import com.devexperts.service.AccountService; import com.devexperts.service.exceptions.NegativeBalanceException; import org.springframework.beans.factory.annotation.Autowired; @@ -15,14 +16,14 @@ public class AccountController extends AbstractAccountController { private AccountService accountService; @Autowired - public void setAccountService(AccountService accountService) { + public void setAccountService( AccountService accountService ) { this.accountService = accountService; } //For test @Deprecated @GetMapping("test/init") - public ResponseEntity testInit () { + public ResponseEntity testInit() { Account account1 = new Account( AccountKey.valueOf( 1 ), "Sergey", "Ivanov", 12d ); Account account2 = new Account( AccountKey.valueOf( 2 ), "Vika", "Okulist", 125.0 ); Account account3 = new Account( AccountKey.valueOf( 3 ), "Nikoly", "Frolov", 0.3d ); @@ -39,21 +40,38 @@ public ResponseEntity transfer( @RequestParam("target_id") long targetId, @RequestParam("amount") double amount ) { - Account source = accountService.getAccount(sourceId); - Account target = accountService.getAccount(targetId); + Account source = accountService.getAccount( sourceId ); + Account target = accountService.getAccount( targetId ); if ( source == null || target == null ) { - return ResponseEntity.notFound().build(); - } - - try { - accountService.transfer(source, target, amount); - } catch ( NegativeBalanceException e ) { - return ResponseEntity.status( 500 ).build(); - } catch ( IllegalArgumentException e ) { - return ResponseEntity.badRequest().build(); + throw new NullPointerException ("account is not found"); } + accountService.transfer( source, target, amount ); return ResponseEntity.ok().build(); } + + @ExceptionHandler({IllegalArgumentException.class}) + public ResponseEntity handleIllegalArgumentException( IllegalArgumentException ex ) { + return ResponseEntity.badRequest().body( + new ResponseMessage( ex.getMessage(), + 400, "BAD_REQUEST", "/operations/transfer") + ); + } + + @ExceptionHandler({NegativeBalanceException.class}) + public ResponseEntity handleNegativeBalanceException( NegativeBalanceException ex ) { + return ResponseEntity.status( 500 ).body( + new ResponseMessage( ex.getMessage(), + 500, "INTERNAL_SERVER_ERROR", "/operations/transfer") + ); + } + + @ExceptionHandler({NullPointerException.class}) + public ResponseEntity handleNullPointerException( NullPointerException ex ) { + return ResponseEntity.status( 404 ).body( + new ResponseMessage( ex.getMessage(), + 404, "NOT_FOUND", "/operations/transfer") + ); + } } diff --git a/src/main/java/com/devexperts/rest/data/ResponseMessage.java b/src/main/java/com/devexperts/rest/data/ResponseMessage.java new file mode 100644 index 0000000..a186468 --- /dev/null +++ b/src/main/java/com/devexperts/rest/data/ResponseMessage.java @@ -0,0 +1,41 @@ +package com.devexperts.rest.data; + +import java.text.SimpleDateFormat; +import java.util.Date; + +public class ResponseMessage { + private final String message; + private final int status; + private final String error; + private final Date date; + private final String path; + + public ResponseMessage( String message, int status, String error, String path ) { + this.message = message; + this.status = status; + this.date = new Date(System.currentTimeMillis());; + this.error = error; + this.path = path; + } + + public String getMessage() { + return message; + } + + public int getStatus() { + return status; + } + + public String getError() { + return error; + } + + public String getDate() { + SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss z"); + return formatter.format( date ); + } + + public String getPath() { + return path; + } +} From eb8dd52f09de96f2f6284b5c8fe9ac9e2ec29a72 Mon Sep 17 00:00:00 2001 From: seregaivanov Date: Mon, 27 Jul 2020 01:15:28 +0300 Subject: [PATCH 8/8] Add additional error handling, for tasks two and three --- .../java/com/devexperts/service/AccountServiceImpl.java | 8 ++++++-- .../service/AccountTransferParallelService.java | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/devexperts/service/AccountServiceImpl.java b/src/main/java/com/devexperts/service/AccountServiceImpl.java index 91e848e..4bbc27d 100644 --- a/src/main/java/com/devexperts/service/AccountServiceImpl.java +++ b/src/main/java/com/devexperts/service/AccountServiceImpl.java @@ -51,8 +51,12 @@ protected void checkArgumentForTransfer( Account source, Account target, double throw new IllegalArgumentException( "amount is negative or zero" ); } - if ( source == null || target == null ) { - throw new IllegalArgumentException( "source or target is null" ); + if ( source == null ) { + throw new IllegalArgumentException( "source is null" ); + } + + if ( target == null ) { + throw new IllegalArgumentException( "target is null" ); } if ( source.getAccountKey().equals( target.getAccountKey() ) ) { diff --git a/src/main/java/com/devexperts/service/AccountTransferParallelService.java b/src/main/java/com/devexperts/service/AccountTransferParallelService.java index 47ab7b1..3c993af 100644 --- a/src/main/java/com/devexperts/service/AccountTransferParallelService.java +++ b/src/main/java/com/devexperts/service/AccountTransferParallelService.java @@ -18,6 +18,14 @@ public void transfer( Account source, Account target, double amount ) { Account sourceCash = getAccount( source.getAccountKey() ); Account targetCash = getAccount( target.getAccountKey() ); + if (sourceCash == null) { + throw new IllegalArgumentException( "source-account is not found" ); + } + + if (targetCash == null) { + throw new IllegalArgumentException( "target-account is not found" ); + } + //used an optimistic approach to blocking resources synchronized ( sourceCash ) { if ( sourceCash.getBalance() - amount < 0 ) {