diff --git a/docs/03-apis/api-v2/editing-resources.md b/docs/03-apis/api-v2/editing-resources.md
index a1662ac2a6..20e35a38a8 100644
--- a/docs/03-apis/api-v2/editing-resources.md
+++ b/docs/03-apis/api-v2/editing-resources.md
@@ -330,6 +330,11 @@ The request body is a JSON-LD object containing the following information about
The optional property `knora-api:deleteComment` specifies a comment to be attached to the
resource, explaining why it has been marked as deleted.
+The optional property `knora-api:deleteDate`
+(an [xsd:dateTimeStamp](https://www.w3.org/TR/xmlschema11-2/#dateTimeStamp))
+indicates when the resource was marked as deleted; if not given, the current
+time is used.
+
The response is a JSON-LD document containing the predicate `knora-api:result`
with a confirmation message.
diff --git a/docs/03-apis/api-v2/editing-values.md b/docs/03-apis/api-v2/editing-values.md
index 61b1882c59..e7710cca48 100644
--- a/docs/03-apis/api-v2/editing-values.md
+++ b/docs/03-apis/api-v2/editing-values.md
@@ -83,8 +83,9 @@ Permissions for the new value can be given by adding `knora-api:hasPermissions`.
}
}
```
+
Each value can have an optional custom IRI (of [Knora IRI](knora-iris.md#iris-for-data) form) specified by the `@id` attribute, a custom creation date specified by adding
-`knora-api:creationDate` (an [xsd:dateTimeStamp](https://www.w3.org/TR/xmlschema11-2/#dateTimeStamp)), or a custom UUID
+`knora-api:valueCreationDate` (an [xsd:dateTimeStamp](https://www.w3.org/TR/xmlschema11-2/#dateTimeStamp)), or a custom UUID
given by `knora-api:valueHasUUID`. Each custom UUID must be [base64url-encoded](rfc:4648#section-5), without padding.
For example:
@@ -97,7 +98,7 @@ For example:
"@type" : "knora-api:IntValue",
"knora-api:intValueAsInt" : 21,
"knora-api:valueHasUUID" : "IN4R19yYR0ygi3K2VEHpUQ",
- "knora-api:creationDate" : {
+ "knora-api:valueCreationDate" : {
"@type" : "xsd:dateTimeStamp",
"@value" : "2020-06-04T12:58:54.502951Z"
}
@@ -108,6 +109,7 @@ For example:
"xsd" : "http://www.w3.org/2001/XMLSchema#"
}
```
+
The format of the object of `knora-api:hasPermissions` is described in
[Permissions](../../02-knora-ontologies/knora-base.md#permissions).
@@ -386,6 +388,9 @@ To update only the permissions on a value, submit it with the new permissions an
To update a link, the user must have **modify permission** on the containing resource as
well as on the value.
+To update a value and give it a custom timestamp, add
+`knora-api:valueCreationDate` (an [xsd:dateTimeStamp](https://www.w3.org/TR/xmlschema11-2/#dateTimeStamp)).
+
The response is a JSON-LD document containing only `@id` and `@type`, returning the IRI
and type of the new value version.
@@ -431,7 +436,10 @@ the resource to the value, and the value's ID and type. For example:
```
The optional property `knora-api:deleteComment` specifies a comment to be attached to the
-value, explaining why it has been marked as deleted.
+value, explaining why it has been marked as deleted
+
+The optional property `knora-api:deleteDate` (an [xsd:dateTimeStamp](https://www.w3.org/TR/xmlschema11-2/#dateTimeStamp))
+specifies a custom timestamp indicating when the value was deleted. If not specified, the current time is used.
The response is a JSON-LD document containing the predicate `knora-api:result`
with a confirmation message.
diff --git a/webapi/src/main/scala/org/knora/webapi/messages/util/JsonLDUtil.scala b/webapi/src/main/scala/org/knora/webapi/messages/util/JsonLDUtil.scala
index 5c5eda5054..4c93020533 100644
--- a/webapi/src/main/scala/org/knora/webapi/messages/util/JsonLDUtil.scala
+++ b/webapi/src/main/scala/org/knora/webapi/messages/util/JsonLDUtil.scala
@@ -461,16 +461,13 @@ case class JsonLDObject(value: Map[String, JsonLDValue]) extends JsonLDValue {
}
/**
- * Validates the optional `uuid` of a JSON-LD object as a value uuid.
+ * Validates an optional Base64-encoded UUID in a JSON-LD object.
*
* @return an optional validated decoded UUID.
*/
- def maybeUUID: Option[UUID] = {
+ def maybeUUID(key: String): Option[UUID] = {
implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance
-
- val maybeUUID: Option[UUID] = maybeStringWithValidation(OntologyConstants.KnoraApiV2Complex.ValueHasUUID, stringFormatter.validateBase64EncodedUuid)
-
- maybeUUID
+ maybeStringWithValidation(key, stringFormatter.validateBase64EncodedUuid)
}
/**
@@ -678,6 +675,11 @@ case class JsonLDDocument(body: JsonLDObject, context: JsonLDObject = JsonLDObje
*/
def requireResourcePropertyValue: (SmartIri, JsonLDObject) = body.requireResourcePropertyApiV2ComplexValue
+ /**
+ * A convenience function that calls `body.maybeUUID`.
+ */
+ def maybeUUID(key: String): Option[UUID] = body.maybeUUID(key: String)
+
/**
* Converts this JSON-LD object to its compacted Java representation.
*/
diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala
index 23726a9ea4..fbfd77ecbd 100644
--- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala
+++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala
@@ -584,13 +584,12 @@ object CreateResourceRequestV2 extends KnoraJsonLDRequestReaderV2[CreateResource
OntologyConstants.KnoraApiV2Complex.CreationDate
)
- valueFutures: Map[SmartIri, Seq[Future[CreateValueInNewResourceV2]]] = propertyIriStrs.map {
+ propertyValueFuturesMap: Map[SmartIri, Seq[Future[CreateValueInNewResourceV2]]] = propertyIriStrs.map {
propertyIriStr =>
val propertyIri: SmartIri = propertyIriStr.toSmartIriWithErr(throw BadRequestException(s"Invalid property IRI: <$propertyIriStr>"))
-
val valuesArray: JsonLDArray = jsonLDDocument.requireArray(propertyIriStr)
- val propertyValues = valuesArray.value.map {
+ val valueFuturesSeq: Seq[Future[CreateValueInNewResourceV2]] = valuesArray.value.map {
valueJsonLD =>
val valueJsonLDObject = valueJsonLD match {
case jsonLDObject: JsonLDObject => jsonLDObject
@@ -606,15 +605,22 @@ object CreateResourceRequestV2 extends KnoraJsonLDRequestReaderV2[CreateResource
settings = settings,
log = log
)
+
maybeCustomValueIri: Option[SmartIri] = valueJsonLDObject.maybeIDAsKnoraDataIri
- maybeCustomValueUUID: Option[UUID] = valueJsonLDObject.maybeUUID
+ maybeCustomValueUUID: Option[UUID] = valueJsonLDObject.maybeUUID(OntologyConstants.KnoraApiV2Complex.ValueHasUUID)
- // Get the values's creation date.
+ // Get the value's creation date.
+ // TODO: creationDate for values is a bug, and will not be supported in future. Use valueCreationDate instead.
maybeCustomValueCreationDate: Option[Instant] = valueJsonLDObject.maybeDatatypeValueInObject(
+ key = OntologyConstants.KnoraApiV2Complex.ValueCreationDate,
+ expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri,
+ validationFun = stringFormatter.xsdDateTimeStampToInstant
+ ).orElse(valueJsonLDObject.maybeDatatypeValueInObject(
key = OntologyConstants.KnoraApiV2Complex.CreationDate,
expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri,
validationFun = stringFormatter.xsdDateTimeStampToInstant
- )
+ ))
+
maybePermissions: Option[String] = valueJsonLDObject.maybeStringWithValidation(OntologyConstants.KnoraApiV2Complex.HasPermissions, stringFormatter.toSparqlEncodedString)
} yield CreateValueInNewResourceV2(
valueContent = valueContent,
@@ -625,23 +631,22 @@ object CreateResourceRequestV2 extends KnoraJsonLDRequestReaderV2[CreateResource
)
}
- propertyIri -> propertyValues
+ propertyIri -> valueFuturesSeq
}.toMap
- values: Map[SmartIri, Seq[CreateValueInNewResourceV2]] <- ActorUtil.sequenceSeqFuturesInMap(valueFutures)
+ propertyValuesMap: Map[SmartIri, Seq[CreateValueInNewResourceV2]] <- ActorUtil.sequenceSeqFuturesInMap(propertyValueFuturesMap)
// Get information about the project that the resource should be created in.
projectInfoResponse: ProjectGetResponseADM <- (responderManager ? ProjectGetRequestADM(
ProjectIdentifierADM(maybeIri = Some(projectIri.toString)),
requestingUser = requestingUser
)).mapTo[ProjectGetResponseADM]
-
} yield CreateResourceRequestV2(
createResource = CreateResourceV2(
resourceIri = maybeCustomResourceIri,
resourceClassIri = resourceClassIri,
label = label,
- values = values,
+ values = propertyValuesMap,
projectADM = projectInfoResponse.project,
permissions = permissions,
creationDate = creationDate
@@ -751,12 +756,15 @@ object UpdateResourceMetadataRequestV2 extends KnoraJsonLDRequestReaderV2[Update
* @param resourceIri the IRI of the resource.
* @param resourceClassIri the IRI of the resource class.
* @param maybeDeleteComment a comment explaining why the resource is being marked as deleted.
+ * @param maybeDeleteDate a timestamp indicating when the resource was marked as deleted. If not supplied,
+ * the current time will be used.
* @param maybeLastModificationDate the resource's last modification date, if any.
* @param erase if `true`, the resource will be erased from the triplestore, otherwise it will be marked as deleted.
*/
case class DeleteOrEraseResourceRequestV2(resourceIri: IRI,
resourceClassIri: SmartIri,
maybeDeleteComment: Option[String] = None,
+ maybeDeleteDate: Option[Instant] = None,
maybeLastModificationDate: Option[Instant] = None,
erase: Boolean = false,
requestingUser: UserADM,
@@ -812,10 +820,17 @@ object DeleteOrEraseResourceRequestV2 extends KnoraJsonLDRequestReaderV2[DeleteO
val maybeDeleteComment: Option[String] = jsonLDDocument.maybeStringWithValidation(OntologyConstants.KnoraApiV2Complex.DeleteComment, stringFormatter.toSparqlEncodedString)
+ val maybeDeleteDate: Option[Instant] = jsonLDDocument.maybeDatatypeValueInObject(
+ key = OntologyConstants.KnoraApiV2Complex.DeleteDate,
+ expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri,
+ validationFun = stringFormatter.xsdDateTimeStampToInstant
+ )
+
DeleteOrEraseResourceRequestV2(
resourceIri = resourceIri.toString,
resourceClassIri = resourceClassIri,
maybeDeleteComment = maybeDeleteComment,
+ maybeDeleteDate = maybeDeleteDate,
maybeLastModificationDate = maybeLastModificationDate,
requestingUser = requestingUser,
apiRequestID = apiRequestID
diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala
index 81d38680fd..c9d783ff7d 100644
--- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala
+++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala
@@ -120,23 +120,29 @@ object CreateValueRequestV2 extends KnoraJsonLDRequestReaderV2[CreateValueReques
maybeCustomValueIri: Option[SmartIri] = jsonLDObject.maybeIDAsKnoraDataIri
// Get the custom value UUID if provided.
- maybeCustomUUID: Option[UUID] = jsonLDObject.maybeUUID
+ maybeCustomUUID: Option[UUID] = jsonLDObject.maybeUUID(OntologyConstants.KnoraApiV2Complex.ValueHasUUID)
// Get the value's creation date.
+ // TODO: creationDate for values is a bug, and will not be supported in future. Use valueCreationDate instead.
maybeCreationDate: Option[Instant] = jsonLDObject.maybeDatatypeValueInObject(
+ key = OntologyConstants.KnoraApiV2Complex.ValueCreationDate,
+ expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri,
+ validationFun = stringFormatter.xsdDateTimeStampToInstant
+ ).orElse(jsonLDObject.maybeDatatypeValueInObject(
key = OntologyConstants.KnoraApiV2Complex.CreationDate,
expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri,
validationFun = stringFormatter.xsdDateTimeStampToInstant
- )
+ ))
+
maybePermissions: Option[String] = jsonLDObject.maybeStringWithValidation(OntologyConstants.KnoraApiV2Complex.HasPermissions, stringFormatter.toSparqlEncodedString)
} yield CreateValueV2(
resourceIri = resourceIri.toString,
resourceClassIri = resourceClassIri,
propertyIri = propertyIri,
valueContent = valueContent,
- customValueIri = maybeCustomValueIri,
- customValueUUID = maybeCustomUUID,
- customValueCreationDate = maybeCreationDate,
+ valueIri = maybeCustomValueIri,
+ valueUUID = maybeCustomUUID,
+ valueCreationDate = maybeCreationDate,
permissions = maybePermissions
)
}
@@ -244,6 +250,13 @@ object UpdateValueRequestV2 extends KnoraJsonLDRequestReaderV2[UpdateValueReques
case (propertyIri: SmartIri, jsonLDObject: JsonLDObject) =>
val valueIri: IRI = jsonLDObject.requireIDAsKnoraDataIri.toString
+ // Get the custom value creation date, if provided.
+ val maybeValueCreationDate: Option[Instant] = jsonLDObject.maybeDatatypeValueInObject(
+ key = OntologyConstants.KnoraApiV2Complex.ValueCreationDate,
+ expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri,
+ validationFun = stringFormatter.xsdDateTimeStampToInstant
+ )
+
// Does the value object just contain knora-api:hasPermissions?
val valuePredicatesMinusIDAndType: Set[IRI] = jsonLDObject.value.keySet - JsonLDConstants.ID - JsonLDConstants.TYPE
@@ -261,7 +274,8 @@ object UpdateValueRequestV2 extends KnoraJsonLDRequestReaderV2[UpdateValueReques
propertyIri = propertyIri,
valueIri = valueIri,
valueType = valueType,
- permissions = permissions
+ permissions = permissions,
+ valueCreationDate = maybeValueCreationDate
)
)
} else {
@@ -284,9 +298,9 @@ object UpdateValueRequestV2 extends KnoraJsonLDRequestReaderV2[UpdateValueReques
propertyIri = propertyIri,
valueIri = valueIri,
valueContent = valueContent,
- permissions = maybePermissions
+ permissions = maybePermissions,
+ valueCreationDate = maybeValueCreationDate
)
-
}
}
} yield UpdateValueRequestV2(
@@ -342,6 +356,8 @@ case class UpdateValueResponseV2(valueIri: IRI,
* @param valueIri the IRI of the value to be marked as deleted.
* @param valueTypeIri the IRI of the value class.
* @param deleteComment an optional comment explaining why the value is being marked as deleted.
+ * @param deleteDate an optional timestamp indicating when the value was deleted. If not supplied,
+ * the current time will be used.
* @param requestingUser the user making the request.
* @param apiRequestID the API request ID.
*/
@@ -351,6 +367,7 @@ case class DeleteValueRequestV2(resourceIri: IRI,
valueIri: IRI,
valueTypeIri: SmartIri,
deleteComment: Option[String] = None,
+ deleteDate: Option[Instant] = None,
requestingUser: UserADM,
apiRequestID: UUID) extends ValuesResponderRequestV2
@@ -413,6 +430,12 @@ object DeleteValueRequestV2 extends KnoraJsonLDRequestReaderV2[DeleteValueReques
val deleteComment: Option[String] = jsonLDObject.maybeStringWithValidation(OntologyConstants.KnoraApiV2Complex.DeleteComment, stringFormatter.toSparqlEncodedString)
+ val deleteDate: Option[Instant] = jsonLDObject.maybeDatatypeValueInObject(
+ key = OntologyConstants.KnoraApiV2Complex.DeleteDate,
+ expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri,
+ validationFun = stringFormatter.xsdDateTimeStampToInstant
+ )
+
DeleteValueRequestV2(
resourceIri = resourceIri.toString,
resourceClassIri = resourceClassIri,
@@ -420,6 +443,7 @@ object DeleteValueRequestV2 extends KnoraJsonLDRequestReaderV2[DeleteValueReques
valueIri = valueIri.toString,
valueTypeIri = valueTypeIri,
deleteComment = deleteComment,
+ deleteDate = deleteDate,
requestingUser = requestingUser,
apiRequestID = apiRequestID
)
@@ -785,22 +809,23 @@ case class ReadOtherValueV2(valueIri: IRI,
/**
* Represents a Knora value to be created in an existing resource.
*
- * @param resourceIri the resource the new value should be attached to.
- * @param resourceClassIri the resource class that the client believes the resource belongs to.
- * @param propertyIri the property of the new value. If the client wants to create a link, this must be a link value property.
- * @param valueContent the content of the new value. If the client wants to create a link, this must be a [[LinkValueContentV2]].
- * @param customValueIri the optional custom IRI supplied for the value.
- * @param customValueUUID the optional custom UUID supplied for the value.
- * @param customValueCreationDate the optional custom creation date supplied for the value.
- * @param permissions the permissions to be given to the new value. If not provided, these will be taken from defaults.
+ * @param resourceIri the resource the new value should be attached to.
+ * @param resourceClassIri the resource class that the client believes the resource belongs to.
+ * @param propertyIri the property of the new value. If the client wants to create a link, this must be a link value property.
+ * @param valueContent the content of the new value. If the client wants to create a link, this must be a [[LinkValueContentV2]].
+ * @param valueIri the optional custom IRI supplied for the value.
+ * @param valueUUID the optional custom UUID supplied for the value.
+ * @param valueCreationDate the optional custom creation date supplied for the value. If not supplied,
+ * the current time will be used.
+ * @param permissions the permissions to be given to the new value. If not provided, these will be taken from defaults.
*/
case class CreateValueV2(resourceIri: IRI,
resourceClassIri: SmartIri,
propertyIri: SmartIri,
valueContent: ValueContentV2,
- customValueIri: Option[SmartIri] = None,
- customValueUUID: Option[UUID] = None,
- customValueCreationDate: Option[Instant] = None,
+ valueIri: Option[SmartIri] = None,
+ valueUUID: Option[UUID] = None,
+ valueCreationDate: Option[Instant] = None,
permissions: Option[String] = None) extends IOValueV2
@@ -827,43 +852,54 @@ trait UpdateValueV2 {
* The value IRI.
*/
val valueIri: IRI
+
+ /**
+ * A custom value creation date.
+ */
+ val valueCreationDate: Option[Instant]
}
/**
* A new version of a value of a Knora property to be created.
*
- * @param resourceIri the resource that the current value version is attached to.
- * @param resourceClassIri the resource class that the client believes the resource belongs to.
- * @param propertyIri the property that the client believes points to the value. If the value is a link value,
- * this must be a link value property.
- * @param valueIri the IRI of the value to be updated.
- * @param valueContent the content of the new version of the value.
- * @param permissions the permissions to be attached to the new value version.
+ * @param resourceIri the resource that the current value version is attached to.
+ * @param resourceClassIri the resource class that the client believes the resource belongs to.
+ * @param propertyIri the property that the client believes points to the value. If the value is a link value,
+ * this must be a link value property.
+ * @param valueIri the IRI of the value to be updated.
+ * @param valueContent the content of the new version of the value.
+ * @param permissions the permissions to be attached to the new value version.
+ * @param valueCreationDate an optional custom creation date to be attached to the new value version. If not supplied,
+ * the current time will be used.
*/
case class UpdateValueContentV2(resourceIri: IRI,
resourceClassIri: SmartIri,
propertyIri: SmartIri,
valueIri: IRI,
valueContent: ValueContentV2,
- permissions: Option[String] = None) extends IOValueV2 with UpdateValueV2
+ permissions: Option[String] = None,
+ valueCreationDate: Option[Instant] = None) extends IOValueV2 with UpdateValueV2
/**
* New permissions for a value.
*
- * @param resourceIri the resource that the current value version is attached to.
- * @param resourceClassIri the resource class that the client believes the resource belongs to.
- * @param propertyIri the property that the client believes points to the value. If the value is a link value,
- * this must be a link value property.
- * @param valueIri the IRI of the value to be updated.
- * @param valueType the IRI of the value type.
- * @param permissions the permissions to be attached to the new value version.
+ * @param resourceIri the resource that the current value version is attached to.
+ * @param resourceClassIri the resource class that the client believes the resource belongs to.
+ * @param propertyIri the property that the client believes points to the value. If the value is a link value,
+ * this must be a link value property.
+ * @param valueIri the IRI of the value to be updated.
+ * @param valueType the IRI of the value type.
+ * @param permissions the permissions to be attached to the new value version.
+ * @param valueCreationDate an optional custom creation date to be attached to the new value version. If not supplied,
+ * the current time will be used.
*/
case class UpdateValuePermissionsV2(resourceIri: IRI,
resourceClassIri: SmartIri,
propertyIri: SmartIri,
valueIri: IRI,
valueType: SmartIri,
- permissions: String) extends UpdateValueV2
+ permissions: String,
+ valueCreationDate: Option[Instant] = None) extends UpdateValueV2
/**
* The IRI and content of a new value or value version whose existence in the triplestore needs to be verified.
diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala
index 80d03e88fc..93a7123350 100644
--- a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala
+++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala
@@ -78,7 +78,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
case ResourceTEIGetRequestV2(resIri, textProperty, mappingIri, gravsearchTemplateIri, headerXSLTIri, requestingUser) => getResourceAsTeiV2(resIri, textProperty, mappingIri, gravsearchTemplateIri, headerXSLTIri, requestingUser)
case createResourceRequestV2: CreateResourceRequestV2 => createResourceV2(createResourceRequestV2)
case updateResourceMetadataRequestV2: UpdateResourceMetadataRequestV2 => updateResourceMetadataV2(updateResourceMetadataRequestV2)
- case deleteResourceRequestV2: DeleteOrEraseResourceRequestV2 => deleteOrEraseResourceV2(deleteResourceRequestV2)
+ case deleteOrEraseResourceRequestV2: DeleteOrEraseResourceRequestV2 => deleteOrEraseResourceV2(deleteOrEraseResourceRequestV2)
case graphDataGetRequest: GraphDataGetRequestV2 => getGraphDataResponseV2(graphDataGetRequest)
case resourceHistoryRequest: ResourceVersionHistoryGetRequestV2 => getResourceHistoryV2(resourceHistoryRequest)
case other => handleUnexpectedMessage(other, log, this.getClass.getName)
@@ -419,6 +419,11 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
throw EditConflictException(s"Resource <${resource.resourceIri}> has been modified since you last read it")
}
+ // If a custom delete date was provided, make sure it's later than the resource's most recent timestamp.
+ _ = if (deleteResourceV2.maybeDeleteDate.exists(!_.isAfter(resource.lastModificationDate.getOrElse(resource.creationDate)))) {
+ throw BadRequestException(s"A custom delete date must be later than the date when the resource was created or last modified")
+ }
+
// Check that the user has permission to mark the resource as deleted.
_ = ResourceUtilV2.checkResourcePermission(
resourceInfo = resource,
@@ -435,7 +440,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
dataNamedGraph = dataNamedGraph,
resourceIri = deleteResourceV2.resourceIri,
maybeDeleteComment = deleteResourceV2.maybeDeleteComment,
- currentTime = Instant.now,
+ currentTime = deleteResourceV2.maybeDeleteDate.getOrElse(Instant.now),
requestingUser = deleteResourceV2.requestingUser.id
).toString()
diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/ValuesResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/ValuesResponderV2.scala
index 866f783c67..5781705782 100644
--- a/webapi/src/main/scala/org/knora/webapi/responders/v2/ValuesResponderV2.scala
+++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/ValuesResponderV2.scala
@@ -251,9 +251,9 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
resourceInfo = resourceInfo,
propertyIri = adjustedInternalPropertyIri,
value = submittedInternalValueContent,
- customValueIri = createValueRequest.createValue.customValueIri,
- customValueUUID = createValueRequest.createValue.customValueUUID,
- customValueCreationDate = createValueRequest.createValue.customValueCreationDate,
+ valueIri = createValueRequest.createValue.valueIri,
+ valueUUID = createValueRequest.createValue.valueUUID,
+ valueCreationDate = createValueRequest.createValue.valueCreationDate,
valueCreator = createValueRequest.requestingUser.id,
valuePermissions = newValuePermissionLiteral,
requestingUser = createValueRequest.requestingUser
@@ -315,19 +315,19 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
* Creates a new value (either an ordinary value or a link), using an existing transaction, assuming that
* pre-update checks have already been done.
*
- * @param dataNamedGraph the named graph in which the value is to be created.
- * @param projectIri the IRI of the project in which to create the value.
- * @param resourceInfo information about the the resource in which to create the value.
- * @param propertyIri the IRI of the property that will point from the resource to the value, or, if
- * the value is a link value, the IRI of the link property.
- * @param value the value to create.
- * @param customValueIri the optional custom IRI supplied for the value.
- * @param customValueUUID the optional custom UUID supplied for the value.
- * @param customValueCreationDate the optional custom creation date supplied for the value.
- * @param valueCreator the IRI of the new value's owner.
- * @param valuePermissions the literal that should be used as the object of the new value's
- * `knora-base:hasPermissions` predicate.
- * @param requestingUser the user making the request.
+ * @param dataNamedGraph the named graph in which the value is to be created.
+ * @param projectIri the IRI of the project in which to create the value.
+ * @param resourceInfo information about the the resource in which to create the value.
+ * @param propertyIri the IRI of the property that will point from the resource to the value, or, if
+ * the value is a link value, the IRI of the link property.
+ * @param value the value to create.
+ * @param valueIri the optional custom IRI supplied for the value.
+ * @param valueUUID the optional custom UUID supplied for the value.
+ * @param valueCreationDate the optional custom creation date supplied for the value.
+ * @param valueCreator the IRI of the new value's owner.
+ * @param valuePermissions the literal that should be used as the object of the new value's
+ * `knora-base:hasPermissions` predicate.
+ * @param requestingUser the user making the request.
* @return an [[UnverifiedValueV2]].
*/
private def createValueV2AfterChecks(dataNamedGraph: IRI,
@@ -335,13 +335,12 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
resourceInfo: ReadResourceV2,
propertyIri: SmartIri,
value: ValueContentV2,
- customValueIri: Option[SmartIri],
- customValueUUID: Option[UUID],
- customValueCreationDate: Option[Instant],
+ valueIri: Option[SmartIri],
+ valueUUID: Option[UUID],
+ valueCreationDate: Option[Instant],
valueCreator: IRI,
valuePermissions: String,
requestingUser: UserADM): Future[UnverifiedValueV2] = {
-
value match {
case linkValueContent: LinkValueContentV2 =>
createLinkValueV2AfterChecks(
@@ -349,9 +348,9 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
resourceInfo = resourceInfo,
linkPropertyIri = propertyIri,
linkValueContent = linkValueContent,
- maybeValueIri = customValueIri,
- maybeValueUUID = customValueUUID,
- maybeCreationDate = customValueCreationDate,
+ maybeValueIri = valueIri,
+ maybeValueUUID = valueUUID,
+ maybeCreationDate = valueCreationDate,
valueCreator = valueCreator,
valuePermissions = valuePermissions,
requestingUser = requestingUser
@@ -363,9 +362,9 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
resourceInfo = resourceInfo,
propertyIri = propertyIri,
value = ordinaryValueContent,
- maybeValueIri = customValueIri,
- maybeValueUUID = customValueUUID,
- maybeValueCreationDate = customValueCreationDate,
+ maybeValueIri = valueIri,
+ maybeValueUUID = valueUUID,
+ maybeValueCreationDate = valueCreationDate,
valueCreator = valueCreator,
valuePermissions = valuePermissions,
requestingUser = requestingUser
@@ -853,6 +852,11 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
_ = if (currentValue.valueContent.valueType != submittedExternalValueType.toOntologySchema(InternalSchema)) {
throw BadRequestException(s"Value <$valueIri> has type <${currentValue.valueContent.valueType.toOntologySchema(ApiV2Complex)}>, but the submitted type was <$submittedExternalValueType>")
}
+
+ // If a custom value creation date was submitted, make sure it's later than the date of the current version.
+ _ = if (updateValueRequest.updateValue.valueCreationDate.exists(!_.isAfter(currentValue.valueCreationDate))) {
+ throw BadRequestException("A custom value creation date must be later than the date of the current version")
+ }
} yield ResourcePropertyValue(
resource = resourceInfo,
submittedInternalPropertyIri = submittedInternalPropertyIri,
@@ -907,7 +911,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
dataNamedGraph: IRI = stringFormatter.projectDataNamedGraphV2(resourceInfo.projectADM)
newValueIri: IRI = stringFormatter.makeRandomValueIri(resourceInfo.resourceIri)
- currentTime: Instant = Instant.now
+ currentTime: Instant = updateValuePermissionsV2.valueCreationDate.getOrElse(Instant.now)
sparqlUpdate = org.knora.webapi.messages.twirl.queries.sparql.v2.txt.changeValuePermissions(
dataNamedGraph = dataNamedGraph,
@@ -1069,6 +1073,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
newLinkValue = newLinkValue,
valueCreator = updateValueRequest.requestingUser.id,
valuePermissions = newValueVersionPermissionLiteral,
+ valueCreationDate = updateValueContentV2.valueCreationDate,
requestingUser = updateValueRequest.requestingUser
)
@@ -1081,6 +1086,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
newValueVersion = submittedInternalValueContent,
valueCreator = updateValueRequest.requestingUser.id,
valuePermissions = newValueVersionPermissionLiteral,
+ valueCreationDate = updateValueContentV2.valueCreationDate,
requestingUser = updateValueRequest.requestingUser
)
}
@@ -1136,14 +1142,15 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
/**
* Changes an ordinary value (i.e. not a link), assuming that pre-update checks have already been done.
*
- * @param dataNamedGraph the IRI of the named graph to be updated.
- * @param resourceInfo information about the resource containing the value.
- * @param propertyIri the IRI of the property that points to the value.
- * @param currentValue a [[ReadValueV2]] representing the existing value version.
- * @param newValueVersion a [[ValueContentV2]] representing the new value version, in the internal schema.
- * @param valueCreator the IRI of the new value's owner.
- * @param valuePermissions the literal that should be used as the object of the new value's `knora-base:hasPermissions` predicate.
- * @param requestingUser the user making the request.
+ * @param dataNamedGraph the IRI of the named graph to be updated.
+ * @param resourceInfo information about the resource containing the value.
+ * @param propertyIri the IRI of the property that points to the value.
+ * @param currentValue a [[ReadValueV2]] representing the existing value version.
+ * @param newValueVersion a [[ValueContentV2]] representing the new value version, in the internal schema.
+ * @param valueCreator the IRI of the new value's owner.
+ * @param valuePermissions the literal that should be used as the object of the new value's `knora-base:hasPermissions` predicate.
+ * @param valueCreationDate a custom value creation date.
+ * @param requestingUser the user making the request.
* @return an [[UnverifiedValueV2]].
*/
private def updateOrdinaryValueV2AfterChecks(dataNamedGraph: IRI,
@@ -1153,6 +1160,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
newValueVersion: ValueContentV2,
valueCreator: IRI,
valuePermissions: String,
+ valueCreationDate: Option[Instant],
requestingUser: UserADM): Future[UnverifiedValueV2] = {
for {
newValueIri: IRI <- makeUnusedValueIri(resourceInfo.resourceIri)
@@ -1203,8 +1211,9 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
case _ => FastFuture.successful(Vector.empty[SparqlTemplateLinkUpdate])
}
- // Make a timestamp to indicate when the value was updated.
- currentTime: Instant = Instant.now
+ // If no custom value creation date was provided, make a timestamp to indicate when the value
+ // was updated.
+ currentTime: Instant = valueCreationDate.getOrElse(Instant.now)
// Generate a SPARQL update.
sparqlUpdate = org.knora.webapi.messages.twirl.queries.sparql.v2.txt.addValueVersion(
@@ -1246,14 +1255,15 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
/**
* Changes a link, assuming that pre-update checks have already been done.
*
- * @param dataNamedGraph the IRI of the named graph to be updated.
- * @param resourceInfo information about the resource containing the link.
- * @param linkPropertyIri the IRI of the link property.
- * @param currentLinkValue a [[ReadLinkValueV2]] representing the `knora-base:LinkValue` for the existing link.
- * @param newLinkValue a [[LinkValueContentV2]] indicating the new target resource.
- * @param valueCreator the IRI of the new link value's owner.
- * @param valuePermissions the literal that should be used as the object of the new link value's `knora-base:hasPermissions` predicate.
- * @param requestingUser the user making the request.
+ * @param dataNamedGraph the IRI of the named graph to be updated.
+ * @param resourceInfo information about the resource containing the link.
+ * @param linkPropertyIri the IRI of the link property.
+ * @param currentLinkValue a [[ReadLinkValueV2]] representing the `knora-base:LinkValue` for the existing link.
+ * @param newLinkValue a [[LinkValueContentV2]] indicating the new target resource.
+ * @param valueCreator the IRI of the new link value's owner.
+ * @param valuePermissions the literal that should be used as the object of the new link value's `knora-base:hasPermissions` predicate.
+ * @param valueCreationDate a custom value creation date.
+ * @param requestingUser the user making the request.
* @return an [[UnverifiedValueV2]].
*/
private def updateLinkValueV2AfterChecks(dataNamedGraph: IRI,
@@ -1263,6 +1273,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
newLinkValue: LinkValueContentV2,
valueCreator: IRI,
valuePermissions: String,
+ valueCreationDate: Option[Instant],
requestingUser: UserADM): Future[UnverifiedValueV2] = {
// Are we changing the link target?
@@ -1288,8 +1299,9 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
requestingUser = requestingUser
)
- // Make a timestamp to indicate when the link value was updated.
- currentTime: Instant = Instant.now
+ // If no custom value creation date was provided, make a timestamp to indicate when the link value
+ // was updated.
+ currentTime: Instant = valueCreationDate.getOrElse(Instant.now)
// Make a new UUID for the new link value.
newLinkValueUUID = UUID.randomUUID
@@ -1465,6 +1477,11 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
throw OntologyConstraintException(s"Resource class <${resourceInfo.resourceClassIri.toOntologySchema(ApiV2Complex)}> has a cardinality of ${cardinalityInfo.cardinality} on property <${deleteValueRequest.propertyIri}>, and this does not allow a value to be deleted for that property from resource <${deleteValueRequest.resourceIri}>")
}
+ // If a custom delete date was submitted, make sure it's later than the date of the current version.
+ _ = if (deleteValueRequest.deleteDate.exists(!_.isAfter(currentValue.valueCreationDate))) {
+ throw BadRequestException("A custom delete date must be later than the value's creation date")
+ }
+
// Get information about the project that the resource is in, so we know which named graph to do the update in.
dataNamedGraph: IRI = stringFormatter.projectDataNamedGraphV2(resourceInfo.projectADM)
@@ -1475,6 +1492,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
resourceInfo = resourceInfo,
propertyIri = adjustedInternalPropertyIri,
deleteComment = deleteValueRequest.deleteComment,
+ deleteDate = deleteValueRequest.deleteDate,
currentValue = currentValue,
requestingUser = deleteValueRequest.requestingUser
)
@@ -1523,6 +1541,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
* @param propertyIri the IRI of the property that points from the resource to the value.
* @param currentValue the value to be deleted.
* @param deleteComment an optional comment explaining why the value is being deleted.
+ * @param deleteDate an optional timestamp indicating when the value was deleted.
* @param requestingUser the user making the request.
* @return the IRI of the value that was marked as deleted.
*/
@@ -1530,6 +1549,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
resourceInfo: ReadResourceV2,
propertyIri: SmartIri,
deleteComment: Option[String],
+ deleteDate: Option[Instant],
currentValue: ReadValueV2,
requestingUser: UserADM): Future[IRI] = {
currentValue.valueContent match {
@@ -1540,6 +1560,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
propertyIri = propertyIri,
currentValue = currentValue,
deleteComment = deleteComment,
+ deleteDate = deleteDate,
requestingUser = requestingUser
)
@@ -1550,6 +1571,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
propertyIri = propertyIri,
currentValue = currentValue,
deleteComment = deleteComment,
+ deleteDate = deleteDate,
requestingUser = requestingUser
)
}
@@ -1563,6 +1585,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
* @param propertyIri the IRI of the property that points from the resource to the value.
* @param currentValue the value to be deleted.
* @param deleteComment an optional comment explaining why the value is being deleted.
+ * @param deleteDate an optional timestamp indicating when the value was deleted.
* @param requestingUser the user making the request.
* @return the IRI of the value that was marked as deleted.
*/
@@ -1571,6 +1594,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
propertyIri: SmartIri,
currentValue: ReadValueV2,
deleteComment: Option[String],
+ deleteDate: Option[Instant],
requestingUser: UserADM): Future[IRI] = {
// Make a new version of of the LinkValue with a reference count of 0, and mark the new
// version as deleted. Give the new version the same permissions as the previous version.
@@ -1580,8 +1604,9 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
case _ => throw AssertionException("Unreachable code")
}
- // Make a timestamp to indicate when the link value was updated.
- val currentTime: Instant = Instant.now
+ // If no custom delete date was provided, make a timestamp to indicate when the link value was
+ // marked as deleted.
+ val currentTime: Instant = deleteDate.getOrElse(Instant.now)
for {
// Delete the existing link and decrement its LinkValue's reference count.
@@ -1616,6 +1641,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
* @param propertyIri the IRI of the property that points from the resource to the value.
* @param currentValue the value to be deleted.
* @param deleteComment an optional comment explaining why the value is being deleted.
+ * @param deleteDate an optional timestamp indicating when the value was deleted.
* @param requestingUser the user making the request.
* @return the IRI of the value that was marked as deleted.
*/
@@ -1624,6 +1650,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
propertyIri: SmartIri,
currentValue: ReadValueV2,
deleteComment: Option[String],
+ deleteDate: Option[Instant],
requestingUser: UserADM): Future[IRI] = {
// Mark the existing version of the value as deleted.
@@ -1648,8 +1675,9 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
val linkUpdateFuture = Future.sequence(linkUpdateFutures)
- // Make a timestamp to indicate when the value was marked as deleted.
- val currentTime: Instant = Instant.now
+ // If no custom delete date was provided, make a timestamp to indicate when the value was
+ // marked as deleted.
+ val currentTime: Instant = deleteDate.getOrElse(Instant.now)
for {
linkUpdates: Seq[SparqlTemplateLinkUpdate] <- linkUpdateFuture
diff --git a/webapi/src/main/scala/org/knora/webapi/routing/v2/ResourcesRouteV2.scala b/webapi/src/main/scala/org/knora/webapi/routing/v2/ResourcesRouteV2.scala
index 9608bbed95..b2ebc666aa 100644
--- a/webapi/src/main/scala/org/knora/webapi/routing/v2/ResourcesRouteV2.scala
+++ b/webapi/src/main/scala/org/knora/webapi/routing/v2/ResourcesRouteV2.scala
@@ -553,9 +553,10 @@ class ResourcesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
}
}
- private def deleteResourceTestRequestAndResponse: Future[Set[TestDataFileContent]] = {
+ private def deleteResourceTestRequestsAndResponses: Future[Set[TestDataFileContent]] = {
val resourceIri = "http://rdfh.ch/0001/a-thing"
val lastModificationDate = Instant.parse("2019-12-12T10:23:25.836924Z")
+ val deleteDate = Instant.parse("2020-08-14T10:00:00Z")
FastFuture.successful(
Set(
@@ -566,6 +567,13 @@ class ResourcesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
lastModificationDate = lastModificationDate
)
),
+ TestDataFileContent(
+ filePath = TestDataFilePath.makeJsonPath("delete-resource-with-custom-delete-date-request"),
+ text = SharedTestDataADM.deleteResourceWithCustomDeleteDate(
+ resourceIri = resourceIri,
+ deleteDate = deleteDate
+ )
+ ),
TestDataFileContent(
filePath = TestDataFilePath.makeJsonPath("delete-resource-response"),
text = SharedTestDataADM.successResponse("Resource marked as deleted"))
@@ -630,7 +638,7 @@ class ResourcesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
previewResponse <- getResourcesPreviewTestResponse
graphResponse <- getResourceGraphTestResponse
metadataRequestsAndResponse <- updateResourceMetadataTestRequestsAndResponse
- deleteRequestAndResponse <- deleteResourceTestRequestAndResponse
+ deleteRequestAndResponse <- deleteResourceTestRequestsAndResponses
eraseRequest <- eraseResourceTestRequest
} yield getResponses ++ createRequests ++ metadataRequestsAndResponse ++ deleteRequestAndResponse +
previewResponse + graphResponse + eraseRequest
diff --git a/webapi/src/main/scala/org/knora/webapi/routing/v2/ValuesRouteV2.scala b/webapi/src/main/scala/org/knora/webapi/routing/v2/ValuesRouteV2.scala
index 0282580440..02b8347227 100644
--- a/webapi/src/main/scala/org/knora/webapi/routing/v2/ValuesRouteV2.scala
+++ b/webapi/src/main/scala/org/knora/webapi/routing/v2/ValuesRouteV2.scala
@@ -210,7 +210,7 @@ class ValuesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
text = SharedTestDataADM.createIntValueWithCustomPermissionsRequest(
resourceIri = SharedTestDataADM.AThing.iri,
intValue = 4,
- customPermissions = "CR knora-admin:Creator|V http://rdfh.ch/groups/0001/thing-searcher"
+ permissions = "CR knora-admin:Creator|V http://rdfh.ch/groups/0001/thing-searcher"
)
),
TestDataFileContent(
@@ -381,14 +381,14 @@ class ValuesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
)
),
TestDataFileContent(
- filePath = TestDataFilePath.makeJsonPath("create-link-value-with-custom-Iri-UUID-CreationDate-request"),
- text = SharedTestDataADM.createLinkValueWithCustomIriRequest(
- resourceIri = SharedTestDataADM.AThing.iri,
- targetResourceIri = "http://rdfh.ch/0001/A67ka6UQRHWf313tbhQBjw",
- customValueIri = "http://rdfh.ch/0001/a-thing/values/link-Value-With-IRI",
- customValueUUID = "IN4R19yYR0ygi3K2VEHpUQ",
- customValueCreationDate = Instant.parse("2020-06-04T11:36:54.502951Z")
- )
+ filePath = TestDataFilePath.makeJsonPath("create-link-value-with-custom-Iri-UUID-CreationDate-request"),
+ text = SharedTestDataADM.createLinkValueWithCustomIriRequest(
+ resourceIri = SharedTestDataADM.AThing.iri,
+ targetResourceIri = "http://rdfh.ch/0001/A67ka6UQRHWf313tbhQBjw",
+ valueIri = "http://rdfh.ch/0001/a-thing/values/link-Value-With-IRI",
+ valueUUID = "IN4R19yYR0ygi3K2VEHpUQ",
+ valueCreationDate = Instant.parse("2020-06-04T11:36:54.502951Z")
+ )
)
)
)
@@ -452,6 +452,8 @@ class ValuesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
* Returns JSON-LD requests for updating values in tests of generated client code.
*/
private def updateValueTestRequests: Future[Set[TestDataFileContent]] = {
+ val customValueCreationDate = Instant.parse("2020-08-14T10:00:00Z")
+
FastFuture.successful(
Set(
TestDataFileContent(
@@ -462,13 +464,22 @@ class ValuesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
intValue = 5
)
),
+ TestDataFileContent(
+ filePath = TestDataFilePath.makeJsonPath("update-int-value-request-with-custom-creation-date"),
+ text = SharedTestDataADM.updateIntValueWithCustomCreationDateRequest(
+ resourceIri = SharedTestDataADM.TestDing.iri,
+ valueIri = SharedTestDataADM.TestDing.intValueIri,
+ intValue = 5,
+ valueCreationDate = customValueCreationDate
+ )
+ ),
TestDataFileContent(
filePath = TestDataFilePath.makeJsonPath("update-int-value-with-custom-permissions-request"),
text = SharedTestDataADM.updateIntValueWithCustomPermissionsRequest(
resourceIri = SharedTestDataADM.TestDing.iri,
valueIri = SharedTestDataADM.TestDing.intValueIri,
intValue = 6,
- customPermissions = "CR http://rdfh.ch/groups/0001/thing-searcher"
+ permissions = "CR http://rdfh.ch/groups/0001/thing-searcher"
)
),
TestDataFileContent(
@@ -476,7 +487,7 @@ class ValuesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
text = SharedTestDataADM.updateIntValuePermissionsOnlyRequest(
resourceIri = SharedTestDataADM.TestDing.iri,
valueIri = SharedTestDataADM.TestDing.intValueIri,
- customPermissions = "CR http://rdfh.ch/groups/0001/thing-searcher|V knora-admin:KnownUser"
+ permissions = "CR http://rdfh.ch/groups/0001/thing-searcher|V knora-admin:KnownUser"
)
),
TestDataFileContent(
@@ -697,6 +708,8 @@ class ValuesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
* Returns JSON-LD requests for deleting values in tests of generated client code.
*/
private def deleteValueTestRequests: Future[Set[TestDataFileContent]] = {
+ val deleteDate = Instant.parse("2020-08-14T10:00:00Z")
+
FastFuture.successful(
Set(
TestDataFileContent(
@@ -707,6 +720,14 @@ class ValuesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
maybeDeleteComment = Some("this value was incorrect")
)
),
+ TestDataFileContent(
+ filePath = TestDataFilePath.makeJsonPath("delete-int-value-request-with-custom-delete-date"),
+ text = SharedTestDataADM.deleteIntValueRequest(
+ resourceIri = SharedTestDataADM.TestDing.iri,
+ valueIri = SharedTestDataADM.TestDing.intValueIri,
+ maybeDeleteComment = Some("this value was incorrect")
+ )
+ ),
TestDataFileContent(
filePath = TestDataFilePath.makeJsonPath("delete-link-value-request"),
text = SharedTestDataADM.deleteLinkValueRequest(
diff --git a/webapi/src/main/scala/org/knora/webapi/sharedtestdata/SharedTestDataADM.scala b/webapi/src/main/scala/org/knora/webapi/sharedtestdata/SharedTestDataADM.scala
index b3a942bc9b..8ed7bbb88b 100644
--- a/webapi/src/main/scala/org/knora/webapi/sharedtestdata/SharedTestDataADM.scala
+++ b/webapi/src/main/scala/org/knora/webapi/sharedtestdata/SharedTestDataADM.scala
@@ -747,7 +747,7 @@ object SharedTestDataADM {
| "@type" : "knora-api:IntValue",
| "knora-api:intValueAsInt" : $intValue,
| "knora-api:valueHasUUID" : "$valueUUID",
- | "knora-api:creationDate" : {
+ | "knora-api:valueCreationDate" : {
| "@type" : "xsd:dateTimeStamp",
| "@value" : "$valueCreationDate"
| }
@@ -803,7 +803,7 @@ object SharedTestDataADM {
| "anything:hasInteger" : {
| "@type" : "knora-api:IntValue",
| "knora-api:intValueAsInt" : $intValue,
- | "knora-api:creationDate" : {
+ | "knora-api:valueCreationDate" : {
| "@type" : "xsd:dateTimeStamp",
| "@value" : "$creationDate"
| }
@@ -816,14 +816,14 @@ object SharedTestDataADM {
|}""".stripMargin
}
- def createIntValueWithCustomPermissionsRequest(resourceIri: IRI, intValue: Int, customPermissions: String): String = {
+ def createIntValueWithCustomPermissionsRequest(resourceIri: IRI, intValue: Int, permissions: String): String = {
s"""{
| "@id" : "$resourceIri",
| "@type" : "anything:Thing",
| "anything:hasInteger" : {
| "@type" : "knora-api:IntValue",
| "knora-api:intValueAsInt" : $intValue,
- | "knora-api:hasPermissions" : "$customPermissions"
+ | "knora-api:hasPermissions" : "$permissions"
| },
| "@context" : {
| "knora-api" : "http://api.knora.org/ontology/knora-api/v2#",
@@ -1190,23 +1190,23 @@ object SharedTestDataADM {
def createLinkValueWithCustomIriRequest(resourceIri: IRI,
targetResourceIri: IRI,
- customValueIri: IRI,
- customValueUUID: String,
- customValueCreationDate: Instant): String = {
+ valueIri: IRI,
+ valueUUID: String,
+ valueCreationDate: Instant): String = {
s"""{
| "@id" : "$resourceIri",
| "@type" : "anything:Thing",
| "anything:hasOtherThingValue" : {
- | "@id" : "$customValueIri",
+ | "@id" : "$valueIri",
| "@type" : "knora-api:LinkValue",
| "knora-api:valueHasUUID": "IN4R19yYR0ygi3K2VEHpUQ",
| "knora-api:linkValueHasTargetIri" : {
| "@id" : "$targetResourceIri"
| },
- | "knora-api:creationDate" : {
+ | "knora-api:valueCreationDate" : {
| "@type" : "xsd:dateTimeStamp",
- | "@value" : "$customValueCreationDate"
- | }
+ | "@value" : "$valueCreationDate"
+ | }
| },
| "@context" : {
| "xsd" : "http://www.w3.org/2001/XMLSchema#",
@@ -1235,10 +1235,34 @@ object SharedTestDataADM {
|}""".stripMargin
}
+ def updateIntValueWithCustomCreationDateRequest(resourceIri: IRI,
+ valueIri: IRI,
+ intValue: Int,
+ valueCreationDate: Instant): String = {
+ s"""{
+ | "@id" : "$resourceIri",
+ | "@type" : "anything:Thing",
+ | "anything:hasInteger" : {
+ | "@id" : "$valueIri",
+ | "@type" : "knora-api:IntValue",
+ | "knora-api:intValueAsInt" : $intValue,
+ | "knora-api:valueCreationDate" : {
+ | "@type" : "xsd:dateTimeStamp",
+ | "@value" : "$valueCreationDate"
+ | }
+ | },
+ | "@context" : {
+ | "knora-api" : "http://api.knora.org/ontology/knora-api/v2#",
+ | "anything" : "http://0.0.0.0:3333/ontology/0001/anything/v2#",
+ | "xsd" : "http://www.w3.org/2001/XMLSchema#"
+ | }
+ |}""".stripMargin
+ }
+
def updateIntValueWithCustomPermissionsRequest(resourceIri: IRI,
valueIri: IRI,
intValue: Int,
- customPermissions: String): String = {
+ permissions: String): String = {
s"""{
| "@id" : "$resourceIri",
| "@type" : "anything:Thing",
@@ -1246,7 +1270,7 @@ object SharedTestDataADM {
| "@id" : "$valueIri",
| "@type" : "knora-api:IntValue",
| "knora-api:intValueAsInt" : $intValue,
- | "knora-api:hasPermissions" : "$customPermissions"
+ | "knora-api:hasPermissions" : "$permissions"
| },
| "@context" : {
| "knora-api" : "http://api.knora.org/ontology/knora-api/v2#",
@@ -1257,14 +1281,14 @@ object SharedTestDataADM {
def updateIntValuePermissionsOnlyRequest(resourceIri: IRI,
valueIri: IRI,
- customPermissions: String): String = {
+ permissions: String): String = {
s"""{
| "@id" : "$resourceIri",
| "@type" : "anything:Thing",
| "anything:hasInteger" : {
| "@id" : "$valueIri",
| "@type" : "knora-api:IntValue",
- | "knora-api:hasPermissions" : "$customPermissions"
+ | "knora-api:hasPermissions" : "$permissions"
| },
| "@context" : {
| "knora-api" : "http://api.knora.org/ontology/knora-api/v2#",
@@ -1719,6 +1743,28 @@ object SharedTestDataADM {
}
}
+ def deleteIntValueRequestWithCustomDeleteDate(resourceIri: IRI,
+ valueIri: IRI,
+ deleteDate: Instant): String = {
+ s"""{
+ | "@id" : "$resourceIri",
+ | "@type" : "anything:Thing",
+ | "anything:hasInteger" : {
+ | "@id" : "$valueIri",
+ | "@type" : "knora-api:IntValue",
+ | "knora-api:deleteDate" : {
+ | "@type" : "xsd:dateTimeStamp",
+ | "@value" : "$deleteDate"
+ | }
+ | },
+ | "@context" : {
+ | "xsd" : "http://www.w3.org/2001/XMLSchema#",
+ | "knora-api" : "http://api.knora.org/ontology/knora-api/v2#",
+ | "anything" : "http://0.0.0.0:3333/ontology/0001/anything/v2#"
+ | }
+ |}""".stripMargin
+ }
+
def deleteLinkValueRequest(resourceIri: IRI,
valueIri: IRI): String = {
s"""{
@@ -1920,9 +1966,9 @@ object SharedTestDataADM {
|}""".stripMargin
}
- def createResourceWithCustomIRI(customIRI: IRI): String = {
+ def createResourceWithCustomIRI(iri: IRI): String = {
s"""{
- | "@id" : "$customIRI",
+ | "@id" : "$iri",
| "@type" : "anything:Thing",
| "knora-api:attachedToProject" : {
| "@id" : "http://rdfh.ch/projects/0001"
@@ -1942,14 +1988,14 @@ object SharedTestDataADM {
|}""".stripMargin
}
- def createResourceWithCustomValueIRI(customValueIRI: IRI): String = {
+ def createResourceWithCustomValueIRI(valueIRI: IRI): String = {
s"""{
| "@type" : "anything:Thing",
| "knora-api:attachedToProject" : {
| "@id" : "http://rdfh.ch/projects/0001"
| },
| "anything:hasBoolean" : {
- | "@id" : "$customValueIRI",
+ | "@id" : "$valueIRI",
| "@type" : "knora-api:BooleanValue",
| "knora-api:booleanValueAsBoolean" : true
| },
@@ -1964,7 +2010,7 @@ object SharedTestDataADM {
|}""".stripMargin
}
- def createResourceWithCustomValueUUID(customValueUUID: String): String = {
+ def createResourceWithCustomValueUUID(valueUUID: String): String = {
s"""{
| "@type" : "anything:Thing",
| "knora-api:attachedToProject" : {
@@ -1973,7 +2019,7 @@ object SharedTestDataADM {
| "anything:hasBoolean" : {
| "@type" : "knora-api:BooleanValue",
| "knora-api:booleanValueAsBoolean" : true,
- | "knora-api:valueHasUUID" : "$customValueUUID"
+ | "knora-api:valueHasUUID" : "$valueUUID"
| },
| "rdfs:label" : "test thing",
| "@context" : {
@@ -1995,7 +2041,7 @@ object SharedTestDataADM {
| "anything:hasBoolean" : {
| "@type" : "knora-api:BooleanValue",
| "knora-api:booleanValueAsBoolean" : false,
- | "knora-api:creationDate" : {
+ | "knora-api:valueCreationDate" : {
| "@type" : "xsd:dateTimeStamp",
| "@value" : "$creationDate"
| }
@@ -2012,26 +2058,26 @@ object SharedTestDataADM {
}
- def createResourceWithCustomResourceIriAndCreationDateAndValueWithCustomIRIAndUUID(customResourceIRI: IRI,
- customCreationDate: Instant,
- customValueIRI: IRI,
- customValueUUID: String): String = {
+ def createResourceWithCustomResourceIriAndCreationDateAndValueWithCustomIRIAndUUID(resourceIRI: IRI,
+ creationDate: Instant,
+ valueIRI: IRI,
+ valueUUID: String): String = {
s"""{
- | "@id" : "$customResourceIRI",
+ | "@id" : "$resourceIRI",
| "@type" : "anything:Thing",
| "knora-api:attachedToProject" : {
| "@id" : "http://rdfh.ch/projects/0001"
| },
| "anything:hasBoolean" : {
- | "@id": "$customValueIRI",
+ | "@id": "$valueIRI",
| "@type" : "knora-api:BooleanValue",
| "knora-api:booleanValueAsBoolean" : true,
- | "knora-api:valueHasUUID" : "$customValueUUID"
+ | "knora-api:valueHasUUID" : "$valueUUID"
| },
| "rdfs:label" : "test thing",
| "knora-api:creationDate" : {
| "@type" : "xsd:dateTimeStamp",
- | "@value" : "$customCreationDate"
+ | "@value" : "$creationDate"
| },
| "@context" : {
| "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
@@ -2146,6 +2192,26 @@ object SharedTestDataADM {
|}""".stripMargin
}
+ def deleteResourceWithCustomDeleteDate(resourceIri: IRI,
+ deleteDate: Instant): String = {
+ s"""|{
+ | "@id" : "$resourceIri",
+ | "@type" : "anything:Thing",
+ | "knora-api:deleteComment" : "This resource is too boring.",
+ | "knora-api:deleteDate" : {
+ | "@type" : "xsd:dateTimeStamp",
+ | "@value" : "$deleteDate"
+ | },
+ | "@context" : {
+ | "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+ | "knora-api" : "http://api.knora.org/ontology/knora-api/v2#",
+ | "rdfs" : "http://www.w3.org/2000/01/rdf-schema#",
+ | "xsd" : "http://www.w3.org/2001/XMLSchema#",
+ | "anything" : "http://0.0.0.0:3333/ontology/0001/anything/v2#"
+ | }
+ |}""".stripMargin
+ }
+
def eraseResource(resourceIri: IRI,
lastModificationDate: Instant): String = {
s"""|{
diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/getDeleteDate.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/getDeleteDate.scala.txt
new file mode 100644
index 0000000000..8b81853797
--- /dev/null
+++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/getDeleteDate.scala.txt
@@ -0,0 +1,45 @@
+@*
+ * Copyright © 2015-2019 the contributors (see Contributors.md).
+ *
+ * This file is part of Knora.
+ *
+ * Knora is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Knora is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with Knora. If not, see .
+ *@
+
+@import org.knora.webapi.IRI
+
+@*
+ * Gets the delete date of a resource or value.
+ *
+ * @param triplestore the name of the triplestore being used.
+ * @param entityIri the IRI of the resource or value.
+ *@
+@(triplestore: String,
+ entityIri: IRI)
+
+PREFIX rdf:
+PREFIX rdfs:
+PREFIX owl:
+PREFIX xsd:
+PREFIX knora-base:
+
+SELECT ?deleteDate
+@* Ensure that inference is not used in this query. *@
+@if(triplestore.startsWith("graphdb")) {
+ FROM
+}
+WHERE {
+ BIND(IRI("@entityIri") AS ?entity)
+ ?entity knora-base:deleteDate ?deleteDate .
+}
diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/BUILD.bazel b/webapi/src/test/scala/org/knora/webapi/e2e/v2/BUILD.bazel
index f8129d4ba9..9d5701a32f 100644
--- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/BUILD.bazel
+++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/BUILD.bazel
@@ -89,9 +89,9 @@ scala_test(
)
##
-## //webapi/src/test/scala/org/knora/webapi/e2e/v2:RessourcesRouteV2E2ESpec
+## //webapi/src/test/scala/org/knora/webapi/e2e/v2:ResourcesRouteV2E2ESpec
scala_test(
- name = "RessourcesRouteV2E2ESpec",
+ name = "ResourcesRouteV2E2ESpec",
size = "medium", # 300s
srcs = [
"ResourcesRouteV2E2ESpec.scala",
diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/ResourcesRouteV2E2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/ResourcesRouteV2E2ESpec.scala
index aa72c924c3..974df8acc3 100644
--- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/ResourcesRouteV2E2ESpec.scala
+++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/ResourcesRouteV2E2ESpec.scala
@@ -675,7 +675,7 @@ class ResourcesRouteV2E2ESpec extends E2ESpec(ResourcesRouteV2E2ESpec.config) {
"create a resource with random resource Iri and custom value UUIDs" in {
val customValueUUID = SharedTestDataADM.customValueUUID
- val jsonLDEntity = SharedTestDataADM.createResourceWithCustomValueUUID(customValueUUID = customValueUUID)
+ val jsonLDEntity = SharedTestDataADM.createResourceWithCustomValueUUID(valueUUID = customValueUUID)
val request = Post(s"$baseApiUrl/v2/resources", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLDEntity)) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
val response: HttpResponse = singleAwaitingRequest(request)
@@ -728,10 +728,10 @@ class ResourcesRouteV2E2ESpec extends E2ESpec(ResourcesRouteV2E2ESpec.config) {
val customValueIRI: IRI = SharedTestDataADM.customValueIRI_withResourceIriAndValueIRIAndValueUUID
val customValueUUID = SharedTestDataADM.customValueUUID
val jsonLDEntity = SharedTestDataADM.createResourceWithCustomResourceIriAndCreationDateAndValueWithCustomIRIAndUUID(
- customResourceIRI = customResourceIRI,
- customCreationDate = customCreationDate,
- customValueIRI = customValueIRI,
- customValueUUID = customValueUUID)
+ resourceIRI = customResourceIRI,
+ creationDate = customCreationDate,
+ valueIRI = customValueIRI,
+ valueUUID = customValueUUID)
val request = Post(s"$baseApiUrl/v2/resources", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLDEntity)) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
val response: HttpResponse = singleAwaitingRequest(request)
@@ -887,6 +887,28 @@ class ResourcesRouteV2E2ESpec extends E2ESpec(ResourcesRouteV2E2ESpec.config) {
assert(previewResponse.status == StatusCodes.NotFound, previewResponseAsString)
}
+ "mark a resource as deleted, supplying a custom delete date" in {
+ val resourceIri = "http://rdfh.ch/0001/5IEswyQFQp2bxXDrOyEfEA"
+ val deleteDate = Instant.now
+
+ val jsonLDEntity = SharedTestDataADM.deleteResourceWithCustomDeleteDate(
+ resourceIri = resourceIri,
+ deleteDate = deleteDate
+ )
+
+ val updateRequest = Post(s"$baseApiUrl/v2/resources/delete", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLDEntity)) ~> addCredentials(BasicHttpCredentials(SharedTestDataADM.superUser.email, password))
+ val updateResponse: HttpResponse = singleAwaitingRequest(updateRequest)
+ val updateResponseAsString: String = responseToString(updateResponse)
+ assert(updateResponse.status == StatusCodes.OK, updateResponseAsString)
+ assert(updateResponseAsString == SharedTestDataADM.successResponse("Resource marked as deleted"))
+
+ val previewRequest = Get(s"$baseApiUrl/v2/resourcespreview/${URLEncoder.encode(resourceIri, "UTF-8")}") ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
+ val previewResponse: HttpResponse = singleAwaitingRequest(previewRequest)
+ val previewResponseAsString = responseToString(previewResponse)
+ assert(previewResponse.status == StatusCodes.NotFound, previewResponseAsString)
+ }
+
+
"create a resource with a large text containing a lot of markup (32849 words, 6738 standoff tags)" ignore { // uses too much memory for GitHub CI
// Create a resource containing the text of Hamlet.
diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/ValuesRouteV2E2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/ValuesRouteV2E2ESpec.scala
index 036bebecf4..5429fba7ef 100644
--- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/ValuesRouteV2E2ESpec.scala
+++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/ValuesRouteV2E2ESpec.scala
@@ -54,6 +54,7 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
private val password = "test"
private val intValueIri = new MutableTestIri
+ private val intValueIriWithCustomCreationDate = new MutableTestIri
private val textValueWithoutStandoffIri = new MutableTestIri
private val textValueWithStandoffIri = new MutableTestIri
private val textValueWithEscapeIri = new MutableTestIri
@@ -253,12 +254,11 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
savedIntValue should ===(intValue)
}
- "create an integer value with a custom valueIri" in {
+ "create an integer value with a custom value IRI" in {
val resourceIri: IRI = SharedTestDataADM.AThing.iri
- val propertyIri: SmartIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri
val intValue: Int = 30
- val maybeResourceLastModDate: Option[Instant] = getResourceLastModificationDate(resourceIri, anythingUserEmail)
val customValueIri: IRI = "http://rdfh.ch/0001/a-customized-thing/values/int-with-valueIRI"
+
val jsonLdEntity = SharedTestDataADM.createIntValueWithCustomValueIriRequest(
resourceIri = resourceIri,
intValue = intValue,
@@ -267,14 +267,13 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
val request = Post(baseApiUrl + "/v2/values", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLdEntity)) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
val response: HttpResponse = singleAwaitingRequest(request)
-
assert(response.status == StatusCodes.OK, response.toString)
val responseJsonDoc: JsonLDDocument = responseToJsonLDDocument(response)
val valueIri: IRI = responseJsonDoc.body.requireStringWithValidation(JsonLDConstants.ID, stringFormatter.validateAndEscapeIri)
assert(valueIri == customValueIri)
}
- "return a DuplicateValueException during value creation when the supplied value Iri is not unique" in {
+ "return a DuplicateValueException during value creation when the supplied value IRI is not unique" in {
// duplicate value IRI
val params =
@@ -297,16 +296,14 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
val response: HttpResponse = singleAwaitingRequest(request)
assert(response.status == StatusCodes.BadRequest, response.toString)
- val errorMessage : String = Await.result(Unmarshal(response.entity).to[String], 1.second)
+ val errorMessage: String = Await.result(Unmarshal(response.entity).to[String], 1.second)
val invalidIri: Boolean = errorMessage.contains(s"IRI: 'http://rdfh.ch/0001/a-customized-thing/values/int-with-valueIRI' already exists, try another one.")
invalidIri should be(true)
}
"create an integer value with a custom UUID" in {
val resourceIri: IRI = SharedTestDataADM.AThing.iri
- val propertyIri: SmartIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri
val intValue: Int = 45
- val maybeResourceLastModDate: Option[Instant] = getResourceLastModificationDate(resourceIri, anythingUserEmail)
val customValueUUID = "IN4R19yYR0ygi3K2VEHpUQ"
val jsonLdEntity = SharedTestDataADM.createIntValueWithCustomUUIDRequest(
resourceIri = resourceIri,
@@ -326,54 +323,56 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
"create an integer value with a custom creation date" in {
val customCreationDate: Instant = Instant.parse("2020-06-04T11:36:54.502951Z")
val resourceIri: IRI = SharedTestDataADM.AThing.iri
- val propertyIri: SmartIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri
val intValue: Int = 25
- val maybeResourceLastModDate: Option[Instant] = getResourceLastModificationDate(resourceIri, anythingUserEmail)
val jsonLdEntity = SharedTestDataADM.createIntValueWithCustomCreationDateRequest(resourceIri = resourceIri, intValue = intValue, creationDate = customCreationDate)
val request = Post(baseApiUrl + "/v2/values", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLdEntity)) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
val response: HttpResponse = singleAwaitingRequest(request)
-
assert(response.status == StatusCodes.OK, response.toString)
val responseJsonDoc: JsonLDDocument = responseToJsonLDDocument(response)
+
+ val valueIri: IRI = responseJsonDoc.body.requireStringWithValidation(JsonLDConstants.ID, stringFormatter.validateAndEscapeIri)
+ intValueIriWithCustomCreationDate.set(valueIri)
+
val savedCreationDate: Instant = responseJsonDoc.body.requireDatatypeValueInObject(
key = OntologyConstants.KnoraApiV2Complex.ValueCreationDate,
expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri,
validationFun = stringFormatter.xsdDateTimeStampToInstant
)
+
assert(savedCreationDate == customCreationDate)
}
- "create an integer value with custom Iri, UUID, and creation Date" in {
+ "create an integer value with custom IRI, UUID, and creation date" in {
val resourceIri: IRI = SharedTestDataADM.AThing.iri
- val propertyIri: SmartIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri
val intValue: Int = 10
- val maybeResourceLastModDate: Option[Instant] = getResourceLastModificationDate(resourceIri, anythingUserEmail)
val customValueIri: IRI = "http://rdfh.ch/0001/a-thing/values/int-with-IRI"
val customValueUUID = "IN4R19yYR0ygi3K2VEHpUQ"
val customCreationDate: Instant = Instant.parse("2020-06-04T12:58:54.502951Z")
+
val jsonLdEntity = SharedTestDataADM.createIntValueWithCustomIRIRequest(
- resourceIri = resourceIri,
- intValue = intValue,
- valueIri = customValueIri,
- valueUUID = customValueUUID,
- valueCreationDate = customCreationDate
- )
+ resourceIri = resourceIri,
+ intValue = intValue,
+ valueIri = customValueIri,
+ valueUUID = customValueUUID,
+ valueCreationDate = customCreationDate
+ )
val request = Post(baseApiUrl + "/v2/values", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLdEntity)) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
val response: HttpResponse = singleAwaitingRequest(request)
-
assert(response.status == StatusCodes.OK, response.toString)
val responseJsonDoc: JsonLDDocument = responseToJsonLDDocument(response)
val valueIri: IRI = responseJsonDoc.body.requireStringWithValidation(JsonLDConstants.ID, stringFormatter.validateAndEscapeIri)
assert(valueIri == customValueIri)
val valueUUID = responseJsonDoc.body.requireString(OntologyConstants.KnoraApiV2Complex.ValueHasUUID)
assert(valueUUID == customValueUUID)
+
val savedCreationDate: Instant = responseJsonDoc.body.requireDatatypeValueInObject(
key = OntologyConstants.KnoraApiV2Complex.ValueCreationDate,
expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri,
validationFun = stringFormatter.xsdDateTimeStampToInstant
)
+
assert(savedCreationDate == customCreationDate)
}
@@ -408,7 +407,7 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
val jsonLdEntity = SharedTestDataADM.createIntValueWithCustomPermissionsRequest(
resourceIri = resourceIri,
intValue = intValue,
- customPermissions = customPermissions
+ permissions = customPermissions
)
val request = Post(baseApiUrl + "/v2/values", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLdEntity)) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
@@ -468,7 +467,7 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
val savedValueAsString: String = savedValue.requireString(OntologyConstants.KnoraApiV2Complex.ValueAsString)
savedValueAsString should ===(valueAsString)
}
-
+
"not update a text value without a comment without changing it" in {
val resourceIri: IRI = SharedTestDataADM.AThing.iri
val valueAsString: String = "text without standoff"
@@ -1995,36 +1994,36 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
savedTargetIri should ===(SharedTestDataADM.TestDing.iri)
}
- "create a link between two resources with a custom link value Iri, UUID, creationDate" in {
+ "create a link between two resources with a custom link value IRI, UUID, creationDate" in {
val resourceIri: IRI = SharedTestDataADM.AThing.iri
val targetResourceIri: IRI = "http://rdfh.ch/0001/CNhWoNGGT7iWOrIwxsEqvA"
val customValueIri: IRI = "http://rdfh.ch/0001/a-thing/values/link-Value-With-IRI"
val customValueUUID = "IN4R19yYR0ygi3K2VEHpUQ"
val customCreationDate: Instant = Instant.parse("2020-06-04T11:36:54.502951Z")
- val maybeResourceLastModDate: Option[Instant] = getResourceLastModificationDate(resourceIri, anythingUserEmail)
val jsonLdEntity = SharedTestDataADM.createLinkValueWithCustomIriRequest(
resourceIri = resourceIri,
targetResourceIri = targetResourceIri,
- customValueIri = customValueIri,
- customValueUUID = customValueUUID,
- customValueCreationDate = customCreationDate
+ valueIri = customValueIri,
+ valueUUID = customValueUUID,
+ valueCreationDate = customCreationDate
)
val request = Post(baseApiUrl + "/v2/values", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLdEntity)) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
val response: HttpResponse = singleAwaitingRequest(request)
assert(response.status == StatusCodes.OK, response.toString)
val responseJsonDoc: JsonLDDocument = responseToJsonLDDocument(response)
-
val valueIri: IRI = responseJsonDoc.body.requireStringWithValidation(JsonLDConstants.ID, stringFormatter.validateAndEscapeIri)
assert(valueIri == customValueIri)
val valueUUID: IRI = responseJsonDoc.body.requireString(OntologyConstants.KnoraApiV2Complex.ValueHasUUID)
assert(valueUUID == customValueUUID)
+
val savedCreationDate: Instant = responseJsonDoc.body.requireDatatypeValueInObject(
key = OntologyConstants.KnoraApiV2Complex.ValueCreationDate,
expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri,
validationFun = stringFormatter.xsdDateTimeStampToInstant
)
+
assert(savedCreationDate == customCreationDate)
}
@@ -2050,7 +2049,6 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
valueType should ===(OntologyConstants.KnoraApiV2Complex.IntValue.toSmartIri)
val newIntegerValueUUID: UUID = responseJsonDoc.body.requireStringWithValidation(OntologyConstants.KnoraApiV2Complex.ValueHasUUID, stringFormatter.validateBase64EncodedUuid)
assert(newIntegerValueUUID == integerValueUUID) // The new version should have the same UUID.
- integerValueUUID = newIntegerValueUUID
val savedValue: JsonLDObject = getValue(
resourceIri = resourceIri,
@@ -2065,6 +2063,52 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
intValueAsInt should ===(intValue)
}
+ "update an integer value with a custom creation date" in {
+ val resourceIri: IRI = SharedTestDataADM.AThing.iri
+ val propertyIri: SmartIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri
+ val intValue: Int = 6
+ val maybeResourceLastModDate: Option[Instant] = getResourceLastModificationDate(resourceIri, anythingUserEmail)
+ val valueCreationDate = Instant.now
+
+ val jsonLdEntity = SharedTestDataADM.updateIntValueWithCustomCreationDateRequest(
+ resourceIri = resourceIri,
+ valueIri = intValueIri.get,
+ intValue = intValue,
+ valueCreationDate = valueCreationDate
+ )
+
+ val request = Put(baseApiUrl + "/v2/values", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLdEntity)) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
+ val response: HttpResponse = singleAwaitingRequest(request)
+ assert(response.status == StatusCodes.OK, response.toString)
+ val responseJsonDoc: JsonLDDocument = responseToJsonLDDocument(response)
+ val valueIri: IRI = responseJsonDoc.body.requireStringWithValidation(JsonLDConstants.ID, stringFormatter.validateAndEscapeIri)
+ intValueIri.set(valueIri)
+ val valueType: SmartIri = responseJsonDoc.body.requireStringWithValidation(JsonLDConstants.TYPE, stringFormatter.toSmartIriWithErr)
+ valueType should ===(OntologyConstants.KnoraApiV2Complex.IntValue.toSmartIri)
+ val newIntegerValueUUID: UUID = responseJsonDoc.body.requireStringWithValidation(OntologyConstants.KnoraApiV2Complex.ValueHasUUID, stringFormatter.validateBase64EncodedUuid)
+ assert(newIntegerValueUUID == integerValueUUID) // The new version should have the same UUID.
+
+ val savedValue: JsonLDObject = getValue(
+ resourceIri = resourceIri,
+ maybePreviousLastModDate = maybeResourceLastModDate,
+ propertyIriForGravsearch = propertyIri,
+ propertyIriInResult = propertyIri,
+ expectedValueIri = intValueIri.get,
+ userEmail = anythingUserEmail
+ )
+
+ val intValueAsInt: Int = savedValue.requireInt(OntologyConstants.KnoraApiV2Complex.IntValueAsInt)
+ intValueAsInt should ===(intValue)
+
+ val savedCreationDate: Instant = savedValue.requireDatatypeValueInObject(
+ key = OntologyConstants.KnoraApiV2Complex.ValueCreationDate,
+ expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri,
+ validationFun = stringFormatter.xsdDateTimeStampToInstant
+ )
+
+ savedCreationDate should ===(valueCreationDate)
+ }
+
"not update an integer value if the simple schema is submitted" in {
val resourceIri: IRI = SharedTestDataADM.AThing.iri
val intValue: Int = 10
@@ -2093,7 +2137,7 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
"update an integer value with custom permissions" in {
val resourceIri: IRI = SharedTestDataADM.AThing.iri
val propertyIri: SmartIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri
- val intValue: Int = 6
+ val intValue: Int = 7
val customPermissions: String = "CR http://rdfh.ch/groups/0001/thing-searcher"
val maybeResourceLastModDate: Option[Instant] = getResourceLastModificationDate(resourceIri, anythingUserEmail)
@@ -2101,7 +2145,7 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
resourceIri = resourceIri,
valueIri = intValueIri.get,
intValue = intValue,
- customPermissions = customPermissions
+ permissions = customPermissions
)
val request = Put(baseApiUrl + "/v2/values", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLDEntity)) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
@@ -2137,7 +2181,7 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
val jsonLDEntity = SharedTestDataADM.updateIntValuePermissionsOnlyRequest(
resourceIri = resourceIri,
valueIri = intValueIri.get,
- customPermissions = customPermissions
+ permissions = customPermissions
)
val request = Put(baseApiUrl + "/v2/values", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLDEntity)) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
@@ -3127,6 +3171,20 @@ class ValuesRouteV2E2ESpec extends E2ESpec {
assert(response.status == StatusCodes.OK, response.toString)
}
+ "delete an integer value, supplying a custom delete date" in {
+ val deleteDate = Instant.now
+
+ val jsonLdEntity = SharedTestDataADM.deleteIntValueRequestWithCustomDeleteDate(
+ resourceIri = SharedTestDataADM.AThing.iri,
+ valueIri = intValueIriWithCustomCreationDate.get,
+ deleteDate = deleteDate
+ )
+
+ val request = Post(baseApiUrl + "/v2/values/delete", HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLdEntity)) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password))
+ val response: HttpResponse = singleAwaitingRequest(request)
+ assert(response.status == StatusCodes.OK, response.toString)
+ }
+
"not delete an integer value if the simple schema is submitted" in {
val jsonLdEntity =
s"""{
diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2Spec.scala
index ea479790df..8c800a9c58 100644
--- a/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2Spec.scala
+++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2Spec.scala
@@ -20,6 +20,7 @@
package org.knora.webapi.responders.v2
import java.time.Instant
+import java.time.temporal.ChronoUnit
import java.util.UUID
import akka.actor.{ActorRef, Props}
@@ -525,6 +526,25 @@ class ResourcesResponderV2Spec extends CoreSpec() with ImplicitSender {
}
}
+ private def getDeleteDate(resourceIri: IRI): Instant = {
+ val sparqlQuery: String = org.knora.webapi.messages.twirl.queries.sparql.v2.txt.getDeleteDate(
+ triplestore = settings.triplestoreType,
+ entityIri = resourceIri
+ ).toString()
+
+ storeManager ! SparqlSelectRequest(sparqlQuery)
+
+ expectMsgPF(timeout) {
+ case sparqlSelectResponse: SparqlSelectResponse =>
+ val savedDeleteDateStr = sparqlSelectResponse.getFirstRow.rowMap("deleteDate")
+
+ stringFormatter.xsdDateTimeStampToInstant(
+ savedDeleteDateStr,
+ throw AssertionException(s"Couldn't parse delete date from triplestore: $savedDeleteDateStr")
+ )
+ }
+ }
+
// The default timeout for receiving reply messages from actors.
private val timeout = 10.seconds
@@ -1740,6 +1760,26 @@ class ResourcesResponderV2Spec extends CoreSpec() with ImplicitSender {
aThingLastModificationDate = updatedLastModificationDate
}
+ "not mark a resource as deleted with a custom delete date that is earlier than the resource's last modification date" in {
+ val deleteDate: Instant = aThingLastModificationDate.minus(1, ChronoUnit.DAYS)
+
+ val deleteRequest = DeleteOrEraseResourceRequestV2(
+ resourceIri = aThingIri,
+ resourceClassIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#Thing".toSmartIri,
+ maybeDeleteComment = Some("This resource is too boring."),
+ maybeDeleteDate = Some(deleteDate),
+ maybeLastModificationDate = Some(aThingLastModificationDate),
+ requestingUser = SharedTestDataADM.anythingUser1,
+ apiRequestID = UUID.randomUUID
+ )
+
+ responderManager ! deleteRequest
+
+ expectMsgPF(timeout) {
+ case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[BadRequestException] should ===(true)
+ }
+ }
+
"mark a resource as deleted" in {
val deleteRequest = DeleteOrEraseResourceRequestV2(
resourceIri = aThingIri,
@@ -1763,6 +1803,36 @@ class ResourcesResponderV2Spec extends CoreSpec() with ImplicitSender {
}
}
+ "mark a resource as deleted, supplying a custom delete date" in {
+ val resourceIri = "http://rdfh.ch/0001/5IEswyQFQp2bxXDrOyEfEA"
+ val deleteDate: Instant = Instant.now
+
+ val deleteRequest = DeleteOrEraseResourceRequestV2(
+ resourceIri = resourceIri,
+ resourceClassIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#Thing".toSmartIri,
+ maybeDeleteComment = Some("This resource is too boring."),
+ maybeDeleteDate = Some(deleteDate),
+ maybeLastModificationDate = None,
+ requestingUser = SharedTestDataADM.superUser,
+ apiRequestID = UUID.randomUUID
+ )
+
+ responderManager ! deleteRequest
+
+ expectMsgType[SuccessResponseV2](timeout)
+
+ // We should now be unable to request the resource.
+
+ responderManager ! ResourcesGetRequestV2(resourceIris = Seq(resourceIri), targetSchema = ApiV2Complex, requestingUser = SharedTestDataADM.anythingUser1)
+
+ expectMsgPF(timeout) {
+ case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[NotFoundException] should ===(true)
+ }
+
+ val savedDeleteDate: Instant = getDeleteDate(resourceIri)
+ assert(savedDeleteDate == deleteDate)
+ }
+
"not accept custom resource permissions that would give the requesting user a higher permission on a resource than the default" in {
val resourceIri: IRI = stringFormatter.makeRandomResourceIri(SharedTestDataADM.imagesProject.shortcode)
diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala
index e1c2703a3f..c568cfb8d5 100644
--- a/webapi/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala
+++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala
@@ -77,6 +77,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender {
private val firstIntValueVersionIri = new MutableTestIri
private val intValueIri = new MutableTestIri
private val intValueIriWithCustomPermissions = new MutableTestIri
+ private val intValueIriWithCustomUuidAndTimestamp = new MutableTestIri
private val zeitglöckleinCommentWithoutStandoffIri = new MutableTestIri
private val zeitglöckleinCommentWithStandoffIri = new MutableTestIri
private val zeitglöckleinCommentWithCommentIri = new MutableTestIri
@@ -203,6 +204,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender {
propertyIriForGravsearch: SmartIri,
propertyIriInResult: SmartIri,
valueIri: IRI,
+ customDeleteDate: Option[Instant] = None,
requestingUser: UserADM): Unit = {
val resource = getResourceWithValues(
resourceIri = resourceIri,
@@ -222,6 +224,31 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender {
case Some(_) => throw AssertionException(s"Value <$valueIri was not deleted>")
case None => ()
}
+
+ // If a custom delete date was used, check that it was saved correctly.
+ customDeleteDate match {
+ case Some(deleteDate) =>
+ val sparqlQuery: String = org.knora.webapi.messages.twirl.queries.sparql.v2.txt.getDeleteDate(
+ triplestore = settings.triplestoreType,
+ entityIri = valueIri
+ ).toString()
+
+ storeManager ! SparqlSelectRequest(sparqlQuery)
+
+ expectMsgPF(timeout) {
+ case sparqlSelectResponse: SparqlSelectResponse =>
+ val savedDeleteDateStr = sparqlSelectResponse.getFirstRow.rowMap("deleteDate")
+
+ val savedDeleteDate: Instant = stringFormatter.xsdDateTimeStampToInstant(
+ savedDeleteDateStr,
+ throw AssertionException(s"Couldn't parse delete date from triplestore: $savedDeleteDateStr")
+ )
+
+ assert(savedDeleteDate == deleteDate)
+ }
+
+ case None => ()
+ }
}
private def checkLastModDate(resourceIri: IRI, maybePreviousLastModDate: Option[Instant], maybeUpdatedLastModDate: Option[Instant]): Unit = {
@@ -445,7 +472,6 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender {
case updateValueResponse: UpdateValueResponseV2 =>
intValueIri.set(updateValueResponse.valueIri)
assert(updateValueResponse.valueUUID == integerValueUUID)
- integerValueUUID = updateValueResponse.valueUUID
}
// Read the value back to check that it was added correctly.
@@ -809,6 +835,146 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender {
}
}
+ "create an integer value with custom UUID and creation date" in {
+ // Add the value.
+
+ val resourceIri: IRI = aThingIri
+ val propertyIri: SmartIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri
+ val intValue = 987
+ val valueUUID = UUID.randomUUID
+ val valueCreationDate = Instant.now
+ val maybeResourceLastModDate: Option[Instant] = getResourceLastModificationDate(resourceIri, anythingUser1)
+
+ responderManager ! CreateValueRequestV2(
+ CreateValueV2(
+ resourceIri = resourceIri,
+ resourceClassIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#Thing".toSmartIri,
+ propertyIri = propertyIri,
+ valueContent = IntegerValueContentV2(
+ ontologySchema = ApiV2Complex,
+ valueHasInteger = intValue
+ ),
+ valueUUID = Some(valueUUID),
+ valueCreationDate = Some(valueCreationDate)
+ ),
+ requestingUser = anythingUser1,
+ apiRequestID = UUID.randomUUID
+ )
+
+ expectMsgPF(timeout) {
+ case createValueResponse: CreateValueResponseV2 => intValueIriWithCustomUuidAndTimestamp.set(createValueResponse.valueIri)
+ }
+
+ // Read the value back to check that it was added correctly.
+
+ val valueFromTriplestore = getValue(
+ resourceIri = resourceIri,
+ maybePreviousLastModDate = maybeResourceLastModDate,
+ propertyIriForGravsearch = propertyIri,
+ propertyIriInResult = propertyIri,
+ expectedValueIri = intValueIriWithCustomUuidAndTimestamp.get,
+ requestingUser = anythingUser1
+ )
+
+ valueFromTriplestore.valueContent match {
+ case savedValue: IntegerValueContentV2 =>
+ savedValue.valueHasInteger should ===(intValue)
+ valueFromTriplestore.valueHasUUID should ===(valueUUID)
+ valueFromTriplestore.valueCreationDate should ===(valueCreationDate)
+
+ case _ => throw AssertionException(s"Expected integer value, got $valueFromTriplestore")
+ }
+ }
+
+ "not update an integer value with a custom creation date that is earlier than the date of the current version" in {
+ val resourceIri: IRI = aThingIri
+ val propertyIri: SmartIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri
+ val intValue = 989
+ val valueCreationDate = Instant.parse("2019-11-29T10:00:00Z")
+
+ responderManager ! UpdateValueRequestV2(
+ UpdateValueContentV2(
+ resourceIri = resourceIri,
+ resourceClassIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#Thing".toSmartIri,
+ propertyIri = propertyIri,
+ valueIri = intValueIriWithCustomUuidAndTimestamp.get,
+ valueContent = IntegerValueContentV2(
+ ontologySchema = ApiV2Complex,
+ valueHasInteger = intValue
+ ),
+ valueCreationDate = Some(valueCreationDate)
+ ),
+ requestingUser = anythingUser1,
+ apiRequestID = UUID.randomUUID
+ )
+
+ expectMsgPF(timeout) {
+ case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[BadRequestException])
+ }
+ }
+
+ "update an integer value with a custom creation date" in {
+ val resourceIri: IRI = aThingIri
+ val propertyIri: SmartIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri
+ val maybeResourceLastModDate: Option[Instant] = getResourceLastModificationDate(resourceIri, anythingUser1)
+
+ // Get the value before update.
+ val previousValueFromTriplestore: ReadValueV2 = getValue(
+ resourceIri = resourceIri,
+ maybePreviousLastModDate = maybeResourceLastModDate,
+ propertyIriForGravsearch = propertyIri,
+ propertyIriInResult = propertyIri,
+ expectedValueIri = intValueIriWithCustomUuidAndTimestamp.get,
+ requestingUser = anythingUser1,
+ checkLastModDateChanged = false
+ )
+
+ // Update the value.
+
+ val intValue = 988
+ val valueCreationDate = Instant.now
+
+ responderManager ! UpdateValueRequestV2(
+ UpdateValueContentV2(
+ resourceIri = resourceIri,
+ resourceClassIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#Thing".toSmartIri,
+ propertyIri = propertyIri,
+ valueIri = intValueIriWithCustomUuidAndTimestamp.get,
+ valueContent = IntegerValueContentV2(
+ ontologySchema = ApiV2Complex,
+ valueHasInteger = intValue
+ ),
+ valueCreationDate = Some(valueCreationDate)
+ ),
+ requestingUser = anythingUser1,
+ apiRequestID = UUID.randomUUID
+ )
+
+ expectMsgPF(timeout) {
+ case updateValueResponse: UpdateValueResponseV2 =>
+ intValueIriWithCustomUuidAndTimestamp.set(updateValueResponse.valueIri)
+ }
+
+ // Read the value back to check that it was added correctly.
+
+ val updatedValueFromTriplestore = getValue(
+ resourceIri = resourceIri,
+ maybePreviousLastModDate = maybeResourceLastModDate,
+ propertyIriForGravsearch = propertyIri,
+ propertyIriInResult = propertyIri,
+ expectedValueIri = intValueIriWithCustomUuidAndTimestamp.get,
+ requestingUser = anythingUser1
+ )
+
+ updatedValueFromTriplestore.valueContent match {
+ case savedValue: IntegerValueContentV2 =>
+ savedValue.valueHasInteger should ===(intValue)
+ updatedValueFromTriplestore.valueCreationDate should ===(valueCreationDate)
+
+ case _ => throw AssertionException(s"Expected integer value, got $updatedValueFromTriplestore")
+ }
+ }
+
"not create a value if the user does not have modify permission on the resource" in {
val resourceIri: IRI = aThingIri
val propertyIri: SmartIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri
@@ -3989,6 +4155,37 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender {
requestingUser = anythingUser1)
}
+ "delete an integer value, specifying a custom delete date" in {
+ val resourceIri: IRI = aThingIri
+ val propertyIri: SmartIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri
+ val maybeResourceLastModDate: Option[Instant] = getResourceLastModificationDate(resourceIri, anythingUser1)
+ val deleteDate: Instant = Instant.now
+
+ responderManager ! DeleteValueRequestV2(
+ resourceIri = resourceIri,
+ resourceClassIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#Thing".toSmartIri,
+ propertyIri = propertyIri,
+ valueIri = intValueIriWithCustomUuidAndTimestamp.get,
+ valueTypeIri = OntologyConstants.KnoraApiV2Complex.IntValue.toSmartIri,
+ deleteComment = Some("this value was incorrect"),
+ deleteDate = Some(deleteDate),
+ requestingUser = anythingUser1,
+ apiRequestID = UUID.randomUUID
+ )
+
+ expectMsgType[SuccessResponseV2](timeout)
+
+ checkValueIsDeleted(
+ resourceIri = resourceIri,
+ maybePreviousLastModDate = maybeResourceLastModDate,
+ propertyIriForGravsearch = propertyIri,
+ propertyIriInResult = propertyIri,
+ valueIri = intValueIriWithCustomUuidAndTimestamp.get,
+ customDeleteDate = Some(deleteDate),
+ requestingUser = anythingUser1
+ )
+ }
+
"not delete a standoff link directly" in {
responderManager ! DeleteValueRequestV2(
resourceIri = zeitglöckleinIri,
@@ -4022,12 +4219,14 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender {
expectMsgType[SuccessResponseV2](timeout)
- checkValueIsDeleted(resourceIri = zeitglöckleinIri,
+ checkValueIsDeleted(
+ resourceIri = zeitglöckleinIri,
maybePreviousLastModDate = maybeResourceLastModDate,
propertyIriForGravsearch = propertyIri,
propertyIriInResult = propertyIri,
valueIri = zeitglöckleinCommentWithStandoffIri.get,
- requestingUser = incunabulaUser)
+ requestingUser = incunabulaUser
+ )
// There should be no standoff link values left in the resource.
@@ -4058,12 +4257,14 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender {
expectMsgType[SuccessResponseV2](timeout)
- checkValueIsDeleted(resourceIri = resourceIri,
+ checkValueIsDeleted(
+ resourceIri = resourceIri,
maybePreviousLastModDate = maybeResourceLastModDate,
propertyIriForGravsearch = linkPropertyIri,
propertyIriInResult = linkValuePropertyIri,
valueIri = linkValueIri.get,
- requestingUser = anythingUser1)
+ requestingUser = anythingUser1
+ )
}
"not delete a value if the property's cardinality doesn't allow it" in {