Skip to content

Commit

Permalink
Split Downloader into Downloader and Parser
Browse files Browse the repository at this point in the history
  • Loading branch information
donfiguerres committed Jun 4, 2023
1 parent 5d8ef27 commit bd9cb5f
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.europeanexchangerates.exchangeapi.exception;

public class NoDataFromSource extends RuntimeException {
public NoDataFromSource(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,50 @@
package com.europeanexchangerates.exchangeapi.provider;

import java.io.InputStream;
import java.time.LocalDate;
import java.util.TreeMap;
import java.util.zip.ZipInputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.europeanexchangerates.exchangeapi.dto.ExchangeRate;
import com.europeanexchangerates.exchangeapi.util.DataDownloader;
import com.europeanexchangerates.exchangeapi.util.UrlCsvZipDataDownloader;
import com.europeanexchangerates.exchangeapi.exception.NoDataFromSource;
import com.europeanexchangerates.exchangeapi.util.datadownloader.DataDownloader;
import com.europeanexchangerates.exchangeapi.util.datadownloader.UrlCsvZipDataDownloader;
import com.europeanexchangerates.exchangeapi.util.dataparser.CsvDataParser;
import com.europeanexchangerates.exchangeapi.util.dataparser.DataParser;

public class UrlCsvZipExchangeRateProvider implements ExchangeRateProvider {
private DataDownloader downloader;
private DataParser parser;
private static final Logger LOGGER = LoggerFactory.getLogger(UrlCsvZipExchangeRateProvider.class);

public UrlCsvZipExchangeRateProvider() {
this.downloader = new UrlCsvZipDataDownloader();
this.parser = new CsvDataParser();
}

public UrlCsvZipExchangeRateProvider(DataDownloader downloader) {
public UrlCsvZipExchangeRateProvider(DataDownloader downloader, DataParser parser) {
this.downloader = downloader;
this.parser = parser;
}

public TreeMap<LocalDate, ExchangeRate> getExchangeRates() throws Exception {
return downloader.downloadData();
InputStream data = downloader.downloadData("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip");

// Only one CSV file is expected from the ZIP file.
if (((ZipInputStream) data).getNextEntry() == null) {
throw new NoDataFromSource("No files found from the zip file");
}
TreeMap<LocalDate, ExchangeRate> exchangeRates = parser.parseData(data);

// If the zip file has changed, skip the next files but log a warning.
// Ideally, alerts should be sent if running in a production environment.
if (((ZipInputStream) data).getNextEntry() != null) {
LOGGER.warn("The contents of the zip archive has changed. Please check the data source.");
}

return exchangeRates;
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.europeanexchangerates.exchangeapi.util.datadownloader;

import java.io.InputStream;

public interface DataDownloader {
public InputStream downloadData(String url) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.europeanexchangerates.exchangeapi.util.datadownloader;

import java.io.InputStream;
import java.net.URL;
import java.util.zip.ZipInputStream;

public class UrlCsvZipDataDownloader implements DataDownloader {
public InputStream downloadData(String url) throws Exception {
ZipInputStream zipInputStream = new ZipInputStream(
(new URL(url)).openStream());
return zipInputStream;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.europeanexchangerates.exchangeapi.util.dataparser;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import com.europeanexchangerates.exchangeapi.dto.ExchangeRate;

public class CsvDataParser implements DataParser {
public TreeMap<LocalDate, ExchangeRate> parseData(InputStream inputStream) throws Exception {
TreeMap<LocalDate, ExchangeRate> exchangeRates = new TreeMap<>();
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(inputStream));

// Iterate over each entry in the zip file
// Skip header line
String[] headers = bufferedReader.readLine().split(",");
String line;
while ((line = bufferedReader.readLine()) != null) {
String[] data = line.split(",");
LocalDate date = LocalDate.parse(data[0]);

Map<String, BigDecimal> rates = new HashMap<>();
for (int i = 1; i < headers.length; i++) {
if (!data[i].equals("N/A")) {
rates.put(headers[i], new BigDecimal(data[i]));
}
}
exchangeRates.put(date, new ExchangeRate(rates));
}

return exchangeRates;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.europeanexchangerates.exchangeapi.util.dataparser;

import java.io.InputStream;
import java.time.LocalDate;
import java.util.TreeMap;

import com.europeanexchangerates.exchangeapi.dto.ExchangeRate;

public interface DataParser {
public TreeMap<LocalDate, ExchangeRate> parseData(InputStream inputStream) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.europeanexchangerates.exchangeapi.util;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import com.europeanexchangerates.exchangeapi.dto.ExchangeRate;
import com.europeanexchangerates.exchangeapi.util.dataparser.CsvDataParser;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Map;
import java.util.TreeMap;

import static org.junit.jupiter.api.Assertions.*;

class CsvZipDataParserTest {

@Mock
InputStream inputStream;

private CsvDataParser parser;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
parser = new CsvDataParser();
}

@Test
void parseData() throws Exception {
String testInput = "Date,USD,EUR\n" +
"2023-06-04,1.2100,0.8400\n" +
"2023-06-03,1.2200,0.8500\n";
InputStream inputStream = new ByteArrayInputStream(testInput.getBytes());

TreeMap<LocalDate, ExchangeRate> result = parser.parseData(inputStream);

assertEquals(new BigDecimal("1.2100"), result.get(LocalDate.of(2023, 6, 4)).getRates().get("USD"));
assertEquals(new BigDecimal("0.8400"), result.get(LocalDate.of(2023, 6, 4)).getRates().get("EUR"));
assertEquals(new BigDecimal("1.2200"), result.get(LocalDate.of(2023, 6, 3)).getRates().get("USD"));
assertEquals(new BigDecimal("0.8500"), result.get(LocalDate.of(2023, 6, 3)).getRates().get("EUR"));
}

@Test
void parseData_throwsExceptionForMalformedData() {
String testInput = "Date,USD,EUR\n" +
"2023-06-04,1.2100,\n" +
"2023-06-03,1.2200,0.8500\n";
InputStream inputStream = new ByteArrayInputStream(testInput.getBytes());

assertThrows(Exception.class, () -> parser.parseData(inputStream));
}
}

0 comments on commit bd9cb5f

Please sign in to comment.