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

knowledgepixels / nanopub-query / 17121899603

21 Aug 2025 08:33AM UTC coverage: 68.694% (+18.7%) from 49.971%
17121899603

push

github

tkuhn
Add GeneratedFlagForDependentElements annotations

234 of 360 branches covered (65.0%)

Branch coverage included in aggregate %.

613 of 873 relevant lines covered (70.22%)

3.5 hits per line

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

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

3
import java.security.GeneralSecurityException;
4
import java.util.ArrayList;
5
import java.util.Calendar;
6
import java.util.Date;
7
import java.util.HashSet;
8
import java.util.List;
9
import java.util.Set;
10
import java.util.concurrent.ExecutionException;
11
import java.util.concurrent.Executors;
12
import java.util.concurrent.Future;
13
import java.util.concurrent.ThreadPoolExecutor;
14
import java.util.function.Consumer;
15

16
import org.apache.http.client.HttpClient;
17
import org.apache.http.impl.client.HttpClientBuilder;
18
import org.eclipse.rdf4j.common.exception.RDF4JException;
19
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
20
import org.eclipse.rdf4j.model.IRI;
21
import org.eclipse.rdf4j.model.Literal;
22
import org.eclipse.rdf4j.model.Resource;
23
import org.eclipse.rdf4j.model.Statement;
24
import org.eclipse.rdf4j.model.Value;
25
import org.eclipse.rdf4j.model.ValueFactory;
26
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
27
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
28
import org.eclipse.rdf4j.model.vocabulary.RDFS;
29
import org.eclipse.rdf4j.query.BindingSet;
30
import org.eclipse.rdf4j.query.QueryLanguage;
31
import org.eclipse.rdf4j.query.TupleQuery;
32
import org.eclipse.rdf4j.query.TupleQueryResult;
33
import org.eclipse.rdf4j.repository.RepositoryConnection;
34
import org.nanopub.Nanopub;
35
import org.nanopub.NanopubUtils;
36
import org.nanopub.SimpleCreatorPattern;
37
import org.nanopub.SimpleTimestampPattern;
38
import org.nanopub.extra.security.KeyDeclaration;
39
import org.nanopub.extra.security.MalformedCryptoElementException;
40
import org.nanopub.extra.security.NanopubSignatureElement;
41
import org.nanopub.extra.security.SignatureUtils;
42
import org.nanopub.extra.server.GetNanopub;
43
import org.nanopub.extra.setting.IntroNanopub;
44

45
import net.trustyuri.TrustyUriUtils;
46

47
/**
48
 * Utility class for loading nanopublications into the database.
49
 */
