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

knowledgepixels / nanopub-query / 17103698952

20 Aug 2025 03:57PM UTC coverage: 48.904% (+11.3%) from 37.628%
17103698952

push

github

web-flow
Merge pull request #43 from knowledgepixels/loader-refactoring

`NanopubLoader` refactor & tests

239 of 498 branches covered (47.99%)

Branch coverage included in aggregate %.

631 of 1281 relevant lines covered (49.26%)

2.43 hits per line

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

48.32
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.client.config.CookieSpecs;
6
import org.apache.http.client.config.RequestConfig;
7
import org.apache.http.impl.client.HttpClientBuilder;
8
import org.eclipse.rdf4j.common.exception.RDF4JException;
9
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
10
import org.eclipse.rdf4j.model.*;
11
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
12
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
13
import org.eclipse.rdf4j.model.vocabulary.RDFS;
14
import org.eclipse.rdf4j.query.BindingSet;
15
import org.eclipse.rdf4j.query.QueryLanguage;
16
import org.eclipse.rdf4j.query.TupleQuery;
17
import org.eclipse.rdf4j.query.TupleQueryResult;
18
import org.eclipse.rdf4j.repository.RepositoryConnection;
19
import org.nanopub.Nanopub;
20
import org.nanopub.NanopubUtils;
21
import org.nanopub.SimpleCreatorPattern;
22
import org.nanopub.SimpleTimestampPattern;
23
import org.nanopub.extra.security.KeyDeclaration;
24
import org.nanopub.extra.security.MalformedCryptoElementException;
25
import org.nanopub.extra.security.NanopubSignatureElement;
26
import org.nanopub.extra.security.SignatureUtils;
27
import org.nanopub.extra.server.GetNanopub;
28
import org.nanopub.extra.setting.IntroNanopub;
29

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

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

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

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

65
        // TODO Ensure proper synchronization and DB rollbacks
66

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

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

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

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

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

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

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

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

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

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

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

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

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

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

256
        metaStatements.addAll(invalidateStatements);
6✔
257

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

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

267
    /**
268
     * Get the HTTP client used for fetching nanopublications.
269
     *
270
     * @return the HTTP client
271
     */
272
    static HttpClient getHttpClient() {
273
        if (httpClient == null) {
2✔
274
            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(1000).setConnectionRequestTimeout(100).setSocketTimeout(1000).setCookieSpec(CookieSpecs.STANDARD).build();
11✔
275
            httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build();
5✔
276
        }
277
        return httpClient;
2✔
278
    }
279

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

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

