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

knowledgepixels / nanopub-query / 16937322790

13 Aug 2025 12:32PM UTC coverage: 18.109% (+0.7%) from 17.369%
16937322790

push

github

ashleycaselli
test: add unit tests for NanopubLoader

87 of 488 branches covered (17.83%)

Branch coverage included in aggregate %.

231 of 1268 relevant lines covered (18.22%)

0.88 hits per line

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

7.25
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 NanopubLoader() {
44
    }  // no instances allowed
45

46
    private static HttpClient httpClient;
47
    private static final ThreadPoolExecutor loadingPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(4);
4✔
48

49
    /**
50
     * Get the HTTP client used for fetching nanopublications.
51
     *
52
     * @return the HTTP client
53
     */
54
    private static HttpClient getHttpClient() {
55
        if (httpClient == null) {
×
56
            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(1000).setConnectionRequestTimeout(100).setSocketTimeout(1000).setCookieSpec(CookieSpecs.STANDARD).build();
×
57
            httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build();
×
58
        }
59
        return httpClient;
×
60
    }
61

62
    /**
63
     * Load the given nanopublication into the database.
64
     *
65
     * @param nanopubUri Nanopublication identifier (URI)
66
     */
67
    public static void load(String nanopubUri) {
68
        if (isNanopubLoaded(nanopubUri)) {
3!
69
            System.err.println("Already loaded: " + nanopubUri);
5✔
70
        } else {
71
            Nanopub np = GetNanopub.get(nanopubUri, getHttpClient());
×
72
            load(np, -1);
×
73
        }
74
    }
1✔
75

76
    /**
77
     * Load a nanopub into the database.
78
     *
79
     * @param np      the nanopub to load
80
     * @param counter the load counter, only used for logging (or -1 if not known)
81
     * @throws RDF4JException if the loading fails
82
     */
