diff --git a/crypto-currency/bitcoin/pom.xml b/crypto-currency/bitcoin/pom.xml index 7f36c48..c8c716d 100644 --- a/crypto-currency/bitcoin/pom.xml +++ b/crypto-currency/bitcoin/pom.xml @@ -166,6 +166,11 @@ pom compile + + org.apache.httpcomponents + httpclient + 4.5.14 + diff --git a/crypto-currency/bitcoin/src/main/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProvider.java b/crypto-currency/bitcoin/src/main/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProvider.java new file mode 100644 index 0000000..68e4aa7 --- /dev/null +++ b/crypto-currency/bitcoin/src/main/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProvider.java @@ -0,0 +1,93 @@ +package org.javamoney.shelter.bitcoin.provider; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.javamoney.moneta.convert.ExchangeRateBuilder; +import org.javamoney.moneta.spi.AbstractRateProvider; +import org.javamoney.moneta.spi.DefaultNumberValue; + +import javax.money.CurrencyUnit; +import javax.money.MonetaryException; +import javax.money.convert.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class CoinbaseRateProvider extends AbstractRateProvider { + private static final RateType RATE_TYPE = RateType.DEFERRED; + private static final ProviderContext CONTEXT = ProviderContextBuilder.of("CoinbaseRateProvider", RATE_TYPE) + .set("providerDescription", "Coinbase - Bitcoin exchange rate provider") + .build(); + private static final String DEFAULT_BASE_CURRENCY = "BTC"; + + private final List supportedCurrencies = new ArrayList<>(); + private final Map rates = new ConcurrentHashMap<>(); + + public CoinbaseRateProvider() { + super(CONTEXT); + loadSupportedCurrencies(); + } + + @Override + public ExchangeRate getExchangeRate(ConversionQuery conversionQuery) { + CurrencyUnit baseCurrency = conversionQuery.getBaseCurrency(); + CurrencyUnit termCurrency = conversionQuery.getCurrency(); + ConversionContext conversionContext = ConversionContext.of(getContext().getProviderName(), RATE_TYPE); + + if (!DEFAULT_BASE_CURRENCY.equals(baseCurrency.getCurrencyCode())) { + throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Base currency not supported: " + baseCurrency); + } + + if (!supportedCurrencies.contains(termCurrency.getCurrencyCode())) { + throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Term currency not supported: " + termCurrency); + } + + loadRates(); + + Number rate = rates.get(termCurrency.getCurrencyCode()); + if (rate == null) { + throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Rate not available for currency: " + termCurrency); + } + return new ExchangeRateBuilder(conversionContext) + .setBase(baseCurrency) + .setTerm(termCurrency) + .setFactor(DefaultNumberValue.of(rate)) + .build(); + } + + private void loadSupportedCurrencies() { + try (CloseableHttpClient httpClient = HttpClients.createDefault()){ + String url = "https://api.coinbase.com/v2/currencies"; + HttpGet request = new HttpGet(url); + try (CloseableHttpResponse response = httpClient.execute(request)){ + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode = mapper.readTree(response.getEntity().getContent()); + JsonNode dataNode = jsonNode.get("data"); + dataNode.forEach(node -> supportedCurrencies.add(node.get("id").asText())); + } + } catch (IOException e) { + throw new MonetaryException("Failed to load supported currencies from Coinbase API", e); + } + } + + private void loadRates() { + try (CloseableHttpClient httpClient = HttpClients.createDefault()){ + String url = "https://api.coinbase.com/v2/exchange-rates?currency=" + DEFAULT_BASE_CURRENCY; + HttpGet request = new HttpGet(url); + try (CloseableHttpResponse response = httpClient.execute(request)){ + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode = mapper.readTree(response.getEntity().getContent()); + JsonNode ratesNode = jsonNode.get("data").get("rates"); + ratesNode.fields().forEachRemaining(entry -> rates.put(entry.getKey(), entry.getValue().asDouble())); + } + } catch (IOException e) { + throw new MonetaryException("Failed to load exchange rates from Coinbase API", e); + } + } +} diff --git a/crypto-currency/bitcoin/src/test/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProviderTest.java b/crypto-currency/bitcoin/src/test/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProviderTest.java new file mode 100644 index 0000000..fb22ac6 --- /dev/null +++ b/crypto-currency/bitcoin/src/test/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProviderTest.java @@ -0,0 +1,43 @@ +package org.javamoney.shelter.bitcoin.provider; + +import org.javamoney.moneta.CurrencyUnitBuilder; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.money.convert.CurrencyConversionException; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; + +public class CoinbaseRateProviderTest { + private static CoinbaseRateProvider coinbaseRateProvider; + + @BeforeClass + public static void setUpBeforeClass() { + coinbaseRateProvider = new CoinbaseRateProvider(); + CurrencyUnitBuilder.of("BTC", "CoinbaseRateProvider") + .setDefaultFractionDigits(8) + .build(true); + } + + @AfterClass + public static void tearDownAfterClass() { + coinbaseRateProvider = null; + } + + @Test + public void testGetExchangeRate() { + assertNotNull(coinbaseRateProvider.getExchangeRate("BTC", "USD")); + } + + @Test + public void testGetExchangeRateWithNotSupportedBaseCurrency() { + assertThrows(CurrencyConversionException.class, () -> coinbaseRateProvider.getExchangeRate("USD", "BTC")); + } + + @Test + public void testGetExchangeRateWithNotSupportedTermCurrency() { + assertThrows(CurrencyConversionException.class, () -> coinbaseRateProvider.getExchangeRate("BTC", "ZWL")); + } +} \ No newline at end of file