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

knowledgepixels / nanodash / 30350501786

28 Jul 2026 10:22AM UTC coverage: 33.452% (+3.2%) from 30.298%
30350501786

Pull #567

github

web-flow
Merge 7dc757397 into 7086521ef
Pull Request #567: Report and block templates with literals in subject/predicate position

2337 of 7695 branches covered (30.37%)

Branch coverage included in aggregate %.

4534 of 12845 relevant lines covered (35.3%)

5.45 hits per line

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

33.65
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.io.Serializable;
65
import java.lang.reflect.InvocationTargetException;
66
import java.util.*;
67

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

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

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

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

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

85
    private static final String INVALID_TEMPLATE_MESSAGE = "This form is based on an invalid template and cannot be published.";
86
    // Page parameters that make the form supersede or override an existing nanopublication
87
    // ("fill" is the deprecated form of "supersede"):
88
    private static final String[] sourceParamKeys = new String[]{"supersede", "supersede-a", "override", "override-a", "fill"};
72✔
89

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

115
    /**
116
     * A problem reported by {@link Template#getStatementErrors()}, together with the part of the
117
     * nanopublication ("Assertion", "Provenance", "Publication info") whose template has it.
118
     *
119
     * @param part    the nanopublication part the offending template defines
120
     * @param message the description of the problem
121
     */
122
    public record TemplateError(String part, String message) implements Serializable {
27✔
123
    }
124

125
    protected Form<?> form;
126
    protected FeedbackPanel feedbackPanel;
127
    private final TemplateContext assertionContext;
128
    private TemplateContext provenanceContext;
129
    private final List<TemplateContext> pubInfoContexts = new ArrayList<>();
15✔
130
    private final Map<String, TemplateContext> pubInfoContextMap = new HashMap<>();
15✔
131
    private final List<TemplateContext> requiredPubInfoContexts = new ArrayList<>();
15✔
132
    private String targetNamespace;
133
    private final Class<? extends WebPage> confirmPageClass;
134

135
    /**
136
     * Constructor for the PublishForm.
137
     *
138
     * @param id the Wicket component ID
139
     * @param pageParams the parameters for the page, which may include
140
     * information on how to fill the form
141
     * @param publishPageClass the class of the page to redirect to after
142
     * successful publication
143
     * @param confirmPageClass the class of the confirmation page to show after
144
     * publication
145
     */
146
    public PublishForm(String id, final PageParameters pageParams, Class<? extends WebPage> publishPageClass, Class<? extends WebPage> confirmPageClass) {
147
        super(id);
9✔
148
        setOutputMarkupId(true);
12✔
149
        this.confirmPageClass = confirmPageClass;
9✔
150

151
        WebMarkupContainer linkMessageItem = new WebMarkupContainer("link-message-item");
15✔
152
        if (pageParams.get("link-message").isNull()) {
15!
153
            linkMessageItem.add(new Label("link-message", ""));
39✔
154
            linkMessageItem.setVisible(false);
15✔
155
        } else {
156
            linkMessageItem.add(new Label("link-message", Utils.sanitizeHtml(pageParams.get("link-message").toString())).setEscapeModelStrings(false));
×
157
        }
158
        add(linkMessageItem);
27✔
159

160
        final Nanopub fillNp;
161
        final FillMode fillMode;
162
        boolean fillOnlyAssertion = false;
6✔
163
        if (!pageParams.get("use").isNull()) {
15!
164
            fillNp = Utils.getNanopub(pageParams.get("use").toString());
×
165
            fillMode = FillMode.USE;
×
166
        } else if (!pageParams.get("use-a").isNull()) {
15!
167
            fillNp = Utils.getNanopub(pageParams.get("use-a").toString());
×
168
            fillMode = FillMode.USE;
×
169
            fillOnlyAssertion = true;
×
170
        } else if (!pageParams.get("supersede").isNull()) {
15!
171
            fillNp = Utils.getNanopub(pageParams.get("supersede").toString());
×
172
            fillMode = FillMode.SUPERSEDE;
×
173
            targetNamespace = pageParams.get("supersede").toString().replaceFirst("RA[A-Za-z0-9-_]{43}$", "");
×
174
        } else if (!pageParams.get("supersede-a").isNull()) {
15!
175
            fillNp = Utils.getNanopub(pageParams.get("supersede-a").toString());
×
176
            fillMode = FillMode.SUPERSEDE;
×
177
            fillOnlyAssertion = true;
×
178
        } else if (!pageParams.get("derive").isNull()) {
15!
179
            fillNp = Utils.getNanopub(pageParams.get("derive").toString());
×
180
            fillMode = FillMode.DERIVE;
×
181
        } else if (!pageParams.get("derive-a").isNull()) {
15!
182
            fillNp = Utils.getNanopub(pageParams.get("derive-a").toString());
×
183
            fillMode = FillMode.DERIVE;
×
184
            fillOnlyAssertion = true;
×
185
        } else if (!pageParams.get("override").isNull()) {
15!
186
            fillNp = Utils.getNanopub(pageParams.get("override").toString());
×
187
            fillMode = FillMode.OVERRIDE;
×
188
        } else if (!pageParams.get("override-a").isNull()) {
15!
189
            fillNp = Utils.getNanopub(pageParams.get("override-a").toString());
×
190
            fillMode = FillMode.OVERRIDE;
×
191
            fillOnlyAssertion = true;
×
192
        } else if (!pageParams.get("fill-all").isNull()) {
15!
193
            // TODO: This is deprecated and should be removed at some point
194
            fillNp = Utils.getNanopub(pageParams.get("fill-all").toString());
×
195
            fillMode = FillMode.USE;
×
196
        } else if (!pageParams.get("fill").isNull()) {
15!
197
            // TODO: This is deprecated and should be removed at some point
198
            fillNp = Utils.getNanopub(pageParams.get("fill").toString());
×
199
            fillMode = FillMode.SUPERSEDE;
×
200
        } else {
201
            fillNp = null;
6✔
202
            fillMode = null;
6✔
203
        }
204

205
        final TemplateData td = TemplateData.get();
6✔
206

207
        // TODO Properly integrate this namespace feature:
208
        String templateId = pageParams.get("template").toString();
15✔
209
        Template template = td.getTemplate(templateId);
12✔
210
        if (template != null && template.getTargetNamespace() != null) {
15!
211
            targetNamespace = template.getTargetNamespace();
×
212
        }
213
        if (!pageParams.get("target-namespace").isNull()) {
15!
214
            targetNamespace = pageParams.get("target-namespace").toString();
×
215
        }
216
        if (targetNamespace == null) {
9!
217
            targetNamespace = Template.DEFAULT_TARGET_NAMESPACE;
9✔
218
        }
219
        String targetNamespaceLabel = targetNamespace + "...";
12✔
220
        targetNamespace = targetNamespace + "~~~ARTIFACTCODE~~~/";
15✔
221

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

307
            String mapping = pageParams.get("values-from-query-mapping").toString();
×
308
            String mapsFrom, mapsTo;
309
            if (mapping.contains(":")) {
×
310
                mapsFrom = mapping.split(":")[0];
×
311
                mapsTo = mapping.split(":")[1];
×
312
            } else {
313
                mapsFrom = mapping;
×
314
                mapsTo = mapping;
×
315
            }
316
            ApiResponse resp = ApiCache.retrieveResponseSync(QueryRef.parseString(querySpec), false);
×
317
            int i = 0;
×
318
            for (ApiResponseEntry e : resp.getData()) {
×
319
                String mapsToSuffix = "";
×
320
                if (i > 0) {
×
321
                    mapsToSuffix = "__" + i;
×
322
                }
323
                assertionContext.setParam(mapsTo + mapsToSuffix, e.get(mapsFrom));
×
324
                i++;
×
325
            }
×
326
        }