83
    public static void load(Nanopub np, long counter) throws RDF4JException {
84
        if (counter >= 0) {
×
85
            System.err.println("Loading " + counter + ": " + np.getUri());
×
86
        } else {
87
            System.err.println("Loading: " + np.getUri());
×
88
        }
89

90
        // TODO Ensure proper synchronization and DB rollbacks
91

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

94
        String ac = TrustyUriUtils.getArtifactCode(np.getUri().toString());
×
95
        if (!np.getHeadUri().toString().contains(ac) || !np.getAssertionUri().toString().contains(ac) || !np.getProvenanceUri().toString().contains(ac) || !np.getPubinfoUri().toString().contains(ac)) {
×
96
            loadNoteToRepo(np.getUri(), "could not load nanopub as not all graphs contained the artifact code");
×
97
            return;
×
98
        }
99

100
        NanopubSignatureElement el = null;
×
101
        try {
102
            el = SignatureUtils.getSignatureElement(np);
×
103
        } catch (MalformedCryptoElementException ex) {
×
104
            loadNoteToRepo(np.getUri(), "Signature error");
×
105
        }
×
106
        if (!hasValidSignature(el)) {
×
107
            return;
×
108
        }
109

110
        List<Statement> metaStatements = new ArrayList<>();
×
111
        List<Statement> nanopubStatements = new ArrayList<>();
×
112
        List<Statement> literalStatements = new ArrayList<>();
×
113
        List<Statement> invalidateStatements = new ArrayList<>();
×
114

115
        final Statement pubkeyStatement = vf.createStatement(np.getUri(), HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY, vf.createLiteral(el.getPublicKeyString()), ADMIN_GRAPH);
×
116
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasValidSignatureForPublicKey, FULL_PUBKEY, npa:graph, meta, full pubkey if signature is valid
117
        metaStatements.add(pubkeyStatement);
×
118
        final Statement pubkeyStatementX = vf.createStatement(np.getUri(), HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY_HASH, vf.createLiteral(Utils.createHash(el.getPublicKeyString())), ADMIN_GRAPH);
×
119
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasValidSignatureForPublicKeyHash, PUBKEY_HASH, npa:graph, meta, hex-encoded SHA256 hash if signature is valid
120
        metaStatements.add(pubkeyStatementX);
×
121

122
        if (el.getSigners().size() == 1) {  // > 1 is deprecated
×
123
            metaStatements.add(vf.createStatement(np.getUri(), NanopubSignatureElement.SIGNED_BY, el.getSigners().iterator().next(), ADMIN_GRAPH));
×
124
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:signedBy, SIGNER, npa:graph, meta, ID of signer
125
        }
126

127
        Set<IRI> subIris = new HashSet<>();
×
128
        Set<IRI> otherNps = new HashSet<>();
×
129
        Set<IRI> invalidated = new HashSet<>();
×
130
        Set<IRI> retracted = new HashSet<>();
×
131
        Set<IRI> superseded = new HashSet<>();
×
132
        String combinedLiterals = "";
×
133
        for (Statement st : NanopubUtils.getStatements(np)) {
×
134
            nanopubStatements.add(st);
×
135

136
            if (st.getPredicate().toString().contains(ac)) {
×
137
                subIris.add(st.getPredicate());
×
138
            } else {
139
                IRI b = getBaseTrustyUri(st.getPredicate());
×
140
                if (b != null) otherNps.add(b);
×
141
            }
142
            if (st.getPredicate().equals(RETRACTS) && st.getObject() instanceof IRI) {
×
143
                retracted.add((IRI) st.getObject());
×
144
            }
145
            if (st.getPredicate().equals(INVALIDATES) && st.getObject() instanceof IRI) {
×
146
                invalidated.add((IRI) st.getObject());
×
147
            }
148
            if (st.getSubject().equals(np.getUri()) && st.getObject() instanceof IRI) {
×
149
                if (st.getPredicate().equals(SUPERSEDES)) {
×
150
                    superseded.add((IRI) st.getObject());
×
151
                }
152
                if (st.getObject().toString().matches(".*[^A-Za-z0-9\\-_]RA[A-Za-z0-9\\-_]{43}")) {
×
153
                    metaStatements.add(vf.createStatement(np.getUri(), st.getPredicate(), st.getObject(), ADMIN_NETWORK_GRAPH));
×
154
                    // @ADMIN-TRIPLE-TABLE@ NANOPUB1, RELATION, NANOPUB2, npa:networkGraph, meta, any inter-nanopub relation found in NANOPUB1
155
                }
156
                if (st.getContext().equals(np.getPubinfoUri())) {
×
157
                    if (st.getPredicate().equals(INTRODUCES) || st.getPredicate().equals(DESCRIBES) || st.getPredicate().equals(EMBEDS)) {
×
158
                        metaStatements.add(vf.createStatement(np.getUri(), st.getPredicate(), st.getObject(), ADMIN_GRAPH));
×
159
                        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:introduces, THING, npa:graph, meta, when such a triple is present in pubinfo of NANOPUB
160
                        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:describes, THING, npa:graph, meta, when such a triple is present in pubinfo of NANOPUB
161
                        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:embeds, THING, npa:graph, meta, when such a triple is present in pubinfo of NANOPUB
162
                    }
163
                }
164
            }
165
            if (st.getSubject().toString().contains(ac)) {
×
166
                subIris.add((IRI) st.getSubject());
×
167
            } else {
168
                IRI b = getBaseTrustyUri(st.getSubject());
×
169
                if (b != null) otherNps.add(b);
×
170
            }
171
            if (st.getObject() instanceof IRI) {
×
172
                if (st.getObject().toString().contains(ac)) {
×
173
                    subIris.add((IRI) st.getObject());
×
174
                } else {
175
                    IRI b = getBaseTrustyUri(st.getObject());
×
176
                    if (b != null) otherNps.add(b);
×
177
                }
×
178
            } else {
179
                combinedLiterals += st.getObject().stringValue().replaceAll("\\s+", " ") + "\n";
×
180
//                                if (st.getSubject().equals(np.getUri()) && !st.getSubject().equals(HAS_FILTER_LITERAL)) {
181
//                                        literalStatements.add(vf.createStatement(np.getUri(), st.getPredicate(), st.getObject(), LITERAL_GRAPH));
182
//                                } else {
183
//                                        literalStatements.add(vf.createStatement(np.getUri(), HAS_LITERAL, st.getObject(), LITERAL_GRAPH));
184
//                                }
185
            }
186
        }
×
187
        subIris.remove(np.getUri());
×
188
        subIris.remove(np.getAssertionUri());
×
189
        subIris.remove(np.getProvenanceUri());
×
190
        subIris.remove(np.getPubinfoUri());
×
191
        for (IRI i : subIris) {
×
192
            metaStatements.add(vf.createStatement(np.getUri(), HAS_SUB_IRI, i, ADMIN_GRAPH));
×
193
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasSubIri, SUB_IRI, npa:graph, meta, for any IRI minted in the namespace of the NANOPUB
194
        }
×
195
        for (IRI i : otherNps) {
×
196
            metaStatements.add(vf.createStatement(np.getUri(), REFERS_TO_NANOPUB, i, ADMIN_NETWORK_GRAPH));
×
197
            // @ADMIN-TRIPLE-TABLE@ NANOPUB1, npa:refersToNanopub, NANOPUB2, npa:networkGraph, meta, generic inter-nanopub relation
198
        }
×
199
        for (IRI i : invalidated) {
×
200
            invalidateStatements.add(vf.createStatement(np.getUri(), INVALIDATES, i, ADMIN_GRAPH));
×
201
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:invalidates, INVALIDATED_NANOPUB, npa:graph, meta, if the NANOPUB retracts or supersedes another nanopub
202
        }
×
203
        for (IRI i : retracted) {
×
204
            invalidateStatements.add(vf.createStatement(np.getUri(), INVALIDATES, i, ADMIN_GRAPH));
×
205
            metaStatements.add(vf.createStatement(np.getUri(), RETRACTS, i, ADMIN_GRAPH));
×
206
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:retracts, RETRACTED_NANOPUB, npa:graph, meta, if the NANOPUB retracts another nanopub
207
        }
×
208
        for (IRI i : superseded) {
×
209
            invalidateStatements.add(vf.createStatement(np.getUri(), INVALIDATES, i, ADMIN_GRAPH));
×
210
            metaStatements.add(vf.createStatement(np.getUri(), SUPERSEDES, i, ADMIN_GRAPH));
×
211
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:supersedes, SUPERSEDED_NANOPUB, npa:graph, meta, if the NANOPUB supersedes another nanopub
212
        }
×
213

214
        metaStatements.add(vf.createStatement(np.getUri(), HAS_HEAD_GRAPH, np.getHeadUri(), ADMIN_GRAPH));
×
215
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasHeadGraph, HEAD_GRAPH, npa:graph, meta, direct link to the head graph of the NANOPUB
216
        metaStatements.add(vf.createStatement(np.getUri(), HAS_GRAPH, np.getHeadUri(), ADMIN_GRAPH));
×
217
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasGraph, GRAPH, npa:graph, meta, generic link to all four graphs of the given NANOPUB
218
        metaStatements.add(vf.createStatement(np.getUri(), Nanopub.HAS_ASSERTION_URI, np.getAssertionUri(), ADMIN_GRAPH));
×
219
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, np:hasAssertion, ASSERTION_GRAPH, npa:graph, meta, direct link to the assertion graph of the NANOPUB
220
        metaStatements.add(vf.createStatement(np.getUri(), HAS_GRAPH, np.getAssertionUri(), ADMIN_GRAPH));
×
221
        metaStatements.add(vf.createStatement(np.getUri(), Nanopub.HAS_PROVENANCE_URI, np.getProvenanceUri(), ADMIN_GRAPH));
×
222
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, np:hasProvenance, PROVENANCE_GRAPH, npa:graph, meta, direct link to the provenance graph of the NANOPUB
223
        metaStatements.add(vf.createStatement(np.getUri(), HAS_GRAPH, np.getProvenanceUri(), ADMIN_GRAPH));
×
224
        metaStatements.add(vf.createStatement(np.getUri(), Nanopub.HAS_PUBINFO_URI, np.getPubinfoUri(), ADMIN_GRAPH));
×
225
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, np:hasPublicationInfo, PUBINFO_GRAPH, npa:graph, meta, direct link to the pubinfo graph of the NANOPUB
226
        metaStatements.add(vf.createStatement(np.getUri(), HAS_GRAPH, np.getPubinfoUri(), ADMIN_GRAPH));
×
227

228
        String artifactCode = TrustyUriUtils.getArtifactCode(np.getUri().stringValue());
×
229
        metaStatements.add(vf.createStatement(np.getUri(), HAS_ARTIFACT_CODE, vf.createLiteral(artifactCode), ADMIN_GRAPH));
×
230
        // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:artifactCode, ARTIFACT_CODE, npa:graph, meta, artifact code starting with 'RA...'
231

232
        if (isIntroNanopub(np)) {
×
233
            IntroNanopub introNp = new IntroNanopub(np);
×
234
            metaStatements.add(vf.createStatement(np.getUri(), IS_INTRO_OF, introNp.getUser(), ADMIN_GRAPH));
×
235
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:isIntroductionOf, AGENT, npa:graph, meta, linking intro nanopub to the agent it is introducing
236
            for (KeyDeclaration kc : introNp.getKeyDeclarations()) {
×
237
                metaStatements.add(vf.createStatement(np.getUri(), DECLARES_KEY, vf.createLiteral(kc.getPublicKeyString()), ADMIN_GRAPH));
×
238
                // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:declaresPubkey, FULL_PUBKEY, npa:graph, meta, full pubkey declared by the given intro NANOPUB
239
            }
×
240
        }
241

242
        Calendar timestamp = null;
×
243
        try {
244
            timestamp = SimpleTimestampPattern.getCreationTime(np);
×
245
        } catch (IllegalArgumentException ex) {
×
246
            loadNoteToRepo(np.getUri(), "Illegal date/time");
×
247
        }
×
248
        if (timestamp != null) {
×
249
            metaStatements.add(vf.createStatement(np.getUri(), DCTERMS.CREATED, vf.createLiteral(timestamp.getTime()), ADMIN_GRAPH));
×
250
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, dct:created, CREATION_DATE, npa:graph, meta, normalized creation timestamp
251
        }
252

253
        String literalFilter = "_pubkey_" + Utils.createHash(el.getPublicKeyString());
×
254
        for (IRI typeIri : NanopubUtils.getTypes(np)) {
×
255
            metaStatements.add(vf.createStatement(np.getUri(), HAS_NANOPUB_TYPE, typeIri, ADMIN_GRAPH));
×
256
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npx:hasNanopubType, NANOPUB_TYPE, npa:graph, meta, type of NANOPUB
257
            literalFilter += " _type_" + Utils.createHash(typeIri);
×
258
        }
×
259
        String label = NanopubUtils.getLabel(np);
×
260
        if (label != null) {
×
261
            metaStatements.add(vf.createStatement(np.getUri(), RDFS.LABEL, vf.createLiteral(label), ADMIN_GRAPH));
×
262
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, rdfs:label, LABEL, npa:graph, meta, label of NANOPUB
263
        }
264
        String description = NanopubUtils.getDescription(np);
×
265
        if (description != null) {
×
266
            metaStatements.add(vf.createStatement(np.getUri(), DCTERMS.DESCRIPTION, vf.createLiteral(description), ADMIN_GRAPH));
×
267
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, dct:description, LABEL, npa:graph, meta, description of NANOPUB
268
        }
269
        for (IRI creatorIri : SimpleCreatorPattern.getCreators(np)) {
×
270
            metaStatements.add(vf.createStatement(np.getUri(), DCTERMS.CREATOR, creatorIri, ADMIN_GRAPH));
×
271
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, dct:creator, CREATOR, npa:graph, meta, creator of NANOPUB (can be several)
272
        }
×
273
        for (IRI authorIri : SimpleCreatorPattern.getAuthors(np)) {
×
274
            metaStatements.add(vf.createStatement(np.getUri(), SimpleCreatorPattern.PAV_AUTHOREDBY, authorIri, ADMIN_GRAPH));
×
275
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, pav:authoredBy, AUTHOR, npa:graph, meta, author of NANOPUB (can be several)
276
        }
×
277

278
        if (!combinedLiterals.isEmpty()) {
×
279
            literalStatements.add(vf.createStatement(np.getUri(), HAS_FILTER_LITERAL, vf.createLiteral(literalFilter + "\n" + combinedLiterals), ADMIN_GRAPH));
×
280
            // @ADMIN-TRIPLE-TABLE@ NANOPUB, npa:hasFilterLiteral, FILTER_LITERAL, npa:graph, literal, auxiliary literal for filtering by type and pubkey in text repo
281
        }
282

283
        // Any statements that express that the currently processed nanopub is already invalidated:
284
        List<Statement> invalidatingStatements = getInvalidatingStatements(np.getUri());
×
285

286
        metaStatements.addAll(invalidateStatements);
×
287

288
        List<Statement> allStatements = new ArrayList<>(nanopubStatements);
×
289
        allStatements.addAll(metaStatements);
×
290
        allStatements.addAll(invalidatingStatements);
×
291

292
        List<Statement> textStatements = new ArrayList<>(literalStatements);
×
293
        textStatements.addAll(metaStatements);
×
294
        textStatements.addAll(invalidatingStatements);
×
295

296
        var runningTasks = new ArrayList<Future<?>>();
×
297
        Consumer<Runnable> runTask = t -> {
×
298
            runningTasks.add(loadingPool.submit(t));
×
299
        };
×
300

301
        if (timestamp != null) {
×
302
            if (new Date().getTime() - timestamp.getTimeInMillis() < THIRTY_DAYS) {
×
303
                runTask.accept(() -> loadNanopubToLatest(allStatements));
×
304
            }
305
        }
306

307
        runTask.accept(() -> loadNanopubToRepo(np.getUri(), textStatements, "text"));
×
308
        runTask.accept(() -> loadNanopubToRepo(np.getUri(), allStatements, "full"));
×
309
        runTask.accept(() -> loadNanopubToRepo(np.getUri(), metaStatements, "meta"));
×
310

311
        NanopubSignatureElement finalEl = el;
×
312
        runTask.accept(() -> loadNanopubToRepo(np.getUri(), allStatements, "pubkey_" + Utils.createHash(finalEl.getPublicKeyString())));
×
313
//                loadNanopubToRepo(np.getUri(), textStatements, "text-pubkey_" + Utils.createHash(el.getPublicKeyString()));
314
        for (IRI typeIri : NanopubUtils.getTypes(np)) {
×
315
            // Exclude locally minted IRIs:
316
            if (typeIri.stringValue().startsWith(np.getUri().stringValue())) continue;
×
317
            if (!typeIri.stringValue().matches("https?://.*")) continue;
×
318
            runTask.accept(() -> loadNanopubToRepo(np.getUri(), allStatements, "type_" + Utils.createHash(typeIri)));
×
319
//                        loadNanopubToRepo(np.getUri(), textStatements, "text-type_" + Utils.createHash(typeIri));
320
        }
×
321
//                for (IRI creatorIri : SimpleCreatorPattern.getCreators(np)) {
322
//                        // Exclude locally minted IRIs:
323
//                        if (creatorIri.stringValue().startsWith(np.getUri().stringValue())) continue;
324
//                        if (!creatorIri.stringValue().matches("https?://.*")) continue;
325
//                        loadNanopubToRepo(np.getUri(), allStatements, "user_" + Utils.createHash(creatorIri));
326
//                        loadNanopubToRepo(np.getUri(), textStatements, "text-user_" + Utils.createHash(creatorIri));
327
//                }
328
//                for (IRI authorIri : SimpleCreatorPattern.getAuthors(np)) {
329
//                        // Exclude locally minted IRIs:
330
//                        if (authorIri.stringValue().startsWith(np.getUri().stringValue())) continue;
331
//                        if (!authorIri.stringValue().matches("https?://.*")) continue;
332
//                        loadNanopubToRepo(np.getUri(), allStatements, "user_" + Utils.createHash(authorIri));
333
//                        loadNanopubToRepo(np.getUri(), textStatements, "text-user_" + Utils.createHash(authorIri));
334
//                }
335

336
        for (Statement st : invalidateStatements) {
×
337
            runTask.accept(() -> loadInvalidateStatements(np, finalEl.getPublicKeyString(), st, pubkeyStatement, pubkeyStatementX));
×
338
        }
×
339

340
        // Wait for all loading tasks to complete before returning
341
        for (var task : runningTasks) {
×
342
            try {
343
                task.get();
×
344
            } catch (ExecutionException | InterruptedException ex) {
×
345
                throw new RuntimeException("Error in nanopub loading thread", ex.getCause());
×
346
            }
×
347
        }
×
348
    }
