From 985c5fd45dcf2b1c9d4a6d47040165a56b7dfe33 Mon Sep 17 00:00:00 2001 From: irinaschubert Date: Tue, 26 Apr 2022 13:17:02 +0200 Subject: [PATCH] feat(ontology): allow deleting comments of properties (DEV-696) (#2042) * add method and test to delete property comment * add route to delete property comment * allow deleting comments for properties * improve code * fix failing test * add more tests * wip * add test for link property * add route * add e2e test * remove print statements --- docs/03-apis/api-v2/ontology-information.md | 16 ++ test_data/ontologies/freetest-onto.ttl | 54 +++++ .../ontologymessages/OntologyMessagesV2.scala | 74 ++++++ .../responders/v2/OntologyResponderV2.scala | 228 ++++++++++++++++++ .../webapi/routing/v2/OntologiesRouteV2.scala | 47 ++++ .../sparql/v2/deletePropertyComment.scala.txt | 59 +++++ .../webapi/e2e/v2/OntologyV2R2RSpec.scala | 78 ++++++ .../v2/OntologyResponderV2Spec.scala | 101 ++++++++ 8 files changed, 657 insertions(+) create mode 100644 webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/deletePropertyComment.scala.txt diff --git a/docs/03-apis/api-v2/ontology-information.md b/docs/03-apis/api-v2/ontology-information.md index d74ff7ef1b..c7b251b67a 100644 --- a/docs/03-apis/api-v2/ontology-information.md +++ b/docs/03-apis/api-v2/ontology-information.md @@ -1516,6 +1516,22 @@ HTTP PUT to http://host/v2/ontologies/properties Values for `rdfs:comment` must be submitted in at least one language, either as an object or as an array of objects. +### Deleting the Comments of a Property + +This operation is permitted even if the property is used in data. + +``` +HTTP DELETE to http://host/v2/ontologies/properties/comment/PROPERTY_IRI?lastModificationDate=ONTOLOGY_LAST_MODIFICATION_DATE +``` + +The property IRI and the ontology's last modification date must be URL-encoded. + +All values i.e. all languages for `rdfs:comment` are deleted. + +If the property is a link property, the `rdfs:comment` of its corresponding link value property will automatically be deleted. + +A successful response will be a JSON-LD document providing the property definition. + ### Changing the GUI Element and GUI Attributes of a Property This operation is permitted even if the property is used in data. diff --git a/test_data/ontologies/freetest-onto.ttl b/test_data/ontologies/freetest-onto.ttl index 5d3a8d9f4f..92ff1551d6 100644 --- a/test_data/ontologies/freetest-onto.ttl +++ b/test_data/ontologies/freetest-onto.ttl @@ -322,3 +322,57 @@ salsah-gui:guiElement salsah-gui:SimpleText ; salsah-gui:guiAttribute "size=80", "maxlength=255" . + +:hasPropertyWithComment + rdf:type owl:ObjectProperty ; + rdfs:subPropertyOf knora-base:hasValue ; + rdfs:label "Property mit einem Kommentar"@de, + "Property with a comment"@en ; + rdfs:comment "Dies ist der Kommentar"@de, + "This is the comment"@en ; + knora-base:objectClassConstraint knora-base:TextValue ; + salsah-gui:guiElement salsah-gui:SimpleText ; + salsah-gui:guiAttribute "size=80", + "maxlength=255" . + +:hasPropertyWithoutComment + rdf:type owl:ObjectProperty ; + rdfs:subPropertyOf knora-base:hasValue ; + rdfs:label "Property ohne Kommentar"@de, + "Property without a comment"@en ; + knora-base:objectClassConstraint knora-base:TextValue ; + salsah-gui:guiElement salsah-gui:SimpleText ; + salsah-gui:guiAttribute "size=80", + "maxlength=255" . + +:hasPropertyWithComment2 + rdf:type owl:ObjectProperty ; + rdfs:subPropertyOf knora-base:hasValue ; + rdfs:label "Property mit einem Kommentar"@de, + "Property with a comment"@en ; + rdfs:comment "Dies ist der Kommentar"@de, + "This is the comment"@en ; + knora-base:objectClassConstraint knora-base:TextValue ; + salsah-gui:guiElement salsah-gui:SimpleText . + +:hasLinkPropertyWithComment + rdf:type owl:ObjectProperty ; + rdfs:subPropertyOf knora-base:hasLinkTo ; + rdfs:label "Link Property mit Kommentar"@de, + "Link property with comment"@en; + rdfs:comment "Dies ist der Kommentar"@de, + "This is the comment"@en ; + knora-base:subjectClassConstraint :Book ; + knora-base:objectClassConstraint :Author ; + salsah-gui:guiElement salsah-gui:Searchbox . + +:hasLinkPropertyWithCommentValue + rdf:type owl:ObjectProperty ; + rdfs:subPropertyOf knora-base:hasLinkToValue ; + rdfs:label "Link Property mit Kommentar"@de, + "Link property with comment"@en; + rdfs:comment "Dies ist der Kommentar"@de, + "This is the comment"@en ; + knora-base:subjectClassConstraint :Book ; + knora-base:objectClassConstraint knora-base:LinkValue ; + salsah-gui:guiElement salsah-gui:Searchbox . diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala index 3900fe0e17..52fd95636d 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala @@ -1151,6 +1151,80 @@ object ChangePropertyLabelsOrCommentsRequestV2 } } +/** + * Deletes the comment from a property. A successful response will be a [[ReadOntologyV2]]. + * + * @param propertyIri the IRI of the property. + * @param lastModificationDate the ontology's last modification date + * @param apiRequestID the ID of the API request. + * @param requestingUser the user making the request. + */ +case class DeletePropertyCommentRequestV2( + propertyIri: SmartIri, + lastModificationDate: Instant, + apiRequestID: UUID, + featureFactoryConfig: FeatureFactoryConfig, + requestingUser: UserADM +) extends OntologiesResponderRequestV2 + +/** + * Constructs instances of [[DeletePropertyCommentRequestV2]] based on JSON-LD input. + */ +object DeletePropertyCommentRequestV2 extends KnoraJsonLDRequestReaderV2[DeletePropertyCommentRequestV2] { + + /** + * Converts a JSON-LD request to a [[DeletePropertyCommentRequestV2]]. + * + * @param jsonLDDocument the JSON-LD input. + * @param apiRequestID the UUID of the API request. + * @param requestingUser the user making the request. + * @param responderManager a reference to the responder manager. + * @param storeManager a reference to the store manager. + * @param featureFactoryConfig the feature factory configuration. + * @param settings the application settings. + * @param log a logging adapter. + * @return a [[DeletePropertyCommentRequestV2]] representing the input. + */ + override def fromJsonLD( + jsonLDDocument: JsonLDDocument, + apiRequestID: UUID, + requestingUser: UserADM, + responderManager: ActorRef, + storeManager: ActorRef, + featureFactoryConfig: FeatureFactoryConfig, + settings: KnoraSettingsImpl, + log: LoggingAdapter + )(implicit timeout: Timeout, executionContext: ExecutionContext): Future[DeletePropertyCommentRequestV2] = + Future { + fromJsonLDSync( + jsonLDDocument = jsonLDDocument, + apiRequestID = apiRequestID, + featureFactoryConfig = featureFactoryConfig, + requestingUser = requestingUser + ) + } + + private def fromJsonLDSync( + jsonLDDocument: JsonLDDocument, + apiRequestID: UUID, + featureFactoryConfig: FeatureFactoryConfig, + requestingUser: UserADM + ): DeletePropertyCommentRequestV2 = { + val inputOntologyV2 = InputOntologyV2.fromJsonLD(jsonLDDocument) + val propertyUpdateInfo = OntologyUpdateHelper.getPropertyDef(inputOntologyV2) + val propertyInfoContent = propertyUpdateInfo.propertyInfoContent + val lastModificationDate = propertyUpdateInfo.lastModificationDate + + DeletePropertyCommentRequestV2( + propertyIri = propertyInfoContent.propertyIri, + lastModificationDate = lastModificationDate, + apiRequestID = apiRequestID, + featureFactoryConfig = featureFactoryConfig, + requestingUser = requestingUser + ) + } +} + /** * Requests that a class's labels or comments are changed. A successful response will be a [[ReadOntologyV2]]. * diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala index 4aa4e78b49..1d54bdf356 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala @@ -112,6 +112,8 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon case createPropertyRequest: CreatePropertyRequestV2 => createProperty(createPropertyRequest) case changePropertyLabelsOrCommentsRequest: ChangePropertyLabelsOrCommentsRequestV2 => changePropertyLabelsOrComments(changePropertyLabelsOrCommentsRequest) + case deletePropertyCommentRequest: DeletePropertyCommentRequestV2 => + deletePropertyComment(deletePropertyCommentRequest) case changePropertyGuiElementRequest: ChangePropertyGuiElementRequest => changePropertyGuiElement(changePropertyGuiElementRequest) case canDeletePropertyRequest: CanDeletePropertyRequestV2 => canDeleteProperty(canDeletePropertyRequest) @@ -3269,4 +3271,230 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon } yield taskResult } + /** + * Delete the `rdfs:comment` in a property definition. + * + * @param deletePropertyCommentRequestV2 the request to delete the property's comment + * @return a [[ReadOntologyV2]] containing the modified property definition. + */ + private def deletePropertyComment( + deletePropertyCommentRequest: DeletePropertyCommentRequestV2 + ): Future[ReadOntologyV2] = { + def makeTaskFuture( + cacheData: Cache.OntologyCacheData, + internalPropertyIri: SmartIri, + internalOntologyIri: SmartIri, + ontology: ReadOntologyV2, + propertyToUpdate: ReadPropertyInfoV2 + ): Future[ReadOntologyV2] = + for { + + // Check that the ontology exists and has not been updated by another user since the client last read its metadata. + _ <- OntologyHelpers.checkOntologyLastModificationDateBeforeUpdate( + settings, + storeManager, + internalOntologyIri = internalOntologyIri, + expectedLastModificationDate = deletePropertyCommentRequest.lastModificationDate, + featureFactoryConfig = deletePropertyCommentRequest.featureFactoryConfig + ) + + // If this is a link property, also delete the comment of the corresponding link value property. + maybeLinkValueOfPropertyToUpdate: Option[ReadPropertyInfoV2] = + if (propertyToUpdate.isLinkProp) { + val linkValuePropertyIri: SmartIri = internalPropertyIri.fromLinkPropToLinkValueProp + Some( + ontology.properties.getOrElse( + linkValuePropertyIri, + throw InconsistentRepositoryDataException( + s"Link value property $linkValuePropertyIri not found" + ) + ) + ) + } else { + None + } + + maybeLinkValueOfPropertyToUpdateIri: Option[SmartIri] = + if (propertyToUpdate.isLinkProp) { + Some(internalPropertyIri.fromLinkPropToLinkValueProp) + } else { + None + } + + currentTime: Instant = Instant.now + + // Delete the comment + updateSparql: String = org.knora.webapi.messages.twirl.queries.sparql.v2.txt + .deletePropertyComment( + ontologyNamedGraphIri = internalOntologyIri, + ontologyIri = internalOntologyIri, + propertyIri = internalPropertyIri, + maybeLinkValuePropertyIri = maybeLinkValueOfPropertyToUpdateIri, + lastModificationDate = deletePropertyCommentRequest.lastModificationDate, + currentTime = currentTime + ) + .toString() + + _ <- (storeManager ? SparqlUpdateRequest(updateSparql)).mapTo[SparqlUpdateResponse] + + // Check that the ontology's last modification date was updated. + _ <- OntologyHelpers.checkOntologyLastModificationDateAfterUpdate( + settings = settings, + storeManager = storeManager, + internalOntologyIri = internalOntologyIri, + expectedLastModificationDate = currentTime, + featureFactoryConfig = deletePropertyCommentRequest.featureFactoryConfig + ) + + // Check that the update was successful. + loadedPropertyDef: PropertyInfoContentV2 <- OntologyHelpers.loadPropertyDefinition( + settings, + storeManager, + propertyIri = internalPropertyIri, + featureFactoryConfig = deletePropertyCommentRequest.featureFactoryConfig + ) + + propertyDefWithoutComment: PropertyInfoContentV2 = + propertyToUpdate.entityInfoContent.copy( + predicates = propertyToUpdate.entityInfoContent.predicates.-( + OntologyConstants.Rdfs.Comment.toSmartIri + ) // the "-" deletes the entry with the comment + ) + + _ = if (loadedPropertyDef != propertyDefWithoutComment) { + throw InconsistentRepositoryDataException( + s"Attempted to save property definition $propertyDefWithoutComment, but $loadedPropertyDef was saved" + ) + } + + maybeLoadedLinkValuePropertyDefFuture: Option[Future[PropertyInfoContentV2]] = + maybeLinkValueOfPropertyToUpdate.map { linkValueReadPropertyInfo: ReadPropertyInfoV2 => + OntologyHelpers.loadPropertyDefinition( + settings, + storeManager, + propertyIri = linkValueReadPropertyInfo.entityInfoContent.propertyIri, + featureFactoryConfig = deletePropertyCommentRequest.featureFactoryConfig + ) + } + + maybeLoadedLinkValuePropertyDef: Option[PropertyInfoContentV2] <- + ActorUtil.optionFuture2FutureOption(maybeLoadedLinkValuePropertyDefFuture) + + maybeNewLinkValuePropertyDef: Option[PropertyInfoContentV2] = + maybeLoadedLinkValuePropertyDef.map { loadedLinkValuePropertyDef: PropertyInfoContentV2 => + val newLinkPropertyDef: PropertyInfoContentV2 = + maybeLinkValueOfPropertyToUpdate.get.entityInfoContent.copy( + predicates = maybeLinkValueOfPropertyToUpdate.get.entityInfoContent.predicates + .-(OntologyConstants.Rdfs.Comment.toSmartIri) + ) + + if (loadedLinkValuePropertyDef != newLinkPropertyDef) { + throw InconsistentRepositoryDataException( + s"Attempted to save link value property definition $newLinkPropertyDef, but $loadedLinkValuePropertyDef was saved" + ) + } + + newLinkPropertyDef + } + + // Update the ontology cache using the new property definition. + newReadPropertyInfo: ReadPropertyInfoV2 = ReadPropertyInfoV2( + entityInfoContent = loadedPropertyDef, + isEditable = true, + isResourceProp = true, + isLinkProp = propertyToUpdate.isLinkProp + ) + + maybeLinkValuePropertyCacheEntry: Option[(SmartIri, ReadPropertyInfoV2)] = + maybeNewLinkValuePropertyDef.map { newLinkPropertyDef: PropertyInfoContentV2 => + newLinkPropertyDef.propertyIri -> ReadPropertyInfoV2( + entityInfoContent = newLinkPropertyDef, + isResourceProp = true, + isLinkValueProp = true + ) + } + + updatedOntologyMetadata: OntologyMetadataV2 = ontology.ontologyMetadata.copy( + lastModificationDate = Some(currentTime) + ) + + updatedOntology: ReadOntologyV2 = + ontology.copy( + ontologyMetadata = updatedOntologyMetadata, + properties = + ontology.properties ++ maybeLinkValuePropertyCacheEntry + (internalPropertyIri -> newReadPropertyInfo) + ) + + _ = Cache.storeCacheData( + cacheData.copy( + ontologies = cacheData.ontologies + (internalOntologyIri -> updatedOntology) + ) + ) + + // Read the data back from the cache. + + response: ReadOntologyV2 <- getPropertyDefinitionsFromOntologyV2( + propertyIris = Set(internalPropertyIri), + allLanguages = true, + requestingUser = deletePropertyCommentRequest.requestingUser + ) + + } yield response + + for { + requestingUser: UserADM <- FastFuture.successful(deletePropertyCommentRequest.requestingUser) + + externalPropertyIri: SmartIri = deletePropertyCommentRequest.propertyIri + externalOntologyIri: SmartIri = externalPropertyIri.getOntologyFromEntity + + _ <- OntologyHelpers.checkOntologyAndEntityIrisForUpdate( + externalOntologyIri = externalOntologyIri, + externalEntityIri = externalPropertyIri, + requestingUser = requestingUser + ) + + internalPropertyIri: SmartIri = externalPropertyIri.toOntologySchema(InternalSchema) + internalOntologyIri: SmartIri = externalOntologyIri.toOntologySchema(InternalSchema) + + cacheData: Cache.OntologyCacheData <- Cache.getCacheData + + ontology: ReadOntologyV2 = cacheData.ontologies(internalOntologyIri) + + propertyToUpdate: ReadPropertyInfoV2 = + ontology.properties.getOrElse( + internalPropertyIri, + throw NotFoundException(s"Property ${deletePropertyCommentRequest.propertyIri} not found") + ) + + hasComment: Boolean = propertyToUpdate.entityInfoContent.predicates.contains( + OntologyConstants.Rdfs.Comment.toSmartIri + ) + + taskResult: ReadOntologyV2 <- + if (hasComment) for { + // Do the remaining pre-update checks and the update while holding a global ontology cache lock. + taskResult: ReadOntologyV2 <- IriLocker.runWithIriLock( + apiRequestID = deletePropertyCommentRequest.apiRequestID, + iri = ONTOLOGY_CACHE_LOCK_IRI, + task = () => + makeTaskFuture( + cacheData = cacheData, + internalPropertyIri = internalPropertyIri, + internalOntologyIri = internalOntologyIri, + ontology = ontology, + propertyToUpdate = propertyToUpdate + ) + ) + } yield taskResult + else { + // not change anything if property has no comment + getPropertyDefinitionsFromOntologyV2( + propertyIris = Set(internalPropertyIri), + allLanguages = true, + requestingUser = deletePropertyCommentRequest.requestingUser + ) + } + } yield taskResult + } + } diff --git a/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala b/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala index 36336b7f78..9a2115d430 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala @@ -57,6 +57,7 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) deleteOntologyComment(featureFactoryConfig) ~ createProperty(featureFactoryConfig) ~ updatePropertyLabelsOrComments(featureFactoryConfig) ~ + deletePropertyComment(featureFactoryConfig) ~ updatePropertyGuiElement(featureFactoryConfig) ~ getProperties(featureFactoryConfig) ~ canDeleteProperty(featureFactoryConfig) ~ @@ -869,6 +870,52 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) } } + // delete the comment of a property definition + private def deletePropertyComment(featureFactoryConfig: FeatureFactoryConfig): Route = + path(OntologiesBasePath / "properties" / "comment" / Segment) { propertyIriStr: IRI => + delete { requestContext => + val propertyIri = propertyIriStr.toSmartIri + + if (!propertyIri.getOntologySchema.contains(ApiV2Complex)) { + throw BadRequestException(s"Invalid property IRI for request: $propertyIriStr") + } + + val lastModificationDateStr = requestContext.request.uri + .query() + .toMap + .getOrElse(LAST_MODIFICATION_DATE, throw BadRequestException(s"Missing parameter: $LAST_MODIFICATION_DATE")) + + val lastModificationDate = stringFormatter.xsdDateTimeStampToInstant( + lastModificationDateStr, + throw BadRequestException(s"Invalid timestamp: $lastModificationDateStr") + ) + + val requestMessageFuture: Future[DeletePropertyCommentRequestV2] = for { + requestingUser <- getUserADM( + requestContext = requestContext, + featureFactoryConfig = featureFactoryConfig + ) + } yield DeletePropertyCommentRequestV2( + propertyIri = propertyIri, + lastModificationDate = lastModificationDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = featureFactoryConfig, + requestingUser = requestingUser + ) + + RouteUtilV2.runRdfRouteWithFuture( + requestMessageF = requestMessageFuture, + requestContext = requestContext, + featureFactoryConfig = featureFactoryConfig, + settings = settings, + responderManager = responderManager, + log = log, + targetSchema = ApiV2Complex, + schemaOptions = RouteUtilV2.getSchemaOptions(requestContext) + ) + } + } + private def updatePropertyGuiElement(featureFactoryConfig: FeatureFactoryConfig): Route = path(OntologiesBasePath / "properties" / "guielement") { put { diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/deletePropertyComment.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/deletePropertyComment.scala.txt new file mode 100644 index 0000000000..077be7af2d --- /dev/null +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/deletePropertyComment.scala.txt @@ -0,0 +1,59 @@ +@* + * Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + *@ + +@import org.knora.webapi._ +@import org.knora.webapi.messages.SmartIri +@import java.time.Instant + +@* + * Delete rdfs:comment from a property + * + * @param ontologyNamedGraphIri the IRI of the named graph where the ontology is stored. + * @param ontologyIri the IRI of the ontology containing the property. + * @param propertyIri the IRI of the property to be updated. + * @param lastModificationDate the xsd:dateTimeStamp that was attached to the ontology when it was last modified. + * @param currentTime an xsd:dateTimeStamp that will be attached to the ontology. + *@ +@(ontologyNamedGraphIri: SmartIri, + ontologyIri: SmartIri, + propertyIri: SmartIri, + maybeLinkValuePropertyIri: Option[SmartIri], + lastModificationDate: Instant, + currentTime: Instant) + +PREFIX rdf: +PREFIX rdfs: +PREFIX owl: +PREFIX xsd: +PREFIX knora-base: +PREFIX salsah-gui: + +@* Delete the existing comments *@ + +DELETE { + GRAPH <@ontologyNamedGraphIri> { + <@propertyIri> rdfs:comment ?comments. + <@ontologyIri> knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . + + @maybeLinkValuePropertyIri match { + case Some(linkValuePropertyIri) => { + <@linkValuePropertyIri> rdfs:comment ?comments . + } + case None => {} + } + } +} +INSERT { + GRAPH <@ontologyNamedGraphIri> { + <@ontologyIri> knora-base:lastModificationDate "@currentTime"^^xsd:dateTime . + } +} +WHERE { + GRAPH <@ontologyNamedGraphIri> { + <@ontologyIri> rdf:type owl:Ontology ; + knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . + <@propertyIri> rdfs:comment ?comments. + } +} diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala index d730c3253c..50fb3628ed 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala @@ -965,6 +965,84 @@ class OntologyV2R2RSpec extends R2RSpec { } } + "delete the rdfs:comment of a property" in { + val propertySegment = + URLEncoder.encode("http://0.0.0.0:3333/ontology/0001/freetest/v2#hasPropertyWithComment2", "UTF-8") + val lastModificationDate = URLEncoder.encode(freetestLastModDate.toString, "UTF-8") + + Delete( + s"/v2/ontologies/properties/comment/$propertySegment?lastModificationDate=$lastModificationDate" + ) ~> addCredentials(BasicHttpCredentials(anythingUsername, password)) ~> ontologiesPath ~> check { + assert(status == StatusCodes.OK, response.toString) + val responseJsonDoc: JsonLDDocument = responseToJsonLDDocument(response) + val newFreetestLastModDate = responseJsonDoc.requireDatatypeValueInObject( + key = OntologyConstants.KnoraApiV2Complex.LastModificationDate, + expectedDatatype = OntologyConstants.Xsd.DateTimeStamp.toSmartIri, + validationFun = stringFormatter.xsdDateTimeStampToInstant + ) + + assert(newFreetestLastModDate.isAfter(freetestLastModDate)) + freetestLastModDate = newFreetestLastModDate + + val expectedResponse: String = + s"""{ + | "knora-api:lastModificationDate": { + | "@value": "${newFreetestLastModDate}", + | "@type": "xsd:dateTimeStamp" + | }, + | "rdfs:label": "freetest", + | "@graph": [ + | { + | "rdfs:label": [ + | { + | "@value": "Property mit einem Kommentar", + | "@language": "de" + | }, + | { + | "@value": "Property with a comment", + | "@language": "en" + | } + | ], + | "rdfs:subPropertyOf": { + | "@id": "knora-api:hasValue" + | }, + | "@type": "owl:ObjectProperty", + | "knora-api:objectType": { + | "@id": "knora-api:TextValue" + | }, + | "salsah-gui:guiElement": { + | "@id": "salsah-gui:SimpleText" + | }, + | "@id": "freetest:hasPropertyWithComment2" + | } + | ], + | "knora-api:attachedToProject": { + | "@id": "http://rdfh.ch/projects/0001" + | }, + | "@type": "owl:Ontology", + | "@id": "http://0.0.0.0:3333/ontology/0001/freetest/v2", + | "@context": { + | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + | "knora-api": "http://api.knora.org/ontology/knora-api/v2#", + | "freetest": "http://0.0.0.0:3333/ontology/0001/freetest/v2#", + | "owl": "http://www.w3.org/2002/07/owl#", + | "salsah-gui": "http://api.knora.org/ontology/salsah-gui/v2#", + | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + | "xsd": "http://www.w3.org/2001/XMLSchema#" + | } + |}""".stripMargin + + val expectedResponseToCompare: InputOntologyV2 = + InputOntologyV2.fromJsonLD(JsonLDUtil.parseJsonLD(expectedResponse)).unescape + + val responseFromJsonLD: InputOntologyV2 = + InputOntologyV2.fromJsonLD(responseJsonDoc, parsingMode = TestResponseParsingModeV2).unescape + responseFromJsonLD.properties.head._2.predicates.toSet should ===( + expectedResponseToCompare.properties.head._2.predicates.toSet + ) + } + } + "change the salsah-gui:guiElement and salsah-gui:guiAttribute of a property" in { val params = s"""{ diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala index 0fbc800fcd..29657ec533 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala @@ -30,6 +30,7 @@ import java.time.Instant import java.util.UUID import scala.concurrent.duration._ import scala.language.postfixOps +import akka.japi.Predicate /** * Tests [[OntologyResponderV2]]. @@ -2404,6 +2405,106 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { } } + "delete the comment of a property that has a comment" in { + val propertyIri: SmartIri = FreeTestOntologyIri.makeEntityIri("hasPropertyWithComment") + responderManager ! DeletePropertyCommentRequestV2( + propertyIri = propertyIri, + lastModificationDate = freetestLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { case msg: ReadOntologyV2 => + val externalOntology: ReadOntologyV2 = msg.toOntologySchema(ApiV2Complex) + assert(externalOntology.properties.size == 1) + val readPropertyInfo: ReadPropertyInfoV2 = externalOntology.properties(propertyIri) + readPropertyInfo.entityInfoContent.predicates.contains( + OntologyConstants.Rdfs.Comment.toSmartIri + ) should ===(false) + val metadata: OntologyMetadataV2 = externalOntology.ontologyMetadata + val newFreeTestLastModDate: Instant = metadata.lastModificationDate.getOrElse( + throw AssertionException(s"${metadata.ontologyIri} has no last modification date") + ) + assert(newFreeTestLastModDate.isAfter(freetestLastModDate)) + freetestLastModDate = newFreeTestLastModDate + } + } + + "not update the ontology when trying to delete a comment of a property that has no comment" in { + val propertyIri: SmartIri = FreeTestOntologyIri.makeEntityIri("hasPropertyWithoutComment") + responderManager ! DeletePropertyCommentRequestV2( + propertyIri = propertyIri, + lastModificationDate = freetestLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { case msg: ReadOntologyV2 => + val externalOntology: ReadOntologyV2 = msg.toOntologySchema(ApiV2Complex) + assert(externalOntology.properties.size == 1) + val readPropertyInfo: ReadPropertyInfoV2 = externalOntology.properties(propertyIri) + readPropertyInfo.entityInfoContent.predicates.contains( + OntologyConstants.Rdfs.Comment.toSmartIri + ) should ===(false) + val metadata: OntologyMetadataV2 = externalOntology.ontologyMetadata + val newFreeTestLastModDate: Instant = metadata.lastModificationDate.getOrElse( + throw AssertionException(s"${metadata.ontologyIri} has no last modification date") + ) + // the ontology was not changed and thus should not have a new last modification date + assert(newFreeTestLastModDate == freetestLastModDate) + freetestLastModDate = newFreeTestLastModDate + } + } + + "delete the comment of a link property and remove the comment of the link value property as well" in { + val linkPropertyIri: SmartIri = FreeTestOntologyIri.makeEntityIri("hasLinkPropertyWithComment") + val linkValueIri: SmartIri = linkPropertyIri.fromLinkPropToLinkValueProp + + // delete the comment of the link property + responderManager ! DeletePropertyCommentRequestV2( + propertyIri = linkPropertyIri, + lastModificationDate = freetestLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { case msg: ReadOntologyV2 => + val externalOntology: ReadOntologyV2 = msg.toOntologySchema(ApiV2Complex) + assert(externalOntology.properties.size == 1) + + val propertyReadPropertyInfo: ReadPropertyInfoV2 = externalOntology.properties(linkPropertyIri) + propertyReadPropertyInfo.entityInfoContent.predicates.contains( + OntologyConstants.Rdfs.Comment.toSmartIri + ) should ===(false) + + val metadata: OntologyMetadataV2 = externalOntology.ontologyMetadata + val newFreeTestLastModDate: Instant = metadata.lastModificationDate.getOrElse( + throw AssertionException(s"${metadata.ontologyIri} has no last modification date") + ) + assert(newFreeTestLastModDate.isAfter(freetestLastModDate)) + freetestLastModDate = newFreeTestLastModDate + } + + // check that the comment of the link value property was deleted as well + responderManager ! PropertiesGetRequestV2( + propertyIris = Set(linkValueIri), + allLanguages = true, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { case msg: ReadOntologyV2 => + val externalOntology: ReadOntologyV2 = msg.toOntologySchema(ApiV2Complex) + val linkValueReadPropertyInfo: ReadPropertyInfoV2 = externalOntology.properties(linkValueIri) + + linkValueReadPropertyInfo.entityInfoContent.predicates.contains( + OntologyConstants.Rdfs.Comment.toSmartIri + ) should ===(false) + } + } + "not allow a user to create a class if they are not a sysadmin or an admin in the ontology's project" in { val classIri = AnythingOntologyIri.makeEntityIri("WildThing")