diff --git a/docs/03-apis/api-v2/ontology-information.md b/docs/03-apis/api-v2/ontology-information.md index c7b251b67a..bfdec5c309 100644 --- a/docs/03-apis/api-v2/ontology-information.md +++ b/docs/03-apis/api-v2/ontology-information.md @@ -1197,7 +1197,7 @@ HTTP POST to http://host/v2/ontologies/classes } ``` -Values for `rdfs:label` and `rdfs:comment` must be submitted in at least +Values for `rdfs:label` must be submitted in at least one language, either as an object or as an array of objects. At least one base class must be provided, which can be @@ -1262,7 +1262,7 @@ to the supported combinations given in `OWL_CARDINALITY_VALUE` is shown here in quotes, but it should be an unquoted integer.) -Values for `rdfs:label` and `rdfs:comment` must be submitted in at least +Values for `rdfs:label` must be submitted in at least one language, either as an object or as an array of objects. At least one base class must be provided. @@ -1353,6 +1353,20 @@ Values for `rdfs:comment` must be submitted in at least one language, either as an object or as an array of objects. The submitted comments will replace the existing ones. +### Deleting the Comments of a Class + +This operation is permitted even if the class is used in data. + +``` +HTTP DELETE to http://host/v2/ontologies/classes/comment/CLASS_IRI?lastModificationDate=ONTOLOGY_LAST_MODIFICATION_DATE +``` + +The class IRI and the ontology's last modification date must be URL-encoded. + +All values i.e. all languages for `rdfs:comment` are deleted. + +A successful response will be a JSON-LD document providing the class definition. + ### Creating a Property ``` @@ -1404,7 +1418,7 @@ HTTP POST to http://host/v2/ontologies/properties } ``` -Values for `rdfs:label` and `rdfs:comment` must be submitted in at least +Values for `rdfs:label` must be submitted in at least one language, either as an object or as an array of objects. At least one base property must be provided, which can be diff --git a/test_data/ontologies/freetest-onto.ttl b/test_data/ontologies/freetest-onto.ttl index 92ff1551d6..b8d9ac4575 100644 --- a/test_data/ontologies/freetest-onto.ttl +++ b/test_data/ontologies/freetest-onto.ttl @@ -312,6 +312,38 @@ knora-base:TextValue ; salsah-gui:guiElement salsah-gui:Richtext . +:BookWithComment + rdf:type owl:Class ; + rdfs:subClassOf knora-base:Resource, + [ rdf:type owl:Restriction ; + owl:onProperty :hasName ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ] ; + rdfs:label "Buch mit Kommentar"@de, + "Book with comment"@en ; + rdfs:comment """A comment for book"""@en . + +:BookWithoutComment + rdf:type owl:Class ; + rdfs:subClassOf knora-base:Resource, + [ rdf:type owl:Restriction ; + owl:onProperty :hasName ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ] ; + rdfs:label "Buch ohne Kommentar"@de, + "Book without comment"@en . + +:BookWithComment2 + rdf:type owl:Class ; + rdfs:subClassOf knora-base:Resource, + [ rdf:type owl:Restriction ; + owl:onProperty :hasName ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ] ; + rdfs:label "Buch 2 mit Kommentar"@de, + "Book 2 with comment"@en ; + rdfs:comment """A comment for book"""@en . + :hasFoafName rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasValue, @@ -348,8 +380,8 @@ :hasPropertyWithComment2 rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasValue ; - rdfs:label "Property mit einem Kommentar"@de, - "Property with a comment"@en ; + rdfs:label "Property mit einem Kommentar 2"@de, + "Property with a comment 2"@en ; rdfs:comment "Dies ist der Kommentar"@de, "This is the comment"@en ; knora-base:objectClassConstraint knora-base:TextValue ; 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 52fd95636d..6a3e1db6cd 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 @@ -1309,6 +1309,80 @@ object ChangeClassLabelsOrCommentsRequestV2 extends KnoraJsonLDRequestReaderV2[C } } +/** + * Deletes the comment from a class. A successful response will be a [[ReadOntologyV2]]. + * + * @param classIri the IRI of the class. + * @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 DeleteClassCommentRequestV2( + classIri: SmartIri, + lastModificationDate: Instant, + apiRequestID: UUID, + featureFactoryConfig: FeatureFactoryConfig, + requestingUser: UserADM +) extends OntologiesResponderRequestV2 + +/** + * Constructs instances of [[DeleteClassCommentRequestV2]] based on JSON-LD input. + */ +object DeleteClassCommentRequestV2 extends KnoraJsonLDRequestReaderV2[DeleteClassCommentRequestV2] { + + /** + * Converts a JSON-LD request to a [[DeleteClassCommentRequestV2]]. + * + * @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 [[DeleteClassCommentRequestV2]] 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[DeleteClassCommentRequestV2] = + Future { + fromJsonLDSync( + jsonLDDocument = jsonLDDocument, + apiRequestID = apiRequestID, + featureFactoryConfig = featureFactoryConfig, + requestingUser = requestingUser + ) + } + + private def fromJsonLDSync( + jsonLDDocument: JsonLDDocument, + apiRequestID: UUID, + featureFactoryConfig: FeatureFactoryConfig, + requestingUser: UserADM + ): DeleteClassCommentRequestV2 = { + val inputOntologyV2: InputOntologyV2 = InputOntologyV2.fromJsonLD(jsonLDDocument) + val classUpdateInfo: ClassUpdateInfo = OntologyUpdateHelper.getClassDef(inputOntologyV2) + val classInfoContent: ClassInfoContentV2 = classUpdateInfo.classInfoContent + val lastModificationDate: Instant = classUpdateInfo.lastModificationDate + + DeleteClassCommentRequestV2( + classIri = classInfoContent.classIri, + lastModificationDate = lastModificationDate, + apiRequestID = apiRequestID, + featureFactoryConfig = featureFactoryConfig, + requestingUser = requestingUser + ) + } +} + case class ChangeGuiOrderRequestV2( classInfoContent: ClassInfoContentV2, lastModificationDate: Instant, 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 1d54bdf356..b322c1a5ad 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 @@ -114,6 +114,8 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon changePropertyLabelsOrComments(changePropertyLabelsOrCommentsRequest) case deletePropertyCommentRequest: DeletePropertyCommentRequestV2 => deletePropertyComment(deletePropertyCommentRequest) + case deleteClassCommentRequest: DeleteClassCommentRequestV2 => + deleteClassComment(deleteClassCommentRequest) case changePropertyGuiElementRequest: ChangePropertyGuiElementRequest => changePropertyGuiElement(changePropertyGuiElementRequest) case canDeletePropertyRequest: CanDeletePropertyRequestV2 => canDeleteProperty(canDeletePropertyRequest) @@ -3497,4 +3499,164 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon } yield taskResult } + /** + * Delete the `rdfs:comment` in a class definition. + * + * @param deleteClassCommentRequestV2 the request to delete the class' comment + * @return a [[ReadOntologyV2]] containing the modified class definition. + */ + private def deleteClassComment( + deleteClassCommentRequest: DeleteClassCommentRequestV2 + ): Future[ReadOntologyV2] = { + def makeTaskFuture( + cacheData: Cache.OntologyCacheData, + internalClassIri: SmartIri, + internalOntologyIri: SmartIri, + ontology: ReadOntologyV2, + classToUpdate: ReadClassInfoV2 + ): 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 = deleteClassCommentRequest.lastModificationDate, + featureFactoryConfig = deleteClassCommentRequest.featureFactoryConfig + ) + + currentTime: Instant = Instant.now + + // Delete the comment + updateSparql: String = org.knora.webapi.messages.twirl.queries.sparql.v2.txt + .deleteClassComment( + ontologyNamedGraphIri = internalOntologyIri, + ontologyIri = internalOntologyIri, + classIri = internalClassIri, + lastModificationDate = deleteClassCommentRequest.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 = deleteClassCommentRequest.featureFactoryConfig + ) + + // Check that the update was successful. + loadedClassDef: ClassInfoContentV2 <- OntologyHelpers.loadClassDefinition( + settings, + storeManager, + classIri = internalClassIri, + featureFactoryConfig = deleteClassCommentRequest.featureFactoryConfig + ) + + classDefWithoutComment: ClassInfoContentV2 = + classToUpdate.entityInfoContent.copy( + predicates = classToUpdate.entityInfoContent.predicates.-( + OntologyConstants.Rdfs.Comment.toSmartIri + ) // the "-" deletes the entry with the comment + ) + + _ = if (loadedClassDef != classDefWithoutComment) { + throw InconsistentRepositoryDataException( + s"Attempted to save class definition $classDefWithoutComment, but $loadedClassDef was saved" + ) + } + + // Update the ontology cache using the new class definition. + newReadClassInfo: ReadClassInfoV2 = ReadClassInfoV2( + entityInfoContent = loadedClassDef, + allBaseClasses = classToUpdate.allBaseClasses + ) + + updatedOntologyMetadata: OntologyMetadataV2 = ontology.ontologyMetadata.copy( + lastModificationDate = Some(currentTime) + ) + + updatedOntology: ReadOntologyV2 = + ontology.copy( + ontologyMetadata = updatedOntologyMetadata, + classes = ontology.classes + (internalClassIri -> newReadClassInfo) + ) + + _ = Cache.storeCacheData( + cacheData.copy( + ontologies = cacheData.ontologies + (internalOntologyIri -> updatedOntology) + ) + ) + + // Read the data back from the cache. + + response: ReadOntologyV2 <- getClassDefinitionsFromOntologyV2( + classIris = Set(internalClassIri), + allLanguages = true, + requestingUser = deleteClassCommentRequest.requestingUser + ) + + } yield response + + for { + requestingUser: UserADM <- FastFuture.successful(deleteClassCommentRequest.requestingUser) + + externalClassIri: SmartIri = deleteClassCommentRequest.classIri + externalOntologyIri: SmartIri = externalClassIri.getOntologyFromEntity + + _ <- OntologyHelpers.checkOntologyAndEntityIrisForUpdate( + externalOntologyIri = externalOntologyIri, + externalEntityIri = externalClassIri, + requestingUser = requestingUser + ) + + internalClassIri: SmartIri = externalClassIri.toOntologySchema(InternalSchema) + internalOntologyIri: SmartIri = externalOntologyIri.toOntologySchema(InternalSchema) + + cacheData: Cache.OntologyCacheData <- Cache.getCacheData + + ontology: ReadOntologyV2 = cacheData.ontologies(internalOntologyIri) + + classToUpdate: ReadClassInfoV2 = + ontology.classes.getOrElse( + internalClassIri, + throw NotFoundException(s"Class ${deleteClassCommentRequest.classIri} not found") + ) + + hasComment: Boolean = classToUpdate.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 = deleteClassCommentRequest.apiRequestID, + iri = ONTOLOGY_CACHE_LOCK_IRI, + task = () => + makeTaskFuture( + cacheData = cacheData, + internalClassIri = internalClassIri, + internalOntologyIri = internalOntologyIri, + ontology = ontology, + classToUpdate = classToUpdate + ) + ) + } yield taskResult + else { + // not change anything if class has no comment + getClassDefinitionsFromOntologyV2( + classIris = Set(internalClassIri), + allLanguages = true, + requestingUser = deleteClassCommentRequest.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 9a2115d430..6820f4c924 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 @@ -45,6 +45,7 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) getOntology(featureFactoryConfig) ~ createClass(featureFactoryConfig) ~ updateClass(featureFactoryConfig) ~ + deleteClassComment(featureFactoryConfig) ~ addCardinalities(featureFactoryConfig) ~ canReplaceCardinalities(featureFactoryConfig) ~ replaceCardinalities(featureFactoryConfig) ~ @@ -311,45 +312,92 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) } } - private def updateClass(featureFactoryConfig: FeatureFactoryConfig): Route = path(OntologiesBasePath / "classes") { - put { - // Change the labels or comments of a class. - entity(as[String]) { jsonRequest => requestContext => - { - val requestMessageFuture: Future[ChangeClassLabelsOrCommentsRequestV2] = for { - requestingUser <- getUserADM( - requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig - ) + private def updateClass(featureFactoryConfig: FeatureFactoryConfig): Route = + path(OntologiesBasePath / "classes") { + put { + // Change the labels or comments of a class. + entity(as[String]) { jsonRequest => requestContext => + { + val requestMessageFuture: Future[ChangeClassLabelsOrCommentsRequestV2] = for { + requestingUser <- getUserADM( + requestContext = requestContext, + featureFactoryConfig = featureFactoryConfig + ) - requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest) + requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest) - requestMessage <- ChangeClassLabelsOrCommentsRequestV2.fromJsonLD( - jsonLDDocument = requestDoc, - apiRequestID = UUID.randomUUID, - requestingUser = requestingUser, - responderManager = responderManager, - storeManager = storeManager, + requestMessage <- ChangeClassLabelsOrCommentsRequestV2.fromJsonLD( + jsonLDDocument = requestDoc, + apiRequestID = UUID.randomUUID, + requestingUser = requestingUser, + responderManager = responderManager, + storeManager = storeManager, + featureFactoryConfig = featureFactoryConfig, + settings = settings, + log = log + ) + } yield requestMessage + + RouteUtilV2.runRdfRouteWithFuture( + requestMessageF = requestMessageFuture, + requestContext = requestContext, featureFactoryConfig = featureFactoryConfig, settings = settings, - log = log + responderManager = responderManager, + log = log, + targetSchema = ApiV2Complex, + schemaOptions = RouteUtilV2.getSchemaOptions(requestContext) ) - } yield requestMessage + } + } + } + } - RouteUtilV2.runRdfRouteWithFuture( - requestMessageF = requestMessageFuture, + // delete the comment of a class definition + private def deleteClassComment(featureFactoryConfig: FeatureFactoryConfig): Route = + path(OntologiesBasePath / "classes" / "comment" / Segment) { classIriStr: IRI => + delete { requestContext => + val classIri = classIriStr.toSmartIri + + if (!classIri.getOntologySchema.contains(ApiV2Complex)) { + throw BadRequestException(s"Invalid class IRI for request: $classIriStr") + } + + 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[DeleteClassCommentRequestV2] = for { + requestingUser <- getUserADM( requestContext = requestContext, - featureFactoryConfig = featureFactoryConfig, - settings = settings, - responderManager = responderManager, - log = log, - targetSchema = ApiV2Complex, - schemaOptions = RouteUtilV2.getSchemaOptions(requestContext) + featureFactoryConfig = featureFactoryConfig ) - } + } yield DeleteClassCommentRequestV2( + classIri = classIri, + 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 addCardinalities(featureFactoryConfig: FeatureFactoryConfig): Route = path(OntologiesBasePath / "cardinalities") { diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/deleteClassComment.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/deleteClassComment.scala.txt new file mode 100644 index 0000000000..57bf71b565 --- /dev/null +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/deleteClassComment.scala.txt @@ -0,0 +1,51 @@ +@* + * 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 class + * + * @param ontologyNamedGraphIri the IRI of the named graph where the ontology is stored. + * @param ontologyIri the IRI of the ontology containing the class. + * @param classIri the IRI of the class 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, + classIri: 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> { + <@classIri> rdfs:comment ?comments. + <@ontologyIri> knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . + } +} +INSERT { + GRAPH <@ontologyNamedGraphIri> { + <@ontologyIri> knora-base:lastModificationDate "@currentTime"^^xsd:dateTime . + } +} +WHERE { + GRAPH <@ontologyNamedGraphIri> { + <@ontologyIri> rdf:type owl:Ontology ; + knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . + <@classIri> 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 50fb3628ed..c9f46f4336 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 @@ -995,11 +995,11 @@ class OntologyV2R2RSpec extends R2RSpec { | { | "rdfs:label": [ | { - | "@value": "Property mit einem Kommentar", + | "@value": "Property mit einem Kommentar 2", | "@language": "de" | }, | { - | "@value": "Property with a comment", + | "@value": "Property with a comment 2", | "@language": "en" | } | ], @@ -1043,6 +1043,82 @@ class OntologyV2R2RSpec extends R2RSpec { } } + "delete the rdfs:comment of a class" in { + val classSegment: String = + URLEncoder.encode("http://0.0.0.0:3333/ontology/0001/freetest/v2#BookWithComment2", "UTF-8") + val lastModificationDate = URLEncoder.encode(freetestLastModDate.toString, "UTF-8") + + Delete( + s"/v2/ontologies/classes/comment/$classSegment?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": "Buch 2 mit Kommentar", + | "@language": "de" + | }, + | { + | "@value": "Book 2 with comment", + | "@language": "en" + | } + | ], + | "rdfs:subClassOf": [ + | { + | "@id": "knora-api:Resource" + | } + | ], + | "@type": "owl:Class", + | "@id": "freetest:BookWithComment2" + | } + | ], + | "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 + + println(responseFromJsonLD) + responseFromJsonLD.classes.head._2.predicates.toSet should ===( + expectedResponseToCompare.classes.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 29657ec533..8a0f61e3cd 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 @@ -2458,6 +2458,59 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { } } + "delete the comment of a class that has a comment" in { + val classIri: SmartIri = FreeTestOntologyIri.makeEntityIri("BookWithComment") + responderManager ! DeleteClassCommentRequestV2( + classIri = classIri, + lastModificationDate = freetestLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { case msg: ReadOntologyV2 => + val externalOntology: ReadOntologyV2 = msg.toOntologySchema(ApiV2Complex) + assert(externalOntology.classes.size == 1) + val readClassInfo: ReadClassInfoV2 = externalOntology.classes(classIri) + readClassInfo.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 class that has no comment" in { + val classIri: SmartIri = FreeTestOntologyIri.makeEntityIri("BookWithoutComment") + responderManager ! DeleteClassCommentRequestV2( + classIri = classIri, + lastModificationDate = freetestLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { case msg: ReadOntologyV2 => + val externalOntology: ReadOntologyV2 = msg.toOntologySchema(ApiV2Complex) + assert(externalOntology.classes.size == 1) + val readClassInfo: ReadClassInfoV2 = externalOntology.classes(classIri) + readClassInfo.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