×
349

350
    private static Long lastUpdateOfLatestRepo = null;
2✔
351
    private static long THIRTY_DAYS = 1000L * 60 * 60 * 24 * 30;
2✔
352
    private static long ONE_HOUR = 1000L * 60 * 60;
2✔
353

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

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

443
    private record RepoStatus(boolean isLoaded, long count, String checksum) {
×
444
    }
445

446
    /**
447
     * To execute before loading a nanopub: check if the nanopub is already loaded and what is the
448
     * current load counter and checksum. This effectively batches three queries into one.
449
     * This method must be called from within a transaction.
450
     *
451
     * @param conn repo connection
452
     * @param npId nanopub ID
453
     * @return the current status
454
     */
455
    private static RepoStatus fetchRepoStatus(RepositoryConnection conn, IRI npId) {
456
        var result = conn.prepareTupleQuery(QueryLanguage.SPARQL, REPO_STATUS_QUERY_TEMPLATE.formatted(npId)).evaluate();
×
457
        try (result) {
×
458
            if (!result.hasNext()) {
×
459
                // This may happen if the repo was created, but is completely empty.
460
                return new RepoStatus(false, 0, NanopubUtils.INIT_CHECKSUM);
×
461
            }
462
            var row = result.next();
×
463
            return new RepoStatus(row.hasBinding("loadNumber"), Long.parseLong(row.getBinding("count").getValue().stringValue()), row.getBinding("checksum").getValue().stringValue());
×
464
        }
×
465
    }
