Files
eru/src/com/venezuela/bcvrate/service/BCVApiService.java
T

305 lines
12 KiB
Java

package com.venezuela.bcvrate.service;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class BCVApiService {
private static final Logger log = Logger.getLogger(BCVApiService.class.getName());
private static final String BASE_URL = "https://bcv.today/api/v1";
private static final String BCV_WEBSITE = "https://www.bcv.org.ve/";
private static final int CONNECTION_TIMEOUT = 30000;
private static final int READ_TIMEOUT = 60000;
private static final int MAX_DAYS_BACK = 10;
public BCVRateResponse getRateForDateWithFallback(String requestedDate) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
log.info("Buscando tasa para fecha solicitada: " + requestedDate);
log.info("Intentando scraping BCV website...");
BCVRateResponse websiteRate = getRateFromBCVWebsite();
if (websiteRate != null && websiteRate.isValid()) {
String scrapedEffectiveDate = websiteRate.getDate();
if (!scrapedEffectiveDate.equals(requestedDate)) {
log.info("Scraping BCV: tasa del " + scrapedEffectiveDate
+ " aplica para " + requestedDate);
} else {
log.info("Scraping BCV: tasa del " + scrapedEffectiveDate
+ " coincide con fecha solicitada");
}
return new BCVRateResponse(websiteRate.getDollar(), requestedDate, scrapedEffectiveDate);
}
log.info("Scraping BCV no disponible, buscando en API hacia atras...");
Calendar cal = Calendar.getInstance();
try {
cal.setTime(sdf.parse(requestedDate));
} catch (Exception e) {
log.log(Level.WARNING, "Error parseando fecha: " + requestedDate, e);
return null;
}
for (int i = 0; i <= MAX_DAYS_BACK; i++) {
String dateToQuery = sdf.format(cal.getTime());
BCVRateResponse rate = getRateFromApi(dateToQuery);
if (rate != null && rate.isValid()) {
log.info("Tasa encontrada (API) consultando " + dateToQuery
+ " para fecha solicitada " + requestedDate
+ ": USD=" + rate.getDollar()
+ " effective=" + rate.getDate());
return new BCVRateResponse(rate.getDollar(), requestedDate, rate.getDate());
}
cal.add(Calendar.DAY_OF_MONTH, -1);
}
log.info("No se encontro tasa para: " + requestedDate);
return null;
}
public BCVRateResponse getRateForDate(String date) {
return getRateFromApi(date);
}
public BCVRateResponse getLatestRate() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 1; i <= 10; i++) {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, i);
String futureDate = sdf.format(cal.getTime());
BCVRateResponse rate = getRateFromApi(futureDate);
if (rate != null && rate.isValid()) {
log.info("Tasa encontrada (API futura) para " + futureDate
+ ": USD=" + rate.getDollar() + " effective=" + rate.getDate());
return rate;
}
}
log.info("API no tiene tasa futura, intentando scraping BCV...");
try {
BCVRateResponse websiteRate = getRateFromBCVWebsite();
if (websiteRate != null && websiteRate.isValid()) {
log.info("Tasa obtenida del sitio web BCV: USD=" + websiteRate.getDollar()
+ " effective=" + websiteRate.getDate());
return websiteRate;
}
log.info("Scraping BCV no devolvio tasa valida");
} catch (Exception e) {
log.log(Level.WARNING, "Scraping BCV fallo", e);
}
log.info("Fallback a API actual...");
BCVRateResponse apiRate = getCurrentRate();
return apiRate;
}
private BCVRateResponse getRateFromApi(String date) {
String urlStr = BASE_URL + "/history/" + date + ".json";
try {
String response = httpGet(urlStr);
if (response == null) return null;
BigDecimal dollar = extractUSD(response);
String effectiveDate = extractField(response, "effective_date");
if (effectiveDate == null) effectiveDate = extractField(response, "date");
if (dollar != null && effectiveDate != null) {
return new BCVRateResponse(dollar, effectiveDate);
}
return null;
} catch (Exception e) {
log.log(Level.WARNING, "Error consultando fecha: " + date, e);
return null;
}
}
public BCVRateResponse getCurrentRate() {
String urlStr = BASE_URL + "/rate.json";
try {
String response = httpGet(urlStr);
if (response == null) return null;
BigDecimal dollar = extractUSD(response);
String effectiveDate = extractField(response, "effective_date");
if (effectiveDate == null) effectiveDate = extractField(response, "date");
if (dollar != null && effectiveDate != null) {
log.info("Tasa actual (API): USD=" + dollar + " effective=" + effectiveDate);
return new BCVRateResponse(dollar, effectiveDate);
}
return null;
} catch (Exception e) {
log.log(Level.WARNING, "Error consultando tasa actual", e);
return null;
}
}
private BCVRateResponse getRateFromBCVWebsite() {
int maxRetries = 3;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
log.info("Scraping BCV - Intento " + attempt + "/" + maxRetries);
String html = httpGetHtml(BCV_WEBSITE);
if (html == null) {
log.warning("Intento " + attempt + "/" + maxRetries + ": No se pudo obtener pagina BCV");
if (attempt < maxRetries) {
log.info("Reintentando en 5 segundos...");
Thread.sleep(5000);
continue;
}
log.severe("Scraping BCV fallo despues de " + maxRetries + " intentos (timeout o error de conexion)");
return null;
}
log.info("Pagina BCV obtenida, longitud: " + html.length());
Matcher rateMatcher = Pattern.compile(
"USD</span>.*?strong-tb\">\\s*([\\d.,]+)\\s*</strong>", Pattern.DOTALL).matcher(html);
if (!rateMatcher.find()) {
log.warning("No se encontro tasa USD en pagina BCV (regex no match)");
return null;
}
String rateStr = rateMatcher.group(1).replace(",", ".");
log.info("Tasa encontrada en HTML: " + rateStr);
BigDecimal dollar = new BigDecimal(rateStr).setScale(4, RoundingMode.HALF_UP);
Matcher dateMatcher = Pattern.compile(
"content=\"(\\d{4}-\\d{2}-\\d{2})T").matcher(html);
String effectiveDate = null;
if (dateMatcher.find()) {
effectiveDate = dateMatcher.group(1);
}
if (dollar.compareTo(BigDecimal.ZERO) > 0) {
if (effectiveDate == null) {
effectiveDate = new SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date());
}
log.info("Scraping BCV exitoso (intento " + attempt + "): USD=" + dollar + " effective=" + effectiveDate);
return new BCVRateResponse(dollar, effectiveDate);
}
return null;
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
log.warning("Scraping BCV interrumpido");
return null;
} catch (Exception e) {
log.warning("Intento " + attempt + "/" + maxRetries + ": Error scraping BCV: " + e.getMessage());
if (attempt < maxRetries) {
log.info("Reintentando en 5 segundos...");
try { Thread.sleep(5000); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); }
} else {
log.severe("Scraping BCV fallo despues de " + maxRetries + " intentos: " + e.getMessage());
}
}
}
return null;
}
private BigDecimal extractUSD(String json) {
Matcher m = Pattern.compile("\"USD\"\\s*:\\s*([0-9.]+)").matcher(json);
if (m.find()) {
return new BigDecimal(m.group(1)).setScale(4, RoundingMode.HALF_UP);
}
return null;
}
private String extractField(String json, String field) {
Matcher m = Pattern.compile("\"" + field + "\"\\s*:\\s*\"([^\"]+)\"").matcher(json);
if (m.find()) {
return m.group(1);
}
return null;
}
private String httpGet(String urlStr) throws Exception {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setConnectTimeout(CONNECTION_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
int responseCode = conn.getResponseCode();
if (responseCode != 200) {
conn.disconnect();
return null;
}
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} finally {
conn.disconnect();
}
return sb.toString();
}
private String httpGetHtml(String urlStr) throws Exception {
URL url = new URL(urlStr);
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sc.getSocketFactory());
conn.setHostnameVerifier((hostname, session) -> true);
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "text/html");
conn.setConnectTimeout(CONNECTION_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
int responseCode = conn.getResponseCode();
if (responseCode != 200) {
conn.disconnect();
return null;
}
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} finally {
conn.disconnect();
}
return sb.toString();
}
}