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

knowledgepixels / nanodash / 17760259429

16 Sep 2025 08:50AM UTC coverage: 14.007% (+0.1%) from 13.879%
17760259429

push

github

web-flow
Merge pull request #255 from knowledgepixels/fix/list-grid-view

Fix the list/grid view mode switch

444 of 4012 branches covered (11.07%)

Branch coverage included in aggregate %.

1143 of 7318 relevant lines covered (15.62%)

0.69 hits per line

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

14.93
src/main/java/com/knowledgepixels/nanodash/NanodashSession.java
1
package com.knowledgepixels.nanodash;
2

3
import com.knowledgepixels.nanodash.component.NanopubResults;
4
import com.knowledgepixels.nanodash.component.PublishForm;
5
import com.knowledgepixels.nanodash.page.OrcidLoginPage;
6
import com.knowledgepixels.nanodash.page.ProfilePage;
7
import jakarta.servlet.http.HttpServletRequest;
8
import jakarta.servlet.http.HttpSession;
9
import jakarta.xml.bind.DatatypeConverter;
10
import org.apache.commons.io.FileUtils;
11
import org.apache.wicket.Session;
12
import org.apache.wicket.protocol.http.WebSession;
13
import org.apache.wicket.request.Request;
14
import org.apache.wicket.request.flow.RedirectToUrlException;
15
import org.apache.wicket.request.mapper.parameter.PageParameters;
16
import org.eclipse.rdf4j.model.IRI;
17
import org.eclipse.rdf4j.model.ValueFactory;
18
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
19
import org.nanopub.extra.security.*;
20
import org.nanopub.extra.setting.IntroNanopub;
21
import org.slf4j.Logger;
22
import org.slf4j.LoggerFactory;
23

24
import java.io.File;
25
import java.io.IOException;
26
import java.nio.charset.StandardCharsets;
27
import java.security.KeyPair;
28
import java.util.Date;
29
import java.util.List;
30
import java.util.Map;
31
import java.util.concurrent.ConcurrentHashMap;
32
import java.util.concurrent.ConcurrentMap;
33

34
/**
35
 * Represents a session in the Nanodash application.
36
 */
