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

knowledgepixels / nanopub-query / 17671313071

12 Sep 2025 10:05AM UTC coverage: 76.087% (-0.7%) from 76.74%
17671313071

push

github

web-flow
Merge pull request #47 from knowledgepixels/update-nanopub-java

Update nanopub java

231 of 318 branches covered (72.64%)

Branch coverage included in aggregate %.

574 of 740 relevant lines covered (77.57%)

3.92 hits per line

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

82.21
src/main/java/com/knowledgepixels/query/NanopubLoader.java
1
package com.knowledgepixels.query;
2

3
import net.trustyuri.TrustyUriUtils;
4
import org.apache.http.client.HttpClient;
5
import org.apache.http.impl.client.HttpClientBuilder;
6
import org.eclipse.rdf4j.common.exception.RDF4JException;
7
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
8
import org.eclipse.rdf4j.model.*;
9
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
10
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
11
import org.eclipse.rdf4j.model.vocabulary.RDFS;
12
import org.eclipse.rdf4j.query.BindingSet;
13
import org.eclipse.rdf4j.query.QueryLanguage;
14
import org.eclipse.rdf4j.query.TupleQuery;
15
import org.eclipse.rdf4j.query.TupleQueryResult;
16
import org.eclipse.rdf4j.repository.RepositoryConnection;
17
import org.nanopub.Nanopub;
18
import org.nanopub.NanopubUtils;
19
import org.nanopub.SimpleCreatorPattern;
20
import org.nanopub.SimpleTimestampPattern;
21
import org.nanopub.extra.security.KeyDeclaration;
22
import org.nanopub.extra.security.MalformedCryptoElementException;
23
import org.nanopub.extra.security.NanopubSignatureElement;
24
import org.nanopub.extra.security.SignatureUtils;
25
import org.nanopub.extra.server.GetNanopub;
26
import org.nanopub.extra.setting.IntroNanopub;
27
import org.nanopub.vocabulary.NP;
28
import org.nanopub.vocabulary.NPA;
29
import org.nanopub.vocabulary.NPX;
30
import org.nanopub.vocabulary.PAV;
31

32
import java.security.GeneralSecurityException;
33
import java.util.*;
34
import java.util.concurrent.ExecutionException;
35
import java.util.concurrent.Executors;
36
import java.util.concurrent.Future;
37
import java.util.concurrent.ThreadPoolExecutor;
38
import java.util.function.Consumer;
39

40
/**
41
 * Utility class for loading nanopublications into the database.
42
 */
