• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

knowledgepixels / nanodash / 23133579782

16 Mar 2026 08:00AM UTC coverage: 15.975% (+0.2%) from 15.811%
23133579782

Pull #402

github

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

717 of 5521 branches covered (12.99%)

Branch coverage included in aggregate %.

1811 of 10304 relevant lines covered (17.58%)

2.39 hits per line

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

0.0
src/main/java/com/knowledgepixels/nanodash/View.java
1
package com.knowledgepixels.nanodash;
2

3
import com.knowledgepixels.nanodash.template.Template;
4
import com.knowledgepixels.nanodash.template.TemplateData;
5
import com.knowledgepixels.nanodash.vocabulary.KPXL_TERMS;
6
import org.eclipse.rdf4j.model.IRI;
7
import org.eclipse.rdf4j.model.Literal;
8
import org.eclipse.rdf4j.model.Statement;
9
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
10
import org.eclipse.rdf4j.model.vocabulary.RDF;
11
import org.eclipse.rdf4j.model.vocabulary.RDFS;
12
import org.nanopub.Nanopub;
13
import org.nanopub.NanopubUtils;
14
import org.slf4j.Logger;
15
import org.slf4j.LoggerFactory;
16

17
import com.google.common.cache.Cache;
18
import com.google.common.cache.CacheBuilder;
19

20
import java.io.Serializable;
21
import java.util.*;
22
import java.util.concurrent.TimeUnit;
23

24
/**
25
 * A class representing a Resource View.
26
 */
27
public class View implements Serializable {
28

29
    private static final Logger logger = LoggerFactory.getLogger(View.class);
×
30
    private static final Set<IRI> supportedViewTypes = Set.of(
×
31
            KPXL_TERMS.TABULAR_VIEW,
32
            KPXL_TERMS.LIST_VIEW,
33
            KPXL_TERMS.PLAIN_PARAGRAPH_VIEW,
34
            KPXL_TERMS.NANOPUB_SET_VIEW,
35
            KPXL_TERMS.ITEM_LIST_VIEW
36
    );
37

38
    static Map<IRI, Integer> columnWidths = new HashMap<>();
×
39

40
    static {
41
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_1_OF_12, 1);
×
42
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_2_OF_12, 2);
×
43
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_3_OF_12, 3);
×
44
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_4_OF_12, 4);
×
45
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_5_OF_12, 5);
×
46
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_6_OF_12, 6);
×
47
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_7_OF_12, 7);
×
48
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_8_OF_12, 8);
×
49
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_9_OF_12, 9);
×
50
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_10_OF_12, 10);
×
51
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_11_OF_12, 11);
×
52
        columnWidths.put(KPXL_TERMS.COLUMN_WIDTH_12_OF_12, 12);
×
53
    }
54

55
    private static final Cache<String, View> views = CacheBuilder.newBuilder()
×
56
        .maximumSize(5_000)
×
57
        .expireAfterAccess(24, TimeUnit.HOURS)
×
58
        .build();
×
59

60
    /**
61
     * Get a View by its ID.
62
     *
63
     * @param id the ID of the View
64
     * @return the View object
65
     */
66
    public static View get(String id) {
67
        String npId = id.replaceFirst("^(.*[^A-Za-z0-9-_]RA[A-Za-z0-9-_]{43})[^A-Za-z0-9-_].*$", "$1");
×
68
        // Automatically selecting latest version of view definition:
69
        // TODO This should be made configurable at some point, so one can make it a fixed version.
70
        String latestNpId = QueryApiAccess.getLatestVersionId(npId);
×
71
        String latestId = id;
×
72
        Nanopub np = Utils.getAsNanopub(latestNpId);
×
73
        if (!latestNpId.equals(npId)) {
×
74
            Set<String> embeddedIris = NanopubUtils.getEmbeddedIriIds(np);
×
75
            if (embeddedIris.size() == 1) {
×
76
                latestId = embeddedIris.iterator().next();
×
77
            } else {
78
                latestNpId = npId;
×
79
                np = Utils.getAsNanopub(npId);
×
80
            }
81
        }
82
        View cached = views.getIfPresent(latestId);
×
83
        if (cached == null) {
×
84
            try {
85
                cached = new View(latestId, np);
×
86
                views.put(latestId, cached);
×
87
            } catch (Exception ex) {
×
88
                logger.error("Couldn't load nanopub for resource: {}", id, ex);
×
89
            }
×
90
        }
91
        return cached;
×
92
    }
