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

knowledgepixels / nanopub-registry / 23937563850

03 Apr 2026 07:05AM UTC coverage: 31.828% (+0.5%) from 31.28%
23937563850

Pull #91

github

web-flow
Merge 286ff9939 into 0be57ff1e
Pull Request #91: perf: batch seqNum allocation to reduce global counter contention

216 of 754 branches covered (28.65%)

Branch coverage included in aggregate %.

714 of 2168 relevant lines covered (32.93%)

5.65 hits per line

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

0.0
src/main/java/com/knowledgepixels/registry/ListPage.java
1
package com.knowledgepixels.registry;
2

3
import com.google.gson.Gson;
4
import com.mongodb.client.ClientSession;
5
import com.mongodb.client.MongoCursor;
6
import io.vertx.ext.web.RoutingContext;
7
import org.bson.Document;
8
import org.bson.conversions.Bson;
9
import org.nanopub.jelly.NanopubStream;
10

11
import java.io.IOException;
12
import java.net.URLEncoder;
13
import java.util.ArrayList;
14
import java.util.List;
15

16
import static com.knowledgepixels.registry.RegistryDB.collection;
17
import static com.knowledgepixels.registry.RegistryDB.unhash;
18
import static com.knowledgepixels.registry.Utils.*;
19
import static com.mongodb.client.model.Aggregates.*;
20
import static com.mongodb.client.model.Filters.gt;
21
import static com.mongodb.client.model.Indexes.ascending;
22
import static com.mongodb.client.model.Indexes.descending;
23
import static com.mongodb.client.model.Projections.exclude;
24
import static com.mongodb.client.model.Projections.include;
25

