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

knowledgepixels / nanodash / 28940381907

08 Jul 2026 11:50AM UTC coverage: 28.281% (-0.03%) from 28.314%
28940381907

Pull #546

github

web-flow
Merge 2691c026a into dd88400e3
Pull Request #546: feat: resolve governed template versions space-based

1840 of 7353 branches covered (25.02%)

Branch coverage included in aggregate %.

3752 of 12420 relevant lines covered (30.21%)

4.49 hits per line

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

31.41
src/main/java/com/knowledgepixels/nanodash/template/TemplateData.java
1
package com.knowledgepixels.nanodash.template;
2

3
import com.google.common.collect.ArrayListMultimap;
4
import com.google.common.collect.Multimap;
5
import com.knowledgepixels.nanodash.ApiCache;
6
import com.knowledgepixels.nanodash.QueryApiAccess;
7
import com.knowledgepixels.nanodash.Utils;
8
import net.trustyuri.TrustyUriUtils;
9
import org.eclipse.rdf4j.model.IRI;
10
import org.eclipse.rdf4j.model.Statement;
11
import org.nanopub.Nanopub;
12
import org.nanopub.extra.services.ApiResponse;
13
import org.nanopub.extra.services.ApiResponseEntry;
14
import org.nanopub.extra.services.QueryRef;
15
import org.nanopub.vocabulary.NTEMPLATE;
16
import org.slf4j.Logger;
17
import org.slf4j.LoggerFactory;
18

19
import java.io.Serializable;
20
import java.util.*;
21
import java.util.concurrent.ConcurrentHashMap;
22
import java.util.concurrent.ConcurrentMap;
23

24
/**
25
 * Singleton class that manages templates data.
26
 */
