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.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 Fond")
055                        .set("days", 0)
056                        .set("User-Agent", "Chrome/51.0.2704.103")
057                        .build();
058
059
060        private final List<YearMonth> cachedHistoric = new ArrayList<>();
061        public IMFHistoricRateProvider() {
062                super(CONTEXT);
063                 LoaderService loader = Bootstrap.getService(LoaderService.class);
064                loader.addLoaderListener(this, DATA_ID);
065                try {
066                    loader.loadData(DATA_ID);
067                } catch (IOException e) {
068                        LOG.log(Level.WARNING, "Error loading initial data from IMF provider...", e);
069                }
070        }
071
072        @Override
073        public ExchangeRate getExchangeRate(ConversionQuery conversionQuery) {
074                LocalDate[] times = getQueryDates(conversionQuery);
075                if(Objects.isNull(times)) {
076                        return super.getExchangeRate(conversionQuery);
077                }
078
079                for (YearMonth yearMonth : Stream.of(times).map(YearMonth::from)
080                                .collect(Collectors.toSet())) {
081
082                        if(!cachedHistoric.contains(yearMonth)){
083                                Map<IMFHistoricalType, InputStream> resources = IMFRemoteSearch.INSTANCE.getResources(yearMonth,
084                                                getContext().get("User-Agent", String.class));
085                                loadFromRemote(resources);
086                                cachedHistoric.add(yearMonth);
087                        }
088                }
089                return super.getExchangeRate(conversionQuery);
090        }
091
092        private void loadFromRemote(Map<IMFHistoricalType, InputStream> resources) {
093                try {
094                        for(IMFHistoricalType type: resources.keySet()) {
095                                RateIMFResult result = handler.read(resources.get(type));
096                                combine(result.getSdrToCurrency(), this.sdrToCurrency);
097                                combine(result.getCurrencyToSdr(), this.currencyToSdr);
098                        }
099            } catch (Exception e) {
100                LOG.log(Level.SEVERE, "Error", e);
101            }
102        }
103
104        private Map<CurrencyUnit, List<ExchangeRate>> combine(Map<CurrencyUnit, List<ExchangeRate>> source, Map<CurrencyUnit, List<ExchangeRate>> destination) {
105                for(CurrencyUnit currency: source.keySet()) {
106                        destination.putIfAbsent(currency, new ArrayList<>());
107                        List<ExchangeRate> rates = source.get(currency);
108                        destination.merge(currency, rates, IMFHistoricRateProvider::merge);
109                }
110                return destination;
111        }
112        private static List<ExchangeRate> merge(List<ExchangeRate> ratesA, List<ExchangeRate> ratesB) {
113                ratesA.addAll(ratesB);
114                ratesA.sort(COMPARATOR_EXCHANGE_BY_LOCAL_DATE.reversed());
115                return ratesA;
116        }
117}