Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental/manifest technical note #1260

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
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
@@ -0,0 +1,100 @@
package se.kb.libris

import whelk.Document
import whelk.JsonLd

class SignificantChangeCalculator {

public static Map getImpliedTechnicalNote(Map note, List typedPath, JsonLd jsonld) {

// Are conditions met for an implied primary-contribution marker?
// In other words: Is this (change-) 'note' a '/change/agent' placed on a primaryContribution (or derivative) path?
if ( typedPathEndsWith(typedPath, ["contribution", "@type=PrimaryContribution", "agent", "@type=Agent", "technicalNote", "@type=ChangeNote"], jsonld) ) {
if (note["category"] && note["category"] instanceof Map && note["category"]["@id"] && note["category"]["@id"] instanceof String) {
if (note["category"]["@id"] == "https://libris.kb.se/change/agent") {
return [
"@type" : "ChangeNote",
"category": ["@id": "https://libris.kb.se/change/primarycontribution"],
"date" : note["date"]
]
}
}
}

// Check for additional implied markers..

// Default: Return the original note
return note
}

/**
* Compares two versions of a document, and mutates postupdateDoc with added
* ChangeNotes where applicable.
*/
public static void markSignificantChanges(Document preUpdateDoc, Document postUpdateDoc, Date modTime, JsonLd jsonld) {
List<String> markersToAdd = []

if (significantlyChangedAgent(preUpdateDoc, postUpdateDoc, jsonld))
markersToAdd.add("https://libris.kb.se/change/agent")

// Add additional rules..

List newTechNotes = []
for (String marker : markersToAdd) {
newTechNotes.add(
[
"@type": "ChangeNote",
"category": ["@id": marker],
"date": modTime.toInstant().toString()
]
)
}

if (newTechNotes) {
if (postUpdateDoc.data["@graph"][1]["technicalNote"] && postUpdateDoc.data["@graph"][1]["technicalNote"] instanceof List) {
Set notes = postUpdateDoc.data["@graph"][1]["technicalNote"] as Set
notes.addAll(newTechNotes)
postUpdateDoc.data["@graph"][1]["technicalNote"] = notes.toList()
} else {
postUpdateDoc.data["@graph"][1]["technicalNote"] = newTechNotes
}
}
}

/**
* Types in 'full' are allowed to inherit types in 'end' and still be considered equal-ending.
*
* For example, full = ["instanceOf", "@type=NotatedMusic"] and end = ["instanceOf", "@type=Work"] is considered
* equal-ending, but if 'full' and 'end' switch places, they are not.
*/
private static boolean typedPathEndsWith(List<String> full, List<String> end, JsonLd jsonLd) {
if (end.size() > full.size())
return false
List fullEnd = full.subList(full.size() - end.size(), full.size())

for (int i = 0; i < end.size(); ++i) {
if (fullEnd[i].startsWith("@type=") && end[i].startsWith("@type=")) {
String t1 = fullEnd[i].substring("@type=".length())
String t2 = end[i].substring("@type=".length())
if (! jsonLd.isSubClassOf(t1, t2) ) {
return false
}
}
else if (fullEnd[i] != end[i]) {
return false
}
}
return true
}

private static boolean significantlyChangedAgent(Document preUpdateDoc, Document postUpdateDoc, JsonLd jsonld) {
if ( ! jsonld.isSubClassOf( preUpdateDoc.getThingType(), "Agent") ||
! jsonld.isSubClassOf( postUpdateDoc.getThingType(), "Agent"))
return false

return preUpdateDoc.data["@graph"][1]["name"] != postUpdateDoc.data["@graph"][1]["name"] ||
preUpdateDoc.data["@graph"][1]["givenName"] != postUpdateDoc.data["@graph"][1]["givenName"] ||
preUpdateDoc.data["@graph"][1]["familyName"] != postUpdateDoc.data["@graph"][1]["familyName"] ||
preUpdateDoc.data["@graph"][1]["lifeSpan"] != postUpdateDoc.data["@graph"][1]["lifeSpan"]
}
}
2 changes: 2 additions & 0 deletions whelk-core/src/main/groovy/whelk/JsonLd.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,8 @@ class JsonLd {

private static boolean shouldAlwaysKeep(String key) {
return key == RECORD_KEY || key == THING_KEY || key == JSONLD_ALT_ID_KEY || key.startsWith("@")
// Temporary hack to make technical notes actually inherit/be embellishable
|| key == "technicalNote" || key == "category" || key == "date" || key == "givenName" || key == "familyName"
}


Expand Down
5 changes: 5 additions & 0 deletions whelk-core/src/main/groovy/whelk/JsonLdValidator.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ class JsonLdValidator {
}

private void verifyVocabTerm(String key, value, validation) {

// Temporary, until "ChangeNote" can be added to vocab
if (key == "@type" && value == "ChangeNote")
return

if ((key == jsonLd.TYPE_KEY || isVocabTerm(key))
&& !jsonLd.vocabIndex.containsKey(value?.toString())) {
handleError(new Error(Error.Type.UNKNOWN_VOCAB_VALUE, key, value), validation)
Expand Down
45 changes: 44 additions & 1 deletion whelk-core/src/main/groovy/whelk/component/ElasticSearch.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,50 @@ class ElasticSearch {
}
}

Set collectTechnicalNotes(Object data, List typedPath, JsonLd jsonld) {
Set results = []
if (data instanceof Map) {

if ("ChangeNote" == data["@type"]) {
results.add( se.kb.libris.SignificantChangeCalculator.getImpliedTechnicalNote(data, typedPath, jsonld) )
results.add( data )
}

data.keySet().each {
List<String> nextPath = new ArrayList<>(typedPath)
nextPath.add(it)
if (data[it] instanceof Map && data[it]["@type"])
nextPath.add("@type=" + data[it]["@type"])
results.addAll(collectTechnicalNotes(data[it], nextPath, jsonld))
}
} else if (data instanceof List) {
data.each { it ->
List<String> nextPath = new ArrayList<>(typedPath)
if (it instanceof Map && it["@type"])
nextPath.add("@type=" + it["@type"])
results.addAll(collectTechnicalNotes(it, nextPath, jsonld))
}
}
return results
}

void compileTechnicalNotes(Map framed, JsonLd jsonld) {
Set<Map> compiledNotes = collectTechnicalNotes(framed, [], jsonld)

System.err.println("Final technical notes for " + framed["@id"] + " : " + compiledNotes)

if ( framed["technicalNote"] ) {
if ( framed["technicalNote"] instanceof List )
compiledNotes.addAll(framed["technicalNote"])
else
compiledNotes.add(framed["technicalNote"])
}
framed["technicalNote"] = compiledNotes.toList()
}

String getShapeForIndex(Document document, Whelk whelk) {
Document copy = document.clone()

whelk.embellish(copy, ['search-chips'])

if (log.isDebugEnabled()) {
Expand Down Expand Up @@ -339,6 +380,8 @@ class ElasticSearch {
REMOVABLE_BASE_URIS,
document.getThingInScheme() ? ['tokens', 'chips'] : ['chips'])

compileTechnicalNotes(framed, whelk.getJsonld())

DocumentUtil.traverse(framed) { value, path ->
if (path && JsonLd.SEARCH_KEY == path.last() && !Unicode.isNormalizedForSearch(value)) {
// TODO: replace with elastic ICU Analysis plugin?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.postgresql.PGNotification
import org.postgresql.PGStatement
import org.postgresql.util.PGobject
import org.postgresql.util.PSQLException
import se.kb.libris.SignificantChangeCalculator
import whelk.Document
import whelk.IdType
import whelk.JsonLd
Expand Down Expand Up @@ -898,6 +899,12 @@ class PostgreSQLComponent {

normalizeDocumentForStorage(doc, connection)

Date modTime = minorUpdate
? new Date(resultSet.getTimestamp("modified").getTime())
: new Date()

SignificantChangeCalculator.markSignificantChanges(preUpdateDoc, doc, modTime, getJsonld())

if (!writeIdenticalVersions && preUpdateDoc.getChecksum(jsonld).equals(doc.getChecksum(jsonld))) {
throw new CancelUpdateException()
}
Expand Down Expand Up @@ -938,11 +945,8 @@ class PostgreSQLComponent {
if (doVerifyDocumentIdRetention) {
verifyDocumentIdRetention(preUpdateDoc, doc, connection)
}

Date createdTime = new Date(resultSet.getTimestamp("created").getTime())
Date modTime = minorUpdate
? new Date(resultSet.getTimestamp("modified").getTime())
: new Date()
doc.setModified(modTime)

if (!minorUpdate) {
Expand Down