konta-walutowe/src/main/java/eu/ztsh/wymiana/service/NbpService.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) {
}
}