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

knowledgepixels / nanopub-registry / 23541488268

25 Mar 2026 12:40PM UTC coverage: 31.704% (+1.1%) from 30.645%
23541488268

push

github

web-flow
Merge pull request #89 from knowledgepixels/perf/db-operation-improvements

perf: optimize DB operations for replication speed

206 of 730 branches covered (28.22%)

Branch coverage included in aggregate %.

689 of 2093 relevant lines covered (32.92%)

5.65 hits per line

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

10.12
src/main/java/com/knowledgepixels/registry/NanopubLoader.java
1
package com.knowledgepixels.registry;
2

3
import com.mongodb.ErrorCategory;
4
import com.mongodb.MongoWriteException;
5
import com.mongodb.client.ClientSession;
6
import com.mongodb.client.MongoCursor;
7
import net.trustyuri.TrustyUriUtils;
8
import net.trustyuri.rdf.RdfModule;
9
import org.apache.http.Header;
10
import org.apache.http.HttpResponse;
11
import org.apache.http.client.HttpClient;
12
import org.apache.http.client.methods.CloseableHttpResponse;
13
import org.apache.http.client.methods.HttpGet;
14
import org.apache.http.util.EntityUtils;
15
import org.bson.Document;
16
import org.bson.types.Binary;
17
import org.eclipse.rdf4j.common.exception.RDF4JException;
18
import org.eclipse.rdf4j.rio.RDFFormat;
19
import org.nanopub.MalformedNanopubException;
20
import org.nanopub.Nanopub;
21
import org.nanopub.NanopubImpl;
22
import org.nanopub.NanopubUtils;
23
import org.nanopub.extra.server.GetNanopub;
24
import org.nanopub.jelly.JellyUtils;
25
import org.nanopub.jelly.MaybeNanopub;
26
import org.nanopub.jelly.NanopubStream;
27
import org.nanopub.trusty.TrustyNanopubUtils;
28
import org.nanopub.vocabulary.NPX;
29
import org.slf4j.Logger;
30
import org.slf4j.LoggerFactory;
31

32
import java.io.IOException;
33
import java.io.InputStream;
34
import java.util.ArrayList;
35
import java.util.Collections;
36
import java.util.List;
37
import java.util.stream.Stream;
38

39
import static com.knowledgepixels.registry.RegistryDB.has;
40
import static com.knowledgepixels.registry.RegistryDB.insert;
41

42
public class NanopubLoader {
43

44
    private NanopubLoader() {
45
    }
46

47
    public final static String INTRO_TYPE = NPX.DECLARED_BY.stringValue();
9✔
48
    public final static String INTRO_TYPE_HASH = Utils.getHash(INTRO_TYPE);
9✔
49
    public final static String ENDORSE_TYPE = Utils.APPROVES_OF.stringValue();
9✔
50
    public final static String ENDORSE_TYPE_HASH = Utils.getHash(ENDORSE_TYPE);
9✔
51
    private static final Logger log = LoggerFactory.getLogger(NanopubLoader.class);
12✔
52

53
    // TODO Distinguish and support these cases:
54
    //      1. Simple load: load to all core lists if pubkey is "core-loaded", or load to all lists if pubkey is "full-loaded"
55
    //      2. Core load: load to all core lists (initialize if needed), or load to all lists if pubkey is "full-loaded"
56
    //      3. Full load: load to all lists (initialize if needed)
57

58
    public static void simpleLoad(ClientSession mongoSession, String nanopubId) {
59
        simpleLoad(mongoSession, retrieveNanopub(mongoSession, nanopubId));
×
60
    }
×
61

62
    public static void simpleLoad(ClientSession mongoSession, Nanopub np) {
63
        String pubkey = RegistryDB.getPubkey(np);
×
64
        if (pubkey == null) {
×
65
            log.info("Ignore (not signed): {}", np.getUri());
×
66
            return;
×
67
        }
68
        simpleLoad(mongoSession, np, pubkey);
×
69
    }
×
70

71
    /**
72
     * Loads a nanopub to the appropriate lists, using a pre-verified public key
73
     * to skip redundant signature verification.
74
     */
75
    public static void simpleLoad(ClientSession mongoSession, Nanopub np, String verifiedPubkey) {
76
        String pubkeyHash = Utils.getHash(verifiedPubkey);
9✔
77
        // TODO Do we need to load anything else here, into the other DB collections?
78
        if (has(mongoSession, "lists", new Document("pubkey", pubkeyHash).append("type", "$").append("status", "loaded"))) {
45!
79
            RegistryDB.loadNanopubVerified(mongoSession, np, verifiedPubkey, pubkeyHash, "$");
×
80
        } else if (has(mongoSession, "lists", new Document("pubkey", pubkeyHash).append("type", INTRO_TYPE_HASH).append("status", "loaded"))) {
45!
81
            RegistryDB.loadNanopubVerified(mongoSession, np, verifiedPubkey, pubkeyHash, INTRO_TYPE, ENDORSE_TYPE);
×
82
        } else if (!has(mongoSession, "lists", new Document("pubkey", pubkeyHash).append("type", INTRO_TYPE_HASH))) {
36!
83
            // Unknown pubkey: create encountered intro list so RUN_OPTIONAL_LOAD picks it up
84
            try {
85
                insert(mongoSession, "lists", new Document("pubkey", pubkeyHash)
30✔
86
                        .append("type", INTRO_TYPE_HASH)
9✔
87
                        .append("status", EntryStatus.encountered.getValue()));
6✔
88
            } catch (MongoWriteException e) {
×
89
                if (e.getError().getCategory() != ErrorCategory.DUPLICATE_KEY) throw e;
×
90
            }
3✔
91
        }
92
    }
3✔
93

94
    /**
95
     * Retrieve Nanopubs from the peers of this Nanopub Registry.
96
     *
97
     * @param typeHash   The hash of the type of the Nanopub to retrieve.
98
     * @param pubkeyHash The hash of the pubkey of the Nanopub to retrieve.
99
     * @return A stream of MaybeNanopub objects, or an empty stream if no peer is available.
100
     */
101
    public static Stream<MaybeNanopub> retrieveNanopubsFromPeers(String typeHash, String pubkeyHash) {
102
        return retrieveNanopubsFromPeers(typeHash, pubkeyHash, null);
×
103
    }
104

105
    /**
106
     * Retrieve Nanopubs from the peers, optionally skipping ahead using checksums.
107
     *
108
     * @param typeHash        The hash of the type of the Nanopub to retrieve.
109
     * @param pubkeyHash      The hash of the pubkey of the Nanopub to retrieve.
110
     * @param afterChecksums  Comma-separated checksums for skip-ahead (geometric fallback), or null for full fetch.
111
     * @return A stream of MaybeNanopub objects, or an empty stream if no peer is available.
112
     */
113
    public static Stream<MaybeNanopub> retrieveNanopubsFromPeers(String typeHash, String pubkeyHash, String afterChecksums) {
114
        // TODO Move the code of this method to nanopub-java library.
115

116
        List<String> peerUrlsToTry = new ArrayList<>(Utils.getPeerUrls());
×
117
        Collections.shuffle(peerUrlsToTry);
×
118
        while (!peerUrlsToTry.isEmpty()) {
×
119
            String peerUrl = peerUrlsToTry.removeFirst();
×
120

121
            String requestUrl = peerUrl + "list/" + pubkeyHash + "/" + typeHash + ".jelly";
×
122
            if (afterChecksums != null) {
×
123
                requestUrl += "?afterChecksums=" + afterChecksums;
×
124
            }
125
            log.info("Request: {}", requestUrl);
×
126
            try {
127
                CloseableHttpResponse resp = NanopubUtils.getHttpClient().execute(new HttpGet(requestUrl));
×
128
                int httpStatus = resp.getStatusLine().getStatusCode();
×
129
                if (httpStatus < 200 || httpStatus >= 300) {
×
130
                    log.info("Request failed: {} {}", peerUrl, httpStatus);
×
131
                    EntityUtils.consumeQuietly(resp.getEntity());
×
132
                    continue;
×
133
                }
134
                Header nrStatus = resp.getFirstHeader("Nanopub-Registry-Status");
×
135
                if (nrStatus == null) {
×
136
                    log.info("Nanopub-Registry-Status header not found at: {}", peerUrl);
×
137
                    EntityUtils.consumeQuietly(resp.getEntity());
×
138
                    continue;
×
139
                } else if (!nrStatus.getValue().equals("ready") && !nrStatus.getValue().equals("updating")) {
×
140
                    log.info("Peer in non-ready state: {} {}", peerUrl, nrStatus.getValue());
×
141
                    EntityUtils.consumeQuietly(resp.getEntity());
×
142
                    continue;
×
143
                }
144
                InputStream is = resp.getEntity().getContent();
×
145
                return NanopubStream.fromByteStream(is).getAsNanopubs().onClose(() -> {
×
146
                    try {
147
                        resp.close();
×
148
                    } catch (IOException e) {
×
149
                        log.debug("Error closing HTTP response", e);
×
150
                    }
×
151
                });
×
152
            } catch (UnsupportedOperationException | IOException ex) {
×
153
                log.info("Request failed: ", ex);
×
154
            }
155
        }
×
156
        return Stream.empty();
×
157
    }
158

159
    public static Nanopub retrieveNanopub(ClientSession mongoSession, String nanopubId) {
160
        Nanopub np = retrieveLocalNanopub(mongoSession, nanopubId);
×
161
        int tryCount = 0;
×
162
        while (np == null) {
×
163
            if (tryCount > 10) {
×
164
                throw new RuntimeException("Could not load nanopub: " + nanopubId);
×
165
            } else if (tryCount > 0) {
×
166
                try {
167
                    Thread.sleep(100);
×
168
                } catch (InterruptedException ex) {
×
169
                    log.info("Thread was interrupted", ex);
×
170
                }
×
171
            }
172
            log.info("Loading {}", nanopubId);
×
173

174
            // TODO Reach out to other Nanopub Registries here:
175
            np = getNanopub(nanopubId);
×
176
            if (np != null) {
×
177
                RegistryDB.loadNanopub(mongoSession, np);
×
178
            } else {
179
                tryCount = tryCount + 1;
×
180
            }
181
        }
182
        return np;
×
183
    }
184

185
    public static Nanopub retrieveLocalNanopub(ClientSession mongoSession, String nanopubId) {
186
        String ac = TrustyUriUtils.getArtifactCode(nanopubId);
×
187
        MongoCursor<Document> cursor = RegistryDB.get(mongoSession, Collection.NANOPUBS.toString(), new Document("_id", ac));
×
188
        if (!cursor.hasNext()) return null;
×
189
        try {
190
            // Parse from Jelly, not TriG (it's faster)
191
            return JellyUtils.readFromDB(((Binary) cursor.next().get("jelly")).getData());
×
192
        } catch (RDF4JException | MalformedNanopubException ex) {
×
193
            log.info("Exception reading Jelly", ex);
×
194
            return null;
×
195
        }
196
    }
197

198
    // TODO Provide this method in nanopub-java (GetNanopub)
199
    private static Nanopub getNanopub(String uriOrArtifactCode) {
200
        List<String> peerUrls = new ArrayList<>(Utils.getPeerUrls());
×
201
        Collections.shuffle(peerUrls);
×
202
        String ac = GetNanopub.getArtifactCode(uriOrArtifactCode);
×
203
        if (!ac.startsWith(RdfModule.MODULE_ID)) {
×
204
            throw new IllegalArgumentException("Not a trusty URI of type RA");
×
205
        }
206
        while (!peerUrls.isEmpty()) {
×
207
            String peerUrl = peerUrls.removeFirst();
×
208
            try {
209
                Nanopub np = get(ac, peerUrl, NanopubUtils.getHttpClient());
×
210
                if (np != null) {
×
211
                    return np;
×
212
                }
213
            } catch (IOException | RDF4JException | MalformedNanopubException ex) {
×
214
                // ignore
215
            }
×
216
        }
×
217
        return null;
×
218
    }
219

220
    // TODO Provide this method in nanopub-java (GetNanopub)
221
    private static Nanopub get(String artifactCode, String registryUrl, HttpClient httpClient)
222
            throws IOException, RDF4JException, MalformedNanopubException {
223
        HttpGet get = null;
×
224
        // TODO Get in Jelly format:
225
        String getUrl = registryUrl + "np/" + artifactCode;
×
226
        try {
227
            get = new HttpGet(getUrl);
×
228
        } catch (IllegalArgumentException ex) {
×
229
            throw new IOException("invalid URL: " + getUrl);
×
230
        }
×
231
        get.setHeader("Accept", "application/trig");
×
232
        InputStream in = null;
×
233
        try {
234
            HttpResponse resp = httpClient.execute(get);
×
235
            if (!wasSuccessful(resp)) {
×
236
                EntityUtils.consumeQuietly(resp.getEntity());
×
237
                throw new IOException(resp.getStatusLine().toString());
×
238
            }
239
            in = resp.getEntity().getContent();
×
240
            Nanopub nanopub = new NanopubImpl(in, RDFFormat.TRIG);
×
241
            if (!TrustyNanopubUtils.isValidTrustyNanopub(nanopub)) {
×
242
                throw new MalformedNanopubException("Nanopub is not trusty");
×
243
            }
244
            return nanopub;
×
245
        } finally {
246
            if (in != null) in.close();
×
247
        }
248
    }
249

250
    private static boolean wasSuccessful(HttpResponse resp) {
251
        int c = resp.getStatusLine().getStatusCode();
×
252
        return c >= 200 && c < 300;
×
253
    }
254

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