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}