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

knowledgepixels / nanodash / 30350036267

28 Jul 2026 10:15AM UTC coverage: 30.298% (+0.9%) from 29.361%
30350036267

Pull #562

github

web-flow
Merge 3b44daf57 into aa51399bf
Pull Request #562: Superseding/Overriding allowed only when latest version

2121 of 7645 branches covered (27.74%)

Branch coverage included in aggregate %.

4058 of 12749 relevant lines covered (31.83%)

4.8 hits per line

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

3.16
src/main/java/com/knowledgepixels/nanodash/component/PublishForm.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.*;
4
import com.knowledgepixels.nanodash.component.menu.EntryActionMenu;
5
import com.knowledgepixels.nanodash.domain.AbstractResourceWithProfile;
6
import com.knowledgepixels.nanodash.domain.User;
7
import com.knowledgepixels.nanodash.page.ExplorePage;
8
import com.knowledgepixels.nanodash.page.NanodashPage;
9
import com.knowledgepixels.nanodash.page.PreviewPage;
10
import com.knowledgepixels.nanodash.template.*;
11
import com.knowledgepixels.nanodash.vocabulary.KPXL_TERMS;
12
import org.apache.commons.lang3.Strings;
13
import org.apache.wicket.Component;
14
import org.apache.wicket.RestartResponseException;
15
import org.apache.wicket.ajax.AjaxEventBehavior;
16
import org.apache.wicket.ajax.AjaxRequestTarget;
17
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
18
import org.apache.wicket.feedback.FeedbackMessage;
19
import org.apache.wicket.markup.html.WebMarkupContainer;
20
import org.apache.wicket.markup.html.WebPage;
21
import org.apache.wicket.markup.html.basic.Label;
22
import org.apache.wicket.markup.html.form.Button;
23
import org.apache.wicket.markup.html.form.CheckBox;
24
import org.apache.wicket.markup.html.form.Form;
25
import org.apache.wicket.markup.html.form.FormComponent;
26
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
27
import org.apache.wicket.markup.html.list.ListItem;
28
import org.apache.wicket.markup.html.list.ListView;
29
import org.apache.wicket.markup.html.panel.FeedbackPanel;
30
import org.apache.wicket.markup.html.panel.Panel;
31
import org.apache.wicket.markup.repeater.Item;
32
import org.apache.wicket.markup.repeater.data.DataView;
33
import org.apache.wicket.markup.repeater.data.ListDataProvider;
34
import org.apache.wicket.model.IModel;
35
import org.apache.wicket.model.Model;
36
import org.apache.wicket.request.mapper.parameter.PageParameters;
37
import org.eclipse.rdf4j.model.IRI;
38
import org.eclipse.rdf4j.model.Literal;
39
import org.eclipse.rdf4j.model.Statement;
40
import org.eclipse.rdf4j.model.ValueFactory;
41
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
42
import org.eclipse.rdf4j.model.vocabulary.FOAF;
43
import org.eclipse.rdf4j.model.vocabulary.RDF;
44
import org.eclipse.rdf4j.model.vocabulary.RDFS;
45
import org.nanopub.MalformedNanopubException;
46
import org.nanopub.Nanopub;
47
import org.nanopub.NanopubAlreadyFinalizedException;
48
import org.nanopub.NanopubCreator;
49
import org.nanopub.extra.security.SignNanopub;
50
import org.nanopub.extra.security.SignatureAlgorithm;
51
import org.nanopub.extra.security.TransformContext;
52
import org.nanopub.extra.server.PublishNanopub;
53
import org.nanopub.extra.services.ApiResponse;
54
import org.nanopub.extra.services.ApiResponseEntry;
55
import org.nanopub.extra.services.QueryRef;
56
import org.nanopub.vocabulary.NPX;
57
import org.nanopub.vocabulary.NTEMPLATE;
58
import org.slf4j.Logger;
59
import org.slf4j.LoggerFactory;
60
import org.wicketstuff.select2.ChoiceProvider;
61
import org.wicketstuff.select2.Response;
62
import org.wicketstuff.select2.Select2Choice;
63

64
import java.lang.reflect.InvocationTargetException;
65
import java.util.*;
66

67
/**
68
 * Form for publishing a nanopublication.
69
 */
