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

knowledgepixels / nanodash / 17380144000

01 Sep 2025 02:12PM UTC coverage: 12.03% (+0.05%) from 11.978%
17380144000

push

github

ashleycaselli
refactor: replace printStackTrace with logger.error for better error handling

330 of 3850 branches covered (8.57%)

Branch coverage included in aggregate %.

958 of 6857 relevant lines covered (13.97%)

0.62 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.ApiResponse;
4
import org.nanopub.extra.services.ApiResponseEntry;
5
import org.nanopub.extra.services.FailedApiCallException;
6
import org.slf4j.Logger;
7
import org.slf4j.LoggerFactory;
8

9
import java.util.*;
10
import java.util.concurrent.ConcurrentHashMap;
11
import java.util.concurrent.ConcurrentMap;
12

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

19
    private ApiCache() {
20
    } // no instances allowed
21

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

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

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

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

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

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

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

142
    /**
143
     * Updates the cached map for a specific query and parameters.
144
     *
145
     * @param queryName The name of the query.
146
     * @param params    The parameters for the query.
147
     * @throws FailedApiCallException If the API call fails.
148
     */
149
    private static void updateMap(String queryName, Map<String, String> params) throws FailedApiCallException {
150
        Map<String, String> map = new HashMap<>();
×
151
        Map<String, String> nanopubParams = new HashMap<>();
×
152
        for (String k : params.keySet()) nanopubParams.put(k, params.get(k));
×
153
        List<ApiResponseEntry> respList = QueryApiAccess.get(queryName, nanopubParams).getData();
×
154
        while (respList != null && !respList.isEmpty()) {
×
155
            ApiResponseEntry resultEntry = respList.remove(0);
×
156
            map.put(resultEntry.get("key"), resultEntry.get("value"));
×
157
        }
×
158
        String cacheId = getCacheId(queryName, params);
×
159
        cachedMaps.put(cacheId, map);
×
160
        lastRefresh.put(cacheId, System.currentTimeMillis());
×
161
    }
×
162

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

211
    /**
212
     * Converts a map of parameters to a sorted string representation.
213
     *
214
     * @param params The map of parameters.
215
     * @return A string representation of the parameters.
216
     */
217
    private static String paramsToString(Map<String, String> params) {
218
        List<String> keys = new ArrayList<>(params.keySet());
×
219
        Collections.sort(keys);
×
220
        String s = "";
×
221
        for (String k : keys) s += " " + k + "=" + params.get(k);
×
222
        return s;
×
223
    }
224

225
    /**
226
     * Generates a unique cache ID for a specific query and parameters.
227
     *
228
     * @param queryName The name of the query.
229
     * @param params    The parameters for the query.
230
     * @return The unique cache ID.
231
     */
232
    public static String getCacheId(String queryName, Map<String, String> params) {
233
        return queryName + " " + paramsToString(params);
×
234
    }
235
}
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