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

knowledgepixels / nanodash / 22628819832

03 Mar 2026 02:59PM UTC coverage: 16.03% (-0.04%) from 16.069%
22628819832

Pull #366

github

web-flow
Merge b80ba3f5f into d4f274b5b
Pull Request #366: fix(user-page): conditionally show latest nanopubs for unconfigured users

699 of 5297 branches covered (13.2%)

Branch coverage included in aggregate %.

1721 of 9800 relevant lines covered (17.56%)

2.41 hits per line

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

18.75
src/main/java/com/knowledgepixels/nanodash/domain/AbstractResourceWithProfile.java
1
package com.knowledgepixels.nanodash.domain;
2

3
import com.knowledgepixels.nanodash.ApiCache;
4
import com.knowledgepixels.nanodash.QueryApiAccess;
5
import com.knowledgepixels.nanodash.ViewDisplay;
6
import com.knowledgepixels.nanodash.repository.SpaceRepository;
7
import com.knowledgepixels.nanodash.vocabulary.KPXL_TERMS;
8
import org.eclipse.rdf4j.model.IRI;
9
import org.nanopub.Nanopub;
10
import org.nanopub.extra.services.ApiResponseEntry;
11
import org.nanopub.extra.services.QueryRef;
12
import org.slf4j.Logger;
13
import org.slf4j.LoggerFactory;
14

15
import java.io.Serializable;
16
import java.util.*;
17
import java.util.concurrent.ConcurrentHashMap;
18

19
/**
20
 * Abstract class representing a resource with a profile in the Nanodash application.
21
 * This class provides common functionality for resources that have associated profiles, such as spaces and users.
22
 */