70
public class PublishForm extends Panel {
71

72
    private static final Logger logger = LoggerFactory.getLogger(PublishForm.class);
9✔
73

74
    private static final ValueFactory vf = SimpleValueFactory.getInstance();
6✔
75

76
    public static final String CREATOR_PUB_INFO_TEMPLATE = "https://w3id.org/np/RAukAcWHRDlkqxk7H2XNSegc1WnHI569INvNr-xdptDGI";
77
    public static final String LICENSE_PUB_INFO_TEMPLATE = "https://w3id.org/np/RACJ58Gvyn91LqCKIO9zu1eijDQIeEff28iyDrJgjSJF8";
78
    public static final String DEFAULT_PROV_TEMPLATE = "https://w3id.org/np/RA7lSq6MuK_TIC6JMSHvLtee3lpLoZDOqLJCLXevnrPoU";
79
    private static final String supersedesPubInfoTemplateId = "https://w3id.org/np/RAoTD7udB2KtUuOuAe74tJi1t3VzK0DyWS7rYVAq1GRvw";
80
    private static final String derivesFromPubInfoTemplateId = "https://w3id.org/np/RARW4MsFkHuwjycNElvEVtuMjpf4yWDL10-0C5l2MqqRQ";
81

82
    private static final String[] fixedPubInfoTemplates = new String[]{CREATOR_PUB_INFO_TEMPLATE, LICENSE_PUB_INFO_TEMPLATE};
33✔
83

84
    // Page parameters that make the form supersede or override an existing nanopublication
85
    // ("fill" is the deprecated form of "supersede"):
86
    private static final String[] sourceParamKeys = new String[]{"supersede", "supersede-a", "override", "override-a", "fill"};
72✔
87

88
    /**
89
     * Fill modes for the nanopublication to be created.
90
     */
91
    public enum FillMode {
9✔
92
        /**
93
         * Use fill mode
94
         */
95
        USE,
18✔
96
        /**
97
         * Supersede fill mode
98
         */
99
        SUPERSEDE,
18✔
100
        /**
101
         * Derive fill mode
102
         */
103
        DERIVE,
18✔
104
        /**
105
         * Override fill mode: like {@link #DERIVE} in that it records a
106
         * {@code prov:wasDerivedFrom} link, but (like {@link #SUPERSEDE}) it
107
         * keeps the source nanopub's introduced resource IRIs and its root
108
         * definition nanopub.
109
         */
110
        OVERRIDE
18✔
111
    }
112

113
    protected Form<?> form;
114
    protected FeedbackPanel feedbackPanel;
115
    private final TemplateContext assertionContext;
116
    private TemplateContext provenanceContext;
117
    private final List<TemplateContext> pubInfoContexts = new ArrayList<>();
×
118
    private final Map<String, TemplateContext> pubInfoContextMap = new HashMap<>();
×
119
    private final List<TemplateContext> requiredPubInfoContexts = new ArrayList<>();
×
120
    private String targetNamespace;
121
    private final Class<? extends WebPage> confirmPageClass;
122

123
    /**
124
     * Constructor for the PublishForm.
125
     *
126
     * @param id the Wicket component ID
127
     * @param pageParams the parameters for the page, which may include
128
     * information on how to fill the form
129
     * @param publishPageClass the class of the page to redirect to after
130
     * successful publication
131
     * @param confirmPageClass the class of the confirmation page to show after
132
     * publication
133
     */
134
    public PublishForm(String id, final PageParameters pageParams, Class<? extends WebPage> publishPageClass, Class<? extends WebPage> confirmPageClass) {
135
        super(id);
×
136
        setOutputMarkupId(true);
×
137
        this.confirmPageClass = confirmPageClass;
×
138

139
        WebMarkupContainer linkMessageItem = new WebMarkupContainer("link-message-item");
×
140
        if (pageParams.get("link-message").isNull()) {
×
141
            linkMessageItem.add(new Label("link-message", ""));
×
142
            linkMessageItem.setVisible(false);
×
143
        } else {
144
            linkMessageItem.add(new Label("link-message", Utils.sanitizeHtml(pageParams.get("link-message").toString())).setEscapeModelStrings(false));
×
145
        }
146
        add(linkMessageItem);
×
147

148
        final Nanopub fillNp;
149
        final FillMode fillMode;
150
        boolean fillOnlyAssertion = false;
×
151
        if (!pageParams.get("use").isNull()) {
×
152
            fillNp = Utils.getNanopub(pageParams.get("use").toString());
×
153
            fillMode = FillMode.USE;
×
154
        } else if (!pageParams.get("use-a").isNull()) {
×
155
            fillNp = Utils.getNanopub(pageParams.get("use-a").toString());
×
156
            fillMode = FillMode.USE;
×
157
            fillOnlyAssertion = true;
×
158
        } else if (!pageParams.get("supersede").isNull()) {
×
159
            fillNp = Utils.getNanopub(pageParams.get("supersede").toString());
×
160
            fillMode = FillMode.SUPERSEDE;
×
161
            targetNamespace = pageParams.get("supersede").toString().replaceFirst("RA[A-Za-z0-9-_]{43}$", "");
×
162
        } else if (!pageParams.get("supersede-a").isNull()) {
×
163
            fillNp = Utils.getNanopub(pageParams.get("supersede-a").toString());
×
164
            fillMode = FillMode.SUPERSEDE;
×
165
            fillOnlyAssertion = true;
×
166
        } else if (!pageParams.get("derive").isNull()) {
×
167
            fillNp = Utils.getNanopub(pageParams.get("derive").toString());
×
168
            fillMode = FillMode.DERIVE;
×
169
        } else if (!pageParams.get("derive-a").isNull()) {
×
170
            fillNp = Utils.getNanopub(pageParams.get("derive-a").toString());
×
171
            fillMode = FillMode.DERIVE;
×
172
            fillOnlyAssertion = true;
×
173
        } else if (!pageParams.get("override").isNull()) {
×
174
            fillNp = Utils.getNanopub(pageParams.get("override").toString());
×
175
            fillMode = FillMode.OVERRIDE;
×
176
        } else if (!pageParams.get("override-a").isNull()) {
×
177
            fillNp = Utils.getNanopub(pageParams.get("override-a").toString());
×
178
            fillMode = FillMode.OVERRIDE;
×
179
            fillOnlyAssertion = true;
×
180
        } else if (!pageParams.get("fill-all").isNull()) {
×
181
            // TODO: This is deprecated and should be removed at some point
182
            fillNp = Utils.getNanopub(pageParams.get("fill-all").toString());
×
183
            fillMode = FillMode.USE;
×
184
        } else if (!pageParams.get("fill").isNull()) {
×
185
            // TODO: This is deprecated and should be removed at some point
186
            fillNp = Utils.getNanopub(pageParams.get("fill").toString());
×
187
            fillMode = FillMode.SUPERSEDE;
×
188
        } else {
189
            fillNp = null;
×
190
            fillMode = null;
×
191
        }
192

193
        final TemplateData td = TemplateData.get();
×
194

195
        // TODO Properly integrate this namespace feature:
196
        String templateId = pageParams.get("template").toString();
×
197
        Template template = td.getTemplate(templateId);
×
198
        if (template != null && template.getTargetNamespace() != null) {
×
199
            targetNamespace = template.getTargetNamespace();
×
200
        }
201
        if (!pageParams.get("target-namespace").isNull()) {
×
202
            targetNamespace = pageParams.get("target-namespace").toString();
×
203
        }
204
        if (targetNamespace == null) {
×
205
            targetNamespace = Template.DEFAULT_TARGET_NAMESPACE;
×
206
        }
207
        String targetNamespaceLabel = targetNamespace + "...";
×
208
        targetNamespace = targetNamespace + "~~~ARTIFACTCODE~~~/";
×
209

210
        assertionContext = new TemplateContext(ContextType.ASSERTION, templateId, "statement", targetNamespace);
×
211
        assertionContext.setFillMode(fillMode);
×
212
        final String prTemplateId;
213
        if (pageParams.get("prtemplate").toString() != null) {
×
214
            prTemplateId = pageParams.get("prtemplate").toString();
×
215
        } else {
216
            if (fillNp != null && !fillOnlyAssertion) {
×
217
                if (td.getProvenanceTemplateId(fillNp) != null) {
×
218
                    prTemplateId = td.getProvenanceTemplateId(fillNp).stringValue();
×
219
                } else {
220
                    prTemplateId = "http://purl.org/np/RAcm8OurwUk15WOgBM9wySo-T3a5h6as4K8YR5MBrrxUc";
×
221
                }
222
            } else if (assertionContext.getTemplate().getDefaultProvenance() != null) {
×
223
                prTemplateId = assertionContext.getTemplate().getDefaultProvenance().stringValue();
×
224
            } else {
225
                prTemplateId = DEFAULT_PROV_TEMPLATE;
×
226
            }
227
        }
228
        provenanceContext = new TemplateContext(ContextType.PROVENANCE, prTemplateId, "pr-statement", targetNamespace);
×
229
        for (String t : fixedPubInfoTemplates) {
×
230
            // TODO consistently check for latest versions of templates here and below:
231
            TemplateContext c = new TemplateContext(ContextType.PUBINFO, t, "pi-statement", targetNamespace);
×
232
            pubInfoContexts.add(c);
×
233
            pubInfoContextMap.put(c.getTemplate().getId(), c);
×
234
            requiredPubInfoContexts.add(c);
×
235
            if (t.equals(LICENSE_PUB_INFO_TEMPLATE) && fillMode != FillMode.SUPERSEDE && fillMode != FillMode.DERIVE && fillMode != FillMode.OVERRIDE) {
×
236
                IRI defaultLicense = User.getDefaultLicense(NanodashSession.get().getUserIri());
×
237
                if (defaultLicense != null) {
×
238
                    c.setParam("license", defaultLicense.stringValue());
×
239
                }
240
            }
241
        }
242
        if (fillMode == FillMode.SUPERSEDE) {
×
243
            TemplateContext c = new TemplateContext(ContextType.PUBINFO, supersedesPubInfoTemplateId, "pi-statement", targetNamespace);
×
244
            pubInfoContexts.add(c);
×
245
            pubInfoContextMap.put(supersedesPubInfoTemplateId, c);
×
246
            //requiredPubInfoContexts.add(c);
247
            c.setParam("np", fillNp.getUri().stringValue());
×
248
        } else if (fillMode == FillMode.DERIVE || fillMode == FillMode.OVERRIDE) {
×
249
            TemplateContext c = new TemplateContext(ContextType.PUBINFO, derivesFromPubInfoTemplateId, "pi-statement", targetNamespace);
×
250
            pubInfoContexts.add(c);
×
251
            pubInfoContextMap.put(derivesFromPubInfoTemplateId, c);
×
252
            c.setParam("np", fillNp.getUri().stringValue());
×
253
        }
254
        for (IRI r : assertionContext.getTemplate().getRequiredPubInfoElements()) {
×
255
            String latestId = td.getLatestTemplateId(r.stringValue());
×
256
            if (pubInfoContextMap.containsKey(r.stringValue()) || pubInfoContextMap.containsKey(latestId)) {
×
257
                continue;
×
258
            }
259
            TemplateContext c = new TemplateContext(ContextType.PUBINFO, r.stringValue(), "pi-statement", targetNamespace);
×
260
            pubInfoContexts.add(c);
×
261
            pubInfoContextMap.put(c.getTemplateId(), c);
×
262
            requiredPubInfoContexts.add(c);
×
263
        }
×
264
        Map<Integer, TemplateContext> piParamIdMap = new HashMap<>();
×
265
        for (String k : pageParams.getNamedKeys()) {
×
266
            if (!k.matches("pitemplate[1-9][0-9]*")) {
×
267
                continue;
×
268
            }
269
            Integer i = Integer.parseInt(k.replaceFirst("^pitemplate([1-9][0-9]*)$", "$1"));
×
270
            String tid = pageParams.get(k).toString();
×
271
            // TODO Allow for automatically using latest template version:
272
            //String piTemplateIdLatest = QueryApiAccess.getLatestVersionId(tid);
273
            TemplateContext c = createPubInfoContext(tid);
×
274
            if (piParamIdMap.containsKey(i)) {
×
275
                // TODO: handle this error better
276
                logger.error("Publication Info template param identifier defined multiple times: {}", i);
×
277
            }
278
            piParamIdMap.put(i, c);
×
279
        }
×
280
        if (fillNp != null && !fillOnlyAssertion) {
×
281
            for (IRI piTemplateId : td.getPubinfoTemplateIds(fillNp)) {
×
282
                String piTemplateIdLatest = td.getLatestTemplateId(piTemplateId.stringValue());
×
283
                if (piTemplateIdLatest.equals(supersedesPubInfoTemplateId)) {
×
284
                    continue;
×
285
                }
286
                if (!pubInfoContextMap.containsKey(piTemplateIdLatest)) {
×
287
                    // TODO Allow for automatically using latest template version
288
                    createPubInfoContext(piTemplateId.stringValue());
×
289
                }
290
            }
×
291
        }
292
        if (!pageParams.get("values-from-query").isEmpty() && !pageParams.get("values-from-query-mapping").isEmpty()) {
×
293
            String querySpec = pageParams.get("values-from-query").toString();
×
294

295
            String mapping = pageParams.get("values-from-query-mapping").toString();
×
296
            String mapsFrom, mapsTo;
297
            if (mapping.contains(":")) {
×
298
                mapsFrom = mapping.split(":")[0];
×
299
                mapsTo = mapping.split(":")[1];
×
300
            } else {
301
                mapsFrom = mapping;
×
302
                mapsTo = mapping;
×
303
            }
304
            ApiResponse resp = ApiCache.retrieveResponseSync(QueryRef.parseString(querySpec), false);
×
305
            int i = 0;
×
306
            for (ApiResponseEntry e : resp.getData()) {
×
307
                String mapsToSuffix = "";
×
308
                if (i > 0) {
×
309
                    mapsToSuffix = "__" + i;
×
310
                }
311
                assertionContext.setParam(mapsTo + mapsToSuffix, e.get(mapsFrom));
×
312
                i++;
×
313
            }
×
314
        }
315
        for (String k : pageParams.getNamedKeys()) {
×
316
            if (k.startsWith("param_")) {
×
317
                assertionContext.setParam(k.substring(6), pageParams.get(k).toString());
×
318
            }
319
            if (k.startsWith("prparam_")) {
×
320
                provenanceContext.setParam(k.substring(8), pageParams.get(k).toString());
×
321
            }
322
            if (k.matches("piparam[1-9][0-9]*_.*")) {
×
323
                Integer i = Integer.parseInt(k.replaceFirst("^piparam([1-9][0-9]*)_.*$", "$1"));
×
324
                if (!piParamIdMap.containsKey(i)) {
×
325
                    logger.error("Parameter {} of the publication info template not found", i);
×
326
                    continue;
×
327
                }
328
                String n = k.replaceFirst("^piparam[1-9][0-9]*_(.*)$", "$1");
×
329
                logger.info(n);
×
330
                piParamIdMap.get(i).setParam(n, pageParams.get(k).toString());
×
331
            }
332
        }
×
333

334
        final Nanopub improveNp;
335
        if (!pageParams.get("improve").isNull()) {
×
336
            improveNp = Utils.getNanopub(pageParams.get("improve").toString());
×
337
        } else {
338
            improveNp = null;
×
339
        }
340

341
        // Propagate fill source (supersede/derive/improve) so contexts can resolve
342
        // the `local:nanopub`/`local:assertion` sentinels to the fill nanopub's URIs.
343
        Nanopub fillSource = fillNp != null ? fillNp : improveNp;
×
344
        if (fillSource != null) {
×
345
            assertionContext.setFillSource(fillSource);
×
346
            provenanceContext.setFillSource(fillSource);
×
347
            for (TemplateContext c : pubInfoContexts) {
×
348
                c.setFillSource(fillSource);
×
349
            }
×
350
        }
351

352
        // Init statements only now, in order to pick up parameter values:
353
        assertionContext.initStatements();
×
354
        provenanceContext.initStatements();
×
355
        for (TemplateContext c : pubInfoContexts) {
×
356
            c.initStatements();
×
357
        }
×
358

359
        String latestAssertionId = td.getLatestTemplateId(assertionContext.getTemplateId());
×
360
        if (!assertionContext.getTemplateId().equals(latestAssertionId)) {
×
361
            add(new Label("newversion", "There is a new version of this assertion template:"));
×
362
            PageParameters params = new PageParameters(pageParams);
×
363
            params.set("template", latestAssertionId);
×
364
            add(new BookmarkablePageLink<Void>("newversionlink", publishPageClass, params));
×
365
            if ("latest".equals(pageParams.get("template-version").toString())) {
×
366
                throw new RestartResponseException(publishPageClass, params);
×
367
            }
368
        } else {
×
369
            add(new Label("newversion", "").setVisible(false));
×
370
            add(new Label("newversionlink", "").setVisible(false));
×
371
        }
372

373
        final List<Statement> unusedStatementList = new ArrayList<>();
×
374
        final List<Statement> unusedPrStatementList = new ArrayList<>();
×
375
        final List<Statement> unusedPiStatementList = new ArrayList<>();
×
376
        if (fillNp != null) {
×
377
            ValueFiller filler = new ValueFiller(fillNp, ContextType.ASSERTION, true, fillMode);
×
378
            filler.fill(assertionContext);
×
379
            unusedStatementList.addAll(filler.getUnusedStatements());
×
380

381
            if (!fillOnlyAssertion) {
×
382
                ValueFiller prFiller = new ValueFiller(fillNp, ContextType.PROVENANCE, true);
×
383
                prFiller.fill(provenanceContext);
×
384
                unusedPrStatementList.addAll(prFiller.getUnusedStatements());
×
385

386
                ValueFiller piFiller = new ValueFiller(fillNp, ContextType.PUBINFO, true);
×
387
                if (!assertionContext.getTemplate().getTargetNanopubTypes().isEmpty()) {
×
388
                    for (Statement st : new ArrayList<>(piFiller.getUnusedStatements())) {
×
389
                        if (st.getSubject().stringValue().equals(LocalUri.of("nanopub").stringValue()) && st.getPredicate().equals(NPX.HAS_NANOPUB_TYPE)) {
×
390
                            if (assertionContext.getTemplate().getTargetNanopubTypes().contains(st.getObject())) {
×
391
                                piFiller.removeUnusedStatement(st);
×
392
                            }
393
                        }
394
                    }
×
395
                }
396
                for (TemplateContext c : pubInfoContexts) {
×
397
                    piFiller.fill(c);
×
398
                }
×
399
                piFiller.removeUnusedStatements(NanodashSession.get().getUserIri(), FOAF.NAME, null);
×
400
                if (piFiller.hasUnusedStatements()) {
×
401
                    final String handcodedStatementsTemplateId = "https://w3id.org/np/RAMEgudZsQ1bh1fZhfYnkthqH6YSXpghSE_DEN1I-6eAI";
×
402
                    if (!pubInfoContextMap.containsKey(handcodedStatementsTemplateId)) {
×
403
                        TemplateContext c = createPubInfoContext(handcodedStatementsTemplateId);
×
404
                        c.setFillSource(fillNp);
×
405
                        c.initStatements();
×
406
                        piFiller.fill(c);
×
407
                    }
408
                }
409
                unusedPiStatementList.addAll(piFiller.getUnusedStatements());
×
410
                // TODO: Also use pubinfo templates stated in nanopub to be filled in?
411
//                                Set<IRI> pubinfoTemplateIds = Template.getPubinfoTemplateIds(fillNp);
412
//                                if (!pubinfoTemplateIds.isEmpty()) {
413
//                                        ValueFiller piFiller = new ValueFiller(fillNp, ContextType.PUBINFO);
414
//                                        for (IRI pubinfoTemplateId : pubinfoTemplateIds) {
415
//                                                // TODO: Make smart choice on the ordering in trying to fill in all pubinfo elements
416
//                                                piFiller.fill(pubInfoContextMap.get(pubinfoTemplateId.stringValue()));
417
//                                        }
418
//                                        warningMessage += (piFiller.getWarningMessage() == null ? "" : "Publication info: " + piFiller.getWarningMessage() + " ");
419
//                                }
420
            }
421
        } else if (improveNp != null) {
×
422
            ValueFiller filler = new ValueFiller(improveNp, ContextType.ASSERTION, true);
×
423
            filler.fill(assertionContext);
×
424
            unusedStatementList.addAll(filler.getUnusedStatements());
×
425
        }
426
        if (!unusedStatementList.isEmpty()) {
×
427
            add(new Label("warnings", "Some content from the existing nanopublication could not be filled in:"));
×
428
        } else {
429
            add(new Label("warnings", "").setVisible(false));
×
430
        }
431
        add(new DataView<Statement>("unused-statements", new ListDataProvider<Statement>(unusedStatementList)) {
×
432

433
            @Override
434
            protected void populateItem(Item<Statement> item) {
435
                item.add(new TripleItem("unused-statement", item.getModelObject(), (fillNp != null ? fillNp : improveNp), null));
×
436
            }
×
437

438
        });
439
        add(new DataView<Statement>("unused-prstatements", new ListDataProvider<Statement>(unusedPrStatementList)) {
×
440

441
            @Override
442
            protected void populateItem(Item<Statement> item) {
443
                item.add(new TripleItem("unused-prstatement", item.getModelObject(), (fillNp != null ? fillNp : improveNp), null));
×
444
            }
×
445

446
        });
447
        add(new DataView<Statement>("unused-pistatements", new ListDataProvider<Statement>(unusedPiStatementList)) {
×
448

449
            @Override
450
            protected void populateItem(Item<Statement> item) {
451
                item.add(new TripleItem("unused-pistatement", item.getModelObject(), (fillNp != null ? fillNp : improveNp), null));
×
452
            }
×
453

454
        });
455

456
        // Finalize statements, which picks up parameter values in repetitions:
457
        assertionContext.finalizeStatements();
×
458
        provenanceContext.finalizeStatements();
×
459
        for (TemplateContext c : pubInfoContexts) {
×
460
            c.finalizeStatements();
×
461
        }
×
462

463
        final CheckBox consentCheck = new CheckBox("consentcheck", new Model<>(false));
×
464
        consentCheck.add(new InvalidityHighlighting());
×
465

466
        form = new Form<Void>("form") {
×
467

468
            @Override
469
            protected void onConfigure() {
470
                super.onConfigure();
×
471
                form.getFeedbackMessages().clear();
×
472
//                                formComponents.clear();
473
            }
×
474

475
            protected void onSubmit() {
476
                // Re-check here too: a newer version might have appeared while the form was open.
477
                if (!canPublishFromSource(pageParams)) {
×
478
                    return;
×
479
                }
480

481
                if (!Boolean.TRUE.equals(consentCheck.getModelObject())) {
×
482
                    feedbackPanel.error("You need to check the checkbox that you understand the consequences.");
×
483
                    return;
×
484
                }
485
                logger.info("Publish form submitted");
×
486
                Nanopub signedNp;
487
                try {
488
                    Nanopub np = createNanopub();
×
489
                    logger.info("Nanopublication created: {}", np.getUri());
×
490
                    TransformContext tc = new TransformContext(SignatureAlgorithm.RSA, NanodashSession.get().getKeyPair(), NanodashSession.get().getUserIri(), false, false, false);
×
491
                    signedNp = SignNanopub.signAndTransform(np, tc);
×
492
                    logger.info("Nanopublication signed: {}", signedNp.getUri());
×
493
                    String npUrl = PublishNanopub.publish(signedNp);
×
494
                    logger.info("Nanopublication published: {}", npUrl);
×
495
                    Utils.cacheNanopub(signedNp);
×
496
                } catch (Exception ex) {
×
497
                    signedNp = null;
×
498
                    logger.error("Nanopublication publishing failed: {}", ex.getMessage());
×
499
                    String message = ex.getClass().getName();
×
500
                    if (ex.getMessage() != null) {
×
501
                        message = ex.getMessage();
×
502
                    }
503
                    feedbackPanel.error(message);
×
504
                }
×
505
                if (signedNp != null) {
×
506
                    String toRefresh = pageParams.get("refresh-upon-publish").toString("");
×
507
                    if (!toRefresh.isEmpty()) {
×
508
                        WicketApplication.get().notifyNanopubPublished(signedNp, toRefresh, 5 * 1000);
×
509
                    }
510
                    String contextId = pageParams.get("context").toString("");
×
511
                    // Broaden the refresh: also force-refresh the context resource's own
512
                    // data (e.g. a space's roles/members) so the page we redirect to —
513
                    // typically its Content tab — reflects the just-published change, not
514
                    // only the specific view query that was acted on.
515
                    if (!contextId.isEmpty() && !contextId.equals(toRefresh)
×
516
                            && AbstractResourceWithProfile.isResourceWithProfile(contextId)) {
×
517
                        WicketApplication.get().notifyNanopubPublished(signedNp, contextId, 5 * 1000);
×
518
                    }
519
                    if (pageParams.get("postpub-redirect-url").isEmpty() && confirmPageClass == null) {
×
520
                        // Forward to the context resource's page, or home if no context; always throws.
521
                        NavigationContext.redirectAfterPublish(signedNp, pageParams);
×
522
                    }
523
                    // Connector flow: show the connector's own confirmation page.
524
                    NanodashPage confirmPage = getConfirmPage(signedNp, pageParams);
×
525
                    if (confirmPage != null) {
×
526
                        throw new RestartResponseException(confirmPage);
×
527
                    }
528
                    NavigationContext.redirectAfterPublish(signedNp, pageParams);
×
529
                }
530
            }
×
531

532
            @Override
533
            protected void onValidate() {
534
                super.onValidate();
×
535
                for (Component fc : assertionContext.getComponents()) {
×
536
                    processFeedback(fc);
×
537
                }
×
538
                for (Component fc : provenanceContext.getComponents()) {
×
539
                    processFeedback(fc);
×
540
                }
×
541
                for (TemplateContext c : pubInfoContexts) {
×
542
                    for (Component fc : c.getComponents()) {
×
543
                        processFeedback(fc);
×
544
                    }
×
545
                }
×
546
            }
×
547

548
            private void processFeedback(Component c) {
549
                if (c instanceof FormComponent) {
×
550
                    ((FormComponent<?>) c).processInput();
×
551
                }
552
                for (FeedbackMessage fm : c.getFeedbackMessages()) {
×
553
                    form.getFeedbackMessages().add(fm);
×
554
                }
×
555
            }
×
556

557
        };
558
        form.setOutputMarkupId(true);
×
559

560
        //form.add(new Label("nanopub-namespace", targetNamespaceLabel));
561
        form.add(newSourceMenu("templatelink", assertionContext.getTemplate().getId()));
×
562
        form.add(new Label("templatename", assertionContext.getTemplate().getLabel()));
×
563
        String description = assertionContext.getTemplate().getLabel();
×
564
        form.add(new Label("templatedesc", assertionContext.getTemplate().getDescription()).setEscapeModelStrings(false));
×
565

566
        form.add(new ListView<StatementItem>("statements", assertionContext.getStatementItems()) {
×
567

568
            protected void populateItem(ListItem<StatementItem> item) {
569
                item.add(item.getModelObject());
×
570
            }
×
571

572
        });
573

574
        final Map<String, Boolean> handledProvTemplates = new HashMap<>();
×
575
        final String defaultProvTemplateId;
576
        if (assertionContext.getTemplate().getDefaultProvenance() != null) {
×
577
            defaultProvTemplateId = assertionContext.getTemplate().getDefaultProvenance().stringValue();
×
578
            handledProvTemplates.put(defaultProvTemplateId, true);
×
579
        } else {
580
            defaultProvTemplateId = null;
×
581
        }
582
        final List<String> recommendedProvTemplateOptionIds = new ArrayList<>();
×
583
        final List<String> provTemplateOptionIds = new ArrayList<>();
×
584
        if (pageParams.get("prtemplate-options").isNull()) {
×
585
            // TODO Make this dynamic and consider updated templates:
586
            recommendedProvTemplateOptionIds.add(DEFAULT_PROV_TEMPLATE);
×
587
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RAcTpoh5Ra0ssqmcpOgWdaZ_YiPE6demO6cpw-2RvSNs8");
×
588
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RA4LGtuOqTIMqVAkjnfBXk1YDcAPNadP5CGiaJiBkdHCQ");
×
589
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RAl_-VTw9Re_uRF8r8y0rjlfnu7FlhTa8xg_8xkcweqiE");
×
590
            recommendedProvTemplateOptionIds.add("https://w3id.org/np/RASORV2mMEVpS4lWh2bwUTEcV-RWjbD9RPbN7J0PIeYAU");
×
591
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RAjkBbM5yQm7hKH1l_Jk3HAUqWi3Bd57TPmAOZCsZmi_M");
×
592
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RAGXx_k9eQMnXaCbsXMsJbGClwZtQEGNg0GVJu6amdAVw");
×
593
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RA1fnITI3Pu1UQ0CHghNpys3JwQrM32LBnjmDLoayp9-4");
×
594
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RAJgbsGeGdTG-zq_gU0TLw4s3raMgoRk-mPlc2DSLXvE0");
×
595
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RA6SXfhUY-xeblZU8HhPddw6tsu-C5NXevG6C_zv4bMxU");
×
596
            for (String s : recommendedProvTemplateOptionIds) {
×
597
                handledProvTemplates.put(s, true);
×
598
            }
×
599

600
            for (ApiResponseEntry t : td.getProvenanceTemplates()) {
×
601
                String tid = t.get("np");
×
602
                if (handledProvTemplates.containsKey(tid)) {
×
603
                    continue;
×
604
                }
605
                provTemplateOptionIds.add(tid);
×
606
                handledProvTemplates.put(tid, true);
×
607
            }
×
608
        } else {
609
            for (String s : pageParams.get("prtemplate-options").toString().split(" ")) {
×
610
                if (handledProvTemplates.containsKey(s)) {
×
611
                    continue;
×
612
                }
613
                recommendedProvTemplateOptionIds.add(s);
×
614
                handledProvTemplates.put(s, true);
×
615
            }
616
        }
617

618
        ChoiceProvider<String> prTemplateChoiceProvider = new ChoiceProvider<String>() {
×
619

620
            @Override
621
            public String getDisplayValue(String object) {
622
                if (object == null || object.isEmpty()) {
×
623
                    return "";
×
624
                }
625
                Template t = td.getTemplate(object);
×
626
                if (t != null) {
×
627
                    return t.getLabel();
×
628
                }
629
                return object;
×
630
            }
631

632
            @Override
633
            public String getIdValue(String object) {
634
                return object;
×
635
            }
636

637
            // Getting strange errors with Tomcat if this method is not overridden:
638
            @Override
639
            public void detach() {
640
            }
×
641

642
            @Override
643
            public void query(String term, int page, Response<String> response) {
644
                if (term == null) {
×
645
                    term = "";
×
646
                }
647
                term = term.toLowerCase();
×
648
                if (pageParams.get("prtemplate").toString() != null) {
×
649
                    // Using this work-around with "——" because 'optgroup' is not available through Wicket's Select2 classes
650
                    response.add("—— default for this link ——");
×
651
                    response.add(prTemplateId);
×
652
                }
653
                if (defaultProvTemplateId != null) {
×
654
                    response.add("—— default for this template ——");
×
655
                    response.add(defaultProvTemplateId);
×
656
                }
657
                if (!recommendedProvTemplateOptionIds.isEmpty()) {
×
658
                    if (pageParams.get("prtemplate-options").isNull()) {
×
659
                        response.add("—— recommended ——");
×
660
                    }
661
                    for (String s : recommendedProvTemplateOptionIds) {
×
662
                        if (s.toLowerCase().contains(term) || getDisplayValue(s).toLowerCase().contains(term)) {
×
663
                            response.add(s);
×
664
                        }
665
                    }
×
666
                }
667
                if (!provTemplateOptionIds.isEmpty()) {
×
668
                    response.add("—— others ——");
×
669
                    for (String s : provTemplateOptionIds) {
×
670
                        if (s.toLowerCase().contains(term) || getDisplayValue(s).toLowerCase().contains(term)) {
×
671
                            response.add(s);
×
672
                        }
673
                    }
×
674
                }
675
            }
×
676

677
            @Override
678
            public Collection<String> toChoices(Collection<String> ids) {
679
                return ids;
×
680
            }
681

682
        };
683
        final IModel<String> prTemplateModel = Model.of(provenanceContext.getTemplate().getId());
×
684
        Select2Choice<String> prTemplateChoice = new Select2Choice<String>("prtemplate", prTemplateModel, prTemplateChoiceProvider);
×
685
        prTemplateChoice.setRequired(true);
×
686
        prTemplateChoice.getSettings().setCloseOnSelect(true);
×
687
        prTemplateChoice.add(new AjaxFormComponentUpdatingBehavior("change") {
×
688

689
            @Override
690
            protected void onUpdate(AjaxRequestTarget target) {
691
                String o = prTemplateModel.getObject();
×
692
                if (o.startsWith("——")) {
×
693
                    o = provenanceContext.getTemplate().getId();
×
694
                    prTemplateModel.setObject(o);
×
695
                }
696
                provenanceContext = new TemplateContext(ContextType.PROVENANCE, prTemplateModel.getObject(), "pr-statement", targetNamespace);
×
697
                provenanceContext.initStatements();
×
698
                refreshProvenance(target);
×
699
                provenanceContext.finalizeStatements();
×
700
            }
×
701

702
        });
703
        form.add(prTemplateChoice);
×
704
        refreshProvenance(null);
×
705

706
        final Map<String, Boolean> handledPiTemplates = new HashMap<>();
×
707
        final List<String> recommendedPiTemplateOptionIds = new ArrayList<>();
×
708
        final List<String> piTemplateOptionIds = new ArrayList<>();
×
709
        // TODO Make this dynamic and consider updated templates:
710
        recommendedPiTemplateOptionIds.add("http://purl.org/np/RAXflINqt3smqxV5Aq7E9lzje4uLdkKIOefa6Bp8oJ8CY");
×
711
        recommendedPiTemplateOptionIds.add("https://w3id.org/np/RARW4MsFkHuwjycNElvEVtuMjpf4yWDL10-0C5l2MqqRQ");
×
712
        recommendedPiTemplateOptionIds.add("https://w3id.org/np/RA16U9Wo30ObhrK1NzH7EsmVRiRtvEuEA_Dfc-u8WkUCA");
×
713
        recommendedPiTemplateOptionIds.add("http://purl.org/np/RAdyqI6k07V5nAS82C6hvIDtNWk179EIV4DV-sLbOFKg4");
×
714
        recommendedPiTemplateOptionIds.add("https://w3id.org/np/RAjvEpLZUE7rMoa8q6mWSsN6utJDp-5FmgO47YGsbgw3w");
×
715
        recommendedPiTemplateOptionIds.add("http://purl.org/np/RAxuGRKID6yNg63V5Mf0ot2NjncOnodh-mkN3qT_1txGI");
×
716
        for (TemplateContext c : pubInfoContexts) {
×
717
            // Key both ID forms: the "others" dedup below compares against listing
718
            // entries keyed by nanopub URI, which for a template with embedded
719
            // identity differs from its canonical ID.
720
            handledPiTemplates.put(c.getTemplate().getId(), true);
×
721
            handledPiTemplates.put(c.getTemplate().getNanopub().getUri().stringValue(), true);
×
722
        }
×
723
        for (String s : recommendedPiTemplateOptionIds) {
×
724
            handledPiTemplates.put(s, true);
×
725
        }
×
726

727
        for (ApiResponseEntry entry : td.getPubInfoTemplates()) {
×
728
            String tid = entry.get("np");
×
729
            if (handledPiTemplates.containsKey(tid)) {
×
730
                continue;
×
731
            }
732
            piTemplateOptionIds.add(tid);
×
733
            handledPiTemplates.put(tid, true);
×
734
        }
×
735

736
        ChoiceProvider<String> piTemplateChoiceProvider = new ChoiceProvider<String>() {
×
737

738
            @Override
739
            public String getDisplayValue(String object) {
740
                if (object == null || object.isEmpty()) {
×
741
                    return "";
×
742
                }
743
                Template t = td.getTemplate(object);
×
744
                if (t != null) {
×
745
                    return t.getLabel();
×
746
                }
747
                return object;
×
748
            }
749

750
            @Override
751
            public String getIdValue(String object) {
752
                return object;
×
753
            }
754

755
            // Getting strange errors with Tomcat if this method is not overridden:
756
            @Override
757
            public void detach() {
758
            }
×
759

760
            @Override
761
            public void query(String term, int page, Response<String> response) {
762
                if (term == null) {
×
763
                    term = "";
×
764
                }
765
                term = term.toLowerCase();
×
766
                if (!recommendedPiTemplateOptionIds.isEmpty()) {
×
767
                    response.add("—— recommended ——");
×
768
                    for (String s : recommendedPiTemplateOptionIds) {
×
769
                        boolean isAlreadyUsed = false;
×
770
                        for (TemplateContext c : pubInfoContexts) {
×
771
                            // TODO: make this more efficient/nicer
772
                            if (c.getTemplate().hasId(s)) {
×
773
                                isAlreadyUsed = true;
×
774
                                break;
×
775
                            }
776
                        }
×
777
                        if (isAlreadyUsed) {
×
778
                            continue;
×
779
                        }
780
                        if (s.toLowerCase().contains(term) || getDisplayValue(s).toLowerCase().contains(term)) {
×
781
                            response.add(s);
×
782
                        }
783
                    }
×
784
                }
785
                if (!piTemplateOptionIds.isEmpty()) {
×
786
                    response.add("—— others ——");
×
787
                    for (String s : piTemplateOptionIds) {
×
788
                        boolean isAlreadyUsed = false;
×
789
                        for (TemplateContext c : pubInfoContexts) {
×
790
                            // TODO: make this more efficient/nicer
791
                            if (c.getTemplate().hasId(s)) {
×
792
                                isAlreadyUsed = true;
×
793
                                break;
×
794
                            }
795
                        }
×
796
                        if (isAlreadyUsed) {
×
797
                            continue;
×
798
                        }
799
                        if (s.toLowerCase().contains(term) || getDisplayValue(s).toLowerCase().contains(term)) {
×
800
                            response.add(s);
×
801
                        }
802
                    }
×
803
                }
804
            }
×
805

806
            @Override
807
            public Collection<String> toChoices(Collection<String> ids) {
808
                return ids;
×
809
            }
810

811
        };
812
        final IModel<String> newPiTemplateModel = Model.of();
×
813
        Select2Choice<String> piTemplateChoice = new Select2Choice<String>("pitemplate", newPiTemplateModel, piTemplateChoiceProvider);
×
814
        piTemplateChoice.getSettings().setCloseOnSelect(true);
×
815
        piTemplateChoice.getSettings().setPlaceholder("add element...");
×
816
        piTemplateChoice.add(new AjaxFormComponentUpdatingBehavior("change") {
×
817

818
            @Override
819
            protected void onUpdate(AjaxRequestTarget target) {
820
                if (newPiTemplateModel.getObject().startsWith("——")) {
×
821
                    newPiTemplateModel.setObject(null);
×
822
                    refreshPubInfo(target);
×
823
                    return;
×
824
                }
825
                String id = newPiTemplateModel.getObject();
×
826
                TemplateContext c = new TemplateContext(ContextType.PUBINFO, id, "pi-statement", targetNamespace);
×
827
                c.initStatements();
×
828
                pubInfoContexts.add(c);
×
829
                newPiTemplateModel.setObject(null);
×
830
                refreshPubInfo(target);
×
831
                c.finalizeStatements();
×
832
            }
×
833

834
        });
835
        form.add(piTemplateChoice);
×
836
        refreshPubInfo(null);
×
837

838
        form.add(consentCheck);
×
839

840
        form.add(new Button("preview-button") {
×
841
            @Override
842
            public void onSubmit() {
843
                try {
844
                    // Re-check here too: a newer version might have appeared while the form was open.
845
                    if (!canPublishFromSource(pageParams)) {
×
846
                        return;
×
847
                    }
848

849
                    Nanopub np = createNanopub();
×
850
                    TransformContext tc = new TransformContext(SignatureAlgorithm.RSA, NanodashSession.get().getKeyPair(), NanodashSession.get().getUserIri(), false, false, false);
×
851
                    Nanopub signedNp = SignNanopub.signAndTransform(np, tc);
×
852
                    String previewId = signedNp.getUri().stringValue();
×
853
                    NanodashSession.get().setPreviewNanopub(previewId,
×
854
                            new NanodashSession.PreviewNanopub(signedNp, pageParams, confirmPageClass, Boolean.TRUE.equals(consentCheck.getModelObject()), getPage().getPageReference()));
×
855
                    throw new RestartResponseException(PreviewPage.class, new PageParameters().set("id", previewId));
×
856
                } catch (RestartResponseException ex) {
×
857
                    throw ex;
×
858
                } catch (Exception ex) {
×
859
                    logger.error("Preview failed: {}", ex.getMessage());
×
860
                    String message = ex.getClass().getName();
×
861
                    if (ex.getMessage() != null) {
×
862
                        message = ex.getMessage();
×
863
                    }
864
                    feedbackPanel.error(message);
×
865
                }
866
            }
×
867
        });
868

869
        add(form);
×
870

871
        feedbackPanel = new FeedbackPanel("feedback");
×
872
        feedbackPanel.setOutputMarkupId(true);
×
873
        add(feedbackPanel);
×
874
    }
×
875

876
    static EntryActionMenu newSourceMenu(String id, String templateId) {
877
        BookmarkablePageLink<Void> sourceLink = new BookmarkablePageLink<>("link", ExplorePage.class, new PageParameters().set("id", templateId));
×
878
        sourceLink.add(NavigationContext.pageContextFallback());
×
879
        sourceLink.setBody(Model.of("<span class=\"actionmenu-icon\">↗︎</span>source")).setEscapeModelStrings(false);
×
880
        return new EntryActionMenu(id, List.of(sourceLink));
×
881
    }
882

883
    private void refreshProvenance(AjaxRequestTarget target) {
884
        if (target != null) {
×
885
            form.remove("prtemplatelink");
×
886
            form.remove("pr-statements");
×
887
            target.add(form);
×
888
            target.appendJavaScript("updateElements();");
×
889
        }
890
        form.add(newSourceMenu("prtemplatelink", provenanceContext.getTemplate().getId()));
×
891
        ListView<StatementItem> list = new ListView<StatementItem>("pr-statements", provenanceContext.getStatementItems()) {
×
892

893
            protected void populateItem(ListItem<StatementItem> item) {
894
                item.add(item.getModelObject());
×
895
            }
×
896

897
        };
898
        list.setOutputMarkupId(true);
×
899
        form.add(list);
×
900
    }
×
901

902
    private void refreshPubInfo(AjaxRequestTarget target) {
903
        ListView<TemplateContext> list = new ListView<TemplateContext>("pis", pubInfoContexts) {
×
904

905
            protected void populateItem(ListItem<TemplateContext> item) {
906
                final TemplateContext pic = item.getModelObject();
×
907
                item.add(new Label("pitemplatename", pic.getTemplate().getLabel()));
×
908
                item.add(newSourceMenu("pitemplatelink", pic.getTemplate().getId()));
×
909
                Label remove = new Label("piremove", "×");
×
910
                item.add(remove);
×
911
                remove.add(new AjaxEventBehavior("click") {
×
912

913
                    @Override
914
                    protected void onEvent(AjaxRequestTarget target) {
915
                        pubInfoContexts.remove(pic);
×
916
                        target.add(PublishForm.this);
×
917
                        target.appendJavaScript("updateElements();");
×
918
                    }
×
919

920
                });
921
                if (requiredPubInfoContexts.contains(pic)) {
×
922
                    remove.setVisible(false);
×
923
                }
924
                item.add(new ListView<StatementItem>("pi-statements", pic.getStatementItems()) {
×
925

926
                    protected void populateItem(ListItem<StatementItem> item) {
927
                        item.add(item.getModelObject());
×
928
                    }
×
929

930
                });
931
            }
×
932

933
        };
934
        list.setOutputMarkupId(true);
×
935
        if (target == null) {
×
936
            form.add(list);
×
937
        } else {
938
            form.remove("pis");
×
939
            form.add(list);
×
940
            target.add(form);
×
941
            target.appendJavaScript("updateElements();");
×
942
        }
943
    }
×
944

945
    private TemplateContext createPubInfoContext(String piTemplateId) {
946
        TemplateContext c;
947
        if (pubInfoContextMap.containsKey(piTemplateId)) {
×
948
            c = pubInfoContextMap.get(piTemplateId);
×
949
        } else {
950
            c = new TemplateContext(ContextType.PUBINFO, piTemplateId, "pi-statement", targetNamespace);
×
951
            pubInfoContextMap.put(piTemplateId, c);
×
952
            pubInfoContexts.add(c);
×
953
        }
954
        return c;
×
955
    }
956

957
    private synchronized Nanopub createNanopub() throws MalformedNanopubException, NanopubAlreadyFinalizedException {
958
        assertionContext.getIntroducedIris().clear();
×
959
        assertionContext.getRolePropertyPins().clear();
×
960
        NanopubCreator npCreator = new NanopubCreator(targetNamespace);
×
961
        npCreator.setAssertionUri(vf.createIRI(targetNamespace + "assertion"));
×
962
        assertionContext.propagateStatements(npCreator);
×
963
        provenanceContext.propagateStatements(npCreator);
×
964
        for (TemplateContext c : pubInfoContexts) {
×
965
            c.propagateStatements(npCreator);
×
966
        }
×
967
        for (IRI introducedIri : assertionContext.getIntroducedIris()) {
×
968
            npCreator.addPubinfoStatement(NPX.INTRODUCES, introducedIri);
×
969
        }
×
970
        for (IRI embeddedIri : assertionContext.getEmbeddedIris()) {
×
971
            npCreator.addPubinfoStatement(NPX.EMBEDS, embeddedIri);
×
972
        }
×
973
        Map<IRI, IRI> rolePropertyPins = assertionContext.getRolePropertyPins();
×
974
        if (!rolePropertyPins.isEmpty()) {
×
975
            // A role predicate carrying a direction pin makes this a role-instantiation
976
            // nanopub; nanopub-query's pin-resolution branch is gated on this type (#525).
977
            npCreator.addPubinfoStatement(NPX.HAS_NANOPUB_TYPE, KPXL_TERMS.ROLE_INSTANTIATION);
×
978
            for (Map.Entry<IRI, IRI> pin : rolePropertyPins.entrySet()) {
×
979
                npCreator.addPubinfoStatement(pin.getKey(), RDF.TYPE, pin.getValue());
×
980
            }
×
981
        }
982
        npCreator.addNamespace("this", targetNamespace);
×
983
        npCreator.addNamespace("sub", targetNamespace + "/");
×
984
        npCreator.addTimestampNow();
×
985
        // The canonical template ID: the embedded template-node IRI for templates
986
        // with embedded identity, the nanopub URI for legacy ones.
987
        IRI templateUri = vf.createIRI(assertionContext.getTemplate().getId());
×
988
        npCreator.addPubinfoStatement(NTEMPLATE.WAS_CREATED_FROM_TEMPLATE, templateUri);
×
989
        IRI prTemplateUri = vf.createIRI(provenanceContext.getTemplate().getId());
×
990
        npCreator.addPubinfoStatement(NTEMPLATE.WAS_CREATED_FROM_PROVENANCE_TEMPLATE, prTemplateUri);
×
991
        for (TemplateContext c : pubInfoContexts) {
×
992
            IRI piTemplateUri = vf.createIRI(c.getTemplate().getId());
×
993
            npCreator.addPubinfoStatement(NTEMPLATE.WAS_CREATED_FROM_PUBINFO_TEMPLATE, piTemplateUri);
×
994
        }
×
995
        String nanopubLabel = getNanopubLabel(npCreator);
×
996
        if (nanopubLabel != null) {
×
997
            npCreator.addPubinfoStatement(RDFS.LABEL, vf.createLiteral(nanopubLabel));
×
998
        }
999
        for (IRI type : assertionContext.getTemplate().getTargetNanopubTypes()) {
×
1000
            npCreator.addPubinfoStatement(NPX.HAS_NANOPUB_TYPE, type);
×
1001
        }
×
1002
        IRI userIri = NanodashSession.get().getUserIri();
×
1003
        if (User.getName(userIri) != null) {
×
1004
            npCreator.addPubinfoStatement(userIri, FOAF.NAME, vf.createLiteral(User.getName(userIri)));
×
1005
            npCreator.addNamespace("foaf", FOAF.NAMESPACE);
×
1006
        }
1007
        String websiteUrl = NanodashPreferences.get().getWebsiteUrl();
×
1008
        if (websiteUrl != null) {
×
1009
            npCreator.addPubinfoStatement(NPX.WAS_CREATED_AT, vf.createIRI(websiteUrl));
×
1010
        }
1011
        return npCreator.finalizeNanopub();
×
1012
    }
1013

1014
    private String getNanopubLabel(NanopubCreator npCreator) {
1015
        if (assertionContext.getTemplate().getNanopubLabelPattern() == null) {
×
1016
            return null;
×
1017
        }
1018

1019
        Map<IRI, String> labelMap = new HashMap<>();
×
1020
        for (Statement st : npCreator.getCurrentPubinfoStatements()) {
×
1021
            if (st.getPredicate().equals(RDFS.LABEL) && st.getObject() instanceof Literal objL) {
×
1022
                labelMap.put((IRI) st.getSubject(), objL.stringValue());
×
1023
            }
1024
        }
×
1025

1026
        String nanopubLabel = assertionContext.getTemplate().getNanopubLabelPattern();
×
1027
        while (nanopubLabel.matches(".*\\$\\{[_a-zA-Z0-9-]+\\}.*")) {
×
1028
            String placeholderPostfix = nanopubLabel.replaceFirst("^.*\\$\\{([_a-zA-Z0-9-]+)\\}.*$", "$1");
×
1029
            IRI placeholderIriHash = vf.createIRI(assertionContext.getTemplateNanopubUri() + "#" + placeholderPostfix);
×
1030
            IRI placeholderIriSlash = vf.createIRI(assertionContext.getTemplateNanopubUri() + "/" + placeholderPostfix);
×
1031
            IRI placeholderIri;
1032
            String placeholderValue = "";
×
1033
            if (assertionContext.getComponentModels().get(placeholderIriSlash) != null) {
×
1034
                placeholderIri = placeholderIriSlash;
×
1035
            } else {
1036
                placeholderIri = placeholderIriHash;
×
1037
            }
1038
            IModel<String> m = (IModel<String>) assertionContext.getComponentModels().get(placeholderIri);
×
1039
            if (m != null) {
×
1040
                placeholderValue = m.orElse("").getObject();
×
1041
            }
1042
            if (placeholderValue == null) {
×
1043
                placeholderValue = "";
×
1044
            }
1045
            String placeholderLabel = placeholderValue;
×
1046
            if (assertionContext.getTemplate().isUriPlaceholder(placeholderIri)) {
×
1047
                try {
1048
                    // TODO Fix this. It doesn't work for placeholders with auto-encode placeholders, etc.
1049
                    //      Not sure we need labels for these, but this code should be improved anyway.
1050
                    String prefix = assertionContext.getTemplate().getPrefix(placeholderIri);
×
1051
                    if (prefix != null) {
×
1052
                        placeholderValue = prefix + placeholderValue;
×
1053
                    }
1054
                    IRI placeholderValueIri = vf.createIRI(placeholderValue);
×
1055
                    String l = assertionContext.getTemplate().getLabel(placeholderValueIri);
×
1056
                    if (labelMap.containsKey(placeholderValueIri)) {
×
1057
                        l = labelMap.get(placeholderValueIri);
×
1058
                    }
1059
                    if (l == null) {
×
1060
                        l = GuidedChoiceItem.getLabel(placeholderValue);
×
1061
                    }
1062
                    if (assertionContext.getTemplate().isAgentPlaceholder(placeholderIri) && !placeholderValue.isEmpty()) {
×
1063
                        l = User.getName(vf.createIRI(placeholderValue));
×
1064
                    }
1065
                    if (l != null && !l.isEmpty()) {
×
1066
                        placeholderLabel = l.replaceFirst(" - .*$", "");
×
1067
                    } else {
1068
                        placeholderLabel = Utils.getShortNameFromURI(placeholderValueIri);
×
1069
                    }
1070
                } catch (Exception ex) {
×
1071
                    logger.error("Nanopub label placeholder IRI error: {}", ex.getMessage());
×
1072
                }
×
1073
            }
1074
            placeholderLabel = placeholderLabel.replaceAll("\\s+", " ");
×
1075
            if (placeholderLabel.length() > 100) {
×
1076
                placeholderLabel = placeholderLabel.substring(0, 97) + "...";
×
1077
            }
1078
            nanopubLabel = Strings.CS.replace(nanopubLabel, "${" + placeholderPostfix + "}", placeholderLabel);
×
1079
        }
×
1080
        return nanopubLabel;
×
1081
    }
1082

1083
    private NanodashPage getConfirmPage(Nanopub signedNp, PageParameters pageParams) {
1084
        if (confirmPageClass == null) {
×
1085
            return null;
×
1086
        }
1087
        try {
1088
            return (NanodashPage) confirmPageClass.getConstructor(Nanopub.class, PageParameters.class).newInstance(signedNp, pageParams);
×
1089
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
×
1090
                | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
1091
            logger.error("Could not create instance of confirmation page: {}", ex.getMessage());
×
1092
        }
1093
        return null;
×
1094
    }
1095

1096
    /**
1097
     * Returns a hint whether the form is stateless or not.
1098
     *
1099
     * @return false if the form is stateful, true if it is stateless.
1100
     */
1101
    // This is supposed to solve the problem that sometimes (but only sometimes) form content is reset
1102
    // if the user browses other pages in parallel:
1103
    protected boolean getStatelessHint() {
1104
        return false;
×
1105
    }
1106

1107
    private boolean canPublishFromSource(PageParameters pageParams) {
1108
        if (!isSourceOutdated(pageParams)) {
×
1109
            return true;
×
1110
        }
1111
        feedbackPanel.error("The nanopublication you are trying to supersede or override is not the latest version.");
×
1112
        return false;
×
1113
    }
1114

1115
    /**
1116
     * Returns the ID of the nanopublication that the given page parameters ask
1117
     * to supersede or override, or null if they ask for neither.
1118
     *
1119
     * @param pageParams the page parameters of the publish page
1120
     * @return the ID of the nanopublication to be superseded or overridden, or
1121
     * null
1122
     */
1123
    public static String getSupersededOrOverriddenNanopubId(PageParameters pageParams) {
1124
        for (String key : sourceParamKeys) {
48✔
1125
            if (!pageParams.get(key).isEmpty()) {
15✔
1126
                return pageParams.get(key).toString();
15✔
1127
            }
1128
        }
1129
        return null;
6✔
1130
    }
1131

1132
    /**
1133
     * Checks whether the given page parameters ask to supersede or override a
1134
     * nanopublication that is not the latest version anymore. Only the latest
1135
     * version can be superseded or overridden.
1136
     *
1137
     * @param pageParams the page parameters of the publish page
1138
     * @return true if a newer version of the nanopublication to be superseded
1139
     * or overridden exists
1140
     */
1141
    public static boolean isSourceOutdated(PageParameters pageParams) {
1142
        String sourceNpId = getSupersededOrOverriddenNanopubId(pageParams);
9✔
1143
        return sourceNpId != null && !sourceNpId.equals(QueryApiAccess.getLatestVersionId(sourceNpId));
33✔
1144
    }
1145

1146
    /**
1147
     * Returns a copy of the given page parameters where the nanopublication to
1148
     * be superseded or overridden is replaced by the given one.
1149
     *
1150
     * @param pageParams the page parameters of the publish page
1151
     * @param npId the ID of the nanopublication to supersede or override
1152
     * instead
1153
     * @return the adjusted copy of the page parameters
1154
     */
1155
    public static PageParameters withSourceNanopub(PageParameters pageParams, String npId) {
1156
        PageParameters newParams = new PageParameters(pageParams);
15✔
1157
        for (String key : sourceParamKeys) {
48✔
1158
            if (!newParams.get(key).isEmpty()) {
15✔
1159
                newParams.set(key, npId);
15✔
1160
            }
1161
        }
1162
        return newParams;
6✔
1163
    }
1164

1165
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc