001/* 002 Copyright (c) 2012, 2015, Credit Suisse (Anatole Tresch), Werner Keil and others by the @author tag. 003 004 Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 use this file except in compliance with the License. You may obtain a copy of 006 the License at 007 008 http://www.apache.org/licenses/LICENSE-2.0 009 010 Unless required by applicable law or agreed to in writing, software 011 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 License for the specific language governing permissions and limitations under 014 the License. 015 */ 016package org.javamoney.moneta.convert.imf; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.time.LocalDate; 021import java.time.YearMonth; 022import java.util.ArrayList; 023import java.util.List; 024import java.util.Map; 025import java.util.Objects; 026import java.util.logging.Level; 027import java.util.logging.Logger; 028import java.util.stream.Collectors; 029import java.util.stream.Stream; 030 031import javax.money.CurrencyUnit; 032import javax.money.convert.ConversionQuery; 033import javax.money.convert.ExchangeRate; 034import javax.money.convert.ProviderContext; 035import javax.money.convert.ProviderContextBuilder; 036import javax.money.convert.RateType; 037import javax.money.spi.Bootstrap; 038 039import org.javamoney.moneta.convert.imf.IMFRateReadingHandler.RateIMFResult; 040import org.javamoney.moneta.spi.loader.LoaderService; 041 042/** 043 * Find by historic from IMF 044 * @author otaviojava 045 * @since 1.0.1 046 */ 047public class IMFHistoricRateProvider extends IMFAbstractRateProvider { 048 049 private static final Logger LOG = Logger.getLogger(IMFHistoricRateProvider.class.getName()); 050 051 private static final String DATA_ID = IMFHistoricRateProvider.class.getSimpleName(); 052 053 private static final ProviderContext CONTEXT = ProviderContextBuilder.of("IMF-HIST", RateType.HISTORIC) 054 .set("providerDescription", "Historic International Monetary Fund") 055 .set("days", 0) 056 //.set("User-Agent", "Chrome/51.0.2704.103") This was a URLConnection hack, OkHttp works without the User-Agent 057 .build(); 058 059 private final List<YearMonth> cachedHistoric = new ArrayList<>(); 060 public IMFHistoricRateProvider() { 061 super(CONTEXT); 062 LoaderService loader = Bootstrap.getService(LoaderService.class); 063 loader.addLoaderListener(this, DATA_ID); 064 try { 065 loader.loadData(DATA_ID); 066 } catch (IOException e) { 067 LOG.log(Level.WARNING, "Error loading initial data from IMF provider...", e); 068 } 069 } 070 071 @Override 072 public ExchangeRate getExchangeRate(ConversionQuery conversionQuery) { 073 LocalDate[] times = getQueryDates(conversionQuery); 074 if(Objects.isNull(times)) { 075 return super.getExchangeRate(conversionQuery); 076 } 077 078 for (YearMonth yearMonth : Stream.of(times).map(YearMonth::from) 079 .collect(Collectors.toSet())) { 080 081 if(!cachedHistoric.contains(yearMonth)){ 082 Map<IMFHistoricalType, InputStream> resources = IMFRemoteSearch.INSTANCE.getResources(yearMonth, 083 getContext().get("User-Agent", String.class)); 084 loadFromRemote(resources); 085 cachedHistoric.add(yearMonth); 086 } 087 } 088 return super.getExchangeRate(conversionQuery); 089 } 090 091 private void loadFromRemote(Map<IMFHistoricalType, InputStream> resources) { 092 try { 093 for(IMFHistoricalType type: resources.keySet()) { 094 RateIMFResult result = handler.read(resources.get(type)); 095 combine(result.getSdrToCurrency(), this.sdrToCurrency); 096 combine(result.getCurrencyToSdr(), this.currencyToSdr); 097 } 098 } catch (Exception e) { 099 LOG.log(Level.SEVERE, "Error", e); 100 } 101 } 102 103 private Map<CurrencyUnit, List<ExchangeRate>> combine(Map<CurrencyUnit, List<ExchangeRate>> source, Map<CurrencyUnit, List<ExchangeRate>> destination) { 104 for(CurrencyUnit currency: source.keySet()) { 105 destination.putIfAbsent(currency, new ArrayList<>()); 106 List<ExchangeRate> rates = source.get(currency); 107 destination.merge(currency, rates, IMFHistoricRateProvider::merge); 108 } 109 return destination; 110 } 111 private static List<ExchangeRate> merge(List<ExchangeRate> ratesA, List<ExchangeRate> ratesB) { 112 ratesA.addAll(ratesB); 113 ratesA.sort(COMPARATOR_EXCHANGE_BY_LOCAL_DATE.reversed()); 114 return ratesA; 115 } 116}