001/**
002 * Copyright (c) 2012, 2014, 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.spi;
017
018import javax.money.convert.ConversionQuery;
019import javax.money.convert.CurrencyConversionException;
020import javax.money.convert.ExchangeRate;
021import javax.money.convert.ExchangeRateProvider;
022import javax.money.convert.ProviderContext;
023import javax.money.convert.ProviderContextBuilder;
024import javax.money.convert.RateType;
025import java.util.ArrayList;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Objects;
029import java.util.Optional;
030import java.util.Set;
031import java.util.logging.Level;
032import java.util.logging.Logger;
033
034/**
035 * This class implements a {@link ExchangeRateProvider} that delegates calls to
036 * a collection of child {@link ExchangeRateProvider} instance.
037 *
038 * @author Anatole Tresch
039 */
040public class CompoundRateProvider extends AbstractRateProvider {
041    /**
042     * Key used to store a list of child {@link javax.money.convert.ProviderContext} instances of the providers
043     * contained within this instance.
044     * @deprecated Will be private in next major release.
045     */
046    @Deprecated
047    public static final String CHILD_PROVIDER_CONTEXTS_KEY = "childProviderContexts";
048    /**
049     * The {@link ExchangeRateProvider} instances.
050     */
051    private final List<ExchangeRateProvider> providers = new ArrayList<>();
052
053    /**
054     * Constructor.
055     *
056     * @param providers The collection of child {@link ExchangeRateProvider}
057     *                  instances this class delegates calls to.
058     */
059    public CompoundRateProvider(Iterable<ExchangeRateProvider> providers) {
060        super(createContext(providers));
061        for (ExchangeRateProvider exchangeRateProvider : providers) {
062            addProvider(exchangeRateProvider);
063        }
064    }
065
066    private static ProviderContext createContext(Iterable<ExchangeRateProvider> providers) {
067        Set<RateType> rateTypeSet = new HashSet<>();
068        StringBuilder providerName = new StringBuilder("Compound: ");
069        List<ProviderContext> childContextList = new ArrayList<>();
070        for (ExchangeRateProvider exchangeRateProvider : providers) {
071            childContextList.add(exchangeRateProvider.getContext());
072            providerName.append(exchangeRateProvider.getContext().getProviderName());
073            providerName.append(',' );
074            rateTypeSet.addAll(exchangeRateProvider.getContext().getRateTypes());
075        }
076        providerName.setLength(providerName.length() - 1);
077
078        ProviderContextBuilder builder = ProviderContextBuilder.of(providerName.toString(), rateTypeSet);
079        builder.set(CHILD_PROVIDER_CONTEXTS_KEY, childContextList);
080        return builder.build();
081    }
082
083    /**
084     * Add an additional {@link ExchangeRateProvider} to the instance's delegate
085     * list.
086     *
087     * @param prov The {@link ExchangeRateProvider} to be added, not {@code null}
088     *             .
089     * @throws java.lang.NullPointerException if the provider is null.
090     *                                        .
091     */
092    private void addProvider(ExchangeRateProvider prov) {
093        providers.add(Optional.ofNullable(prov)
094                .orElseThrow(() -> new NullPointerException("ConversionProvider required.")));
095    }
096
097    /*
098     * (non-Javadoc)
099     *
100     * @see
101     * javax.money.convert.ExchangeRateProvider#getExchangeRate(javax.money.
102     * CurrencyUnit, javax.money.CurrencyUnit,
103     * javax.money.convert.ConversionContext)
104     */
105    @Override
106    public ExchangeRate getExchangeRate(ConversionQuery conversionQuery) {
107        for (ExchangeRateProvider prov : this.providers) {
108            try {
109                if (prov.isAvailable(conversionQuery)) {
110                    ExchangeRate rate = prov.getExchangeRate(conversionQuery);
111                    if (Objects.nonNull(rate)) {
112                        return rate;
113                    }
114                }
115            } catch (Exception e) {
116                Logger.getLogger(getClass().getName()).log(Level.WARNING,
117                        "Rate Provider did not return data though at check before data was flagged as available," +
118                                " provider=" + prov.getContext().getProviderName() + ", query=" + conversionQuery);
119            }
120        }
121        throw new CurrencyConversionException(conversionQuery.getBaseCurrency(), conversionQuery.getCurrency(), null,
122                "All delegate prov iders failed to deliver rate, providers=" + this.providers +
123                        ", query=" + conversionQuery);
124    }
125
126
127}