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}