Skip to content

Commit 3c777da

Browse files
committed
Working on #353
1 parent fb5b6ad commit 3c777da

21 files changed

+1490
-61
lines changed

moneta-core/src/main/java/module-info.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import org.javamoney.moneta.spi.loader.LoaderService;
55

66
/*
7-
Copyright (c) 2012, 2023, Werner Keil and others by the @author tag.
7+
Copyright (c) 2012, 2024, Werner Keil and others by the @author tag.
88
99
Licensed under the Apache License, Version 2.0 (the "License"); you may not
1010
use this file except in compliance with the License. You may obtain a copy of
@@ -26,6 +26,7 @@
2626
exports org.javamoney.moneta.spi.format;
2727
exports org.javamoney.moneta.spi.loader;
2828
exports org.javamoney.moneta.spi.loader.urlconnection;
29+
exports org.javamoney.moneta.spi.loader.okhttp;
2930
requires transitive java.money;
3031
requires transitive java.logging;
3132
requires jakarta.annotation;

moneta-core/src/main/java/org/javamoney/moneta/spi/loader/urlconnection/URLConnectionLoaderListener.java renamed to moneta-core/src/main/java/org/javamoney/moneta/spi/loader/ConnectionLoaderListener.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* License for the specific language governing permissions and limitations under
1414
* the License.
1515
*/
16-
package org.javamoney.moneta.spi.loader.urlconnection;
16+
package org.javamoney.moneta.spi.loader;
1717

1818
import java.util.ArrayList;
1919
import java.util.Collections;
@@ -24,12 +24,11 @@
2424
import java.util.logging.Level;
2525
import java.util.logging.Logger;
2626

27-
import org.javamoney.moneta.spi.loader.DataStreamFactory;
2827
import org.javamoney.moneta.spi.loader.LoaderService.LoaderListener;
2928

