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

knowledgepixels / nanopub-query / 17120526436

21 Aug 2025 07:44AM UTC coverage: 49.971% (-0.09%) from 50.058%
17120526436

push

github

tkuhn
Define timeouts via environment variables, and some refactoring

240 of 490 branches covered (48.98%)

Branch coverage included in aggregate %.

629 of 1249 relevant lines covered (50.36%)

2.49 hits per line

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

48.23
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
    private void executeLoading() {
315
        var runningTasks = new ArrayList<Future<?>>();
4✔
316
        Consumer<Runnable> runTask = t -> runningTasks.add(loadingPool.submit(t));
3✔
317

318
        for (String note : notes) {
7!
319
            loadNoteToRepo(np.getUri(), note);
×
320
        }
×
321

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

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

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

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

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

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

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

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

465
    private record RepoStatus(boolean isLoaded, long count, String checksum) {
×
466
    }
467

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

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

499
                Value pubkeyValue = Utils.getObjectForPattern(metaConn, ADMIN_GRAPH, invalidatedNpId, HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY);
×
500
                if (pubkeyValue != null) {
×
501
                    String pubkey = pubkeyValue.stringValue();
×
502

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

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

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

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

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

563
    static List<Statement> getInvalidatingStatements(IRI npId) {
564
        List<Statement> invalidatingStatements = new ArrayList<>();
4✔
565
        boolean success = false;
2✔
566
        while (!success) {
2!
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 <" + ADMIN_GRAPH + "> { " + "?np <" + INVALIDATES + "> <" + npId + "> ; <" + 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(), INVALIDATES, npId, ADMIN_GRAPH));
×
577
                        invalidatingStatements.add(vf.createStatement((IRI) b.getBinding("np").getValue(), HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY, b.getBinding("pubkey").getValue(), ADMIN_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
    private static void loadNoteToRepo(Resource subj, String note) {
598
        boolean success = false;
×
599
        while (!success) {
×
600
            RepositoryConnection conn = TripleStore.get().getAdminRepoConnection();
×
601
            try (conn) {
×
602
                List<Statement> statements = new ArrayList<>();
×
603
                statements.add(vf.createStatement(subj, NOTE, vf.createLiteral(note), ADMIN_GRAPH));
×
604
                conn.add(statements);
×
605
                success = true;
×
606
            } catch (Exception ex) {
×
607
                ex.printStackTrace();
×
608
            }
×
609
            if (!success) {
×
610
                System.err.println("Retrying in 10 second...");
×
611
                try {
612
                    Thread.sleep(10000);
×
613
                } catch (InterruptedException x) {
×
614
                }
×
615
            }
616
        }
×
617
    }
×
618

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

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

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

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

667
    private static ValueFactory vf = SimpleValueFactory.getInstance();
2✔
668

669
    // TODO remove the constants and use the ones from the nanopub library instead
670

671
    /**
672
     * Admin graph IRI.
673
     */
674
    public static final IRI ADMIN_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/graph");
4✔
675

676
    /**
677
     * Admin network graph IRI.
678
     */
679
    public static final IRI ADMIN_NETWORK_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/networkGraph");
4✔
680

681
    /**
682
     * IRI for the head graph of a nanopub.
683
     */
684
    public static final IRI HAS_HEAD_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/hasHeadGraph");
4✔
685

686
    /**
687
     * IRI for the graph of a nanopub.
688
     */
689
    public static final IRI HAS_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/hasGraph");
4✔
690

691
    /**
692
     * IRI for the note about a nanopub.
693
     */
694
    public static final IRI NOTE = vf.createIRI("http://purl.org/nanopub/admin/note");
4✔
695

696
    /**
697
     * IRI for the subIRI of a nanopub.
698
     */
699
    public static final IRI HAS_SUB_IRI = vf.createIRI("http://purl.org/nanopub/admin/hasSubIri");
4✔
700

701
    /**
702
     * IRI for the refers to nanopub relation.
703
     */
704
    public static final IRI REFERS_TO_NANOPUB = vf.createIRI("http://purl.org/nanopub/admin/refersToNanopub");
4✔
705

706
    /**
707
     * IRI for the has valid signature for public key relation.
708
     */
709
    public static final IRI HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY = vf.createIRI("http://purl.org/nanopub/admin/hasValidSignatureForPublicKey");
4✔
710

711
    /**
712
     * IRI for the has valid signature for public key hash relation.
713
     */
714
    public static final IRI HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY_HASH = vf.createIRI("http://purl.org/nanopub/admin/hasValidSignatureForPublicKeyHash");
4✔
715

716
    /**
717
     * IRI for the has artifact code relation.
718
     */
719
    public static final IRI HAS_ARTIFACT_CODE = vf.createIRI("http://purl.org/nanopub/admin/artifactCode");
4✔
720

721
    /**
722
     * IRI for the is introduction of relation.
723
     */
724
    public static final IRI IS_INTRO_OF = vf.createIRI("http://purl.org/nanopub/admin/isIntroductionOf");
4✔
725

726
    /**
727
     * IRI for the declares pubkey relation.
728
     */
729
    public static final IRI DECLARES_KEY = vf.createIRI("http://purl.org/nanopub/admin/declaresPubkey");
4✔
730

731
    /**
732
     * IRI for the supersedes relation.
733
     */
734
    public static final IRI SUPERSEDES = vf.createIRI("http://purl.org/nanopub/x/supersedes");
4✔
735

736
    /**
737
     * IRI for the retracts relation.
738
     */
739
    public static final IRI RETRACTS = vf.createIRI("http://purl.org/nanopub/x/retracts");
4✔
740

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

746
    /**
747
     * IRI for the has nanopub type relation.
748
     */
749
    public static final IRI HAS_NANOPUB_TYPE = vf.createIRI("http://purl.org/nanopub/x/hasNanopubType");
4✔
750

751
    /**
752
     * IRI for the has filter literal relation.
753
     */
754
    public static final IRI HAS_FILTER_LITERAL = vf.createIRI("http://purl.org/nanopub/admin/hasFilterLiteral");
4✔
755

756
    /**
757
     * IRI for the introduces relation.
758
     */
759
    public static final IRI INTRODUCES = vf.createIRI("http://purl.org/nanopub/x/introduces");
4✔
760

761
    /**
762
     * IRI for the describes relation.
763
     */
764
    public static final IRI DESCRIBES = vf.createIRI("http://purl.org/nanopub/x/describes");
4✔
765

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

771
    /**
772
     * Template for the query that fetches the status of a repository.
773
     */
774
    // Template for .fetchRepoStatus
775
    private static final String REPO_STATUS_QUERY_TEMPLATE = """
28✔
776
            SELECT * { graph <%s> {
777
              OPTIONAL { <%s> <%s> ?loadNumber . }
778
              <%s> <%s> ?count ;
779
                   <%s> ?checksum .
780
            } }
781
            """.formatted(ADMIN_GRAPH, "%s", TripleStore.HAS_LOAD_NUMBER, TripleStore.THIS_REPO_ID, TripleStore.HAS_NANOPUB_COUNT, TripleStore.HAS_NANOPUB_CHECKSUM);
2✔
782
}
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