Skip to content

Commit d5e70ba

Browse files
feat(events): update resource last modification date event (#1877)
* feat(events): event type for update resource metadata * feat(events): form resource metadata update event * feat (event): test for update resource metadata event * fix (api-v2): make sure lastModificationDate of an already updated resource is part of the resource metadata update request before attempting to update the resource. * refactor(events): some refactoring
1 parent 61531ab commit d5e70ba

4 files changed

Lines changed: 208 additions & 34 deletions

File tree

‎webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala‎

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,14 +1346,16 @@ case class ResourceAndValueHistoryV2(eventType: String,
13461346
abstract class ResourceOrValueEventBody
13471347

13481348
/**
1349-
* Represents a resource event (createResource) body with all the information required for the request body of this operation.
1350-
* @param resourceIri the IRI of the resource.
1351-
* @param resourceClassIri the class of the resource.
1352-
* @param label the label of the resource.
1353-
* @param values the values of the resource at creation time.
1354-
* @param permissions the permissions assigned to the new resource.
1355-
* @param creationDate the creation date of the resource.
1356-
* @param projectADM the project which the resource belongs to.
1349+
* Represents a resource event (create or delete) body with all the information required for the request body of this operation.
1350+
* @param resourceIri the IRI of the resource.
1351+
* @param resourceClassIri the class of the resource.
1352+
* @param label the label of the resource.
1353+
* @param values the values of the resource at creation time.
1354+
* @param permissions the permissions assigned to the new resource.
1355+
* @param lastModificationDate the last modification date of the resource.
1356+
* @param creationDate the creation date of the resource.
1357+
* @param deletionInfo the deletion info of the resource.
1358+
* @param projectADM the project which the resource belongs to.
13571359
*/
13581360
case class ResourceEventBody(resourceIri: IRI,
13591361
resourceClassIri: SmartIri,
@@ -1423,6 +1425,42 @@ case class ResourceEventBody(resourceIri: IRI,
14231425
}
14241426
}
14251427

1428+
/**
1429+
* Represents an update resource Metadata event body with all the information required for the request body of this operation.
1430+
* The version history of metadata changes are not kept, however every time metadata of a resource has changed, its lastModificationDate
1431+
* is updated accordingly. An event is thus necessary to update the last modification date of the resource.
1432+
* @param resourceIri the IRI of the resource.
1433+
* @param resourceClassIri the class of the resource.
1434+
* @param lastModificationDate the last modification date of the resource.
1435+
* @param newModificationDate the new modification date of the resource.
1436+
1437+
*/
1438+
case class ResourceMetadataEventBody(resourceIri: IRI,
1439+
resourceClassIri: SmartIri,
1440+
lastModificationDate: Instant,
1441+
newModificationDate: Instant)
1442+
extends ResourceOrValueEventBody {
1443+
1444+
def toJsonLD: JsonLDObject = {
1445+
implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance
1446+
1447+
JsonLDObject(
1448+
Map(
1449+
OntologyConstants.KnoraApiV2Complex.ResourceIri -> JsonLDString(resourceIri),
1450+
OntologyConstants.KnoraApiV2Complex.ResourceClassIri -> JsonLDString(resourceClassIri.toString),
1451+
OntologyConstants.KnoraApiV2Complex.LastModificationDate -> JsonLDUtil.datatypeValueToJsonLDObject(
1452+
value = lastModificationDate.toString,
1453+
datatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri
1454+
),
1455+
OntologyConstants.KnoraApiV2Complex.NewModificationDate -> JsonLDUtil.datatypeValueToJsonLDObject(
1456+
value = newModificationDate.toString,
1457+
datatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri
1458+
)
1459+
)
1460+
)
1461+
}
1462+
}
1463+
14261464
/**
14271465
* Represents a value event (create/update content/update permission/delete) body with all the information required for
14281466
* the request body of the operation.
@@ -1536,9 +1574,12 @@ case class ResourceAndValueVersionHistoryResponseV2(projectHistory: Seq[Resource
15361574
val projectHistoryAsJsonLD: Seq[JsonLDObject] = projectHistory.map { historyEntry: ResourceAndValueHistoryV2 =>
15371575
// convert event body to JsonLD object
15381576
val eventBodyAsJsonLD: JsonLDObject = historyEntry.eventBody match {
1539-
case valueEventBody: ValueEventBody => valueEventBody.toJsonLD(targetSchema, settings, schemaOptions)
1540-
case resourceEventBody: ResourceEventBody => resourceEventBody.toJsonLD(targetSchema, settings, schemaOptions)
1541-
case _ => throw NotFoundException(s"Event body is missing or has wrong type.")
1577+
case valueEventBody: ValueEventBody => valueEventBody.toJsonLD(targetSchema, settings, schemaOptions)
1578+
case resourceEventBody: ResourceEventBody =>
1579+
resourceEventBody.toJsonLD(targetSchema, settings, schemaOptions)
1580+
case resourceMetadataEventBody: ResourceMetadataEventBody =>
1581+
resourceMetadataEventBody.toJsonLD
1582+
case _ => throw NotFoundException(s"Event body is missing or has wrong type.")
15421583
}
15431584

15441585
JsonLDObject(

‎webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/resourceAndValueEventsUtil.scala‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ object ResourceAndValueEventsUtil {
77

88
val CREATE_RESOURCE_EVENT = "createResource"
99
val DELETE_RESOURCE_EVENT = "deleteResource"
10+
val UPDATE_RESOURCE_METADATA_EVENT = "updateResourceMetadata"
1011
val CREATE_VALUE_EVENT = "createValue"
1112
val UPDATE_VALUE_CONTENT_EVENT = "updateValueContent"
1213
val UPDATE_VALUE_PERMISSION_EVENT = "updateValuePermission"

‎webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala‎

Lines changed: 120 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,16 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
385385
s"Resource <${resource.resourceIri}> is not a member of class <${updateResourceMetadataRequestV2.resourceClassIri}>")
386386
}
387387

388+
// If resource has already been modified, make sure that its lastModificationDate is given in the request body.
389+
_ = if (resource.lastModificationDate.nonEmpty && updateResourceMetadataRequestV2.maybeLastModificationDate.isEmpty) {
390+
throw EditConflictException(
391+
s"Resource <${resource.resourceIri}> has been modified in the past. Its lastModificationDate " +
392+
s"${resource.lastModificationDate.get} must be included in the request body.")
393+
}
394+
388395
// Make sure that the resource hasn't been updated since the client got its last modification date.
389-
_ = if (resource.lastModificationDate != updateResourceMetadataRequestV2.maybeLastModificationDate) {
396+
_ = if (updateResourceMetadataRequestV2.maybeLastModificationDate.nonEmpty &&
397+
resource.lastModificationDate != updateResourceMetadataRequestV2.maybeLastModificationDate) {
390398
throw EditConflictException(s"Resource <${resource.resourceIri}> has been modified since you last read it")
391399
}
392400

@@ -2491,7 +2499,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
24912499
def getResourceHistoryEvents(
24922500
resourceFullHistRequest: ResourceFullHistoryGetRequestV2): Future[Seq[ResourceAndValueHistoryV2]] = {
24932501

2494-
val resourceHist = resourceFullHistRequest.resourceVersionHistory.reverse
2502+
val resourceHist: Seq[ResourceHistoryEntry] = resourceFullHistRequest.resourceVersionHistory.reverse
24952503
// Collect the full representations of the resource for each version date
24962504
val histories: Seq[Future[(ResourceHistoryEntry, ReadResourceV2)]] = resourceHist.map { hist =>
24972505
for {
@@ -2508,8 +2516,8 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
25082516

25092517
// Create an event for the resource at creation time
25102518
(creationTimeHist, resourceAtCreation) = fullReps.head
2511-
resourceCreateEvent: ResourceAndValueHistoryV2 = getResourceAtCreationDate(resourceAtCreation, creationTimeHist)
2512-
resourceCreationEvent: Seq[ResourceAndValueHistoryV2] = Seq(resourceCreateEvent)
2519+
resourceCreationEvent: Seq[ResourceAndValueHistoryV2] = getResourceCreationEvent(resourceAtCreation,
2520+
creationTimeHist)
25132521

25142522
// If there is a version history for deletion of the event, create a delete resource event for it.
25152523
(deletionRep, resourceAtValueChanges) = fullReps.tail.partition {
@@ -2519,14 +2527,19 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
25192527
.deletionInfo
25202528
.exists(deletionInfo => deletionInfo.deleteDate == resHist.versionDate)
25212529
}
2522-
resourceDeleteEvent = getResourceAtDeletionDates(deletionRep)
2530+
resourceDeleteEvent = getResourceDeletionEvents(deletionRep)
25232531

25242532
// For each value version, form an event
25252533
valuesEvents: Seq[ResourceAndValueHistoryV2] = resourceAtValueChanges.flatMap {
2526-
case (versionHist, readResource) => getValueAtGivenVersionDate(readResource, versionHist, fullReps)
2534+
case (versionHist, readResource) => getValueEvents(readResource, versionHist, fullReps)
25272535
}
25282536

2529-
} yield resourceCreationEvent ++ resourceDeleteEvent ++ valuesEvents
2537+
// Get the update resource metadata event, if there is any.
2538+
resourceMetadataUpdateEvent: Seq[ResourceAndValueHistoryV2] = getResourceMetadataUpdateEvent(fullReps.last,
2539+
valuesEvents,
2540+
resourceDeleteEvent)
2541+
2542+
} yield resourceCreationEvent ++ resourceDeleteEvent ++ valuesEvents ++ resourceMetadataUpdateEvent
25302543
}
25312544

25322545
/**
@@ -2562,8 +2575,8 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
25622575
* @param versionInfoAtCreation the history info of the version; i.e. versionDate and author.
25632576
* @return a createResource event.
25642577
*/
2565-
private def getResourceAtCreationDate(resourceAtTimeOfCreation: ReadResourceV2,
2566-
versionInfoAtCreation: ResourceHistoryEntry): ResourceAndValueHistoryV2 = {
2578+
private def getResourceCreationEvent(resourceAtTimeOfCreation: ReadResourceV2,
2579+
versionInfoAtCreation: ResourceHistoryEntry): Seq[ResourceAndValueHistoryV2] = {
25672580

25682581
val requestBody: ResourceEventBody = ResourceEventBody(
25692582
resourceIri = resourceAtTimeOfCreation.resourceIri,
@@ -2577,12 +2590,13 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
25772590
creationDate = Some(resourceAtTimeOfCreation.creationDate)
25782591
)
25792592

2580-
ResourceAndValueHistoryV2(
2581-
eventType = ResourceAndValueEventsUtil.CREATE_RESOURCE_EVENT,
2582-
versionDate = versionInfoAtCreation.versionDate,
2583-
author = versionInfoAtCreation.author,
2584-
eventBody = requestBody
2585-
)
2593+
Seq(
2594+
ResourceAndValueHistoryV2(
2595+
eventType = ResourceAndValueEventsUtil.CREATE_RESOURCE_EVENT,
2596+
versionDate = versionInfoAtCreation.versionDate,
2597+
author = versionInfoAtCreation.author,
2598+
eventBody = requestBody
2599+
))
25862600
}
25872601

25882602
/**
@@ -2592,7 +2606,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
25922606
* the full representation of resource at time of deletion.
25932607
* @return a seq of deleteResource events.
25942608
*/
2595-
private def getResourceAtDeletionDates(
2609+
private def getResourceDeletionEvents(
25962610
resourceDeletionInfo: Seq[(ResourceHistoryEntry, ReadResourceV2)]): Seq[ResourceAndValueHistoryV2] = {
25972611
resourceDeletionInfo.map {
25982612
case (delHist, fullRepresentation) =>
@@ -2620,7 +2634,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
26202634
* @param allResourceVersions all full representations of resource for each version date in its history.
26212635
* @return a create/update/delete value event.
26222636
*/
2623-
private def getValueAtGivenVersionDate(
2637+
private def getValueEvents(
26242638
resourceAtGivenTime: ReadResourceV2,
26252639
versionHist: ResourceHistoryEntry,
26262640
allResourceVersions: Seq[(ResourceHistoryEntry, ReadResourceV2)]): Seq[ResourceAndValueHistoryV2] = {
@@ -2693,7 +2707,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
26932707
} else {
26942708
// No. return updateValue event
26952709
val (updateEventType: String, updateEventRequestBody: ValueEventBody) =
2696-
getUpdateEventType(propIri, readValue, allResourceVersions, resourceAtGivenTime)
2710+
getValueUpdateEventType(propIri, readValue, allResourceVersions, resourceAtGivenTime)
26972711
ResourceAndValueHistoryV2(
26982712
eventType = updateEventType,
26992713
versionDate = versionHist.versionDate,
@@ -2719,10 +2733,10 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
27192733
* @param resourceAtGivenTime the full representation of the resource at time of value update.
27202734
* @return (eventType, update event request body)
27212735
*/
2722-
private def getUpdateEventType(propertyIri: SmartIri,
2723-
currentVersionOfValue: ReadValueV2,
2724-
allResourceVersions: Seq[(ResourceHistoryEntry, ReadResourceV2)],
2725-
resourceAtGivenTime: ReadResourceV2): (String, ValueEventBody) = {
2736+
private def getValueUpdateEventType(propertyIri: SmartIri,
2737+
currentVersionOfValue: ReadValueV2,
2738+
allResourceVersions: Seq[(ResourceHistoryEntry, ReadResourceV2)],
2739+
resourceAtGivenTime: ReadResourceV2): (String, ValueEventBody) = {
27262740
val previousValueIri: IRI = currentVersionOfValue.previousValueIri.getOrElse(
27272741
throw BadRequestException("No previous value IRI found for the value, Please report this as a bug."))
27282742

@@ -2777,4 +2791,88 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
27772791
}
27782792
event
27792793
}
2794+
2795+
/**
2796+
* Returns an updateResourceMetadata event as [[ResourceAndValueHistoryV2]] with request body of the form
2797+
* [[ResourceMetadataEventBody]] with information necessary to make update metadata of resource request with a
2798+
* given modification date.
2799+
*
2800+
* @param latestVersionOfResource the full representation of the resource.
2801+
* @param valueEvents the events describing value operations.
2802+
* @param resourceDeleteEvents the events describing resource deletion operations.
2803+
* @return an updateResourceMetadata event.
2804+
*/
2805+
private def getResourceMetadataUpdateEvent(
2806+
latestVersionOfResource: (ResourceHistoryEntry, ReadResourceV2),
2807+
valueEvents: Seq[ResourceAndValueHistoryV2],
2808+
resourceDeleteEvents: Seq[ResourceAndValueHistoryV2]): Seq[ResourceAndValueHistoryV2] = {
2809+
val readResource: ReadResourceV2 = latestVersionOfResource._2
2810+
val author: IRI = latestVersionOfResource._1.author
2811+
// Is lastModificationDate of resource None
2812+
readResource.lastModificationDate match {
2813+
// Yes. Do nothing.
2814+
case None => Seq.empty[ResourceAndValueHistoryV2]
2815+
// No. Either a value or the resource metadata must have been modified.
2816+
case Some(modDate) =>
2817+
val deletionEventWithSameDate = resourceDeleteEvents.find(event => event.versionDate == modDate)
2818+
// Is the lastModificationDate of the resource the same as its deletion date?
2819+
val updateMetadataEvent = if (deletionEventWithSameDate.isDefined) {
2820+
// Yes. Do noting.
2821+
Seq.empty[ResourceAndValueHistoryV2]
2822+
// No. Is there any value event?
2823+
} else if (valueEvents.isEmpty) {
2824+
// No. After creation of the resource its metadata must have been updated, use creation date as the lastModification date of the event.
2825+
val requestBody = ResourceMetadataEventBody(
2826+
resourceIri = readResource.resourceIri,
2827+
resourceClassIri = readResource.resourceClassIri,
2828+
lastModificationDate = readResource.creationDate,
2829+
newModificationDate = modDate
2830+
)
2831+
val event = ResourceAndValueHistoryV2(
2832+
eventType = ResourceAndValueEventsUtil.UPDATE_RESOURCE_METADATA_EVENT,
2833+
versionDate = modDate,
2834+
author = author,
2835+
eventBody = requestBody
2836+
)
2837+
Seq(event)
2838+
} else {
2839+
// Yes. Sort the value events by version date.
2840+
val sortedEvents = valueEvents.sortBy(_.versionDate)
2841+
// Is there any value event with version date equal to lastModificationDate of the resource?
2842+
val modDateExists = valueEvents.find(event => event.versionDate == modDate)
2843+
modDateExists match {
2844+
// Yes. The last modification date of the resource reflects the modification of a value. Return nothing.
2845+
case Some(_) => Seq.empty[ResourceAndValueHistoryV2]
2846+
// No. The last modification date of the resource reflects update of a resource's metadata. Return an updateMetadataEvent
2847+
case None =>
2848+
// Find the event with version date before resource's last modification date.
2849+
val eventsBeforeModDate = sortedEvents.filter(event => event.versionDate.isBefore(modDate))
2850+
// Is there any value with versionDate before this date?
2851+
val oldModDate = if (eventsBeforeModDate.nonEmpty) {
2852+
// Yes. assign the versionDate of the last value event as lastModificationDate for request.
2853+
eventsBeforeModDate.last.versionDate
2854+
2855+
} else {
2856+
// No. The metadata of the resource must have been updated after the value operations, use the version date
2857+
// of the last value event as the lastModificationDate
2858+
sortedEvents.last.versionDate
2859+
}
2860+
val requestBody = ResourceMetadataEventBody(
2861+
resourceIri = readResource.resourceIri,
2862+
resourceClassIri = readResource.resourceClassIri,
2863+
lastModificationDate = oldModDate,
2864+
newModificationDate = modDate
2865+
)
2866+
val event = ResourceAndValueHistoryV2(
2867+
eventType = ResourceAndValueEventsUtil.UPDATE_RESOURCE_METADATA_EVENT,
2868+
versionDate = modDate,
2869+
author = author,
2870+
eventBody = requestBody
2871+
)
2872+
Seq(event)
2873+
}
2874+
}
2875+
updateMetadataEvent
2876+
}
2877+
}
27802878
}

0 commit comments

Comments
 (0)