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

knowledgepixels / nanodash / 28789064412

06 Jul 2026 11:44AM UTC coverage: 28.51% (+0.008%) from 28.502%
28789064412

push

github

web-flow
Merge pull request #535 from knowledgepixels/feat/navigation-context

feat: persistent navigation context with back-link and post-publish forwarding

1811 of 7177 branches covered (25.23%)

Branch coverage included in aggregate %.

3710 of 12188 relevant lines covered (30.44%)

4.52 hits per line

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

43.25
src/main/java/com/knowledgepixels/nanodash/component/NanodashLink.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.NavigationContext;
4
import com.knowledgepixels.nanodash.Utils;
5
import com.knowledgepixels.nanodash.domain.IndividualAgent;
6
import com.knowledgepixels.nanodash.domain.MaintainedResource;
7
import com.knowledgepixels.nanodash.domain.Space;
8
import com.knowledgepixels.nanodash.domain.User;
9
import com.knowledgepixels.nanodash.connector.ios.DsConfig;
10
import com.knowledgepixels.nanodash.connector.ios.DsNanopubPage;
11
import com.knowledgepixels.nanodash.connector.pensoft.BdjConfig;
12
import com.knowledgepixels.nanodash.connector.pensoft.BdjNanopubPage;
13
import com.knowledgepixels.nanodash.connector.pensoft.RioConfig;
14
import com.knowledgepixels.nanodash.connector.pensoft.RioNanopubPage;
15
import com.knowledgepixels.nanodash.page.ExplorePage;
16
import com.knowledgepixels.nanodash.page.MaintainedResourcePage;
17
import com.knowledgepixels.nanodash.page.ResourcePartPage;
18
import com.knowledgepixels.nanodash.page.SpacePage;
19
import com.knowledgepixels.nanodash.page.UserPage;
20
import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository;
21
import com.knowledgepixels.nanodash.repository.SpaceRepository;
22
import com.knowledgepixels.nanodash.template.Template;
23
import com.knowledgepixels.nanodash.template.TemplateData;
24
import net.trustyuri.TrustyUriUtils;
25
import org.apache.commons.codec.Charsets;
26
import org.apache.wicket.Component;
27
import org.apache.wicket.behavior.AttributeAppender;
28
import org.apache.wicket.markup.html.basic.Label;
29
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
30
import org.apache.wicket.markup.html.link.ExternalLink;
31
import org.apache.wicket.markup.html.panel.Panel;
32
import org.apache.wicket.model.Model;
33
import org.apache.wicket.request.mapper.parameter.PageParameters;
34
import org.eclipse.rdf4j.model.IRI;
35
import org.eclipse.rdf4j.model.Statement;
36
import org.eclipse.rdf4j.model.ValueFactory;
37
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
38
import org.eclipse.rdf4j.model.vocabulary.RDFS;
39
import org.nanopub.Nanopub;
40
import org.nanopub.vocabulary.NP;
41
import org.nanopub.vocabulary.NTEMPLATE;
42

43
import java.net.URLEncoder;
44
import java.util.*;
45

46
/**
47
 * A Wicket component that creates a link to a nanopublication or an IRI.
48
 */
