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

knowledgepixels / nanodash / 26281741789

22 May 2026 10:10AM UTC coverage: 20.915% (+0.2%) from 20.748%
26281741789

Pull #468

github

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

1040 of 6284 branches covered (16.55%)

Branch coverage included in aggregate %.

2672 of 11464 relevant lines covered (23.31%)

3.33 hits per line

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

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

3
import com.knowledgepixels.nanodash.SpacesRepoAccess;
4
import com.knowledgepixels.nanodash.domain.MaintainedResource;
5
import com.knowledgepixels.nanodash.domain.MaintainedResourceFactory;
6
import com.knowledgepixels.nanodash.domain.Space;
7
import org.nanopub.extra.services.ApiResponseEntry;
8
import org.slf4j.Logger;
9
import org.slf4j.LoggerFactory;
10

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

18
public class MaintainedResourceRepository {
19

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

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

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

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

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

45
    // OPTIONAL pulled outside the GRAPH ?a wrapper: `GRAPH ?a { OPTIONAL { ... } }`
46
    // returns zero solutions when no triple in ?a matches the inner pattern, which
47
    // would drop every resource that has no gen:hasNamespace.
48
    private static final String MAINTAINED_RESOURCES_QUERY = SpacesRepoAccess.PREFIXES
49
            + "SELECT ?resource ?space ?np ?label ?namespace ?date WHERE {\n"
50
            + SpacesRepoAccess.CURRENT_STATE_POINTER
51
            + "  GRAPH ?g { ?resource npa:isMaintainedBy ?space . }\n"
52
            + "  GRAPH npa:spacesGraph {\n"
53
            + "    ?d a npa:MaintainedResourceDeclaration ;\n"
54
            + "       npa:resourceIri ?resource ;\n"
55
            + "       npa:maintainerSpace ?space ;\n"
56
            + "       npa:viaNanopub ?np ;\n"
57
            + "       dct:created ?date .\n"
58
            + "  }\n"
59
            + "  GRAPH npa:graph {\n"
60
            + "    ?np np:hasAssertion ?a .\n"
61
            + "    OPTIONAL { ?np rdfs:label ?label }\n"
62
            + "  }\n"
63
            + "  OPTIONAL { GRAPH ?a { ?resource gen:hasNamespace ?namespace } }\n"
64
            + "} ORDER BY DESC(?date)";
65

66
    /**
67
     * Refresh the list of maintained resources from the spaces repo. Pulls
68
     * server-validated {@code npa:isMaintainedBy} links from the current
69
     * space-state graph; only the most recent declaration per resource is kept.
70
     */
71
    public synchronized void refresh() {
72
        List<MaintainedResource> newResourceList = new ArrayList<>();
12✔
73
        Map<String, MaintainedResource> newResourcesById = new HashMap<>();
12✔
74
        Map<Space, List<MaintainedResource>> newResourcesBySpace = new HashMap<>();
12✔
75
        Map<String, MaintainedResource> newResourcesByNamespace = new HashMap<>();
12✔
76
        Set<String> seenResources = new HashSet<>();
12✔
77
        SpacesRepoAccess.get().select(MAINTAINED_RESOURCES_QUERY, null, b -> {
33✔
78
            String resourceId = b.getValue("resource").stringValue();
15✔
79
            if (!seenResources.add(resourceId)) return null; // first row (newest date) wins
18✔
80
            String spaceId = b.getValue("space").stringValue();
15✔
81
            Space space = SpaceRepository.get().findById(spaceId);
12✔
82
            if (space == null) return null;
6!
83
            ApiResponseEntry entry = new ApiResponseEntry();
12✔
84
            entry.add("resource", resourceId);
12✔
85
            entry.add("np", b.getValue("np") == null ? null : b.getValue("np").stringValue());
33!
86
            if (b.getValue("label") != null) entry.add("label", b.getValue("label").stringValue());
33!
87
            if (b.getValue("namespace") != null) entry.add("namespace", b.getValue("namespace").stringValue());
33✔
88
            MaintainedResource resource = MaintainedResourceFactory.getOrCreate(entry, space);
12✔
89
            newResourceList.add(resource);
12✔
90
            newResourcesById.put(resourceId, resource);
15✔
91
            newResourcesBySpace.computeIfAbsent(space, k -> new ArrayList<>()).add(resource);
36✔
92
            if (resource.getNamespace() != null) {
9✔
93
                // TODO Handle conflicts when two resources claim the same namespace:
94
                newResourcesByNamespace.put(resource.getNamespace(), resource);
18✔
95
            }
96
            return null;
6✔
97
        });
98
        MaintainedResourceFactory.removeStale(newResourcesById.keySet());
9✔
99
        resourcesById = newResourcesById;
9✔
100
        resourcesBySpace = newResourcesBySpace;
9✔
101
        resourcesByNamespace = newResourcesByNamespace;
9✔
102
        loaded = true;
9✔
103
        lastRefreshTime = System.currentTimeMillis();
9✔
104
        resourceList = newResourceList; // volatile write last — establishes happens-before for all above
9✔
105
    }
3✔
106

107
    /**
108
     * Find a maintained resource by its namespace.
109
     *
110
     * @param namespace The namespace to search for.
111
     * @return The MaintainedResource with the given namespace, or null if not found.
112
     */
113
    public MaintainedResource findByNamespace(String namespace) {
114
        ensureLoaded();
×
115
        return resourcesByNamespace.get(namespace);
×
116
    }
117

118
    /**
119
     * Find the maintained resources belonging to a given space.
120
     *
121
     * @param space The space to look up.
122
     * @return The list of maintained resources for the space, possibly empty.
123
     */
124
    public List<MaintainedResource> findResourcesBySpace(Space space) {
125
        ensureLoaded();
×
126
        return resourcesBySpace.computeIfAbsent(space, k -> new ArrayList<>());
×
127
    }
128

129
    /**
130
     * Get a maintained resource by its id.
131
     *
132
     * @param id The id of the resource.
133
     * @return The corresponding MaintainedResource object, or null if not found.
134
     */
135
    public MaintainedResource findById(String id) {
136
        ensureLoaded();
6✔
137
        return resourcesById.get(id);
18✔
138
    }
139

140
    /**
141
     * Ensure that the resources are loaded, fetching them from the API if necessary.
142
     */
143
    public void ensureLoaded() {
144
        if (resourceList == null) {
9✔
145
            try {
146
                synchronized (loadLock) {
15✔
147
                    if (runRootUpdateAfter != null) {
9!
148
                        while (runRootUpdateAfter != null && System.currentTimeMillis() < runRootUpdateAfter) {
×
149
                            Thread.sleep(100);
×
150
                        }
151
                        runRootUpdateAfter = null;
×
152
                    }
153
                }
9✔
154
            } catch (InterruptedException ex) {
×
155
                logger.error("Interrupted", ex);
×
156
            }
3✔
157
            if (resourceList == null) { // double-check after potential wait
9!
158
                refresh();
9✔
159
            }
160
        } else if (System.currentTimeMillis() - lastRefreshTime > 60_000) {
21!
161
            refresh();
×
162
        }
163
    }
3✔
164

165
    /**
166
     * Force a refresh of the maintained resources on the next access, with an optional delay to allow for updates to propagate.
167
     *
168
     * @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.
169
     */
170
    public void forceRootRefresh(long waitMillis) {
171
        resourceList = null;
×
172
        runRootUpdateAfter = System.currentTimeMillis() + waitMillis;
×
173
    }
×
174

175
    /**
176
     * Refresh the maintained resources and mark each as needing a downstream data update.
177
     */
178
    public void refreshAndInvalidate() {
179
        refresh();
×
180
        for (MaintainedResource resource : resourceList) {
×
181
            resource.setDataNeedsUpdate();
×
182
        }
×
183
    }
×
184

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