27
public class TemplateData implements Serializable {
28

29
    private static final Logger logger = LoggerFactory.getLogger(TemplateData.class);
9✔
30

31
    private static TemplateData instance;
32

33
    /**
34
     * Refreshes the templates data by creating a new instance of TemplateData.
35
     */
36
    public static synchronized void refreshTemplates() {
37
        logger.info("Refreshing templates...");
9✔
38
        instance = new TemplateData();
12✔
39
    }
3✔
40

41
    /**
42
     * Ensures that the TemplateData instance is loaded.
43
     */
44
    public static synchronized void ensureLoaded() {
45
        if (instance == null) refreshTemplates();
6!
46
    }
3✔
47

48
    /**
49
     * Gets the singleton instance of TemplateData.
50
     *
51
     * @return the TemplateData instance
52
     */
53
    public static TemplateData get() {
54
        ensureLoaded();
3✔
55
        return instance;
6✔
56
    }
57

58
    private List<ApiResponseEntry> assertionTemplates, provenanceTemplates, pubInfoTemplates;
59
    private ConcurrentMap<String, Template> templateMap;
60

61
    /**
62
     * Constructor to initialize the TemplateData instance.
63
     */
64
    public TemplateData() {
6✔
65
        assertionTemplates = new ArrayList<>();
15✔
66
        provenanceTemplates = new ArrayList<>();
15✔
67
        pubInfoTemplates = new ArrayList<>();
15✔
68
        templateMap = new ConcurrentHashMap<>();
15✔
69
        refreshTemplates(assertionTemplates, QueryApiAccess.GET_ASSERTION_TEMPLATES);
15✔
70
        refreshTemplates(provenanceTemplates, QueryApiAccess.GET_PROVENANCE_TEMPLATES);
15✔
71
        refreshTemplates(pubInfoTemplates, QueryApiAccess.GET_PUBINFO_TEMPLATES);
15✔
72
    }
3✔
73

74
    private void refreshTemplates(List<ApiResponseEntry> templates, String queryId) {
75
        ApiResponse templateEntries = ApiCache.retrieveResponseSync(new QueryRef(queryId), true);
21✔
76
        String previousId = null;
6✔
77
        logger.info("Loading templates...");
9✔
78
        for (ApiResponseEntry entry : templateEntries.getData()) {
33✔
79
            if ("true".equals(entry.get("unlisted"))) continue;
21✔
80
            if (!entry.get("np").equals(previousId)) {
18✔
81
                templates.add(entry);
12✔
82
            }
83
            previousId = entry.get("np");
12✔
84
        }
3✔
85
        templates.sort(templateComparator);
9✔
86
    }
3✔
87

88
    /**
89
     * Returns the list of assertion templates.
90
     *
91
     * @return a list of assertion templates
92
     */
93
    public List<ApiResponseEntry> getAssertionTemplates() {
94
        return assertionTemplates;
×
95
    }
96

97
    /**
98
     * Returns the list of provenance templates.
99
     *
100
     * @return a list of provenance templates
101
     */
102
    public List<ApiResponseEntry> getProvenanceTemplates() {
103
        return provenanceTemplates;
×
104
    }
105

106
    /**
107
     * Returns the list of publication information templates.
108
     *
109
     * @return a list of publication information templates
110
     */
111
    public List<ApiResponseEntry> getPubInfoTemplates() {
112
        return pubInfoTemplates;
×
113
    }
114

115
    /**
116
     * Returns a Template object for the given template ID. The ID may be either
117
     * form: the nanopublication URI, or (for templates with embedded identity) the
118
     * embedded template-node IRI.
119
     *
120
     * @param id the ID of the template
121
     * @return the Template object if found, or null if not found or invalid
122
     */
123
    public Template getTemplate(String id) {
124
        Template template = templateMap.get(id);
18✔
125
        if (template != null) return template;
12✔
126
        String npId = Utils.stripToNanopubId(id);
9✔
127
        template = templateMap.get(npId);
18✔
128
        if (template != null) {
6!
129
            templateMap.put(id, template);
×
130
            return template;
×
131
        }
132
        if (TrustyUriUtils.isPotentialTrustyUri(npId)) {
9!
133
            try {
134
                Template t = new Template(npId);
15✔
135
                templateMap.put(id, t);
18✔
136
                templateMap.put(npId, t);
18✔
137
                templateMap.put(t.getId(), t);
21✔
138
                return t;
6✔
139
            } catch (Exception ex) {
×
140
                logger.error("Exception: {}", ex.getMessage());
×
141
                return null;
×
142
            }
143
        }
144
        return null;
×
145
    }
146

147
    /**
148
     * Registers a template from a Nanopub object directly, without fetching it from the registry.
149
     * This is useful for previewing templates that have not yet been published.
150
     *
151
     * @param np the Nanopub containing the template definition
152
     * @return the Template object, or null if the nanopub is not a valid template
153
     */
154
    public Template registerTemplate(Nanopub np) {
155
        String id = np.getUri().stringValue();
12✔
156
        Template template = templateMap.get(id);
18✔
157
        if (template != null) return template;
6!
158
        try {
159
            Template t = new Template(np);
15✔
160
            templateMap.put(id, t);
18✔
161
            templateMap.put(t.getId(), t);
21✔
162
            return t;
6✔
163
        } catch (Exception ex) {
×
164
            logger.error("Exception registering template from nanopub: {}", ex.getMessage());
×
165
            return null;
×
166
        }
167
    }
168

169
    /**
170
     * Resolves a template ID to the ID of the latest version of that template.
171
     * A version declaring {@code gen:governedBy} resolves space-based: the newest
172
     * version of its (kind, space) pair signed by a current member+ of the
173
     * governing space, checked server-side by the
174
     * {@link QueryApiAccess#GET_LATEST_GOVERNED_VERSION} query, with the pinned
175
     * version as the floor on an empty result or failure. A version without it
176
     * follows the supersedes chain of the containing nanopublication. Accepts
177
     * either ID form (nanopublication URI or embedded template-node IRI) and
178
     * returns the latest version's canonical ID ({@link Template#getId()}) — so
179
     * even when there is no newer version, the given ID is normalized to canonical
180
     * form. Falls back to the given ID if the template cannot be loaded.
181
     *
182
     * @param templateId the ID of the template
183
     * @return the canonical ID of the latest version, or the given ID as fallback
184
     */
185
    public String getLatestTemplateId(String templateId) {
186
        Template pinned = getTemplate(templateId);
×
187
        if (pinned != null && pinned.getGoverningSpace() != null && pinned.getTemplateKindIri() != null) {
×
188
            String governedId = resolveGovernedTemplateId(pinned);
×
189
            if (governedId != null) return governedId;
×
190
            return pinned.getId();
×
191
        }
192
        String npId = Utils.stripToNanopubId(templateId);
×
193
        String latestNpId = QueryApiAccess.getLatestVersionId(npId);
×
194
        Template latest = getTemplate(latestNpId);
×
195
        if (latest != null) return latest.getId();
×
196
        return templateId;
×
197
    }
198

199
    /**
200
     * Resolves the latest space-governed version of the pinned template's
201
     * (kind, space) pair, or null if no valid floating candidate is found (the
202
     * caller then keeps the pin).
203
     */
204
    private String resolveGovernedTemplateId(Template pinned) {
205
        try {
206
            Multimap<String, String> params = ArrayListMultimap.create();
×
207
            params.put("kind", pinned.getTemplateKindIri().stringValue());
×
208
            params.put("space", pinned.getGoverningSpace().stringValue());
×
209
            ApiResponse resp = ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_LATEST_GOVERNED_VERSION, params), false);
×
210
            if (resp != null && !resp.getData().isEmpty()) {
×
211
                String latestId = resp.getData().get(0).get("version");
×
212
                if (latestId != null && !latestId.isEmpty()) {
×
213
                    Template resolved = getTemplate(latestId);
×
214
                    if (resolved != null) return resolved.getId();
×
215
                }
216
            }
217
        } catch (Exception ex) {
×
218
            logger.error("Error resolving governed version for template: {}", pinned.getId(), ex);
×
219
        }
×
220
        return null;
×
221
    }
222

223
    /**
224
     * Returns a Template object for the template of the given Nanopub.
225
     *
226
     * @param np the Nanopub from which to extract the template
227
     * @return the Template object if found, or null if not found or invalid
228
     */