327
        for (String k : pageParams.getNamedKeys()) {
33✔
328
            if (k.startsWith("param_")) {
12!
329
                assertionContext.setParam(k.substring(6), pageParams.get(k).toString());
×
330
            }
331
            if (k.startsWith("prparam_")) {
12!
332
                provenanceContext.setParam(k.substring(8), pageParams.get(k).toString());
×
333
            }
334
            if (k.matches("piparam[1-9][0-9]*_.*")) {
12!
335
                Integer i = Integer.parseInt(k.replaceFirst("^piparam([1-9][0-9]*)_.*$", "$1"));
×
336
                if (!piParamIdMap.containsKey(i)) {
×
337
                    logger.error("Parameter {} of the publication info template not found", i);
×
338
                    continue;
×
339
                }
340
                String n = k.replaceFirst("^piparam[1-9][0-9]*_(.*)$", "$1");
×
341
                logger.info(n);
×
342
                piParamIdMap.get(i).setParam(n, pageParams.get(k).toString());
×
343
            }
344
        }
3✔
345

346
        final Nanopub improveNp;
347
        if (!pageParams.get("improve").isNull()) {
15!
348
            improveNp = Utils.getNanopub(pageParams.get("improve").toString());
×
349
        } else {
350
            improveNp = null;
6✔
351
        }
352

353
        // Propagate fill source (supersede/derive/improve) so contexts can resolve
354
        // the `local:nanopub`/`local:assertion` sentinels to the fill nanopub's URIs.
355
        Nanopub fillSource = fillNp != null ? fillNp : improveNp;
12!
356
        if (fillSource != null) {
6!
357
            assertionContext.setFillSource(fillSource);
×
358
            provenanceContext.setFillSource(fillSource);
×
359
            for (TemplateContext c : pubInfoContexts) {
×
360
                c.setFillSource(fillSource);
×
361
            }
×
362
        }
363

364
        // Init statements only now, in order to pick up parameter values:
365
        assertionContext.initStatements();
9✔
366
        provenanceContext.initStatements();
9✔
367
        for (TemplateContext c : pubInfoContexts) {
33✔
368
            c.initStatements();
6✔
369
        }
3✔
370

371
        String latestAssertionId = td.getLatestTemplateId(assertionContext.getTemplateId());
18✔
372
        if (!assertionContext.getTemplateId().equals(latestAssertionId)) {
18!
373
            add(new Label("newversion", "There is a new version of this assertion template:"));
×
374
            PageParameters params = new PageParameters(pageParams);
×
375
            params.set("template", latestAssertionId);
×
376
            add(new BookmarkablePageLink<Void>("newversionlink", publishPageClass, params));
×
377
            if ("latest".equals(pageParams.get("template-version").toString())) {
×
378
                throw new RestartResponseException(publishPageClass, params);
×
379
            }
380
        } else {
×
381
            add(new Label("newversion", "").setVisible(false));
45✔
382
            add(new Label("newversionlink", "").setVisible(false));
45✔
383
        }
384

385
        final List<Statement> unusedStatementList = new ArrayList<>();
12✔
386
        final List<Statement> unusedPrStatementList = new ArrayList<>();
12✔
387
        final List<Statement> unusedPiStatementList = new ArrayList<>();
12✔
388
        if (fillNp != null) {
6!
389
            ValueFiller filler = new ValueFiller(fillNp, ContextType.ASSERTION, true, fillMode);
×
390
            filler.fill(assertionContext);
×
391
            unusedStatementList.addAll(filler.getUnusedStatements());
×
392

393
            if (!fillOnlyAssertion) {
×
394
                ValueFiller prFiller = new ValueFiller(fillNp, ContextType.PROVENANCE, true);
×
395
                prFiller.fill(provenanceContext);
×
396
                unusedPrStatementList.addAll(prFiller.getUnusedStatements());
×
397

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

445
            @Override
446
            protected void populateItem(Item<Statement> item) {
447
                item.add(new TripleItem("unused-statement", item.getModelObject(), (fillNp != null ? fillNp : improveNp), null));
×
448
            }
×
449

450
        });
451
        add(new DataView<Statement>("unused-prstatements", new ListDataProvider<Statement>(unusedPrStatementList)) {
99✔
452

453
            @Override
454
            protected void populateItem(Item<Statement> item) {
455
                item.add(new TripleItem("unused-prstatement", item.getModelObject(), (fillNp != null ? fillNp : improveNp), null));
×
456
            }
×
457

458
        });
459
        add(new DataView<Statement>("unused-pistatements", new ListDataProvider<Statement>(unusedPiStatementList)) {
99✔
460

461
            @Override
462
            protected void populateItem(Item<Statement> item) {
463
                item.add(new TripleItem("unused-pistatement", item.getModelObject(), (fillNp != null ? fillNp : improveNp), null));
×
464
            }
×
465

466
        });
467

468
        // Statements of the involved templates that cannot produce valid RDF, e.g. a literal
469
        // placeholder in subject position. Such a template silently publishes something other
470
        // than what it describes, so the problem is spelled out above the form:
471
        List<TemplateError> templateErrors = new ArrayList<>();
12✔
472
        collectTemplateErrors("Assertion", assertionContext, templateErrors);
