001/*
002 * CREDIT SUISSE IS WILLING TO LICENSE THIS SPECIFICATION TO YOU ONLY UPON THE CONDITION THAT YOU
003 * ACCEPT ALL OF THE TERMS CONTAINED IN THIS AGREEMENT. PLEASE READ THE TERMS AND CONDITIONS OF THIS
004 * AGREEMENT CAREFULLY. BY DOWNLOADING THIS SPECIFICATION, YOU ACCEPT THE TERMS AND CONDITIONS OF
005 * THE AGREEMENT. IF YOU ARE NOT WILLING TO BE BOUND BY IT, SELECT THE "DECLINE" BUTTON AT THE
006 * BOTTOM OF THIS PAGE. Specification: JSR-354 Money and Currency API ("Specification") Copyright
007 * (c) 2012-2015, Credit Suisse All rights reserved.
008 */
009package org.javamoney.moneta.spi.base;
010
011import javax.money.CurrencyUnit;
012import javax.money.MonetaryException;
013import javax.money.convert.ConversionQuery;
014import javax.money.convert.ConversionQueryBuilder;
015import javax.money.convert.CurrencyConversion;
016import javax.money.convert.ExchangeRateProvider;
017import javax.money.spi.MonetaryConversionsSingletonSpi;
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.List;
022import java.util.Objects;
023
024/**
025 * SPI (conversion) that implements the functionality provided by the
026 * {@code MonetaryConversions} singleton accessor. It should be registered as a
027 * service using the JDK {@code ServiceLoader}. Hereby only one instance can be
028 * registered at a time.
029 * <p>
030 * This interface is designed to support also contextual behaviour, e.g. in Java
031 * EE containers each application may provide its own
032 * {@link javax.money.convert.ExchangeRateProvider} instances, e.g. by registering them as CDI
033 * beans. An EE container can register an according
034 * {@link BaseMonetaryConversionsSingletonSpi} that manages the different application
035 * contexts transparently. In a SE environment this class is expected to behave
036 * like an ordinary singleton, loading its SPIs from the {@link java.util.ServiceLoader}.
037 * <p>
038 * Instances of this class must be thread safe. It is not a requirement that
039 * they are serializable.
040 * <p>
041 * Only one instance can be registered using the {@link java.util.ServiceLoader}. When
042 * registering multiple instances the {@link javax.money.convert.MonetaryConversions} accessor will
043 * not work.
044 *
045 * @author Anatole Tresch
046 * @author Werner Keil
047 */
048public abstract class BaseMonetaryConversionsSingletonSpi implements MonetaryConversionsSingletonSpi{
049
050    /**
051     * Allows to quickly check, if a {@link javax.money.convert.ExchangeRateProvider} is accessible for the given
052     * {@link javax.money.convert.ConversionQuery}.
053     *
054     * @param conversionQuery the {@link javax.money.convert.ConversionQuery} determining the type of conversion
055     *                        required, not null.
056     * @return {@code true}, if such a conversion is supported, meaning an according
057     * {@link javax.money.convert.ExchangeRateProvider} can be
058     * accessed.
059     * @see #getExchangeRateProvider(javax.money.convert.ConversionQuery)
060     * @see #getExchangeRateProvider(String...)}
061     */
062    public boolean isExchangeRateProviderAvailable(ConversionQuery conversionQuery) {
063        try {
064            return getExchangeRateProvider(conversionQuery) != null;
065        } catch (Exception e) {
066            return false;
067        }
068    }
069
070    /**
071     * Allows to quickly check, if a {@link javax.money.convert.CurrencyConversion} is accessible for the given
072     * {@link javax.money.convert.ConversionQuery}.
073     *
074     * @param conversionQuery the {@link javax.money.convert.ConversionQuery} determining the type of conversion
075     *                        required, not null.
076     * @return {@code true}, if such a conversion is supported, meaning an according
077     * {@link javax.money.convert.CurrencyConversion} can be
078     * accessed.
079     * @see #getConversion(javax.money.convert.ConversionQuery)
080     * @see #getConversion(javax.money.CurrencyUnit, String...)}
081     */
082    public boolean isConversionAvailable(ConversionQuery conversionQuery) {
083        try {
084            return getConversion(conversionQuery) != null;
085        } catch (Exception e) {
086            return false;
087        }
088    }
089
090    /**
091     * Allows to quickly check, if a {@link javax.money.convert.CurrencyConversion} is accessible for the given
092     * {@link javax.money.convert.ConversionQuery}.
093     *
094     * @param termCurrency the terminating/target currency unit, not null.
095     * @param providers    the provider names defines a corresponding
096     *                     provider chain that must be encapsulated by the resulting {@link javax
097     *                     .money.convert.CurrencyConversion}. By default the provider
098     *                     chain as defined by #getDefaultRoundingProviderChain will be used.
099     * @return {@code true}, if such a conversion is supported, meaning an according
100     * {@link javax.money.convert.CurrencyConversion} can be
101     * accessed.
102     * @see #getConversion(javax.money.convert.ConversionQuery)
103     * @see #getConversion(javax.money.CurrencyUnit, String...)}
104     */
105    public boolean isConversionAvailable(CurrencyUnit termCurrency, String... providers) {
106        return isConversionAvailable(
107                ConversionQueryBuilder.of().setTermCurrency(termCurrency).setProviderNames(providers).build());
108    }
109
110    /**
111     * Access the current registered {@link javax.money.convert.ExchangeRateProvider} instances. If no provider
112     * names are passed ALL current registered providers are returned in undefined order.
113     *
114     * @param providers the provider names of hte providers to be accessed
115     * @return the list of providers, in the same order as requested.
116     * @throws javax.money.MonetaryException if a provider could not be resolved.
117     */
118    public List<ExchangeRateProvider> getExchangeRateProviders(String... providers) {
119        List<ExchangeRateProvider> provInstances = new ArrayList<>();
120        Collection<String> providerNames = Arrays.asList(providers);
121        if (providerNames.isEmpty()) {
122            providerNames = getProviderNames();
123        }
124        for (String provName : providerNames) {
125            ExchangeRateProvider provider = getExchangeRateProvider(provName);
126            if(provider==null){
127                throw new MonetaryException("Unsupported conversion/rate provider: " + provName);
128            }
129            provInstances.add(provider);
130        }
131        return provInstances;
132    }
133
134    /**
135     * Access a compound instance of an {@link javax.money.convert.ExchangeRateProvider} based on the given provider chain.
136     *
137     * @param providers the {@link javax.money.convert.ConversionQuery} provider names defines a corresponding
138     *                  provider chain that must be
139     *                  encapsulated by the resulting {@link javax.money.convert.ExchangeRateProvider}. By default
140     *                  the default
141     *                  provider changes as defined in #getDefaultRoundingProviderChain will be used.
142     * @return an {@link javax.money.convert.ExchangeRateProvider} built up with the given sub
143     * providers, never {@code null}.
144     * @throws javax.money.MonetaryException if a provider listed could not be found.
145     * @see #getProviderNames()
146     * @see #isExchangeRateProviderAvailable(javax.money.convert.ConversionQuery)
147     */
148    public ExchangeRateProvider getExchangeRateProvider(String... providers) {
149        return getExchangeRateProvider(ConversionQueryBuilder.of().setProviderNames(providers).build());
150    }
151
152    /**
153     * Access an instance of {@link javax.money.convert.CurrencyConversion}.
154     *
155     * @param conversionQuery the {@link javax.money.convert.ConversionQuery} determining the type of conversion
156     *                        required, not null.
157     * @return the corresponding conversion, not null.
158     * @throws javax.money.MonetaryException if no matching conversion could be found.
159     * @see #isConversionAvailable(javax.money.convert.ConversionQuery)
160     */
161    public CurrencyConversion getConversion(ConversionQuery conversionQuery) {
162        return getExchangeRateProvider(conversionQuery).getCurrencyConversion(
163                Objects.requireNonNull(conversionQuery.getCurrency(), "Terminating Currency is required.")
164        );
165    }
166
167    /**
168     * Access an instance of {@link javax.money.convert.CurrencyConversion}.
169     *
170     * @param termCurrency the terminating/target currency unit, not null.
171     * @param providers    the {@link javax.money.convert.ConversionQuery} provider names defines a corresponding
172     *                     provider chain that must be encapsulated by the resulting {@link javax
173     *                     .money.convert.CurrencyConversion}. By default the default
174     *                     provider chain as defined by #getDefaultRoundingProviderChain will be used.
175     * @return the corresponding conversion, not null.
176     * @throws javax.money.MonetaryException if no matching conversion could be found.
177     * @see #isConversionAvailable(javax.money.convert.ConversionQuery)
178     */
179    public CurrencyConversion getConversion(CurrencyUnit termCurrency, String... providers) {
180        return getConversion(ConversionQueryBuilder.of().setTermCurrency(termCurrency).setProviderNames(providers).build());
181    }
182
183}