229
    public Template getTemplate(Nanopub np) {
230
        IRI templateId = getTemplateId(np);
×
231
        if (templateId == null) return null;
×
232
        return getTemplate(templateId.stringValue());
×
233
    }
234

235
    /**
236
     * Returns a Template object for the provenance template of the given Nanopub.
237
     *
238
     * @param np the Nanopub from which to extract the provenance template
239
     * @return the Template object if found, or null if not found or invalid
240
     */
241
    public Template getProvenanceTemplate(Nanopub np) {
242
        IRI templateId = getProvenanceTemplateId(np);
×
243
        if (templateId == null) return null;
×
244
        return getTemplate(templateId.stringValue());
×
245
    }
246

247
    /**
248
     * Returns a set of Template objects for the publication information templates of the given Nanopub.
249
     *
250
     * @param np the Nanopub from which to extract the publication information templates
251
     * @return a set of Template objects
252
     */
253
    public Set<Template> getPubinfoTemplates(Nanopub np) {
254
        Set<Template> templates = new HashSet<>();
×
255
        for (IRI id : getPubinfoTemplateIds(np)) {
×
256
            templates.add(getTemplate(id.stringValue()));
×
257
        }
×
258
        return templates;
×
259
    }
260

261
    /**
262
     * Returns the template ID of the given Nanopub.
263
     *
264
     * @param nanopub the Nanopub from which to extract the template ID
265
     * @return the IRI of the template ID, or null if not found
266
     */
267
    public IRI getTemplateId(Nanopub nanopub) {
268
        for (Statement st : nanopub.getPubinfo()) {
×
269
            if (!st.getSubject().equals(nanopub.getUri())) continue;
×
270
            if (!st.getPredicate().equals(NTEMPLATE.WAS_CREATED_FROM_TEMPLATE)) continue;
×
271
            if (!(st.getObject() instanceof IRI)) continue;
×
272
            return (IRI) st.getObject();
×
273
        }
274
        return null;
×
275
    }
276

277
    /**
278
     * Returns the provenance template ID of the given Nanopub.
279
     *
280
     * @param nanopub the Nanopub from which to extract the provenance template ID
281
     * @return the IRI of the provenance template ID, or null if not found
282
     */
283
    public IRI getProvenanceTemplateId(Nanopub nanopub) {
284
        for (Statement st : nanopub.getPubinfo()) {
×
285
            if (!st.getSubject().equals(nanopub.getUri())) continue;
×
286
            if (!st.getPredicate().equals(NTEMPLATE.WAS_CREATED_FROM_PROVENANCE_TEMPLATE)) continue;
×
287
            if (!(st.getObject() instanceof IRI)) continue;
×
288
            return (IRI) st.getObject();
×
289
        }
290
        return null;
×
291
    }
292

293
    /**
294
     * Returns the set of publication information template IDs for the given Nanopub.
295
     *
296
     * @param nanopub the Nanopub from which to extract the publication information template IDs
297
     * @return a set of IRI objects representing the publication information template IDs
298
     */
299
    public Set<IRI> getPubinfoTemplateIds(Nanopub nanopub) {
300
        Set<IRI> iriSet = new HashSet<>();
×
301
        for (Statement st : nanopub.getPubinfo()) {
×
302
            if (!st.getSubject().equals(nanopub.getUri())) continue;
×
303
            if (!st.getPredicate().equals(NTEMPLATE.WAS_CREATED_FROM_PUBINFO_TEMPLATE)) continue;
×
304
            if (!(st.getObject() instanceof IRI)) continue;
×
305
            iriSet.add((IRI) st.getObject());
×
306
        }
×
307
        return iriSet;
×
308
    }
309

310
    /**
311
     * Returns a list of Template objects from the given ApiResponse.
312
     *
313
     * @param apiResponse the ApiResponse containing template entries
314
     * @return a list of Template objects
315
     */
316
    public static List<Template> getTemplateList(ApiResponse apiResponse) {
317
        List<Template> templates = new ArrayList<>();
×
318
        for (ApiResponseEntry e : apiResponse.getData()) {
×
319
            String templateNpId = e.get("template_np");
×
320
            if (templateNpId == null) templateNpId = e.get("np");
×
321
            templates.add(TemplateData.get().getTemplate(templateNpId));
×
322
        }
×
323
        return templates;
×
324
    }
325

326
    private static final TemplateComparator templateComparator = new TemplateComparator();
15✔
327

328
    private static class TemplateComparator implements Comparator<ApiResponseEntry>, Serializable {
329

330
        /**
331
         * Compares two Template objects based on their labels.
332
         *
333
         * @param o1 the first object to be compared.
334
         * @param o2 the second object to be compared.
335
         * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
336
         */
337
        @Override
338
        public int compare(ApiResponseEntry o1, ApiResponseEntry o2) {
339
            return o1.get("label").compareTo(o2.get("label"));
24✔
340
        }
341

342
    }
343

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