466

467
    private static void loadInvalidateStatements(Nanopub thisNp, String thisPubkey, Statement invalidateStatement, Statement pubkeyStatement, Statement pubkeyStatementX) {
468
        boolean success = false;
×
469
        while (!success) {
×
470
            List<RepositoryConnection> connections = new ArrayList<>();
×
471
            RepositoryConnection metaConn = TripleStore.get().getRepoConnection("meta");
×
472
            try {
473
                IRI invalidatedNpId = (IRI) invalidateStatement.getObject();
×
474
                // Basic isolation because here we only read append-only data.
475
                metaConn.begin(IsolationLevels.READ_COMMITTED);
×
476

477
                Value pubkeyValue = Utils.getObjectForPattern(metaConn, ADMIN_GRAPH, invalidatedNpId, HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY);
×
478
                if (pubkeyValue != null) {
×
479
                    String pubkey = pubkeyValue.stringValue();
×
480

481
                    if (!pubkey.equals(thisPubkey)) {
×
482
                        //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for pubkey " + pubkey);
483
                        connections.add(loadStatements("pubkey_" + Utils.createHash(pubkey), invalidateStatement, pubkeyStatement, pubkeyStatementX));
×
484
//                                                connections.add(loadStatements("text-pubkey_" + Utils.createHash(pubkey), invalidateStatement, pubkeyStatement));
485
                    }
486

487
                    for (Value v : Utils.getObjectsForPattern(metaConn, ADMIN_GRAPH, invalidatedNpId, HAS_NANOPUB_TYPE)) {
×
488
                        IRI typeIri = (IRI) v;
×
489
                        // TODO Avoid calling getTypes and getCreators multiple times:
490
                        if (!NanopubUtils.getTypes(thisNp).contains(typeIri)) {
×
491
                            //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for type " + typeIri);
492
                            connections.add(loadStatements("type_" + Utils.createHash(typeIri), invalidateStatement, pubkeyStatement, pubkeyStatementX));
×
493
//                                                        connections.add(loadStatements("text-type_" + Utils.createHash(typeIri), invalidateStatement, pubkeyStatement));
494
                        }
495
                    }
×
496

497
//                                        for (Value v : Utils.getObjectsForPattern(metaConn, ADMIN_GRAPH, invalidatedNpId, DCTERMS.CREATOR)) {
498
//                                                IRI creatorIri = (IRI) v;
499
//                                                if (!SimpleCreatorPattern.getCreators(thisNp).contains(creatorIri)) {
500
//                                                        //System.err.println("Adding invalidation expressed in " + thisNp.getUri() + " also to repo for user " + creatorIri);
501
//                                                        connections.add(loadStatements("user_" + Utils.createHash(creatorIri), invalidateStatement, pubkeyStatement));
502
//                                                        connections.add(loadStatements("text-user_" + Utils.createHash(creatorIri), invalidateStatement, pubkeyStatement));
503
//                                                }
504
//                                        }
505
                }
506

507
                metaConn.commit();
×
508
                // TODO handle case that some commits succeed and some fail
509
                for (RepositoryConnection c : connections) c.commit();
×
510
                success = true;
×
511
            } catch (Exception ex) {
×
512
                ex.printStackTrace();
×
513
                if (metaConn.isActive()) metaConn.rollback();
×
514
                for (RepositoryConnection c : connections) {
×
515
                    if (c.isActive()) c.rollback();
×
516
                }
×
517
            } finally {
518
                metaConn.close();
×
519
                for (RepositoryConnection c : connections) c.close();
×
520
            }
521
            if (!success) {
×
522
                System.err.println("Retrying in 10 second...");
×
523
                try {
524
                    Thread.sleep(10000);
×
525
                } catch (InterruptedException x) {
×
526
                }
×
527
            }
528
        }
×
529
    }