15✔
473
        collectTemplateErrors("Provenance", provenanceContext, templateErrors);
15✔
474
        for (TemplateContext c : pubInfoContexts) {
33✔
475
            collectTemplateErrors("Publication info", c, templateErrors);
12✔
476
        }
3✔
477
        if (!templateErrors.isEmpty()) {
9✔
478
            add(new Label("template-error-intro", "This form is based on an invalid template and will not produce the nanopublication it describes:"));
42✔
479
        } else {
480
            add(new Label("template-error-intro", "").setVisible(false));
45✔
481
        }
482
        add(new ListView<TemplateError>("template-errors", templateErrors) {
66✔
483

484
            @Override
485
            protected void populateItem(ListItem<TemplateError> item) {
486
                item.add(new Label("template-error-part", item.getModelObject().part() + ": "));
51✔
487
                item.add(new Label("template-error", item.getModelObject().message()));
48✔
488
            }
3✔
489

490
        });
491

492
        // Finalize statements, which picks up parameter values in repetitions:
493
        assertionContext.finalizeStatements();
9✔
494
        provenanceContext.finalizeStatements();
9✔
495
        for (TemplateContext c : pubInfoContexts) {
33✔
496
            c.finalizeStatements();
6✔
497
        }
3✔
498

499
        final CheckBox consentCheck = new CheckBox("consentcheck", new Model<>(false));
30✔
500
        consentCheck.add(new InvalidityHighlighting());
33✔
501

502
        form = new Form<Void>("form") {
90✔
503

504
            @Override
505
            protected void onConfigure() {
506
                super.onConfigure();
6✔
507
                form.getFeedbackMessages().clear();
18✔
508
//                                formComponents.clear();
509
            }
3✔
510

511
            protected void onSubmit() {
512
                // The publish button is hidden in this case, but the form can still be
513
                // submitted by pressing Enter in one of its fields:
514
                if (!templateErrors.isEmpty()) {
×
515
                    feedbackPanel.error(INVALID_TEMPLATE_MESSAGE);
×
516
                    return;
×
517
                }
518
                // Re-check here too: a newer version might have appeared while the form was open.
519
                if (!canPublishFromSource(pageParams)) {
×
520
                    return;
×
521
                }
522

523
                if (!Boolean.TRUE.equals(consentCheck.getModelObject())) {
×
524
                    feedbackPanel.error("You need to check the checkbox that you understand the consequences.");
×
525
                    return;
×
526
                }
527
                logger.info("Publish form submitted");
×
528
                Nanopub signedNp;
529
                try {
530
                    Nanopub np = createNanopub();
×
531
                    logger.info("Nanopublication created: {}", np.getUri());
×
532
                    TransformContext tc = new TransformContext(SignatureAlgorithm.RSA, NanodashSession.get().getKeyPair(), NanodashSession.get().getUserIri(), false, false, false);
×
533
                    signedNp = SignNanopub.signAndTransform(np, tc);
×
534
                    logger.info("Nanopublication signed: {}", signedNp.getUri());
×
535
                    String npUrl = PublishNanopub.publish(signedNp);
×
536
                    logger.info("Nanopublication published: {}", npUrl);
×
537
                    Utils.cacheNanopub(signedNp);
×
538
                } catch (Exception ex) {
×
539
                    signedNp = null;
×
540
                    logger.error("Nanopublication publishing failed: {}", ex.getMessage());
×
541
                    String message = ex.getClass().getName();
×
542
                    if (ex.getMessage() != null) {
×
543
                        message = ex.getMessage();
×
544
                    }
545
                    feedbackPanel.error(message);
×
546
                }
×
547
                if (signedNp != null) {
×
548
                    String toRefresh = pageParams.get("refresh-upon-publish").toString("");
×
549
                    if (!toRefresh.isEmpty()) {
×
550
                        WicketApplication.get().notifyNanopubPublished(signedNp, toRefresh, 5 * 1000);
×
551
                    }
552
                    String contextId = pageParams.get("context").toString("");
×
553
                    // Broaden the refresh: also force-refresh the context resource's own
554
                    // data (e.g. a space's roles/members) so the page we redirect to —
555
                    // typically its Content tab — reflects the just-published change, not
556
                    // only the specific view query that was acted on.
557
                    if (!contextId.isEmpty() && !contextId.equals(toRefresh)
×
558
                            && AbstractResourceWithProfile.isResourceWithProfile(contextId)) {
×
559
                        WicketApplication.get().notifyNanopubPublished(signedNp, contextId, 5 * 1000);
×
560
                    }
561
                    if (pageParams.get("postpub-redirect-url").isEmpty() && confirmPageClass == null) {
×
562
                        // Forward to the context resource's page, or home if no context; always throws.
563
                        NavigationContext.redirectAfterPublish(signedNp, pageParams);
×
564
                    }
565
                    // Connector flow: show the connector's own confirmation page.
566
                    NanodashPage confirmPage = getConfirmPage(signedNp, pageParams);
×
567
                    if (confirmPage != null) {
×
568
                        throw new RestartResponseException(confirmPage);
×
569
                    }
570
                    NavigationContext.redirectAfterPublish(signedNp, pageParams);
×
571
                }
572
            }
×
573

574
            @Override
575
            protected void onValidate() {
576
                super.onValidate();
×
577
                for (Component fc : assertionContext.getComponents()) {
×
578
                    processFeedback(fc);
×
579
                }
×
580
                for (Component fc : provenanceContext.getComponents()) {
×
581
                    processFeedback(fc);
×
582
                }
×
583
                for (TemplateContext c : pubInfoContexts) {
×
584
                    for (Component fc : c.getComponents()) {
×
585
                        processFeedback(fc);
×
586
                    }
×
587
                }
×
588
            }
×
589

590
            private void processFeedback(Component c) {
591
                if (c instanceof FormComponent) {
×
592
                    ((FormComponent<?>) c).processInput();
×
593
                }
594
                for (FeedbackMessage fm : c.getFeedbackMessages()) {
×
595
                    form.getFeedbackMessages().add(fm);
×
596
                }
×
597
            }
×
598

599
        };
600
        form.setOutputMarkupId(true);
15✔
601

602
        //form.add(new Label("nanopub-namespace", targetNamespaceLabel));
603
        form.add(newSourceMenu("templatelink", assertionContext.getTemplate().getId()));
45✔
604
        form.add(new Label("templatename", assertionContext.getTemplate().getLabel()));
51✔
605
        String description = assertionContext.getTemplate().getLabel();
