Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.nanopub.extra.services.ApiResponseEntry;
import org.nanopub.extra.services.QueryRef;
import org.nanopub.vocabulary.NPX;
import org.nanopub.SimpleCreatorPattern;
import org.nanopub.vocabulary.NTEMPLATE;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -79,6 +80,7 @@ public class PublishForm extends Panel {
public static final String DEFAULT_PROV_TEMPLATE = "https://w3id.org/np/RA7lSq6MuK_TIC6JMSHvLtee3lpLoZDOqLJCLXevnrPoU";
private static final String supersedesPubInfoTemplateId = "https://w3id.org/np/RAoTD7udB2KtUuOuAe74tJi1t3VzK0DyWS7rYVAq1GRvw";
private static final String derivesFromPubInfoTemplateId = "https://w3id.org/np/RARW4MsFkHuwjycNElvEVtuMjpf4yWDL10-0C5l2MqqRQ";
private static final String changeNotePubInfoTemplateId = "https://w3id.org/np/RAVXmu2rj-pkWoDjHH4n1oAHbseAf3RcUYpYiwI1WXDmY";

private static final String[] fixedPubInfoTemplates = new String[]{CREATOR_PUB_INFO_TEMPLATE, LICENSE_PUB_INFO_TEMPLATE};

Expand Down Expand Up @@ -267,6 +269,17 @@ public PublishForm(String id, final PageParameters pageParams, Class<? extends W
pubInfoContextMap.put(derivesFromPubInfoTemplateId, c);
c.setParam("np", fillNp.getUri().stringValue());
}
if (fillMode == FillMode.SUPERSEDE || fillMode == FillMode.OVERRIDE) {
// Offer a change-note field by default when a new version of a nanopub is
// made (removable, and its statement is optional in the template, so an
// empty note doesn't block publishing):
String changeNoteIdLatest = td.getLatestTemplateId(changeNotePubInfoTemplateId);
if (!pubInfoContextMap.containsKey(changeNoteIdLatest)) {
TemplateContext c = newContext(ContextType.PUBINFO, changeNoteIdLatest, "pi-statement");
pubInfoContexts.add(c);
pubInfoContextMap.put(c.getTemplateId(), c);
}
}
for (IRI r : assertionContext.getTemplate().getRequiredPubInfoElements()) {
String latestId = td.getLatestTemplateId(r.stringValue());
if (pubInfoContextMap.containsKey(r.stringValue()) || pubInfoContextMap.containsKey(latestId)) {
Expand All @@ -293,12 +306,23 @@ public PublishForm(String id, final PageParameters pageParams, Class<? extends W
}
piParamIdMap.put(i, c);
}
// Pubinfo templates of the fill source that are marked transient: their content
// applies to the source nanopub only, so they get no form context; their
// statements are consumed and discarded below instead of carried over.
final List<String> transientPiTemplateIds = new ArrayList<>();
if (fillNp != null && !fillOnlyAssertion) {
for (IRI piTemplateId : td.getPubinfoTemplateIds(fillNp)) {
// The flag is checked on the latest template version, so flagging a
// template also stops carry-over from nanopubs created with older,
// unflagged versions of it:
String piTemplateIdLatest = td.getLatestTemplateId(piTemplateId.stringValue());
if (piTemplateIdLatest.equals(supersedesPubInfoTemplateId)) {
continue;
}
if (isTransientTemplate(piTemplateIdLatest)) {
transientPiTemplateIds.add(piTemplateId.stringValue());
continue;
}
if (!pubInfoContextMap.containsKey(piTemplateIdLatest)) {
// TODO Allow for automatically using latest template version
createPubInfoContext(piTemplateId.stringValue());
Expand Down Expand Up @@ -409,18 +433,68 @@ public PublishForm(String id, final PageParameters pageParams, Class<? extends W
}
}
}
// Consume the transient templates' statements first, so they neither
// fill into another template below nor end up as unused-statement
// warnings; the throwaway contexts are then simply discarded. One
// context consumes only one fill's worth, so repeat with fresh
// contexts until nothing more is consumed (e.g. several
// prov:wasDerivedFrom triples from a multi-source derivation):
for (String tid : transientPiTemplateIds) {
int unusedBefore;
do {
unusedBefore = piFiller.getUnusedStatements().size();
TemplateContext c = newContext(ContextType.PUBINFO, tid, "pi-statement");
c.setFillSource(fillNp);
// The creator slot is preset to the session user and then only
// unifies with them; a throwaway must consume the template's
// triples whoever they were about, so clear the preset:
IModel<?> creatorModel = c.getComponentModels().get(NTEMPLATE.CREATOR_PLACEHOLDER);
if (creatorModel instanceof Model) {
((Model<String>) creatorModel).setObject("");
}
c.initStatements();
piFiller.fill(c);
} while (piFiller.getUnusedStatements().size() < unusedBefore);
}
// The hand-coded catch-all (present already when the source was made with
// it) is filled last, after the cleanup below, so it only gets what no
// other template and no cleanup rule has claimed:
final String handcodedStatementsTemplateId = "https://w3id.org/np/RAMEgudZsQ1bh1fZhfYnkthqH6YSXpghSE_DEN1I-6eAI";
final String handcodedIdLatest = td.getLatestTemplateId(handcodedStatementsTemplateId);
TemplateContext handcodedContext = null;
for (TemplateContext c : pubInfoContexts) {
String latestId = td.getLatestTemplateId(c.getTemplateId());
if (isTransientTemplate(latestId)) {
// A transient template kept in the form (e.g. the fresh derivation
// link in derive mode) starts from its parameters only; values from
// the fill source must not leak into it.
continue;
}
if (latestId.equals(handcodedIdLatest)) {
handcodedContext = c;
continue;
}
piFiller.fill(c);
}
piFiller.removeUnusedStatements(NanodashSession.get().getUserIri(), FOAF.NAME, null);
// The name triples of the source's creators are auto-generated alongside
// the creator statements; with those discarded as transient, keeping the
// orphaned names would leak them into hand-coded statements:
for (IRI creator : SimpleCreatorPattern.getCreators(fillNp)) {
piFiller.removeUnusedStatements(creator, FOAF.NAME, null);
}
if (piFiller.hasUnusedStatements()) {
final String handcodedStatementsTemplateId = "https://w3id.org/np/RAMEgudZsQ1bh1fZhfYnkthqH6YSXpghSE_DEN1I-6eAI";
if (!pubInfoContextMap.containsKey(handcodedStatementsTemplateId)) {
TemplateContext c = createPubInfoContext(handcodedStatementsTemplateId);
c.setFillSource(fillNp);
c.initStatements();
piFiller.fill(c);
if (handcodedContext == null) {
handcodedContext = createPubInfoContext(handcodedStatementsTemplateId);
handcodedContext.setFillSource(fillNp);
handcodedContext.initStatements();
}
piFiller.fill(handcodedContext);
} else if (handcodedContext != null && !requiredPubInfoContexts.contains(handcodedContext)) {
// Everything was claimed or discarded: an empty catch-all would only
// block publishing on its required statement, so drop it.
pubInfoContexts.remove(handcodedContext);
pubInfoContextMap.values().remove(handcodedContext);
}
unusedPiStatementList.addAll(piFiller.getUnusedStatements());
// TODO: Also use pubinfo templates stated in nanopub to be filled in?
Expand Down Expand Up @@ -1045,6 +1119,15 @@ private TemplateContext newContext(ContextType contextType, String templateId, S
return context;
}

/**
* Checks whether the given template is transient (see {@link Template#isTransient()}),
* i.e. its filled content is not carried over when the nanopub is used as fill source.
*/
private static boolean isTransientTemplate(String templateId) {
Template t = TemplateData.get().getTemplate(templateId);
return t != null && t.isTransient();
}

private TemplateContext createPubInfoContext(String piTemplateId) {
TemplateContext c;
if (pubInfoContextMap.containsKey(piTemplateId)) {
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/com/knowledgepixels/nanodash/template/Template.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ public class Template implements Serializable {
*/
public static final IRI POSSIBLE_LANGUAGE_TAG = vf.createIRI("https://w3id.org/np/o/ntemplate/possibleLanguageTag");

/**
* Type of a template whose filled content applies only to the specific nanopublication
* it was published with, and is not carried over when that nanopublication is
* superseded, overridden, derived from, or otherwise used as a fill source.
*/
public static final IRI TRANSIENT_TEMPLATE = vf.createIRI("https://w3id.org/np/o/ntemplate/TransientTemplate");

private final Nanopub nanopub;
private String label;
private String description;
Expand Down Expand Up @@ -118,6 +125,18 @@ public boolean isUnlisted() {
return types != null && types.contains(NTEMPLATE.UNLISTED_TEMPLATE);
}

/**
* Checks if the template is transient, meaning its filled content applies only to the
* specific nanopublication it was published with and is not carried over when that
* nanopublication is superseded, overridden, derived from, or used as a fill source.
*
* @return true if the template is transient, false otherwise.
*/
public boolean isTransient() {
List<IRI> types = typeMap.get(templateIri);
return types != null && types.contains(TRANSIENT_TEMPLATE);
}

/**
* Returns the Nanopub object representing the template.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.knowledgepixels.nanodash.template;

import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
import org.junit.jupiter.api.Test;
import org.nanopub.NanopubCreator;
import org.nanopub.vocabulary.NTEMPLATE;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Tests for the nt:TransientTemplate flag (issue #606): a pubinfo template so flagged
* has its filled content bound to the one nanopub it was published with, and the
* publish form discards that content instead of carrying it over when the nanopub is
* superseded, overridden, derived from, or used as a fill source.
*/
public class TransientTemplateTest {

private static final ValueFactory vf = SimpleValueFactory.getInstance();

private static final String NP_URI = "https://w3id.org/np/RAAbCdEfGhIjKlMnOpQrStUvWxYz0123456789-_AbCdE";
private static final IRI ST1 = vf.createIRI(NP_URI + "/st1");
private static final IRI NOTE = vf.createIRI(NP_URI + "/note");

private static NanopubCreator templateCreator() throws Exception {
NanopubCreator creator = new NanopubCreator(NP_URI);
creator.addProvenanceStatement(vf.createStatement(creator.getAssertionUri(), RDFS.SEEALSO, creator.getAssertionUri()));
creator.addPubinfoStatement(vf.createStatement(creator.getNanopubUri(), RDFS.SEEALSO, creator.getNanopubUri()));
IRI templateNode = creator.getAssertionUri();
creator.addAssertionStatement(templateNode, RDF.TYPE, NTEMPLATE.PUBINFO_TEMPLATE);
creator.addAssertionStatement(templateNode, RDFS.LABEL, vf.createLiteral("Transient template test template"));
creator.addAssertionStatement(templateNode, NTEMPLATE.HAS_STATEMENT, ST1);
creator.addAssertionStatement(ST1, RDF.SUBJECT, vf.createIRI(NP_URI + "/nanopub"));
creator.addAssertionStatement(ST1, RDF.PREDICATE, RDFS.COMMENT);
creator.addAssertionStatement(ST1, RDF.OBJECT, NOTE);
creator.addAssertionStatement(NOTE, RDF.TYPE, NTEMPLATE.LITERAL_PLACEHOLDER);
return creator;
}

@Test
void flaggedTemplateIsTransient() throws Exception {
NanopubCreator creator = templateCreator();
creator.addAssertionStatement(creator.getAssertionUri(), RDF.TYPE, Template.TRANSIENT_TEMPLATE);
Template t = new Template(creator.finalizeNanopub());
assertTrue(t.isTransient());
}

@Test
void unflaggedTemplateIsNotTransient() throws Exception {
Template t = new Template(templateCreator().finalizeNanopub());
assertFalse(t.isTransient());
}

}