26
public class ListPage extends Page {
27

28
    private static final Gson gson = new Gson();
×
29

30
    public static void show(RoutingContext context) {
31
        ListPage page;
32
        try (ClientSession s = RegistryDB.getClient().startSession()) {
×
33
            // No transaction here: the nanopubs.jelly endpoint streams large result sets
34
            // that would exceed MongoDB's transaction timeout.
35
            page = new ListPage(s, context);
×
36
            page.show();
×
37
        } catch (IOException ex) {
×
38
            ex.printStackTrace();
×
39
        } finally {
40
            context.response().end();
×
41
        }
42
    }
×
43

44
    private ListPage(ClientSession mongoSession, RoutingContext context) {
45
        super(mongoSession, context);
×
46
    }
×
47

48
    protected void show() throws IOException {
49
        RoutingContext context = getContext();
×
50
        String format;
51
        String ext = getExtension();
×
52
        final String req = getRequestString();
×
53
        if ("json".equals(ext)) {
×
54
            format = TYPE_JSON;
×
55
        } else if ("jelly".equals(ext)) {
×
56
            format = TYPE_JELLY;
×
57
        } else if (ext == null || "html".equals(ext)) {
×
58
            format = Utils.getMimeType(context, SUPPORTED_TYPES_LIST);
×
59
        } else {
60
            context.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
×
61
            return;
×
62
        }
63

64
        if (getPresentationFormat() != null) {
×
65
            setRespContentType(getPresentationFormat());
×
66
        } else {
67
            setRespContentType(format);
×
68
        }
69

70
        if (req.matches("/list/[0-9a-f]{64}/([0-9a-f]{64}|\\$)")) {
×
71
            String pubkey = req.replaceFirst("/list/([0-9a-f]{64})/([0-9a-f]{64}|\\$)", "$1");
×
72
            String type = req.replaceFirst("/list/([0-9a-f]{64})/([0-9a-f]{64}|\\$)", "$2");
×
73

74
            if (TYPE_JELLY.equals(format)) {
×
75
                // Determine start position from afterChecksums parameter (comma-separated, geometric fallback)
76
                long afterPosition = -1;
×
77
                String afterChecksums = getParam("afterChecksums", null);
×
78
                if (afterChecksums != null) {
×
79
                    for (String checksum : afterChecksums.split(",")) {
×
80
                        checksum = checksum.trim();
×
81
                        if (checksum.isEmpty()) continue;
×
82
                        Document match = collection("listEntries").find(mongoSession,
×
83
                                new Document("pubkey", pubkey).append("type", type).append("checksum", checksum)).first();
×
84
                        if (match != null) {
×
85
                            long matchPos = match.getLong("position");
×
86
                            if (matchPos > afterPosition) {
×
87
                                afterPosition = matchPos;
×
88
                            }
89
                        }
90
                    }
91
                }
92

93
                // Build pipeline with optional position filter
94
                Document matchFilter = new Document("pubkey", pubkey).append("type", type);
×
95
                if (afterPosition >= 0) {
×
96
                    matchFilter.append("position", new Document("$gt", afterPosition));
×
97
                }
98
                List<Bson> pipeline = List.of(match(matchFilter), sort(ascending("position")),
×
99
                        lookup("nanopubs", "np", "_id", "nanopub"), project(new Document("jelly", "$nanopub.jelly")), unwind("$jelly"));
×
100
                try (var result = collection("listEntries").aggregate(mongoSession, pipeline).cursor()) {
×
101
                    NanopubStream npStream = NanopubStream.fromMongoCursor(result);
×
102
                    BufferOutputStream outputStream = new BufferOutputStream();
×
103
                    npStream.writeToByteStream(outputStream);
×
104
                    context.response().write(outputStream.getBuffer());
×
105
                }
106
            } else {
×
107
                MongoCursor<Document> c = collection("listEntries").find(mongoSession, new Document("pubkey", pubkey).append("type", type)).projection(exclude("_id")).sort(ascending("position")).cursor();
×
108

109
                if (TYPE_JSON.equals(format)) {
×
110
                    println("[");
×
111
                    while (c.hasNext()) {
×
112
                        Document d = c.next();
×
113
                        // Transforming long to int, so the JSON output looks nice:
114
                        // TODO Make this scale beyond the int range
115
                        d.replace("position", d.getLong("position").intValue());
×
116
                        print(d.toJson());
×
117
                        println(c.hasNext() ? "," : "");
×
118
                    }
×
119
                    println("]");
×
120
                } else {
121
                    printHtmlHeader("List for pubkey " + getLabel(pubkey) + " / type " + getLabel(type) + " - Nanopub Registry");
×
122
                    println("<h1>List</h1>");
×
123
                    println("<p><a href=\"/list/" + pubkey + "\">&lt; Pubkey</a></p>");
×
124
                    println("<h3>Formats</h3>");
×
125
                    println("<p>");
×
126
                    println("<a href=\"/list/" + pubkey + "/" + type + ".json\">.json</a> |");
×
127
                    println("<a href=\"/list/" + pubkey + "/" + type + ".json.txt\">.json.txt</a>");
×
128
                    println("</p>");
×
129
                    println("<h3>Pubkey Hash</h3>");
×
130
                    println("<p><code>" + pubkey + "</code></p>");
×
131
                    println("<h3>Type Hash</h3>");
×
132
                    println("<p><code>" + type + "</code></p>");
×
133
                    println("<h3>Entries</h3>");
×
134
                    println("<ol>");
×
135
                    while (c.hasNext()) {
×
136
                        Document d = c.next();
×
137
                        println("<li><a href=\"/np/" + d.getString("np") + "\"><code>" + getLabel(d.getString("np")) + "</code></a></li>");
×
138
                    }
×
139
                    println("</ol>");
×
140
                    printHtmlFooter();
×
141
                }
142
            }
143
        } else if (req.matches("/list/[0-9a-f]{64}")) {
×
144
            String pubkey = req.replaceFirst("/list/([0-9a-f]{64})", "$1");
×
145
            MongoCursor<Document> c = collection("lists").find(mongoSession, new Document("pubkey", pubkey)).projection(exclude("_id")).cursor();
×
146
            if (TYPE_JSON.equals(format)) {
×
147
                println("[");
×
148
                while (c.hasNext()) {
×
149
                    print(c.next().toJson());
×
150
                    println(c.hasNext() ? "," : "");
×
151
                }
152
                println("]");
×
153
            } else {
154
                printHtmlHeader("Accounts for Pubkey " + getLabel(pubkey) + " - Nanopub Registry");
×
155
                println("<h1>Accounts for Pubkey " + getLabel(pubkey) + "</h1>");
×
156
                println("<p><a href=\"/list\">&lt; Account List</a></p>");
×
157
                println("<h3>Formats</h3>");
×
158
                println("<p>");
×
159
                println("<a href=\"/list/" + pubkey + ".json\">.json</a> |");
×
160
                println("<a href=\"/list/" + pubkey + ".json.txt\">.json.txt</a>");
×
161
                println("</p>");
×
162
                println("<h3>Pubkey Hash</h3>");
×
163
                println("<p><code>" + pubkey + "</code></p>");
×
164
                println("<h3>Entry Lists</h3>");
×
165
                println("<ol>");
×
166
                while (c.hasNext()) {
×
167
                    Document d = c.next();
×
168
                    String type = d.getString("type");
×
169
                    println("<li>");
×
170
                    println("<a href=\"/list/" + pubkey + "/" + type + "\"><code>" + getLabel(type) + "</code></a> ");
×
171
                    if (type.equals("$")) {
×
172
                        println("(all types)");
×
173
                    } else {
174
                        println("(type " + unhash(type) + ")");
×
175
                    }
176
                    println("</li>");
×
177
                }
×
178
                println("</ol>");
×
179
                printHtmlFooter();
×
180
            }
181
        } else if (req.equals("/list")) {
×
182
            try (var c = collection(Collection.ACCOUNTS.toString()).find(mongoSession).sort(ascending("pubkey")).projection(exclude("_id")).cursor()) {
×
183
                if (TYPE_JSON.equals(format)) {
×
184
                    println("[");
×
185
                    while (c.hasNext()) {
×
186
                        print(c.next().toJson());
×
187
                        println(c.hasNext() ? "," : "");
×
188
                    }
189
                    println("]");
×
190
                } else {
191
                    printHtmlHeader("Account List - Nanopub Registry");
×
192
                    println("<h1>Account List</h1>");
×
193
                    println("<p><a href=\"/\">&lt; Home</a></p>");
×
194
                    println("<h3>Formats</h3>");
×
195
                    println("<p>");
×
196
                    println("<a href=\"list.json\">.json</a> |");
×
197
                    println("<a href=\"list.json.txt\">.json.txt</a>");
×
198
                    println("</p>");
×
199
                    println("<h3>Accounts</h3>");
×
200
                    println("<ol>");
×
201
                    while (c.hasNext()) {
×
202
                        Document d = c.next();
×
203
                        String pubkey = d.getString("pubkey");
×
204
                        if (!pubkey.equals("$")) {
×
205
                            println("<li>");
×
206
                            println("<a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>");
×
207
                            String a = d.getString("agent");
×
208
                            print(" by <a href=\"/agent?id=" + URLEncoder.encode(a, "UTF-8") + "\">" + Utils.getAgentLabel(a) + "</a>");
×
209
                            print(", status: " + d.get("status"));
×
210
                            print(", depth: " + d.get("depth"));
×
211
                            if (d.get("pathCount") != null) {
×
212
                                print(", pathCount: " + d.get("pathCount"));
×
213
                            }
214
                            if (d.get("ratio") != null) {
×
215
                                print(", ratio: " + df8.format(d.get("ratio")));
×
216
                            }
217
                            if (d.get("quota") != null) {
×
218
                                print(", quota: " + d.get("quota"));
×
219
                            }
220
                            println("");
×
221
                            println("</li>");
×
222
                        }
223
                    }
×
224
                    println("</ol>");
×
225
                    printHtmlFooter();
×
226
                }
227
            }
228
        } else if (req.equals("/agent") && context.request().getParam("id") != null) {
×
229
            String agentId = context.request().getParam("id");
×
230
            if (TYPE_JSON.equals(format)) {
×
231
                print(AgentInfo.get(mongoSession, agentId).asJson());
×
232
            } else {
233
                Document agentDoc = RegistryDB.getOne(mongoSession, Collection.AGENTS.toString(), new Document("agent", agentId));
×
234
                printHtmlHeader("Agent " + Utils.getAgentLabel(agentId) + " - Nanopub Registry");
×
235
                println("<h1>Agent " + Utils.getAgentLabel(agentId) + "</h1>");
×
236
                println("<p><a href=\"/agents\">&lt; Agent List</a></p>");
×
237
                println("<h3>Formats</h3>");
×
238
                println("<p>");
×
239
                println("<a href=\"agent.json?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json</a> |");
×
240
                println("<a href=\"agent.json.txt?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json.txt</a>");
×
241
                println("</p>");
×
242
                println("<h3>ID</h3>");
×
243
                println("<p><a href=\"" + agentId + "\"><code>" + agentId + "</code></a></p>");
×
244
                println("<h3>Properties</h3>");
×
245
                println("<ul>");
×
246
                println("<li>Average path count: " + agentDoc.get("avgPathCount") + "</li>");
×
247
                println("<li>Total ratio: " + agentDoc.get("totalRatio") + "</li>");
×
248
                println("</ul>");
×
249
                println("<h3>Accounts</h3>");
×
250
                println("<p>Count: " + agentDoc.get("accountCount") + "</p>");
×
251
                println("<p><a href=\"agentAccounts?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">&gt; agentAccounts</a></p>");
×
252
                printHtmlFooter();
×
253
            }
254
        } else if (req.equals("/agentAccounts") && context.request().getParam("id") != null) {
×
255
            String agentId = context.request().getParam("id");
×
256
            MongoCursor<Document> c = collection(Collection.ACCOUNTS.toString()).find(mongoSession, new Document("agent", agentId)).projection(exclude("_id")).cursor();
×
257
            if (TYPE_JSON.equals(format)) {
×
258
                println("[");
×
259
                while (c.hasNext()) {
×
260
                    print(c.next().toJson());
×
261
                    println(c.hasNext() ? "," : "");
×
262
                }
263
                println("]");
×
264
            } else {
265
                printHtmlHeader("Accounts of Agent " + Utils.getAgentLabel(agentId) + " - Nanopub Registry");
×
266
                println("<h1>Accounts of Agent " + Utils.getAgentLabel(agentId) + "</h1>");
×
267
                println("<p><a href=\"/agent?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">&lt; Agent</a></p>");
×
268
                println("<h3>Formats</h3>");
×
269
                println("<p>");
×
270
                println("<a href=\"agentAccounts.json?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json</a> |");
×
271
                println("<a href=\"agentAccounts.json.txt?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json.txt</a>");
×
272
                println("</p>");
×
273
                println("<h3>Account List</h3>");
×
274
                println("<ul>");
×
275
                while (c.hasNext()) {
×
276
                    Document d = c.next();
×
277
                    String pubkey = d.getString("pubkey");
×
278
                    //                                Object iCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", INTRO_TYPE_HASH), "position");
279
                    //                                Object eCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", ENDORSE_TYPE), "position");
280
                    //                                Object fCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", "$"), "position");
281
                    println("<li><a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a> (" + d.get("status") + "), " + "quota " + d.get("quota") + ", " + "ratio " + df8.format(d.get("ratio")) + ", " + "path count " + d.get("pathCount") + "</li>");
×
282
                }
×
283
                println("</ul>");
×
284
                printHtmlFooter();
×
285
            }
286
        } else if (req.equals("/agents")) {
×
287
            MongoCursor<Document> c = collection(Collection.AGENTS.toString()).find(mongoSession).sort(descending("totalRatio")).projection(exclude("_id")).cursor();
×
288
            if (TYPE_JSON.equals(format)) {
×
289
                println("[");
×
290
                while (c.hasNext()) {
×
291
                    print(c.next().toJson());
×
292
                    println(c.hasNext() ? "," : "");
×
293
                }
294
                println("]");
×
295
            } else {
296
                printHtmlHeader("Agent List - Nanopub Registry");
×
297
                println("<h1>Agent List</h1>");
×
298
                println("<p><a href=\"/\">&lt; Home</a></p>");
×
299
                println("<h3>Formats</h3>");
×
300
                println("<p>");
×
301
                println("<a href=\"agents.json\">.json</a> |");
×
302
                println("<a href=\"agents.json.txt\">.json.txt</a>");
×
303
                println("</p>");
×
304
                println("<h3>Agents</h3>");
×
305
                println("<ol>");
×
306
                while (c.hasNext()) {
×
307
                    Document d = c.next();
×
308
                    if (d.get("agent").equals("$")) continue;
×
309
                    String a = d.getString("agent");
×
310
                    int accountCount = d.getInteger("accountCount");
×
311
                    println("<li><a href=\"/agent?id=" + URLEncoder.encode(a, "UTF-8") + "\">" + Utils.getAgentLabel(a) + "</a>, " + accountCount + " account" + (accountCount == 1 ? "" : "s") + ", " + "ratio " + df8.format(d.get("totalRatio")) + ", " + "avg. path count " + df1.format(d.get("avgPathCount")) + "</li>");
×
312
                }
×
313
                println("</ol>");
×
314
                printHtmlFooter();
×
315
            }
316
        } else if (req.equals("/nanopubs")) {
×
317
            if (TYPE_JELLY.equals(format)) {
×
318
                // Return all nanopubs after seqNum X (-1 by default)
319
                // TODO(transition): Remove afterCounter fallback after all peers upgraded
320
                long afterSeqNum;
321
                try {
322
                    afterSeqNum = Long.parseLong(getParam("afterSeqNum",
×
323
                            getParam("afterCounter", "-1")));
×
324
                } catch (NumberFormatException ex) {
×
325
                    context.response().setStatusCode(400).setStatusMessage("Invalid afterSeqNum parameter.");
×
326
                    return;
×
327
                }
×
328
                var pipeline = collection(Collection.NANOPUBS.toString()).find(mongoSession).filter(gt("seqNum", afterSeqNum)).sort(ascending("seqNum"))
×
329
                        // TODO(transition): Change "counter" to "seqNum" once nanopub-java library is updated
330
                        // NanopubStream.fromMongoCursorWithCounter reads the hardcoded "counter" field
331
                        .projection(include("jelly", "counter"));
×
332

333
                try (var result = pipeline.cursor()) {
×
334
                    NanopubStream npStream = NanopubStream.fromMongoCursorWithCounter(result);
×
335
                    BufferOutputStream outputStream = new BufferOutputStream();
×
336
                    npStream.writeToByteStream(outputStream);
×
337
                    context.response().write(outputStream.getBuffer());
×
338
                }
339
            } else {
×
340
                // Return nanopubs as streamed JSON or HTML
341
                String sortParam = getParam("sort", "date");
×
342

343
                if (TYPE_JSON.equals(format)) {
×
344
                    Bson filter;
345
                    Bson sort;
346
                    if ("id".equals(sortParam)) {
×
347
                        String afterId = getParam("after", "");
×
348
                        filter = afterId.isEmpty() ? new Document() : gt("_id", afterId);
×
349
                        sort = ascending("_id");
×
350
                    } else {
×
351
                        // sort=date (default): latest first, using indexed seqNum field
352
                        filter = new Document();
×
353
                        sort = descending("seqNum");
×
354
                    }
355
                    try (MongoCursor<Document> c = collection(Collection.NANOPUBS.toString()).find(mongoSession)
×
356
                            .filter(filter).sort(sort)
×
357
                            .projection(include("_id")).cursor()) {
×
358
                        println("[");
×
359
                        boolean first = true;
×
360
                        while (c.hasNext()) {
×
361
                            if (!first) println(",");
×
362
                            first = false;
×
363
                            print(gson.toJson(c.next().getString("_id")));
×
364
                        }
365
                        println("\n]");
×
366
                    }
367
                } else {
×
368
                    printHtmlHeader("Nanopubs - Nanopub Registry");
×
369
                    println("<h1>Nanopubs</h1>");
×
370
                    println("<p><a href=\"/\">&lt; Home</a></p>");
×
371
                    println("<h3>All Nanopub IDs (JSON, latest first)</h3>");
×
372
                    println("<p>");
×
373
                    println("<a href=\"nanopubs.json\">.json</a> |");
×
374
                    println("<a href=\"nanopubs.json.txt\">.json.txt</a>");
×
375
                    println("</p>");
×
376
                    println("<h3>All Nanopub IDs (JSON, sorted by artifact code)</h3>");
×
377
                    println("<p>");
×
378
                    println("<a href=\"nanopubs.json?sort=id\">.json</a> |");
×
379
                    println("<a href=\"nanopubs.json.txt?sort=id\">.json.txt</a>");
×
380
                    println("</p>");
×
381
                    println("<h3>All Nanopubs (Jelly)</h3>");
×
382
                    println("<p><a href=\"nanopubs.jelly\">.jelly</a></p>");
×
383
                    println("<h3>Latest Nanopubs (max. 1000)</h3>");
×
384
                    println("<ol>");
×
385
                    try (MongoCursor<Document> c = collection(Collection.NANOPUBS.toString()).find(mongoSession)
×
386
                            .sort(descending("seqNum")).limit(1000).cursor()) {
×
387
                        while (c.hasNext()) {
×
388
                            String npId = c.next().getString("_id");
×
389
                            println("<li><a href=\"/np/" + npId + "\"><code>" + getLabel(npId) + "</code></a></li>");
×
390
                        }
×
391
                    }
392
                    println("</ol>");
×
393
                    printHtmlFooter();
×
394
                }
395
            }
×
396
        } else if (req.equals("/pubkeys")) {
×
397
            try (var c = collection("lists").distinct(mongoSession, "pubkey", String.class).cursor()) {
×
398
                if (TYPE_JSON.equals(format)) {
×
399
                    println("[");
×
400
                    while (c.hasNext()) {
×
401
                        print(gson.toJson(c.next()));
×
402
                        println(c.hasNext() ? "," : "");
×
403
                    }
404
                    println("]");
×
405
                } else {
406
                    printHtmlHeader("Pubkey List - Nanopub Registry");
×
407
                    println("<h1>Pubkey List</h1>");
×
408
                    println("<p><a href=\"/\">&lt; Home</a></p>");
×
409
                    println("<h3>Formats</h3>");
×
410
                    println("<p>");
×
411
                    println("<a href=\"pubkeys.json\">.json</a> |");
×
412
                    println("<a href=\"pubkeys.json.txt\">.json.txt</a>");
×
413
                    println("</p>");
×
414
                    println("<h3>Pubkeys</h3>");
×
415
                    println("<ol>");
×
416
                    while (c.hasNext()) {
×
417
                        String pubkey = c.next();
×
418
                        if (!pubkey.equals("$")) {
×
419
                            println("<li>");
×
420
                            println("<a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>");
×
421
                            println("</li>");
×
422
                        }
423
                    }
×
424
                    println("</ol>");
×
425
                    printHtmlFooter();
×
426
                }
427
            }
428
        } else {
429
            context.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
×
430
        }
431
    }
×
432

433
    private static String getLabel(Object obj) {
434
        if (obj == null) return null;
×
435
        if (obj.toString().length() < 10) return obj.toString();
×
436
        return obj.toString().substring(0, 10);
×
437
    }
438

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