×
530

531
    private static RepositoryConnection loadStatements(String repoName, Statement... statements) {
532
        RepositoryConnection conn = TripleStore.get().getRepoConnection(repoName);
×
533
        // Basic isolation: we only append new statements
534
        conn.begin(IsolationLevels.READ_COMMITTED);
×
535
        for (Statement st : statements) {
×
536
            conn.add(st);
×
537
        }
538
        return conn;
×
539
    }
540

541
    private static List<Statement> getInvalidatingStatements(IRI npId) {
542
        List<Statement> invalidatingStatements = new ArrayList<>();
×
543
        boolean success = false;
×
544
        while (!success) {
×
545
            RepositoryConnection conn = TripleStore.get().getRepoConnection("meta");
×
546
            try (conn) {
×
547
                // Basic isolation because here we only read append-only data.
548
                conn.begin(IsolationLevels.READ_COMMITTED);
×
549

550
                TupleQueryResult r = conn.prepareTupleQuery(QueryLanguage.SPARQL, "SELECT * { graph <" + ADMIN_GRAPH + "> { " + "?np <" + INVALIDATES + "> <" + npId + "> ; <" + HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY + "> ?pubkey . " + "} }").evaluate();
×
551
                try (r) {
×
552
                    while (r.hasNext()) {
×
553
                        BindingSet b = r.next();
×
554
                        invalidatingStatements.add(vf.createStatement((IRI) b.getBinding("np").getValue(), INVALIDATES, npId, ADMIN_GRAPH));
×
555
                        invalidatingStatements.add(vf.createStatement((IRI) b.getBinding("np").getValue(), HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY, b.getBinding("pubkey").getValue(), ADMIN_GRAPH));
×
556
                    }
×
557
                }
558
                conn.commit();
×
559
                success = true;
×
560
            } catch (Exception ex) {
×
561
                ex.printStackTrace();
×
562
                if (conn.isActive()) conn.rollback();
×
563
            }
×
564
            if (!success) {
×
565
                System.err.println("Retrying in 10 second...");
×
566
                try {
567
                    Thread.sleep(10000);
×
568
                } catch (InterruptedException x) {
×
569
                }
×
570
            }
571
        }
×
572
        return invalidatingStatements;
×
573
    }
