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.convert.internal;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.List;
021import java.util.Map;
022import java.util.Optional;
023import java.util.Set;
024import java.util.concurrent.ConcurrentHashMap;
025import java.util.logging.Level;
026import java.util.logging.Logger;
027
028import javax.money.MonetaryException;
029import javax.money.convert.ConversionQuery;
030import javax.money.convert.ExchangeRateProvider;
031import javax.money.spi.Bootstrap;
032import javax.money.spi.MonetaryConversionsSingletonSpi;
033
034import org.javamoney.moneta.spi.CompoundRateProvider;
035import org.javamoney.moneta.spi.MonetaryConfig;
036
037/**
038 * This is the default implementation of the {@link javax.money.spi.MonetaryConversionsSingletonSpi} interface, backing
039 * up the {@link javax.money.convert.MonetaryConversions} singleton.
040 */
041public class DefaultMonetaryConversionsSingletonSpi implements MonetaryConversionsSingletonSpi {
042    /**
043     * Logger used.
044     */
045    private static final Logger LOG = Logger.getLogger(DefaultMonetaryConversionsSingletonSpi.class.getName());
046
047    /**
048     * The providers loaded.
049     */
050    private Map<String, ExchangeRateProvider> conversionProviders = new ConcurrentHashMap<>();
051
052    /**
053     * Constructors, loads the providers from the {@link javax.money.spi.Bootstrap} component.
054     */
055    public DefaultMonetaryConversionsSingletonSpi() {
056        reload();
057    }
058
059    /**
060     * Reloads/reinitializes the providers found.
061     */
062    public void reload() {
063        Map<String, ExchangeRateProvider> newProviders = new ConcurrentHashMap<>();
064        for (ExchangeRateProvider prov : Bootstrap.getServices(ExchangeRateProvider.class)) {
065            newProviders.put(prov.getContext().getProviderName(), prov);
066        }
067        this.conversionProviders = newProviders;
068    }
069
070    @Override
071    public ExchangeRateProvider getExchangeRateProvider(ConversionQuery query) {
072        Collection<String> providers = getProvidersToUse(query);
073        List<ExchangeRateProvider> provInstances = new ArrayList<>();
074        for (String provName : providers) {
075            ExchangeRateProvider prov = Optional.ofNullable(
076                    this.conversionProviders.get(provName))
077                    .orElseThrow(
078                            () -> new MonetaryException(
079                                    "Unsupported conversion/rate provider: "
080                                            + provName));
081            provInstances.add(prov);
082        }
083        if (provInstances.isEmpty()) {
084            throw new MonetaryException("No such providers: " + query);
085        }
086        if (provInstances.size() == 1) {
087            return provInstances.get(0);
088        }
089        return new CompoundRateProvider(provInstances);
090    }
091
092    @Override
093    public boolean isExchangeRateProviderAvailable(ConversionQuery conversionQuery) {
094        Collection<String> providers = getProvidersToUse(conversionQuery);
095        return !providers.isEmpty();
096    }
097
098    @Override
099    public boolean isConversionAvailable(ConversionQuery conversionQuery) {
100        try {
101            if (isExchangeRateProviderAvailable(conversionQuery)) {
102                return getExchangeRateProvider(conversionQuery).getCurrencyConversion(conversionQuery) != null;
103            }
104        } catch (Exception e) {
105            LOG.log(Level.FINEST, "Error during availability check for conversion: " + conversionQuery, e);
106        }
107        return false;
108    }
109
110    @Override
111    public ExchangeRateProvider getExchangeRateProvider(String... providers) {
112        List<ExchangeRateProvider> provInstances = new ArrayList<>();
113        for (String provName : providers) {
114            ExchangeRateProvider prov = Optional.ofNullable(
115                    this.conversionProviders.get(provName))
116                    .orElseThrow(
117                            () -> new MonetaryException(
118                                    "Unsupported conversion/rate provider: "
119                                            + provName));
120            provInstances.add(prov);
121        }
122        if (provInstances.size() == 1) {
123            return provInstances.get(0);
124        }
125        return new CompoundRateProvider(provInstances);
126    }
127
128    private Collection<String> getProvidersToUse(ConversionQuery query) {
129        List<String> providersToUse = new ArrayList<>();
130        List<String> providers = query.getProviderNames();
131        if (providers.isEmpty()) {
132            providers = getDefaultProviderChain();
133            if (providers.isEmpty()) {
134                throw new IllegalStateException("No default provider chain available.");
135            }
136        }
137        for (String provider : providers) {
138            ExchangeRateProvider prov = this.conversionProviders.get(provider);
139            if (prov == null) {
140                throw new MonetaryException("Invalid ExchangeRateProvider (not found): " + provider);
141            }
142            providersToUse.add(provider);
143        }
144        return providersToUse;
145    }
146
147    @Override
148    public Set<String> getProviderNames() {
149        return this.conversionProviders.keySet();
150    }
151
152    @Override
153    public List<String> getDefaultProviderChain() {
154        List<String> provList = new ArrayList<>();
155        String defaultChain = MonetaryConfig.getConfig().get("conversion.default-chain");
156        String[] items = defaultChain.split(",");
157        for (String item : items) {
158            if (getProviderNames().contains(item.trim())) {
159                provList.add(item);
160            } else {
161                LOG.warning("Ignoring non existing default provider: " + item);
162            }
163        }
164        return provList;
165    }
166
167
168}