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