49
public class NanodashLink extends Panel {
50

51
    public NanodashLink(String id, String uri, Nanopub np, IRI templateClass, String label) {
52
        this(id, uri, np, templateClass, label, null);
24✔
53
    }
3✔
54

55
    /**
56
     * Creates a link to a nanopublication or an IRI.
57
     *
58
     * @param id            the Wicket component ID
59
     * @param uri           the URI of the nanopublication or IRI
60
     * @param np            the nanopublication, or null if the link is not to a nanopublication
61
     * @param templateClass the template class of the nanopublication, or null if the link is not to a nanopublication
62
     * @param label         the label to display for the link, or null to derive it from the nanopublication or IRI
63
     */
64
    public NanodashLink(String id, String uri, Nanopub np, IRI templateClass, String label, String contextId) {
65
        super(id);
9✔
66

67
        final List<Template> templates = new ArrayList<>();
12✔
68
        final Map<IRI, String> labels = new HashMap<>();
12✔
69
        if (np != null) {
6!
70
            for (Statement st : np.getPubinfo()) {
33✔
71
                if (st.getPredicate().equals(NTEMPLATE.HAS_LABEL_FROM_API) || st.getPredicate().equals(RDFS.LABEL)) {
30!
72
                    labels.put((IRI) st.getSubject(), st.getObject().stringValue());
×
73
                }
74
            }
3✔
75
        }
76

77
        final TemplateData td = TemplateData.get();
6✔
78

79
        if (NTEMPLATE.ASSERTION_TEMPLATE.equals(templateClass)) {
12!
80
            IRI templateId = td.getTemplateId(np);
×
81
            if (templateId != null) {
×
82
                templates.add(td.getTemplate(templateId.stringValue()));
×
83
            }
84
        } else if (NTEMPLATE.PROVENANCE_TEMPLATE.equals(templateClass)) {
12!
85
            IRI templateId = td.getProvenanceTemplateId(np);
×
86
            if (templateId != null) {
×
87
                templates.add(td.getTemplate(templateId.stringValue()));
×
88
            }
89
        } else if (NTEMPLATE.PUBINFO_TEMPLATE.equals(templateClass)) {
12!
90
            Set<IRI> templateIds = td.getPubinfoTemplateIds(np);
×
91
            for (IRI templateId : templateIds) {
×
92
                templates.add(td.getTemplate(templateId.stringValue()));
×
93
            }
×
94
        }
95

96
        final IRI iriObj = vf.createIRI(uri);
12✔
97
        if (np != null && uri.equals(np.getUri().stringValue())) {
24!
98
            ExternalLink link = new ExternalLink("link", ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, Charsets.UTF_8), "this");
×
99
            add(link);
×
100
            add(new Label("description", "this specific nanopublication"));
×
101
        } else if (np != null && uri.equals(np.getAssertionUri().stringValue())) {
24!
102
            ExternalLink link = new ExternalLink("link", ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, Charsets.UTF_8), "this assertion");
×
103
            link.add(new AttributeAppender("class", " nanopub-assertion "));
×
104
            add(link);
×
105
            add(new Label("description", "the assertion of this specific nanopublication"));
×
106
        } else if (uri.equals(NP.HAS_ASSERTION.stringValue())) {
15!
107
            ExternalLink link = new ExternalLink("link", ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, Charsets.UTF_8), "assertion");
×
108
            link.add(new AttributeAppender("class", " nanopub-assertion "));
×
109
            add(link);
×
110
            add(new Label("description", "links a nanopublication to its assertion"));
×
111
        } else if (uri.equals(NP.HAS_PROVENANCE.stringValue())) {
15!
112
            ExternalLink link = new ExternalLink("link", ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, Charsets.UTF_8), "provenance");
×
113
            link.add(new AttributeAppender("class", " nanopub-provenance "));
×
114
            add(link);
×
115
            add(new Label("description", "links a nanopublication to its provenance"));
×
116
        } else if (uri.equals(NP.HAS_PUBINFO.stringValue())) {
15!
117
            ExternalLink link = new ExternalLink("link", ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, Charsets.UTF_8), "pubinfo");
×
118
            link.add(new AttributeAppender("class", " nanopub-pubinfo "));
×
119
            add(link);
×
120
            add(new Label("description", "links a nanopublication to its pubinfo"));
