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