• 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

65.22
src/main/java/com/knowledgepixels/nanodash/repository/SpaceRepository.java
1
package com.knowledgepixels.nanodash.repository;
2

3
import com.github.jsonldjava.shaded.com.google.common.collect.Ordering;
4
import com.knowledgepixels.nanodash.ApiCache;
5
import com.knowledgepixels.nanodash.QueryApiAccess;
6
import com.knowledgepixels.nanodash.domain.Space;
7
import com.knowledgepixels.nanodash.domain.SpaceFactory;
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.*;
14

15
/**
16
 * Repository class for managing Space instances, providing methods to refresh, retrieve, and query spaces based on API responses.
17
 */
18
public class SpaceRepository {
19

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

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

24
    /**
25
     * Get the singleton instance of SpaceRepository.
26
     *
27
     * @return The singleton instance of SpaceRepository.
28
     */
29
    public static SpaceRepository get() {
30
        return INSTANCE;
6✔
31
    }
32

33
    private volatile List<Space> spaceList;
34
    private Map<String, List<Space>> spaceListByType;
35
    private Map<String, Space> spacesById;
36
    private Map<String, Space> spacesByAltId;
37
    private Map<Space, Set<Space>> subspaceMap;
38
    private Map<Space, Set<Space>> superspaceMap;
39
    private boolean loaded = false;
9✔
40
    private volatile Long runRootUpdateAfter = null;
9✔
41
    private volatile long lastRefreshTime = 0;
9✔
42

43
    private final Object loadLock = new Object();
15✔
44

45
    private SpaceRepository() {
6✔
46
    }
3✔
47

48
    /**
49
     * Refresh the list of spaces from the spaces repo. Pulls the latest
50
     * non-invalidated SpaceDefinition per Space IRI from {@code npa:spacesGraph}
51
     * and joins to the declaring nanopub for label and type.
52
     * <p>
53
     * The {@code get-spaces} query returns one row per (SpaceRef, SpaceDefinition)
54
     * — many spaces have multiple contributing nanopubs (root + updates) and even
55
     * multiple SpaceRefs during the rootless transition phase. The query orders by
56
     * {@code DESC(?date)}, so dedup'ing by spaceIri here (first row wins) picks the
57
     * latest update per space.
58
     */
59
    public synchronized void refresh() {
60
        List<Space> newSpaceList = new ArrayList<>();
12✔
61
        Map<String, List<Space>> newSpaceListByType = new HashMap<>();
12✔
62
        Map<String, Space> newSpacesById = new HashMap<>();
12✔
63
        Map<String, Space> newSpacesByAltId = new HashMap<>();
12✔
64
        Map<Space, Set<Space>> newSubspaceMap = new HashMap<>();
12✔
65
        Map<Space, Set<Space>> newSuperspaceMap = new HashMap<>();
12✔
66
        Set<String> seen = new HashSet<>();
12✔
67
        for (ApiResponseEntry r : ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_SPACES), true).getData()) {
48✔
68
            String spaceIri = r.get("spaceIri");
12✔
69
            if (spaceIri == null || spaceIri.isEmpty()) continue;
15!
70
            if (!seen.add(spaceIri)) continue; // first row (latest date) wins
15✔
71
            ApiResponseEntry entry = new ApiResponseEntry();
12✔
72
            entry.add("space", spaceIri);
12✔
73
            entry.add("np", r.get("np"));
18✔
74
            entry.add("label", r.get("label"));
18✔
75
            entry.add("type", r.get("type"));
18✔
76
            Space space = SpaceFactory.getOrCreate(entry);
9✔
77
            newSpaceList.add(space);
12✔
78
            newSpaceListByType.computeIfAbsent(space.getType(), k -> new ArrayList<>()).add(space);
39✔
79
            newSpacesById.put(space.getId(), space);
18✔
80
            for (String altId : space.getAltIDs()) {
33✔
81
                newSpacesByAltId.put(altId, space);
15✔
82
            }
3✔
83
        }
3✔
84
        logger.info("Refreshed spaces from spaces repo: {} distinct spaces", newSpaceList.size());
18✔
85
        SpaceFactory.removeStale(newSpacesById.keySet());
9✔
86
        populateSubspaceRelations(newSpacesById, newSubspaceMap, newSuperspaceMap);
12✔
87
        for (Space space : newSpaceList) {
30✔
88
            space.setDataNeedsUpdate();
6✔
89
        }
