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

knowledgepixels / nanodash / 23133101585

16 Mar 2026 07:45AM UTC coverage: 15.984% (+0.2%) from 15.811%
23133101585

Pull #402

github

web-flow
Merge bd8288c47 into 39c6ac11c
Pull Request #402: Fix unbounded memory growth and resource exhaustion

717 of 5509 branches covered (13.02%)

Branch coverage included in aggregate %.

1809 of 10294 relevant lines covered (17.57%)

2.39 hits per line

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

17.86
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.NanodashThreadPool;
5
import com.knowledgepixels.nanodash.QueryApiAccess;
6
import com.knowledgepixels.nanodash.ViewDisplay;
7
import com.knowledgepixels.nanodash.repository.SpaceRepository;
8
import com.knowledgepixels.nanodash.vocabulary.KPXL_TERMS;
9
import org.eclipse.rdf4j.model.IRI;
10
import org.nanopub.Nanopub;
11
import org.nanopub.extra.services.ApiResponseEntry;
12
import org.nanopub.extra.services.QueryRef;
13
import org.slf4j.Logger;
14
import org.slf4j.LoggerFactory;
15

16
import java.io.Serializable;
17
import java.util.*;
18
import java.util.concurrent.ConcurrentHashMap;
19
import java.util.concurrent.Future;
20

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

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

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

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

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

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

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

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

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

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

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

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

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

132
                    ResourceWithProfile newData = new ResourceWithProfile();
×
133

134
                    for (ApiResponseEntry r : ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_VIEW_DISPLAYS, "resource", id), true).getData()) {
×
135
                        if (space != null && !space.isAdminPubkey(r.get("pubkey"))) {
×
136
                            continue;
×
137
                        }
138
                        try {
139
                            newData.viewDisplays.add(ViewDisplay.get(r.get("display")));
×
140
                        } catch (IllegalArgumentException ex) {
×
141
                            logger.error("Couldn't generate view display object", ex);
×
142
                        }
×
143
                    }
×
144
                    data = newData;
×
145
                    dataInitialized = true;
×
146
                } catch (Exception ex) {
×
147
                    logger.error("Error while trying to update data for resource {}", id, ex);
×
148
                    dataNeedsUpdate = true;
×
149
                }
×
150
            });
×
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
        // Results are sorted by date (most recent first); only the most recent per view-kind is considered
225
        for (ViewDisplay vd : getViewDisplays()) {
×
226
            IRI kind = vd.getViewKindIri();
×
227
            if (kind != null) {
×
228
                if (viewKinds.contains(kind)) {
×
229
                    continue;
×
230
                }
231
                viewKinds.add(kind);
×
232
            }
233

234
            if (vd.hasType(KPXL_TERMS.DEACTIVATED_VIEW_DISPLAY)) {
×
235
                continue;
×
236
            }
237

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

249
        Collections.sort(viewDisplays);
×
250
        return viewDisplays;
×
251
    }
252

253
    @Override
254
    public abstract String getLabel();
255

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

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

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

295
    /**
296
     * Checks if any view display of this resource applies to the given element ID and set of classes by triggering a data update and checking each view display for applicability.
297
     *
298
     * @param elementId the ID of the element to check for applicability
299
     * @param classes   the set of classes to check for applicability
300
     * @return true if any view display of this resource applies to the given element ID and set of classes, false otherwise
301
     */
302
    public boolean appliesTo(String elementId, Set<IRI> classes) {
303
        triggerDataUpdate();
×
304
        for (ViewDisplay v : getViewDisplays()) {
×
305
            if (v.appliesTo(elementId, classes)) {
×
306
                return true;
×
307
            }
308
        }
×
309
        return false;
×
310
    }
311

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