Skip to content
This repository has been archived by the owner on Jan 17, 2022. It is now read-only.

Dmitry Noskov's test task #62

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/main/java/com/devexperts/account/AccountKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,26 @@
public class AccountKey {
private final long accountId;

private AccountKey(long accountId) {
protected AccountKey(long accountId) {
this.accountId = accountId;
}

public static AccountKey valueOf(long accountId) {
return new AccountKey(accountId);
}

@Override
public boolean equals(Object anotherKey) {
if (anotherKey instanceof AccountKey) {
return this.accountId == ((AccountKey) anotherKey).accountId;
} else
{
return false;
}
}

public int compare(AccountKey anotherKey) {
return Long.compare(this.accountId, anotherKey.accountId);
}

}
45 changes: 43 additions & 2 deletions src/main/java/com/devexperts/rest/AccountController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,55 @@
package com.devexperts.rest;

import com.devexperts.account.Account;
import com.devexperts.account.AccountKey;
import com.devexperts.service.AccountServiceImpl;
import com.devexperts.service.exceptions.IncorrectAmountOfTransfer;
import com.devexperts.service.exceptions.IncorrectTargetOfTransfer;
import com.devexperts.service.exceptions.InsufficientFundsException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class AccountController extends AbstractAccountController {

public ResponseEntity<Void> transfer(long sourceId, long targetId, double amount) {
return null;
final AccountServiceImpl accountService;

public AccountController(AccountServiceImpl accountService) {
this.accountService = accountService;

//TODO remove after adding proper way to load accounts into accountService
accountService.createAccount(new Account(AccountKey.valueOf(1), "Ivan", "Ivanov", 100.0));
accountService.createAccount(new Account(AccountKey.valueOf(2), "Petr", "Petrov", 200.0));
accountService.createAccount(new Account(AccountKey.valueOf(3), "Pavel", "Pavlov", 300.0));
}

@RequestMapping(value = "/operations/transfer", method = RequestMethod.POST)
public ResponseEntity<Void> transfer(@RequestParam long sourceId, @RequestParam long targetId, @RequestParam double amount) {
try {
accountService.transferThreadSafely(
accountService.getAccount(sourceId),
accountService.getAccount(targetId),
amount);

} catch (IncorrectAmountOfTransfer ex) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} catch (NullPointerException | IncorrectTargetOfTransfer ex) {
// also executes if source and target are equal
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} catch (InsufficientFundsException ex) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
} catch (Exception ex) {
ex.printStackTrace();
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<>(HttpStatus.OK);
}

}
9 changes: 8 additions & 1 deletion src/main/java/com/devexperts/service/AccountService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.devexperts.service;

import com.devexperts.account.Account;
import com.devexperts.service.exceptions.IncorrectAmountOfTransfer;
import com.devexperts.service.exceptions.IncorrectTargetOfTransfer;
import com.devexperts.service.exceptions.InsufficientFundsException;

public interface AccountService {

Expand Down Expand Up @@ -32,6 +35,10 @@ public interface AccountService {
* @param source account to transfer money from
* @param target account to transfer money to
* @param amount dollar amount to transfer
* @throws IncorrectTargetOfTransfer if no such target in service or source and target are equals
* @throws IncorrectAmountOfTransfer if amount of transfer lesser than 0
* @throws InsufficientFundsException if source don't have enough money
* */
void transfer(Account source, Account target, double amount);
void transfer(Account source, Account target, double amount)
throws IncorrectTargetOfTransfer, IncorrectAmountOfTransfer, InsufficientFundsException;
}
153 changes: 146 additions & 7 deletions src/main/java/com/devexperts/service/AccountServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import com.devexperts.account.Account;
import com.devexperts.account.AccountKey;
import com.devexperts.service.exceptions.*;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

// TODO rewrite all null-checks using lombok library's annotaion @NonNull

@Service
public class AccountServiceImpl implements AccountService {

Expand All @@ -17,21 +20,157 @@ public void clear() {
accounts.clear();
}

/**
* Add account into service's list
*
* @param account to add
*/
@Override
public void createAccount(Account account) {
accounts.add(account);
if (account != null)
accounts.add(account);
else {
//throw new NullPointerException();
}
}

// another version, with changed signature (so require more changes),
// but this one checks, is such account has already added,
// and return error code if such occurrence happens
// another way of realization - create a custom exception AccountAlreadyExist and throw it

// /**
// * Add account into service's list
// * @param account
// * @return 0, if successfully added or 1, if such account already exist
// */
// public int createAccount(Account account) {
// if (account != null) {
// if (this.getAccount(account.getAccountKey()) == null) {
// accounts.add(account);
// return 0;
// } else {
// return 1;
// }
// } else {
// //throw new NullPointerException();
// }
// }

@Override
public Account getAccount(long id) {
return accounts.stream()
.filter(account -> account.getAccountKey() == AccountKey.valueOf(id))
.findAny()
.orElse(null);
return getAccount(AccountKey.valueOf(id));
}

// more general realization
// if we will have way to translate AccountKey back to long - can be useless
// but I didn't find such method
public Account getAccount(AccountKey key) {
if (key != null) {
return accounts.parallelStream()
.filter(account -> account.getAccountKey().equals(key))
.findFirst()
.orElse(null);
}
else {
//throw new NullPointerException();
return null;
}
}

// TASK 2

// this method REQUIRE change of signature
// if something happen inside (for example, source of transfer don't have enough money)
// there is no way to find it quickly
//
// in addition, I think, that throwing error is more suitable way here than error code
// because problem is more severe and have parameters (how much money was not enough)

// should this method allow transfer only between accounts that exist in collection "accounts"?
// if it is correct - it should be stated somewhere
// if it isn't correct - why we need List in this class?
// I decide, that it is correct - otherwise, collection of this class will be useless
// however, this means, that we can send to this method only AccountKeys of accounts instead of all Account object
// but it also requires change of signature

@Override
public void transfer(Account source, Account target, double amount) {
//do nothing for now
public void transfer(Account source, Account target, double amount)
throws IncorrectTargetOfTransfer, IncorrectAmountOfTransfer, InsufficientFundsException {
if (amount <= 0.0) {
throw new IncorrectAmountOfTransfer(amount);
}

if (source == null || target == null) {
throw new NullPointerException();
}

if (source.getAccountKey().equals(target.getAccountKey())) {
throw new IncorrectTargetOfTransfer(TypeOfIncorrection.SameAccount);
}

source = this.getAccount(source.getAccountKey());
target = this.getAccount(target.getAccountKey());

if (source == null || target == null)
throw new IncorrectTargetOfTransfer(TypeOfIncorrection.NoSuchAccount);

double moneyLeft = source.getBalance() - amount;
if (moneyLeft >= 0) {
source.setBalance(moneyLeft);
target.setBalance(target.getBalance() + amount);
} else {
throw new InsufficientFundsException(-moneyLeft);
}
}

// TASK 3

public void transferThreadSafely(Account source, Account target, double amount)
throws IncorrectTargetOfTransfer, NullPointerException, InsufficientFundsException, IncorrectAmountOfTransfer {

if (amount <= 0.0) {
throw new IncorrectAmountOfTransfer(amount);
}

if (source == null || target == null) {
throw new NullPointerException();
}

if (source.getAccountKey().equals(target.getAccountKey())) {
throw new IncorrectTargetOfTransfer(TypeOfIncorrection.SameAccount);
}

source = this.getAccount(source.getAccountKey());
target = this.getAccount(target.getAccountKey());

if (source == null || target == null)
throw new IncorrectTargetOfTransfer(TypeOfIncorrection.NoSuchAccount);

// getting the order of locking objects
// source and target must be different accounts!
Account willBeLockedFirst;
Account willBeLockedSecond;

if (source.getAccountKey().compare(target.getAccountKey()) > 0) {
willBeLockedFirst = source;
willBeLockedSecond = target;
} else {
willBeLockedFirst = target;
willBeLockedSecond = source;
}

synchronized (willBeLockedFirst) {
synchronized (willBeLockedSecond) {
double moneyLeft = source.getBalance() - amount;
if (moneyLeft >= 0) {
source.setBalance(moneyLeft);
target.setBalance(target.getBalance() + amount);
} else {
throw new InsufficientFundsException(-moneyLeft);
}
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.devexperts.service.exceptions;

public class IncorrectAmountOfTransfer extends Exception {
private double incorrectAmount;

public IncorrectAmountOfTransfer(double incorrectAmount) {
this.incorrectAmount = incorrectAmount;
}

public double getIncorrectAmount() {
return incorrectAmount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.devexperts.service.exceptions;

public class IncorrectTargetOfTransfer extends Exception {
private TypeOfIncorrection typeOfIncorrection;

public IncorrectTargetOfTransfer(TypeOfIncorrection typeOfIncorrection) {
this.typeOfIncorrection = typeOfIncorrection;
}

public String getTypeOfIncorrection() {
return typeOfIncorrection.toString();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.devexperts.service.exceptions;

public class InsufficientFundsException extends Exception {

private double lackOfFunds;

public InsufficientFundsException(double lack) {
this.lackOfFunds = lack;
}

public double getLackOfFunds() {
return lackOfFunds;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.devexperts.service.exceptions;

public enum TypeOfIncorrection {
NoSuchAccount,
SameAccount
}
Loading