93

94
    private String id;
95
    private Nanopub nanopub;
96
    private IRI viewKind;
97
    private String label;
98
    private String title = "View";
×
99
    private GrlcQuery query;
100
    private String queryField = "resource";
×
101
    private Integer pageSize;
102
    private Integer displayWidth;
103
    private String structuralPosition;
104
    private List<IRI> viewResultActionList = new ArrayList<>();
×
105
    private List<IRI> viewEntryActionList = new ArrayList<>();
×
106
    private Set<IRI> appliesToClasses = new HashSet<>();
×
107
    private Set<IRI> appliesToNamespaces = new HashSet<>();
×
108
    private Map<IRI, Template> actionTemplateMap = new HashMap<>();
×
109
    private Map<IRI, String> actionTemplateTargetFieldMap = new HashMap<>();
×
110
    private Map<IRI, IRI> actionTemplateTypeMap = new HashMap<>();
×
111
    private Map<IRI, String> actionTemplatePartFieldMap = new HashMap<>();
×
112
    private Map<IRI, String> actionTemplateQueryMappingMap = new HashMap<>();
×
113
    private Map<IRI, String> labelMap = new HashMap<>();
×
114
    private IRI viewType;
115

116
    private View(String id, Nanopub nanopub) {
×
117
        this.id = id;
×
118
        this.nanopub = nanopub;
×
119
        List<IRI> actionList = new ArrayList<>();
×
120
        boolean viewTypeFound = false;
×
121
        for (Statement st : nanopub.getAssertion()) {
×
122
            if (st.getSubject().stringValue().equals(id)) {
×
123
                if (st.getPredicate().equals(RDF.TYPE)) {
×
124
                    if (st.getObject().equals(KPXL_TERMS.RESOURCE_VIEW)) {
×
125
                        viewTypeFound = true;
×
126
                    }
127
                    if (st.getObject() instanceof IRI objIri && supportedViewTypes.contains(objIri)) {
×
128
                        viewType = objIri;
×
129
                    }
130
                } else if (st.getPredicate().equals(DCTERMS.IS_VERSION_OF) && st.getObject() instanceof IRI objIri) {
×
131
                    viewKind = objIri;
×
132
                } else if (st.getPredicate().equals(RDFS.LABEL)) {
×
133
                    label = st.getObject().stringValue();
×
134
                } else if (st.getPredicate().equals(DCTERMS.TITLE)) {
×
135
                    title = st.getObject().stringValue();
×
136
                } else if (st.getPredicate().equals(KPXL_TERMS.HAS_VIEW_QUERY)) {
×
137
                    query = GrlcQuery.get(st.getObject().stringValue());
×
138
                } else if (st.getPredicate().equals(KPXL_TERMS.HAS_VIEW_QUERY_TARGET_FIELD)) {
×
139
                    queryField = st.getObject().stringValue();
×
140
                } else if (st.getPredicate().equals(KPXL_TERMS.HAS_VIEW_ACTION) && st.getObject() instanceof IRI objIri) {
×
141
                    actionList.add(objIri);
×
142
                } else if (st.getPredicate().equals(KPXL_TERMS.APPLIES_TO_NAMESPACE) && st.getObject() instanceof IRI objIri) {
×
143
                    appliesToNamespaces.add(objIri);
×
144
                } else if (st.getPredicate().equals(KPXL_TERMS.APPLIES_TO_INSTANCES_OF) && st.getObject() instanceof IRI objIri) {
×
145
                    appliesToClasses.add(objIri);
×
146
                } else if (st.getPredicate().equals(KPXL_TERMS.HAS_VIEW_TARGET_CLASS) && st.getObject() instanceof IRI objIri) {
×
147
                    // Deprecated
148
                    appliesToClasses.add(objIri);
×
149
                } else if (st.getPredicate().equals(KPXL_TERMS.HAS_PAGE_SIZE) && st.getObject() instanceof Literal objL) {
×
150
                    try {
151
                        pageSize = Integer.parseInt(objL.stringValue());
×
152
                    } catch (NumberFormatException ex) {
×
153
                        logger.error("Invalid page size value: {}", objL.stringValue(), ex);
×
154
                    }
×
155
                } else if (st.getPredicate().equals(KPXL_TERMS.HAS_DISPLAY_WIDTH) && st.getObject() instanceof IRI objIri) {
×
156
                    displayWidth = columnWidths.get(objIri);
×
157
                } else if (st.getPredicate().equals(KPXL_TERMS.HAS_STRUCTURAL_POSITION) && st.getObject() instanceof Literal objL) {
×
158
                    structuralPosition = objL.stringValue();
×
159
                }
160
            } else if (st.getPredicate().equals(KPXL_TERMS.HAS_ACTION_TEMPLATE)) {
×
161
                Template template = TemplateData.get().getTemplate(st.getObject().stringValue());
×
162
                actionTemplateMap.put((IRI) st.getSubject(), template);
×
163
            } else if (st.getPredicate().equals(KPXL_TERMS.HAS_ACTION_TEMPLATE_TARGET_FIELD)) {
×
164
                actionTemplateTargetFieldMap.put((IRI) st.getSubject(), st.getObject().stringValue());
×
165
            } else if (st.getPredicate().equals(KPXL_TERMS.HAS_ACTION_TEMPLATE_PART_FIELD)) {
×
166
                actionTemplatePartFieldMap.put((IRI) st.getSubject(), st.getObject().stringValue());
×
167
            } else if (st.getPredicate().equals(KPXL_TERMS.HAS_ACTION_TEMPLATE_QUERY_MAPPING)) {
×
168
                actionTemplateQueryMappingMap.put((IRI) st.getSubject(), st.getObject().stringValue());
×
169
            } else if (st.getPredicate().equals(RDFS.LABEL)) {
×
170
                labelMap.put((IRI) st.getSubject(), st.getObject().stringValue());
×
171
            } else if (st.getPredicate().equals(RDF.TYPE)) {
×
172
                if (st.getObject().equals(KPXL_TERMS.VIEW_ACTION) || st.getObject().equals(KPXL_TERMS.VIEW_ENTRY_ACTION)) {
×
173
                    actionTemplateTypeMap.put((IRI) st.getSubject(), (IRI) st.getObject());
×
174
                }
175
            }
176
        }
×
177
        for (IRI actionIri : actionList) {
×
178
            if (actionTemplateTypeMap.containsKey(actionIri) && actionTemplateTypeMap.get(actionIri).equals(KPXL_TERMS.VIEW_ENTRY_ACTION)) {
×
179
                viewEntryActionList.add(actionIri);
×
180
            } else {
181
                viewResultActionList.add(actionIri);
×
182
            }
183
        }
×
184
        if (!viewTypeFound) throw new IllegalArgumentException("Not a proper resource view nanopub: " + id);
×
185
        if (query == null) throw new IllegalArgumentException("Query not found: " + id);
×
186
    }