15✔
606
        form.add(new Label("templatedesc", assertionContext.getTemplate().getDescription()).setEscapeModelStrings(false));
57✔
607

608
        form.add(new ListView<StatementItem>("statements", assertionContext.getStatementItems()) {
75✔
609

610
            protected void populateItem(ListItem<StatementItem> item) {
611
                item.add(item.getModelObject());
33✔
612
            }
3✔
613

614
        });
615

616
        final Map<String, Boolean> handledProvTemplates = new HashMap<>();
12✔
617
        final String defaultProvTemplateId;
618
        if (assertionContext.getTemplate().getDefaultProvenance() != null) {
15!
619
            defaultProvTemplateId = assertionContext.getTemplate().getDefaultProvenance().stringValue();
×
620
            handledProvTemplates.put(defaultProvTemplateId, true);
×
621
        } else {
622
            defaultProvTemplateId = null;
6✔
623
        }
624
        final List<String> recommendedProvTemplateOptionIds = new ArrayList<>();
12✔
625
        final List<String> provTemplateOptionIds = new ArrayList<>();
12✔
626
        if (pageParams.get("prtemplate-options").isNull()) {
15!
627
            // TODO Make this dynamic and consider updated templates:
628
            recommendedProvTemplateOptionIds.add(DEFAULT_PROV_TEMPLATE);
12✔
629
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RAcTpoh5Ra0ssqmcpOgWdaZ_YiPE6demO6cpw-2RvSNs8");
12✔
630
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RA4LGtuOqTIMqVAkjnfBXk1YDcAPNadP5CGiaJiBkdHCQ");
12✔
631
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RAl_-VTw9Re_uRF8r8y0rjlfnu7FlhTa8xg_8xkcweqiE");
12✔
632
            recommendedProvTemplateOptionIds.add("https://w3id.org/np/RASORV2mMEVpS4lWh2bwUTEcV-RWjbD9RPbN7J0PIeYAU");
12✔
633
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RAjkBbM5yQm7hKH1l_Jk3HAUqWi3Bd57TPmAOZCsZmi_M");
12✔
634
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RAGXx_k9eQMnXaCbsXMsJbGClwZtQEGNg0GVJu6amdAVw");
12✔
635
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RA1fnITI3Pu1UQ0CHghNpys3JwQrM32LBnjmDLoayp9-4");
12✔
636
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RAJgbsGeGdTG-zq_gU0TLw4s3raMgoRk-mPlc2DSLXvE0");
12✔
637
            recommendedProvTemplateOptionIds.add("http://purl.org/np/RA6SXfhUY-xeblZU8HhPddw6tsu-C5NXevG6C_zv4bMxU");
12✔
638
            for (String s : recommendedProvTemplateOptionIds) {
30✔
639
                handledProvTemplates.put(s, true);
18✔
640
            }
3✔
641

642
            for (ApiResponseEntry t : td.getProvenanceTemplates()) {
33✔
643
                String tid = t.get("np");
12✔
644
                if (handledProvTemplates.containsKey(tid)) {
12✔
645
                    continue;
3✔
646
                }
647
                provTemplateOptionIds.add(tid);
12✔
648
                handledProvTemplates.put(tid, true);
18✔
649
            }
6✔
650
        } else {
651
            for (String s : pageParams.get("prtemplate-options").toString().split(" ")) {
×
652
                if (handledProvTemplates.containsKey(s)) {
×
653
                    continue;
×
654
                }
655
                recommendedProvTemplateOptionIds.add(s);
×
656
                handledProvTemplates.put(s, true);
×
657
            }
658
        }
659

660
        ChoiceProvider<String> prTemplateChoiceProvider = new ChoiceProvider<String>() {
105✔
661

662
            @Override
663
            public String getDisplayValue(String object) {
664
                if (object == null || object.isEmpty()) {
15!
665
                    return "";
×
666
                }
667
                Template t = td.getTemplate(object);
15✔
668
                if (t != null) {
6!
669
                    return t.getLabel();
9✔
670
                }
671
                return object;
×
672
            }
673

674
            @Override
675
            public String getIdValue(String object) {
676
                return object;
6✔
677
            }
678

679
            // Getting strange errors with Tomcat if this method is not overridden:
680
            @Override
681
            public void detach() {
682
            }
3✔
683

684
            @Override
685
            public void query(String term, int page, Response<String> response) {
686
                if (term == null) {
×
687
                    term = "";
×
688
                }
689
                term = term.toLowerCase();
×
690
                if (pageParams.get("prtemplate").toString() != null) {
×
691
                    // Using this work-around with "——" because 'optgroup' is not available through Wicket's Select2 classes
692
                    response.add("—— default for this link ——");
×
693
                    response.add(prTemplateId);
×
694
                }
695
                if (defaultProvTemplateId != null) {
×
696
                    response.add("—— default for this template ——");
×
697
                    response.add(defaultProvTemplateId);
×
698
                }
699
                if (!recommendedProvTemplateOptionIds.isEmpty()) {
×
700
                    if (pageParams.get("prtemplate-options").isNull()) {
×
701
                        response.add("—— recommended ——");
×
702
                    }
703
                    for (String s : recommendedProvTemplateOptionIds) {
×
704
                        if (s.toLowerCase().contains(term) || getDisplayValue(s).toLowerCase().contains(term)) {
×
705
                            response.add(s);
×
706
                        }
707
                    }
×
708
                }
709
                if (!provTemplateOptionIds.isEmpty()) {
×
710
                    response.add("—— others ——");
×
711
                    for (String s : provTemplateOptionIds) {
×
712
                        if (s.toLowerCase().contains(term) || getDisplayValue(s).toLowerCase().contains(term)) {
×
713
                            response.add(s);
×
714
                        }
715
                    }
×
716
                }
717
            }
×
718

719
            @Override
720
            public Collection<String> toChoices(Collection<String> ids) {
721
                return ids;
×
722
            }
723

724
        };
725
        final IModel<String> prTemplateModel = Model.of(provenanceContext.getTemplate().getId());
18✔
726
        Select2Choice<String> prTemplateChoice = new Select2Choice<String>("prtemplate", prTemplateModel, prTemplateChoiceProvider);
21✔
727
        prTemplateChoice.setRequired(true);
12✔
728
        prTemplateChoice.getSettings().setCloseOnSelect(true);
