package name.abuchen.portfolio.money.impl; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigDecimal; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.text.ParseException; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; import java.util.HashMap; import java.util.Map; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import name.abuchen.portfolio.money.ExchangeRate; import name.abuchen.portfolio.money.ExchangeRateProvider; import name.abuchen.portfolio.util.Dates; /** * Opens a connection to the ECB server, parses XML files and updates the given * {@link ECBData} object. */ /* package */class ECBUpdater { private static final String SOURCE_URL = "http://www.ecb.europa.eu/stats/eurofxref/"; //$NON-NLS-1$ private enum Feeds { HISTORIC("eurofxref-hist.xml"), //$NON-NLS-1$ LAST_90_DAYS("eurofxref-hist-90d.xml"), //$NON-NLS-1$ DAILY("eurofxref-daily.xml"); //$NON-NLS-1$ private String xmlFileName; private Feeds(String xmlFileName) { this.xmlFileName = xmlFileName; } public String getXmlFileName() { return this.xmlFileName; } } public void update(ExchangeRateProvider provider, ECBData data) throws IOException { // determine which files must be loaded: full, last 90 days, daily Feeds f = Feeds.HISTORIC; if (data.getLastModified() != 0) { LocalDate lastModified = LocalDate.from(Instant.ofEpochMilli(data.getLastModified()).atZone( ZoneId.systemDefault())); int days = Dates.daysBetween(lastModified, LocalDate.now()); if (days <= 1) f = Feeds.DAILY; else if (days <= 90) f = Feeds.LAST_90_DAYS; else f = Feeds.HISTORIC; } // download feed InputStream input = null; HttpURLConnection connection = null; try { URL feedUrl = new URI(SOURCE_URL + f.getXmlFileName()).toURL(); connection = (HttpURLConnection) feedUrl.openConnection(); // fortunately, the last modified date for all three feeds is // identical on the server. If nothing changed, parse nothing. long lastModified = connection.getLastModified(); if (lastModified <= data.getLastModified()) return; input = connection.getInputStream(); XMLInputFactory factory = XMLInputFactory.newInstance(); XMLStreamReader reader = factory.createXMLStreamReader(new InputStreamReader(input, "UTF-8")); //$NON-NLS-1$ readCubes(provider, data, reader); data.setDirty(true); data.setLastModified(lastModified); } catch (XMLStreamException e) { throw new IOException(e); } catch (URISyntaxException e) { throw new IOException(e); } catch (ParseException e) { throw new IOException(e); } finally { try { if (input != null) input.close(); } catch (IOException ignore) {} if (connection != null) connection.disconnect(); } } private void readCubes(ExchangeRateProvider provider, ECBData data, XMLStreamReader reader) throws XMLStreamException, ParseException { // lookup map by term currency Map<String, ExchangeRateTimeSeriesImpl> currency2series = new HashMap<String, ExchangeRateTimeSeriesImpl>(); for (ExchangeRateTimeSeriesImpl series : data.getSeries()) currency2series.put(series.getTermCurrency(), series); LocalDate currentDate = null; while (reader.hasNext()) { int event = reader.next(); if (event != XMLStreamConstants.START_ELEMENT) continue; if (!"Cube".equals(reader.getLocalName())) //$NON-NLS-1$ continue; // lookup currency first: it is more likely to show up String termCurrency = reader.getAttributeValue(null, "currency"); //$NON-NLS-1$ if (termCurrency != null) { ExchangeRateTimeSeriesImpl series = currency2series.get(termCurrency); if (series == null) { series = new ExchangeRateTimeSeriesImpl(); series.setProvider(provider); series.setBaseCurrency(ECBExchangeRateProvider.EUR); series.setTermCurrency(termCurrency); currency2series.put(termCurrency, series); data.addSeries(series); } String rateValue = reader.getAttributeValue(null, "rate"); //$NON-NLS-1$ BigDecimal rateNumber = new BigDecimal(rateValue); ExchangeRate rate = new ExchangeRate(currentDate, rateNumber); series.addRate(rate); } else { String time = reader.getAttributeValue(null, "time"); //$NON-NLS-1$ if (time != null) currentDate = LocalDate.parse(time); } } } }