×
121
        } else {
×
122
            if (label == null || label.isBlank()) {
6!
123
                label = Utils.getShortNameFromURI(uri);
9✔
124
                if (iriObj.equals(User.getSignatureOwnerIri(np))) {
15!
125
                    // TODO We might want to introduce a "(you)" flag here at some point
126
                    label = User.getShortDisplayName(iriObj);
×
127
                } else if (User.getName(iriObj) != null) {
9!
128
                    label = User.getShortDisplayName(iriObj);
×
129
                } else if (SpaceRepository.get().findById(uri) != null) {
12!
130
                    label = SpaceRepository.get().findById(uri).getLabel();
×
131
                } else if (SpaceRepository.get().findByAltId(uri) != null) {
12!
132
                    label = SpaceRepository.get().findByAltId(uri).getLabel();
×
133
                } else if (MaintainedResourceRepository.get().findById(uri) != null) {
12!
134
                    label = MaintainedResourceRepository.get().findById(uri).getLabel();
×
135
                } else {
136
                    for (Template template : templates) {
18!
137
                        // TODO For pubinfo templates, we don't consider which triple came from which template (which is non-trivial):
138
                        String l = template.getLabel(iriObj);
×
139
                        if (l != null) {
×
140
                            label = l;
×
141
                            break;
×
142
                        }
143
                        l = labels.get(iriObj);
×
144
                        if (l != null) {
×
145
                            label = l;
×
146
                            break;
×
147
                        }
148
                    }
×
149
                }
150
            }
151
            String shortLabel = label.replaceFirst(" - [\\s\\S]*$", "");
15✔
152
            add(createLink("link", uri, shortLabel, contextId));
39✔
153
            String description = "";
6✔
154
            if (np != null && uri.startsWith(np.getUri().stringValue())) {
24!
155
                description = "This is a local identifier that was minted when the nanopublication was created.";
×
156
            }
157
            if (label.contains(" - ")) description = label.replaceFirst("^.* - ", "");
12!
158
            add(new Label("description", description));
39✔
159
        }
160
        add(Utils.getUriLink("uri", uri));
33✔
161
    }
3✔
162

163
    /**
164
     * <p>createLink.</p>
165
     *
166
     * @param markupId a {@link java.lang.String} object
167
     * @param uri      a {@link java.lang.String} object
168
     * @param label    a {@link java.lang.String} object
169
     * @return a {@link org.apache.wicket.Component} object
170
     */
171
    public static Component createLink(String markupId, String uri, String label, String contextId) {
172
        Component link = createLinkComponent(markupId, uri, label, contextId);
18✔
173
        // Fall back to the page's navigation context when the caller didn't supply one
174
        // (e.g. nanopub cards), so the target page's back-link still points back here.
175
        if (link instanceof BookmarkablePageLink) {
9!
176
            link.add(NavigationContext.pageContextFallback());
27✔
177
        }
178
        return link;
6✔
179
    }
180

181
    private static Component createLinkComponent(String markupId, String uri, String label, String contextId) {
182
        boolean isNp = TrustyUriUtils.isPotentialTrustyUri(uri);
9✔
183
        PageParameters params = new PageParameters().set("id", uri);
21✔
184
        if (contextId != null) params.set("context", contextId);
6!
185
        // TODO Improve this
186
        if (isNp && uri.startsWith(DsConfig.get().getTargetNamespace())) {
6!
187
            return new BookmarkablePageLink<Void>(markupId, DsNanopubPage.class, params.set("mode", "final")).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
188
        } else if (isNp && uri.startsWith(BdjConfig.get().getTargetNamespace())) {
6!
189
            return new BookmarkablePageLink<Void>(markupId, BdjNanopubPage.class, params.set("mode", "final")).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
190
        } else if (isNp && uri.startsWith(RioConfig.get().getTargetNamespace())) {
6!
191
            return new BookmarkablePageLink<Void>(markupId, RioNanopubPage.class, params.set("mode", "final")).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
192
        } else if (IndividualAgent.isUser(uri) || IndividualAgent.isOrcidIri(uri)) {
18!
193
            IRI userIri = vf.createIRI(uri);
×
194
            // Prefer our own known display name; otherwise keep a label supplied by the
195
            // caller (e.g. a name resolved by a query) before falling back to the bare id.
196
            if (User.getName(userIri) != null || label == null || label.isBlank()) {
×
197
                label = User.getShortDisplayName(userIri);
×
198
            }
199
            return new BookmarkablePageLink<Void>(markupId, UserPage.class, params).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
200
        } else if (SpaceRepository.get().findById(uri) != null) {
12!
201
            label = SpaceRepository.get().findById(uri).getLabel();
×
202
            return new BookmarkablePageLink<Void>(markupId, SpacePage.class, params).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
203
        } else if (SpaceRepository.get().findByAltId(uri) != null) {
12!
204
            Space space = SpaceRepository.get().findByAltId(uri);
×
205
            label = space.getLabel();
×
206
            params.set("id", space.getId());
×
207
            return new BookmarkablePageLink<Void>(markupId, SpacePage.class, params).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
208
        } else if (MaintainedResourceRepository.get().findById(uri) != null) {
12!
209
            label = MaintainedResourceRepository.get().findById(uri).getLabel();
×
210
            return new BookmarkablePageLink<Void>(markupId, MaintainedResourcePage.class, params).setBody(Model.of(Utils.truncateLinkLabel(label)));
×
211
        } else {
212
            boolean isCaret = "^".equals(label);
12✔
213
            if (!isCaret) params.set("label", label);
21!
214
            BookmarkablePageLink<Void> link;
215
            if (isPartOfResource(uri, contextId)) {
12!
216
                link = new BookmarkablePageLink<Void>(markupId, ResourcePartPage.class, params);
×
217
            } else {
218
                link = new BookmarkablePageLink<Void>(markupId, ExplorePage.class, params.set("forward-to-part", "true"));
30✔
219
            }
220
            // Truncate only the displayed label; the full label stays in the page
221
            // params above so the destination page can still show it as its title.
222
            link.setBody(Model.of(Utils.truncateLinkLabel(label)));
18✔
223
            // The "^" source-nanopub caret is styled like the admin lists (subtle, with
224
            // hover-darken), reusing SourceNanopub's `a.source` styling.
225
            if (isCaret) link.add(new AttributeAppender("class", "source"));
6!
226
            return link;
6✔
227
        }
228
    }
