Skip to content

Commit

Permalink
feat(events): update resource last modification date event (#1877)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
SepidehAlassi committed Jun 17, 2021
1 parent 61531ab commit d5e70ba
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 34 deletions.
Expand Up @@ -1346,14 +1346,16 @@ case class ResourceAndValueHistoryV2(eventType: String,
abstract class ResourceOrValueEventBody

/**
* Represents a resource event (createResource) body with all the information required for the request body of this operation.
* @param resourceIri the IRI of the resource.
* @param resourceClassIri the class of the resource.
* @param label the label of the resource.
* @param values the values of the resource at creation time.
* @param permissions the permissions assigned to the new resource.
* @param creationDate the creation date of the resource.
* @param projectADM the project which the resource belongs to.
* Represents a resource event (create or delete) body with all the information required for the request body of this operation.
* @param resourceIri the IRI of the resource.
* @param resourceClassIri the class of the resource.
* @param label the label of the resource.
* @param values the values of the resource at creation time.
* @param permissions the permissions assigned to the new resource.
* @param lastModificationDate the last modification date of the resource.
* @param creationDate the creation date of the resource.
* @param deletionInfo the deletion info of the resource.
* @param projectADM the project which the resource belongs to.
*/
case class ResourceEventBody(resourceIri: IRI,
resourceClassIri: SmartIri,
Expand Down Expand Up @@ -1423,6 +1425,42 @@ case class ResourceEventBody(resourceIri: IRI,
}
}

/**
* Represents an update resource Metadata event body with all the information required for the request body of this operation.
* The version history of metadata changes are not kept, however every time metadata of a resource has changed, its lastModificationDate
* is updated accordingly. An event is thus necessary to update the last modification date of the resource.
* @param resourceIri the IRI of the resource.
* @param resourceClassIri the class of the resource.
* @param lastModificationDate the last modification date of the resource.
* @param newModificationDate the new modification date of the resource.
*/
case class ResourceMetadataEventBody(resourceIri: IRI,
resourceClassIri: SmartIri,
lastModificationDate: Instant,
newModificationDate: Instant)
extends ResourceOrValueEventBody {

def toJsonLD: JsonLDObject = {
implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance

JsonLDObject(
Map(
OntologyConstants.KnoraApiV2Complex.ResourceIri -> JsonLDString(resourceIri),
OntologyConstants.KnoraApiV2Complex.ResourceClassIri -> JsonLDString(resourceClassIri.toString),
OntologyConstants.KnoraApiV2Complex.LastModificationDate -> JsonLDUtil.datatypeValueToJsonLDObject(
value = lastModificationDate.toString,
datatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri
),
OntologyConstants.KnoraApiV2Complex.NewModificationDate -> JsonLDUtil.datatypeValueToJsonLDObject(
value = newModificationDate.toString,
datatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri
)
)
)
}
}

/**
* Represents a value event (create/update content/update permission/delete) body with all the information required for
* the request body of the operation.
Expand Down Expand Up @@ -1536,9 +1574,12 @@ case class ResourceAndValueVersionHistoryResponseV2(projectHistory: Seq[Resource
val projectHistoryAsJsonLD: Seq[JsonLDObject] = projectHistory.map { historyEntry: ResourceAndValueHistoryV2 =>
// convert event body to JsonLD object
val eventBodyAsJsonLD: JsonLDObject = historyEntry.eventBody match {
case valueEventBody: ValueEventBody => valueEventBody.toJsonLD(targetSchema, settings, schemaOptions)
case resourceEventBody: ResourceEventBody => resourceEventBody.toJsonLD(targetSchema, settings, schemaOptions)
case _ => throw NotFoundException(s"Event body is missing or has wrong type.")
case valueEventBody: ValueEventBody => valueEventBody.toJsonLD(targetSchema, settings, schemaOptions)
case resourceEventBody: ResourceEventBody =>
resourceEventBody.toJsonLD(targetSchema, settings, schemaOptions)
case resourceMetadataEventBody: ResourceMetadataEventBody =>
resourceMetadataEventBody.toJsonLD
case _ => throw NotFoundException(s"Event body is missing or has wrong type.")
}

JsonLDObject(
Expand Down
Expand Up @@ -7,6 +7,7 @@ object ResourceAndValueEventsUtil {

val CREATE_RESOURCE_EVENT = "createResource"
val DELETE_RESOURCE_EVENT = "deleteResource"
val UPDATE_RESOURCE_METADATA_EVENT = "updateResourceMetadata"
val CREATE_VALUE_EVENT = "createValue"
val UPDATE_VALUE_CONTENT_EVENT = "updateValueContent"
val UPDATE_VALUE_PERMISSION_EVENT = "updateValuePermission"
Expand Down
Expand Up @@ -385,8 +385,16 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
s"Resource <${resource.resourceIri}> is not a member of class <${updateResourceMetadataRequestV2.resourceClassIri}>")
}

// If resource has already been modified, make sure that its lastModificationDate is given in the request body.
_ = if (resource.lastModificationDate.nonEmpty && updateResourceMetadataRequestV2.maybeLastModificationDate.isEmpty) {
throw EditConflictException(
s"Resource <${resource.resourceIri}> has been modified in the past. Its lastModificationDate " +
s"${resource.lastModificationDate.get} must be included in the request body.")
}

// Make sure that the resource hasn't been updated since the client got its last modification date.
_ = if (resource.lastModificationDate != updateResourceMetadataRequestV2.maybeLastModificationDate) {
_ = if (updateResourceMetadataRequestV2.maybeLastModificationDate.nonEmpty &&
resource.lastModificationDate != updateResourceMetadataRequestV2.maybeLastModificationDate) {
throw EditConflictException(s"Resource <${resource.resourceIri}> has been modified since you last read it")
}

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

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

// Create an event for the resource at creation time
(creationTimeHist, resourceAtCreation) = fullReps.head
resourceCreateEvent: ResourceAndValueHistoryV2 = getResourceAtCreationDate(resourceAtCreation, creationTimeHist)
resourceCreationEvent: Seq[ResourceAndValueHistoryV2] = Seq(resourceCreateEvent)
resourceCreationEvent: Seq[ResourceAndValueHistoryV2] = getResourceCreationEvent(resourceAtCreation,
creationTimeHist)

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

// For each value version, form an event
valuesEvents: Seq[ResourceAndValueHistoryV2] = resourceAtValueChanges.flatMap {
case (versionHist, readResource) => getValueAtGivenVersionDate(readResource, versionHist, fullReps)
case (versionHist, readResource) => getValueEvents(readResource, versionHist, fullReps)
}

} yield resourceCreationEvent ++ resourceDeleteEvent ++ valuesEvents
// Get the update resource metadata event, if there is any.
resourceMetadataUpdateEvent: Seq[ResourceAndValueHistoryV2] = getResourceMetadataUpdateEvent(fullReps.last,
valuesEvents,
resourceDeleteEvent)

} yield resourceCreationEvent ++ resourceDeleteEvent ++ valuesEvents ++ resourceMetadataUpdateEvent
}

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

val requestBody: ResourceEventBody = ResourceEventBody(
resourceIri = resourceAtTimeOfCreation.resourceIri,
Expand All @@ -2577,12 +2590,13 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
creationDate = Some(resourceAtTimeOfCreation.creationDate)
)

ResourceAndValueHistoryV2(
eventType = ResourceAndValueEventsUtil.CREATE_RESOURCE_EVENT,
versionDate = versionInfoAtCreation.versionDate,
author = versionInfoAtCreation.author,
eventBody = requestBody
)
Seq(
ResourceAndValueHistoryV2(
eventType = ResourceAndValueEventsUtil.CREATE_RESOURCE_EVENT,
versionDate = versionInfoAtCreation.versionDate,
author = versionInfoAtCreation.author,
eventBody = requestBody
))
}

/**
Expand All @@ -2592,7 +2606,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
* the full representation of resource at time of deletion.
* @return a seq of deleteResource events.
*/
private def getResourceAtDeletionDates(
private def getResourceDeletionEvents(
resourceDeletionInfo: Seq[(ResourceHistoryEntry, ReadResourceV2)]): Seq[ResourceAndValueHistoryV2] = {
resourceDeletionInfo.map {
case (delHist, fullRepresentation) =>
Expand Down Expand Up @@ -2620,7 +2634,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
* @param allResourceVersions all full representations of resource for each version date in its history.
* @return a create/update/delete value event.
*/
private def getValueAtGivenVersionDate(
private def getValueEvents(
resourceAtGivenTime: ReadResourceV2,
versionHist: ResourceHistoryEntry,
allResourceVersions: Seq[(ResourceHistoryEntry, ReadResourceV2)]): Seq[ResourceAndValueHistoryV2] = {
Expand Down Expand Up @@ -2693,7 +2707,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
} else {
// No. return updateValue event
val (updateEventType: String, updateEventRequestBody: ValueEventBody) =
getUpdateEventType(propIri, readValue, allResourceVersions, resourceAtGivenTime)
getValueUpdateEventType(propIri, readValue, allResourceVersions, resourceAtGivenTime)
ResourceAndValueHistoryV2(
eventType = updateEventType,
versionDate = versionHist.versionDate,
Expand All @@ -2719,10 +2733,10 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
* @param resourceAtGivenTime the full representation of the resource at time of value update.
* @return (eventType, update event request body)
*/
private def getUpdateEventType(propertyIri: SmartIri,
currentVersionOfValue: ReadValueV2,
allResourceVersions: Seq[(ResourceHistoryEntry, ReadResourceV2)],
resourceAtGivenTime: ReadResourceV2): (String, ValueEventBody) = {
private def getValueUpdateEventType(propertyIri: SmartIri,
currentVersionOfValue: ReadValueV2,
allResourceVersions: Seq[(ResourceHistoryEntry, ReadResourceV2)],
resourceAtGivenTime: ReadResourceV2): (String, ValueEventBody) = {
val previousValueIri: IRI = currentVersionOfValue.previousValueIri.getOrElse(
throw BadRequestException("No previous value IRI found for the value, Please report this as a bug."))

Expand Down Expand Up @@ -2777,4 +2791,88 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
}
event
}

/**
* Returns an updateResourceMetadata event as [[ResourceAndValueHistoryV2]] with request body of the form
* [[ResourceMetadataEventBody]] with information necessary to make update metadata of resource request with a
* given modification date.
*
* @param latestVersionOfResource the full representation of the resource.
* @param valueEvents the events describing value operations.
* @param resourceDeleteEvents the events describing resource deletion operations.
* @return an updateResourceMetadata event.
*/
private def getResourceMetadataUpdateEvent(
latestVersionOfResource: (ResourceHistoryEntry, ReadResourceV2),
valueEvents: Seq[ResourceAndValueHistoryV2],
resourceDeleteEvents: Seq[ResourceAndValueHistoryV2]): Seq[ResourceAndValueHistoryV2] = {
val readResource: ReadResourceV2 = latestVersionOfResource._2
val author: IRI = latestVersionOfResource._1.author
// Is lastModificationDate of resource None
readResource.lastModificationDate match {
// Yes. Do nothing.
case None => Seq.empty[ResourceAndValueHistoryV2]
// No. Either a value or the resource metadata must have been modified.
case Some(modDate) =>
val deletionEventWithSameDate = resourceDeleteEvents.find(event => event.versionDate == modDate)
// Is the lastModificationDate of the resource the same as its deletion date?
val updateMetadataEvent = if (deletionEventWithSameDate.isDefined) {
// Yes. Do noting.
Seq.empty[ResourceAndValueHistoryV2]
// No. Is there any value event?
} else if (valueEvents.isEmpty) {
// No. After creation of the resource its metadata must have been updated, use creation date as the lastModification date of the event.
val requestBody = ResourceMetadataEventBody(
resourceIri = readResource.resourceIri,
resourceClassIri = readResource.resourceClassIri,
lastModificationDate = readResource.creationDate,
newModificationDate = modDate
)
val event = ResourceAndValueHistoryV2(
eventType = ResourceAndValueEventsUtil.UPDATE_RESOURCE_METADATA_EVENT,
versionDate = modDate,
author = author,
eventBody = requestBody
)
Seq(event)
} else {
// Yes. Sort the value events by version date.
val sortedEvents = valueEvents.sortBy(_.versionDate)
// Is there any value event with version date equal to lastModificationDate of the resource?
val modDateExists = valueEvents.find(event => event.versionDate == modDate)
modDateExists match {
// Yes. The last modification date of the resource reflects the modification of a value. Return nothing.
case Some(_) => Seq.empty[ResourceAndValueHistoryV2]
// No. The last modification date of the resource reflects update of a resource's metadata. Return an updateMetadataEvent
case None =>
// Find the event with version date before resource's last modification date.
val eventsBeforeModDate = sortedEvents.filter(event => event.versionDate.isBefore(modDate))
// Is there any value with versionDate before this date?
val oldModDate = if (eventsBeforeModDate.nonEmpty) {
// Yes. assign the versionDate of the last value event as lastModificationDate for request.
eventsBeforeModDate.last.versionDate

} else {
// No. The metadata of the resource must have been updated after the value operations, use the version date
// of the last value event as the lastModificationDate
sortedEvents.last.versionDate
}
val requestBody = ResourceMetadataEventBody(
resourceIri = readResource.resourceIri,
resourceClassIri = readResource.resourceClassIri,
lastModificationDate = oldModDate,
newModificationDate = modDate
)
val event = ResourceAndValueHistoryV2(
eventType = ResourceAndValueEventsUtil.UPDATE_RESOURCE_METADATA_EVENT,
versionDate = modDate,
author = author,
eventBody = requestBody
)
Seq(event)
}
}
updateMetadataEvent
}
}
}

0 comments on commit d5e70ba

Please sign in to comment.