574

575
    private static void loadNoteToRepo(Resource subj, String note) {
576
        boolean success = false;
×
577
        while (!success) {
×
578
            RepositoryConnection conn = TripleStore.get().getAdminRepoConnection();
×
579
            try (conn) {
×
580
                List<Statement> statements = new ArrayList<>();
×
581
                statements.add(vf.createStatement(subj, NOTE, vf.createLiteral(note), ADMIN_GRAPH));
×
582
                conn.add(statements);
×
583
                success = true;
×
584
            } catch (Exception ex) {
×
585
                ex.printStackTrace();
×
586
            }
×
587
            if (!success) {
×
588
                System.err.println("Retrying in 10 second...");
×
589
                try {
590
                    Thread.sleep(10000);
×
591
                } catch (InterruptedException x) {
×
592
                }
×
593
            }
594
        }
×
595
    }
×
596

597
    // TODO remove this method and use SignatureUtils.hasValidSignature() instead?
598
    private static boolean hasValidSignature(NanopubSignatureElement el) {
599
        try {
600
            if (el != null && SignatureUtils.hasValidSignature(el) && el.getPublicKeyString() != null) {
×
601
                return true;
×
602
            }
603
        } catch (GeneralSecurityException ex) {
×
604
            System.err.println("Error for signature element " + el.getUri());
×
605
            ex.printStackTrace();
×
606
        }
×
607
        return false;
×
608
    }