229

230
    /**
231
     * Returns the best page URL for a given URI, routing to user/space/resource pages when applicable,
232
     * falling back to the Explore page otherwise.
233
     */
234
    public static String getPageUrl(String uri) {
235
        if (IndividualAgent.isUser(uri) || IndividualAgent.isOrcidIri(uri)) {
18!
236
            return UserPage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, java.nio.charset.StandardCharsets.UTF_8);
×
237
        } else if (SpaceRepository.get().findById(uri) != null) {
12!
238
            return SpacePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, java.nio.charset.StandardCharsets.UTF_8);
×
239
        } else if (SpaceRepository.get().findByAltId(uri) != null) {
12!
240
            String spaceId = SpaceRepository.get().findByAltId(uri).getId();
×
241
            return SpacePage.MOUNT_PATH + "?id=" + URLEncoder.encode(spaceId, java.nio.charset.StandardCharsets.UTF_8);
×
242
        } else if (MaintainedResourceRepository.get().findById(uri) != null) {
12!
243
            return MaintainedResourcePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, java.nio.charset.StandardCharsets.UTF_8);
×
244
        } else {
245
            return ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(uri, java.nio.charset.StandardCharsets.UTF_8);
15✔
246
        }
247
    }
248

249
    private static boolean isPartOfResource(String uri, String contextId) {
250
        if (contextId == null) return false;
12!
251
        String uriNamespace = MaintainedResource.getNamespace(uri);
×
252
        MaintainedResource resource = MaintainedResourceRepository.get().findByNamespace(uriNamespace);
×
253
        if (resource == null) return false;
×
254
        return resource.getId().equals(contextId);
×
255
    }
256

257
    /**
258
     * Creates a link to a nanopublication or an IRI.
259
     *
260
     * @param id            the Wicket component ID
261
     * @param uri           the URI of the nanopublication or IRI
262
     * @param np            the nanopublication, or null if the link is not to a nanopublication
263
     * @param templateClass the template class of the nanopublication, or null if the link is not to a nanopublication
264
     */
265
    public NanodashLink(String id, String uri, Nanopub np, IRI templateClass) {
266
        this(id, uri, np, templateClass, null);
21✔
267
    }
3✔
268

269
    /**
270
     * Creates a link to a nanopublication or an IRI.
271
     *
272
     * @param id  the Wicket component ID
273
     * @param uri the URI of the nanopublication or IRI
274
     * @param np  the nanopublication, or null if the link is not to a nanopublication
275
     */
276
    public NanodashLink(String id, String uri, Nanopub np) {
277
        this(id, uri, np, null);
×
278
    }
×
279

280
    /**
281
     * Creates a link to a nanopublication or an IRI.
282
     *
283
     * @param id  the Wicket component ID
284
     * @param uri the URI of the nanopublication or IRI
285
     */
286
    public NanodashLink(String id, String uri) {
287
        this(id, uri, null, null);
×
288
    }
×
289

290
    private static ValueFactory vf = SimpleValueFactory.getInstance();
9✔
291

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