50
public class NanopubLoader {
51

52
    private static HttpClient httpClient;
53
    private static final ThreadPoolExecutor loadingPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(4);
4✔
54
    private Nanopub np;
55
    private NanopubSignatureElement el = null;
3✔
56
    private List<Statement> metaStatements = new ArrayList<>();
5✔
57
    private List<Statement> nanopubStatements = new ArrayList<>();
5✔
58
    private List<Statement> literalStatements = new ArrayList<>();
5✔
59
    private List<Statement> invalidateStatements = new ArrayList<>();
5✔
60
    private List<Statement> textStatements, allStatements;
61
    private Calendar timestamp = null;
3✔
62
    private Statement pubkeyStatement, pubkeyStatementX;
63
    private List<String> notes = new ArrayList<>();
5✔
64
    private boolean aborted = false;
3✔
65

66
    NanopubLoader(Nanopub np, long counter) {
2✔
67
        this.np = np;
3✔
68
        if (counter >= 0) {
4✔
69
            System.err.println("Loading " + counter + ": " + np.getUri());
8✔
70
        } else {
71
            System.err.println("Loading: " + np.getUri());
6✔
72
        }
73

74
        // TODO Ensure proper synchronization and DB rollbacks
75

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

78
        String ac = TrustyUriUtils.getArtifactCode(np.getUri().toString());
5✔
79
        if (!np.getHeadUri().toString().contains(ac) || !np.getAssertionUri().toString().contains(ac) || !np.getProvenanceUri().toString().contains(ac) || !np.getPubinfoUri().toString().contains(ac)) {
24!
80
            notes.add("could not load nanopub as not all graphs contained the artifact code");
×
81
            aborted = true;
×
82
            return;
×
83
        }
84

85
        try {
86
            el = SignatureUtils.getSignatureElement(np);
4✔
87
        } catch (MalformedCryptoElementException ex) {
×
88
            notes.add("Signature error");
×
89
        }
1✔
90
        if (!hasValidSignature(el)) {
4✔
91
            aborted = true;
3✔
92
            return;
1✔
93
        }
94

95
        pubkeyStatement = vf.createStatement(np.getUri(), HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY, vf.createLiteral(el.getPublicKeyString()), ADMIN_GRAPH);
13✔
96
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasValidSignatureForPublicKey, FULL_PUBKEY, npa:graph, meta, full pubkey if signature is valid
97
        metaStatements.add(pubkeyStatement);
6✔
98
        pubkeyStatementX = vf.createStatement(np.getUri(), HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY_HASH, vf.createLiteral(Utils.createHash(el.getPublicKeyString())), ADMIN_GRAPH);
14✔
99
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasValidSignatureForPublicKeyHash, PUBKEY_HASH, npa:graph, meta, hex-encoded SHA256 hash if signature is valid
100
        metaStatements.add(pubkeyStatementX);
6✔
101

102
        if (el.getSigners().size() == 1) {  // > 1 is deprecated
6!
103
            metaStatements.add(vf.createStatement(np.getUri(), NanopubSignatureElement.SIGNED_BY, el.getSigners().iterator().next(), ADMIN_GRAPH));
16✔
104
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:signedBy, SIGNER, npa:graph, meta, ID of signer
105
        }
106

107
        Set<IRI> subIris = new HashSet<>();
4✔
108
        Set<IRI> otherNps = new HashSet<>();
4✔
109
        Set<IRI> invalidated = new HashSet<>();
4✔
110
        Set<IRI> retracted = new HashSet<>();
4✔
111
        Set<IRI> superseded = new HashSet<>();
4✔
112
        String combinedLiterals = "";
2✔
113
        for (Statement st : NanopubUtils.getStatements(np)) {
11✔
114
            nanopubStatements.add(st);
5✔
115

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

194
        metaStatements.add(vf.createStatement(np.getUri(), HAS_HEAD_GRAPH, np.getHeadUri(), ADMIN_GRAPH));
12✔
195
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasHeadGraph, HEAD_GRAPH, npa:graph, meta, direct link to the head graph of the NANOPUB
196
        metaStatements.add(vf.createStatement(np.getUri(), HAS_GRAPH, np.getHeadUri(), ADMIN_GRAPH));
12✔
197
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasGraph, GRAPH, npa:graph, meta, generic link to all four graphs of the given NANOPUB
198
        metaStatements.add(vf.createStatement(np.getUri(), Nanopub.HAS_ASSERTION_URI, np.getAssertionUri(), ADMIN_GRAPH));
12✔
199
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, np:hasAssertion, ASSERTION_GRAPH, npa:graph, meta, direct link to the assertion graph of the NANOPUB
200
        metaStatements.add(vf.createStatement(np.getUri(), HAS_GRAPH, np.getAssertionUri(), ADMIN_GRAPH));
12✔
201
        metaStatements.add(vf.createStatement(np.getUri(), Nanopub.HAS_PROVENANCE_URI, np.getProvenanceUri(), ADMIN_GRAPH));
12✔
202
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, np:hasProvenance, PROVENANCE_GRAPH, npa:graph, meta, direct link to the provenance graph of the NANOPUB
203
        metaStatements.add(vf.createStatement(np.getUri(), HAS_GRAPH, np.getProvenanceUri(), ADMIN_GRAPH));
12✔
204
        metaStatements.add(vf.createStatement(np.getUri(), Nanopub.HAS_PUBINFO_URI, np.getPubinfoUri(), ADMIN_GRAPH));
12✔
205
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, np:hasPublicationInfo, PUBINFO_GRAPH, npa:graph, meta, direct link to the pubinfo graph of the NANOPUB
206
        metaStatements.add(vf.createStatement(np.getUri(), HAS_GRAPH, np.getPubinfoUri(), ADMIN_GRAPH));
12✔
207

208
        String artifactCode = TrustyUriUtils.getArtifactCode(np.getUri().stringValue());
5✔
209
        metaStatements.add(vf.createStatement(np.getUri(), HAS_ARTIFACT_CODE, vf.createLiteral(artifactCode), ADMIN_GRAPH));
13✔
210
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:artifactCode, ARTIFACT_CODE, npa:graph, meta, artifact code starting with 'RA...'
211

212
        if (isIntroNanopub(np)) {
3✔
213
            IntroNanopub introNp = new IntroNanopub(np);
5✔
214
            metaStatements.add(vf.createStatement(np.getUri(), IS_INTRO_OF, introNp.getUser(), ADMIN_GRAPH));
12✔
215
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:isIntroductionOf, AGENT, npa:graph, meta, linking intro nanopub to the agent it is introducing
216
            for (KeyDeclaration kc : introNp.getKeyDeclarations()) {
11✔
217
                metaStatements.add(vf.createStatement(np.getUri(), DECLARES_KEY, vf.createLiteral(kc.getPublicKeyString()), ADMIN_GRAPH));
14✔
218
                // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:declaresPubkey, FULL_PUBKEY, npa:graph, meta, full pubkey declared by the given intro NANOPUB
219
            }
1✔
220
        }
221

222
        try {
223
            timestamp = SimpleTimestampPattern.getCreationTime(np);
4✔
224
        } catch (IllegalArgumentException ex) {
×
225
            notes.add("Illegal date/time");
×
226
        }
1✔
227
        if (timestamp != null) {
3!
228
            metaStatements.add(vf.createStatement(np.getUri(), DCTERMS.CREATED, vf.createLiteral(timestamp.getTime()), ADMIN_GRAPH));
15✔
229
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, dct:created, CREATION_DATE, npa:graph, meta, normalized creation timestamp
230
        }
231

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

257
        if (!combinedLiterals.isEmpty()) {
3!
258
            literalStatements.add(vf.createStatement(np.getUri(), HAS_FILTER_LITERAL, vf.createLiteral(literalFilter + "\n" + combinedLiterals), ADMIN_GRAPH));
15✔
259
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasFilterLiteral, FILTER_LITERAL, npa:graph, literal, auxiliary literal for filtering by type and pubkey in text repo
260
        }
261

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

265
        metaStatements.addAll(invalidateStatements);
6✔
266

267
        allStatements = new ArrayList<>(nanopubStatements);
7✔
268
        allStatements.addAll(metaStatements);
6✔
269
        allStatements.addAll(invalidatingStatements);
5✔
270

271
        textStatements = new ArrayList<>(literalStatements);
7✔
272
        textStatements.addAll(metaStatements);
6✔
273
        textStatements.addAll(invalidatingStatements);
5✔
274
    }
1✔
275

276
    /**
277
     * Get the HTTP client used for fetching nanopublications.
278
     *
279
     * @return the HTTP client
280
     */
281
    static HttpClient getHttpClient() {
282
        if (httpClient == null) {
2✔
283
            httpClient = HttpClientBuilder.create().setDefaultRequestConfig(Utils.getHttpRequestConfig()).build();
5✔
284
        }
285
        return httpClient;
2✔
286
    }
287

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

302
    /**
303
     * Load a nanopub into the database.
304
     *
305
     * @param np      the nanopub to load
306
     * @param counter the load counter, only used for logging (or -1 if not known)
307
     * @throws RDF4JException if the loading fails
308
     */
309
    public static void load(Nanopub np, long counter) throws RDF4JException {
310
        NanopubLoader loader = new NanopubLoader(np, counter);
6✔
311
        loader.executeLoading();
2✔
312
    }
1✔
313

314
    @GeneratedFlagForDependentElements
315
    private void executeLoading() {
316
        var runningTasks = new ArrayList<Future<?>>();
317
        Consumer<Runnable> runTask = t -> runningTasks.add(loadingPool.submit(t));
×
318

319
        for (String note : notes) {
320
            loadNoteToRepo(np.getUri(), note);
321
        }
322

323
        if (!aborted) {
324
            if (timestamp != null) {
325
                if (new Date().getTime() - timestamp.getTimeInMillis() < THIRTY_DAYS) {
326
                    runTask.accept(() -> loadNanopubToLatest(allStatements));
×
327
                }
328
            }
329

330
            runTask.accept(() -> loadNanopubToRepo(np.getUri(), textStatements, "text"));
×
331
            runTask.accept(() -> loadNanopubToRepo(np.getUri(), allStatements, "full"));
×
332
            runTask.accept(() -> loadNanopubToRepo(np.getUri(), metaStatements, "meta"));
×
333

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

358
            for (Statement st : invalidateStatements) {
359
                runTask.accept(() -> loadInvalidateStatements(np, el.getPublicKeyString(), st, pubkeyStatement, pubkeyStatementX));
×
360
            }
361
        }
362

363
        // Wait for all loading tasks to complete before returning
364
        for (var task : runningTasks) {
365
            try {
366
                task.get();
367
            } catch (ExecutionException | InterruptedException ex) {
368
                throw new RuntimeException("Error in nanopub loading thread", ex.getCause());
369
            }
370
        }
371
    }
372

373
    private static Long lastUpdateOfLatestRepo = null;
2✔
374
    private static long THIRTY_DAYS = 1000L * 60 * 60 * 24 * 30;
2✔
375
    private static long ONE_HOUR = 1000L * 60 * 60;
2✔
376

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

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

468
    private record RepoStatus(boolean isLoaded, long count, String checksum) {
×
469
    }
470

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

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

504
                Value pubkeyValue = Utils.getObjectForPattern(metaConn, ADMIN_GRAPH, invalidatedNpId, HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY);
505
                if (pubkeyValue != null) {
506
                    String pubkey = pubkeyValue.stringValue();
507

508
                    if (!pubkey.equals(thisPubkey)) {
509
                        //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for pubkey " + pubkey);
510
                        connections.add(loadStatements("pubkey_" + Utils.createHash(pubkey), invalidateStatement, pubkeyStatement, pubkeyStatementX));
511
//                                                connections.add(loadStatements("text-pubkey_" + Utils.createHash(pubkey), invalidateStatement, pubkeyStatement));
512
                    }
513

514
                    for (Value v : Utils.getObjectsForPattern(metaConn, ADMIN_GRAPH, invalidatedNpId, HAS_NANOPUB_TYPE)) {
515
                        IRI typeIri = (IRI) v;
516
                        // TODO Avoid calling getTypes and getCreators multiple times:
517
                        if (!NanopubUtils.getTypes(thisNp).contains(typeIri)) {
518
                            //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for type " + typeIri);
519
                            connections.add(loadStatements("type_" + Utils.createHash(typeIri), invalidateStatement, pubkeyStatement, pubkeyStatementX));
520
//                                                        connections.add(loadStatements("text-type_" + Utils.createHash(typeIri), invalidateStatement, pubkeyStatement));
521
                        }
522
                    }
523

524
//                                        for (Value v : Utils.getObjectsForPattern(metaConn, ADMIN_GRAPH, invalidatedNpId, DCTERMS.CREATOR)) {
525
//                                                IRI creatorIri = (IRI) v;
526
//                                                if (!SimpleCreatorPattern.getCreators(thisNp).contains(creatorIri)) {
527
//                                                        //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for user " + creatorIri);
528
//                                                        connections.add(loadStatements("user_" + Utils.createHash(creatorIri), invalidateStatement, pubkeyStatement));
529
//                                                        connections.add(loadStatements("text-user_" + Utils.createHash(creatorIri), invalidateStatement, pubkeyStatement));
530
//                                                }
531
//                                        }
532
                }
533

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

558
    @GeneratedFlagForDependentElements
559
    private static RepositoryConnection loadStatements(String repoName, Statement... statements) {
560
        RepositoryConnection conn = TripleStore.get().getRepoConnection(repoName);
561
        // Basic isolation: we only append new statements
562
        conn.begin(IsolationLevels.READ_COMMITTED);
563
        for (Statement st : statements) {
564
            conn.add(st);
565
        }
566
        return conn;
567
    }
568

569
    @GeneratedFlagForDependentElements
570
    static List<Statement> getInvalidatingStatements(IRI npId) {
571
        List<Statement> invalidatingStatements = new ArrayList<>();
572
        boolean success = false;
573
        while (!success) {
574
            RepositoryConnection conn = TripleStore.get().getRepoConnection("meta");
575
            try (conn) {
576
                // Basic isolation because here we only read append-only data.
577
                conn.begin(IsolationLevels.READ_COMMITTED);
578

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

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

627
    static boolean hasValidSignature(NanopubSignatureElement el) {
628
        try {
629
            if (el != null && SignatureUtils.hasValidSignature(el) && el.getPublicKeyString() != null) {
8!
630
                return true;
2✔
631
            }
632
        } catch (GeneralSecurityException ex) {
1✔
633
            System.err.println("Error for signature element " + el.getUri());
6✔
634
            ex.printStackTrace();
2✔
635
        }
1✔
636
        return false;
2✔
637
    }
638

639
    private static IRI getBaseTrustyUri(Value v) {
640
        if (!(v instanceof IRI)) return null;
3!
641
        String s = v.stringValue();
3✔
642
        if (!s.matches(".*[^A-Za-z0-9\\-_]RA[A-Za-z0-9\\-_]{43}([^A-Za-z0-9\\\\-_].{0,43})?")) {
4✔
643
            return null;
2✔
644
        }
645
        return vf.createIRI(s.replaceFirst("^(.*[^A-Za-z0-9\\-_]RA[A-Za-z0-9\\-_]{43})([^A-Za-z0-9\\\\-_].{0,43})?$", "$1"));
7✔
646
    }
647

648
    // TODO: Move this to nanopub library:
649
    private static boolean isIntroNanopub(Nanopub np) {
650
        for (Statement st : np.getAssertion()) {
11✔
651
            if (st.getPredicate().equals(KeyDeclaration.DECLARED_BY)) return true;
7✔
652
        }
1✔
653
        return false;
2✔
654
    }
655

656
    /**
657
     * Check if a nanopub is already loaded in the admin graph.
658
     *
659
     * @param npId the nanopub ID
660
     * @return true if the nanopub is loaded, false otherwise
661
     */
662
    @GeneratedFlagForDependentElements
663
    static boolean isNanopubLoaded(String npId) {
664
        boolean loaded = false;
665
        RepositoryConnection conn = TripleStore.get().getRepoConnection("meta");
666
        try (conn) {
667
            if (Utils.getObjectForPattern(conn, ADMIN_GRAPH, vf.createIRI(npId), TripleStore.HAS_LOAD_NUMBER) != null) {
668
                loaded = true;
669
            }
670
        } catch (Exception ex) {
671
            ex.printStackTrace();
672
        }
673
        return loaded;
674
    }
675

676
    private static ValueFactory vf = SimpleValueFactory.getInstance();
2✔
677

678
    // TODO remove the constants and use the ones from the nanopub library instead
679

680
    /**
681
     * Admin graph IRI.
682
     */
683
    public static final IRI ADMIN_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/graph");
4✔
684

685
    /**
686
     * Admin network graph IRI.
687
     */
688
    public static final IRI ADMIN_NETWORK_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/networkGraph");
4✔
689

690
    /**
691
     * IRI for the head graph of a nanopub.
692
     */
693
    public static final IRI HAS_HEAD_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/hasHeadGraph");
4✔
694

695
    /**
696
     * IRI for the graph of a nanopub.
697
     */
698
    public static final IRI HAS_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/hasGraph");
4✔
699

700
    /**
701
     * IRI for the note about a nanopub.
702
     */
703
    public static final IRI NOTE = vf.createIRI("http://purl.org/nanopub/admin/note");
4✔
704

705
    /**
706
     * IRI for the subIRI of a nanopub.
707
     */
708
    public static final IRI HAS_SUB_IRI = vf.createIRI("http://purl.org/nanopub/admin/hasSubIri");
4✔
709

710
    /**
711
     * IRI for the refers to nanopub relation.
712
     */
713
    public static final IRI REFERS_TO_NANOPUB = vf.createIRI("http://purl.org/nanopub/admin/refersToNanopub");
4✔
714

715
    /**
716
     * IRI for the has valid signature for public key relation.
717
     */
718
    public static final IRI HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY = vf.createIRI("http://purl.org/nanopub/admin/hasValidSignatureForPublicKey");
4✔
719

720
    /**
721
     * IRI for the has valid signature for public key hash relation.
722
     */
723
    public static final IRI HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY_HASH = vf.createIRI("http://purl.org/nanopub/admin/hasValidSignatureForPublicKeyHash");
4✔
724

725
    /**
726
     * IRI for the has artifact code relation.
727
     */
728
    public static final IRI HAS_ARTIFACT_CODE = vf.createIRI("http://purl.org/nanopub/admin/artifactCode");
4✔
729

730
    /**
731
     * IRI for the is introduction of relation.
732
     */
733
    public static final IRI IS_INTRO_OF = vf.createIRI("http://purl.org/nanopub/admin/isIntroductionOf");
4✔
734

735
    /**
736
     * IRI for the declares pubkey relation.
737
     */
738
    public static final IRI DECLARES_KEY = vf.createIRI("http://purl.org/nanopub/admin/declaresPubkey");
4✔
739

740
    /**
741
     * IRI for the supersedes relation.
742
     */
743
    public static final IRI SUPERSEDES = vf.createIRI("http://purl.org/nanopub/x/supersedes");
4✔
744

745
    /**
746
     * IRI for the retracts relation.
747
     */
748
    public static final IRI RETRACTS = vf.createIRI("http://purl.org/nanopub/x/retracts");
4✔
749

750
    /**
751
     * IRI for the invalidates relation.
752
     */
753
    public static final IRI INVALIDATES = vf.createIRI("http://purl.org/nanopub/x/invalidates");
4✔
754

755
    /**
756
     * IRI for the has nanopub type relation.
757
     */
758
    public static final IRI HAS_NANOPUB_TYPE = vf.createIRI("http://purl.org/nanopub/x/hasNanopubType");
4✔
759

760
    /**
761
     * IRI for the has filter literal relation.
762
     */
763
    public static final IRI HAS_FILTER_LITERAL = vf.createIRI("http://purl.org/nanopub/admin/hasFilterLiteral");
4✔
764

765
    /**
766
     * IRI for the introduces relation.
767
     */
768
    public static final IRI INTRODUCES = vf.createIRI("http://purl.org/nanopub/x/introduces");
4✔
769

770
    /**
771
     * IRI for the describes relation.
772
     */
773
    public static final IRI DESCRIBES = vf.createIRI("http://purl.org/nanopub/x/describes");
4✔
774

775
    /**
776
     * IRI for the embeds relation.
777
     */
778
    public static final IRI EMBEDS = vf.createIRI("http://purl.org/nanopub/x/embeds");
4✔
779

780
    /**
781
     * Template for the query that fetches the status of a repository.
782
     */
783
    // Template for .fetchRepoStatus
784
    private static final String REPO_STATUS_QUERY_TEMPLATE = """
28✔
785
            SELECT * { graph <%s> {
786
              OPTIONAL { <%s> <%s> ?loadNumber . }
787
              <%s> <%s> ?count ;
788
                   <%s> ?checksum .
789
            } }
790
            """.formatted(ADMIN_GRAPH, "%s", TripleStore.HAS_LOAD_NUMBER, TripleStore.THIS_REPO_ID, TripleStore.HAS_NANOPUB_COUNT, TripleStore.HAS_NANOPUB_CHECKSUM);
2✔
791
}
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