15✔
729
        prTemplateChoice.add(new AjaxFormComponentUpdatingBehavior("change") {
72✔
730

731
            @Override
732
            protected void onUpdate(AjaxRequestTarget target) {
733
                String o = prTemplateModel.getObject();
×
734
                if (o.startsWith("——")) {
×
735
                    o = provenanceContext.getTemplate().getId();
×
736
                    prTemplateModel.setObject(o);
×
737
                }
738
                provenanceContext = new TemplateContext(ContextType.PROVENANCE, prTemplateModel.getObject(), "pr-statement", targetNamespace);
×
739
                provenanceContext.initStatements();
×
740
                refreshProvenance(target);
×
741
                provenanceContext.finalizeStatements();
×
742
            }
×
743

744
        });
745
        form.add(prTemplateChoice);
30✔
746
        refreshProvenance(null);
9✔
747

748
        final Map<String, Boolean> handledPiTemplates = new HashMap<>();
12✔
749
        final List<String> recommendedPiTemplateOptionIds = new ArrayList<>();
12✔
750
        final List<String> piTemplateOptionIds = new ArrayList<>();
12✔
751
        // TODO Make this dynamic and consider updated templates:
752
        recommendedPiTemplateOptionIds.add("http://purl.org/np/RAXflINqt3smqxV5Aq7E9lzje4uLdkKIOefa6Bp8oJ8CY");
12✔
753
        recommendedPiTemplateOptionIds.add("https://w3id.org/np/RARW4MsFkHuwjycNElvEVtuMjpf4yWDL10-0C5l2MqqRQ");
12✔
754
        recommendedPiTemplateOptionIds.add("https://w3id.org/np/RA16U9Wo30ObhrK1NzH7EsmVRiRtvEuEA_Dfc-u8WkUCA");
12✔
755
        recommendedPiTemplateOptionIds.add("http://purl.org/np/RAdyqI6k07V5nAS82C6hvIDtNWk179EIV4DV-sLbOFKg4");
12✔
756
        recommendedPiTemplateOptionIds.add("https://w3id.org/np/RAjvEpLZUE7rMoa8q6mWSsN6utJDp-5FmgO47YGsbgw3w");
12✔
757
        recommendedPiTemplateOptionIds.add("http://purl.org/np/RAxuGRKID6yNg63V5Mf0ot2NjncOnodh-mkN3qT_1txGI");
12✔
758
        for (TemplateContext c : pubInfoContexts) {
33✔
759
            // Key both ID forms: the "others" dedup below compares against listing
760
            // entries keyed by nanopub URI, which for a template with embedded
761
            // identity differs from its canonical ID.
762
            handledPiTemplates.put(c.getTemplate().getId(), true);
24✔
763
            handledPiTemplates.put(c.getTemplate().getNanopub().getUri().stringValue(), true);
30✔
764
        }
3✔
765
        for (String s : recommendedPiTemplateOptionIds) {
30✔
766
            handledPiTemplates.put(s, true);
18✔
767
        }
3✔
768

769
        for (ApiResponseEntry entry : td.getPubInfoTemplates()) {
33✔
770
            String tid = entry.get("np");
12✔
771
            if (handledPiTemplates.containsKey(tid)) {
12✔
772
                continue;
3✔
773
            }
774
            piTemplateOptionIds.add(tid);
12✔
775
            handledPiTemplates.put(tid, true);
18✔
776
        }
3✔
777

778
        ChoiceProvider<String> piTemplateChoiceProvider = new ChoiceProvider<String>() {
69✔
779

780
            @Override
781
            public String getDisplayValue(String object) {
782
                if (object == null || object.isEmpty()) {
×
783
                    return "";
×
784
                }
785
                Template t = td.getTemplate(object);
×
786
                if (t != null) {
×
787
                    return t.getLabel();
×
788
                }
789
                return object;
×
790
            }
791

792
            @Override
793
            public String getIdValue(String object) {
794
                return object;
×
795
            }
796

797
            // Getting strange errors with Tomcat if this method is not overridden:
798
            @Override
799
            public void detach() {
800
            }
3✔
801

802
            @Override
803
            public void query(String term, int page, Response<String> response) {
804
                if (term == null) {
×
805
                    term = "";
×
806
                }
807
                term = term.toLowerCase();
×
808
                if (!recommendedPiTemplateOptionIds.isEmpty()) {
×
809
                    response.add("—— recommended ——");
×
810
                    for (String s : recommendedPiTemplateOptionIds) {
×
811
                        boolean isAlreadyUsed = false;
×
812
                        for (TemplateContext c : pubInfoContexts) {
×
813
                            // TODO: make this more efficient/nicer
814
                            if (c.getTemplate().hasId(s)) {
×
815
                                isAlreadyUsed = true;
×
816
                                break;
×
817
                            }
818
                        }
×
819
                        if (isAlreadyUsed) {
×
820
                            continue;
×
821
                        }
822
                        if (s.toLowerCase().contains(term) || getDisplayValue(s).toLowerCase().contains(term)) {
×
823
                            response.add(s);
×
824
                        }
825
                    }
×
826
                }
827
                if (!piTemplateOptionIds.isEmpty()) {
×
828
                    response.add("—— others ——");
×
829
                    for (String s : piTemplateOptionIds) {
×
830
                        boolean isAlreadyUsed = false;
×
831
                        for (TemplateContext c : pubInfoContexts) {
×
832
                            // TODO: make this more efficient/nicer
833
                            if (c.getTemplate().hasId(s)) {
×
834
                                isAlreadyUsed = true;
×
835
                                break;
×
836
                            }
837
                        }
×
838
                        if (isAlreadyUsed) {
×
839
                            continue;
×
840
                        }
841
                        if (s.toLowerCase().contains(term) || getDisplayValue(s).toLowerCase().contains(term)) {
×
842
                            response.add(s);
×
843
                        }
844
                    }
×
845
                }
846
            }
×
847

848
            @Override
849
            public Collection<String> toChoices(Collection<String> ids) {
850
                return ids;
×
851
            }
852

853
        };
854
        final IModel<String> newPiTemplateModel = Model.of();
6✔
855
        Select2Choice<String> piTemplateChoice = new Select2Choice<String>("pitemplate", newPiTemplateModel, piTemplateChoiceProvider);
21✔
856
        piTemplateChoice.getSettings().setCloseOnSelect(true);
15✔
857
        piTemplateChoice.getSettings().setPlaceholder("add element...");