23
public abstract class AbstractResourceWithProfile implements Serializable, ResourceWithProfile {
24

25
    private static final Logger logger = LoggerFactory.getLogger(AbstractResourceWithProfile.class);
9✔
26

27
    private static final Map<Class<?>, Map<String, AbstractResourceWithProfile>> instances = new ConcurrentHashMap<>();
15✔
28

29
    private final String id;
30
    private Space space;
31
    private ResourceWithProfile data = new ResourceWithProfile();
15✔
32
    private volatile boolean dataInitialized = false;
9✔
33
    private volatile boolean dataNeedsUpdate = true;
9✔
34
    private volatile Long runUpdateAfter = null;
9✔
35

36
    /**
37
     * Inner class to hold the data associated with a resource, including its view displays.
38
     */
39
    protected static class ResourceWithProfile implements Serializable {
6✔
40
        List<ViewDisplay> viewDisplays = new ArrayList<>();
18✔
41
    }
42

43
    /**
44
     * Checks if a resource with the given unique identifier exists in the system.
45
     *
46
     * @param id the unique identifier of the resource
47
     * @return true if a resource with the given id exists, false otherwise
48
     */
49
    public static boolean isResourceWithProfile(String id) {
50
        return get(id) != null;
×
51
    }
52

53
    /**
54
     * Retrieves an instance of AbstractResourceWithProfile by its unique identifier.
55
     *
56
     * @param id the unique identifier of the resource
57
     * @return the AbstractResourceWithProfile instance associated with the given id, or null if no such instance exists
58
     */
59
    public static AbstractResourceWithProfile get(String id) {
60
        for (Map<String, AbstractResourceWithProfile> map : instances.values()) {
33✔
61
            if (map.containsKey(id)) {
12✔
62
                return map.get(id);
15✔
63
            }
64
        }
3✔
65
        return null;
6✔
66
    }
67

68
    /**
69
     * Constructor for AbstractResourceWithProfile.
70
     *
71
     * @param id the unique identifier for this resource
72
     */
73
    protected AbstractResourceWithProfile(String id) {
6✔
74
        this.id = id;
9✔
75
        instances.computeIfAbsent(getClass(), k -> new ConcurrentHashMap<>()).put(id, this);
42✔
76
    }
3✔
77

78
    /**
79
     * Removes an instance of AbstractResourceWithProfile from the instances map based on its type and unique identifier.
80
     *
81
     * @param type the class type of the resource to remove
82
     * @param id   the unique identifier of the resource to remove
83
     */
84
    protected static void removeInstance(Class<?> type, String id) {
85
        Map<String, AbstractResourceWithProfile> map = instances.get(type);
×
86
        if (map != null) {
×
87
            map.remove(id);
×
88
        }
89
    }
×
90

91
    /**
92
     * Retrieves all instances of AbstractResourceWithProfile of a specific type.
93
     *
94
     * @param type the class type of the resources to retrieve
95
     * @return a map of resource IDs to AbstractResourceWithProfile instances of the specified type, or an empty map if no instances exist for that type
96
     */
97
    protected static Map<String, AbstractResourceWithProfile> getInstances(Class<?> type) {
98
        return instances.getOrDefault(type, Collections.emptyMap());
18✔
99
    }
100

101
    /**
102
     * Initializes the space for this resource.
103
     *
104
     * @param space the space to associate with this resource
105
     */
106
    protected void initSpace(Space space) {
107
        this.space = space;
9✔
108
        logger.info("Initialized space {} for resource {}", space.getId(), id);
21✔
109
    }
3✔
110

111
    @Override
112
    public String getId() {
113
        return id;
9✔
114
    }
115

116
    @Override
117
    public synchronized Thread triggerDataUpdate() {
118
        if (dataNeedsUpdate) {
×
119
            logger.info("Data needs update for resource {}, starting update thread", id);
×
120
            dataNeedsUpdate = false;
×
121
            Thread thread = new Thread(() -> {
×
122
                try {
123
                    if (runUpdateAfter != null) {
×
124
                        while (System.currentTimeMillis() < runUpdateAfter) {
×
125
                            Thread.sleep(100);
×
126
                        }
127
                        runUpdateAfter = null;
×
128
                    }
129

130
                    ResourceWithProfile newData = new ResourceWithProfile();
×
131

132
                    for (ApiResponseEntry r : ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_VIEW_DISPLAYS, "resource", id), true).getData()) {
×
133
                        if (space != null && !space.isAdminPubkey(r.get("pubkey"))) {
×
134
                            continue;
×
135
                        }
136
                        try {
137
                            newData.viewDisplays.add(ViewDisplay.get(r.get("display")));
×
138
                        } catch (IllegalArgumentException ex) {
×
139
                            logger.error("Couldn't generate view display object", ex);
×
140
                        }
×
141
                    }
×
142
                    data = newData;
×
143
                    dataInitialized = true;
×
144
                } catch (Exception ex) {
×
145
                    logger.error("Error while trying to update data for resource {}", id, ex);
×
146
                    dataNeedsUpdate = true;
×
147
                }
×
148
            });
×
149
            thread.start();
×
150
            return thread;
×
151
        }
152
        return null;
×
153
    }
154

155
    /**
156
     * Forces a refresh of the resource data after a specified delay.
157
     *
158
     * @param waitMillis the delay in milliseconds before the data refresh is triggered
159
     */
160
    public void forceRefresh(long waitMillis) {
161
        logger.info("Forcing refresh of resource {} after {} ms", id, waitMillis);
×
162
        dataNeedsUpdate = true;
×
163
        dataInitialized = false;
×
164
        runUpdateAfter = System.currentTimeMillis() + waitMillis;
×
165
    }
×
166

167
    /**
168
     * Static method to force a refresh of the resource data for all instances of AbstractResourceWithProfile.
169
     */
170
    public static void refresh() {
171
        instances.values().forEach(map -> map.values().forEach(AbstractResourceWithProfile::setDataNeedsUpdate));
12✔
172
    }
3✔
173

174
    @Override
175
    public Long getRunUpdateAfter() {
176
        return runUpdateAfter;
×
177
    }
178

179
    @Override
180
    public Space getSpace() {
181
        return space;
×
182
    }
