• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanodash / 17611181855

10 Sep 2025 10:40AM UTC coverage: 14.17% (+0.1%) from 14.059%
17611181855

push

github

ashleycaselli
refactor: update API response retrieval using QueryRef

431 of 3880 branches covered (11.11%)

Branch coverage included in aggregate %.

1112 of 7009 relevant lines covered (15.87%)

0.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
src/main/java/com/knowledgepixels/nanodash/ApiCache.java
1
package com.knowledgepixels.nanodash;
2

3
import org.nanopub.extra.services.*;
4
import org.slf4j.Logger;
5
import org.slf4j.LoggerFactory;
6

7
import java.util.*;
8
import java.util.concurrent.ConcurrentHashMap;
9
import java.util.concurrent.ConcurrentMap;
10

11
/**
12
 * A utility class for caching API responses and maps to reduce redundant API calls.
13
 * This class is thread-safe and ensures that cached data is refreshed periodically.
14
 */
15
public class ApiCache {
16

17
    private ApiCache() {
18
    } // no instances allowed
19

20
    private transient static ConcurrentMap<String, ApiResponse> cachedResponses = new ConcurrentHashMap<>();
×
21
    private transient static ConcurrentMap<String, Map<String, String>> cachedMaps = new ConcurrentHashMap<>();
×
22
    private transient static ConcurrentMap<String, Long> lastRefresh = new ConcurrentHashMap<>();
×
23
    private transient static ConcurrentMap<String, Long> refreshStart = new ConcurrentHashMap<>();
×
24
    private static final Logger logger = LoggerFactory.getLogger(ApiCache.class);
×
25

26
    /**
27
     * Checks if a cache refresh is currently running for the given cache ID.
28
     *
29
     * @param cacheId The unique identifier for the cache.
30
     * @return True if a refresh is running, false otherwise.
31
     */
32
    private static boolean isRunning(String cacheId) {
33
        if (!refreshStart.containsKey(cacheId)) return false;
×
34
        return System.currentTimeMillis() - refreshStart.get(cacheId) < 60 * 1000;
×
35
    }
36

37
    public static boolean isRunning(QueryRef queryRef) {
38
        return isRunning(queryRef.getName(), queryRef.getParams());
×
39
    }
40

41
    /**
42
     * Checks if a cache refresh is running for a specific query and parameters.
43
     *
44
     * @param queryName The name of the query.
45
     * @param params    The parameters for the query.
46
     * @return True if a refresh is running, false otherwise.
47
     */
48
    public static boolean isRunning(String queryName, Map<String, String> params) {
49
        return isRunning(getCacheId(queryName, params));
×
50
    }
51

52
    /**
53
     * Checks if a cache refresh is running for a specific query and a single parameter.
54
     *
55
     * @param queryName  The name of the query.
56
     * @param paramName  The name of the parameter.
57
     * @param paramValue The value of the parameter.
58
     * @return True if a refresh is running, false otherwise.
59
     */
60
    public static boolean isRunning(String queryName, String paramName, String paramValue) {
61
        Map<String, String> params = new HashMap<>();
×
62
        params.put(paramName, paramValue);
×
63
        return isRunning(getCacheId(queryName, params));
×
64
    }
65

66
    /**
67
     * Updates the cached API response for a specific query and parameters.
68
     *
69
     * @param queryName The name of the query.
70
     * @param params    The parameters for the query.
71
     * @throws FailedApiCallException If the API call fails.
72
     */
73
    private static void updateResponse(String queryName, Map<String, String> params) throws FailedApiCallException, APINotReachableException, NotEnoughAPIInstancesException {
74
        Map<String, String> nanopubParams = new HashMap<>();
×
75
        for (String k : params.keySet()) nanopubParams.put(k, params.get(k));
×
76
        ApiResponse response = QueryApiAccess.get(queryName, nanopubParams);
×
77
        String cacheId = getCacheId(queryName, params);
×
78
        cachedResponses.put(cacheId, response);
×
79
        lastRefresh.put(cacheId, System.currentTimeMillis());
×
80
    }
×
81

82
    /**
83
     * Retrieves a cached API response for a specific query and a single parameter.
84
     *
85
     * @param queryName  The name of the query.
86
     * @param paramName  The name of the parameter.
87
     * @param paramValue The value of the parameter.
88
     * @return The cached API response, or null if not cached.
89
     */
90
    public static ApiResponse retrieveResponse(String queryName, String paramName, String paramValue) {
91
        Map<String, String> params = new HashMap<>();
×
92
        params.put(paramName, paramValue);
×
93
        return retrieveResponse(queryName, params);
×
94
    }
95

96
    /**
97
     * Retrieves a cached API response for a specific QueryRef.
98
     *
99
     * @param queryRef The QueryRef object containing the query name and parameters.
100
     * @return The cached API response, or null if not cached.
101
     */
102
    public static ApiResponse retrieveResponse(QueryRef queryRef) {
103
        return retrieveResponse(queryRef.getName(), queryRef.getParams());
×
104
    }
105

106
    /**
107
     * Retrieves a cached API response for a specific query and parameters.
108
     * If the cache is stale, it triggers a background refresh.
109
     *
110
     * @param queryName The name of the query.
111
     * @param params    The parameters for the query.
112
     * @return The cached API response, or null if not cached.
113
     */
114
    public static synchronized ApiResponse retrieveResponse(final String queryName, final Map<String, String> params) {
115
        long timeNow = System.currentTimeMillis();
×
116
        String cacheId = getCacheId(queryName, params);
×
117
        boolean isCached = false;
×
118
        boolean needsRefresh = true;
×
119
        if (cachedResponses.containsKey(cacheId) && cachedResponses.get(cacheId) != null) {
×
120
            long cacheAge = timeNow - lastRefresh.get(cacheId);
×
121
            isCached = cacheAge < 24 * 60 * 60 * 1000;
×
122
            needsRefresh = cacheAge > 60 * 1000;
×
123
        }
124
        if (needsRefresh && !isRunning(cacheId)) {
×
125
            refreshStart.put(cacheId, timeNow);
×
126
            new Thread(() -> {
×
127
                try {
128
                    Thread.sleep(100 + new Random().nextLong(400));
×
129
                } catch (InterruptedException ex) {
×
130
                    logger.error("Interrupted while waiting to refresh cache: {}", ex.getMessage());
×
131
                }
×
132
                try {
133
                    ApiCache.updateResponse(queryName, params);
×
134
                } catch (Exception ex) {
×
135
                    logger.error("Failed to update cache for {}: {}", cacheId, ex.getMessage());
×
136
                    cachedResponses.put(cacheId, null);
×
137
                    lastRefresh.put(cacheId, System.currentTimeMillis());
×
138
                } finally {
139
                    refreshStart.remove(cacheId);
×
140
                }
141
            }).start();
×
142
        }
143
        if (isCached) {
×
144
            if (cachedResponses.get(cacheId) == null) {
×
145
                cachedResponses.remove(cacheId);
×
146
                throw new RuntimeException("Query failed: " + cacheId);
×
147
            }
148
            return cachedResponses.get(cacheId);
×
149
        } else {
150
            return null;
×
151
        }
152
    }
153

154
    /**
155
     * Updates the cached map for a specific query and parameters.
156
     *
157
     * @param queryName The name of the query.
158
     * @param params    The parameters for the query.
159
     * @throws FailedApiCallException If the API call fails.
160
     */
161
    private static void updateMap(String queryName, Map<String, String> params) throws FailedApiCallException, APINotReachableException, NotEnoughAPIInstancesException {
162
        Map<String, String> map = new HashMap<>();
×
163
        Map<String, String> nanopubParams = new HashMap<>();
×
164
        for (String k : params.keySet()) nanopubParams.put(k, params.get(k));
×
165
        List<ApiResponseEntry> respList = QueryApiAccess.get(queryName, nanopubParams).getData();
×
166
        while (respList != null && !respList.isEmpty()) {
×
167
            ApiResponseEntry resultEntry = respList.remove(0);
×
168
            map.put(resultEntry.get("key"), resultEntry.get("value"));
×
169
        }
×
170
        String cacheId = getCacheId(queryName, params);
×
171
        cachedMaps.put(cacheId, map);
×
172
        lastRefresh.put(cacheId, System.currentTimeMillis());
×
173
    }
×
174

175
    /**
176
     * Retrieves a cached map for a specific query and parameters.
177
     * If the cache is stale, it triggers a background refresh.
178
     *
179
     * @param queryName The name of the query.
180
     * @param params    The parameters for the query.
181
     * @return The cached map, or null if not cached.
182
     */
183
    public static synchronized Map<String, String> retrieveMap(String queryName, Map<String, String> params) {
184
        long timeNow = System.currentTimeMillis();
×
185
        String cacheId = getCacheId(queryName, params);
×
186
        boolean isCached = false;
×
187
        boolean needsRefresh = true;
×
188
        if (cachedMaps.containsKey(cacheId)) {
×
189
            long cacheAge = timeNow - lastRefresh.get(cacheId);
×
190
            isCached = cacheAge < 24 * 60 * 60 * 1000;
×
191
            needsRefresh = cacheAge > 60 * 1000;
×
192
        }
193
        if (needsRefresh && !isRunning(cacheId)) {
×
194
            refreshStart.put(cacheId, timeNow);
×
195
            new Thread(() -> {
×
196
                try {
197
                    Thread.sleep(100 + new Random().nextLong(400));
×
198
                } catch (InterruptedException ex) {
×
199
                    logger.error("Interrupted while waiting to refresh cache: {}", ex.getMessage());
×
200
                }
×
201
                try {
202
                    ApiCache.updateMap(queryName, params);
×
203
                } catch (Exception ex) {
×
204
                    logger.error("Failed to update cache for {}: {}", cacheId, ex.getMessage());
×
205
                    cachedResponses.put(cacheId, null);
×
206
                    lastRefresh.put(cacheId, System.currentTimeMillis());
×
207
                } finally {
208
                    refreshStart.remove(cacheId);
×
209
                }
210
            }).start();
×
211
        }
212
        if (isCached) {
×
213
            if (cachedResponses.get(cacheId) == null) {
×
214
                cachedResponses.remove(cacheId);
×
215
                throw new RuntimeException("Query failed: " + cacheId);
×
216
            }
217
            return cachedMaps.get(cacheId);
×
218
        } else {
219
            return null;
×
220
        }
221
    }
222

223
    /**
224
     * Converts a map of parameters to a sorted string representation.
225
     *
226
     * @param params The map of parameters.
227
     * @return A string representation of the parameters.
228
     */
229
    private static String paramsToString(Map<String, String> params) {
230
        List<String> keys = new ArrayList<>(params.keySet());
×
231
        Collections.sort(keys);
×
232
        String s = "";
×
233
        for (String k : keys) s += " " + k + "=" + params.get(k);
×
234
        return s;
×
235
    }
236

237
    /**
238
     * Generates a unique cache ID for a specific query and parameters.
239
     *
240
     * @param queryName The name of the query.
241
     * @param params    The parameters for the query.
242
     * @return The unique cache ID.
243
     */
244
    public static String getCacheId(String queryName, Map<String, String> params) {
245
        return queryName + " " + paramsToString(params);
×
246
    }
247

248
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc