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

knowledgepixels / nanopub-registry / 22870977169

09 Mar 2026 07:28PM UTC coverage: 27.117% (+1.6%) from 25.557%
22870977169

push

github

web-flow
Merge pull request #75 from knowledgepixels/74-peer-registry-sync

feat: add registry-to-registry peer sync for CHECK_NEW

166 of 676 branches covered (24.56%)

Branch coverage included in aggregate %.

545 of 1946 relevant lines covered (28.01%)

4.97 hits per line

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

3.21
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
        String pubkeyHash = Utils.getHash(pubkey);
×
69
        // TODO Do we need to load anything else here, into the other DB collections?
70
        if (has(mongoSession, "lists", new Document("pubkey", pubkeyHash).append("type", "$").append("status", "loaded"))) {
×
71
            RegistryDB.loadNanopub(mongoSession, np, pubkeyHash, "$");
×
72
        } else if (has(mongoSession, "lists", new Document("pubkey", pubkeyHash).append("type", INTRO_TYPE_HASH).append("status", "loaded"))) {
×
73
            RegistryDB.loadNanopub(mongoSession, np, pubkeyHash, INTRO_TYPE, ENDORSE_TYPE);
×
74
        } else if (!has(mongoSession, "lists", new Document("pubkey", pubkeyHash).append("type", INTRO_TYPE_HASH))) {
×
75
            // Unknown pubkey: create encountered intro list so RUN_OPTIONAL_LOAD picks it up
76
            try {
77
                insert(mongoSession, "lists", new Document("pubkey", pubkeyHash)
×
78
                        .append("type", INTRO_TYPE_HASH)
×
79
                        .append("status", EntryStatus.encountered.getValue()));
×
80
            } catch (MongoWriteException e) {
×
81
                if (e.getError().getCategory() != ErrorCategory.DUPLICATE_KEY) throw e;
×
82
            }
×
83
        }
84
    }
×
85

86
    /**
87
     * Retrieve Nanopubs from the peers of this Nanopub Registry.
88
     *
89
     * @param typeHash   The hash of the type of the Nanopub to retrieve.
90
     * @param pubkeyHash The hash of the pubkey of the Nanopub to retrieve.
91
     * @return A stream of MaybeNanopub objects, or an empty stream if no peer is available.
92
     */
93
    public static Stream<MaybeNanopub> retrieveNanopubsFromPeers(String typeHash, String pubkeyHash) {
94
        // TODO Move the code of this method to nanopub-java library.
95

96
        List<String> peerUrlsToTry = new ArrayList<>(Utils.getPeerUrls());
×
97
        Collections.shuffle(peerUrlsToTry);
×
98
        while (!peerUrlsToTry.isEmpty()) {
×
99
            String peerUrl = peerUrlsToTry.removeFirst();
×
100

101
            String requestUrl = peerUrl + "list/" + pubkeyHash + "/" + typeHash + ".jelly";
×
102
            log.info("Request: {}", requestUrl);
×
103
            try {
104
                CloseableHttpResponse resp = NanopubUtils.getHttpClient().execute(new HttpGet(requestUrl));
×
105
                int httpStatus = resp.getStatusLine().getStatusCode();
×
106
                if (httpStatus < 200 || httpStatus >= 300) {
×
107
                    log.info("Request failed: {} {}", peerUrl, httpStatus);
×
108
                    EntityUtils.consumeQuietly(resp.getEntity());
×
109
                    continue;
×
110
                }
111
                Header nrStatus = resp.getFirstHeader("Nanopub-Registry-Status");
×
112
                if (nrStatus == null) {
×
113
                    log.info("Nanopub-Registry-Status header not found at: {}", peerUrl);
×
114
                    EntityUtils.consumeQuietly(resp.getEntity());
×
115
                    continue;
×
116
                } else if (!nrStatus.getValue().equals("ready") && !nrStatus.getValue().equals("updating")) {
×
117
                    log.info("Peer in non-ready state: {} {}", peerUrl, nrStatus.getValue());
×
118
                    EntityUtils.consumeQuietly(resp.getEntity());
×
119
                    continue;
×
120
                }
121
                InputStream is = resp.getEntity().getContent();
×
122
                return NanopubStream.fromByteStream(is).getAsNanopubs();
×
123
            } catch (UnsupportedOperationException | IOException ex) {
×
124
                log.info("Request failed: ", ex);
×
125
            }
126
        }
×
127
        return Stream.empty();
×
128
    }
129

130
    public static Nanopub retrieveNanopub(ClientSession mongoSession, String nanopubId) {
131
        Nanopub np = retrieveLocalNanopub(mongoSession, nanopubId);
×
132
        int tryCount = 0;
×
133
        while (np == null) {
×
134
            if (tryCount > 10) {
×
135
                throw new RuntimeException("Could not load nanopub: " + nanopubId);
×
136
            } else if (tryCount > 0) {
×
137
                try {
138
                    Thread.sleep(100);
×
139
                } catch (InterruptedException ex) {
×
140
                    log.info("Thread was interrupted", ex);
×
141
                }
×
142
            }
143
            log.info("Loading {}", nanopubId);
×
144

145
            // TODO Reach out to other Nanopub Registries here:
146
            np = getNanopub(nanopubId);
×
147
            if (np != null) {
×
148
                RegistryDB.loadNanopub(mongoSession, np);
×
149
            } else {
150
                tryCount = tryCount + 1;
×
151
            }
152
        }
153
        return np;
×
154
    }
155

156
    public static Nanopub retrieveLocalNanopub(ClientSession mongoSession, String nanopubId) {
157
        String ac = TrustyUriUtils.getArtifactCode(nanopubId);
×
158
        MongoCursor<Document> cursor = RegistryDB.get(mongoSession, Collection.NANOPUBS.toString(), new Document("_id", ac));
×
159
        if (!cursor.hasNext()) return null;
×
160
        try {
161
            // Parse from Jelly, not TriG (it's faster)
162
            return JellyUtils.readFromDB(((Binary) cursor.next().get("jelly")).getData());
×
163
        } catch (RDF4JException | MalformedNanopubException ex) {
×
164
            log.info("Exception reading Jelly", ex);
×
165
            return null;
×
166
        }
167
    }
168

169
    // TODO Provide this method in nanopub-java (GetNanopub)
170
    private static Nanopub getNanopub(String uriOrArtifactCode) {
171
        List<String> peerUrls = new ArrayList<>(Utils.getPeerUrls());
×
172
        Collections.shuffle(peerUrls);
×
173
        String ac = GetNanopub.getArtifactCode(uriOrArtifactCode);
×
174
        if (!ac.startsWith(RdfModule.MODULE_ID)) {
×
175
            throw new IllegalArgumentException("Not a trusty URI of type RA");
×
176
        }
177
        while (!peerUrls.isEmpty()) {
×
178
            String peerUrl = peerUrls.removeFirst();
×
179
            try {
180
                Nanopub np = get(ac, peerUrl, NanopubUtils.getHttpClient());
×
181
                if (np != null) {
×
182
                    return np;
×
183
                }
184
            } catch (IOException | RDF4JException | MalformedNanopubException ex) {
×
185
                // ignore
186
            }
×
187
        }
×
188
        return null;
×
189
    }
190

191
    // TODO Provide this method in nanopub-java (GetNanopub)
192
    private static Nanopub get(String artifactCode, String registryUrl, HttpClient httpClient)
193
            throws IOException, RDF4JException, MalformedNanopubException {
194
        HttpGet get = null;
×
195
        // TODO Get in Jelly format:
196
        String getUrl = registryUrl + "np/" + artifactCode;
×
197
        try {
198
            get = new HttpGet(getUrl);
×
199
        } catch (IllegalArgumentException ex) {
×
200
            throw new IOException("invalid URL: " + getUrl);
×
201
        }
×
202
        get.setHeader("Accept", "application/trig");
×
203
        InputStream in = null;
×
204
        try {
205
            HttpResponse resp = httpClient.execute(get);
×
206
            if (!wasSuccessful(resp)) {
×
207
                EntityUtils.consumeQuietly(resp.getEntity());
×
208
                throw new IOException(resp.getStatusLine().toString());
×
209
            }
210
            in = resp.getEntity().getContent();
×
211
            Nanopub nanopub = new NanopubImpl(in, RDFFormat.TRIG);
×
212
            if (!TrustyNanopubUtils.isValidTrustyNanopub(nanopub)) {
×
213
                throw new MalformedNanopubException("Nanopub is not trusty");
×
214
            }
215
            return nanopub;
×
216
        } finally {
217
            if (in != null) in.close();
×
218
        }
219
    }
220

221
    private static boolean wasSuccessful(HttpResponse resp) {
222
        int c = resp.getStatusLine().getStatusCode();
×
223
        return c >= 200 && c < 300;
×
224
    }
225

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