37
public class NanodashSession extends WebSession {
38

39
    private static final long serialVersionUID = -7920814788717089213L;
40
    private transient HttpSession httpSession;
41
    private static final Logger logger = LoggerFactory.getLogger(NanodashSession.class);
3✔
42

43
    /**
44
     * Retrieves the current Nanodash session.
45
     *
46
     * @return The current NanodashSession instance.
47
     */
48
    public static NanodashSession get() {
49
        return (NanodashSession) Session.get();
×
50
    }
51

52
    /**
53
     * Constructs a new NanodashSession for the given request.
54
     * Initializes the HTTP session and loads profile information.
55
     *
56
     * @param request The HTTP request.
57
     */
58
    public NanodashSession(Request request) {
59
        super(request);
3✔
60
        httpSession = ((HttpServletRequest) request.getContainerRequest()).getSession();
6✔
61
        bind();
2✔
62
        loadProfileInfo();
2✔
63
    }
1✔
64

65
    private static ValueFactory vf = SimpleValueFactory.getInstance();
3✔
66

67
//        private IntroExtractor introExtractor;
68

69
    private String userDir = System.getProperty("user.home") + "/.nanopub/";
5✔
70
    private NanopubResults.ViewMode nanopubResultsViewMode = NanopubResults.ViewMode.GRID;
3✔
71

72
    private KeyPair keyPair;
73
    private IRI userIri;
74
    private ConcurrentMap<IRI, IntroNanopub> introNps;
75
//        private Boolean isOrcidLinked;
76
//        private String orcidLinkError;
77

78
    private Integer localIntroCount = null;
3✔
79
    private IntroNanopub localIntro = null;
3✔
80

81
    private Date lastTimeIntroPublished = null;
3✔
82

83
    // We should store here some sort of form model and not the forms themselves, but I couldn't figure
84
    // how to do it, so doing it like this for the moment...
85
    private ConcurrentMap<String, PublishForm> formMap = new ConcurrentHashMap<>();
5✔
86

87
    /**
88
     * Associates a form object with a specific ID.
89
     *
90
     * @param formObjId The ID of the form object.
91
     * @param formObj   The form object to associate.
92
     */
93
    public void setForm(String formObjId, PublishForm formObj) {
94
        formMap.put(formObjId, formObj);
×
95
    }
×
96

97
    /**
98
     * Checks if a form object with the given ID exists.
99
     *
100
     * @param formObjId The ID of the form object.
101
     * @return True if the form object exists, false otherwise.
102
     */
103
    public boolean hasForm(String formObjId) {
104
        return formMap.containsKey(formObjId);
×
105
    }
106

107
    /**
108
     * Retrieves the form object associated with the given ID.
109
     *
110
     * @param formObjId The ID of the form object.
111
     * @return The associated form object, or null if not found.
112
     */
113
    public PublishForm getForm(String formObjId) {
114
        return formMap.get(formObjId);
×
115
    }
116

117
    /**
118
     * Loads profile information for the user.
119
     * Initializes user-related data such as keys and introductions.
120
     */
121
    public void loadProfileInfo() {
122
        localIntroCount = null;
3✔
123
        localIntro = null;
3✔
124
        NanodashPreferences prefs = NanodashPreferences.get();
2✔
125
        if (prefs.isOrcidLoginMode()) {
3!
126
            File usersDir = new File(System.getProperty("user.home") + "/.nanopub/nanodash-users/");
×
127
            if (!usersDir.exists()) usersDir.mkdir();
×
128
        }
129
        if (userIri == null && !prefs.isReadOnlyMode() && !prefs.isOrcidLoginMode()) {
9!
130
            if (getOrcidFile().exists()) {
4!
131
                try {
132
                    String orcid = FileUtils.readFileToString(getOrcidFile(), StandardCharsets.UTF_8).trim();
×
133
                    //String orcid = Files.readString(orcidFile.toPath(), StandardCharsets.UTF_8).trim();
134
                    if (orcid.matches(ProfilePage.ORCID_PATTERN)) {
×
135
                        userIri = vf.createIRI("https://orcid.org/" + orcid);
×
136
                        if (httpSession != null) httpSession.setMaxInactiveInterval(24 * 60 * 60);  // 24h
×
137
                    }
138
                } catch (IOException ex) {
×
139
                    logger.error("Couldn't read ORCID file", ex);
×
140
                }
×
141
            }
142
        }
143
        if (userIri != null && keyPair == null) {
3!
144
            File keyFile = getKeyFile();
×
145
            if (keyFile.exists()) {
×
146
                try {
147
                    keyPair = SignNanopub.loadKey(keyFile.getPath(), SignatureAlgorithm.RSA);
×
148
                } catch (Exception ex) {
×
149
                    logger.error("Couldn't load key pair", ex);
×
150
                }
×
151
            } else {
152
                // Automatically generate new keys
153
                makeKeys();
×
154
            }
155
        }
156
        if (userIri != null && keyPair != null && introNps == null) {
3!
157
            introNps = new ConcurrentHashMap<>(User.getIntroNanopubs(getPubkeyString()));
×
158
        }
159
//                checkOrcidLink();
160
    }
1✔
161

162
    /**
163
     * Checks if the user's profile is complete.
164
     *
165
     * @return True if the profile is complete, false otherwise.
166
     */
167
    public boolean isProfileComplete() {
168
        return userIri != null && keyPair != null && introNps != null;
×
169
    }
170

171
    /**
172
     * Redirects the user to the login page if their profile is incomplete.
173
     *
174
     * @param path       The path to redirect to after login.
175
     * @param parameters The page parameters for the redirect.
176
     */
177
    public void redirectToLoginIfNeeded(String path, PageParameters parameters) {
178
        String loginUrl = getLoginUrl(path, parameters);
×
179
        if (loginUrl == null) return;
×
180
        throw new RedirectToUrlException(loginUrl);
×
181
    }
182

183
    /**
184
     * Retrieves the login URL for the user.
185
     *
186
     * @param path       The path to redirect to after login.
187
     * @param parameters The page parameters for the redirect.
188
     * @return The login URL, or null if the user is already logged in.
189
     */
190
    public String getLoginUrl(String path, PageParameters parameters) {
191
        if (isProfileComplete()) return null;
×
192
        if (NanodashPreferences.get().isOrcidLoginMode()) {
×
193
            return OrcidLoginPage.getOrcidLoginUrl(path, parameters);
×
194
        } else {
195
            return ProfilePage.MOUNT_PATH;
×
196
        }
197
    }
198

199
    /**
200
     * Retrieves the public key as a Base64-encoded string.
201
     *
202
     * @return The public key string, or null if the key pair is not set.
203
     */
204
    public String getPubkeyString() {
205
        if (keyPair == null) return null;
×
206
        return DatatypeConverter.printBase64Binary(keyPair.getPublic().getEncoded()).replaceAll("\\s", "");
×
207
    }
208

209
    /**
210
     * Retrieves the public key hash for the user.
211
     *
212
     * @return The SHA-256 hash of the public key, or null if the public key is not set.
213
     */
214
    public String getPubkeyhash() {
215
        String pubkey = getPubkeyString();
×
216
        if (pubkey == null) return null;
×
217
        return Utils.createSha256HexHash(pubkey);
×
218
    }
219

220
    /**
221
     * Checks if the user's public key is approved.
222
     *
223
     * @return True if the public key is approved, false otherwise.
224
     */
225
    public boolean isPubkeyApproved() {
226
        if (keyPair == null || userIri == null) return false;
×
227
        return User.isApprovedPubkeyhashForUser(getPubkeyhash(), userIri);
×
228
    }
229

230
    /**
231
     * Retrieves the user's key pair.
232
     *
233
     * @return The key pair.
234
     */
235
    public KeyPair getKeyPair() {
236
        return keyPair;
×
237
    }
238

239
    /**
240
     * Generates a new key pair for the user.
241
     */
242
    public void makeKeys() {
243
        try {
244
            MakeKeys.make(getKeyFile().getAbsolutePath().replaceFirst("_rsa$", ""), SignatureAlgorithm.RSA);
×
245
            keyPair = SignNanopub.loadKey(getKeyFile().getPath(), SignatureAlgorithm.RSA);
×
246
        } catch (Exception ex) {
×
247
            logger.error("Couldn't create key pair", ex);
×
248
        }
×
249
    }
×
250

251
    /**
252
     * Retrieves the user's IRI.
253
     *
254
     * @return The user's IRI, or null if not set.
255
     */
256
    public IRI getUserIri() {
257
        return userIri;
×
258
    }
259

260
    /**
261
     * Retrieves the user's introduction nanopublications.
262
     *
263
     * @return A list of user's introduction nanopublications.
264
     */
265
    public List<IntroNanopub> getUserIntroNanopubs() {
266
        return User.getIntroNanopubs(userIri);
×
267
    }
268

269
    /**
270
     * Counts the number of local introduction nanopublications.
271
     *
272
     * @return The count of local introduction nanopublications.
273
     */
274
    public int getLocalIntroCount() {
275
        if (localIntroCount == null) {
×
276
            localIntroCount = 0;
×
277
            for (IntroNanopub inp : getUserIntroNanopubs()) {
×
278
                if (isIntroWithLocalKey(inp)) {
×
279
                    localIntroCount++;
×
280
                    localIntro = inp;
×
281
                }
282
            }
×
283
            if (localIntroCount > 1) localIntro = null;
×
284
        }
285
        return localIntroCount;
×
286
    }
287

288
    /**
289
     * Retrieves the local introduction nanopublication.
290
     *
291
     * @return The local introduction nanopublication, or null if not found.
292
     */
293
    public IntroNanopub getLocalIntro() {
294
        getLocalIntroCount();
×
295
        return localIntro;
×
296
    }
297

298
    /**
299
     * Checks if the given introduction nanopublication is associated with the local key.
300
     *
301
     * @param inp The introduction nanopublication.
302
     * @return True if associated with the local key, false otherwise.
303
     */
304
    public boolean isIntroWithLocalKey(IntroNanopub inp) {
305
        IRI location = Utils.getLocation(inp);
×
306
        NanopubSignatureElement el = Utils.getNanopubSignatureElement(inp);
×
307
        String siteUrl = NanodashPreferences.get().getWebsiteUrl();
×
308
        if (location != null && siteUrl != null) {
×
309
            String l = location.stringValue();
×
310
            // TODO: Solve the name change recognition in a better way:
311
            if (!l.equals(siteUrl) && !l.replace("nanobench", "nanodash").equals(siteUrl)) return false;
×
312
        }
313
        if (!getPubkeyString().equals(el.getPublicKeyString())) return false;
×
314
        for (KeyDeclaration kd : inp.getKeyDeclarations()) {
×
315
            if (getPubkeyString().equals(kd.getPublicKeyString())) return true;
×
316
        }
×
317
        return false;
×
318
    }
319

320
    /**
321
     * Sets the user's ORCID identifier.
322
     *
323
     * @param orcid The ORCID identifier.
324
     */
325
    public void setOrcid(String orcid) {
326
        if (!orcid.matches(ProfilePage.ORCID_PATTERN)) {
×
327
            throw new RuntimeException("Illegal ORCID identifier: " + orcid);
×
328
        }
329
        if (NanodashPreferences.get().isOrcidLoginMode()) {
×
330
            userDir = System.getProperty("user.home") + "/.nanopub/nanodash-users/" + orcid + "/";
×
331
            File f = new File(userDir);
×
332
            if (!f.exists()) f.mkdir();
×
333
        } else {
×
334
            try {
335
                FileUtils.writeStringToFile(getOrcidFile(), orcid + "\n", StandardCharsets.UTF_8);
×
336
                //                        Files.writeString(orcidFile.toPath(), orcid + "\n");
337
            } catch (IOException ex) {
×
338
                logger.error("Couldn't write ORCID file", ex);
×
339
            }
×
340
        }
341
        userIri = vf.createIRI("https://orcid.org/" + orcid);
×
342
        loadProfileInfo();
×
343
        if (httpSession != null) httpSession.setMaxInactiveInterval(24 * 60 * 60);  // 24h
×
344
    }
×
345

346
    /**
347
     * Logs out the user and invalidates the session.
348
     */
349
    public void logout() {
350
        userIri = null;
×
351
        invalidateNow();
×
352
    }
×
353

354
    /**
355
     * Retrieves the user's introduction nanopublications as a map.
356
     *
357
     * @return A map of introduction nanopublications.
358
     */
359
    public Map<IRI, IntroNanopub> getIntroNanopubs() {
360
        return introNps;
×
361
    }
362

363
//        public void checkOrcidLink() {
364
//                if (isOrcidLinked == null && userIri != null) {
365
//                        orcidLinkError = "";
366
//                        introExtractor = null;
367
//                        try {
368
//                                introExtractor = IntroNanopub.extract(userIri.stringValue(), null);
369
//                                if (introExtractor.getIntroNanopub() == null) {
370
//                                        orcidLinkError = "ORCID account is not linked.";
371
//                                        isOrcidLinked = false;
372
//                                } else {
373
//                                        IntroNanopub inp = IntroNanopub.get(userIri.stringValue(), introExtractor);
374
//                                        if (introNps != null && introNps.containsKey(inp.getNanopub().getUri())) {
375
//                                                // TODO: also check whether introduction contains local key
376
//                                                isOrcidLinked = true;
377
//                                        } else {
378
//                                                isOrcidLinked = false;
379
//                                                orcidLinkError = "Error: ORCID is linked to another introduction nanopublication.";
380
//                                        }
381
//                                }
382
//                        } catch (Exception ex) {
383
//                                logger.error("ORCID check failed");
384
//                                orcidLinkError = "ORCID check failed.";
385
//                        }
386
//                }
387
//        }
388
//
389
//        public void resetOrcidLinked() {
390
//                isOrcidLinked = null;
391
//        }
392
//
393
//        public boolean isOrcidLinked() {
394
//                checkOrcidLink();
395
//                return isOrcidLinked != null && isOrcidLinked == true;
396
//        }
397
//
398
//        public String getOrcidLinkError() {
399
//                return orcidLinkError;
400
//        }
401
//
402
//        public String getOrcidName() {
403
//                if (introExtractor == null || introExtractor.getName() == null) return null;
404
//                if (introExtractor.getName().trim().isEmpty()) return null;
405
//                return introExtractor.getName();
406
//        }
407

408
    /**
409
     * Retrieves the file for storing the user's ORCID identifier.
410
     *
411
     * @return The ORCID file.
412
     */
413
    private File getOrcidFile() {
414
        return new File(userDir + "orcid");
7✔
415
    }
416

417
    /**
418
     * Retrieves the file for storing the user's private key.
419
     *
420
     * @return The key file.
421
     */
422
    public File getKeyFile() {
423
        return new File(userDir + "id_rsa");
×
424
    }
425

426
    /**
427
     * Sets the time when the introduction was last published.
428
     */
429
    public void setIntroPublishedNow() {
430
        lastTimeIntroPublished = new Date();
×
431
    }
×
432

433
    /**
434
     * Checks if the introduction has been published.
435
     *
436
     * @return True if the introduction has been published, false otherwise.
437
     */
438
    public boolean hasIntroPublished() {
439
        return lastTimeIntroPublished != null;
×
440
    }
441

442
    /**
443
     * Calculates the time since the last introduction was published.
444
     *
445
     * @return The time in milliseconds since the last introduction was published, or Long.MAX_VALUE if it has never been published.
446
     */
447
    public long getTimeSinceLastIntroPublished() {
448
        if (lastTimeIntroPublished == null) return Long.MAX_VALUE;
×
449
        return new Date().getTime() - lastTimeIntroPublished.getTime();
×
450
    }
451

452
    /**
453
     * Sets the view mode for displaying nanopublication results.
454
     *
455
     * @param viewMode The desired view mode (e.g., GRID or LIST).
456
     */
457
    public void setNanopubResultsViewMode(NanopubResults.ViewMode viewMode) {
458
        this.nanopubResultsViewMode = viewMode;
×
459
    }
×
460

461
    /**
462
     * Retrieves the current view mode for displaying nanopublication results.
463
     *
464
     * @return The current view mode.
465
     */
466
    public NanopubResults.ViewMode getNanopubResultsViewMode() {
467
        return this.nanopubResultsViewMode;
×
468
    }
469

470
}
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