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; 017 018import org.javamoney.moneta.spi.ServicePriority; 019 020import javax.money.MonetaryAmount; 021import javax.money.MonetaryAmountFactory; 022import javax.money.MonetaryException; 023import javax.money.spi.Bootstrap; 024import javax.money.spi.MonetaryAmountFactoryProviderSpi; 025import javax.money.spi.MonetaryAmountsSingletonSpi; 026 027import java.util.Comparator; 028import java.util.Map; 029import java.util.Objects; 030import java.util.Optional; 031import java.util.Set; 032import java.util.concurrent.ConcurrentHashMap; 033import java.util.logging.Logger; 034 035/** 036 * Default implementation ot {@link javax.money.spi.MonetaryAmountsSingletonSpi} loading the SPIs on startup 037 * initially once, using the 038 * JSR's {@link javax.money.spi.Bootstrap} mechanism. 039 */ 040public class DefaultMonetaryAmountsSingletonSpi implements MonetaryAmountsSingletonSpi{ 041 042 private Map<Class<? extends MonetaryAmount>,MonetaryAmountFactoryProviderSpi<?>> factories = 043 new ConcurrentHashMap<>(); 044 045 private Class<? extends MonetaryAmount> configuredDefaultAmountType = loadDefaultAmountType(); 046 047 public DefaultMonetaryAmountsSingletonSpi(){ 048 for(MonetaryAmountFactoryProviderSpi<?> f : Bootstrap.getServices(MonetaryAmountFactoryProviderSpi.class)){ 049 MonetaryAmountFactoryProviderSpi<?> existing = factories.put(f.getAmountType(), f); 050 if(Objects.nonNull(existing)){ 051 int compare = comparePriority(existing, f); 052 if(compare < 0){ 053 Logger.getLogger(getClass().getName()) 054 .warning("MonetaryAmountFactoryProviderSpi with lower prio ignored: " + f); 055 factories.put(f.getAmountType(), existing); 056 }else if(compare == 0){ 057 throw new IllegalStateException( 058 "Ambigous MonetaryAmountFactoryProviderSpi found for " + f.getAmountType() + ": " + 059 f.getClass().getName() + '/' + existing.getClass().getName()); 060 } 061 } 062 } 063 } 064 065 /** 066 * Comparator used for ordering the services provided. 067 * 068 * @author Anatole Tresch 069 */ 070 public static final class ProviderComparator implements Comparator<Object>{ 071 @Override 072 public int compare(Object p1, Object p2){ 073 return comparePriority(p1, p2); 074 } 075 } 076 077 /** 078 * Evaluates the service priority. Uses a {@link ServicePriority}, if 079 * present. 080 * 081 * @param service the service, not null. 082 * @return the priority from {@link ServicePriority}, or 0. 083 */ 084 private static int getServicePriority(Object service){ 085 if(Objects.isNull(service)){ 086 return Integer.MIN_VALUE; 087 } 088 ServicePriority prio = service.getClass().getAnnotation(ServicePriority.class); 089 if(Objects.nonNull(prio)){ 090 return prio.value(); 091 } 092 return 0; 093 } 094 095 /** 096 * Compare two service priorities given the same service interface. 097 * 098 * @param service1 first service, not null. 099 * @param service2 second service, not null. 100 * @param <T> the interface type 101 * @return the comparison result. 102 */ 103 public static <T> int comparePriority(T service1, T service2){ 104 int servicePriority1 = getServicePriority(service1); 105 int servicePriority2 = getServicePriority(service2); 106 return servicePriority1 == servicePriority2 ? 0 : servicePriority1 < servicePriority2 ? 1 : -1; 107 } 108 109 /** 110 * Tries to load the default {@link MonetaryAmount} class from 111 * {@code javamoney.properties} with contents as follows:<br/> 112 * <code> 113 * javax.money.defaults.amount.class=my.fully.qualified.ClassName 114 * </code> 115 * 116 * @return the loaded default class, or {@code null} 117 */ 118 // type check should be safe, exception will be logged if not. 119 private Class<? extends MonetaryAmount> loadDefaultAmountType(){ 120 return null; 121 } 122 123 124 // save cast, since members are managed by this instance 125 @SuppressWarnings("unchecked") 126 @Override 127 public <T extends MonetaryAmount> MonetaryAmountFactory<T> getAmountFactory(Class<T> amountType){ 128 MonetaryAmountFactoryProviderSpi<T> f = MonetaryAmountFactoryProviderSpi.class.cast(factories.get(amountType)); 129 if(Objects.nonNull(f)){ 130 return f.createMonetaryAmountFactory(); 131 } 132 throw new MonetaryException("No matching MonetaryAmountFactory found, type=" + amountType.getName()); 133 } 134 135 @Override 136 public Set<Class<? extends MonetaryAmount>> getAmountTypes(){ 137 return factories.keySet(); 138 } 139 140 /* 141 * (non-Javadoc) 142 * 143 * @see javax.money.spi.MonetaryAmountsSpi#getDefaultAmountType() 144 */ 145 @Override 146 public Class<? extends MonetaryAmount> getDefaultAmountType(){ 147 if(Objects.isNull(configuredDefaultAmountType)){ 148 for(MonetaryAmountFactoryProviderSpi<?> f : Bootstrap.getServices(MonetaryAmountFactoryProviderSpi.class)){ 149 if(f.getQueryInclusionPolicy() == MonetaryAmountFactoryProviderSpi.QueryInclusionPolicy.ALWAYS){ 150 configuredDefaultAmountType = f.getAmountType(); 151 break; 152 } 153 } 154 } 155 return Optional.ofNullable(configuredDefaultAmountType) 156 .orElseThrow(() -> new MonetaryException("No MonetaryAmountFactoryProviderSpi registered.")); 157 } 158 159}