183

184
    @Override
185
    public abstract String getNanopubId();
186

187
    @Override
188
    public abstract Nanopub getNanopub();
189

190
    public abstract String getNamespace();
191

192
    @Override
193
    public void setDataNeedsUpdate() {
194
        dataNeedsUpdate = true;
9✔
195
    }
3✔
196

197
    @Override
198
    public boolean isDataInitialized() {
199
        triggerDataUpdate();
×
200
        return dataInitialized;
×
201
    }
202

203
    @Override
204
    public List<ViewDisplay> getViewDisplays() {
205
        logger.info("Getting view displays for resource {}", id);
×
206
        return data.viewDisplays;
×
207
    }
208

209
    @Override
210
    public List<ViewDisplay> getTopLevelViewDisplays() {
211
        return getViewDisplays(true, getId(), null);
×
212
    }
213

214
    @Override
215
    public List<ViewDisplay> getPartLevelViewDisplays(String resourceId, Set<IRI> classes) {
216
        return getViewDisplays(false, resourceId, classes);
×
217
    }
218

219
    private List<ViewDisplay> getViewDisplays(boolean toplevel, String resourceId, Set<IRI> classes) {
220
        triggerDataUpdate();
×
221
        List<ViewDisplay> viewDisplays = new ArrayList<>();
×
222
        Set<IRI> viewKinds = new HashSet<>();
×
223

224
        for (ViewDisplay vd : getViewDisplays()) {
×
225
            IRI kind = vd.getViewKindIri();
×
226
            if (kind != null) {
×
227
                if (viewKinds.contains(kind)) {
×
228
                    continue;
×
229
                }
230
                viewKinds.add(vd.getViewKindIri());
×
231
            }
232
            if (vd.hasType(KPXL_TERMS.DEACTIVATED_VIEW_DISPLAY)) {
×
233
                continue;
×
234
            }
235

236
            if (!toplevel && vd.hasType(KPXL_TERMS.TOP_LEVEL_VIEW_DISPLAY)) {
×
237
                // Deprecated
238
                // do nothing
239
            } else if (vd.appliesTo(resourceId, classes)) {
×
240
                viewDisplays.add(vd);
×
241
            } else if (toplevel && vd.hasType(KPXL_TERMS.TOP_LEVEL_VIEW_DISPLAY)) {
×
242
                // Deprecated
243
                viewDisplays.add(vd);
×
244
            }
245
        }
×
246

247
        Collections.sort(viewDisplays);
×
248
        return viewDisplays;
×
249
    }
250

251
    @Override
252
    public abstract String getLabel();
253

254
    @Override
255
    public String toString() {
256
        return id;
×
257
    }
258

259
    /**
260
     * Gets the chain of superspaces from the current space up to the root space.
261
     *
262
     * @return the list of superspaces from the given space to the root space
263
     */
264
    @Override
265
    public List<AbstractResourceWithProfile> getAllSuperSpacesUntilRoot() {
266
        List<AbstractResourceWithProfile> chain = new ArrayList<>();
×
267
        Set<String> visited = new HashSet<>();
×
268
        collectAncestors(space, chain, visited);
×
269
        Collections.reverse(chain);
×
270
        return chain;
×
271
    }
272

273
    private void collectAncestors(Space current, List<AbstractResourceWithProfile> chain, Set<String> visited) {
274
        if (current == null) {
×
275
            return;
×
276
        }
277
        List<Space> parents = SpaceRepository.get().findSuperspaces(current);
×
278
        if (parents == null || parents.isEmpty()) {
×
279
            return;
×
280
        }
281
        Space parent = parents.getFirst();
×
282
        if (parent == null) {
×
283
            return;
×
284
        }
285
        String pid = parent.getId();
×
286
        if (pid == null || !visited.add(pid)) {
×
287
            return;
×
288
        }
289
        chain.add(parent);
×
290
        collectAncestors(parent, chain, visited);
×
291
    }
×
292

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