91 lines
3 KiB
Java
91 lines
3 KiB
Java
package eu.ztsh.wymiana.service;
|
|
|
|
import eu.ztsh.wymiana.exception.NoDataException;
|
|
import eu.ztsh.wymiana.model.Rates;
|
|
import eu.ztsh.wymiana.model.Symbol;
|
|
import lombok.RequiredArgsConstructor;
|
|
import org.assertj.core.util.VisibleForTesting;
|
|
import org.springframework.http.HttpStatusCode;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.web.client.RestClient;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.time.Clock;
|
|
import java.time.DayOfWeek;
|
|
import java.time.LocalDate;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.temporal.TemporalAdjusters;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ConcurrentMap;
|
|
|
|
/**
|
|
* NBP exchange rates service
|
|
*/
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class NbpService {
|
|
|
|
private final Clock clock;
|
|
private final RestClient restClient;
|
|
private static final String URI_PATTERN = "/api/exchangerates/rates/c/{code}/{date}/";
|
|
private final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
|
private final ConcurrentMap<Symbol, RatesCache> cache = new ConcurrentHashMap<>(1);
|
|
|
|
public BigDecimal getSellRate(Symbol currency) {
|
|
return getCurrency(currency).sell();
|
|
}
|
|
|
|
public BigDecimal getBuyRate(Symbol currency) {
|
|
return getCurrency(currency).buy();
|
|
}
|
|
|
|
private synchronized RatesCache getCurrency(Symbol currency) {
|
|
var today = getFetchDate();
|
|
var cacheObject = cache.get(currency);
|
|
if (cacheObject == null || cacheObject.date().isBefore(today)) {
|
|
var fresh = fetchData(currency, dtf.format(today));
|
|
var rate = fresh.getRates().getFirst();
|
|
cacheObject = new RatesCache(
|
|
LocalDate.parse(rate.getEffectiveDate(), dtf),
|
|
rate.getBid(),
|
|
rate.getAsk()
|
|
);
|
|
cache.put(Symbol.valueOf(fresh.getCode().toUpperCase()), cacheObject);
|
|
}
|
|
return cacheObject;
|
|
}
|
|
|
|
/**
|
|
* Calculates date for data fetch.
|
|
* <p>
|
|
* Usually this would be today, but as rates are set only Mon-Fri, during weekends it is needed to fetch last friday rates.
|
|
*
|
|
* @return date for data fetch
|
|
*/
|
|
@VisibleForTesting
|
|
LocalDate getFetchDate() {
|
|
var today = LocalDate.now(clock);
|
|
return isWeekend(today) ? today.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY)) : today;
|
|
}
|
|
|
|
@VisibleForTesting
|
|
Rates fetchData(Symbol code, String date) {
|
|
return restClient.get().uri(URI_PATTERN, code.name().toLowerCase(), date)
|
|
.retrieve()
|
|
.onStatus(HttpStatusCode::is4xxClientError, ((request, response) -> {
|
|
throw new NoDataException(code, date);
|
|
}))
|
|
.body(Rates.class);
|
|
}
|
|
|
|
private static boolean isWeekend(LocalDate today) {
|
|
return today.getDayOfWeek() == DayOfWeek.SATURDAY
|
|
|| today.getDayOfWeek() == DayOfWeek.SUNDAY;
|
|
}
|
|
|
|
private record RatesCache(LocalDate date, BigDecimal buy, BigDecimal sell) {
|
|
|
|
}
|
|
|
|
}
|