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

knowledgepixels / nanodash / 27145358627

08 Jun 2026 02:39PM UTC coverage: 20.682% (-0.3%) from 20.947%
27145358627

push

github

web-flow
Merge pull request #479 from knowledgepixels/feat/about-pages-478

Resource-page tabs, presets, and role-gated view actions (#478, #302)

1052 of 6429 branches covered (16.36%)

Branch coverage included in aggregate %.

2642 of 11432 relevant lines covered (23.11%)

3.31 hits per line

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

0.0
src/main/java/com/knowledgepixels/nanodash/page/ExplorePage.java
1
package com.knowledgepixels.nanodash.page;
2

3
import com.google.common.collect.ArrayListMultimap;
4
import com.google.common.collect.Multimap;
5
import com.knowledgepixels.nanodash.*;
6
import com.knowledgepixels.nanodash.component.*;
7
import com.knowledgepixels.nanodash.domain.AbstractResourceWithProfile;
8
import com.knowledgepixels.nanodash.domain.IndividualAgent;
9
import com.knowledgepixels.nanodash.domain.User;
10
import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository;
11
import com.knowledgepixels.nanodash.repository.SpaceRepository;
12
import jakarta.servlet.http.HttpServletRequest;
13
import net.trustyuri.TrustyUriUtils;
14
import org.apache.wicket.RestartResponseException;
15
import org.apache.wicket.markup.html.WebMarkupContainer;
16
import org.apache.wicket.markup.html.basic.Label;
17
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
18
import org.apache.wicket.markup.html.link.ExternalLink;
19
import org.apache.wicket.model.LoadableDetachableModel;
20
import org.apache.wicket.model.Model;
21
import org.apache.wicket.request.flow.RedirectToUrlException;
22
import org.apache.wicket.request.mapper.parameter.INamedParameters.NamedPair;
23
import org.apache.wicket.request.mapper.parameter.PageParameters;
24
import org.commonjava.mimeparse.MIMEParse;
25
import org.eclipse.rdf4j.model.IRI;
26
import org.eclipse.rdf4j.model.Statement;
27
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
28
import org.eclipse.rdf4j.model.vocabulary.RDF;
29
import org.eclipse.rdf4j.model.vocabulary.SKOS;
30
import org.nanopub.Nanopub;
31
import org.nanopub.NanopubUtils;
32
import org.nanopub.extra.security.SignatureUtils;
33
import org.nanopub.extra.services.ApiResponse;
34
import org.nanopub.extra.services.QueryRef;
35
import org.nanopub.vocabulary.KPXL_GRLC;
36
import org.nanopub.vocabulary.NPX;
37
import org.nanopub.vocabulary.NTEMPLATE;
38
import org.slf4j.Logger;
39
import org.slf4j.LoggerFactory;
40

41
import java.util.HashMap;
42
import java.util.HashSet;
43
import java.util.Map;
44
import java.util.Set;
45

46
/**
47
 * ExplorePage is a page that allows users to explore a specific Nanopublication or Thing.
48
 */
49
public class ExplorePage extends NanodashPage {
50

51
    private static final Logger logger = LoggerFactory.getLogger(ExplorePage.class);
×
52

53
    /**
54
     * The mount path for this page.
55
     */
56
    public static final String MOUNT_PATH = "/explore";
57

58
    private static final String DESCRIBED_IN_VIEW = "https://w3id.org/np/RAMH_7qMY-jmgXr2jqqk5F_XW7t2k2n3NCB6LtoKEXDzY/described-in-view";
59
    private static final String CLASSES_VIEW = "https://w3id.org/np/RAHPtR1VriEW09tcvZhrM8Dr3vE1JnMWWi9-ajKJWNOJs/classes-view";
60
    private static final String INSTANCES_VIEW = "https://w3id.org/np/RABXfsNoT_RYlk8LpDmKfJ2poSlvIGk3jgq4DkR4YLAps/instances-view";
61
    private static final String TEMPLATES_VIEW = "https://w3id.org/np/RAP0-S9PUUVF1rQiqo8vq8z6XWsXkeGBUo60DJf8JsXsc/templates-view";
62

63
    /**
64
     * {@inheritDoc}
65
     */
66
    @Override
67
    public String getMountPath() {
68
        return MOUNT_PATH;
×
69
    }
70

71
    private Nanopub publishedNanopub = null;
×
72

73
    /**
74
     * Constructor for ExplorePage.
75
     *
76
     * @param parameters Page parameters containing the ID of the Nanopublication or Thing to explore.
77
     */
78
    public ExplorePage(final PageParameters parameters) {
79
        super(parameters);
×
80
        add(new Label("publish-confirm-panel").setVisible(false));
×
81
        initPage();
×
82
    }
×
83

84
    public ExplorePage(final Nanopub publishedNanopub, final PageParameters parameters) {
85
        super(parameters.set("id", publishedNanopub.getUri()));
×
86
        this.publishedNanopub = publishedNanopub;
×
87

88
        WebMarkupContainer publishConfirmPanel = new WebMarkupContainer("publish-confirm-panel");
×
89
        final NanodashSession session = NanodashSession.get();
×
90
        boolean hasKnownOwnLocalIntro = session.getLocalIntroCount() > 0;
×
91
        boolean someIntroJustNowPublished = Utils.usesPredicateInAssertion(publishedNanopub, NPX.DECLARED_BY);
×
92
        if (someIntroJustNowPublished) NanodashSession.get().setIntroPublishedNow();
×
93
        boolean lastIntroPublishedMoreThanFiveMinsAgo = session.getTimeSinceLastIntroPublished() > 5 * 60 * 1000;
×
94
        if (!hasKnownOwnLocalIntro && session.hasIntroPublished()) User.refreshUsers();
×
95
        publishConfirmPanel.add(new WebMarkupContainer("missing-intro-warning").setVisible(!hasKnownOwnLocalIntro && lastIntroPublishedMoreThanFiveMinsAgo));
×
96

97
        PageParameters plainLinkParams = new PageParameters();
×
98
        plainLinkParams.set("template", parameters.get("template"));
×
99
        if (!parameters.get("template-version").isEmpty()) {
×
100
            plainLinkParams.set("template-version", parameters.get("template-version"));
×
101
        }
102
        publishConfirmPanel.add(new BookmarkablePageLink<Void>("publish-another-link", PublishPage.class, plainLinkParams));
×
103

104
        PageParameters linkParams = new PageParameters(parameters);
×
105
        linkParams.remove("supersede");
×
106
        linkParams.remove("supersede-a");
×
107
        boolean publishAnotherFilledLinkVisible = false;
×
108
        for (NamedPair n : linkParams.getAllNamed()) {
×
109
            if (n.getKey().matches("(param|prparam|piparam[1-9][0-9]*)_.*")) {
×
110
                publishAnotherFilledLinkVisible = true;
×
111
                break;
×
112
            }
113
        }
×
114
        if (publishAnotherFilledLinkVisible) {
×
115
            publishConfirmPanel.add(new BookmarkablePageLink<Void>("publish-another-filled-link", PublishPage.class, linkParams));
×
116
        } else {
117
            publishConfirmPanel.add(new Label("publish-another-filled-link", "").setVisible(false));
×
118
        }
119
        add(publishConfirmPanel);
×
120

121
        initPage();
×
122
    }
×
123

124
    private void initPage() {
125
        PageParameters parameters = getPageParameters();
×
126

127
        String tempRef = parameters.get("id").toString();
×
128
        // Sometimes these Wicket session IDs end up here and they can mess up the query cache:
129
        tempRef = tempRef.replaceFirst(";jsessionid.*$", "");
×
130

131
        String contextId = parameters.get("context").toString("");
×
132

133
        // Known resources are explored via their own page's Explore tab; forward
134
        // there instead of rendering the generic explore page.
135
        if (publishedNanopub == null) {
×
136
            if (SpaceRepository.get().findById(tempRef) != null) {
×
137
                throw new RestartResponseException(SpacePage.class, new PageParameters().set("id", tempRef).set("tab", "explore"));
×
138
            } else if (MaintainedResourceRepository.get().findById(tempRef) != null) {
×
139
                throw new RestartResponseException(MaintainedResourcePage.class, new PageParameters().set("id", tempRef).set("tab", "explore"));
×
140
            } else if (User.getUserData().isUser(tempRef)) {
×
141
                throw new RestartResponseException(UserPage.class, new PageParameters().set("id", tempRef).set("tab", "explore"));
×
142
            } else if (!contextId.isEmpty() && MaintainedResourceRepository.get().findById(contextId) != null) {
×
143
                throw new RestartResponseException(ResourcePartPage.class, new PageParameters().set("id", tempRef).set("context", contextId).set("tab", "explore"));
×
144
            }
145
        }
146

147
        add(new TitleBar("titlebar", this, null));
×
148

149
        if (SpaceRepository.get().findById(contextId) != null) {
×
150
            add(new BookmarkablePageLink<Void>("back-to-context-link", SpacePage.class, new PageParameters().set("id", contextId)).setBody(LoadableDetachableModel.of(() -> {
×
151
                var space = SpaceRepository.get().findById(contextId);
×
152
                return "back to " + (space == null ? contextId : space.getLabel());
×
153
            })));
154
        } else if (MaintainedResourceRepository.get().findById(contextId) != null) {
×
155
            add(new BookmarkablePageLink<Void>("back-to-context-link", MaintainedResourcePage.class, new PageParameters().set("id", contextId)).setBody(LoadableDetachableModel.of(() -> {
×
156
                var resource = MaintainedResourceRepository.get().findById(contextId);
×
157
                return "back to " + (resource == null ? contextId : resource.getLabel());
×
158
            })));
159
        } else if (IndividualAgent.isUser(contextId)) {
×
160
            add(new BookmarkablePageLink<Void>("back-to-context-link", UserPage.class, new PageParameters().set("id", contextId)).setBody(LoadableDetachableModel.of(() -> "back to " + User.getShortDisplayName(Utils.vf.createIRI(contextId)))));
×
161
        } else {
162
            add(new Label("back-to-context-link").setVisible(false));
×
163
        }
164

165
        if (User.getUserData().isUser(tempRef)) {
×
166
            add(new BookmarkablePageLink<Void>("to-specific-page-link", UserPage.class, parameters).setBody(Model.of("go to user page")));
×
167
        } else if (SpaceRepository.get().findById(tempRef) != null) {
×
168
            add(new BookmarkablePageLink<Void>("to-specific-page-link", SpacePage.class, parameters).setBody(Model.of("go to Space page")));
×
169
        } else {
170
            add(new Label("to-specific-page-link").setVisible(false));
×
171
        }
172

173
        WebMarkupContainer raw = new WebMarkupContainer("raw");
×
174
        add(raw);
×
175

176
        Map<String, String> nanopubParams = new HashMap<>();
×
177
        nanopubParams.put("ref", tempRef);
×
178
        Nanopub np = Utils.getAsNanopub(tempRef);
×
179
        boolean isNanopubId = (np != null);
×
180
        if (isNanopubId) {
×
181
            tempRef = np.getUri().stringValue();
×
182
        }
183
        if (!isNanopubId && tempRef.matches("^(.*[^A-Za-z0-9-_])?RA[A-Za-z0-9-_]{43}[^A-Za-z0-9-_].*$")) {
×
184
            np = Utils.getAsNanopub(tempRef.replaceFirst("^(.*[^A-Za-z0-9-_])?(RA[A-Za-z0-9-_]{43})[^A-Za-z0-9-_].*$", "$2"));
×
185
            if (np != null) {
×
186
                String npId = np.getUri().stringValue();
×
187
                tempRef = npId + tempRef.replaceFirst("^(.*[^A-Za-z0-9-_])?(RA[A-Za-z0-9-_]{43})([^A-Za-z0-9-_].*)$", "$3");
×
188
                Multimap<String, String> params = ArrayListMultimap.create();
×
189
                params.put("thing", tempRef);
×
190
                params.put("np", npId);
×
191
                ApiResponse resp = ApiCache.retrieveResponseSync(new QueryRef(QueryApiAccess.GET_LATEST_THING_NANOPUB, params), false);
×
192
                if (!resp.getData().isEmpty()) {
×
193
                    // TODO We take the most recent in case more than one latest version exists. Make other latest versions visible too.
194
                    npId = resp.getData().getFirst().get("latestVersion");
×
195
                }
196
                np = Utils.getAsNanopub(npId);
×
197
            }
198
        }
199

200
        if (parameters.get("forward-to-part").toString("").equals("true") && !contextId.isEmpty() && publishedNanopub == null) {
×
201
            parameters.remove("forward-to-part");
×
202
            Set<IRI> classes = new HashSet<>();
×
203
            if (np != null) {
×
204
                Set<String> introducedIds = NanopubUtils.getIntroducedIriIds(np);
×
205
                if (introducedIds.size() == 1 && introducedIds.iterator().next().equals(tempRef)) {
×
206
                    for (Statement st : np.getAssertion()) {
×
207
                        if (!st.getSubject().stringValue().equals(tempRef)) continue;
×
208
                        if (st.getPredicate().equals(DCTERMS.IS_PART_OF) || st.getPredicate().equals(SKOS.IN_SCHEME)) {
×
209
                            String resourceId = st.getObject().stringValue();
×
210
                            if (MaintainedResourceRepository.get().findById(resourceId) == null) continue;
×
211
                            throw new RestartResponseException(ResourcePartPage.class, parameters);
×
212
                        } else if (st.getPredicate().equals(RDF.TYPE) && st.getObject() instanceof IRI objIri) {
×
213
                            classes.add(objIri);
×
214
                        }
215
                    }
×
216
                }
217
            }
218
            AbstractResourceWithProfile contextResource = AbstractResourceWithProfile.get(contextId);
×
219
            if (contextResource instanceof IndividualAgent && !IndividualAgent.isUser(contextId)) {
×
220
                contextResource = null;
×
221
            }
222
            if (contextResource != null && contextResource.appliesTo(tempRef, classes)) {
×
223
                throw new RestartResponseException(ResourcePartPage.class, parameters);
×
224
            }
225
        }
226

227
        WebMarkupContainer nanopubSection = new WebMarkupContainer("nanopub-section");
×
228

229
        if (np == null) {
×
230
            nanopubSection.setVisible(false);
×
231
            nanopubSection.add(new Label("nanopub-header", ""));
×
232
            nanopubSection.add(new Label("nanopub", ""));
×
233
            raw.setVisible(false);
×
234
            nanopubSection.add(raw);
×
235
            nanopubSection.add(new WebMarkupContainer("use-template").setVisible(false));
×
236
            nanopubSection.add(new WebMarkupContainer("run-query").setVisible(false));
×
237
        } else {
238

239
            // Check whether we should redirect to Nanopub Registry for machine-friendly formats:
240
            String mimeType = Utils.TYPE_HTML;
×
241
            try {
242
                HttpServletRequest httpRequest = (HttpServletRequest) getRequest().getContainerRequest();
×
243
                mimeType = MIMEParse.bestMatch(Utils.SUPPORTED_TYPES_LIST, httpRequest.getHeader("Accept"));
×
244
            } catch (Exception ex) {
×
245
                logger.error("Error determining MIME type from Accept header.", ex);
×
246
            }
×
247
            if (!mimeType.equals(Utils.TYPE_HTML)) {
×
248
                logger.info("Non-HTML content type: {}", mimeType);
×
249
                // TODO Make this registry URL configurable/dynamic:
250
                String redirectUrl = Utils.getMainRegistryUrl() + "np/" + TrustyUriUtils.getArtifactCode(np.getUri().stringValue());
×
251
                logger.info("Redirecting to: {}", redirectUrl);
×
252
                throw new RedirectToUrlException(redirectUrl, 302);
×
253
            }
254

255
            String nanopubHeaderLabel = "<h4>%s</h4>";
×
256
            if (isNanopubId) {
×
257
                nanopubHeaderLabel = String.format(nanopubHeaderLabel, "📌 Nanopublication");
×
258
            } else {
259
                nanopubHeaderLabel = String.format(nanopubHeaderLabel, "🚩 Minted in Nanopublication");
×
260
            }
261
            nanopubSection.add(new Label("nanopub-header", nanopubHeaderLabel).setEscapeModelStrings(false));
×
262
            nanopubSection.add(new NanopubItem("nanopub", NanopubElement.get(np)));
×
263
            String url = Utils.getMainRegistryUrl() + "np/" + TrustyUriUtils.getArtifactCode(np.getUri().stringValue());
×
264
            raw.add(new ExternalLink("trig-txt", url + ".trig.txt"));
×
265
            raw.add(new ExternalLink("jsonld-txt", url + ".jsonld.txt"));
×
266
            raw.add(new ExternalLink("nq-txt", url + ".nq.txt"));
×
267
            raw.add(new ExternalLink("xml-txt", url + ".xml.txt"));
×
268
            raw.add(new ExternalLink("trig", url + ".trig"));
×
269
            raw.add(new ExternalLink("jsonld", url + ".jsonld"));
×
270
            raw.add(new ExternalLink("nq", url + ".nq"));
×
271
            raw.add(new ExternalLink("xml", url + ".xml"));
×
272
            String npUri = np.getUri().stringValue();
×
273
            raw.add(createAssertionLink("a-turtle", npUri, "turtle", false));
×
274
            raw.add(createAssertionLink("a-turtle-txt", npUri, "turtle", true));
×
275
            raw.add(createAssertionLink("a-jsonld", npUri, "jsonld", false));
×
276
            raw.add(createAssertionLink("a-jsonld-txt", npUri, "jsonld", true));
×
277
            raw.add(createAssertionLink("a-nt", npUri, "nt", false));
×
278
            raw.add(createAssertionLink("a-nt-txt", npUri, "nt", true));
×
279
            raw.add(createAssertionLink("a-rdfxml", npUri, "rdfxml", false));
×
280
            raw.add(createAssertionLink("a-rdfxml-txt", npUri, "rdfxml", true));
×
281
            nanopubSection.add(raw);
×
282
            if (Utils.isNanopubOfClass(np, NTEMPLATE.ASSERTION_TEMPLATE)) {
×
283
                nanopubSection.add(new WebMarkupContainer("use-template").add(new BookmarkablePageLink<Void>("template-link", PublishPage.class, new PageParameters().set("template", np.getUri()))));
×
284
            } else {
285
                nanopubSection.add(new WebMarkupContainer("use-template").setVisible(false));
×
286
            }
287
            if (Utils.isNanopubOfClass(np, KPXL_GRLC.GRLC_QUERY)) {
×
288
                nanopubSection.add(new WebMarkupContainer("run-query").add(new BookmarkablePageLink<Void>("query-link", QueryPage.class, new PageParameters().set("id", np.getUri()))));
×
289
            } else {
290
                nanopubSection.add(new WebMarkupContainer("run-query").setVisible(false));
×
291
            }
292
        }
293
        add(nanopubSection);
×
294

295
        final String ref = tempRef;
×
296
        final String shortName;
297
        if (publishedNanopub != null) {
×
298
            shortName = NanopubUtils.getLabel(np);
×
299
        } else if (parameters.get("label").isEmpty()) {
×
300
            shortName = Utils.getShortNameFromURI(ref);
×
301
        } else {
302
            shortName = parameters.get("label").toString();
×
303
        }
304
        add(new Label("pagetitle", shortName + " (explore) | nanodash"));
×
305
        add(new Label("termname", shortName));
×
306

307
        //add(new ExternalLink("urilink", ref, ref));
308
        add(new ExternalLinkWithActionsPanel("urilink", Model.of(ref)));
×
309

310
        if (publishedNanopub != null) {
×
311
            add(new Label("statusLine").setVisible(false));
×
312
        } else if (np != null && SignatureUtils.seemsToHaveSignature(np)) {
×
313
            add(StatusLine.createComponent("statusLine", np.getUri().stringValue()));
×
314
        } else {
315
            add(new Label("statusLine").setVisible(false));
×
316
        }
317
        WebMarkupContainer infoSection = new WebMarkupContainer("info-section");
×
318
        if (publishedNanopub != null) {
×
319
            infoSection.add(new Label("classes-panel").setVisible(false));
×
320
        } else {
321
            View classesView = View.get(CLASSES_VIEW);
×
322
            QueryRef classesQueryRef = new QueryRef(classesView.getQuery().getQueryId(), "thing", ref);
×
323
            infoSection.add(QueryResultListBuilder.create("classes-panel", classesQueryRef, new ViewDisplay(classesView)).build());
×
324
        }
325
        if (isNanopubId) {
×
326
            infoSection.add(new Label("definitions-panel").setVisible(false));
×
327
            infoSection.add(new Label("instances-panel").setVisible(false));
×
328
            infoSection.add(new Label("templates-panel").setVisible(false));
×
329
        } else {
330
            View describedInView = View.get(DESCRIBED_IN_VIEW);
×
331
            QueryRef describedInQueryRef = new QueryRef(describedInView.getQuery().getQueryId(), "term", ref);
×
332
            infoSection.add(QueryResultNanopubSetBuilder.create("definitions-panel", describedInQueryRef, new ViewDisplay(describedInView)).build());
×
333

334
            View instancesView = View.get(INSTANCES_VIEW);
×
335
            QueryRef instancesQueryRef = new QueryRef(instancesView.getQuery().getQueryId(), "class", ref);
×
336
            infoSection.add(QueryResultListBuilder.create("instances-panel", instancesQueryRef, new ViewDisplay(instancesView)).build());
×
337

338
            View templatesView = View.get(TEMPLATES_VIEW);
×
339
            QueryRef templatesQueryRef = new QueryRef(templatesView.getQuery().getQueryId(), "thing", ref);
×
340
            infoSection.add(QueryResultListBuilder.create("templates-panel", templatesQueryRef, new ViewDisplay(templatesView)).build());
×
341
        }
342
        add(infoSection);
×
343

344
        // References to the explored thing (the References view, moved here from
345
        // the About pages).
346
        View refView = View.get(ReferencesPage.REFERENCES_VIEW);
×
347
        QueryRef refQueryRef = new QueryRef(refView.getQuery().getQueryId(), "ref", ref);
×
348
        add(QueryResultTableBuilder.create("references", refQueryRef, new ViewDisplay(refView)).build());
×
349
    }
×
350

351
    @Override
352
    protected void onBeforeRender() {
353
        if (publishedNanopub != null && !getPageParameters().get("postpub-redirect-url").isNull()) {
×
354
            String forwardUrl = getPageParameters().get("postpub-redirect-url").toString();
×
355
            if (forwardUrl.contains("?")) {
×
356
                // TODO: Add here URI of created nanopublication too?
357
                throw new RedirectToUrlException(forwardUrl);
×
358
            } else {
359
                Set<String> introducedIds = NanopubUtils.getIntroducedIriIds(publishedNanopub);
×
360
                String redirectId = introducedIds.size() == 1 ? introducedIds.iterator().next() : publishedNanopub.getUri().stringValue();
×
361
                String paramString = Utils.getPageParametersAsString(new PageParameters().set("id", redirectId));
×
362
                throw new RedirectToUrlException(forwardUrl + "?" + paramString);
×
363
            }
364
        }
365
        super.onBeforeRender();
×
366
    }
×
367

368
    private BookmarkablePageLink<Void> createAssertionLink(String wicketId, String npUri, String format, boolean txt) {
369
        PageParameters params = new PageParameters()
×
370
                .set("type", "np")
×
371
                .set("id", npUri)
×
372
                .set("format", format)
×
373
                .set("assertions", "");
×
374
        if (txt) {
×
375
            params.set("txt", "");
×
376
        }
377
        return new BookmarkablePageLink<>(wicketId, DownloadRdfPage.class, params);
×
378
    }
379

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