15✔
858
        piTemplateChoice.add(new AjaxFormComponentUpdatingBehavior("change") {
72✔
859

860
            @Override
861
            protected void onUpdate(AjaxRequestTarget target) {
862
                if (newPiTemplateModel.getObject().startsWith("——")) {
×
863
                    newPiTemplateModel.setObject(null);
×
864
                    refreshPubInfo(target);
×
865
                    return;
×
866
                }
867
                String id = newPiTemplateModel.getObject();
×
868
                TemplateContext c = new TemplateContext(ContextType.PUBINFO, id, "pi-statement", targetNamespace);
×
869
                c.initStatements();
×
870
                pubInfoContexts.add(c);
×
871
                newPiTemplateModel.setObject(null);
×
872
                refreshPubInfo(target);
×
873
                c.finalizeStatements();
×
874
            }
×
875

876
        });
877
        form.add(piTemplateChoice);
30✔
878
        refreshPubInfo(null);
9✔
879

880
        // An invalid template cannot produce the nanopublication it describes, so there is
881
        // nothing to consent to, publish or preview; the reasons are listed above the form.
882
        boolean publishingDisabled = !templateErrors.isEmpty();
21✔
883

884
        WebMarkupContainer consentSection = new WebMarkupContainer("consent-section");
15✔
885
        consentSection.add(consentCheck);
27✔
886
        consentSection.setVisible(!publishingDisabled);
24✔
887
        form.add(consentSection);
30✔
888

889
        WebMarkupContainer buttonSection = new WebMarkupContainer("button-section");
15✔
890
        buttonSection.setVisible(!publishingDisabled);
24✔
891
        form.add(buttonSection);
30✔
892

893
        form.add(new Label("publishing-disabled", "Publishing is disabled because of the template errors listed above.")
42✔
894
                .setVisible(publishingDisabled));
6✔
895

896
        buttonSection.add(new Button("preview-button") {
108✔
897
            @Override
898
            public void onSubmit() {
899
                if (!templateErrors.isEmpty()) {
×
900
                    feedbackPanel.error(INVALID_TEMPLATE_MESSAGE);
×
901
                    return;
×
902
                }
903
                try {
904
                    // Re-check here too: a newer version might have appeared while the form was open.
905
                    if (!canPublishFromSource(pageParams)) {
×
906
                        return;
×
907
                    }
908

909
                    Nanopub np = createNanopub();
×
910
                    TransformContext tc = new TransformContext(SignatureAlgorithm.RSA, NanodashSession.get().getKeyPair(), NanodashSession.get().getUserIri(), false, false, false);
×
911
                    Nanopub signedNp = SignNanopub.signAndTransform(np, tc);
×
912
                    String previewId = signedNp.getUri().stringValue();
×
913
                    NanodashSession.get().setPreviewNanopub(previewId,
×
914
                            new NanodashSession.PreviewNanopub(signedNp, pageParams, confirmPageClass, Boolean.TRUE.equals(consentCheck.getModelObject()), getPage().getPageReference()));
×
915
                    throw new RestartResponseException(PreviewPage.class, new PageParameters().set("id", previewId));
×
916
                } catch (RestartResponseException ex) {
×
917
                    throw ex;
×
918
                } catch (Exception ex) {
×
919
                    logger.error("Preview failed: {}", ex.getMessage());
×
920
                    String message = ex.getClass().getName();
×
921
                    if (ex.getMessage() != null) {
×
922
                        message = ex.getMessage();
×
923
                    }
924
                    feedbackPanel.error(message);
×
925
                }
926
            }
×
927
        });
928

929
        add(form);
30✔
930

931
        feedbackPanel = new FeedbackPanel("feedback");
18✔
932
        feedbackPanel.setOutputMarkupId(true);
15✔
933
        add(feedbackPanel);
30✔
934
    }
3✔
935

936
    static EntryActionMenu newSourceMenu(String id, String templateId) {
937
        BookmarkablePageLink<Void> sourceLink = new BookmarkablePageLink<>("link", ExplorePage.class, new PageParameters().set("id", templateId));
36✔
938
        sourceLink.add(NavigationContext.pageContextFallback());
27✔
939
        sourceLink.setBody(Model.of("<span class=\"actionmenu-icon\">↗︎</span>source")).setEscapeModelStrings(false);
21✔
940
        return new EntryActionMenu(id, List.of(sourceLink));
21✔
941
    }
942

943
    private void refreshProvenance(AjaxRequestTarget target) {
944
        if (target != null) {
6!
945
            form.remove("prtemplatelink");
×
946
            form.remove("pr-statements");
×
947
            target.add(form);
×
948
            target.appendJavaScript("updateElements();");
×
949
        }
950
        form.add(newSourceMenu("prtemplatelink", provenanceContext.getTemplate().getId()));
45✔
951
        ListView<StatementItem> list = new ListView<StatementItem>("pr-statements", provenanceContext.getStatementItems()) {
51✔
952

953
            protected void populateItem(ListItem<StatementItem> item) {
954
                item.add(item.getModelObject());
33✔
955
            }
3✔
956

957
        };
958
        list.setOutputMarkupId(true);
12✔
959
        form.add(list);
30✔
960
    }
3✔
961

962
    private void refreshPubInfo(AjaxRequestTarget target) {
963
        ListView<TemplateContext> list = new ListView<TemplateContext>("pis", pubInfoContexts) {
48✔
964

965
            protected void populateItem(ListItem<TemplateContext> item) {
966
                final TemplateContext pic = item.getModelObject();
12✔
967
                item.add(new Label("pitemplatename", pic.getTemplate().getLabel()));
45✔
968
                item.add(newSourceMenu("pitemplatelink", pic.getTemplate().getId()));
39✔
969
                Label remove = new Label("piremove", "×");
18✔
970
                item.add(remove);
27✔
971
                remove.add(new AjaxEventBehavior("click") {
72✔
972

973
                    @Override
974
                    protected void onEvent(AjaxRequestTarget target) {
975
                        pubInfoContexts.remove(pic);
×
976
                        target.add(PublishForm.this);
×
977
                        target.appendJavaScript("updateElements();");
×
978
                    }
×
979

980
                });
981
                if (requiredPubInfoContexts.contains(pic)) {
18!
982
                    remove.setVisible(false);
12✔
983
                }
984
                item.add(new ListView<StatementItem>("pi-statements", pic.getStatementItems()) {
69✔
985

986
                    protected void populateItem(ListItem<StatementItem> item) {
987
                        item.add(item.getModelObject());
33✔
988
                    }
3✔
989

990
                });
991
            }
3✔
992

993
        };
994
        list.setOutputMarkupId(true);
12✔
995
        if (target == null) {
6!
996
            form.add(list);
33✔
997
        } else {
998
            form.remove("pis");
×
999
            form.add(list);
×
1000
            target.add(form);
×
1001
            target.appendJavaScript("updateElements();");
×
1002
        }
1003
    }
