From dd707a1636ca657a48398883cbb0da3cb997269f Mon Sep 17 00:00:00 2001 From: Riyaz Saiyed Date: Wed, 25 Jan 2023 11:46:33 +0000 Subject: [PATCH 01/13] test --- .../cmcodingchallenge/CmCodingChallengeApplicationTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplicationTests.java b/src/test/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplicationTests.java index 0ca0d16..4a63095 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplicationTests.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplicationTests.java @@ -8,6 +8,7 @@ class CmCodingChallengeApplicationTests { @Test void contextLoads() { + System.out.println("success"); } } From 642ab7a15a328c3b8fc647a620468d3ecdcdfab3 Mon Sep 17 00:00:00 2001 From: Riyaz Saiyed Date: Wed, 25 Jan 2023 11:47:36 +0000 Subject: [PATCH 02/13] test push from terminal --- .../cmcodingchallenge/CmCodingChallengeApplicationTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplicationTests.java b/src/test/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplicationTests.java index 4a63095..0ca0d16 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplicationTests.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplicationTests.java @@ -8,7 +8,6 @@ class CmCodingChallengeApplicationTests { @Test void contextLoads() { - System.out.println("success"); } } From e4e276204f05490ab41fa17249f5c8e5090ac976 Mon Sep 17 00:00:00 2001 From: Riyaz Saiyed Date: Wed, 25 Jan 2023 14:43:32 +0000 Subject: [PATCH 03/13] updates --- pom.xml | 5 +++++ .../currency/CurrencyController.java | 11 ++++++++++- .../currency/CurrencyConversionRates.java | 13 +++++-------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index b3c1148..48d5a83 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,11 @@ org.springframework.boot spring-boot-starter-web + + org.projectlombok + lombok + 1.18.24 + org.springframework.boot diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java index d69be89..77cbe26 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java @@ -15,8 +15,17 @@ public class CurrencyController { @GetMapping("/currencies") public ResponseEntity> getCurrencies() { ArrayList currencyConversionRates = new ArrayList(); - currencyConversionRates.add(new CurrencyConversionRates(2.5)); + currencyConversionRates.add(new CurrencyConversionRates(3.5)); return new ResponseEntity>(currencyConversionRates, HttpStatus.OK); } + + @GetMapping("/exchangerates") + public ResponseEntity> getExchangeRates() { + ArrayList currencyConversionRates = new ArrayList(); + currencyConversionRates.add(new CurrencyConversionRates(3.5)); + + return new ResponseEntity>(currencyConversionRates, HttpStatus.OK); + } + } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyConversionRates.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyConversionRates.java index d3bf6e5..c4857b7 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyConversionRates.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyConversionRates.java @@ -1,17 +1,14 @@ package com.crewmeister.cmcodingchallenge.currency; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter public class CurrencyConversionRates { private double conversionRate; public CurrencyConversionRates(double conversionRate) { this.conversionRate = conversionRate; } - - public double getConversionRate() { - return conversionRate; - } - - public void setConversionRate(double conversionRate) { - this.conversionRate = conversionRate; - } } From a82ce76a00247f04655e3ebb2ffad1a524c3b253 Mon Sep 17 00:00:00 2001 From: M Riyaz Saiyed Date: Thu, 26 Jan 2023 12:35:43 +0100 Subject: [PATCH 04/13] story 1 - get All Currencies --- README.md | 14 ++- pom.xml | 10 ++ .../CmCodingChallengeApplication.java | 7 +- .../config/RestTemplateConfiguration.java | 15 +++ .../cmcodingchallenge/constants/Currency.java | 51 ++++++++++ .../currency/CurrencyController.java | 28 ++++-- .../currency/CurrencyConversionRates.java | 14 --- .../schema/external/DataSet.java | 18 ++++ .../schema/external/GenericData.java | 17 ++++ .../schema/external/Header.java | 10 ++ .../schema/external/Obs.java | 17 ++++ .../schema/external/ObsDimension.java | 10 ++ .../schema/external/ObsValue.java | 10 ++ .../schema/external/Series.java | 21 ++++ .../schema/external/SeriesKey.java | 16 +++ .../schema/external/Value.java | 15 +++ .../schema/internal/ConversionRate.java | 8 ++ .../internal/CurrencyConversionRates.java | 14 +++ .../schema/internal/CurrencyResponse.java | 12 +++ .../service/FXRateService.java | 21 ++++ .../cmcodingchallenge/util/RestUtil.java | 21 ++++ .../cmcodingchallenge/util/XMLUtil.java | 12 +++ .../cmcodingchallenge/TestRestUtil.java | 33 +++++++ .../cmcodingchallenge/TestXMLUtil.java | 22 +++++ src/test/resources/test-data.xml | 99 +++++++++++++++++++ 25 files changed, 487 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/config/RestTemplateConfiguration.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/constants/Currency.java delete mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyConversionRates.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/external/DataSet.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/external/GenericData.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Header.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Obs.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsDimension.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsValue.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Series.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/external/SeriesKey.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Value.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyResponse.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/util/XMLUtil.java create mode 100644 src/test/java/com/crewmeister/cmcodingchallenge/TestRestUtil.java create mode 100644 src/test/java/com/crewmeister/cmcodingchallenge/TestXMLUtil.java create mode 100644 src/test/resources/test-data.xml diff --git a/README.md b/README.md index 44aae28..6529558 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,19 @@ Your task is to create a foreign exchange rate service as SpringBoot-based micro The exchange rates can be received from [2]. This is a public service provided by the German central bank. As we are using user story format to specify our requirements, here are the user stories to implement: - -- As a client, I want to get a list of all available currencies +/api +- As a client, I want to get a list of all available currencies + - /currencies + - getAllCurrencies() - As a client, I want to get all EUR-FX exchange rates at all available dates as a collection -- As a client, I want to get the EUR-FX exchange rate at particular day + - /exchangerates?date=all + - getExchangeRates() +- As a client, I want to get the EUR-FX exchange rate at particular day + - /exchangerates?date='12-JAN-23' + - getExchangeRates(DATE perticularDat) - As a client, I want to get a foreign exchange amount for a given currency converted to EUR on a particular day + - /exchangerates?date='12-JAN-23'¤cy='USD' + - getExchangeRates(Date perticularDate, Currency currencyCode) If you think that your service would require storage, please use H2 for simplicity, even if this would not be your choice if you would implement an endpoint for real clients. diff --git a/pom.xml b/pom.xml index 48d5a83..97f886f 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,16 @@ lombok 1.18.24 + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.11.1 + + + com.fasterxml.jackson.core + jackson-databind + 2.11.1 + org.springframework.boot diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplication.java b/src/main/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplication.java index 409a877..f27e62c 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplication.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplication.java @@ -1,13 +1,16 @@ package com.crewmeister.cmcodingchallenge; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; @SpringBootApplication public class CmCodingChallengeApplication { - public static void main(String[] args) { SpringApplication.run(CmCodingChallengeApplication.class, args); } - } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/config/RestTemplateConfiguration.java b/src/main/java/com/crewmeister/cmcodingchallenge/config/RestTemplateConfiguration.java new file mode 100644 index 0000000..19861a3 --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/config/RestTemplateConfiguration.java @@ -0,0 +1,15 @@ +package com.crewmeister.cmcodingchallenge.config; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfiguration { + + @Bean + public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder.defaultMessageConverters().build(); + } +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/constants/Currency.java b/src/main/java/com/crewmeister/cmcodingchallenge/constants/Currency.java new file mode 100644 index 0000000..3a3a1f6 --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/constants/Currency.java @@ -0,0 +1,51 @@ +package com.crewmeister.cmcodingchallenge.constants; + +public enum Currency { + AUD("AUD"), + BGN("BGN"), + BRL("BRL"), + CAD("CAD"), + CHF("CHF"), + CNY("CNY"), + CYP("CYP"), + CZK("CZK"), + DKK("DKK"), + EEK("EEK"), + GBP("GBP"), + GRD("GRD"), + HKD("HKD"), + HRK("HRK"), + HUF("HUF"), + IDR("IDR"), + ILS("ILS"), + INR("INR"), + ISK("ISK"), + JPY("JPY"), + KRW("KRW"), + LTL("LTL"), + LVL("LVL"), + MTL("MTL"), + MXN("MXN"), + MYR("MYR"), + NOK("NOK"), + NZD("NZD"), + PHP("PHP"), + PLN("PLN"), + ROL("ROL"), + RON("RON"), + RUB("RUB"), + SEK("SEK"), + SGD("SGD"), + SIT("SIT"), + SKK("SKK"), + THB("THB"), + TRL("TRL"), + TRY("TRY"), + USD("USD"), + ZAR("ZAR"); + private final String isoCode; + + Currency(String isoCode) { + this.isoCode = isoCode; + } +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java index 77cbe26..dac1eee 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java @@ -1,5 +1,13 @@ package com.crewmeister.cmcodingchallenge.currency; +import com.crewmeister.cmcodingchallenge.constants.Currency; +import com.crewmeister.cmcodingchallenge.schema.external.GenericData; +import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyConversionRates; +import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyResponse; +import com.crewmeister.cmcodingchallenge.util.RestUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -7,25 +15,27 @@ import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; +import java.util.List; @RestController() @RequestMapping("/api") public class CurrencyController { + private static final Logger log = LoggerFactory.getLogger(CurrencyController.class); - @GetMapping("/currencies") - public ResponseEntity> getCurrencies() { - ArrayList currencyConversionRates = new ArrayList(); - currencyConversionRates.add(new CurrencyConversionRates(3.5)); - return new ResponseEntity>(currencyConversionRates, HttpStatus.OK); + String url = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.PLN.EUR.BB.AC.000?detail=dataonly&lastNObservations=10"; + + @GetMapping("/currencies") + public ResponseEntity getCurrencies() { + return new ResponseEntity(new CurrencyResponse(Currency.values()), HttpStatus.OK); } - + @GetMapping("/exchangerates") public ResponseEntity> getExchangeRates() { - ArrayList currencyConversionRates = new ArrayList(); - currencyConversionRates.add(new CurrencyConversionRates(3.5)); + //ArrayList currencyConversionRates = new ArrayList(); + //currencyConversionRates.add(new CurrencyConversionRates(3.5)); - return new ResponseEntity>(currencyConversionRates, HttpStatus.OK); + return new ResponseEntity>(HttpStatus.OK); } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyConversionRates.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyConversionRates.java deleted file mode 100644 index c4857b7..0000000 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyConversionRates.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.crewmeister.cmcodingchallenge.currency; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class CurrencyConversionRates { - private double conversionRate; - - public CurrencyConversionRates(double conversionRate) { - this.conversionRate = conversionRate; - } -} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/DataSet.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/DataSet.java new file mode 100644 index 0000000..49c5c84 --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/DataSet.java @@ -0,0 +1,18 @@ +package com.crewmeister.cmcodingchallenge.schema.external; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Getter; +import lombok.Setter; + +//@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +@Setter +public class DataSet { + String structureRef; + String setID; + String action; + String validFromDate; + + @JacksonXmlProperty(localName ="Series") + Series series; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/GenericData.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/GenericData.java new file mode 100644 index 0000000..3cf657f --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/GenericData.java @@ -0,0 +1,17 @@ +package com.crewmeister.cmcodingchallenge.schema.external; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Data; + +@Data +@JacksonXmlRootElement(localName = "GenericData") +public class GenericData { + String schemaLocation; + + @JacksonXmlProperty(localName ="Header") + Header header; + + @JacksonXmlProperty(localName ="DataSet") + DataSet dataSet; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Header.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Header.java new file mode 100644 index 0000000..d8cae9f --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Header.java @@ -0,0 +1,10 @@ +package com.crewmeister.cmcodingchallenge.schema.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Header { + @JsonProperty("ID") + String id; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Obs.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Obs.java new file mode 100644 index 0000000..db1392e --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Obs.java @@ -0,0 +1,17 @@ +package com.crewmeister.cmcodingchallenge.schema.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Getter; +import lombok.Setter; + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +@Setter +public class Obs { + @JacksonXmlProperty(localName ="ObsDimension") + ObsDimension obsDimension; + + @JacksonXmlProperty(localName ="ObsValue") + ObsValue obsValue; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsDimension.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsDimension.java new file mode 100644 index 0000000..9d6001b --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsDimension.java @@ -0,0 +1,10 @@ +package com.crewmeister.cmcodingchallenge.schema.external; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ObsDimension { + String value; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsValue.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsValue.java new file mode 100644 index 0000000..898f43a --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsValue.java @@ -0,0 +1,10 @@ +package com.crewmeister.cmcodingchallenge.schema.external; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ObsValue { + String value; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Series.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Series.java new file mode 100644 index 0000000..833c6f6 --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Series.java @@ -0,0 +1,21 @@ +package com.crewmeister.cmcodingchallenge.schema.external; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +@Setter +public class Series { + @JacksonXmlProperty(localName ="SeriesKey") + SeriesKey seriesKey; + + @JacksonXmlElementWrapper(useWrapping = false) + @JacksonXmlProperty(localName ="Obs") + List obsList; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/SeriesKey.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/SeriesKey.java new file mode 100644 index 0000000..128ddd9 --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/SeriesKey.java @@ -0,0 +1,16 @@ +package com.crewmeister.cmcodingchallenge.schema.external; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class SeriesKey { + @JacksonXmlElementWrapper(useWrapping = false) + @JacksonXmlProperty(localName = "Value") + List valueList; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Value.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Value.java new file mode 100644 index 0000000..fc06891 --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Value.java @@ -0,0 +1,15 @@ +package com.crewmeister.cmcodingchallenge.schema.external; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Value { + @JacksonXmlProperty(localName = "id") + String id; + + @JacksonXmlProperty(localName = "value") + String value; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java new file mode 100644 index 0000000..9943219 --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java @@ -0,0 +1,8 @@ +package com.crewmeister.cmcodingchallenge.schema.internal; + +import com.crewmeister.cmcodingchallenge.constants.Currency; + +public class ConversionRate { + private Currency currency; + private double conversionRate; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java new file mode 100644 index 0000000..1d259ee --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java @@ -0,0 +1,14 @@ +package com.crewmeister.cmcodingchallenge.schema.internal; + +import com.crewmeister.cmcodingchallenge.constants.Currency; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class CurrencyConversionRates { + + private List conversionRateList; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyResponse.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyResponse.java new file mode 100644 index 0000000..505967f --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyResponse.java @@ -0,0 +1,12 @@ +package com.crewmeister.cmcodingchallenge.schema.internal; + +import com.crewmeister.cmcodingchallenge.constants.Currency; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@AllArgsConstructor +public class CurrencyResponse { + private Currency[] currencies; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java b/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java new file mode 100644 index 0000000..5448e3b --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java @@ -0,0 +1,21 @@ +package com.crewmeister.cmcodingchallenge.service; + +import com.crewmeister.cmcodingchallenge.constants.Currency; +import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyConversionRates; +import com.crewmeister.cmcodingchallenge.util.RestUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class FXRateService { + + @Autowired + private RestUtil restUtil; + + public List getFXRates(String date, String currencyCode) { + return null; + } + +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java b/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java new file mode 100644 index 0000000..ffd413e --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java @@ -0,0 +1,21 @@ +package com.crewmeister.cmcodingchallenge.util; + +import com.crewmeister.cmcodingchallenge.CmCodingChallengeApplication; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +public class RestUtil { + private static final Logger log = LoggerFactory.getLogger(CmCodingChallengeApplication.class); + @Autowired + private RestTemplate restTemplate; + + public T getForObject(String url, Class typeC) { + log.info("invoking:"+ url); + return restTemplate.getForObject(url, typeC); + } +} + diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/util/XMLUtil.java b/src/main/java/com/crewmeister/cmcodingchallenge/util/XMLUtil.java new file mode 100644 index 0000000..52609d4 --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/util/XMLUtil.java @@ -0,0 +1,12 @@ +package com.crewmeister.cmcodingchallenge.util; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +import java.io.IOException; + +public class XMLUtil { + private static final XmlMapper xmlMapper = new XmlMapper(); + public static T parseData(String xmlData, Class typeC) throws IOException { + return xmlMapper.readValue(xmlData, typeC); + } +} diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/TestRestUtil.java b/src/test/java/com/crewmeister/cmcodingchallenge/TestRestUtil.java new file mode 100644 index 0000000..97b975a --- /dev/null +++ b/src/test/java/com/crewmeister/cmcodingchallenge/TestRestUtil.java @@ -0,0 +1,33 @@ +package com.crewmeister.cmcodingchallenge; + +import com.crewmeister.cmcodingchallenge.schema.external.GenericData; +import com.crewmeister.cmcodingchallenge.util.RestUtil; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.util.Currency; + +public class TestRestUtil { + + @Autowired + private RestUtil restUtil; + + + + String url1 = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.PLN.EUR.BB.AC.000?detail=dataonly&lastNObservations=10"; + + //@Test + /*void testRestServiceUtil() throws IOException { + GenericData value = restUtil.getForObject(url, GenericData.class); + System.out.println(value.getDataSet()); + }*/ + + @Test + void testRestService() throws IOException { + System.out.println(Currency.getAvailableCurrencies()); + GenericData value = new RestTemplate().getForObject(url1, GenericData.class); + System.out.println(value.getDataSet()); + } +} diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/TestXMLUtil.java b/src/test/java/com/crewmeister/cmcodingchallenge/TestXMLUtil.java new file mode 100644 index 0000000..ca6df00 --- /dev/null +++ b/src/test/java/com/crewmeister/cmcodingchallenge/TestXMLUtil.java @@ -0,0 +1,22 @@ +package com.crewmeister.cmcodingchallenge; + +import com.crewmeister.cmcodingchallenge.util.XMLUtil; +import com.crewmeister.cmcodingchallenge.schema.external.GenericData; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class TestXMLUtil { + + @Test + void testXmlToEntity() throws IOException { + String resourceName = "test-data.xml"; + ClassLoader classLoader = getClass().getClassLoader(); + File file = new File(classLoader.getResource(resourceName).getFile()); + String content = Files.readString(file.toPath()); + GenericData value = XMLUtil.parseData(content, GenericData.class); + System.out.println(value.getDataSet()); + } +} diff --git a/src/test/resources/test-data.xml b/src/test/resources/test-data.xml new file mode 100644 index 0000000..63dec0a --- /dev/null +++ b/src/test/resources/test-data.xml @@ -0,0 +1,99 @@ + + + + BBEX3_D_PLN_EUR_BB_AC_000 + false + 2023-01-25T16:00:29+01:00 + + Deutsche Bundesbank + + presse-information@bundesbank.de + + + + + urn:sdmx:org.sdmx.infomodel.datastructure.DataStructure=BBK:BBK_ERX(1.0) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 086c30bec68610e036e319f1d84c0263952bd12c Mon Sep 17 00:00:00 2001 From: M Riyaz Saiyed Date: Tue, 31 Jan 2023 12:19:59 +0100 Subject: [PATCH 05/13] rest api for getting exchange rates --- .../cmcodingchallenge/constants/Currency.java | 8 +++ .../currency/CurrencyController.java | 60 ++++++++++++++----- .../CurrencyNotSupportedException.java | 13 ++++ .../schema/external/ObsValue.java | 8 ++- .../schema/internal/ConversionRate.java | 9 ++- .../internal/CurrencyConversionRates.java | 2 +- .../schema/internal/CurrencyResponse.java | 3 + .../schema/internal/ExchangedAmount.java | 12 ++++ .../service/FXRateService.java | 38 +++++++++++- .../cmcodingchallenge/util/RestUtil.java | 7 ++- .../util/SchemaTransformer.java | 50 ++++++++++++++++ src/main/resources/application.properties | 1 - src/main/resources/application.yaml | 10 ++++ .../TestCurrencyController.java | 33 ++++++++++ 14 files changed, 230 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/exception/CurrencyNotSupportedException.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ExchangedAmount.java create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/util/SchemaTransformer.java delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yaml create mode 100644 src/test/java/com/crewmeister/cmcodingchallenge/TestCurrencyController.java diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/constants/Currency.java b/src/main/java/com/crewmeister/cmcodingchallenge/constants/Currency.java index 3a3a1f6..13969bc 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/constants/Currency.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/constants/Currency.java @@ -1,5 +1,8 @@ package com.crewmeister.cmcodingchallenge.constants; +import java.util.Arrays; +import java.util.Optional; + public enum Currency { AUD("AUD"), BGN("BGN"), @@ -48,4 +51,9 @@ public enum Currency { Currency(String isoCode) { this.isoCode = isoCode; } + + public static Optional getCurrency(String currencyCode) { + return Arrays.stream(values()) + .filter(code -> code.isoCode.equalsIgnoreCase(currencyCode)).findFirst(); + } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java index dac1eee..71d4e3d 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java @@ -1,41 +1,71 @@ package com.crewmeister.cmcodingchallenge.currency; import com.crewmeister.cmcodingchallenge.constants.Currency; -import com.crewmeister.cmcodingchallenge.schema.external.GenericData; +import com.crewmeister.cmcodingchallenge.exception.CurrencyNotSupportedException; +import com.crewmeister.cmcodingchallenge.schema.internal.ConversionRate; import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyConversionRates; import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyResponse; -import com.crewmeister.cmcodingchallenge.util.RestUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.crewmeister.cmcodingchallenge.schema.internal.ExchangedAmount; +import com.crewmeister.cmcodingchallenge.service.FXRateService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; 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; -import java.util.ArrayList; -import java.util.List; +import java.util.Optional; @RestController() @RequestMapping("/api") public class CurrencyController { - private static final Logger log = LoggerFactory.getLogger(CurrencyController.class); - - String url = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.PLN.EUR.BB.AC.000?detail=dataonly&lastNObservations=10"; + @Autowired + private FXRateService fxRateService; @GetMapping("/currencies") public ResponseEntity getCurrencies() { - return new ResponseEntity(new CurrencyResponse(Currency.values()), HttpStatus.OK); + return new ResponseEntity<>(new CurrencyResponse(Currency.values()), HttpStatus.OK); + } + + @GetMapping("/exchange-rates") + public ResponseEntity getExchangeRates( + @RequestParam() String currencyCode, + @RequestParam(required = false) String date + ) { + CurrencyConversionRates rates = null; + try { + rates = fxRateService.getFXRates(currencyCode, date); + } catch (CurrencyNotSupportedException e) { + handleCurrencyNotSupportedException(); + } + return new ResponseEntity<>(rates, HttpStatus.OK); } - @GetMapping("/exchangerates") - public ResponseEntity> getExchangeRates() { - //ArrayList currencyConversionRates = new ArrayList(); - //currencyConversionRates.add(new CurrencyConversionRates(3.5)); + @GetMapping("/exchange-currency") + public ResponseEntity getExchangeValue( + @RequestParam() String currencyCode, + @RequestParam() String date, + @RequestParam() String amount + ) { + Optional currencyConversionRate = null; + try { + currencyConversionRate = fxRateService.getFXRates(currencyCode, date).getConversionRateList().stream().findFirst(); + } catch (CurrencyNotSupportedException e) { + handleCurrencyNotSupportedException(); + } + var exchangedAmount = new ExchangedAmount(); - return new ResponseEntity>(HttpStatus.OK); + if (currencyConversionRate.isPresent()) { + exchangedAmount.setAmount(currencyConversionRate.get().getConversionRate() * Double.parseDouble(amount)); + exchangedAmount.setConversionRate(currencyConversionRate.get()); + } + return new ResponseEntity<>(exchangedAmount, HttpStatus.OK); } + private void handleCurrencyNotSupportedException() { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Currency Not Supported"); + } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/exception/CurrencyNotSupportedException.java b/src/main/java/com/crewmeister/cmcodingchallenge/exception/CurrencyNotSupportedException.java new file mode 100644 index 0000000..3c381e0 --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/exception/CurrencyNotSupportedException.java @@ -0,0 +1,13 @@ +package com.crewmeister.cmcodingchallenge.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Getter +@AllArgsConstructor +public class CurrencyNotSupportedException extends Exception { + private String isoCode; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsValue.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsValue.java index 898f43a..49be10f 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsValue.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsValue.java @@ -1,10 +1,16 @@ package com.crewmeister.cmcodingchallenge.schema.external; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter +@AllArgsConstructor public class ObsValue { - String value; + private String value; + + public ObsValue() { + } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java index 9943219..7015e8d 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java @@ -1,8 +1,15 @@ package com.crewmeister.cmcodingchallenge.schema.internal; import com.crewmeister.cmcodingchallenge.constants.Currency; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +@Getter +@Setter +@AllArgsConstructor public class ConversionRate { - private Currency currency; private double conversionRate; + private String date; + private Currency currency; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java index 1d259ee..59dbc82 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java @@ -1,6 +1,7 @@ package com.crewmeister.cmcodingchallenge.schema.internal; import com.crewmeister.cmcodingchallenge.constants.Currency; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -9,6 +10,5 @@ @Getter @Setter public class CurrencyConversionRates { - private List conversionRateList; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyResponse.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyResponse.java index 505967f..28b7a01 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyResponse.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyResponse.java @@ -6,7 +6,10 @@ import lombok.Setter; @Getter +@Setter @AllArgsConstructor public class CurrencyResponse { + public CurrencyResponse() { + } private Currency[] currencies; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ExchangedAmount.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ExchangedAmount.java new file mode 100644 index 0000000..86158d5 --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ExchangedAmount.java @@ -0,0 +1,12 @@ +package com.crewmeister.cmcodingchallenge.schema.internal; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ExchangedAmount { + private double amount; + private ConversionRate conversionRate; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java b/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java index 5448e3b..e3a1444 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java @@ -1,21 +1,53 @@ package com.crewmeister.cmcodingchallenge.service; import com.crewmeister.cmcodingchallenge.constants.Currency; +import com.crewmeister.cmcodingchallenge.exception.CurrencyNotSupportedException; +import com.crewmeister.cmcodingchallenge.schema.external.GenericData; import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyConversionRates; import com.crewmeister.cmcodingchallenge.util.RestUtil; +import com.crewmeister.cmcodingchallenge.util.SchemaTransformer; +import lombok.extern.slf4j.Slf4j; +import lombok.val; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.web.util.UriComponentsBuilder; -import java.util.List; +import java.util.Arrays; +@Slf4j @Service public class FXRateService { + @Value("${external.fx.scheme}") + private String fxScheme; + + @Value("${external.fx.host}") + private String fxHost; + @Autowired private RestUtil restUtil; - public List getFXRates(String date, String currencyCode) { - return null; + private static final String TIME_SERIES = "BBEX3/D.XXX.EUR.BB.AC.000"; + + public CurrencyConversionRates getFXRates(String currencyCode, String date) throws CurrencyNotSupportedException { + validateSupportedCurrency(currencyCode); + UriComponentsBuilder uriComponents = UriComponentsBuilder.newInstance() + .scheme(fxScheme).host(fxHost) + .path(TIME_SERIES.replace("XXX", currencyCode)) + .queryParam("detail","dataonly"); + + if (date != null) { + uriComponents.queryParam("startPeriod",date) + .queryParam("endPeriod",date); + } + return SchemaTransformer.transform(restUtil.getForObject(uriComponents.build().toString(), GenericData.class)); } + private void validateSupportedCurrency(String currencyCode) throws CurrencyNotSupportedException { + val currencyStream = Arrays.stream(Currency.values()); + if(Currency.getCurrency(currencyCode).isEmpty()) { + throw new CurrencyNotSupportedException(currencyCode); + } + } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java b/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java index ffd413e..b51c2e7 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java @@ -1,20 +1,23 @@ package com.crewmeister.cmcodingchallenge.util; import com.crewmeister.cmcodingchallenge.CmCodingChallengeApplication; +import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; +import java.util.Map; + +@Slf4j @Component public class RestUtil { - private static final Logger log = LoggerFactory.getLogger(CmCodingChallengeApplication.class); @Autowired private RestTemplate restTemplate; public T getForObject(String url, Class typeC) { - log.info("invoking:"+ url); + log.info("invoking:" + url); return restTemplate.getForObject(url, typeC); } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/util/SchemaTransformer.java b/src/main/java/com/crewmeister/cmcodingchallenge/util/SchemaTransformer.java new file mode 100644 index 0000000..6faa7f9 --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/util/SchemaTransformer.java @@ -0,0 +1,50 @@ +package com.crewmeister.cmcodingchallenge.util; + +import com.crewmeister.cmcodingchallenge.constants.Currency; +import com.crewmeister.cmcodingchallenge.schema.external.*; +import com.crewmeister.cmcodingchallenge.schema.internal.ConversionRate; +import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyConversionRates; +import lombok.val; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class SchemaTransformer { + private static final String BBK_STD_CURRENCY = "BBK_STD_CURRENCY"; + + public static CurrencyConversionRates transform(GenericData genericData) { + var currencyConversionRates = new CurrencyConversionRates(); + var series = extractSeries(genericData); + var obsList = extractObsList(series); + if (!obsList.isEmpty()) { + currencyConversionRates.setConversionRateList( + obsList.stream().map(e -> constructObject(series, e)).collect(Collectors.toList())); + } + return currencyConversionRates; + } + + private static Series extractSeries(GenericData genericData) { + return Optional.ofNullable(genericData) + .map(o -> Optional.ofNullable(o.getDataSet()).orElse(new DataSet())) + .map(o -> Optional.ofNullable(o.getSeries()).orElse(new Series())).get(); + } + + private static String extractSeriesValue(List valueList, String key) { + return valueList.stream().filter(v -> key.equals(v.getId())).findFirst().orElse(new Value()).getValue(); + } + + private static List extractObsList(Series series) { + return Optional.ofNullable(series) + .map(o -> Optional.ofNullable(o.getObsList()).orElse(new ArrayList<>())) + .orElse(new ArrayList<>()); + } + + private static ConversionRate constructObject(Series series, Obs e) { + return new ConversionRate(Double.parseDouble( + Optional.ofNullable(e.getObsValue()).orElse(new ObsValue("0.0")).getValue()), + Optional.ofNullable(e.getObsDimension()).orElse(new ObsDimension()).getValue(), + Currency.valueOf(extractSeriesValue(series.getSeriesKey().getValueList(), BBK_STD_CURRENCY))); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 8b13789..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..bf3895e --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,10 @@ +server: + application: + name: FX Rate API + error: + include-stacktrace: never + +external: + fx: + scheme: https + host: api.statistiken.bundesbank.de/rest/data \ No newline at end of file diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/TestCurrencyController.java b/src/test/java/com/crewmeister/cmcodingchallenge/TestCurrencyController.java new file mode 100644 index 0000000..ae0dbdf --- /dev/null +++ b/src/test/java/com/crewmeister/cmcodingchallenge/TestCurrencyController.java @@ -0,0 +1,33 @@ +package com.crewmeister.cmcodingchallenge; + +import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyResponse; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; + +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class TestCurrencyController { + + @Value(value = "${local.server.port}") + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void testGetCurrenciesSuccess() { + var response = this.restTemplate.getForEntity( + "http://localhost:" + port + "/api/currencies", + CurrencyResponse.class); + + assertEquals(response.getStatusCode(), HttpStatus.OK); + assertEquals(Objects.requireNonNull(response.getBody()).getCurrencies().length, 42); + } +} From 4ce854c2d661a1f0472108129ac61a3607e313d8 Mon Sep 17 00:00:00 2001 From: M Riyaz Saiyed Date: Tue, 31 Jan 2023 15:12:53 +0100 Subject: [PATCH 06/13] error handling --- .../currency/CurrencyController.java | 27 +++++++------- .../exception/RateNotAvailableException.java | 10 +++++ .../schema/internal/ConversionRate.java | 2 +- .../internal/CurrencyConversionRates.java | 1 + .../schema/internal/ExchangedAmount.java | 2 +- .../service/FXRateService.java | 37 +++++++++++++++++-- .../cmcodingchallenge/util/RestUtil.java | 5 --- .../util/SchemaTransformer.java | 9 ++--- src/main/resources/application.yaml | 2 +- 9 files changed, 64 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/exception/RateNotAvailableException.java diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java index 71d4e3d..e4c4531 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java @@ -2,7 +2,7 @@ import com.crewmeister.cmcodingchallenge.constants.Currency; import com.crewmeister.cmcodingchallenge.exception.CurrencyNotSupportedException; -import com.crewmeister.cmcodingchallenge.schema.internal.ConversionRate; +import com.crewmeister.cmcodingchallenge.exception.RateNotAvailableException; import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyConversionRates; import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyResponse; import com.crewmeister.cmcodingchallenge.schema.internal.ExchangedAmount; @@ -16,8 +16,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; -import java.util.Optional; - @RestController() @RequestMapping("/api") public class CurrencyController { @@ -45,22 +43,19 @@ public ResponseEntity getExchangeRates( } @GetMapping("/exchange-currency") - public ResponseEntity getExchangeValue( + public ResponseEntity getExchangedAmount( @RequestParam() String currencyCode, - @RequestParam() String date, - @RequestParam() String amount + @RequestParam() String amount, + @RequestParam(required = false) String date + ) { - Optional currencyConversionRate = null; + ExchangedAmount exchangedAmount = null; try { - currencyConversionRate = fxRateService.getFXRates(currencyCode, date).getConversionRateList().stream().findFirst(); + exchangedAmount = fxRateService.getFXValue(currencyCode, amount, date); } catch (CurrencyNotSupportedException e) { handleCurrencyNotSupportedException(); - } - var exchangedAmount = new ExchangedAmount(); - - if (currencyConversionRate.isPresent()) { - exchangedAmount.setAmount(currencyConversionRate.get().getConversionRate() * Double.parseDouble(amount)); - exchangedAmount.setConversionRate(currencyConversionRate.get()); + } catch (RateNotAvailableException e) { + handleRateNotAvailableException(); } return new ResponseEntity<>(exchangedAmount, HttpStatus.OK); } @@ -68,4 +63,8 @@ public ResponseEntity getExchangeValue( private void handleCurrencyNotSupportedException() { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Currency Not Supported"); } + + private void handleRateNotAvailableException() { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Rate Not Available"); + } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/exception/RateNotAvailableException.java b/src/main/java/com/crewmeister/cmcodingchallenge/exception/RateNotAvailableException.java new file mode 100644 index 0000000..1a2e1cf --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/exception/RateNotAvailableException.java @@ -0,0 +1,10 @@ +package com.crewmeister.cmcodingchallenge.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class RateNotAvailableException extends Exception { + private String date; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java index 7015e8d..7aaf5c5 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java @@ -9,7 +9,7 @@ @Setter @AllArgsConstructor public class ConversionRate { - private double conversionRate; + private String conversionRate; private String date; private Currency currency; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java index 59dbc82..fe1b6a2 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java @@ -9,6 +9,7 @@ @Getter @Setter +@AllArgsConstructor public class CurrencyConversionRates { private List conversionRateList; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ExchangedAmount.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ExchangedAmount.java index 86158d5..158c945 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ExchangedAmount.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ExchangedAmount.java @@ -7,6 +7,6 @@ @Getter @Setter public class ExchangedAmount { - private double amount; + private String amount; private ConversionRate conversionRate; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java b/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java index e3a1444..961c82b 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java @@ -2,8 +2,11 @@ import com.crewmeister.cmcodingchallenge.constants.Currency; import com.crewmeister.cmcodingchallenge.exception.CurrencyNotSupportedException; +import com.crewmeister.cmcodingchallenge.exception.RateNotAvailableException; import com.crewmeister.cmcodingchallenge.schema.external.GenericData; +import com.crewmeister.cmcodingchallenge.schema.internal.ConversionRate; import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyConversionRates; +import com.crewmeister.cmcodingchallenge.schema.internal.ExchangedAmount; import com.crewmeister.cmcodingchallenge.util.RestUtil; import com.crewmeister.cmcodingchallenge.util.SchemaTransformer; import lombok.extern.slf4j.Slf4j; @@ -13,7 +16,12 @@ import org.springframework.stereotype.Service; import org.springframework.web.util.UriComponentsBuilder; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.Arrays; +import java.util.Optional; + +import static org.apache.tomcat.util.http.FastHttpDateFormat.getCurrentDate; @Slf4j @Service @@ -30,24 +38,45 @@ public class FXRateService { private static final String TIME_SERIES = "BBEX3/D.XXX.EUR.BB.AC.000"; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd"); + public CurrencyConversionRates getFXRates(String currencyCode, String date) throws CurrencyNotSupportedException { validateSupportedCurrency(currencyCode); UriComponentsBuilder uriComponents = UriComponentsBuilder.newInstance() .scheme(fxScheme).host(fxHost) .path(TIME_SERIES.replace("XXX", currencyCode)) - .queryParam("detail","dataonly"); + .queryParam("detail", "dataonly"); if (date != null) { - uriComponents.queryParam("startPeriod",date) - .queryParam("endPeriod",date); + uriComponents.queryParam("startPeriod", date) + .queryParam("endPeriod", date); } return SchemaTransformer.transform(restUtil.getForObject(uriComponents.build().toString(), GenericData.class)); } + public ExchangedAmount getFXValue(String currencyCode, String amount, String date) throws CurrencyNotSupportedException, RateNotAvailableException { + Optional currencyConversionRate = getFXRates(currencyCode, date == null ? getCurrentDate() : date).getConversionRateList().stream().findFirst(); + var exchangedAmount = new ExchangedAmount(); + if (currencyConversionRate.isPresent()) { + try { + double conversionRate = Double.parseDouble(currencyConversionRate.get().getConversionRate()); + exchangedAmount.setConversionRate(currencyConversionRate.get()); + exchangedAmount.setAmount(String.valueOf(conversionRate * Double.parseDouble(amount))); + } catch (NumberFormatException nfe) { + throw new RateNotAvailableException(date); + } + } + return exchangedAmount; + } + private void validateSupportedCurrency(String currencyCode) throws CurrencyNotSupportedException { val currencyStream = Arrays.stream(Currency.values()); - if(Currency.getCurrency(currencyCode).isEmpty()) { + if (Currency.getCurrency(currencyCode).isEmpty()) { throw new CurrencyNotSupportedException(currencyCode); } } + + private String getCurrentDate() { + return DATE_TIME_FORMATTER.format(LocalDate.now()); + } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java b/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java index b51c2e7..40deae8 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java @@ -1,15 +1,10 @@ package com.crewmeister.cmcodingchallenge.util; -import com.crewmeister.cmcodingchallenge.CmCodingChallengeApplication; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; -import java.util.Map; - @Slf4j @Component public class RestUtil { diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/util/SchemaTransformer.java b/src/main/java/com/crewmeister/cmcodingchallenge/util/SchemaTransformer.java index 6faa7f9..73c8b8b 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/util/SchemaTransformer.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/util/SchemaTransformer.java @@ -4,7 +4,6 @@ import com.crewmeister.cmcodingchallenge.schema.external.*; import com.crewmeister.cmcodingchallenge.schema.internal.ConversionRate; import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyConversionRates; -import lombok.val; import java.util.ArrayList; import java.util.List; @@ -15,11 +14,11 @@ public class SchemaTransformer { private static final String BBK_STD_CURRENCY = "BBK_STD_CURRENCY"; public static CurrencyConversionRates transform(GenericData genericData) { - var currencyConversionRates = new CurrencyConversionRates(); + var currencyConversionRates = new CurrencyConversionRates(new ArrayList<>()); var series = extractSeries(genericData); var obsList = extractObsList(series); if (!obsList.isEmpty()) { - currencyConversionRates.setConversionRateList( + currencyConversionRates.getConversionRateList().addAll( obsList.stream().map(e -> constructObject(series, e)).collect(Collectors.toList())); } return currencyConversionRates; @@ -42,8 +41,8 @@ private static List extractObsList(Series series) { } private static ConversionRate constructObject(Series series, Obs e) { - return new ConversionRate(Double.parseDouble( - Optional.ofNullable(e.getObsValue()).orElse(new ObsValue("0.0")).getValue()), + return new ConversionRate( + Optional.ofNullable(e.getObsValue()).orElse(new ObsValue("No value available")).getValue(), Optional.ofNullable(e.getObsDimension()).orElse(new ObsDimension()).getValue(), Currency.valueOf(extractSeriesValue(series.getSeriesKey().getValueList(), BBK_STD_CURRENCY))); } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index bf3895e..6478d8a 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -2,7 +2,7 @@ server: application: name: FX Rate API error: - include-stacktrace: never + include-stacktrace: ALWAYS external: fx: From 2324156d3cd38720cf5a00a9c116f34e660bd240 Mon Sep 17 00:00:00 2001 From: M Riyaz Saiyed Date: Tue, 31 Jan 2023 20:35:21 +0100 Subject: [PATCH 07/13] test cases --- .../CmCodingChallengeApplication.java | 7 +- .../config/RestTemplateConfiguration.java | 2 +- .../{ => currency}/constants/Currency.java | 2 +- .../{ => controller}/CurrencyController.java | 16 ++-- .../domain}/SchemaTransformer.java | 22 +++-- .../domain}/external/DataSet.java | 6 +- .../domain}/external/GenericData.java | 9 ++- .../domain}/external/Header.java | 2 +- .../domain}/external/Obs.java | 7 +- .../domain/external/ObsDimension.java | 14 ++++ .../domain}/external/ObsValue.java | 8 +- .../domain}/external/Series.java | 6 +- .../domain}/external/SeriesKey.java | 6 +- .../domain}/external/Value.java | 7 +- .../domain}/internal/ConversionRate.java | 4 +- .../internal/CurrencyConversionRates.java | 5 +- .../domain}/internal/CurrencyResponse.java | 4 +- .../domain}/internal/ExchangedAmount.java | 3 +- .../CurrencyNotSupportedException.java | 2 +- .../exception/RateNotAvailableException.java | 2 +- .../{ => currency}/service/FXRateService.java | 25 +++--- .../{ => currency}/util/RestUtil.java | 2 +- .../{ => currency}/util/XMLUtil.java | 2 +- .../schema/external/ObsDimension.java | 10 --- .../TestCurrencyController.java | 33 -------- .../CmCodingChallengeApplicationTests.java | 2 +- .../integration/TestCurrencyController.java | 80 +++++++++++++++++++ .../{ => currency/unit}/TestRestUtil.java | 6 +- .../{ => currency/unit}/TestXMLUtil.java | 6 +- 29 files changed, 186 insertions(+), 114 deletions(-) rename src/main/java/com/crewmeister/cmcodingchallenge/{ => currency}/CmCodingChallengeApplication.java (53%) rename src/main/java/com/crewmeister/cmcodingchallenge/{ => currency}/config/RestTemplateConfiguration.java (88%) rename src/main/java/com/crewmeister/cmcodingchallenge/{ => currency}/constants/Currency.java (94%) rename src/main/java/com/crewmeister/cmcodingchallenge/currency/{ => controller}/CurrencyController.java (78%) rename src/main/java/com/crewmeister/cmcodingchallenge/{util => currency/domain}/SchemaTransformer.java (70%) rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/external/DataSet.java (69%) rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/external/GenericData.java (59%) rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/external/Header.java (76%) rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/external/Obs.java (75%) create mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/ObsDimension.java rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/external/ObsValue.java (58%) rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/external/Series.java (78%) rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/external/SeriesKey.java (71%) rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/external/Value.java (61%) rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/internal/ConversionRate.java (63%) rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/internal/CurrencyConversionRates.java (66%) rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/internal/CurrencyResponse.java (61%) rename src/main/java/com/crewmeister/cmcodingchallenge/{schema => currency/domain}/internal/ExchangedAmount.java (63%) rename src/main/java/com/crewmeister/cmcodingchallenge/{ => currency}/exception/CurrencyNotSupportedException.java (83%) rename src/main/java/com/crewmeister/cmcodingchallenge/{ => currency}/exception/RateNotAvailableException.java (73%) rename src/main/java/com/crewmeister/cmcodingchallenge/{ => currency}/service/FXRateService.java (74%) rename src/main/java/com/crewmeister/cmcodingchallenge/{ => currency}/util/RestUtil.java (88%) rename src/main/java/com/crewmeister/cmcodingchallenge/{ => currency}/util/XMLUtil.java (84%) delete mode 100644 src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsDimension.java delete mode 100644 src/test/java/com/crewmeister/cmcodingchallenge/TestCurrencyController.java rename src/test/java/com/crewmeister/cmcodingchallenge/{ => currency/integration}/CmCodingChallengeApplicationTests.java (75%) create mode 100644 src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java rename src/test/java/com/crewmeister/cmcodingchallenge/{ => currency/unit}/TestRestUtil.java (81%) rename src/test/java/com/crewmeister/cmcodingchallenge/{ => currency/unit}/TestXMLUtil.java (74%) diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplication.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CmCodingChallengeApplication.java similarity index 53% rename from src/main/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplication.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/CmCodingChallengeApplication.java index f27e62c..2afa395 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplication.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CmCodingChallengeApplication.java @@ -1,12 +1,7 @@ -package com.crewmeister.cmcodingchallenge; +package com.crewmeister.cmcodingchallenge.currency; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; @SpringBootApplication public class CmCodingChallengeApplication { diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/config/RestTemplateConfiguration.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/config/RestTemplateConfiguration.java similarity index 88% rename from src/main/java/com/crewmeister/cmcodingchallenge/config/RestTemplateConfiguration.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/config/RestTemplateConfiguration.java index 19861a3..ae32a51 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/config/RestTemplateConfiguration.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/config/RestTemplateConfiguration.java @@ -1,4 +1,4 @@ -package com.crewmeister.cmcodingchallenge.config; +package com.crewmeister.cmcodingchallenge.currency.config; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/constants/Currency.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/constants/Currency.java similarity index 94% rename from src/main/java/com/crewmeister/cmcodingchallenge/constants/Currency.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/constants/Currency.java index 13969bc..a301eac 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/constants/Currency.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/constants/Currency.java @@ -1,4 +1,4 @@ -package com.crewmeister.cmcodingchallenge.constants; +package com.crewmeister.cmcodingchallenge.currency.constants; import java.util.Arrays; import java.util.Optional; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/controller/CurrencyController.java similarity index 78% rename from src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/controller/CurrencyController.java index e4c4531..0b3e3a2 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CurrencyController.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/controller/CurrencyController.java @@ -1,12 +1,12 @@ -package com.crewmeister.cmcodingchallenge.currency; +package com.crewmeister.cmcodingchallenge.currency.controller; -import com.crewmeister.cmcodingchallenge.constants.Currency; -import com.crewmeister.cmcodingchallenge.exception.CurrencyNotSupportedException; -import com.crewmeister.cmcodingchallenge.exception.RateNotAvailableException; -import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyConversionRates; -import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyResponse; -import com.crewmeister.cmcodingchallenge.schema.internal.ExchangedAmount; -import com.crewmeister.cmcodingchallenge.service.FXRateService; +import com.crewmeister.cmcodingchallenge.currency.constants.Currency; +import com.crewmeister.cmcodingchallenge.currency.exception.CurrencyNotSupportedException; +import com.crewmeister.cmcodingchallenge.currency.exception.RateNotAvailableException; +import com.crewmeister.cmcodingchallenge.currency.domain.internal.CurrencyConversionRates; +import com.crewmeister.cmcodingchallenge.currency.domain.internal.CurrencyResponse; +import com.crewmeister.cmcodingchallenge.currency.domain.internal.ExchangedAmount; +import com.crewmeister.cmcodingchallenge.currency.service.FXRateService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/util/SchemaTransformer.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/SchemaTransformer.java similarity index 70% rename from src/main/java/com/crewmeister/cmcodingchallenge/util/SchemaTransformer.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/SchemaTransformer.java index 73c8b8b..91c3d6a 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/util/SchemaTransformer.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/SchemaTransformer.java @@ -1,9 +1,9 @@ -package com.crewmeister.cmcodingchallenge.util; +package com.crewmeister.cmcodingchallenge.currency.domain; -import com.crewmeister.cmcodingchallenge.constants.Currency; -import com.crewmeister.cmcodingchallenge.schema.external.*; -import com.crewmeister.cmcodingchallenge.schema.internal.ConversionRate; -import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyConversionRates; +import com.crewmeister.cmcodingchallenge.currency.constants.Currency; +import com.crewmeister.cmcodingchallenge.currency.domain.external.*; +import com.crewmeister.cmcodingchallenge.currency.domain.internal.ConversionRate; +import com.crewmeister.cmcodingchallenge.currency.domain.internal.CurrencyConversionRates; import java.util.ArrayList; import java.util.List; @@ -25,9 +25,15 @@ public static CurrencyConversionRates transform(GenericData genericData) { } private static Series extractSeries(GenericData genericData) { - return Optional.ofNullable(genericData) - .map(o -> Optional.ofNullable(o.getDataSet()).orElse(new DataSet())) - .map(o -> Optional.ofNullable(o.getSeries()).orElse(new Series())).get(); + Optional dataset = Optional + .ofNullable(genericData) + .map(o -> Optional.ofNullable(o.getDataSet()).orElse(DataSet.builder().build())); + + Optional series = Optional.empty(); + if (dataset.isPresent()) { + series = dataset.map(o -> Optional.ofNullable(o.getSeries())).get(); + } + return series.orElse(Series.builder().build()); } private static String extractSeriesValue(List valueList, String key) { diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/DataSet.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java similarity index 69% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/external/DataSet.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java index 49c5c84..f678f4d 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/DataSet.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java @@ -1,12 +1,16 @@ -package com.crewmeister.cmcodingchallenge.schema.external; +package com.crewmeister.cmcodingchallenge.currency.domain.external; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Builder; import lombok.Getter; import lombok.Setter; +import lombok.extern.jackson.Jacksonized; //@JsonIgnoreProperties(ignoreUnknown = true) @Getter @Setter +@Builder +@Jacksonized public class DataSet { String structureRef; String setID; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/GenericData.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/GenericData.java similarity index 59% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/external/GenericData.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/GenericData.java index 3cf657f..dde3a0f 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/GenericData.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/GenericData.java @@ -1,11 +1,18 @@ -package com.crewmeister.cmcodingchallenge.schema.external; +package com.crewmeister.cmcodingchallenge.currency.domain.external; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.jackson.Jacksonized; @Data +@Builder @JacksonXmlRootElement(localName = "GenericData") +@Jacksonized public class GenericData { String schemaLocation; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Header.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Header.java similarity index 76% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Header.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Header.java index d8cae9f..ccb1344 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Header.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Header.java @@ -1,4 +1,4 @@ -package com.crewmeister.cmcodingchallenge.schema.external; +package com.crewmeister.cmcodingchallenge.currency.domain.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Obs.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Obs.java similarity index 75% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Obs.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Obs.java index db1392e..61e87a6 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Obs.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Obs.java @@ -1,13 +1,14 @@ -package com.crewmeister.cmcodingchallenge.schema.external; +package com.crewmeister.cmcodingchallenge.currency.domain.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import lombok.Getter; -import lombok.Setter; +import lombok.*; @JsonIgnoreProperties(ignoreUnknown = true) @Getter @Setter +@AllArgsConstructor +@NoArgsConstructor public class Obs { @JacksonXmlProperty(localName ="ObsDimension") ObsDimension obsDimension; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/ObsDimension.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/ObsDimension.java new file mode 100644 index 0000000..bff47bf --- /dev/null +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/ObsDimension.java @@ -0,0 +1,14 @@ +package com.crewmeister.cmcodingchallenge.currency.domain.external; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ObsDimension { + String value; +} diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsValue.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/ObsValue.java similarity index 58% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsValue.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/ObsValue.java index 49be10f..56573e8 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsValue.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/ObsValue.java @@ -1,16 +1,14 @@ -package com.crewmeister.cmcodingchallenge.schema.external; +package com.crewmeister.cmcodingchallenge.currency.domain.external; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter +@NoArgsConstructor @AllArgsConstructor public class ObsValue { private String value; - - public ObsValue() { - } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Series.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Series.java similarity index 78% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Series.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Series.java index 833c6f6..127c71f 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Series.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Series.java @@ -1,16 +1,20 @@ -package com.crewmeister.cmcodingchallenge.schema.external; +package com.crewmeister.cmcodingchallenge.currency.domain.external; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Builder; import lombok.Getter; import lombok.Setter; +import lombok.extern.jackson.Jacksonized; import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) @Getter @Setter +@Builder +@Jacksonized public class Series { @JacksonXmlProperty(localName ="SeriesKey") SeriesKey seriesKey; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/SeriesKey.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/SeriesKey.java similarity index 71% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/external/SeriesKey.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/SeriesKey.java index 128ddd9..9f8ba79 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/SeriesKey.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/SeriesKey.java @@ -1,14 +1,18 @@ -package com.crewmeister.cmcodingchallenge.schema.external; +package com.crewmeister.cmcodingchallenge.currency.domain.external; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Builder; import lombok.Getter; import lombok.Setter; +import lombok.extern.jackson.Jacksonized; import java.util.List; @Getter @Setter +@Builder +@Jacksonized public class SeriesKey { @JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "Value") diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Value.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Value.java similarity index 61% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Value.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Value.java index fc06891..64aa2c4 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/Value.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Value.java @@ -1,15 +1,18 @@ -package com.crewmeister.cmcodingchallenge.schema.external; +package com.crewmeister.cmcodingchallenge.currency.domain.external; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter +@AllArgsConstructor +@NoArgsConstructor public class Value { @JacksonXmlProperty(localName = "id") String id; - @JacksonXmlProperty(localName = "value") String value; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/ConversionRate.java similarity index 63% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/ConversionRate.java index 7aaf5c5..fb2b441 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ConversionRate.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/ConversionRate.java @@ -1,6 +1,6 @@ -package com.crewmeister.cmcodingchallenge.schema.internal; +package com.crewmeister.cmcodingchallenge.currency.domain.internal; -import com.crewmeister.cmcodingchallenge.constants.Currency; +import com.crewmeister.cmcodingchallenge.currency.constants.Currency; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyConversionRates.java similarity index 66% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyConversionRates.java index fe1b6a2..e39b648 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyConversionRates.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyConversionRates.java @@ -1,14 +1,15 @@ -package com.crewmeister.cmcodingchallenge.schema.internal; +package com.crewmeister.cmcodingchallenge.currency.domain.internal; -import com.crewmeister.cmcodingchallenge.constants.Currency; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import java.util.List; @Getter @Setter +@NoArgsConstructor @AllArgsConstructor public class CurrencyConversionRates { private List conversionRateList; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyResponse.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyResponse.java similarity index 61% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyResponse.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyResponse.java index 28b7a01..9d6b59b 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/CurrencyResponse.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyResponse.java @@ -1,6 +1,6 @@ -package com.crewmeister.cmcodingchallenge.schema.internal; +package com.crewmeister.cmcodingchallenge.currency.domain.internal; -import com.crewmeister.cmcodingchallenge.constants.Currency; +import com.crewmeister.cmcodingchallenge.currency.constants.Currency; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ExchangedAmount.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/ExchangedAmount.java similarity index 63% rename from src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ExchangedAmount.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/ExchangedAmount.java index 158c945..b25125d 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/internal/ExchangedAmount.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/ExchangedAmount.java @@ -1,6 +1,5 @@ -package com.crewmeister.cmcodingchallenge.schema.internal; +package com.crewmeister.cmcodingchallenge.currency.domain.internal; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/exception/CurrencyNotSupportedException.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/exception/CurrencyNotSupportedException.java similarity index 83% rename from src/main/java/com/crewmeister/cmcodingchallenge/exception/CurrencyNotSupportedException.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/exception/CurrencyNotSupportedException.java index 3c381e0..fc401e4 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/exception/CurrencyNotSupportedException.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/exception/CurrencyNotSupportedException.java @@ -1,4 +1,4 @@ -package com.crewmeister.cmcodingchallenge.exception; +package com.crewmeister.cmcodingchallenge.currency.exception; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/exception/RateNotAvailableException.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/exception/RateNotAvailableException.java similarity index 73% rename from src/main/java/com/crewmeister/cmcodingchallenge/exception/RateNotAvailableException.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/exception/RateNotAvailableException.java index 1a2e1cf..ee056b5 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/exception/RateNotAvailableException.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/exception/RateNotAvailableException.java @@ -1,4 +1,4 @@ -package com.crewmeister.cmcodingchallenge.exception; +package com.crewmeister.cmcodingchallenge.currency.exception; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java similarity index 74% rename from src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java index 961c82b..50e26a1 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/service/FXRateService.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java @@ -1,14 +1,14 @@ -package com.crewmeister.cmcodingchallenge.service; +package com.crewmeister.cmcodingchallenge.currency.service; -import com.crewmeister.cmcodingchallenge.constants.Currency; -import com.crewmeister.cmcodingchallenge.exception.CurrencyNotSupportedException; -import com.crewmeister.cmcodingchallenge.exception.RateNotAvailableException; -import com.crewmeister.cmcodingchallenge.schema.external.GenericData; -import com.crewmeister.cmcodingchallenge.schema.internal.ConversionRate; -import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyConversionRates; -import com.crewmeister.cmcodingchallenge.schema.internal.ExchangedAmount; -import com.crewmeister.cmcodingchallenge.util.RestUtil; -import com.crewmeister.cmcodingchallenge.util.SchemaTransformer; +import com.crewmeister.cmcodingchallenge.currency.constants.Currency; +import com.crewmeister.cmcodingchallenge.currency.exception.CurrencyNotSupportedException; +import com.crewmeister.cmcodingchallenge.currency.exception.RateNotAvailableException; +import com.crewmeister.cmcodingchallenge.currency.domain.external.GenericData; +import com.crewmeister.cmcodingchallenge.currency.domain.internal.ConversionRate; +import com.crewmeister.cmcodingchallenge.currency.domain.internal.CurrencyConversionRates; +import com.crewmeister.cmcodingchallenge.currency.domain.internal.ExchangedAmount; +import com.crewmeister.cmcodingchallenge.currency.util.RestUtil; +import com.crewmeister.cmcodingchallenge.currency.domain.SchemaTransformer; import lombok.extern.slf4j.Slf4j; import lombok.val; import org.springframework.beans.factory.annotation.Autowired; @@ -21,8 +21,6 @@ import java.util.Arrays; import java.util.Optional; -import static org.apache.tomcat.util.http.FastHttpDateFormat.getCurrentDate; - @Slf4j @Service public class FXRateService { @@ -51,7 +49,8 @@ public CurrencyConversionRates getFXRates(String currencyCode, String date) thro uriComponents.queryParam("startPeriod", date) .queryParam("endPeriod", date); } - return SchemaTransformer.transform(restUtil.getForObject(uriComponents.build().toString(), GenericData.class)); + var resp = restUtil.getForObject(uriComponents.build().toString(), GenericData.class); + return SchemaTransformer.transform(resp); } public ExchangedAmount getFXValue(String currencyCode, String amount, String date) throws CurrencyNotSupportedException, RateNotAvailableException { diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/util/RestUtil.java similarity index 88% rename from src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/util/RestUtil.java index 40deae8..5f2e4d6 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/util/RestUtil.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/util/RestUtil.java @@ -1,4 +1,4 @@ -package com.crewmeister.cmcodingchallenge.util; +package com.crewmeister.cmcodingchallenge.currency.util; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/util/XMLUtil.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/util/XMLUtil.java similarity index 84% rename from src/main/java/com/crewmeister/cmcodingchallenge/util/XMLUtil.java rename to src/main/java/com/crewmeister/cmcodingchallenge/currency/util/XMLUtil.java index 52609d4..d1fa3c1 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/util/XMLUtil.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/util/XMLUtil.java @@ -1,4 +1,4 @@ -package com.crewmeister.cmcodingchallenge.util; +package com.crewmeister.cmcodingchallenge.currency.util; import com.fasterxml.jackson.dataformat.xml.XmlMapper; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsDimension.java b/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsDimension.java deleted file mode 100644 index 9d6001b..0000000 --- a/src/main/java/com/crewmeister/cmcodingchallenge/schema/external/ObsDimension.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.crewmeister.cmcodingchallenge.schema.external; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class ObsDimension { - String value; -} diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/TestCurrencyController.java b/src/test/java/com/crewmeister/cmcodingchallenge/TestCurrencyController.java deleted file mode 100644 index ae0dbdf..0000000 --- a/src/test/java/com/crewmeister/cmcodingchallenge/TestCurrencyController.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.crewmeister.cmcodingchallenge; - -import com.crewmeister.cmcodingchallenge.schema.internal.CurrencyResponse; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.HttpStatus; - -import java.util.Objects; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class TestCurrencyController { - - @Value(value = "${local.server.port}") - private int port; - - @Autowired - private TestRestTemplate restTemplate; - - @Test - public void testGetCurrenciesSuccess() { - var response = this.restTemplate.getForEntity( - "http://localhost:" + port + "/api/currencies", - CurrencyResponse.class); - - assertEquals(response.getStatusCode(), HttpStatus.OK); - assertEquals(Objects.requireNonNull(response.getBody()).getCurrencies().length, 42); - } -} diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplicationTests.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/CmCodingChallengeApplicationTests.java similarity index 75% rename from src/test/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplicationTests.java rename to src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/CmCodingChallengeApplicationTests.java index 0ca0d16..33c82eb 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/CmCodingChallengeApplicationTests.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/CmCodingChallengeApplicationTests.java @@ -1,4 +1,4 @@ -package com.crewmeister.cmcodingchallenge; +package com.crewmeister.cmcodingchallenge.currency.integration; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java new file mode 100644 index 0000000..d90f2ed --- /dev/null +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java @@ -0,0 +1,80 @@ +package com.crewmeister.cmcodingchallenge.currency.integration; + +import com.crewmeister.cmcodingchallenge.currency.domain.external.*; +import com.crewmeister.cmcodingchallenge.currency.domain.internal.CurrencyConversionRates; +import com.crewmeister.cmcodingchallenge.currency.domain.internal.CurrencyResponse; +import com.crewmeister.cmcodingchallenge.currency.util.RestUtil; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; + +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class TestCurrencyController { + + @Value(value = "${local.server.port}") + private int port; + + @Autowired + private TestRestTemplate testRestTemplate; + + @MockBean + private RestUtil restUtil; + + @Test + public void testGetCurrenciesSuccess() { + var response = this.testRestTemplate.getForEntity( + "http://localhost:" + port + "/api/currencies", + CurrencyResponse.class); + + assertEquals(response.getStatusCode(), HttpStatus.OK); + assertEquals(42, Objects.requireNonNull(response.getBody()).getCurrencies().length); + } + + @Test + public void testGetExchangeRateForDateSuccess() { + String targetUrl = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.CHF.EUR.BB.AC.000?detail=dataonly&startPeriod=2023-01-29&endPeriod=2023-01-29"; + + Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse()); + var response = this.testRestTemplate.getForEntity( + "http://localhost:" + port + "/api/exchange-rates?date=2023-01-29¤cyCode=CHF", CurrencyConversionRates.class); + + assertEquals(response.getStatusCode(), HttpStatus.OK); + assertEquals(1, Objects.requireNonNull(response.getBody()).getConversionRateList().size()); + assertEquals("1.4517", response.getBody().getConversionRateList().get(0).getConversionRate()); + } + + @Test + public void testGetExchangeRateForDateBadRequest() { + String targetUrl = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.AED.EUR.BB.AC.000?detail=dataonly&startPeriod=2023-01-29&endPeriod=2023-01-29"; + + Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse()); + var response = this.testRestTemplate.getForEntity( + "http://localhost:" + port + "/api/exchange-rates?date=2023-01-29¤cyCode=INR", CurrencyConversionRates.class); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(1, Objects.requireNonNull(response.getBody()).getConversionRateList().size()); + assertEquals("1.4517", response.getBody().getConversionRateList().get(0).getConversionRate()); + } + + private GenericData createDummyResponse() { + var obsList = List.of(new Obs(new ObsDimension("2023-01-24"), new ObsValue("1.4517"))); + var series = Series.builder() + .seriesKey(SeriesKey.builder().valueList(List.of(new com.crewmeister.cmcodingchallenge.currency.domain.external.Value("BBK_STD_CURRENCY", "CHF"))).build()) + .obsList(obsList).build(); + var genericData = + GenericData.builder() + .dataSet(DataSet.builder().series(series).build()) + .build(); + return genericData; + } +} diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/TestRestUtil.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestRestUtil.java similarity index 81% rename from src/test/java/com/crewmeister/cmcodingchallenge/TestRestUtil.java rename to src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestRestUtil.java index 97b975a..d96f64a 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/TestRestUtil.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestRestUtil.java @@ -1,7 +1,7 @@ -package com.crewmeister.cmcodingchallenge; +package com.crewmeister.cmcodingchallenge.currency.unit; -import com.crewmeister.cmcodingchallenge.schema.external.GenericData; -import com.crewmeister.cmcodingchallenge.util.RestUtil; +import com.crewmeister.cmcodingchallenge.currency.domain.external.GenericData; +import com.crewmeister.cmcodingchallenge.currency.util.RestUtil; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.client.RestTemplate; diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/TestXMLUtil.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java similarity index 74% rename from src/test/java/com/crewmeister/cmcodingchallenge/TestXMLUtil.java rename to src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java index ca6df00..b6b1abf 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/TestXMLUtil.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java @@ -1,7 +1,7 @@ -package com.crewmeister.cmcodingchallenge; +package com.crewmeister.cmcodingchallenge.currency.unit; -import com.crewmeister.cmcodingchallenge.util.XMLUtil; -import com.crewmeister.cmcodingchallenge.schema.external.GenericData; +import com.crewmeister.cmcodingchallenge.currency.util.XMLUtil; +import com.crewmeister.cmcodingchallenge.currency.domain.external.GenericData; import org.junit.jupiter.api.Test; import java.io.File; From 706b2e38d1d446caa1f38875f74cd0c9378ddbf8 Mon Sep 17 00:00:00 2001 From: M Riyaz Saiyed Date: Wed, 1 Feb 2023 18:44:42 +0100 Subject: [PATCH 08/13] test cases --- .../controller/CurrencyController.java | 2 +- .../domain/internal/ConversionRate.java | 2 +- .../currency/service/FXRateService.java | 2 +- .../integration/TestCurrencyController.java | 44 ++++++++++++++----- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/controller/CurrencyController.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/controller/CurrencyController.java index 0b3e3a2..a225599 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/controller/CurrencyController.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/controller/CurrencyController.java @@ -43,7 +43,7 @@ public ResponseEntity getExchangeRates( } @GetMapping("/exchange-currency") - public ResponseEntity getExchangedAmount( + public ResponseEntity getCurrencyExchange( @RequestParam() String currencyCode, @RequestParam() String amount, @RequestParam(required = false) String date diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/ConversionRate.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/ConversionRate.java index fb2b441..0ed3c97 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/ConversionRate.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/ConversionRate.java @@ -9,7 +9,7 @@ @Setter @AllArgsConstructor public class ConversionRate { - private String conversionRate; + private String rate; private String date; private Currency currency; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java index 50e26a1..0ab7471 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java @@ -58,7 +58,7 @@ public ExchangedAmount getFXValue(String currencyCode, String amount, String dat var exchangedAmount = new ExchangedAmount(); if (currencyConversionRate.isPresent()) { try { - double conversionRate = Double.parseDouble(currencyConversionRate.get().getConversionRate()); + double conversionRate = Double.parseDouble(currencyConversionRate.get().getRate()); exchangedAmount.setConversionRate(currencyConversionRate.get()); exchangedAmount.setAmount(String.valueOf(conversionRate * Double.parseDouble(amount))); } catch (NumberFormatException nfe) { diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java index d90f2ed..8c1ca43 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java @@ -3,6 +3,7 @@ import com.crewmeister.cmcodingchallenge.currency.domain.external.*; import com.crewmeister.cmcodingchallenge.currency.domain.internal.CurrencyConversionRates; import com.crewmeister.cmcodingchallenge.currency.domain.internal.CurrencyResponse; +import com.crewmeister.cmcodingchallenge.currency.domain.internal.ExchangedAmount; import com.crewmeister.cmcodingchallenge.currency.util.RestUtil; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -17,6 +18,7 @@ import java.util.Objects; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class TestCurrencyController { @@ -44,30 +46,50 @@ public void testGetCurrenciesSuccess() { public void testGetExchangeRateForDateSuccess() { String targetUrl = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.CHF.EUR.BB.AC.000?detail=dataonly&startPeriod=2023-01-29&endPeriod=2023-01-29"; - Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse()); + Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse("2023-01-24","1.4517")); var response = this.testRestTemplate.getForEntity( "http://localhost:" + port + "/api/exchange-rates?date=2023-01-29¤cyCode=CHF", CurrencyConversionRates.class); assertEquals(response.getStatusCode(), HttpStatus.OK); assertEquals(1, Objects.requireNonNull(response.getBody()).getConversionRateList().size()); - assertEquals("1.4517", response.getBody().getConversionRateList().get(0).getConversionRate()); + assertEquals("1.4517", response.getBody().getConversionRateList().get(0).getRate()); } @Test - public void testGetExchangeRateForDateBadRequest() { - String targetUrl = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.AED.EUR.BB.AC.000?detail=dataonly&startPeriod=2023-01-29&endPeriod=2023-01-29"; + public void testGetCurrencyExchangeSuccess() {//"2023-01-24" //"1.4517" + String targetUrl = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.CHF.EUR.BB.AC.000?detail=dataonly&startPeriod=2023-01-29&endPeriod=2023-01-29"; - Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse()); + Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse("2023-01-24","1.4517")); var response = this.testRestTemplate.getForEntity( - "http://localhost:" + port + "/api/exchange-rates?date=2023-01-29¤cyCode=INR", CurrencyConversionRates.class); + "http://localhost:" + port + "/api/exchange-currency?date=2023-01-29¤cyCode=CHF&amount=2000", ExchangedAmount.class); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(1, Objects.requireNonNull(response.getBody()).getConversionRateList().size()); - assertEquals("1.4517", response.getBody().getConversionRateList().get(0).getConversionRate()); + assertEquals(response.getStatusCode(), HttpStatus.OK); + assertNotNull(Objects.requireNonNull(response.getBody()).getAmount()); + assertEquals("1.4517", Objects.requireNonNull(response.getBody()).getConversionRate().getRate()); + assertEquals("2903.4", Objects.requireNonNull(response.getBody()).getAmount()); + } + + @Test + public void testThrowRateNotAvailableException() { + String targetUrl = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.CHF.EUR.BB.AC.000?detail=dataonly&startPeriod=2023-01-28&endPeriod=2023-01-28"; + + Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse("2023-01-28",null)); + var response = this.testRestTemplate.getForEntity( + "http://localhost:" + port + "/api/exchange-currency?date=2023-01-28¤cyCode=CHF&amount=2000", ExchangedAmount.class); + + assertEquals(response.getStatusCode(), HttpStatus.BAD_REQUEST); + } + @Test + public void testGetExchangeRateForNotSupportedCurrency() { + var response = this.testRestTemplate.getForEntity( + "http://localhost:" + port + "/api/exchange-rates?date=2023-01-29¤cyCode=AED", CurrencyConversionRates.class); + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } - private GenericData createDummyResponse() { - var obsList = List.of(new Obs(new ObsDimension("2023-01-24"), new ObsValue("1.4517"))); + private GenericData createDummyResponse(String date, String value) { + var obsDimension = new ObsDimension(date); + var obsValue = value == null ? null : new ObsValue(value); + var obsList = List.of(new Obs(obsDimension, obsValue)); var series = Series.builder() .seriesKey(SeriesKey.builder().valueList(List.of(new com.crewmeister.cmcodingchallenge.currency.domain.external.Value("BBK_STD_CURRENCY", "CHF"))).build()) .obsList(obsList).build(); From 26f77deafabb1f69e276bb151e6fa9a5d74cd5d9 Mon Sep 17 00:00:00 2001 From: M Riyaz Saiyed Date: Wed, 1 Feb 2023 20:55:21 +0100 Subject: [PATCH 09/13] time out and retry strategies --- .mvn/wrapper/MavenWrapperDownloader.java | 11 +- README.md | 59 +++++---- pom.xml | 122 ++++++++++-------- .../CmCodingChallengeApplication.java | 6 +- .../config/RestTemplateConfiguration.java | 14 +- .../controller/CurrencyController.java | 6 +- .../currency/domain/external/DataSet.java | 2 +- .../currency/domain/external/GenericData.java | 7 +- .../currency/domain/external/Obs.java | 9 +- .../currency/domain/external/Series.java | 4 +- .../internal/CurrencyConversionRates.java | 2 +- .../domain/internal/CurrencyResponse.java | 1 + .../CurrencyNotSupportedException.java | 3 - .../currency/service/FXRateService.java | 19 ++- .../currency/util/XMLUtil.java | 3 +- src/main/resources/application.yaml | 8 +- .../CmCodingChallengeApplicationTests.java | 6 +- .../integration/TestCurrencyController.java | 7 +- .../currency/unit/TestRestUtil.java | 1 - .../currency/unit/TestXMLUtil.java | 2 +- src/test/resources/test-data.xml | 9 +- 21 files changed, 173 insertions(+), 128 deletions(-) diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index e76d1f3..a45eb6b 100644 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import java.net.*; import java.io.*; import java.nio.channels.*; @@ -25,7 +26,7 @@ public class MavenWrapperDownloader { * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. */ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; /** * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to @@ -54,7 +55,7 @@ public static void main(String args[]) { // wrapperUrl parameter. File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { + if (mavenWrapperPropertyFile.exists()) { FileInputStream mavenWrapperPropertyFileInputStream = null; try { mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); @@ -65,7 +66,7 @@ public static void main(String args[]) { System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); } finally { try { - if(mavenWrapperPropertyFileInputStream != null) { + if (mavenWrapperPropertyFileInputStream != null) { mavenWrapperPropertyFileInputStream.close(); } } catch (IOException e) { @@ -76,8 +77,8 @@ public static void main(String args[]) { System.out.println("- Downloading from: " + url); File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { System.out.println( "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); } diff --git a/README.md b/README.md index 6529558..ec09a19 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,73 @@ # Crewmeister Test Assignment - Java Backend Developer ## Intro + Thank you for taking the time to complete this challenge as part of your application at Crewmeister! -We are taking development skills very serious and invest a lot of time to find the right candidate. +We are taking development skills very serious and invest a lot of time to find the right candidate. -At Crewmeister we aim to write excellent software and are convinced that this requires a high level of passion for and +At Crewmeister we aim to write excellent software and are convinced that this requires a high level of passion for and attention to topics such as software design and principles, best practices and clean code. We take pride in the fact -that the code we produce is extensible, testable, maintainable and runs fast. +that the code we produce is extensible, testable, maintainable and runs fast. At the same time, we always try to improve the effectiveness of our evaluation and improve the candidate journey throughout the process. Our aim is that our hiring process is mutually inspiring and feels like a gain for -both parties regardless of the outcome. If you feel to give us feedback on that, please don't hesitate to do so. +both parties regardless of the outcome. If you feel to give us feedback on that, please don't hesitate to do so. ## The Challenge -Your task is to create a foreign exchange rate service as SpringBoot-based microservice. +Your task is to create a foreign exchange rate service as SpringBoot-based microservice. The exchange rates can be received from [2]. This is a public service provided by the German central bank. As we are using user story format to specify our requirements, here are the user stories to implement: /api -- As a client, I want to get a list of all available currencies - - /currencies - - getAllCurrencies() + +- As a client, I want to get a list of all available currencies + - /currencies + - getAllCurrencies() - As a client, I want to get all EUR-FX exchange rates at all available dates as a collection - - /exchangerates?date=all - - getExchangeRates() -- As a client, I want to get the EUR-FX exchange rate at particular day - - /exchangerates?date='12-JAN-23' - - getExchangeRates(DATE perticularDat) + - /exchangerates?date=all + - getExchangeRates() +- As a client, I want to get the EUR-FX exchange rate at particular day + - /exchangerates?date='12-JAN-23' + - getExchangeRates(DATE perticularDat) - As a client, I want to get a foreign exchange amount for a given currency converted to EUR on a particular day - - /exchangerates?date='12-JAN-23'¤cy='USD' - - getExchangeRates(Date perticularDate, Currency currencyCode) + - /exchangerates?date='12-JAN-23'¤cy='USD' + - getExchangeRates(Date perticularDate, Currency currencyCode) -If you think that your service would require storage, please use H2 for simplicity, even if this would not be your choice if -you would implement an endpoint for real clients. +If you think that your service would require storage, please use H2 for simplicity, even if this would not be your +choice if +you would implement an endpoint for real clients. We are looking out for the following aspects in your submission: -- Well structured and thought-through api and endpoint design + +- Well structured and thought-through api and endpoint design - Clean code - Application of best practices & design patterns - That being said it is not enough to "just make it work", show your full potential to write excellent software - for Crewmeister ! - +for Crewmeister ! + ## Setup + #### Requirements + - Java 11 (will run with OpenSDK 15 as well) - Maven 3.x #### Project + The project was generated through the Spring initializer [1] for Java - 11 with dev tools and Spring Web as dependencies. In order to build and - run it, you just need to click the green arrow in the Application class in your Intellij - CE IDE or run the following command from your project root und Linux or ios. +11 with dev tools and Spring Web as dependencies. In order to build and +run it, you just need to click the green arrow in the Application class in your Intellij +CE IDE or run the following command from your project root und Linux or ios. ````shell script $ mvn spring-boot:run ```` -After running, the project, switch to your browser and hit http://localhost:8080/api/currencies. You should see some -demo output. - +After running, the project, switch to your browser and hit http://localhost:8080/api/currencies. You should see some +demo output. [1] https://start.spring.io/ diff --git a/pom.xml b/pom.xml index 97f886f..7bdf8a5 100644 --- a/pom.xml +++ b/pom.xml @@ -1,64 +1,74 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.4.1 - - - com.crewmeister - cm-coding-challenge - 0.0.1-SNAPSHOT - cm-coding-challenge - The java coding challenge for developer candidates applying at Crewmeister + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.1 + + + com.crewmeister + cm-coding-challenge + 0.0.1-SNAPSHOT + cm-coding-challenge + The java coding challenge for developer candidates applying at Crewmeister - - 11 - + + 11 + 1.18.24 + 2.11.1 + - - - org.springframework.boot - spring-boot-starter-web - - - org.projectlombok - lombok - 1.18.24 - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.11.1 - - - com.fasterxml.jackson.core - jackson-databind - 2.11.1 - + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.retry + spring-retry + + + org.springframework + spring-aspects + + + org.projectlombok + lombok + ${lombok.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jacson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jacson.version} + - - org.springframework.boot - spring-boot-devtools - runtime - true - - - org.springframework.boot - spring-boot-starter-test - test - - + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-test + test + + - - - - org.springframework.boot - spring-boot-maven-plugin - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + + diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CmCodingChallengeApplication.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CmCodingChallengeApplication.java index 2afa395..4f09ffc 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/CmCodingChallengeApplication.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/CmCodingChallengeApplication.java @@ -5,7 +5,7 @@ @SpringBootApplication public class CmCodingChallengeApplication { - public static void main(String[] args) { - SpringApplication.run(CmCodingChallengeApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(CmCodingChallengeApplication.class, args); + } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/config/RestTemplateConfiguration.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/config/RestTemplateConfiguration.java index ae32a51..8198d23 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/config/RestTemplateConfiguration.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/config/RestTemplateConfiguration.java @@ -1,15 +1,27 @@ package com.crewmeister.cmcodingchallenge.currency.config; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.retry.annotation.EnableRetry; import org.springframework.web.client.RestTemplate; +import java.time.Duration; + @Configuration +@EnableRetry public class RestTemplateConfiguration { + @Value("${external.fx.timeout}") + private int timeout; + @Bean public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { - return restTemplateBuilder.defaultMessageConverters().build(); + return restTemplateBuilder + .defaultMessageConverters() + .setConnectTimeout(Duration.ofMillis(timeout)) + .setReadTimeout(Duration.ofMillis(timeout)) + .build(); } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/controller/CurrencyController.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/controller/CurrencyController.java index a225599..0ab742d 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/controller/CurrencyController.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/controller/CurrencyController.java @@ -1,11 +1,11 @@ package com.crewmeister.cmcodingchallenge.currency.controller; import com.crewmeister.cmcodingchallenge.currency.constants.Currency; -import com.crewmeister.cmcodingchallenge.currency.exception.CurrencyNotSupportedException; -import com.crewmeister.cmcodingchallenge.currency.exception.RateNotAvailableException; import com.crewmeister.cmcodingchallenge.currency.domain.internal.CurrencyConversionRates; import com.crewmeister.cmcodingchallenge.currency.domain.internal.CurrencyResponse; import com.crewmeister.cmcodingchallenge.currency.domain.internal.ExchangedAmount; +import com.crewmeister.cmcodingchallenge.currency.exception.CurrencyNotSupportedException; +import com.crewmeister.cmcodingchallenge.currency.exception.RateNotAvailableException; import com.crewmeister.cmcodingchallenge.currency.service.FXRateService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -65,6 +65,6 @@ private void handleCurrencyNotSupportedException() { } private void handleRateNotAvailableException() { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Rate Not Available"); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Rate Not Available for the specified date"); } } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java index f678f4d..7f29dac 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java @@ -17,6 +17,6 @@ public class DataSet { String action; String validFromDate; - @JacksonXmlProperty(localName ="Series") + @JacksonXmlProperty(localName = "Series") Series series; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/GenericData.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/GenericData.java index dde3a0f..58ab7ad 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/GenericData.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/GenericData.java @@ -1,12 +1,9 @@ package com.crewmeister.cmcodingchallenge.currency.domain.external; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import lombok.NoArgsConstructor; import lombok.extern.jackson.Jacksonized; @Data @@ -16,9 +13,9 @@ public class GenericData { String schemaLocation; - @JacksonXmlProperty(localName ="Header") + @JacksonXmlProperty(localName = "Header") Header header; - @JacksonXmlProperty(localName ="DataSet") + @JacksonXmlProperty(localName = "DataSet") DataSet dataSet; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Obs.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Obs.java index 61e87a6..d74ac18 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Obs.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Obs.java @@ -2,7 +2,10 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @JsonIgnoreProperties(ignoreUnknown = true) @Getter @@ -10,9 +13,9 @@ @AllArgsConstructor @NoArgsConstructor public class Obs { - @JacksonXmlProperty(localName ="ObsDimension") + @JacksonXmlProperty(localName = "ObsDimension") ObsDimension obsDimension; - @JacksonXmlProperty(localName ="ObsValue") + @JacksonXmlProperty(localName = "ObsValue") ObsValue obsValue; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Series.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Series.java index 127c71f..c83b4a4 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Series.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Series.java @@ -16,10 +16,10 @@ @Builder @Jacksonized public class Series { - @JacksonXmlProperty(localName ="SeriesKey") + @JacksonXmlProperty(localName = "SeriesKey") SeriesKey seriesKey; @JacksonXmlElementWrapper(useWrapping = false) - @JacksonXmlProperty(localName ="Obs") + @JacksonXmlProperty(localName = "Obs") List obsList; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyConversionRates.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyConversionRates.java index e39b648..614a22a 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyConversionRates.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyConversionRates.java @@ -12,5 +12,5 @@ @NoArgsConstructor @AllArgsConstructor public class CurrencyConversionRates { - private List conversionRateList; + private List conversionRateList; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyResponse.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyResponse.java index 9d6b59b..fe05d0e 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyResponse.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/internal/CurrencyResponse.java @@ -11,5 +11,6 @@ public class CurrencyResponse { public CurrencyResponse() { } + private Currency[] currencies; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/exception/CurrencyNotSupportedException.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/exception/CurrencyNotSupportedException.java index fc401e4..a983c43 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/exception/CurrencyNotSupportedException.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/exception/CurrencyNotSupportedException.java @@ -2,9 +2,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.Setter; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; @Getter @AllArgsConstructor diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java index 0ab7471..47a1cd6 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java @@ -1,21 +1,24 @@ package com.crewmeister.cmcodingchallenge.currency.service; import com.crewmeister.cmcodingchallenge.currency.constants.Currency; -import com.crewmeister.cmcodingchallenge.currency.exception.CurrencyNotSupportedException; -import com.crewmeister.cmcodingchallenge.currency.exception.RateNotAvailableException; +import com.crewmeister.cmcodingchallenge.currency.domain.SchemaTransformer; import com.crewmeister.cmcodingchallenge.currency.domain.external.GenericData; import com.crewmeister.cmcodingchallenge.currency.domain.internal.ConversionRate; import com.crewmeister.cmcodingchallenge.currency.domain.internal.CurrencyConversionRates; import com.crewmeister.cmcodingchallenge.currency.domain.internal.ExchangedAmount; +import com.crewmeister.cmcodingchallenge.currency.exception.CurrencyNotSupportedException; +import com.crewmeister.cmcodingchallenge.currency.exception.RateNotAvailableException; import com.crewmeister.cmcodingchallenge.currency.util.RestUtil; -import com.crewmeister.cmcodingchallenge.currency.domain.SchemaTransformer; import lombok.extern.slf4j.Slf4j; import lombok.val; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.web.util.UriComponentsBuilder; +import java.net.SocketTimeoutException; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Arrays; @@ -38,6 +41,8 @@ public class FXRateService { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd"); + @Retryable(value = SocketTimeoutException.class, maxAttemptsExpression = "${external.retry.maxAttempts}", + backoff = @Backoff(delayExpression = "${external.retry.maxDelay}")) public CurrencyConversionRates getFXRates(String currencyCode, String date) throws CurrencyNotSupportedException { validateSupportedCurrency(currencyCode); UriComponentsBuilder uriComponents = UriComponentsBuilder.newInstance() @@ -53,8 +58,12 @@ public CurrencyConversionRates getFXRates(String currencyCode, String date) thro return SchemaTransformer.transform(resp); } - public ExchangedAmount getFXValue(String currencyCode, String amount, String date) throws CurrencyNotSupportedException, RateNotAvailableException { - Optional currencyConversionRate = getFXRates(currencyCode, date == null ? getCurrentDate() : date).getConversionRateList().stream().findFirst(); + @Retryable(value = SocketTimeoutException.class, maxAttemptsExpression = "${external.retry.maxAttempts}", + backoff = @Backoff(delayExpression = "${external.retry.maxDelay}")) + public ExchangedAmount getFXValue(String currencyCode, String amount, String date) + throws CurrencyNotSupportedException, RateNotAvailableException { + Optional currencyConversionRate = + getFXRates(currencyCode, date == null ? getCurrentDate() : date).getConversionRateList().stream().findFirst(); var exchangedAmount = new ExchangedAmount(); if (currencyConversionRate.isPresent()) { try { diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/util/XMLUtil.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/util/XMLUtil.java index d1fa3c1..fa5d574 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/util/XMLUtil.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/util/XMLUtil.java @@ -6,7 +6,8 @@ public class XMLUtil { private static final XmlMapper xmlMapper = new XmlMapper(); - public static T parseData(String xmlData, Class typeC) throws IOException { + + public static T parseData(String xmlData, Class typeC) throws IOException { return xmlMapper.readValue(xmlData, typeC); } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 6478d8a..44207ea 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -2,9 +2,13 @@ server: application: name: FX Rate API error: - include-stacktrace: ALWAYS + include-stacktrace: NEVER external: fx: scheme: https - host: api.statistiken.bundesbank.de/rest/data \ No newline at end of file + host: api.statistiken.bundesbank.de/rest/data + timeout: 500 + retry: + maxAttempts: 2 + maxDelay: 2000 \ No newline at end of file diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/CmCodingChallengeApplicationTests.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/CmCodingChallengeApplicationTests.java index 33c82eb..4fa51ff 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/CmCodingChallengeApplicationTests.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/CmCodingChallengeApplicationTests.java @@ -6,8 +6,8 @@ @SpringBootTest class CmCodingChallengeApplicationTests { - @Test - void contextLoads() { - } + @Test + void contextLoads() { + } } diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java index 8c1ca43..636bef6 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java @@ -46,7 +46,7 @@ public void testGetCurrenciesSuccess() { public void testGetExchangeRateForDateSuccess() { String targetUrl = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.CHF.EUR.BB.AC.000?detail=dataonly&startPeriod=2023-01-29&endPeriod=2023-01-29"; - Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse("2023-01-24","1.4517")); + Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse("2023-01-24", "1.4517")); var response = this.testRestTemplate.getForEntity( "http://localhost:" + port + "/api/exchange-rates?date=2023-01-29¤cyCode=CHF", CurrencyConversionRates.class); @@ -59,7 +59,7 @@ public void testGetExchangeRateForDateSuccess() { public void testGetCurrencyExchangeSuccess() {//"2023-01-24" //"1.4517" String targetUrl = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.CHF.EUR.BB.AC.000?detail=dataonly&startPeriod=2023-01-29&endPeriod=2023-01-29"; - Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse("2023-01-24","1.4517")); + Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse("2023-01-24", "1.4517")); var response = this.testRestTemplate.getForEntity( "http://localhost:" + port + "/api/exchange-currency?date=2023-01-29¤cyCode=CHF&amount=2000", ExchangedAmount.class); @@ -73,12 +73,13 @@ public void testGetCurrencyExchangeSuccess() {//"2023-01-24" //"1.4517" public void testThrowRateNotAvailableException() { String targetUrl = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.CHF.EUR.BB.AC.000?detail=dataonly&startPeriod=2023-01-28&endPeriod=2023-01-28"; - Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse("2023-01-28",null)); + Mockito.when(restUtil.getForObject(targetUrl, GenericData.class)).thenReturn(createDummyResponse("2023-01-28", null)); var response = this.testRestTemplate.getForEntity( "http://localhost:" + port + "/api/exchange-currency?date=2023-01-28¤cyCode=CHF&amount=2000", ExchangedAmount.class); assertEquals(response.getStatusCode(), HttpStatus.BAD_REQUEST); } + @Test public void testGetExchangeRateForNotSupportedCurrency() { var response = this.testRestTemplate.getForEntity( diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestRestUtil.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestRestUtil.java index d96f64a..5847fdb 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestRestUtil.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestRestUtil.java @@ -15,7 +15,6 @@ public class TestRestUtil { private RestUtil restUtil; - String url1 = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.PLN.EUR.BB.AC.000?detail=dataonly&lastNObservations=10"; //@Test diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java index b6b1abf..7aadee7 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java @@ -1,7 +1,7 @@ package com.crewmeister.cmcodingchallenge.currency.unit; -import com.crewmeister.cmcodingchallenge.currency.util.XMLUtil; import com.crewmeister.cmcodingchallenge.currency.domain.external.GenericData; +import com.crewmeister.cmcodingchallenge.currency.util.XMLUtil; import org.junit.jupiter.api.Test; import java.io.File; diff --git a/src/test/resources/test-data.xml b/src/test/resources/test-data.xml index 63dec0a..ad5e8ca 100644 --- a/src/test/resources/test-data.xml +++ b/src/test/resources/test-data.xml @@ -1,5 +1,9 @@ - + BBEX3_D_PLN_EUR_BB_AC_000 false @@ -16,7 +20,8 @@ - + From 17e42dfccdd49bb037f2bf4027efc3ee8fc964ca Mon Sep 17 00:00:00 2001 From: M Riyaz Saiyed Date: Wed, 1 Feb 2023 21:14:00 +0100 Subject: [PATCH 10/13] time out and retry strategies --- .../currency/domain/external/DataSet.java | 8 ++--- .../currency/domain/external/GenericData.java | 6 ++-- .../currency/domain/external/Header.java | 2 +- .../currency/domain/external/Obs.java | 4 +-- .../domain/external/ObsDimension.java | 2 +- .../currency/domain/external/Series.java | 4 +-- .../currency/domain/external/SeriesKey.java | 2 +- .../currency/domain/external/Value.java | 4 +-- .../integration/TestCurrencyController.java | 8 ++--- .../currency/unit/TestRestUtil.java | 32 ------------------- .../currency/unit/TestXMLUtil.java | 10 ++++++ 11 files changed, 29 insertions(+), 53 deletions(-) delete mode 100644 src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestRestUtil.java diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java index 7f29dac..5c2ec7b 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java @@ -12,10 +12,10 @@ @Builder @Jacksonized public class DataSet { - String structureRef; - String setID; - String action; - String validFromDate; + private String structureRef; + private String setID; + private String action; + private String validFromDate; @JacksonXmlProperty(localName = "Series") Series series; diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/GenericData.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/GenericData.java index 58ab7ad..2de5fdd 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/GenericData.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/GenericData.java @@ -11,11 +11,11 @@ @JacksonXmlRootElement(localName = "GenericData") @Jacksonized public class GenericData { - String schemaLocation; + private String schemaLocation; @JacksonXmlProperty(localName = "Header") - Header header; + private Header header; @JacksonXmlProperty(localName = "DataSet") - DataSet dataSet; + private DataSet dataSet; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Header.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Header.java index ccb1344..edf65f0 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Header.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Header.java @@ -6,5 +6,5 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class Header { @JsonProperty("ID") - String id; + private String id; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Obs.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Obs.java index d74ac18..6c9cc1a 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Obs.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Obs.java @@ -14,8 +14,8 @@ @NoArgsConstructor public class Obs { @JacksonXmlProperty(localName = "ObsDimension") - ObsDimension obsDimension; + private ObsDimension obsDimension; @JacksonXmlProperty(localName = "ObsValue") - ObsValue obsValue; + private ObsValue obsValue; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/ObsDimension.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/ObsDimension.java index bff47bf..edcaf68 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/ObsDimension.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/ObsDimension.java @@ -10,5 +10,5 @@ @NoArgsConstructor @AllArgsConstructor public class ObsDimension { - String value; + private String value; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Series.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Series.java index c83b4a4..07ec852 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Series.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Series.java @@ -17,9 +17,9 @@ @Jacksonized public class Series { @JacksonXmlProperty(localName = "SeriesKey") - SeriesKey seriesKey; + private SeriesKey seriesKey; @JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "Obs") - List obsList; + private List obsList; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/SeriesKey.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/SeriesKey.java index 9f8ba79..3f3281a 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/SeriesKey.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/SeriesKey.java @@ -16,5 +16,5 @@ public class SeriesKey { @JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "Value") - List valueList; + private List valueList; } diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Value.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Value.java index 64aa2c4..0cd48db 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Value.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/Value.java @@ -12,7 +12,7 @@ @NoArgsConstructor public class Value { @JacksonXmlProperty(localName = "id") - String id; + private String id; @JacksonXmlProperty(localName = "value") - String value; + private String value; } diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java index 636bef6..e601f49 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/TestCurrencyController.java @@ -94,10 +94,8 @@ private GenericData createDummyResponse(String date, String value) { var series = Series.builder() .seriesKey(SeriesKey.builder().valueList(List.of(new com.crewmeister.cmcodingchallenge.currency.domain.external.Value("BBK_STD_CURRENCY", "CHF"))).build()) .obsList(obsList).build(); - var genericData = - GenericData.builder() - .dataSet(DataSet.builder().series(series).build()) - .build(); - return genericData; + return GenericData.builder() + .dataSet(DataSet.builder().series(series).build()) + .build(); } } diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestRestUtil.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestRestUtil.java deleted file mode 100644 index 5847fdb..0000000 --- a/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestRestUtil.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.crewmeister.cmcodingchallenge.currency.unit; - -import com.crewmeister.cmcodingchallenge.currency.domain.external.GenericData; -import com.crewmeister.cmcodingchallenge.currency.util.RestUtil; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.client.RestTemplate; - -import java.io.IOException; -import java.util.Currency; - -public class TestRestUtil { - - @Autowired - private RestUtil restUtil; - - - String url1 = "https://api.statistiken.bundesbank.de/rest/data/BBEX3/D.PLN.EUR.BB.AC.000?detail=dataonly&lastNObservations=10"; - - //@Test - /*void testRestServiceUtil() throws IOException { - GenericData value = restUtil.getForObject(url, GenericData.class); - System.out.println(value.getDataSet()); - }*/ - - @Test - void testRestService() throws IOException { - System.out.println(Currency.getAvailableCurrencies()); - GenericData value = new RestTemplate().getForObject(url1, GenericData.class); - System.out.println(value.getDataSet()); - } -} diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java index 7aadee7..34d0f5a 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java @@ -8,6 +8,9 @@ import java.io.IOException; import java.nio.file.Files; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class TestXMLUtil { @Test @@ -18,5 +21,12 @@ void testXmlToEntity() throws IOException { String content = Files.readString(file.toPath()); GenericData value = XMLUtil.parseData(content, GenericData.class); System.out.println(value.getDataSet()); + assertNotNull(value); + assertNotNull(value.getDataSet()); + assertNotNull(value.getDataSet().getSeries()); + assertNotNull(value.getDataSet().getSeries().getObsList()); + assertEquals(10, value.getDataSet().getSeries().getObsList().size()); + assertNotNull(value.getDataSet().getSeries().getObsList().get(0).getObsValue()); + assertNotNull(value.getDataSet().getSeries().getObsList().get(0).getObsValue().getValue()); } } From 8ca108c2147c220b7f102bb09bd0903c0b18b9c1 Mon Sep 17 00:00:00 2001 From: M Riyaz Saiyed Date: Wed, 1 Feb 2023 21:22:32 +0100 Subject: [PATCH 11/13] time out and retry strategies --- .../currency/domain/external/DataSet.java | 1 - .../integration/CmCodingChallengeApplicationTests.java | 8 +++++++- .../cmcodingchallenge/currency/unit/TestXMLUtil.java | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java index 5c2ec7b..0ef44b9 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/domain/external/DataSet.java @@ -6,7 +6,6 @@ import lombok.Setter; import lombok.extern.jackson.Jacksonized; -//@JsonIgnoreProperties(ignoreUnknown = true) @Getter @Setter @Builder diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/CmCodingChallengeApplicationTests.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/CmCodingChallengeApplicationTests.java index 4fa51ff..99d57b9 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/CmCodingChallengeApplicationTests.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/integration/CmCodingChallengeApplicationTests.java @@ -1,13 +1,19 @@ package com.crewmeister.cmcodingchallenge.currency.integration; +import com.crewmeister.cmcodingchallenge.currency.controller.CurrencyController; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; + +import static org.junit.jupiter.api.Assertions.assertNotNull; @SpringBootTest class CmCodingChallengeApplicationTests { @Test - void contextLoads() { + void contextLoads(ApplicationContext context) { + assertNotNull(context); + assertNotNull(context.getBean(CurrencyController.class)); } } diff --git a/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java index 34d0f5a..f7f5030 100644 --- a/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java +++ b/src/test/java/com/crewmeister/cmcodingchallenge/currency/unit/TestXMLUtil.java @@ -20,7 +20,6 @@ void testXmlToEntity() throws IOException { File file = new File(classLoader.getResource(resourceName).getFile()); String content = Files.readString(file.toPath()); GenericData value = XMLUtil.parseData(content, GenericData.class); - System.out.println(value.getDataSet()); assertNotNull(value); assertNotNull(value.getDataSet()); assertNotNull(value.getDataSet().getSeries()); From de50ee9518c0d0de28c60dbfa1f00de86b3bd0da Mon Sep 17 00:00:00 2001 From: M Riyaz Saiyed Date: Wed, 1 Feb 2023 21:28:24 +0100 Subject: [PATCH 12/13] README revert --- README.md | 47 +++++++++++++++++------------------------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index ec09a19..44aae28 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,60 @@ # Crewmeister Test Assignment - Java Backend Developer ## Intro - Thank you for taking the time to complete this challenge as part of your application at Crewmeister! -We are taking development skills very serious and invest a lot of time to find the right candidate. +We are taking development skills very serious and invest a lot of time to find the right candidate. -At Crewmeister we aim to write excellent software and are convinced that this requires a high level of passion for and +At Crewmeister we aim to write excellent software and are convinced that this requires a high level of passion for and attention to topics such as software design and principles, best practices and clean code. We take pride in the fact -that the code we produce is extensible, testable, maintainable and runs fast. +that the code we produce is extensible, testable, maintainable and runs fast. At the same time, we always try to improve the effectiveness of our evaluation and improve the candidate journey throughout the process. Our aim is that our hiring process is mutually inspiring and feels like a gain for -both parties regardless of the outcome. If you feel to give us feedback on that, please don't hesitate to do so. +both parties regardless of the outcome. If you feel to give us feedback on that, please don't hesitate to do so. ## The Challenge -Your task is to create a foreign exchange rate service as SpringBoot-based microservice. +Your task is to create a foreign exchange rate service as SpringBoot-based microservice. The exchange rates can be received from [2]. This is a public service provided by the German central bank. As we are using user story format to specify our requirements, here are the user stories to implement: -/api - As a client, I want to get a list of all available currencies - - /currencies - - getAllCurrencies() - As a client, I want to get all EUR-FX exchange rates at all available dates as a collection - - /exchangerates?date=all - - getExchangeRates() - As a client, I want to get the EUR-FX exchange rate at particular day - - /exchangerates?date='12-JAN-23' - - getExchangeRates(DATE perticularDat) - As a client, I want to get a foreign exchange amount for a given currency converted to EUR on a particular day - - /exchangerates?date='12-JAN-23'¤cy='USD' - - getExchangeRates(Date perticularDate, Currency currencyCode) -If you think that your service would require storage, please use H2 for simplicity, even if this would not be your -choice if -you would implement an endpoint for real clients. +If you think that your service would require storage, please use H2 for simplicity, even if this would not be your choice if +you would implement an endpoint for real clients. We are looking out for the following aspects in your submission: - -- Well structured and thought-through api and endpoint design +- Well structured and thought-through api and endpoint design - Clean code - Application of best practices & design patterns -That being said it is not enough to "just make it work", show your full potential to write excellent software -for Crewmeister ! +That being said it is not enough to "just make it work", show your full potential to write excellent software + for Crewmeister ! + ## Setup - #### Requirements - - Java 11 (will run with OpenSDK 15 as well) - Maven 3.x #### Project - The project was generated through the Spring initializer [1] for Java -11 with dev tools and Spring Web as dependencies. In order to build and -run it, you just need to click the green arrow in the Application class in your Intellij -CE IDE or run the following command from your project root und Linux or ios. + 11 with dev tools and Spring Web as dependencies. In order to build and + run it, you just need to click the green arrow in the Application class in your Intellij + CE IDE or run the following command from your project root und Linux or ios. ````shell script $ mvn spring-boot:run ```` -After running, the project, switch to your browser and hit http://localhost:8080/api/currencies. You should see some -demo output. +After running, the project, switch to your browser and hit http://localhost:8080/api/currencies. You should see some +demo output. + [1] https://start.spring.io/ From ce2e38beb0d9f4f2f2e0f7bae80299e27449ca71 Mon Sep 17 00:00:00 2001 From: M Riyaz Saiyed Date: Mon, 13 Feb 2023 12:44:22 +0100 Subject: [PATCH 13/13] retry update --- .../currency/service/FXRateService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java b/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java index 47a1cd6..2dd35a2 100644 --- a/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java +++ b/src/main/java/com/crewmeister/cmcodingchallenge/currency/service/FXRateService.java @@ -58,8 +58,6 @@ public CurrencyConversionRates getFXRates(String currencyCode, String date) thro return SchemaTransformer.transform(resp); } - @Retryable(value = SocketTimeoutException.class, maxAttemptsExpression = "${external.retry.maxAttempts}", - backoff = @Backoff(delayExpression = "${external.retry.maxDelay}")) public ExchangedAmount getFXValue(String currencyCode, String amount, String date) throws CurrencyNotSupportedException, RateNotAvailableException { Optional currencyConversionRate = @@ -76,7 +74,7 @@ public ExchangedAmount getFXValue(String currencyCode, String amount, String dat } return exchangedAmount; } - + // usd against euro -> 0.8 -> 0.8 * 100 -> 80 euro private void validateSupportedCurrency(String currencyCode) throws CurrencyNotSupportedException { val currencyStream = Arrays.stream(Currency.values()); if (Currency.getCurrency(currencyCode).isEmpty()) { @@ -88,3 +86,8 @@ private String getCurrentDate() { return DATE_TIME_FORMATTER.format(LocalDate.now()); } } + + +// user -> service -> 3rd service - 1st aattmep X + // -> 3rd service - 2nd attempt X + // <- rate not available responmse \ No newline at end of file