v1.0.11 - Plugin BCV Exchange Rate para iDempiere v10
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
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 = 15000;
|
||||
private static final int READ_TIMEOUT = 30000;
|
||||
private static final int MAX_DAYS_BACK = 10;
|
||||
|
||||
public BCVRateResponse getRateForDateWithFallback(String requestedDate) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
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("API no tiene tasa para " + requestedDate + ", intentando scraping BCV...");
|
||||
BCVRateResponse websiteRate = getRateFromBCVWebsite();
|
||||
if (websiteRate != null && websiteRate.isValid()) {
|
||||
log.info("Tasa obtenida del sitio web BCV: USD=" + websiteRate.getDollar()
|
||||
+ " effective=" + websiteRate.getDate());
|
||||
return new BCVRateResponse(websiteRate.getDollar(), requestedDate, websiteRate.getDate());
|
||||
}
|
||||
|
||||
log.info("No se encontro tasa en los ultimos " + MAX_DAYS_BACK + " dias 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() {
|
||||
try {
|
||||
log.info("Iniciando scraping de BCV website...");
|
||||
String html = httpGetHtml(BCV_WEBSITE);
|
||||
if (html == null) {
|
||||
log.warning("No se pudo obtener pagina BCV (httpGetHtml returned null)");
|
||||
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: USD=" + dollar + " effective=" + effectiveDate);
|
||||
return new BCVRateResponse(dollar, effectiveDate);
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Error scraping BCV website: " + e.getMessage(), e);
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user