43
public class NanopubLoader {
44

45
    private static HttpClient httpClient;
46
    private static final ThreadPoolExecutor loadingPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(4);
4✔
47
    private Nanopub np;
48
    private NanopubSignatureElement el = null;
3✔
49
    private List<Statement> metaStatements = new ArrayList<>();
5✔
50
    private List<Statement> nanopubStatements = new ArrayList<>();
5✔
51
    private List<Statement> literalStatements = new ArrayList<>();
5✔
52
    private List<Statement> invalidateStatements = new ArrayList<>();
5✔
53
    private List<Statement> textStatements, allStatements;
54
    private Calendar timestamp = null;
3✔
55
    private Statement pubkeyStatement, pubkeyStatementX;
56
    private List<String> notes = new ArrayList<>();
5✔
57
    private boolean aborted = false;
3✔
58

59
    NanopubLoader(Nanopub np, long counter) {
2✔
60
        this.np = np;
3✔
61
        if (counter >= 0) {
4✔
62
            System.err.println("Loading " + counter + ": " + np.getUri());
8✔
63
        } else {
64
            System.err.println("Loading: " + np.getUri());
6✔
65
        }
66

67
        // TODO Ensure proper synchronization and DB rollbacks
68

69
        // TODO Check for null characters ("\0"), which can cause problems in Virtuoso.
70

71
        String ac = TrustyUriUtils.getArtifactCode(np.getUri().toString());
5✔
72
        if (!np.getHeadUri().toString().contains(ac) || !np.getAssertionUri().toString().contains(ac) || !np.getProvenanceUri().toString().contains(ac) || !np.getPubinfoUri().toString().contains(ac)) {
24!
73
            notes.add("could not load nanopub as not all graphs contained the artifact code");
×
74
            aborted = true;
×
75
            return;
×
76
        }
77

78
        try {
79
            el = SignatureUtils.getSignatureElement(np);
4✔
80
        } catch (MalformedCryptoElementException ex) {
×
81
            notes.add("Signature error");
×
82
        }
1✔
83
        if (!hasValidSignature(el)) {
4✔
84
            aborted = true;
3✔
85
            return;
1✔
86
        }
87

88
        pubkeyStatement = vf.createStatement(np.getUri(), NPA.HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY, vf.createLiteral(el.getPublicKeyString()), NPA.GRAPH);
13✔
89
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasValidSignatureForPublicKey, FULL_PUBKEY, npa:graph, meta, full pubkey if signature is valid
90
        metaStatements.add(pubkeyStatement);
6✔
91
        pubkeyStatementX = vf.createStatement(np.getUri(), NPA.HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY_HASH, vf.createLiteral(Utils.createHash(el.getPublicKeyString())), NPA.GRAPH);
14✔
92
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasValidSignatureForPublicKeyHash, PUBKEY_HASH, npa:graph, meta, hex-encoded SHA256 hash if signature is valid
93
        metaStatements.add(pubkeyStatementX);
6✔
94

95
        if (el.getSigners().size() == 1) {  // > 1 is deprecated
6!
96
            metaStatements.add(vf.createStatement(np.getUri(), NPX.SIGNED_BY, el.getSigners().iterator().next(), NPA.GRAPH));
16✔
97
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:signedBy, SIGNER, npa:graph, meta, ID of signer
98
        }
99

100
        Set<IRI> subIris = new HashSet<>();
4✔
101
        Set<IRI> otherNps = new HashSet<>();
4✔
102
        Set<IRI> invalidated = new HashSet<>();
4✔
103
        Set<IRI> retracted = new HashSet<>();
4✔
104
        Set<IRI> superseded = new HashSet<>();
4✔
105
        String combinedLiterals = "";
2✔
106
        for (Statement st : NanopubUtils.getStatements(np)) {
11✔
107
            nanopubStatements.add(st);
5✔
108

109
            if (st.getPredicate().toString().contains(ac)) {
6!
110
                subIris.add(st.getPredicate());
×
111
            } else {
112
                IRI b = getBaseTrustyUri(st.getPredicate());
4✔
113
                if (b != null) otherNps.add(b);
2!
114
            }
115
            if (st.getPredicate().equals(NPX.RETRACTS) && st.getObject() instanceof IRI) {
5!
116
                retracted.add((IRI) st.getObject());
×
117
            }
118
            if (st.getPredicate().equals(NPX.INVALIDATES) && st.getObject() instanceof IRI) {
5!
119
                invalidated.add((IRI) st.getObject());
×
120
            }
121
            if (st.getSubject().equals(np.getUri()) && st.getObject() instanceof IRI) {
10✔
122
                if (st.getPredicate().equals(NPX.SUPERSEDES)) {
5✔
123
                    superseded.add((IRI) st.getObject());
6✔
124
                }
125
                if (st.getObject().toString().matches(".*[^A-Za-z0-9\\-_]RA[A-Za-z0-9\\-_]{43}")) {
6✔
126
                    metaStatements.add(vf.createStatement(np.getUri(), st.getPredicate(), st.getObject(), NPA.NETWORK_GRAPH));
13✔
127
                    // @ADMIN-TRIPLE-TABLE@ NANOPUB1, RELATION, NANOPUB2, npa:networkGraph, meta, any inter-nanopub relation found in NANOPUB1
128
                }
129
                if (st.getContext().equals(np.getPubinfoUri())) {
6✔
130
                    if (st.getPredicate().equals(NPX.INTRODUCES) || st.getPredicate().equals(NPX.DESCRIBES) || st.getPredicate().equals(NPX.EMBEDS)) {
15!
131
                        metaStatements.add(vf.createStatement(np.getUri(), st.getPredicate(), st.getObject(), NPA.GRAPH));
13✔
132
                        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:introduces, THING, npa:graph, meta, when such a triple is present in pubinfo of NANOPUB
133
                        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:describes, THING, npa:graph, meta, when such a triple is present in pubinfo of NANOPUB
134
                        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:embeds, THING, npa:graph, meta, when such a triple is present in pubinfo of NANOPUB
135
                    }
136
                }
137
            }
138
            if (st.getSubject().toString().contains(ac)) {
6✔
139
                subIris.add((IRI) st.getSubject());
7✔
140
            } else {
141
                IRI b = getBaseTrustyUri(st.getSubject());
4✔
142
                if (b != null) otherNps.add(b);
2!
143
            }
144
            if (st.getObject() instanceof IRI) {
4✔
145
                if (st.getObject().toString().contains(ac)) {
6✔
146
                    subIris.add((IRI) st.getObject());
7✔
147
                } else {
148
                    IRI b = getBaseTrustyUri(st.getObject());
4✔
149
                    if (b != null) otherNps.add(b);
6✔
150
                }
1✔
151
            } else {
152
                combinedLiterals += st.getObject().stringValue().replaceAll("\\s+", " ") + "\n";
9✔
153
//                                if (st.getSubject().equals(np.getUri()) && !st.getSubject().equals(HAS_FILTER_LITERAL)) {
154
//                                        literalStatements.add(vf.createStatement(np.getUri(), st.getPredicate(), st.getObject(), LITERAL_GRAPH));
155
//                                } else {
156
//                                        literalStatements.add(vf.createStatement(np.getUri(), HAS_LITERAL, st.getObject(), LITERAL_GRAPH));
157
//                                }
158
            }
159
        }
1✔
160
        subIris.remove(np.getUri());
5✔
161
        subIris.remove(np.getAssertionUri());
5✔
162
        subIris.remove(np.getProvenanceUri());
5✔
163
        subIris.remove(np.getPubinfoUri());
5✔
164
        for (IRI i : subIris) {
10✔
165
            metaStatements.add(vf.createStatement(np.getUri(), NPA.HAS_SUB_IRI, i, NPA.GRAPH));
11✔
166
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasSubIri, SUB_IRI, npa:graph, meta, for any IRI minted in the namespace of the NANOPUB
167
        }
1✔
168
        for (IRI i : otherNps) {
10✔
169
            metaStatements.add(vf.createStatement(np.getUri(), NPA.REFERS_TO_NANOPUB, i, NPA.NETWORK_GRAPH));
11✔
170
            // @ADMIN-TRIPLE-TABLE@ NANOPUB1, npa:refersToNanopub, NANOPUB2, npa:networkGraph, meta, generic inter-nanopub relation
171
        }
1✔
172
        for (IRI i : invalidated) {
6!
173
            invalidateStatements.add(vf.createStatement(np.getUri(), NPX.INVALIDATES, i, NPA.GRAPH));
×
174
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:invalidates, INVALIDATED_NANOPUB, npa:graph, meta, if the NANOPUB retracts or supersedes another nanopub
175
        }
×
176
        for (IRI i : retracted) {
6!
177
            invalidateStatements.add(vf.createStatement(np.getUri(), NPX.INVALIDATES, i, NPA.GRAPH));
×
178
            metaStatements.add(vf.createStatement(np.getUri(), NPX.RETRACTS, i, NPA.GRAPH));
×
179
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:retracts, RETRACTED_NANOPUB, npa:graph, meta, if the NANOPUB retracts another nanopub
180
        }
×
181
        for (IRI i : superseded) {
10✔
182
            invalidateStatements.add(vf.createStatement(np.getUri(), NPX.INVALIDATES, i, NPA.GRAPH));
11✔
183
            metaStatements.add(vf.createStatement(np.getUri(), NPX.SUPERSEDES, i, NPA.GRAPH));
11✔
184
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:supersedes, SUPERSEDED_NANOPUB, npa:graph, meta, if the NANOPUB supersedes another nanopub
185
        }
1✔
186

187
        metaStatements.add(vf.createStatement(np.getUri(), NPA.HAS_HEAD_GRAPH, np.getHeadUri(), NPA.GRAPH));
12✔
188
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasHeadGraph, HEAD_GRAPH, npa:graph, meta, direct link to the head graph of the NANOPUB
189
        metaStatements.add(vf.createStatement(np.getUri(), NPA.HAS_GRAPH, np.getHeadUri(), NPA.GRAPH));
12✔
190
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasGraph, GRAPH, npa:graph, meta, generic link to all four graphs of the given NANOPUB
191
        metaStatements.add(vf.createStatement(np.getUri(), NP.HAS_ASSERTION, np.getAssertionUri(), NPA.GRAPH));
12✔
192
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, np:hasAssertion, ASSERTION_GRAPH, npa:graph, meta, direct link to the assertion graph of the NANOPUB
193
        metaStatements.add(vf.createStatement(np.getUri(), NPA.HAS_GRAPH, np.getAssertionUri(), NPA.GRAPH));
12✔
194
        metaStatements.add(vf.createStatement(np.getUri(), NP.HAS_PROVENANCE, np.getProvenanceUri(), NPA.GRAPH));
12✔
195
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, np:hasProvenance, PROVENANCE_GRAPH, npa:graph, meta, direct link to the provenance graph of the NANOPUB
196
        metaStatements.add(vf.createStatement(np.getUri(), NPA.HAS_GRAPH, np.getProvenanceUri(), NPA.GRAPH));
12✔
197
        metaStatements.add(vf.createStatement(np.getUri(), NP.HAS_PUBINFO, np.getPubinfoUri(), NPA.GRAPH));
12✔
198
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, np:hasPublicationInfo, PUBINFO_GRAPH, npa:graph, meta, direct link to the pubinfo graph of the NANOPUB
199
        metaStatements.add(vf.createStatement(np.getUri(), NPA.HAS_GRAPH, np.getPubinfoUri(), NPA.GRAPH));
12✔
200

201
        String artifactCode = TrustyUriUtils.getArtifactCode(np.getUri().stringValue());
5✔
202
        metaStatements.add(vf.createStatement(np.getUri(), NPA.ARTIFACT_CODE, vf.createLiteral(artifactCode), NPA.GRAPH));
13✔
203
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:artifactCode, ARTIFACT_CODE, npa:graph, meta, artifact code starting with 'RA...'
204

205
        if (isIntroNanopub(np)) {
3✔
206
            IntroNanopub introNp = new IntroNanopub(np);
5✔
207
            metaStatements.add(vf.createStatement(np.getUri(), NPA.IS_INTRODUCTION_OF, introNp.getUser(), NPA.GRAPH));
12✔
208
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:isIntroductionOf, AGENT, npa:graph, meta, linking intro nanopub to the agent it is introducing
209
            for (KeyDeclaration kc : introNp.getKeyDeclarations()) {
11✔
210
                metaStatements.add(vf.createStatement(np.getUri(), NPA.DECLARES_PUBKEY, vf.createLiteral(kc.getPublicKeyString()), NPA.GRAPH));
14✔
211
                // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:declaresPubkey, FULL_PUBKEY, npa:graph, meta, full pubkey declared by the given intro NANOPUB
212
            }
1✔
213
        }
214

215
        try {
216
            timestamp = SimpleTimestampPattern.getCreationTime(np);
4✔
217
        } catch (IllegalArgumentException ex) {
×
218
            notes.add("Illegal date/time");
×
219
        }
1✔
220
        if (timestamp != null) {
3!
221
            metaStatements.add(vf.createStatement(np.getUri(), DCTERMS.CREATED, vf.createLiteral(timestamp.getTime()), NPA.GRAPH));
15✔
222
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, dct:created, CREATION_DATE, npa:graph, meta, normalized creation timestamp
223
        }
224

225
        String literalFilter = "_pubkey_" + Utils.createHash(el.getPublicKeyString());
6✔
226
        for (IRI typeIri : NanopubUtils.getTypes(np)) {
11✔
227
            metaStatements.add(vf.createStatement(np.getUri(), NPX.HAS_NANOPUB_TYPE, typeIri, NPA.GRAPH));
11✔
228
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:hasNanopubType, NANOPUB_TYPE, npa:graph, meta, type of NANOPUB
229
            literalFilter += " _type_" + Utils.createHash(typeIri);
5✔
230
        }
1✔
231
        String label = NanopubUtils.getLabel(np);
3✔
232
        if (label != null) {
2!
233
            metaStatements.add(vf.createStatement(np.getUri(), RDFS.LABEL, vf.createLiteral(label), NPA.GRAPH));
13✔
234
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, rdfs:label, LABEL, npa:graph, meta, label of NANOPUB
235
        }
236
        String description = NanopubUtils.getDescription(np);
3✔
237
        if (description != null) {
2✔
238
            metaStatements.add(vf.createStatement(np.getUri(), DCTERMS.DESCRIPTION, vf.createLiteral(description), NPA.GRAPH));
13✔
239
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, dct:description, LABEL, npa:graph, meta, description of NANOPUB
240
        }
241
        for (IRI creatorIri : SimpleCreatorPattern.getCreators(np)) {
11✔
242
            metaStatements.add(vf.createStatement(np.getUri(), DCTERMS.CREATOR, creatorIri, NPA.GRAPH));
11✔
243
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, dct:creator, CREATOR, npa:graph, meta, creator of NANOPUB (can be several)
244
        }
1✔
245
        for (IRI authorIri : SimpleCreatorPattern.getAuthors(np)) {
7!
246
            metaStatements.add(vf.createStatement(np.getUri(), PAV.AUTHORED_BY, authorIri, NPA.GRAPH));
×
247
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, pav:authoredBy, AUTHOR, npa:graph, meta, author of NANOPUB (can be several)
248
        }
×
249

250
        if (!combinedLiterals.isEmpty()) {
3!
251
            literalStatements.add(vf.createStatement(np.getUri(), NPA.HAS_FILTER_LITERAL, vf.createLiteral(literalFilter + "\n" + combinedLiterals), NPA.GRAPH));
15✔
252
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasFilterLiteral, FILTER_LITERAL, npa:graph, literal, auxiliary literal for filtering by type and pubkey in text repo
253
        }
254

255
        // Any statements that express that the currently processed nanopub is already invalidated:
256
        List<Statement> invalidatingStatements = getInvalidatingStatements(np.getUri());
4✔
257

258
        metaStatements.addAll(invalidateStatements);
6✔
259

260
        allStatements = new ArrayList<>(nanopubStatements);
7✔
261
        allStatements.addAll(metaStatements);
6✔
262
        allStatements.addAll(invalidatingStatements);
5✔
263

264
        textStatements = new ArrayList<>(literalStatements);
7✔
265
        textStatements.addAll(metaStatements);
6✔
266
        textStatements.addAll(invalidatingStatements);
5✔
267
    }
1✔
268

269
    /**
270
     * Get the HTTP client used for fetching nanopublications.
271
     *
272
     * @return the HTTP client
273
     */
274
    static HttpClient getHttpClient() {
275
        if (httpClient == null) {
2✔
276
            httpClient = HttpClientBuilder.create().setDefaultRequestConfig(Utils.getHttpRequestConfig()).build();
5✔
277
        }
278
        return httpClient;
2✔
279
    }
280

281
    /**
282
     * Load the given nanopublication into the database.
283
     *
284
     * @param nanopubUri Nanopublication identifier (URI)
285
     */
286
    public static void load(String nanopubUri) {
287
        if (isNanopubLoaded(nanopubUri)) {
3✔
288
            System.err.println("Already loaded: " + nanopubUri);
5✔
289
        } else {
290
            Nanopub np = GetNanopub.get(nanopubUri, getHttpClient());
4✔
291
            load(np, -1);
3✔
292
        }
293
    }
1✔
294

295
    /**
296
     * Load a nanopub into the database.
297
     *
298
     * @param np      the nanopub to load
299
     * @param counter the load counter, only used for logging (or -1 if not known)
300
     * @throws RDF4JException if the loading fails
301
     */
302
    public static void load(Nanopub np, long counter) throws RDF4JException {
303
        NanopubLoader loader = new NanopubLoader(np, counter);
6✔
304
        loader.executeLoading();
2✔
305
    }
1✔
306

307
    @GeneratedFlagForDependentElements
308
    private void executeLoading() {
309
        var runningTasks = new ArrayList<Future<?>>();
310
        Consumer<Runnable> runTask = t -> runningTasks.add(loadingPool.submit(t));
×
311

312
        for (String note : notes) {
313
            loadNoteToRepo(np.getUri(), note);
314
        }
315

316
        if (!aborted) {
317
            if (timestamp != null) {
318
                if (new Date().getTime() - timestamp.getTimeInMillis() < THIRTY_DAYS) {
319
                    runTask.accept(() -> loadNanopubToLatest(allStatements));
×
320
                }
321
            }
322

323
            runTask.accept(() -> loadNanopubToRepo(np.getUri(), textStatements, "text"));
×
324
            runTask.accept(() -> loadNanopubToRepo(np.getUri(), allStatements, "full"));
×
325
            runTask.accept(() -> loadNanopubToRepo(np.getUri(), metaStatements, "meta"));
×
326

327
            runTask.accept(() -> loadNanopubToRepo(np.getUri(), allStatements, "pubkey_" + Utils.createHash(el.getPublicKeyString())));
×
328
            //                loadNanopubToRepo(np.getUri(), textStatements, "text-pubkey_" + Utils.createHash(el.getPublicKeyString()));
329
            for (IRI typeIri : NanopubUtils.getTypes(np)) {
330
                // Exclude locally minted IRIs:
331
                if (typeIri.stringValue().startsWith(np.getUri().stringValue())) continue;
332
                if (!typeIri.stringValue().matches("https?://.*")) continue;
333
                runTask.accept(() -> loadNanopubToRepo(np.getUri(), allStatements, "type_" + Utils.createHash(typeIri)));
×
334
                //                        loadNanopubToRepo(np.getUri(), textStatements, "text-type_" + Utils.createHash(typeIri));
335
            }
336
            //                for (IRI creatorIri : SimpleCreatorPattern.getCreators(np)) {
337
            //                        // Exclude locally minted IRIs:
338
            //                        if (creatorIri.stringValue().startsWith(np.getUri().stringValue())) continue;
339
            //                        if (!creatorIri.stringValue().matches("https?://.*")) continue;
340
            //                        loadNanopubToRepo(np.getUri(), allStatements, "user_" + Utils.createHash(creatorIri));
341
            //                        loadNanopubToRepo(np.getUri(), textStatements, "text-user_" + Utils.createHash(creatorIri));
342
            //                }
343
            //                for (IRI authorIri : SimpleCreatorPattern.getAuthors(np)) {
344
            //                        // Exclude locally minted IRIs:
345
            //                        if (authorIri.stringValue().startsWith(np.getUri().stringValue())) continue;
346
            //                        if (!authorIri.stringValue().matches("https?://.*")) continue;
347
            //                        loadNanopubToRepo(np.getUri(), allStatements, "user_" + Utils.createHash(authorIri));
348
            //                        loadNanopubToRepo(np.getUri(), textStatements, "text-user_" + Utils.createHash(authorIri));
349
            //                }
350

351
            for (Statement st : invalidateStatements) {
352
                runTask.accept(() -> loadInvalidateStatements(np, el.getPublicKeyString(), st, pubkeyStatement, pubkeyStatementX));
×
353
            }
354
        }
355

356
        // Wait for all loading tasks to complete before returning
357
        for (var task : runningTasks) {
358
            try {
359
                task.get();
360
            } catch (ExecutionException | InterruptedException ex) {
361
                throw new RuntimeException("Error in nanopub loading thread", ex.getCause());
362
            }
363
        }
364
    }
365

366
    private static Long lastUpdateOfLatestRepo = null;
2✔
367
    private static long THIRTY_DAYS = 1000L * 60 * 60 * 24 * 30;
2✔
368
    private static long ONE_HOUR = 1000L * 60 * 60;
2✔
369

370
    @GeneratedFlagForDependentElements
371
    private static void loadNanopubToLatest(List<Statement> statements) {
372
        boolean success = false;
373
        while (!success) {
374
            RepositoryConnection conn = TripleStore.get().getRepoConnection("last30d");
375
            try (conn) {
376
                // Read committed, because deleting old nanopubs is idempotent. Inserts do not collide
377
                // with deletes, because we are not inserting old nanopubs.
378
                conn.begin(IsolationLevels.READ_COMMITTED);
379
                conn.add(statements);
380
                if (lastUpdateOfLatestRepo == null || new Date().getTime() - lastUpdateOfLatestRepo > ONE_HOUR) {
381
                    //System.err.println("Remove old nanopubs...");
382
                    Literal thirtyDaysAgo = vf.createLiteral(new Date(new Date().getTime() - THIRTY_DAYS));
383
                    TupleQuery q = conn.prepareTupleQuery(QueryLanguage.SPARQL, "SELECT * { graph <" + NPA.GRAPH + "> { " + "?np <" + DCTERMS.CREATED + "> ?date . " + "filter ( ?date < ?thirtydaysago ) " + "} }");
384
                    q.setBinding("thirtydaysago", thirtyDaysAgo);
385
                    try (TupleQueryResult r = q.evaluate()) {
386
                        while (r.hasNext()) {
387
                            BindingSet b = r.next();
388
                            IRI oldNpId = (IRI) b.getBinding("np").getValue();
389
                            //System.err.println("Remove old nanopub: " + oldNpId);
390
                            for (Value v : Utils.getObjectsForPattern(conn, NPA.GRAPH, oldNpId, NPA.HAS_GRAPH)) {
391
                                // Remove all four nanopub graphs:
392
                                conn.remove((Resource) null, (IRI) null, (Value) null, (IRI) v);
393
                            }
394
                            // Remove nanopubs in admin graphs:
395
                            conn.remove(oldNpId, null, null, NPA.GRAPH);
396
                            conn.remove(oldNpId, null, null, NPA.NETWORK_GRAPH);
397
                        }
398
                    }
399
                    lastUpdateOfLatestRepo = new Date().getTime();
400
                }
401
                conn.commit();
402
                success = true;
403
            } catch (Exception ex) {
404
                ex.printStackTrace();
405
                if (conn.isActive()) conn.rollback();
406
            }
407
            if (!success) {
408
                System.err.println("Retrying in 10 second...");
409
                try {
410
                    Thread.sleep(10000);
411
                } catch (InterruptedException x) {
412
                }
413
            }
414
        }
415
    }
416

417
    @GeneratedFlagForDependentElements
418
    private static void loadNanopubToRepo(IRI npId, List<Statement> statements, String repoName) {
419
        boolean success = false;
420
        while (!success) {
421
            RepositoryConnection conn = TripleStore.get().getRepoConnection(repoName);
422
            try (conn) {
423
                // Serializable, because write skew would cause the chain of hashes to be broken.
424
                // The inserts must be done serially.
425
                conn.begin(IsolationLevels.SERIALIZABLE);
426
                var repoStatus = fetchRepoStatus(conn, npId);
427
                if (repoStatus.isLoaded) {
428
                    System.err.println("Already loaded: " + npId);
429
                } else {
430
                    String newChecksum = NanopubUtils.updateXorChecksum(npId, repoStatus.checksum);
431
                    conn.remove(NPA.THIS_REPO, NPA.HAS_NANOPUB_COUNT, null, NPA.GRAPH);
432
                    conn.remove(NPA.THIS_REPO, NPA.HAS_NANOPUB_CHECKSUM, null, NPA.GRAPH);
433
                    conn.add(NPA.THIS_REPO, NPA.HAS_NANOPUB_COUNT, vf.createLiteral(repoStatus.count + 1), NPA.GRAPH);
434
                    // @ADMIN-TRIPLE-TABLE@ REPO, npa:hasNanopubCount, NANOPUB_COUNT, npa:graph, admin, number of nanopubs loaded
435
                    conn.add(NPA.THIS_REPO, NPA.HAS_NANOPUB_CHECKSUM, vf.createLiteral(newChecksum), NPA.GRAPH);
436
                    // @ADMIN-TRIPLE-TABLE@ REPO, npa:hasNanopubChecksum, NANOPUB_CHECKSUM, npa:graph, admin, checksum of all loaded nanopubs (order-independent XOR checksum on trusty URIs in Base64 notation)
437
                    conn.add(npId, NPA.HAS_LOAD_NUMBER, vf.createLiteral(repoStatus.count), NPA.GRAPH);
438
                    // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasLoadNumber, LOAD_NUMBER, npa:graph, admin, the sequential number at which this NANOPUB was loaded
439
                    conn.add(npId, NPA.HAS_LOAD_CHECKSUM, vf.createLiteral(newChecksum), NPA.GRAPH);
440
                    // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasLoadChecksum, LOAD_CHECKSUM, npa:graph, admin, the checksum of all loaded nanopubs after loading the given NANOPUB
441
                    conn.add(npId, NPA.HAS_LOAD_TIMESTAMP, vf.createLiteral(new Date()), NPA.GRAPH);
442
                    // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasLoadTimestamp, LOAD_TIMESTAMP, npa:graph, admin, the time point at which this NANOPUB was loaded
443
                    conn.add(statements);
444
                }
445
                conn.commit();
446
                success = true;
447
            } catch (Exception ex) {
448
                ex.printStackTrace();
449
                if (conn.isActive()) conn.rollback();
450
            }
451
            if (!success) {
452
                System.err.println("Retrying in 10 second...");
453
                try {
454
                    Thread.sleep(10000);
455
                } catch (InterruptedException x) {
456
                }
457
            }
458
        }
459
    }
460

461
    private record RepoStatus(boolean isLoaded, long count, String checksum) {
×
462
    }
463

464
    /**
465
     * To execute before loading a nanopub: check if the nanopub is already loaded and what is the
466
     * current load counter and checksum. This effectively batches three queries into one.
467
     * This method must be called from within a transaction.
468
     *
469
     * @param conn repo connection
470
     * @param npId nanopub ID
471
     * @return the current status
472
     */
473
    @GeneratedFlagForDependentElements
474
    private static RepoStatus fetchRepoStatus(RepositoryConnection conn, IRI npId) {
475
        var result = conn.prepareTupleQuery(QueryLanguage.SPARQL, REPO_STATUS_QUERY_TEMPLATE.formatted(npId)).evaluate();
476
        try (result) {
477
            if (!result.hasNext()) {
478
                // This may happen if the repo was created, but is completely empty.
479
                return new RepoStatus(false, 0, NanopubUtils.INIT_CHECKSUM);
480
            }
481
            var row = result.next();
482
            return new RepoStatus(row.hasBinding("loadNumber"), Long.parseLong(row.getBinding("count").getValue().stringValue()), row.getBinding("checksum").getValue().stringValue());
483
        }
484
    }
485

486
    @GeneratedFlagForDependentElements
487
    private static void loadInvalidateStatements(Nanopub thisNp, String thisPubkey, Statement invalidateStatement, Statement pubkeyStatement, Statement pubkeyStatementX) {
488
        boolean success = false;
489
        while (!success) {
490
            List<RepositoryConnection> connections = new ArrayList<>();
491
            RepositoryConnection metaConn = TripleStore.get().getRepoConnection("meta");
492
            try {
493
                IRI invalidatedNpId = (IRI) invalidateStatement.getObject();
494
                // Basic isolation because here we only read append-only data.
495
                metaConn.begin(IsolationLevels.READ_COMMITTED);
496

497
                Value pubkeyValue = Utils.getObjectForPattern(metaConn, NPA.GRAPH, invalidatedNpId, NPA.HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY);
498
                if (pubkeyValue != null) {
499
                    String pubkey = pubkeyValue.stringValue();
500

501
                    if (!pubkey.equals(thisPubkey)) {
502
                        //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for pubkey " + pubkey);
503
                        connections.add(loadStatements("pubkey_" + Utils.createHash(pubkey), invalidateStatement, pubkeyStatement, pubkeyStatementX));
504
//                                                connections.add(loadStatements("text-pubkey_" + Utils.createHash(pubkey), invalidateStatement, pubkeyStatement));
505
                    }
506

507
                    for (Value v : Utils.getObjectsForPattern(metaConn, NPA.GRAPH, invalidatedNpId, NPX.HAS_NANOPUB_TYPE)) {
508
                        IRI typeIri = (IRI) v;
509
                        // TODO Avoid calling getTypes and getCreators multiple times:
510
                        if (!NanopubUtils.getTypes(thisNp).contains(typeIri)) {
511
                            //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for type " + typeIri);
512
                            connections.add(loadStatements("type_" + Utils.createHash(typeIri), invalidateStatement, pubkeyStatement, pubkeyStatementX));
513
//                                                        connections.add(loadStatements("text-type_" + Utils.createHash(typeIri), invalidateStatement, pubkeyStatement));
514
                        }
515
                    }
516

517
//                                        for (Value v : Utils.getObjectsForPattern(metaConn, NPA.GRAPH, invalidatedNpId, DCTERMS.CREATOR)) {
518
//                                                IRI creatorIri = (IRI) v;
519
//                                                if (!SimpleCreatorPattern.getCreators(thisNp).contains(creatorIri)) {
520
//                                                        //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for user " + creatorIri);
521
//                                                        connections.add(loadStatements("user_" + Utils.createHash(creatorIri), invalidateStatement, pubkeyStatement));
522
//                                                        connections.add(loadStatements("text-user_" + Utils.createHash(creatorIri), invalidateStatement, pubkeyStatement));
523
//                                                }
524
//                                        }
525
                }
526

527
                metaConn.commit();
528
                // TODO handle case that some commits succeed and some fail
529
                for (RepositoryConnection c : connections) c.commit();
530
                success = true;
531
            } catch (Exception ex) {
532
                ex.printStackTrace();
533
                if (metaConn.isActive()) metaConn.rollback();
534
                for (RepositoryConnection c : connections) {
535
                    if (c.isActive()) c.rollback();
536
                }
537
            } finally {
538
                metaConn.close();
539
                for (RepositoryConnection c : connections) c.close();
540
            }
541
            if (!success) {
542
                System.err.println("Retrying in 10 second...");
543
                try {
544
                    Thread.sleep(10000);
545
                } catch (InterruptedException x) {
546
                }
547
            }
548
        }
549
    }
550

551
    @GeneratedFlagForDependentElements
552
    private static RepositoryConnection loadStatements(String repoName, Statement... statements) {
553
        RepositoryConnection conn = TripleStore.get().getRepoConnection(repoName);
554
        // Basic isolation: we only append new statements
555
        conn.begin(IsolationLevels.READ_COMMITTED);
556
        for (Statement st : statements) {
557
            conn.add(st);
558
        }
559
        return conn;
560
    }
561

562
    @GeneratedFlagForDependentElements
563
    static List<Statement> getInvalidatingStatements(IRI npId) {
564
        List<Statement> invalidatingStatements = new ArrayList<>();
565
        boolean success = false;
566
        while (!success) {
567
            RepositoryConnection conn = TripleStore.get().getRepoConnection("meta");
568
            try (conn) {
569
                // Basic isolation because here we only read append-only data.
570
                conn.begin(IsolationLevels.READ_COMMITTED);
571

572
                TupleQueryResult r = conn.prepareTupleQuery(QueryLanguage.SPARQL, "SELECT * { graph <" + NPA.GRAPH + "> { " + "?np <" + NPX.INVALIDATES + "> <" + npId + "> ; <" + NPA.HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY + "> ?pubkey . " + "} }").evaluate();
573
                try (r) {
574
                    while (r.hasNext()) {
575
                        BindingSet b = r.next();
576
                        invalidatingStatements.add(vf.createStatement((IRI) b.getBinding("np").getValue(), NPX.INVALIDATES, npId, NPA.GRAPH));
577
                        invalidatingStatements.add(vf.createStatement((IRI) b.getBinding("np").getValue(), NPA.HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY, b.getBinding("pubkey").getValue(), NPA.GRAPH));
578
                    }
579
                }
580
                conn.commit();
581
                success = true;
582
            } catch (Exception ex) {
583
                ex.printStackTrace();
584
                if (conn.isActive()) conn.rollback();
585
            }
586
            if (!success) {
587
                System.err.println("Retrying in 10 second...");
588
                try {
589
                    Thread.sleep(10000);
590
                } catch (InterruptedException x) {
591
                }
592
            }
593
        }
594
        return invalidatingStatements;
595
    }
596

597
    @GeneratedFlagForDependentElements
598
    private static void loadNoteToRepo(Resource subj, String note) {
599
        boolean success = false;
600
        while (!success) {
601
            RepositoryConnection conn = TripleStore.get().getAdminRepoConnection();
602
            try (conn) {
603
                List<Statement> statements = new ArrayList<>();
604
                statements.add(vf.createStatement(subj, NPA.NOTE, vf.createLiteral(note), NPA.GRAPH));
605
                conn.add(statements);
606
                success = true;
607
            } catch (Exception ex) {
608
                ex.printStackTrace();
609
            }
610
            if (!success) {
611
                System.err.println("Retrying in 10 second...");
612
                try {
613
                    Thread.sleep(10000);
614
                } catch (InterruptedException x) {
615
                }
616
            }
617
        }
618
    }
619

620
    static boolean hasValidSignature(NanopubSignatureElement el) {
621
        try {
622
            if (el != null && SignatureUtils.hasValidSignature(el) && el.getPublicKeyString() != null) {
8!
623
                return true;
2✔
624
            }
625
        } catch (GeneralSecurityException ex) {
1✔
626
            System.err.println("Error for signature element " + el.getUri());
6✔
627
            ex.printStackTrace();
2✔
628
        }
1✔
629
        return false;
2✔
630
    }
631

632
    private static IRI getBaseTrustyUri(Value v) {
633
        if (!(v instanceof IRI)) return null;
3!
634
        String s = v.stringValue();
3✔
635
        if (!s.matches(".*[^A-Za-z0-9\\-_]RA[A-Za-z0-9\\-_]{43}([^A-Za-z0-9\\\\-_].{0,43})?")) {
4✔
636
            return null;
2✔
637
        }
638
        return vf.createIRI(s.replaceFirst("^(.*[^A-Za-z0-9\\-_]RA[A-Za-z0-9\\-_]{43})([^A-Za-z0-9\\\\-_].{0,43})?$", "$1"));
7✔
639
    }
640

641
    // TODO: Move this to nanopub library:
642
    private static boolean isIntroNanopub(Nanopub np) {
643
        for (Statement st : np.getAssertion()) {
11✔
644
            if (st.getPredicate().equals(NPX.DECLARED_BY)) return true;
7✔
645
        }
1✔
646
        return false;
2✔
647
    }
648

649
    /**
650
     * Check if a nanopub is already loaded in the admin graph.
651
     *
652
     * @param npId the nanopub ID
653
     * @return true if the nanopub is loaded, false otherwise
654
     */
655
    @GeneratedFlagForDependentElements
656
    static boolean isNanopubLoaded(String npId) {
657
        boolean loaded = false;
658
        RepositoryConnection conn = TripleStore.get().getRepoConnection("meta");
659
        try (conn) {
660
            if (Utils.getObjectForPattern(conn, NPA.GRAPH, vf.createIRI(npId), NPA.HAS_LOAD_NUMBER) != null) {
661
                loaded = true;
662
            }
663
        } catch (Exception ex) {
664
            ex.printStackTrace();
665
        }
666
        return loaded;
667
    }
668

669
    private static ValueFactory vf = SimpleValueFactory.getInstance();
2✔
670

671
    // TODO remove the constants and use the ones from the nanopub library instead
672

673
    /**
674
     * Template for the query that fetches the status of a repository.
675
     */
676
    // Template for .fetchRepoStatus
677
    private static final String REPO_STATUS_QUERY_TEMPLATE = """
28✔
678
            SELECT * { graph <%s> {
679
              OPTIONAL { <%s> <%s> ?loadNumber . }
680
              <%s> <%s> ?count ;
681
                   <%s> ?checksum .
682
            } }
683
            """.formatted(NPA.GRAPH, "%s", NPA.HAS_LOAD_NUMBER, NPA.THIS_REPO, NPA.HAS_NANOPUB_COUNT, NPA.HAS_NANOPUB_CHECKSUM);
2✔
684
}
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