×
187

188
    /**
189
     * Gets the ID of the View.
190
     *
191
     * @return the ID of the View
192
     */
193
    public String getId() {
194
        return id;
×
195
    }
196

197
    /**
198
     * Gets the Nanopub defining this View.
199
     *
200
     * @return the Nanopub defining this View
201
     */
202
    public Nanopub getNanopub() {
203
        return nanopub;
×
204
    }
205

206
    public IRI getViewKindIri() {
207
        return viewKind;
×
208
    }
209

210
    /**
211
     * Gets the label of the View.
212
     *
213
     * @return the label of the View
214
     */
215
    public String getLabel() {
216
        return label;
×
217
    }
218

219
    /**
220
     * Gets the title of the View.
221
     *
222
     * @return the title of the View
223
     */
224
    public String getTitle() {
225
        return title;
×
226
    }
227

228
    /**
229
     * Gets the GrlcQuery associated with the View.
230
     *
231
     * @return the GrlcQuery associated with the View
232
     */
233
    public GrlcQuery getQuery() {
234
        return query;
×
235
    }
236

237
    /**
238
     * Gets the query field of the View.
239
     *
240
     * @return the query field
241
     */
242
    public String getQueryField() {
243
        return queryField;
×
244
    }
245

246
    /**
247
     * Returns the preferred page size.
248
     *
249
     * @return page size (0 = everything on first page)
250
     */
