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}