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

knowledgepixels / nanodash / 26455055595

26 May 2026 02:38PM UTC coverage: 20.427% (-0.3%) from 20.748%
26455055595

Pull #468

github

web-flow
Merge 0354b3eb9 into 65b0c8452
Pull Request #468: Source space data from nanopub-query spaces repo

1005 of 6260 branches covered (16.05%)

Branch coverage included in aggregate %.

2600 of 11388 relevant lines covered (22.83%)

3.27 hits per line

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

68.87
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.ApiResponseEntry;
9
import org.nanopub.extra.services.QueryRef;
10
import org.slf4j.Logger;
11
import org.slf4j.LoggerFactory;
12

13
import java.util.ArrayList;
14
import java.util.HashMap;
15
import java.util.HashSet;
16
import java.util.List;
17
import java.util.Map;
18
import java.util.Set;
19

20
public class MaintainedResourceRepository {
21

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

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

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

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

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

47
    /**
48
     * Refresh the list of maintained resources from the spaces repo. Pulls
49
     * server-validated {@code npa:isMaintainedBy} links from the current
50
     * space-state graph; only the most recent declaration per resource is kept
51
     * (the {@code get-maintained-resources} query orders by {@code DESC(?date)},
52
     * so the first row per resource wins).
53
     */
54
    public synchronized void refresh() {
55
        List<MaintainedResource> newResourceList = new ArrayList<>();
12✔
56
        Map<String, MaintainedResource> newResourcesById = new HashMap<>();
12✔
57
        Map<Space, List<MaintainedResource>> newResourcesBySpace = new HashMap<>();
12✔
58
        Map<String, MaintainedResource> newResourcesByNamespace = new HashMap<>();
12✔
59
        Set<String> seenResources = new HashSet<>();
12✔
60
        for (ApiResponseEntry r : ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_MAINTAINED_RESOURCES), true).getData()) {
48✔
61
            String resourceId = r.get("resource");
12✔
62
            if (resourceId == null || resourceId.isEmpty()) continue;
15!
63
            if (!seenResources.add(resourceId)) continue; // first row (newest date) wins
15✔
64
            String spaceId = r.get("space");
12✔
65
            Space space = SpaceRepository.get().findById(spaceId);
12✔
66
            if (space == null) continue;
6!
67
            ApiResponseEntry entry = new ApiResponseEntry();
12✔
68
            entry.add("resource", resourceId);
12✔
69
            entry.add("np", r.get("np"));
18✔
70
            String label = r.get("label");
12✔
71
            if (label != null && !label.isEmpty()) entry.add("label", label);
27!
72
            String namespace = r.get("namespace");
12✔
73
            if (namespace != null && !namespace.isEmpty()) entry.add("namespace", namespace);
27!
74
            MaintainedResource resource = MaintainedResourceFactory.getOrCreate(entry, space);
12✔
75
            newResourceList.add(resource);
12✔
76
            newResourcesById.put(resourceId, resource);
15✔
77
            newResourcesBySpace.computeIfAbsent(space, k -> new ArrayList<>()).add(resource);
36✔
78
            if (resource.getNamespace() != null) {
9✔
79
                // TODO Handle conflicts when two resources claim the same namespace:
80
                newResourcesByNamespace.put(resource.getNamespace(), resource);
18✔
81
            }
82
        }
3✔
83
        MaintainedResourceFactory.removeStale(newResourcesById.keySet());
9✔
84
        resourcesById = newResourcesById;
9✔
85
        resourcesBySpace = newResourcesBySpace;
9✔
86
        resourcesByNamespace = newResourcesByNamespace;
9✔
87
        loaded = true;
9✔
88
        lastRefreshTime = System.currentTimeMillis();
9✔
89
        resourceList = newResourceList; // volatile write last — establishes happens-before for all above
9✔
90
    }
3✔
91

92
    /**
93
     * Find a maintained resource by its namespace.
94
     *
95
     * @param namespace The namespace to search for.
96
     * @return The MaintainedResource with the given namespace, or null if not found.
97
     */
98
    public MaintainedResource findByNamespace(String namespace) {
99
        ensureLoaded();
×
100
        return resourcesByNamespace.get(namespace);
×
101
    }
102

103
    /**
104
     * Find the maintained resources belonging to a given space.
105
     *
106
     * @param space The space to look up.
107
     * @return The list of maintained resources for the space, possibly empty.
108
     */
109
    public List<MaintainedResource> findResourcesBySpace(Space space) {
110
        ensureLoaded();
×
111
        return resourcesBySpace.computeIfAbsent(space, k -> new ArrayList<>());
×
112
    }
113

114
    /**
115
     * Get a maintained resource by its id.
116
     *
117
     * @param id The id of the resource.
118
     * @return The corresponding MaintainedResource object, or null if not found.
119
     */
120
    public MaintainedResource findById(String id) {
121
        ensureLoaded();
6✔
122
        return resourcesById.get(id);
18✔
123
    }
124

125
    /**
126
     * Ensure that the resources are loaded, fetching them from the API if necessary.
127
     */
128
    public void ensureLoaded() {
129
        if (resourceList == null) {
9✔
130
            try {
131
                synchronized (loadLock) {
15✔
132
                    if (runRootUpdateAfter != null) {
9!
133
                        while (runRootUpdateAfter != null && System.currentTimeMillis() < runRootUpdateAfter) {
×
134
                            Thread.sleep(100);
×
135
                        }
136
                        runRootUpdateAfter = null;
×
137
                    }
138
                }
9✔
139
            } catch (InterruptedException ex) {
×
140
                logger.error("Interrupted", ex);
×
141
            }
3✔
142
            if (resourceList == null) { // double-check after potential wait
9!
143
                refresh();
9✔
144
            }
145
        } else if (System.currentTimeMillis() - lastRefreshTime > 60_000) {
21!
146
            refresh();
×
147
        }
148
    }
3✔
149

150
    /**
151
     * Force a refresh of the maintained resources on the next access, with an optional delay to allow for updates to propagate.
152
     *
153
     * @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.
154
     */
155
    public void forceRootRefresh(long waitMillis) {
156
        resourceList = null;
×
157
        runRootUpdateAfter = System.currentTimeMillis() + waitMillis;
×
158
    }
×
159

160
    /**
161
     * Refresh the maintained resources and mark each as needing a downstream data update.
162
     */
163
    public void refreshAndInvalidate() {
164
        refresh();
×
165
        for (MaintainedResource resource : resourceList) {
×
166
            resource.setDataNeedsUpdate();
×
167
        }
×
168
    }
×
169

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