3✔
1004

1005
    private TemplateContext createPubInfoContext(String piTemplateId) {
1006
        TemplateContext c;
1007
        if (pubInfoContextMap.containsKey(piTemplateId)) {
×
1008
            c = pubInfoContextMap.get(piTemplateId);
×
1009
        } else {
1010
            c = new TemplateContext(ContextType.PUBINFO, piTemplateId, "pi-statement", targetNamespace);
×
1011
            pubInfoContextMap.put(piTemplateId, c);
×
1012
            pubInfoContexts.add(c);
×
1013
        }
1014
        return c;
×
1015
    }
1016

1017
    private static void collectTemplateErrors(String part, TemplateContext context, List<TemplateError> errors) {
1018
        if (context == null || context.getTemplate() == null) return;
15!
1019
        for (String error : context.getTemplate().getStatementErrors()) {
36✔
1020
            errors.add(new TemplateError(part, error));
24✔
1021
        }
3✔
1022
    }
3✔
1023

1024
    private synchronized Nanopub createNanopub() throws MalformedNanopubException, NanopubAlreadyFinalizedException {
1025
        assertionContext.getIntroducedIris().clear();
×
1026
        assertionContext.getRolePropertyPins().clear();
×
1027
        NanopubCreator npCreator = new NanopubCreator(targetNamespace);
×
1028
        npCreator.setAssertionUri(vf.createIRI(targetNamespace + "assertion"));
×
1029
        assertionContext.propagateStatements(npCreator);
×
1030
        provenanceContext.propagateStatements(npCreator);
×
1031
        for (TemplateContext c : pubInfoContexts) {
×
1032
            c.propagateStatements(npCreator);
×
1033
        }
×
1034
        for (IRI introducedIri : assertionContext.getIntroducedIris()) {
×
1035
            npCreator.addPubinfoStatement(NPX.INTRODUCES, introducedIri);
×
1036
        }
×
1037
        for (IRI embeddedIri : assertionContext.getEmbeddedIris()) {
×
1038
            npCreator.addPubinfoStatement(NPX.EMBEDS, embeddedIri);
×
1039
        }
×
1040
        Map<IRI, IRI> rolePropertyPins = assertionContext.getRolePropertyPins();
×
1041
        if (!rolePropertyPins.isEmpty()) {
×
1042
            // A role predicate carrying a direction pin makes this a role-instantiation
1043
            // nanopub; nanopub-query's pin-resolution branch is gated on this type (#525).
1044
            npCreator.addPubinfoStatement(NPX.HAS_NANOPUB_TYPE, KPXL_TERMS.ROLE_INSTANTIATION);
×
1045
            for (Map.Entry<IRI, IRI> pin : rolePropertyPins.entrySet()) {
×
1046
                npCreator.addPubinfoStatement(pin.getKey(), RDF.TYPE, pin.getValue());
×
1047
            }
×
1048
        }
1049
        npCreator.addNamespace("this", targetNamespace);
×
1050
        npCreator.addNamespace("sub", targetNamespace + "/");
×
1051
        npCreator.addTimestampNow();
×
1052
        // The canonical template ID: the embedded template-node IRI for templates
1053
        // with embedded identity, the nanopub URI for legacy ones.
1054
        IRI templateUri = vf.createIRI(assertionContext.getTemplate().getId());
×
1055
        npCreator.addPubinfoStatement(NTEMPLATE.WAS_CREATED_FROM_TEMPLATE, templateUri);
×
1056
        IRI prTemplateUri = vf.createIRI(provenanceContext.getTemplate().getId());
×
1057
        npCreator.addPubinfoStatement(NTEMPLATE.WAS_CREATED_FROM_PROVENANCE_TEMPLATE, prTemplateUri);
×
1058
        for (TemplateContext c : pubInfoContexts) {
×
1059
            IRI piTemplateUri = vf.createIRI(c.getTemplate().getId());
×
1060
            npCreator.addPubinfoStatement(NTEMPLATE.WAS_CREATED_FROM_PUBINFO_TEMPLATE, piTemplateUri);
×
1061
        }
×
1062
        String nanopubLabel = getNanopubLabel(npCreator);
×
1063
        if (nanopubLabel != null) {
×
1064
            npCreator.addPubinfoStatement(RDFS.LABEL, vf.createLiteral(nanopubLabel));
×
1065
        }
1066
        for (IRI type : assertionContext.getTemplate().getTargetNanopubTypes()) {
×
1067
            npCreator.addPubinfoStatement(NPX.HAS_NANOPUB_TYPE, type);
×
1068
        }
×
1069
        IRI userIri = NanodashSession.get().getUserIri();
×
1070
        if (User.getName(userIri) != null) {
×
1071
            npCreator.addPubinfoStatement(userIri, FOAF.NAME, vf.createLiteral(User.getName(userIri)));
×
1072
            npCreator.addNamespace("foaf", FOAF.NAMESPACE);
×
1073
        }
1074
        String websiteUrl = NanodashPreferences.get().getWebsiteUrl();
×
1075
        if (websiteUrl != null) {
×
1076
            npCreator.addPubinfoStatement(NPX.WAS_CREATED_AT, vf.createIRI(websiteUrl));
×
1077
        }
1078
        return npCreator.finalizeNanopub();
×
1079
    }
1080

