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

knowledgepixels / nanodash / 23341874583

20 Mar 2026 12:00PM UTC coverage: 16.379% (+0.05%) from 16.327%
23341874583

Pull #410

github

web-flow
Merge a84d7b4d5 into 3faec6ac7
Pull Request #410: fix: update Space and MaintainedResource data when root nanopub changes

731 of 5533 branches covered (13.21%)

Branch coverage included in aggregate %.

1877 of 10390 relevant lines covered (18.07%)

2.47 hits per line

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

67.47
src/main/java/com/knowledgepixels/nanodash/repository/MaintainedResourceRepository.java
1
package com.knowledgepixels.nanodash.repository;
2

3
import com.knowledgepixels.nanodash.ApiCache;
4
import com.knowledgepixels.nanodash.QueryApiAccess;
5
import com.knowledgepixels.nanodash.domain.MaintainedResource;
6
import com.knowledgepixels.nanodash.domain.MaintainedResourceFactory;
7
import com.knowledgepixels.nanodash.domain.Space;
8
import org.nanopub.extra.services.ApiResponse;
9
import org.nanopub.extra.services.ApiResponseEntry;
10
import org.nanopub.extra.services.QueryRef;
11
import org.slf4j.Logger;
12
import org.slf4j.LoggerFactory;
13

14
import java.util.ArrayList;
15
import java.util.HashMap;
16
import java.util.List;
17
import java.util.Map;
18

19
public class MaintainedResourceRepository {
20

21
    private static final Logger logger = LoggerFactory.getLogger(MaintainedResourceRepository.class);
9✔
22

23
    private static final MaintainedResourceRepository INSTANCE = new MaintainedResourceRepository();
15✔
24

25
    private volatile List<MaintainedResource> resourceList;
26
    private Map<String, MaintainedResource> resourcesById;
27
    private Map<String, MaintainedResource> resourcesByNamespace;
28
    private Map<Space, List<MaintainedResource>> resourcesBySpace;
29
    private boolean loaded = false;
9✔
30
    private volatile Long runRootUpdateAfter = null;
9✔
31
    private volatile long lastRefreshTime = 0;
9✔
32
    private final Object loadLock = new Object();
15✔
33

34
    /**
35
     * Get the singleton instance of MaintainedResourceRepository.
36
     *
37
     * @return The singleton instance of MaintainedResourceRepository.
38
     */
39
    public static MaintainedResourceRepository get() {
40
        return INSTANCE;
6✔
41
    }
42

43
    private MaintainedResourceRepository() {
6✔
44
    }
3✔
45

46
    /**
47
     * Refresh the list of maintained resources from the API response, updating the internal state accordingly.
48
     *
49
     * @param resp The API response containing maintained resource data.
50
     */
51
    public synchronized void refresh(ApiResponse resp) {
52
        List<MaintainedResource> newResourceList = new ArrayList<>();
12✔
53
        Map<String, MaintainedResource> newResourcesById = new HashMap<>();
12✔
54
        Map<Space, List<MaintainedResource>> newResourcesBySpace = new HashMap<>();
12✔
55
        Map<String, MaintainedResource> newResourcesByNamespace = new HashMap<>();
12✔
56
        for (ApiResponseEntry entry : resp.getData()) {
33✔
57
            Space space = SpaceRepository.get().findById(entry.get("space"));
18✔
58
            if (space == null) {
6!
59
                continue;
×
60
            }
61
            MaintainedResource resource = MaintainedResourceFactory.getOrCreate(entry, space);
12✔
62
            if (newResourcesById.containsKey(resource.getId())) {
15✔
63
                continue;
3✔
64
            }
65
            newResourceList.add(resource);
12✔
66
            newResourcesById.put(resource.getId(), resource);
18✔
67
            newResourcesBySpace.computeIfAbsent(space, k -> new ArrayList<>()).add(resource);
36✔
68
            if (resource.getNamespace() != null) {
9✔
69
                // TODO Handle conflicts when two resources claim the same namespace:
70
                newResourcesByNamespace.put(resource.getNamespace(), resource);
18✔
71
            }
72
        }
3✔
73
        MaintainedResourceFactory.removeStale(newResourcesById.keySet());
9✔
74
        resourcesById = newResourcesById;
9✔
75
        resourcesBySpace = newResourcesBySpace;
9✔
76
        resourcesByNamespace = newResourcesByNamespace;
9✔
77
        loaded = true;
9✔
78
        lastRefreshTime = System.currentTimeMillis();
9✔
79
        resourceList = newResourceList; // volatile write last — establishes happens-before for all above
9✔
80
    }
3✔
81

82
    /**
83
     * Find a maintained resource by its namespace.
84
     *
85
     * @param namespace The namespace to search for.
86
     * @return The MaintainedResource with the given namespace, or null if not found.
87
     */
88
    public MaintainedResource findByNamespace(String namespace) {
89
        return resourcesByNamespace.get(namespace);
×
90
    }
91

92
    /**
93
     * Find a maintained resource by its ID.
94
     *
95
     * @param space The space to which the resource belongs.
96
     * @return The MaintainedResource with the given ID, or null if not found.
97
     */
98
    public List<MaintainedResource> findResourcesBySpace(Space space) {
99
        return resourcesBySpace.computeIfAbsent(space, k -> new ArrayList<>());
×
100
    }
101

102
    /**
103
     * Get a maintained resource by its id.
104
     *
105
     * @param id The id of the resource.
106
     * @return The corresponding MaintainedResource object, or null if not found.
107
     */
108
    public MaintainedResource findById(String id) {
109
        ensureLoaded();
6✔
110
        return resourcesById.get(id);
18✔
111
    }
112

113
    /**
114
     * Ensure that the resources are loaded, fetching them from the API if necessary.
115
     */
116
    public void ensureLoaded() {
117
        if (resourceList == null) {
9✔
118
            try {
119
                synchronized (loadLock) {
15✔
120
                    if (runRootUpdateAfter != null) {
9!
121
                        while (runRootUpdateAfter != null && System.currentTimeMillis() < runRootUpdateAfter) {
×
122
                            Thread.sleep(100);
×
123
                        }
124
                        runRootUpdateAfter = null;
×
125
                    }
126
                }
9✔
127
            } catch (InterruptedException ex) {
×
128
                logger.error("Interrupted", ex);
×
129
            }
3✔
130
            if (resourceList == null) { // double-check after potential wait
9!
131
                refresh(ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_MAINTAINED_RESOURCES), true));
27✔
132
            }
133
        } else if (System.currentTimeMillis() - lastRefreshTime > 60_000) {
21!
134
            refresh(ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_MAINTAINED_RESOURCES), true));
×
135
        }
136
    }
3✔
137

138
    /**
139
     * Force a refresh of the maintained resources on the next access, with an optional delay to allow for updates to propagate.
140
     *
141
     * @param waitMillis Number of milliseconds to wait before allowing the next access to trigger a refresh. If 0, the refresh will happen immediately on the next access.
142
     */
143
    public void forceRootRefresh(long waitMillis) {
144
        resourceList = null;
×
145
        runRootUpdateAfter = System.currentTimeMillis() + waitMillis;
×
146
    }
×
147

148
    /**
149
     * Refresh the maintained resources by fetching the latest data from the API and updating the internal state accordingly.
150
     */
151
    public void refresh() {
152
        refresh(ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_MAINTAINED_RESOURCES), true));
×
153
        for (MaintainedResource resource : resourceList) {
×
154
            resource.setDataNeedsUpdate();
×
155
        }
×
156
    }
×
157

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