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

knowledgepixels / nanodash / 26284735769

22 May 2026 11:18AM UTC coverage: 20.838% (+0.09%) from 20.748%
26284735769

Pull #468

github

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

1034 of 6284 branches covered (16.45%)

Branch coverage included in aggregate %.

2668 of 11482 relevant lines covered (23.24%)

3.32 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
    // TODO Replace this programmatically-built SPARQL with a published grlc
46
    // query template (like the constants in QueryApiAccess), so all Nanopub
47
    // Query access goes through the same query-template pipeline.
48
    //
49
    // OPTIONAL pulled outside the GRAPH ?a wrapper: `GRAPH ?a { OPTIONAL { ... } }`
50
    // returns zero solutions when no triple in ?a matches the inner pattern, which
51
    // would drop every resource that has no gen:hasNamespace.
52
    private static final String MAINTAINED_RESOURCES_QUERY = SpacesRepoAccess.PREFIXES
53
            + "SELECT ?resource ?space ?np ?label ?namespace ?date WHERE {\n"
54
            + SpacesRepoAccess.CURRENT_STATE_POINTER
55
            + "  GRAPH ?g { ?resource npa:isMaintainedBy ?space . }\n"
56
            + "  GRAPH npa:spacesGraph {\n"
57
            + "    ?d a npa:MaintainedResourceDeclaration ;\n"
58
            + "       npa:resourceIri ?resource ;\n"
59
            + "       npa:maintainerSpace ?space ;\n"
60
            + "       npa:viaNanopub ?np ;\n"
61
            + "       dct:created ?date .\n"
62
            + "  }\n"
63
            + "  GRAPH npa:graph {\n"
64
            + "    ?np np:hasAssertion ?a .\n"
65
            + "    OPTIONAL { ?np rdfs:label ?label }\n"
66
            + "  }\n"
67
            + "  OPTIONAL { GRAPH ?a { ?resource gen:hasNamespace ?namespace } }\n"
68
            + "} ORDER BY DESC(?date)";
69

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

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

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

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

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

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

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

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