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

Initial commit Tihomir #34

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
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/devexperts/ApplicationRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication
@EnableSwagger2
public class ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(ApplicationRunner.class, args);
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/com/devexperts/account/Account.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.devexperts.account;

import java.util.Objects;

public class Account {
private final AccountKey accountKey;
private final String firstName;
Expand All @@ -17,6 +19,10 @@ public AccountKey getAccountKey() {
return accountKey;
}

public long getAccountKeyId() {
return accountKey.getAccountId();
}

public String getFirstName() {
return firstName;
}
Expand All @@ -32,4 +38,29 @@ public Double getBalance() {
public void setBalance(Double balance) {
this.balance = balance;
}

public void withdraw(Double amount) {
this.balance -= amount;
}

public void deposit(Double amount) {
this.balance += amount;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Account account = (Account) o;
return accountKey.equals(account.accountKey);
}

@Override
public int hashCode() {
return Objects.hash(accountKey);
}
}
43 changes: 34 additions & 9 deletions src/main/java/com/devexperts/account/AccountKey.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,45 @@
package com.devexperts.account;

import java.util.Objects;

/**
* Unique Account identifier
*
* <p>
* NOTE: we suspect that later {@link #accountId} is not going to be uniquely identifying an account,
* as we might add human-readable account representation and some clearing codes for partners.
* */
* NOTE: we suspect that later {@link #accountId} is not going to be uniquely identifying an
* account, as we might add human-readable account representation and some clearing codes for
* partners.
*/
public class AccountKey {
private final long accountId;

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

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

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

public static AccountKey valueOf(long accountId) {
return new AccountKey(accountId);
public long getAccountId() {
return 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);
}
}
56 changes: 54 additions & 2 deletions src/main/java/com/devexperts/rest/AccountController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,66 @@
package com.devexperts.rest;

import com.devexperts.account.Account;
import com.devexperts.service.AccountServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

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

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

@Autowired
AccountController(AccountServiceImpl accountService) {
this.accountService = accountService;
}

/**
* Details: The operation should be available at POST localhost:8080/api/operations/transfer with
* required query parameters: source_id, target_id, amount. Response codes are the following:
* <p>
* 200 (OK) - successful transfer 400 (Bad Request) - one of the parameters in not present or
* amount is invalid 404 (Not Found) - account is not found 500 (Internal Server Error) -
* insufficient account balance
*
* @param sourceId
* @param targetId
* @param amount
* @return
*/
@PostMapping(value = "/operations/transfer")
@Override
public ResponseEntity<Void> transfer(@RequestParam long sourceId, @RequestParam long targetId,
@RequestParam double amount) {

if (amount < 0) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}

Account sourceAccount = accountService.getAccount(sourceId);
Account targetAccount = accountService.getAccount(targetId);

if (sourceAccount == null || targetAccount == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}

if (sourceAccount.getBalance() < amount) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}

try {
accountService.transfer(sourceAccount, targetAccount, amount);

return new ResponseEntity<>(HttpStatus.OK);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
62 changes: 36 additions & 26 deletions src/main/java/com/devexperts/service/AccountServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,46 @@

import com.devexperts.account.Account;
import com.devexperts.account.AccountKey;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Service;

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

@Service
public class AccountServiceImpl implements AccountService {

private final List<Account> accounts = new ArrayList<>();

@Override
public void clear() {
accounts.clear();
}

@Override
public void createAccount(Account account) {
accounts.add(account);
}

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

@Override
public void transfer(Account source, Account target, double amount) {
//do nothing for now
private final Map<Long, Account> accounts;

public AccountServiceImpl() {
accounts = new HashMap<>();
}

@Override
public void clear() {
accounts.clear();
}

@Override
public void createAccount(Account account) {
accounts.put(account.getAccountKeyId(), account);
}

@Override
public Account getAccount(long id) {
return accounts.get(id);
}

@Override
public void transfer(Account source, Account target, double amount) {
// In order to avoid a deadlock we acquire locks in the same order always.
final Account lock1 =
source.getAccountKeyId() < target.getAccountKeyId() ? source : target;
final Account lock2 =
source.getAccountKeyId() < target.getAccountKeyId() ? target : source;
synchronized (lock1) {
synchronized (lock2) {
source.withdraw(amount);
target.deposit(amount);
}
}
}
}
29 changes: 29 additions & 0 deletions src/main/java/com/devexperts/swagger/SpringFoxConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.devexperts.swagger;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SpringFoxConfig {

/**
* Docket configuration bean.
*
* @return Docket
*/
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.devexperts.rest"))
.paths(PathSelectors.any())
.build();
}
}
7 changes: 7 additions & 0 deletions src/main/resources/sql/data/accounts.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
create table accounts
(
ID LONG AUTO_INCREMENT Primary Key,
FIRST_NAME varchar(255) NOT NULL,
LAST_NAME varchar(255) NOT NULL,
BALANCE DOUBLE NOT NULL
);
6 changes: 6 additions & 0 deletions src/main/resources/sql/data/select.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SELECT accounts.ID, accounts.FIRST_NAME, accounts.LAST_NAME
FROM accounts, transfers
WHERE transfers.SOURCE_ID = accounts.ID
AND transfers.TRANSFER_TIME >= 2019-01-01
GROUP BY accounts.ID
HAVING SUM(transfers.AMOUNT) > 1000.00
10 changes: 10 additions & 0 deletions src/main/resources/sql/data/transfers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
create table transfers
(
ID LONG AUTO_INCREMENT Primary Key,
SOURCE_ID long NOT NULL,
TARGET_ID long NOT NULL,
AMOUNT DOUBLE NOT NULL,
TRANSFER_TIME DATE NOT NULL,
FOREIGN KEY (SOURCE_ID) REFERENCES accounts(ID),
FOREIGN KEY (TARGET_ID) REFERENCES accounts(ID)
);