30-
class URLConnectionLoaderListener {
29+
public class ConnectionLoaderListener {
3130

32-
private static final Logger LOG = Logger.getLogger(URLConnectionLoaderListener.class.getName());
31+
private static final Logger LOG = Logger.getLogger(ConnectionLoaderListener.class.getName());
3332

3433
private final Map<String, List<LoaderListener>> listenersMap = new ConcurrentHashMap<>();
3534

@@ -91,7 +90,7 @@ public void trigger(String dataId, DataStreamFactory dataStreamFactory) {
9190

9291
@Override
9392
public String toString() {
94-
return URLConnectionLoaderListener.class.getName() + '{' +
93+
return ConnectionLoaderListener.class.getName() + '{' +
9594
"listenersMap: " + listenersMap + '}';
9695
}
9796
}

moneta-core/src/main/java/org/javamoney/moneta/spi/loader/DataStreamFactory.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ public interface DataStreamFactory
2222

2323
InputStream getDataStream();
2424

25+
boolean loadRemote();
26+
27+
boolean readCache();
2528
}
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
/*
2+
Copyright (c) 2023, 2024, Werner Keil and others by the @author tag.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
use this file except in compliance with the License. You may obtain a copy of
6+
the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
License for the specific language governing permissions and limitations under
14+
the License.
15+
*/
16+
package org.javamoney.moneta.spi.loader.okhttp;
17+
18+
import org.javamoney.moneta.spi.loader.*;
19+
20+
import javax.money.spi.Bootstrap;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.net.URI;
24+
import java.util.*;
25+
import java.util.concurrent.ConcurrentHashMap;
26+
import java.util.concurrent.ExecutorService;
27+
import java.util.concurrent.Executors;
28+
import java.util.concurrent.Future;
29+
import java.util.logging.Level;
30+
import java.util.logging.Logger;
31+
32+
/**
33+
* This class provides a mechanism to register resources, that may be updated
34+
* regularly. The implementation, based on the {@link UpdatePolicy}
35+
* loads/updates the resources from arbitrary locations via {@link OkHttpClient} and stores them to the
36+
* format file cache. Default loading tasks can be configured within the <code>javamoney.properties</code>
37+
* file.
38+
* @see LoaderConfigurator
39+
* @see okhttp3.OkHttpClient
40+
* @author Werner Keil
41+
*/
42+
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
43+
public class HttpConnectionLoaderService implements LoaderService {
44+
/**
45+
* Logger used.
46+
*/
47+
private static final Logger LOG = Logger.getLogger(HttpConnectionLoaderService.class.getName());
48+
/**
49+
* The data resources managed by this instance.
50+
*/
51+
private final Map<String, LoadableHttpResource> resources = new ConcurrentHashMap<>();
52+
/**
53+
* The registered {@link LoaderListener} instances.
54+
*/
55+
private final ConnectionLoaderListener listener = new ConnectionLoaderListener();
56+
57+
/**
58+
* The local resource cache, to allow keeping current data on the local
59+
* system.
60+
*/
61+
private static final ResourceCache CACHE = loadResourceCache();
62+
/**
63+
* The thread pool used for loading of data, triggered by the timer.
64+
*/
65+
private final ExecutorService executors = Executors.newCachedThreadPool(DaemonThreadFactory.INSTANCE);
66+
67+
private HttpConnectionLoaderServiceFacade defaultLoaderServiceFacade;
68+
69+
/**
70+
* The timer used for schedules.
71+
*/
72+
private volatile Timer timer;
73+
74+
/**
75+
* Constructor, initializing from config.
76+
*/
77+
public HttpConnectionLoaderService() {
78+
initialize();
79+
}
80+
81+
/**
82+
* This method reads initial loads from the javamoney.properties and installs the according timers.
83+
*/
84+
void initialize() {
85+
// Cancel any running tasks
86+
Timer oldTimer = timer;
87+
timer = new Timer(true);
88+
if (Objects.nonNull(oldTimer)) {
89+
oldTimer.cancel();
90+
}
91+
// (re)initialize
92+
LoaderConfigurator configurator = LoaderConfigurator.of(this);
93+
defaultLoaderServiceFacade = new HttpConnectionLoaderServiceFacade(timer, listener, resources);
94+
configurator.load();
95+
}
96+
97+
/**
98+
* Loads the cache to be used.
99+
*
100+
* @return the cache to be used, not null.
101+
*/
102+
private static ResourceCache loadResourceCache() {
103+
try {
104+
return Optional.ofNullable(Bootstrap.getService(ResourceCache.class)).orElseGet(
105+
HttpConnectionResourceCache::new);
106+
} catch (Exception e) {
107+
LOG.log(Level.SEVERE, "Error loading ResourceCache instance.", e);
108+
return new HttpConnectionResourceCache();
109+
}
110+
}
111+
112+
/**
113+
* Get the resource cache loaded.
114+
*
115+
* @return the resource cache, not null.
116+
*/
117+
static ResourceCache getResourceCache() {
118+
return HttpConnectionLoaderService.CACHE;
119+
}
120+
121+
/**
122+
* Removes a resource managed.
123+
*
124+
* @param resourceId the resource id.
125+
*/
126+
public void unload(String resourceId) {
127+
LoadableHttpResource res = this.resources.get(resourceId);
128+
if (Objects.nonNull(res)) {
129+
res.unload();
130+
}
131+
}
132+
133+
/*
134+
* (non-Javadoc)
135+
*
136+
* @see
137+
* org.javamoney.moneta.spi.loader.LoaderService#registerData(java.lang.String,
138+
* org.javamoney.moneta.spi.loader.LoaderService.UpdatePolicy, java.util.Map,
139+
* java.net.URL, java.net.URL[])
140+
*/
141+
@Override
142+
public void registerData(LoadDataInformation loadDataInformation) {
143+
144+
if (resources.containsKey(loadDataInformation.getResourceId())) {
145+
throw new IllegalArgumentException("Resource : " + loadDataInformation.getResourceId() + " already registered.");
146+
}
147+
148+
LoadableHttpResource resource = new LoadableHttpResourceBuilder()
149+
.withCache(CACHE).withLoadDataInformation(loadDataInformation)
150+
.build();
151+
this.resources.put(loadDataInformation.getResourceId(), resource);
152+
153+
if (loadDataInformation.getLoaderListener() != null) {
154+
this.addLoaderListener(loadDataInformation.getLoaderListener(), loadDataInformation.getResourceId());
155+
}
156+
157+
if(loadDataInformation.isStartRemote()) {
158+
defaultLoaderServiceFacade.loadDataRemote(loadDataInformation.getResourceId(), resources);
159+
}
160+
switch (loadDataInformation.getUpdatePolicy()) {
161+
case NEVER:
162+
loadDataLocal(loadDataInformation.getResourceId());
163+
break;
164+
case ONSTARTUP:
165+
loadDataAsync(loadDataInformation.getResourceId());
166+
break;
167+
case SCHEDULED:
168+
defaultLoaderServiceFacade.scheduledData(resource);
169+
break;
170+
case LAZY:
171+
default:
172+
break;
173+
}
174+
}
175+
176+
@Override
177+
public void registerAndLoadData(LoadDataInformation loadDataInformation) {
178+
registerData(loadDataInformation);
179+
loadData(loadDataInformation.getResourceId());
180+
}
181+
182+
@Override
183+
public void registerAndLoadData(String resourceId, UpdatePolicy updatePolicy, Map<String, String> properties, LoaderListener loaderListener, URI backupResource, URI... resourceLocations) {
184+
registerAndLoadData(new LoadDataInformationBuilder()
185+
.withResourceId(resourceId)
186+
.withUpdatePolicy(updatePolicy)
187+
.withProperties(properties)
188+
.withLoaderListener(loaderListener)
189+
.withBackupResource(backupResource)
190+
.withResourceLocations(resourceLocations)
191+
.build());
192+
}
193+
194+
@Override
195+
public void registerData(String resourceId, UpdatePolicy updatePolicy, Map<String, String> properties, LoaderListener loaderListener, URI backupResource, URI... resourceLocations) {
196+
if (resources.containsKey(resourceId)) {
197+
throw new IllegalArgumentException("Resource : " + resourceId + " already registered.");
198+
}
199+
LoadDataInformation loadInfo = new LoadDataInformationBuilder()
200+
.withResourceId(resourceId)
201+
.withUpdatePolicy(updatePolicy)
202+
.withProperties(properties)
203+
.withLoaderListener(loaderListener)
204+
.withBackupResource(backupResource)
205+
.withResourceLocations(resourceLocations)
206+
.build();
207+
208+
LoadableHttpResource resource = new LoadableHttpResourceBuilder()
209+
.withCache(CACHE).withLoadDataInformation(loadInfo)
210+
.build();
211+
this.resources.put(loadInfo.getResourceId(), resource);
212+
213+
if (loadInfo.getLoaderListener() != null) {
214+
this.addLoaderListener(loadInfo.getLoaderListener(), loadInfo.getResourceId());
215+
}
216+
217+
switch (loadInfo.getUpdatePolicy()) {
218+
case SCHEDULED:
219+
defaultLoaderServiceFacade.scheduledData(resource);
220+
break;
221+
case LAZY:
222+
default:
223+
break;
224+
}
225+
}
226+
227+
@Override
228+
public Map<String, String> getUpdateConfiguration(String resourceId) {
229+
LoadableHttpResource load = this.resources.get(resourceId);
230+
if (Objects.nonNull(load)) {
231+
return load.getProperties();
232+
}
233+
return null;
234+
}
235+
236+
@Override
237+
public boolean isResourceRegistered(String resourceId) {
238+
return this.resources.containsKey(resourceId);
239+
}
240+
241+
@Override
242+
public Set<String> getResourceIds() {
243+
return this.resources.keySet();
244+
}
245+
246+
@Override
247+
public InputStream getData(String resourceId) throws IOException {
248+
LoadableHttpResource load = this.resources.get(resourceId);
249+
if (Objects.nonNull(load)) {
250+
return load.getDataStream();
251+
}
252+
throw new IllegalArgumentException("No such resource: " + resourceId);
253+
}
254+
255+
@Override
256+
public boolean loadData(String resourceId) {
257+
return defaultLoaderServiceFacade.loadData(resourceId, resources);
258+
}
259+
260+
@Override
261+
public Future<Boolean> loadDataAsync(final String resourceId) {
262+
return executors.submit(() -> defaultLoaderServiceFacade.loadData(resourceId, resources));
263+
}
264+
265+
@Override
266+
public boolean loadDataLocal(String resourceId) {
267+
return defaultLoaderServiceFacade.loadDataLocal(resourceId);
268+
}
269+
270+
271+
@Override
272+
public void resetData(String resourceId) throws IOException {
273+
LoadableHttpResource load = Optional.ofNullable(this.resources.get(resourceId))
274+
.orElseThrow(() -> new IllegalArgumentException("No such resource: " + resourceId));
275+
if (load.resetToFallback()) {
276+
listener.trigger(resourceId, load);
277+
}
278+
}
279+
280+
@Override
281+
public void addLoaderListener(LoaderListener l, String... resourceIds) {
282+
if (resourceIds.length == 0) {
283+
List<LoaderListener> listeners = listener.getListeners("");
284+
synchronized (listeners) {
285+
listeners.add(l);
286+
}
287+
} else {
288+
for (String dataId : resourceIds) {
289+
List<LoaderListener> listeners = listener.getListeners(dataId);
290+
synchronized (listeners) {
291+
listeners.add(l);
292+
}
293+
}
294+
}
295+
}
296+
297+
@Override
298+
public void removeLoaderListener(LoaderListener loadListener, String... resourceIds) {
299+
if (resourceIds.length == 0) {
300+
List<LoaderListener> listeners = listener.getListeners("");
301+
synchronized (listeners) {
302+
listeners.remove(loadListener);
303+
}
304+
} else {
305+
for (String dataId : resourceIds) {
306+
List<LoaderListener> listeners = listener.getListeners(dataId);
307+
synchronized (listeners) {
308+
listeners.remove(loadListener);
309+
}
310+
}
311+
}
312+
}
313+
314+
@Override
315+
public UpdatePolicy getUpdatePolicy(String resourceId) {
316+
LoadableHttpResource load = Optional.of(this.resources.get(resourceId))
317+
.orElseThrow(() -> new IllegalArgumentException("No such resource: " + resourceId));
318+
return load.getUpdatePolicy();
319+
}
320+
321+
@Override
322+
public String toString() {
323+
return "URLConnectionLoaderService [resources=" + resources + ']';
324+
}
325+
}

0 commit comments

Comments
 (0)