3✔
90
        spacesById = newSpacesById;
9✔
91
        spacesByAltId = newSpacesByAltId;
9✔
92
        spaceListByType = newSpaceListByType;
9✔
93
        subspaceMap = newSubspaceMap;
9✔
94
        superspaceMap = newSuperspaceMap;
9✔
95
        loaded = true;
9✔
96
        lastRefreshTime = System.currentTimeMillis();
9✔
97
        spaceList = newSpaceList; // volatile write last — establishes happens-before for all above
9✔
98
    }
3✔
99

100
    /**
101
     * Force a refresh of the root spaces after a specified delay, allowing for any ongoing updates to complete before the next refresh.
102
     *
103
     * @param waitMillis The number of milliseconds to wait before allowing the next refresh to occur.
104
     */
105
    public void forceRootRefresh(long waitMillis) {
106
        spaceList = null;
×
107
        runRootUpdateAfter = System.currentTimeMillis() + waitMillis;
×
108
    }
×
109

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

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

146
    /**
147
     * Get a space by one of its alternative IDs.
148
     *
149
     * @param altId The alternative ID of the space.
150
     * @return The corresponding Space object, or null if not found.
151
     */
152
    public Space findByAltId(String altId) {
153
        ensureLoaded();
6✔
154
        return spacesByAltId.get(altId);
18✔
155
    }
156

157
    /**
158
     * Get spaces by their type.
159
     *
160
     * @param type The type of spaces to retrieve.
161
     * @return List of Space objects matching the specified type, or an empty list if none are found.
162
     */
163
    public List<Space> findByType(String type) {
164
        ensureLoaded();
×
165
        return spaceListByType.computeIfAbsent(type, k -> new ArrayList<>());
×
166
    }
167

168
    /**
169
     * Get subspaces of a given space.
170
     *
171
     * @param space The space for which to find subspaces.
172
     * @return List of subspaces.
173
     */
174
    public List<Space> findSubspaces(Space space) {
175
        if (subspaceMap.containsKey(space)) {
×
176
            List<Space> subspaces = new ArrayList<>(subspaceMap.get(space));
×
177
            subspaces.sort(Ordering.usingToString());
×
178
            return subspaces;
×
179
        }
180
        return new ArrayList<>();
×
181
    }
182

183
    /**
184
     * Get subspaces of a given space that match a specific type.
185
     *
186
     * @param space The space for which to find subspaces.
187
     * @param type  The type of subspaces to filter by.
188
     * @return List of subspaces matching the specified type.
189
     */
190
    public List<Space> findSubspaces(Space space, String type) {
191
        List<Space> l = new ArrayList<>();
×
192
        for (Space s : findSubspaces(space)) {
×
193
            if (s.getType().equals(type)) l.add(s);
×
194
        }
×
195
        return l;
×
196
    }
197

198
    /**
199
     * Get superspaces of this space.
200
     *
201
     * @return List of superspaces.
202
     */
203
    public List<Space> findSuperspaces(Space space) {
204
        if (superspaceMap.containsKey(space)) {
×
205
            List<Space> superspaces = new ArrayList<>(superspaceMap.get(space));
×
206
            superspaces.sort(Ordering.usingToString());
×
207
            return superspaces;
×
208
        }
209
        return new ArrayList<>();
×
210
    }
211

212
    /**
213
     * Refresh the spaces and mark each as needing a downstream data update.
214
     */
215
    public void refreshAndInvalidate() {
216
        refresh();
×
217
        for (Space space : spaceList) {
×
218
            space.setDataNeedsUpdate();
×
219
        }
×
220
    }
×
221

222
    private static void populateSubspaceRelations(
223
            Map<String, Space> spacesById,
224
            Map<Space, Set<Space>> subspaceMap,
225
            Map<Space, Set<Space>> superspaceMap) {
226
        for (ApiResponseEntry r : ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_SUB_SPACE_LINKS), true).getData()) {
48✔
227
            Space child = spacesById.get(r.get("child"));
21✔
228
            Space parent = spacesById.get(r.get("parent"));
21✔
229
            if (child == null || parent == null) continue;
15✔
230
            subspaceMap.computeIfAbsent(parent, k -> new HashSet<>()).add(child);
36✔
231
            superspaceMap.computeIfAbsent(child, k -> new HashSet<>()).add(parent);
36✔
232
        }
3✔
233
    }
3✔
234

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