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.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 conversionQuery) { 072 Collection<String> providers = getProvidersToUse(conversionQuery); 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: " + conversionQuery); 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}