1081
    private String getNanopubLabel(NanopubCreator npCreator) {
1082
        if (assertionContext.getTemplate().getNanopubLabelPattern() == null) {
×
1083
            return null;
×
1084
        }
1085

1086
        Map<IRI, String> labelMap = new HashMap<>();
×
1087
        for (Statement st : npCreator.getCurrentPubinfoStatements()) {
×
1088
            if (st.getPredicate().equals(RDFS.LABEL) && st.getObject() instanceof Literal objL) {
×
1089
                labelMap.put((IRI) st.getSubject(), objL.stringValue());
×
1090
            }
1091
        }
×
1092

1093
        String nanopubLabel = assertionContext.getTemplate().getNanopubLabelPattern();
×
1094
        while (nanopubLabel.matches(".*\\$\\{[_a-zA-Z0-9-]+\\}.*")) {
×
1095
            String placeholderPostfix = nanopubLabel.replaceFirst("^.*\\$\\{([_a-zA-Z0-9-]+)\\}.*$", "$1");
×
1096
            IRI placeholderIriHash = vf.createIRI(assertionContext.getTemplateNanopubUri() + "#" + placeholderPostfix);
×
1097
            IRI placeholderIriSlash = vf.createIRI(assertionContext.getTemplateNanopubUri() + "/" + placeholderPostfix);
×
1098
            IRI placeholderIri;
1099
            String placeholderValue = "";
×
1100
            if (assertionContext.getComponentModels().get(placeholderIriSlash) != null) {
×
1101
                placeholderIri = placeholderIriSlash;
×
1102
            } else {
1103
                placeholderIri = placeholderIriHash;
×
1104
            }
1105
            IModel<String> m = (IModel<String>) assertionContext.getComponentModels().get(placeholderIri);
×
1106
            if (m != null) {
×
1107
                placeholderValue = m.orElse("").getObject();
×
1108
            }
1109
            if (placeholderValue == null) {
×
1110
                placeholderValue = "";
×
1111
            }
1112
            String placeholderLabel = placeholderValue;
×
1113
            if (assertionContext.getTemplate().isUriPlaceholder(placeholderIri)) {
×
1114
                try {
1115
                    // TODO Fix this. It doesn't work for placeholders with auto-encode placeholders, etc.
1116
                    //      Not sure we need labels for these, but this code should be improved anyway.
1117
                    String prefix = assertionContext.getTemplate().getPrefix(placeholderIri);
×
1118
                    if (prefix != null) {
×
1119
                        placeholderValue = prefix + placeholderValue;
×
1120
                    }
1121
                    IRI placeholderValueIri = vf.createIRI(placeholderValue);
×
1122
                    String l = assertionContext.getTemplate().getLabel(placeholderValueIri);
×
1123
                    if (labelMap.containsKey(placeholderValueIri)) {
×
1124
                        l = labelMap.get(placeholderValueIri);
×
1125
                    }
1126
                    if (l == null) {
×
1127
                        l = GuidedChoiceItem.getLabel(placeholderValue);
×
1128
                    }
1129
                    if (assertionContext.getTemplate().isAgentPlaceholder(placeholderIri) && !placeholderValue.isEmpty()) {
×
1130
                        l = User.getName(vf.createIRI(placeholderValue));
×
1131
                    }
1132
                    if (l != null && !l.isEmpty()) {
×
1133
                        placeholderLabel = l.replaceFirst(" - .*$", "");
×
1134
                    } else {
1135
                        placeholderLabel = Utils.getShortNameFromURI(placeholderValueIri);
×
1136
                    }
1137
                } catch (Exception ex) {
×
1138
                    logger.error("Nanopub label placeholder IRI error: {}", ex.getMessage());
×
1139
                }
×
1140
            }
1141
            placeholderLabel = placeholderLabel.replaceAll("\\s+", " ");
×
1142
            if (placeholderLabel.length() > 100) {
×
1143
                placeholderLabel = placeholderLabel.substring(0, 97) + "...";
×
1144
            }
1145
            nanopubLabel = Strings.CS.replace(nanopubLabel, "${" + placeholderPostfix + "}", placeholderLabel);
×
1146
        }
×
1147
        return nanopubLabel;
×
1148
    }
1149

1150
    private NanodashPage getConfirmPage(Nanopub signedNp, PageParameters pageParams) {
1151
        if (confirmPageClass == null) {
×
1152
            return null;
×
1153
        }
1154
        try {
1155
            return (NanodashPage) confirmPageClass.getConstructor(Nanopub.class, PageParameters.class).newInstance(signedNp, pageParams);
×
1156
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
×
1157
                | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
1158
            logger.error("Could not create instance of confirmation page: {}", ex.getMessage());
×
1159
        }
1160
        return null;
×
1161
    }
1162

1163
    /**
1164
     * Returns a hint whether the form is stateless or not.
1165
     *
1166
     * @return false if the form is stateful, true if it is stateless.
1167
     */
1168
    // This is supposed to solve the problem that sometimes (but only sometimes) form content is reset
1169
    // if the user browses other pages in parallel:
1170
    protected boolean getStatelessHint() {
1171
        return false;
×
1172
    }
1173

1174
    private boolean canPublishFromSource(PageParameters pageParams) {
1175
        if (!isSourceOutdated(pageParams)) {
×
1176
            return true;
×
1177
        }
1178
        feedbackPanel.error("The nanopublication you are trying to supersede or override is not the latest version.");
×
1179
        return false;
×
1180
    }
1181

1182
    /**
1183
     * Returns the ID of the nanopublication that the given page parameters ask
1184
     * to supersede or override, or null if they ask for neither.
1185
     *
1186
     * @param pageParams the page parameters of the publish page
1187
     * @return the ID of the nanopublication to be superseded or overridden, or
1188
     * null
1189
     */
1190
    public static String getSupersededOrOverriddenNanopubId(PageParameters pageParams) {
1191
        for (String key : sourceParamKeys) {
48✔
1192
            if (!pageParams.get(key).isEmpty()) {
15✔
1193
                return pageParams.get(key).toString();
15✔
1194
            }
1195
        }
1196
        return null;
6✔
1197
    }
1198

1199
    /**
1200
     * Checks whether the given page parameters ask to supersede or override a
1201
     * nanopublication that is not the latest version anymore. Only the latest
1202
     * version can be superseded or overridden.
1203
     *
1204
     * @param pageParams the page parameters of the publish page
1205
     * @return true if a newer version of the nanopublication to be superseded
1206
     * or overridden exists
1207
     */
1208
    public static boolean isSourceOutdated(PageParameters pageParams) {
1209
        String sourceNpId = getSupersededOrOverriddenNanopubId(pageParams);
9✔
1210
        return sourceNpId != null && !sourceNpId.equals(QueryApiAccess.getLatestVersionId(sourceNpId));
33✔
1211
    }
1212

1213
    /**
1214
     * Returns a copy of the given page parameters where the nanopublication to
1215
     * be superseded or overridden is replaced by the given one.
1216
     *
1217
     * @param pageParams the page parameters of the publish page
1218
     * @param npId the ID of the nanopublication to supersede or override
1219
     * instead
1220
     * @return the adjusted copy of the page parameters
1221
     */
1222
    public static PageParameters withSourceNanopub(PageParameters pageParams, String npId) {
1223
        PageParameters newParams = new PageParameters(pageParams);
15✔
1224
        for (String key : sourceParamKeys) {
48✔
1225
            if (!newParams.get(key).isEmpty()) {
15✔
1226
                newParams.set(key, npId);
15✔
1227
            }
1228
        }
1229
        return newParams;
6✔
1230
    }
1231

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