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}