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