609

610
    private static IRI getBaseTrustyUri(Value v) {
611
        if (!(v instanceof IRI)) return null;
×
612
        String s = v.stringValue();
×
613
        if (!s.matches(".*[^A-Za-z0-9\\-_]RA[A-Za-z0-9\\-_]{43}([^A-Za-z0-9\\\\-_].{0,43})?")) {
×
614
            return null;
×
615
        }
616
        return vf.createIRI(s.replaceFirst("^(.*[^A-Za-z0-9\\-_]RA[A-Za-z0-9\\-_]{43})([^A-Za-z0-9\\\\-_].{0,43})?$", "$1"));
×
617
    }
618

619
    // TODO: Move this to nanopub library:
620
    private static boolean isIntroNanopub(Nanopub np) {
621
        for (Statement st : np.getAssertion()) {
×
622
            if (st.getPredicate().equals(KeyDeclaration.DECLARED_BY)) return true;
×
623
        }
×
624
        return false;
×
625
    }
626

627
    /**
628
     * Check if a nanopub is already loaded in the admin graph.
629
     *
630
     * @param npId the nanopub ID
631
     * @return true if the nanopub is loaded, false otherwise
632
     */
633
    static boolean isNanopubLoaded(String npId) {
634
        boolean loaded = false;
2✔
635
        RepositoryConnection conn = TripleStore.get().getRepoConnection("meta");
4✔
636
        try (conn) {
2✔
637
            if (Utils.getObjectForPattern(conn, ADMIN_GRAPH, vf.createIRI(npId), TripleStore.HAS_LOAD_NUMBER) != null) {
8✔
638
                loaded = true;
2✔
639
            }
640
        } catch (Exception ex) {
×
641
            ex.printStackTrace();
×
642
        }
1✔
643
        return loaded;
2✔
644
    }
