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

knowledgepixels / nanodash / 18155160356

01 Oct 2025 07:42AM UTC coverage: 13.788% (-0.03%) from 13.817%
18155160356

push

github

ashleycaselli
docs: add missing or update Javadoc annotations

445 of 4084 branches covered (10.9%)

Branch coverage included in aggregate %.

1155 of 7520 relevant lines covered (15.36%)

0.69 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.HashMap;
8
import java.util.List;
9
import java.util.Map;
10
import java.util.Random;
11
import java.util.concurrent.ConcurrentHashMap;
12
import java.util.concurrent.ConcurrentMap;
13

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

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

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

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

41
    /**
42
     * Checks if a cache refresh is currently running for the given QueryRef.
43
     *
44
     * @param queryRef The query reference
45
     * @return True if a refresh is running, false otherwise.
46
     */
47
    public static boolean isRunning(QueryRef queryRef) {
48
        return isRunning(queryRef.getAsUrlString());
×
49
    }
50

51
    /**
52
     * Updates the cached API response for a specific query reference.
53
     *
54
     * @param queryRef The query reference
55
     * @throws FailedApiCallException If the API call fails.
56
     */
57
    private static void updateResponse(QueryRef queryRef) throws FailedApiCallException, APINotReachableException, NotEnoughAPIInstancesException {
58
        ApiResponse response = QueryApiAccess.get(queryRef);
×
59
        String cacheId = queryRef.getAsUrlString();
×
60
        cachedResponses.put(cacheId, response);
×
61
        lastRefresh.put(cacheId, System.currentTimeMillis());
×
62
    }
×
63

64
    /**
65
     * Retrieves a cached API response for a specific QueryRef.
66
     *
67
     * @param queryRef The QueryRef object containing the query name and parameters.
68
     * @return The cached API response, or null if not cached.
69
     */
70
    public static ApiResponse retrieveResponse(QueryRef queryRef) {
71
        long timeNow = System.currentTimeMillis();
×
72
        String cacheId = queryRef.getAsUrlString();
×
73
        boolean isCached = false;
×
74
        boolean needsRefresh = true;
×
75
        if (cachedResponses.containsKey(cacheId) && cachedResponses.get(cacheId) != null) {
×
76
            long cacheAge = timeNow - lastRefresh.get(cacheId);
×
77
            isCached = cacheAge < 24 * 60 * 60 * 1000;
×
78
            needsRefresh = cacheAge > 60 * 1000;
×
79
        }
80
        if (failed.get(cacheId) != null) {
×
81
            cachedResponses.remove(cacheId);
×
82
            failed.remove(cacheId);
×
83
            throw new RuntimeException("Query failed: " + cacheId);
×
84
        }
85
        if (needsRefresh && !isRunning(cacheId)) {
×
86
            refreshStart.put(cacheId, timeNow);
×
87
            new Thread(() -> {
×
88
                try {
89
                    Thread.sleep(100 + new Random().nextLong(400));
×
90
                } catch (InterruptedException ex) {
×
91
                    logger.error("Interrupted while waiting to refresh cache: {}", ex.getMessage());
×
92
                }
×
93
                try {
94
                    ApiCache.updateResponse(queryRef);
×
95
                } catch (Exception ex) {
×
96
                    logger.error("Failed to update cache for {}: {}", cacheId, ex.getMessage());
×
97
                    cachedResponses.remove(cacheId);
×
98
                    failed.put(cacheId, true);
×
99
                    lastRefresh.put(cacheId, System.currentTimeMillis());
×
100
                } finally {
101
                    refreshStart.remove(cacheId);
×
102
                }
103
            }).start();
×
104
        }
105
        if (isCached) {
×
106
            return cachedResponses.get(cacheId);
×
107
        } else {
108
            return null;
×
109
        }
110
    }
111

112
    /**
113
     * Updates the cached map for a specific query reference.
114
     *
115
     * @param queryRef The query reference
116
     * @throws FailedApiCallException If the API call fails.
117
     */
118
    private static void updateMap(QueryRef queryRef) throws FailedApiCallException, APINotReachableException, NotEnoughAPIInstancesException {
119
        Map<String, String> map = new HashMap<>();
×
120
        List<ApiResponseEntry> respList = QueryApiAccess.get(queryRef).getData();
×
121
        while (respList != null && !respList.isEmpty()) {
×
122
            ApiResponseEntry resultEntry = respList.remove(0);
×
123
            map.put(resultEntry.get("key"), resultEntry.get("value"));
×
124
        }
×
125
        String cacheId = queryRef.getAsUrlString();
×
126
        cachedMaps.put(cacheId, map);
×
127
        lastRefresh.put(cacheId, System.currentTimeMillis());
×
128
    }
×
129

130
    /**
131
     * Retrieves a cached map for a specific query reference.
132
     * If the cache is stale, it triggers a background refresh.
133
     *
134
     * @param queryRef The query reference
135
     * @return The cached map, or null if not cached.
136
     */
137
    public static synchronized Map<String, String> retrieveMap(QueryRef queryRef) {
138
        long timeNow = System.currentTimeMillis();
×
139
        String cacheId = queryRef.getAsUrlString();
×
140
        boolean isCached = false;
×
141
        boolean needsRefresh = true;
×
142
        if (cachedMaps.containsKey(cacheId)) {
×
143
            long cacheAge = timeNow - lastRefresh.get(cacheId);
×
144
            isCached = cacheAge < 24 * 60 * 60 * 1000;
×
145
            needsRefresh = cacheAge > 60 * 1000;
×
146
        }
147
        if (needsRefresh && !isRunning(cacheId)) {
×
148
            refreshStart.put(cacheId, timeNow);
×
149
            new Thread(() -> {
×
150
                try {
151
                    Thread.sleep(100 + new Random().nextLong(400));
×
152
                } catch (InterruptedException ex) {
×
153
                    logger.error("Interrupted while waiting to refresh cache: {}", ex.getMessage());
×
154
                }
×
155
                try {
156
                    ApiCache.updateMap(queryRef);
×
157
                } catch (Exception ex) {
×
158
                    logger.error("Failed to update cache for {}: {}", cacheId, ex.getMessage());
×
159
                    cachedResponses.put(cacheId, null);
×
160
                    lastRefresh.put(cacheId, System.currentTimeMillis());
×
161
                } finally {
162
                    refreshStart.remove(cacheId);
×
163
                }
164
            }).start();
×
165
        }
166
        if (isCached) {
×
167
            if (cachedResponses.get(cacheId) == null) {
×
168
                cachedResponses.remove(cacheId);
×
169
                throw new RuntimeException("Query failed: " + cacheId);
×
170
            }
171
            return cachedMaps.get(cacheId);
×
172
        } else {
173
            return null;
×
174
        }
175
    }
176

177
}
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