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.loader; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.net.URI; 021import java.util.List; 022import java.util.Map; 023import java.util.Objects; 024import java.util.Optional; 025import java.util.Set; 026import java.util.Timer; 027import java.util.concurrent.ConcurrentHashMap; 028import java.util.concurrent.ExecutorService; 029import java.util.concurrent.Executors; 030import java.util.concurrent.Future; 031import java.util.logging.Level; 032import java.util.logging.Logger; 033 034import javax.money.spi.Bootstrap; 035 036import org.javamoney.moneta.spi.LoadDataInformation; 037import org.javamoney.moneta.spi.LoaderService; 038 039/** 040 * This class provides a mechanism to register resources, that may be updated 041 * regularly. The implementation, based on the {@link UpdatePolicy} 042 * loads/updates the resources from arbitrary locations and stores it to the 043 * format file cache. Default loading tasks can be configured within the javamoney.properties 044 * file, @see org.javamoney.moneta.loader.format.LoaderConfigurator . 045 * <p> 046 * @author Anatole Tresch 047 */ 048public class DefaultLoaderService implements LoaderService { 049 /** 050 * Logger used. 051 */ 052 private static final Logger LOG = Logger.getLogger(DefaultLoaderService.class.getName()); 053 /** 054 * The data resources managed by this instance. 055 */ 056 private final Map<String, LoadableResource> resources = new ConcurrentHashMap<>(); 057 /** 058 * The registered {@link LoaderListener} instances. 059 */ 060 private final DefaultLoaderListener listener = new DefaultLoaderListener(); 061 062 /** 063 * The local resource cache, to allow keeping current data on the local 064 * system. 065 */ 066 private static final ResourceCache CACHE = loadResourceCache(); 067 /** 068 * The thread pool used for loading of data, triggered by the timer. 069 */ 070 private final ExecutorService executors = Executors.newCachedThreadPool(DaemonThreadFactory.INSTANCE); 071 072 private DefaultLoaderServiceFacade defaultLoaderServiceFacade; 073 074 /** 075 * The timer used for schedules. 076 */ 077 private volatile Timer timer; 078 079 /** 080 * Constructor, initializing from config. 081 */ 082 public DefaultLoaderService() { 083 initialize(); 084 } 085 086 /** 087 * This method reads initial loads from the javamoney.properties and installs the according timers. 088 */ 089 void initialize() { 090 // Cancel any running tasks 091 Timer oldTimer = timer; 092 timer = new Timer(true); 093 if (Objects.nonNull(oldTimer)) { 094 oldTimer.cancel(); 095 } 096 // (re)initialize 097 LoaderConfigurator configurator = new LoaderConfigurator(this); 098 defaultLoaderServiceFacade = new DefaultLoaderServiceFacade(timer, listener, resources); 099 configurator.load(); 100 } 101 102 /** 103 * Loads the cache to be used. 104 * 105 * @return the cache to be used, not null. 106 */ 107 private static ResourceCache loadResourceCache() { 108 try { 109 return Optional.ofNullable(Bootstrap.getService(ResourceCache.class)).orElseGet( 110 DefaultResourceCache::new); 111 } catch (Exception e) { 112 LOG.log(Level.SEVERE, "Error loading ResourceCache instance.", e); 113 return new DefaultResourceCache(); 114 } 115 } 116 117 /** 118 * Get the resource cache loaded. 119 * 120 * @return the resource cache, not null. 121 */ 122 static ResourceCache getResourceCache() { 123 return DefaultLoaderService.CACHE; 124 } 125 126 /** 127 * Removes a resource managed. 128 * 129 * @param resourceId the resource id. 130 */ 131 public void unload(String resourceId) { 132 LoadableResource res = this.resources.get(resourceId); 133 if (Objects.nonNull(res)) { 134 res.unload(); 135 } 136 } 137 138 /* 139 * (non-Javadoc) 140 * 141 * @see 142 * org.javamoney.moneta.spi.LoaderService#registerData(java.lang.String, 143 * org.javamoney.moneta.spi.LoaderService.UpdatePolicy, java.util.Map, 144 * java.net.URL, java.net.URL[]) 145 */ 146 @Override 147 public void registerData(LoadDataInformation loadDataInformation) { 148 149 if (resources.containsKey(loadDataInformation.getResourceId())) { 150 throw new IllegalArgumentException("Resource : " + loadDataInformation.getResourceId() + " already registered."); 151 } 152 153 LoadableResource resource = new LoadableResourceBuilder() 154 .withCache(CACHE).withLoadDataInformation(loadDataInformation) 155 .build(); 156 this.resources.put(loadDataInformation.getResourceId(), resource); 157 158 if (loadDataInformation.getLoaderListener() != null) { 159 this.addLoaderListener(loadDataInformation.getLoaderListener(), loadDataInformation.getResourceId()); 160 } 161 162 if(loadDataInformation.isStartRemote()) { 163 defaultLoaderServiceFacade.loadDataRemote(loadDataInformation.getResourceId(), resources); 164 } 165 switch (loadDataInformation.getUpdatePolicy()) { 166 case NEVER: 167 loadDataLocal(loadDataInformation.getResourceId()); 168 break; 169 case ONSTARTUP: 170 loadDataAsync(loadDataInformation.getResourceId()); 171 break; 172 case SCHEDULED: 173 defaultLoaderServiceFacade.scheduledData(resource); 174 break; 175 case LAZY: 176 default: 177 break; 178 } 179 } 180 181 @Override 182 public void registerAndLoadData(LoadDataInformation loadDataInformation) { 183 184 if (resources.containsKey(loadDataInformation.getResourceId())) { 185 throw new IllegalArgumentException("Resource : " + loadDataInformation.getResourceId() + " already registered."); 186 } 187 LoadableResource resource = new LoadableResourceBuilder() 188 .withCache(CACHE).withLoadDataInformation(loadDataInformation) 189 .build(); 190 this.resources.put(loadDataInformation.getResourceId(), resource); 191 192 193 if (loadDataInformation.getLoaderListener() != null) { 194 this.addLoaderListener(loadDataInformation.getLoaderListener(), loadDataInformation.getResourceId()); 195 } 196 197 switch (loadDataInformation.getUpdatePolicy()) { 198 case SCHEDULED: 199 defaultLoaderServiceFacade.scheduledData(resource); 200 break; 201 case LAZY: 202 default: 203 break; 204 } 205 loadData(loadDataInformation.getResourceId()); 206 } 207 208 @Override 209 public void registerAndLoadData(String resourceId, UpdatePolicy updatePolicy, Map<String, String> properties, LoaderListener loaderListener, URI backupResource, URI... resourceLocations) { 210 211 } 212 213 @Override 214 public void registerData(String resourceId, UpdatePolicy updatePolicy, Map<String, String> properties, LoaderListener loaderListener, URI backupResource, URI... resourceLocations) { 215 216 } 217 218 @Override 219 public Map<String, String> getUpdateConfiguration(String resourceId) { 220 LoadableResource load = this.resources.get(resourceId); 221 if (Objects.nonNull(load)) { 222 return load.getProperties(); 223 } 224 return null; 225 } 226 227 @Override 228 public boolean isResourceRegistered(String resourceId) { 229 return this.resources.containsKey(resourceId); 230 } 231 232 @Override 233 public Set<String> getResourceIds() { 234 return this.resources.keySet(); 235 } 236 237 @Override 238 public InputStream getData(String resourceId) throws IOException { 239 LoadableResource load = this.resources.get(resourceId); 240 if (Objects.nonNull(load)) { 241 load.getDataStream(); 242 } 243 throw new IllegalArgumentException("No such resource: " + resourceId); 244 } 245 246 @Override 247 public boolean loadData(String resourceId) { 248 return defaultLoaderServiceFacade.loadData(resourceId, resources); 249 } 250 251 @Override 252 public Future<Boolean> loadDataAsync(final String resourceId) { 253 return executors.submit(() -> defaultLoaderServiceFacade.loadData(resourceId, resources)); 254 } 255 256 @Override 257 public boolean loadDataLocal(String resourceId) { 258 return defaultLoaderServiceFacade.loadDataLocal(resourceId); 259 } 260 261 262 @Override 263 public void resetData(String resourceId) throws IOException { 264 LoadableResource load = Optional.ofNullable(this.resources.get(resourceId)) 265 .orElseThrow(() -> new IllegalArgumentException("No such resource: " + resourceId)); 266 if (load.resetToFallback()) { 267 listener.trigger(resourceId, load.getDataStream()); 268 } 269 } 270 271 @Override 272 public void addLoaderListener(LoaderListener l, String... resourceIds) { 273 if (resourceIds.length == 0) { 274 List<LoaderListener> listeners = listener.getListeners(""); 275 synchronized (listeners) { 276 listeners.add(l); 277 } 278 } else { 279 for (String dataId : resourceIds) { 280 List<LoaderListener> listeners = listener.getListeners(dataId); 281 synchronized (listeners) { 282 listeners.add(l); 283 } 284 } 285 } 286 } 287 288 @Override 289 public void removeLoaderListener(LoaderListener loadListener, String... resourceIds) { 290 if (resourceIds.length == 0) { 291 List<LoaderListener> listeners = listener.getListeners(""); 292 synchronized (listeners) { 293 listeners.remove(loadListener); 294 } 295 } else { 296 for (String dataId : resourceIds) { 297 List<LoaderListener> listeners = listener.getListeners(dataId); 298 synchronized (listeners) { 299 listeners.remove(loadListener); 300 } 301 } 302 } 303 } 304 305 @Override 306 public UpdatePolicy getUpdatePolicy(String resourceId) { 307 LoadableResource load = Optional.of(this.resources.get(resourceId)) 308 .orElseThrow(() -> new IllegalArgumentException("No such resource: " + resourceId)); 309 return load.getUpdatePolicy(); 310 } 311 312 @Override 313 public String toString() { 314 return "DefaultLoaderService [resources=" + resources + ']'; 315 } 316}