645

646
    private static ValueFactory vf = SimpleValueFactory.getInstance();
2✔
647

648
    // TODO remove the constants and use the ones from the nanopub library instead
649

650
    /**
651
     * Admin graph IRI.
652
     */
653
    public static final IRI ADMIN_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/graph");
4✔
654

655
    /**
656
     * Admin network graph IRI.
657
     */
658
    public static final IRI ADMIN_NETWORK_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/networkGraph");
4✔
659

660
    /**
661
     * IRI for the head graph of a nanopub.
662
     */
663
    public static final IRI HAS_HEAD_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/hasHeadGraph");
4✔
664

665
    /**
666
     * IRI for the graph of a nanopub.
667
     */
668
    public static final IRI HAS_GRAPH = vf.createIRI("http://purl.org/nanopub/admin/hasGraph");
4✔
669

670
    /**
671
     * IRI for the note about a nanopub.
672
     */
673
    public static final IRI NOTE = vf.createIRI("http://purl.org/nanopub/admin/note");
4✔
674

675
    /**
676
     * IRI for the subIRI of a nanopub.
677
     */
678
    public static final IRI HAS_SUB_IRI = vf.createIRI("http://purl.org/nanopub/admin/hasSubIri");
4✔
679

680
    /**
681
     * IRI for the refers to nanopub relation.
682
     */
683
    public static final IRI REFERS_TO_NANOPUB = vf.createIRI("http://purl.org/nanopub/admin/refersToNanopub");
4✔
684

685
    /**
686
     * IRI for the has valid signature for public key relation.
687
     */
688
    public static final IRI HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY = vf.createIRI("http://purl.org/nanopub/admin/hasValidSignatureForPublicKey");
4✔
689

690
    /**
691
     * IRI for the has valid signature for public key hash relation.
692
     */
693
    public static final IRI HAS_VALID_SIGNATURE_FOR_PUBLIC_KEY_HASH = vf.createIRI("http://purl.org/nanopub/admin/hasValidSignatureForPublicKeyHash");
4✔
694

695
    /**
696
     * IRI for the has artifact code relation.
697
     */
698
    public static final IRI HAS_ARTIFACT_CODE = vf.createIRI("http://purl.org/nanopub/admin/artifactCode");
4✔
699

700
    /**
701
     * IRI for the is introduction of relation.
702
     */
703
    public static final IRI IS_INTRO_OF = vf.createIRI("http://purl.org/nanopub/admin/isIntroductionOf");
4✔
704

705
    /**
706
     * IRI for the declares pubkey relation.
707
     */
708
    public static final IRI DECLARES_KEY = vf.createIRI("http://purl.org/nanopub/admin/declaresPubkey");
4✔
709

710
    /**
711
     * IRI for the supersedes relation.
712
     */
713
    public static final IRI SUPERSEDES = vf.createIRI("http://purl.org/nanopub/x/supersedes");
4✔
714

715
    /**
716
     * IRI for the retracts relation.
717
     */
718
    public static final IRI RETRACTS = vf.createIRI("http://purl.org/nanopub/x/retracts");
4✔
719

720
    /**
721
     * IRI for the invalidates relation.
722
     */
723
    public static final IRI INVALIDATES = vf.createIRI("http://purl.org/nanopub/x/invalidates");
4✔
724

725
    /**
726
     * IRI for the has nanopub type relation.
727
     */
728
    public static final IRI HAS_NANOPUB_TYPE = vf.createIRI("http://purl.org/nanopub/x/hasNanopubType");
4✔
729

730
    /**
731
     * IRI for the has filter literal relation.
732
     */
733
    public static final IRI HAS_FILTER_LITERAL = vf.createIRI("http://purl.org/nanopub/admin/hasFilterLiteral");
4✔
734

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

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

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

750
    /**
751
     * Template for the query that fetches the status of a repository.
752
     */
753
    // Template for .fetchRepoStatus
754
    private static final String REPO_STATUS_QUERY_TEMPLATE = """
28✔
755
            SELECT * { graph <%s> {
756
              OPTIONAL { <%s> <%s> ?loadNumber . }
757
              <%s> <%s> ?count ;
758
                   <%s> ?checksum .
759
            } }
760
            """.formatted(ADMIN_GRAPH, "%s", TripleStore.HAS_LOAD_NUMBER, TripleStore.THIS_REPO_ID, TripleStore.HAS_NANOPUB_COUNT, TripleStore.HAS_NANOPUB_CHECKSUM);
2✔
761
}
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