306
    private void executeLoading() {
307
        var runningTasks = new ArrayList<Future<?>>();
4✔
308
        Consumer<Runnable> runTask = t -> runningTasks.add(loadingPool.submit(t));
3✔
309

310
        for (String note : notes) {
7!
311
            loadNoteToRepo(np.getUri(), note);
×
312
        }
×
313

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

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

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

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

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

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

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

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

457
    private record RepoStatus(boolean isLoaded, long count, String checksum) {
×
458
    }
459

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

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

491
                Value pubkeyValue = Utils.getObjectForPattern(metaConn, ADMIN_GRAPH, invalidatedNpId, HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY);
×
492
                if (pubkeyValue != null) {
×
493
                    String pubkey = pubkeyValue.stringValue();
×
494

495
                    if (!pubkey.equals(thisPubkey)) {
×
496
                        //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for pubkey " + pubkey);
497
                        connections.add(loadStatements("pubkey_" + Utils.createHash(pubkey), invalidateStatement, pubkeyStatement, pubkeyStatementX));
×
498
//                                                connections.add(loadStatements("text-pubkey_" + Utils.createHash(pubkey), invalidateStatement, pubkeyStatement));
499
                    }
500

501
                    for (Value v : Utils.getObjectsForPattern(metaConn, ADMIN_GRAPH, invalidatedNpId, HAS_NANOPUB_TYPE)) {
×
502
                        IRI typeIri = (IRI) v;
×
503
                        // TODO Avoid calling getTypes and getCreators multiple times:
504
                        if (!NanopubUtils.getTypes(thisNp).contains(typeIri)) {
×
505
                            //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for type " + typeIri);
506
                            connections.add(loadStatements("type_" + Utils.createHash(typeIri), invalidateStatement, pubkeyStatement, pubkeyStatementX));
×
507
//                                                        connections.add(loadStatements("text-type_" + Utils.createHash(typeIri), invalidateStatement, pubkeyStatement));
508
                        }
509
                    }
×
510

511
//                                        for (Value v : Utils.getObjectsForPattern(metaConn, ADMIN_GRAPH, invalidatedNpId, DCTERMS.CREATOR)) {
512
//                                                IRI creatorIri = (IRI) v;
513
//                                                if (!SimpleCreatorPattern.getCreators(thisNp).contains(creatorIri)) {
514
//                                                        //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for user " + creatorIri);
515
//                                                        connections.add(loadStatements("user_" + Utils.createHash(creatorIri), invalidateStatement, pubkeyStatement));
516
//                                                        connections.add(loadStatements("text-user_" + Utils.createHash(creatorIri), invalidateStatement, pubkeyStatement));
517
//                                                }
518
//                                        }
519
                }
520

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

545
    private static RepositoryConnection loadStatements(String repoName, Statement... statements) {
546
        RepositoryConnection conn = TripleStore.get().getRepoConnection(repoName);
×
547
        // Basic isolation: we only append new statements
548
        conn.begin(IsolationLevels.READ_COMMITTED);
×
549
        for (Statement st : statements) {
×
550
            conn.add(st);
×
551
        }
552
        return conn;
×
553
    }
554

555
    static List<Statement> getInvalidatingStatements(IRI npId) {
556
        List<Statement> invalidatingStatements = new ArrayList<>();
4✔
557
        boolean success = false;
2✔
558
        while (!success) {
2!
559
            RepositoryConnection conn = TripleStore.get().getRepoConnection("meta");
×
560
            try (conn) {
×
561
                // Basic isolation because here we only read append-only data.
562
                conn.begin(IsolationLevels.READ_COMMITTED);
×
563

564
                TupleQueryResult r = conn.prepareTupleQuery(QueryLanguage.SPARQL, "SELECT * { graph <" + ADMIN_GRAPH + "> { " + "?np <" + INVALIDATES + "> <" + npId + "> ; <" + HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY + "> ?pubkey . " + "} }").evaluate();
×
565
                try (r) {
×
566
                    while (r.hasNext()) {
×
567
                        BindingSet b = r.next();
×
568
                        invalidatingStatements.add(vf.createStatement((IRI) b.getBinding("np").getValue(), INVALIDATES, npId, ADMIN_GRAPH));
×
569
                        invalidatingStatements.add(vf.createStatement((IRI) b.getBinding("np").getValue(), HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY, b.getBinding("pubkey").getValue(), ADMIN_GRAPH));
×
570
                    }
×
571
                }
572
                conn.commit();
×
573
                success = true;
×
574
            } catch (Exception ex) {
×
575
                ex.printStackTrace();
×
576
                if (conn.isActive()) conn.rollback();
×
577
            }
×
578
            if (!success) {
×
579
                System.err.println("Retrying in 10 second...");
×
580
                try {
581
                    Thread.sleep(10000);
×
582
                } catch (InterruptedException x) {
×
583
                }
×
584
            }
585
        }
×
586
        return invalidatingStatements;
×
587
    }
588

589
    private static void loadNoteToRepo(Resource subj, String note) {
590
        boolean success = false;
×
591
        while (!success) {
×
592
            RepositoryConnection conn = TripleStore.get().getAdminRepoConnection();
×
593
            try (conn) {
×
594
                List<Statement> statements = new ArrayList<>();
×
595
                statements.add(vf.createStatement(subj, NOTE, vf.createLiteral(note), ADMIN_GRAPH));
×
596
                conn.add(statements);
×
597
                success = true;
×
598
            } catch (Exception ex) {
×
599
                ex.printStackTrace();
×
600
            }
×
601
            if (!success) {
×
602
                System.err.println("Retrying in 10 second...");
×
603
                try {
604
                    Thread.sleep(10000);
×
605
                } catch (InterruptedException x) {
×
606
                }
×
607
            }
608
        }
×
609
    }
×
610

611
    static boolean hasValidSignature(NanopubSignatureElement el) {
612
        try {
613
            if (el != null && SignatureUtils.hasValidSignature(el) && el.getPublicKeyString() != null) {
8!
614
                return true;
2✔
615
            }
616
        } catch (GeneralSecurityException ex) {
1✔
617
            System.err.println("Error for signature element " + el.getUri());
6✔
618
            ex.printStackTrace();
2✔
619
        }
1✔
620
        return false;
2✔
621
    }
622

623
    private static IRI getBaseTrustyUri(Value v) {
624
        if (!(v instanceof IRI)) return null;
3!
625
        String s = v.stringValue();
3✔
626
        if (!s.matches(".*[^A-Za-z0-9\\-_]RA[A-Za-z0-9\\-_]{43}([^A-Za-z0-9\\\\-_].{0,43})?")) {
4✔
627
            return null;
2✔
628
        }
629
        return vf.createIRI(s.replaceFirst("^(.*[^A-Za-z0-9\\-_]RA[A-Za-z0-9\\-_]{43})([^A-Za-z0-9\\\\-_].{0,43})?$", "$1"));
7✔
630
    }
631

632
    // TODO: Move this to nanopub library:
633
    private static boolean isIntroNanopub(Nanopub np) {
634
        for (Statement st : np.getAssertion()) {
11✔
635
            if (st.getPredicate().equals(KeyDeclaration.DECLARED_BY)) return true;
7✔
636
        }
1✔
637
        return false;
2✔
638
    }
639

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

659
    private static ValueFactory vf = SimpleValueFactory.getInstance();
2✔
660

661
    // TODO remove the constants and use the ones from the nanopub library instead
662

663
    /**
664
     * Admin graph IRI.
665
     */
666
    public static final IRI ADMIN_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/graph");
4✔
667

668
    /**
669
     * Admin network graph IRI.
670
     */
671
    public static final IRI ADMIN_NETWORK_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/networkGraph");
4✔
672

673
    /**
674
     * IRI for the head graph of a nanopub.
675
     */
676
    public static final IRI HAS_HEAD_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/hasHeadGraph");
4✔
677

678
    /**
679
     * IRI for the graph of a nanopub.
680
     */
681
    public static final IRI HAS_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/hasGraph");
4✔
682

683
    /**
684
     * IRI for the note about a nanopub.
685
     */
686
    public static final IRI NOTE = vf.createIRI("http://purl.org/nanopub/admin/note");
4✔
687

688
    /**
689
     * IRI for the subIRI of a nanopub.
690
     */
691
    public static final IRI HAS_SUB_IRI = vf.createIRI("http://purl.org/nanopub/admin/hasSubIri");
4✔
692

693
    /**
694
     * IRI for the refers to nanopub relation.
695
     */
696
    public static final IRI REFERS_TO_NANOPUB = vf.createIRI("http://purl.org/nanopub/admin/refersToNanopub");
4✔
697

698
    /**
699
     * IRI for the has valid signature for public key relation.
700
     */
701
    public static final IRI HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY = vf.createIRI("http://purl.org/nanopub/admin/hasValidSignatureForPublicKey");
4✔
702

703
    /**
704
     * IRI for the has valid signature for public key hash relation.
705
     */
706
    public static final IRI HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY_HASH = vf.createIRI("http://purl.org/nanopub/admin/hasValidSignatureForPublicKeyHash");
4✔
707

708
    /**
709
     * IRI for the has artifact code relation.
710
     */
711
    public static final IRI HAS_ARTIFACT_CODE = vf.createIRI("http://purl.org/nanopub/admin/artifactCode");
4✔
712

713
    /**
714
     * IRI for the is introduction of relation.
715
     */
716
    public static final IRI IS_INTRO_OF = vf.createIRI("http://purl.org/nanopub/admin/isIntroductionOf");
4✔
717

718
    /**
719
     * IRI for the declares pubkey relation.
720
     */
721
    public static final IRI DECLARES_KEY = vf.createIRI("http://purl.org/nanopub/admin/declaresPubkey");
4✔
722

723
    /**
724
     * IRI for the supersedes relation.
725
     */
726
    public static final IRI SUPERSEDES = vf.createIRI("http://purl.org/nanopub/x/supersedes");
4✔
727

728
    /**
729
     * IRI for the retracts relation.
730
     */
731
    public static final IRI RETRACTS = vf.createIRI("http://purl.org/nanopub/x/retracts");
4✔
732

733
    /**
734
     * IRI for the invalidates relation.
735
     */
736
    public static final IRI INVALIDATES = vf.createIRI("http://purl.org/nanopub/x/invalidates");
4✔
737

738
    /**
739
     * IRI for the has nanopub type relation.
740
     */
741
    public static final IRI HAS_NANOPUB_TYPE = vf.createIRI("http://purl.org/nanopub/x/hasNanopubType");
4✔
742

743
    /**
744
     * IRI for the has filter literal relation.
745
     */
746
    public static final IRI HAS_FILTER_LITERAL = vf.createIRI("http://purl.org/nanopub/admin/hasFilterLiteral");
4✔
747

748
    /**
749
     * IRI for the introduces relation.
750
     */
751
    public static final IRI INTRODUCES = vf.createIRI("http://purl.org/nanopub/x/introduces");
4✔
752

753
    /**
754
     * IRI for the describes relation.
755
     */
756
    public static final IRI DESCRIBES = vf.createIRI("http://purl.org/nanopub/x/describes");
4✔
757

758
    /**
759
     * IRI for the embeds relation.
760
     */
761
    public static final IRI EMBEDS = vf.createIRI("http://purl.org/nanopub/x/embeds");
4✔
762

763
    /**
764
     * Template for the query that fetches the status of a repository.
765
     */
766
    // Template for .fetchRepoStatus
767
    private static final String REPO_STATUS_QUERY_TEMPLATE = """
28✔
768
            SELECT * { graph <%s> {
769
              OPTIONAL { <%s> <%s> ?loadNumber . }
770
              <%s> <%s> ?count ;
771
                   <%s> ?checksum .
772
            } }
773
            """.formatted(ADMIN_GRAPH, "%s", TripleStore.HAS_LOAD_NUMBER, TripleStore.THIS_REPO_ID, TripleStore.HAS_NANOPUB_COUNT, TripleStore.HAS_NANOPUB_CHECKSUM);
2✔
774
}
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