251
    public Integer getPageSize() {
252
        return pageSize;
×
253
    }
254

255
    public Integer getDisplayWidth() {
256
        return displayWidth;
×
257
    }
258

259
    public String getStructuralPosition() {
260
        return structuralPosition;
×
261
    }
262

263
    /**
264
     * Gets the list of action IRIs associated with the View.
265
     *
266
     * @return the list of action IRIs
267
     */
268
    public List<IRI> getViewResultActionList() {
269
        return viewResultActionList;
×
270
    }
271

272
    public List<IRI> getViewEntryActionList() {
273
        return viewEntryActionList;
×
274
    }
275

276
    /**
277
     * Gets the Template for a given action IRI.
278
     *
279
     * @param actionIri the action IRI
280
     * @return the Template for the action IRI
281
     */
282
    public Template getTemplateForAction(IRI actionIri) {
283
        return actionTemplateMap.get(actionIri);
×
284
    }
285

286
    /**
287
     * Gets the template field for a given action IRI.
288
     *
289
     * @param actionIri the action IRI
290
     * @return the template field for the action IRI
291
     */
292
    public String getTemplateTargetFieldForAction(IRI actionIri) {
293
        return actionTemplateTargetFieldMap.get(actionIri);
×
294
    }
295

296
    public String getTemplatePartFieldForAction(IRI actionIri) {
297
        return actionTemplatePartFieldMap.get(actionIri);
×
298
    }
299

300
    public String getTemplateQueryMapping(IRI actionIri) {
301
        return actionTemplateQueryMappingMap.get(actionIri);
×
302
    }
303

304
    /**
305
     * Gets the label for a given action IRI.
306
     *
307
     * @param actionIri the action IRI
308
     * @return the label for the action IRI
309
     */
310
    public String getLabelForAction(IRI actionIri) {
311
        return labelMap.get(actionIri);
×
312
    }
313

314
    public boolean appliesTo(String resourceId, Set<IRI> classes) {
315
        for (IRI namespace : appliesToNamespaces) {
×
316
            if (resourceId.startsWith(namespace.stringValue())) return true;
×
317
        }
×
318
        if (classes != null) {
×
319
            for (IRI c : classes) {
×
320
                if (appliesToClasses.contains(c)) return true;
×
321
            }
×
322
        }
323
        return false;
×
324
    }
325

326
    /**
327
     * Checks if the View has target classes.
328
     *
329
     * @return true if the View has target classes, false otherwise
330
     */
331
    public boolean appliesToClasses() {
332
        return !appliesToClasses.isEmpty();
×
333
    }
334

335
    /**
336
     * Checks if the View has a specific target class.
337
     *
338
     * @param targetClass the target class IRI
339
     * @return true if the View has the target class, false otherwise
340
     */
341
    public boolean appliesToClass(IRI targetClass) {
342
        return appliesToClasses.contains(targetClass);
×
343
    }
344

345
    @Override
346
    public String toString() {
347
        return id;
×
348
    }
349

350
    /**
351
     * Gets the view type of the View.
352
     *
353
     * @return the view type mode IRI
354
     */
355
    public IRI getViewType() {
356
        return viewType;
×
357
    }
358

359
    /**
360
     * Get the supported view types.
361
     *
362
     * @return a set of supported view type IRIs
363
     */
364
    public static Set<IRI> getSupportedViewTypes() {
365
        return supportedViewTypes;
×
366
    }
367

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