diff --git a/docs/03-apis/api-v2/ontology-information.md b/docs/03-apis/api-v2/ontology-information.md index b03836cdae..0c365e1dfa 100644 --- a/docs/03-apis/api-v2/ontology-information.md +++ b/docs/03-apis/api-v2/ontology-information.md @@ -979,8 +979,8 @@ be a valid XML [NCName](https://www.w3.org/TR/xml-names/#NT-NCName). ### Changing an Ontology's Metadata -Currently, the only modifiable ontology metadata is the ontology's -`rdfs:label`. +One can modify an ontology's metadata by updating its `rdfs:label` or `rdfs:comment` +or both. The example below shows the request for changing the label of an ontology. ``` HTTP PUT to http://host/v2/ontologies/metadata @@ -1002,6 +1002,26 @@ HTTP PUT to http://host/v2/ontologies/metadata } ``` +Similarly, a user can change an ontology's existing comment or add one by specifying +the new comment in the request body: + +```jsonld +{ + "@id" : "ONTOLOGY_IRI", + "rdfs:comment" : "NEW_ONTOLOGY_COMMENT", + "knora-api:lastModificationDate" : { + "@type" : "xsd:dateTimeStamp", + "@value" : "ONTOLOGY_LAST_MODIFICATION_DATE" + }, + "@context" : { + "xsd" : "http://www.w3.org/2001/XMLSchema#", + "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + "knora-api" : "http://api.knora.org/ontology/knora-api/v2#" + } +} +``` + +The request body can also contain a new label and a new comment for the ontology's metadata. A successful response will be a JSON-LD document providing only the ontology's metadata. 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 98ab5c37b4..77616e5b5a 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 @@ -19,6 +19,7 @@ package org.knora.webapi.messages.v2.responder.ontologymessages +import java.io import java.time.Instant import java.util.UUID @@ -827,12 +828,14 @@ object ChangeClassLabelsOrCommentsRequestV2 extends KnoraJsonLDRequestReaderV2[C * * @param ontologyIri the external ontology IRI. * @param label the ontology's new label. + * @param comment the ontology's new comment. * @param lastModificationDate the ontology's last modification date, returned in a previous operation. * @param apiRequestID the ID of the API request. * @param requestingUser the user making the request. */ case class ChangeOntologyMetadataRequestV2(ontologyIri: SmartIri, - label: String, + label: Option[String] = None, + comment: Option[String] = None, lastModificationDate: Instant, apiRequestID: UUID, requestingUser: UserADM) extends OntologiesResponderRequestV2 @@ -876,12 +879,14 @@ object ChangeOntologyMetadataRequestV2 extends KnoraJsonLDRequestReaderV2[Change val inputOntologyV2 = InputOntologyV2.fromJsonLD(jsonLDDocument) val inputMetadata = inputOntologyV2.ontologyMetadata val ontologyIri = inputMetadata.ontologyIri - val label = inputMetadata.label.getOrElse(throw BadRequestException(s"No rdfs:label submitted")) + val label: Option[String] = inputMetadata.label + val comment: Option[String] = inputMetadata.comment val lastModificationDate = inputMetadata.lastModificationDate.getOrElse(throw BadRequestException("No knora-api:lastModificationDate submitted")) ChangeOntologyMetadataRequestV2( ontologyIri = ontologyIri, label = label, + comment = comment, lastModificationDate = lastModificationDate, apiRequestID = apiRequestID, requestingUser = requestingUser 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 2d0c68844a..9b908d02a9 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 @@ -1816,6 +1816,11 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon // Check that the ontology exists and has not been updated by another user since the client last read its metadata. _ <- checkOntologyLastModificationDateBeforeUpdate(internalOntologyIri = internalOntologyIri, expectedLastModificationDate = changeOntologyMetadataRequest.lastModificationDate) + // get the metadata of the ontology. + oldMetadata: OntologyMetadataV2 = cacheData.ontologies(internalOntologyIri).ontologyMetadata + // Was there a comment in the ontology metadata? + ontologyHasComment: Boolean = oldMetadata.comment.nonEmpty + // Update the metadata. currentTime: Instant = Instant.now @@ -1825,6 +1830,8 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon ontologyNamedGraphIri = internalOntologyIri, ontologyIri = internalOntologyIri, newLabel = changeOntologyMetadataRequest.label, + hasOldComment = ontologyHasComment, + newComment = changeOntologyMetadataRequest.comment, lastModificationDate = changeOntologyMetadataRequest.lastModificationDate, currentTime = currentTime ).toString() @@ -1833,10 +1840,20 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon // Check that the update was successful. To do this, we have to undo the SPARQL-escaping of the input. + // Is there any new label given? + label = if (changeOntologyMetadataRequest.label.isEmpty) { + // No. Consider the old label for checking the update. + oldMetadata.label + } else { + // Yes. Consider the new label for checking the update. + changeOntologyMetadataRequest.label + } + unescapedNewMetadata = OntologyMetadataV2( ontologyIri = internalOntologyIri, projectIri = Some(projectIri), - label = Some(changeOntologyMetadataRequest.label), + label = label, + comment = changeOntologyMetadataRequest.comment, lastModificationDate = Some(currentTime) ).unescape 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 a364d3e868..dfb5c306bd 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 @@ -188,12 +188,13 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) val ontologyIri = SharedOntologyTestDataADM.FOO_ONTOLOGY_IRI_LocalHost val newLabel = "The modified foo ontology" + val newComment = "new comment" val newModificationDate = Instant.now FastFuture.successful( TestDataFileContent( filePath = TestDataFilePath.makeJsonPath("update-ontology-metadata-request"), text = SharedTestDataADM.changeOntologyMetadata( - ontologyIri, newLabel, newModificationDate + ontologyIri, newLabel, newComment, newModificationDate ) ) ) 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 81c70b7998..2823935cfc 100644 --- a/webapi/src/main/scala/org/knora/webapi/sharedtestdata/SharedTestDataADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/sharedtestdata/SharedTestDataADM.scala @@ -2464,11 +2464,12 @@ object SharedTestDataADM { | } |}""".stripMargin - def changeOntologyMetadata(ontologyIri: IRI, newLabel: String, modificationDate: Instant): String = { + def changeOntologyMetadata(ontologyIri: IRI, newLabel: String, newComment: String, modificationDate: Instant): String = { s""" |{ | "@id": "$ontologyIri", | "rdfs:label": "$newLabel", + | "rdfs:comment": "$newComment", | "knora-api:lastModificationDate": { | "@type" : "xsd:dateTimeStamp", | "@value" : "$modificationDate" diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/changeOntologyMetadata.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/changeOntologyMetadata.scala.txt index 78fb38a1ab..4bc26df084 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/changeOntologyMetadata.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/changeOntologyMetadata.scala.txt @@ -27,13 +27,16 @@ * @param ontologyNamedGraphIri the IRI of the named graph where the ontology should be stored. * @param ontologyIri the IRI of the ontology to be created. * @param newLabel the ontology's new label. + * @param newComment the ontology's new comment. * @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. *@ @(triplestore: String, ontologyNamedGraphIri: SmartIri, ontologyIri: SmartIri, - newLabel: String, + newLabel: Option[String], + hasOldComment: Boolean, + newComment: Option[String], lastModificationDate: Instant, currentTime: Instant) @@ -45,13 +48,24 @@ PREFIX knora-base: DELETE { GRAPH ?ontologyNamedGraph { - ?ontology rdfs:label ?oldLabel ; - knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . + @if(newLabel.nonEmpty) { + ?ontology rdfs:label ?oldLabel . + } + @if(hasOldComment && newComment.nonEmpty) { + ?ontology rdfs:comment ?oldComment . + } + ?ontology knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . + } } INSERT { GRAPH ?ontologyNamedGraph { - ?ontology rdfs:label """@newLabel"""^^xsd:string ; - knora-base:lastModificationDate "@currentTime"^^xsd:dateTime . + @if(newLabel.nonEmpty) { + ?ontology rdfs:label """@newLabel.get"""^^xsd:string . + } + @if(newComment.nonEmpty) { + ?ontology rdfs:comment """@newComment.get"""^^xsd:string . + } + ?ontology knora-base:lastModificationDate "@currentTime"^^xsd:dateTime . } } @* Ensure that inference is not used in the WHERE clause of this update. *@ @@ -64,7 +78,12 @@ WHERE { GRAPH ?ontologyNamedGraph { ?ontology rdf:type owl:Ontology ; + @if(newLabel.nonEmpty) { rdfs:label ?oldLabel ; + } + @if(hasOldComment && newComment.nonEmpty) { + rdfs:comment ?oldComment ; + } knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . } } 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 0d89f9a23f..70e9f9e0b8 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 @@ -274,8 +274,8 @@ class OntologyV2R2RSpec extends R2RSpec { "change the metadata of 'foo'" in { val newLabel = "The modified foo ontology" - - val params = SharedTestDataADM.changeOntologyMetadata(fooIri.get, newLabel, fooLastModDate) + val newComment = "new comment" + val params = SharedTestDataADM.changeOntologyMetadata(fooIri.get, newLabel, newComment, fooLastModDate) Put("/v2/ontologies/metadata", HttpEntity(RdfMediaTypes.`application/ld+json`, params)) ~> addCredentials(BasicHttpCredentials(imagesUsername, password)) ~> ontologiesPath ~> check { @@ -285,6 +285,7 @@ class OntologyV2R2RSpec extends R2RSpec { val ontologyIri = metadata.value("@id").asInstanceOf[JsonLDString].value assert(ontologyIri == fooIri.get) assert(metadata.value(OntologyConstants.Rdfs.Label) == JsonLDString(newLabel)) + assert(metadata.value(OntologyConstants.Rdfs.Comment) == JsonLDString(newComment)) val lastModDate = metadata.requireDatatypeValueInObject( key = OntologyConstants.KnoraApiV2Complex.LastModificationDate, 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 07062d91d0..b2f2e2e5fb 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 @@ -59,6 +59,8 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { private val fooIri = new MutableTestIri private var fooLastModDate: Instant = Instant.now + private val barIri = new MutableTestIri + private var barLastModDate: Instant = Instant.now private val chairIri = new MutableTestIri private var chairLastModDate: Instant = Instant.now @@ -117,6 +119,48 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { fooLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) } + "change the label in the metadata of 'foo'" in { + val newLabel = "The modified foo ontology" + + responderManager ! ChangeOntologyMetadataRequestV2( + ontologyIri = fooIri.get.toSmartIri.toOntologySchema(ApiV2Complex), + label = Some(newLabel), + lastModificationDate = fooLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = imagesUser + ) + + val response = expectMsgType[ReadOntologyMetadataV2](timeout) + assert(response.ontologies.size == 1) + val metadata = response.ontologies.head + assert(metadata.ontologyIri == fooIri.get.toSmartIri) + assert(metadata.label.contains(newLabel)) + val newFooLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newFooLastModDate.isAfter(fooLastModDate)) + fooLastModDate = newFooLastModDate + } + + "add a comment to the metadata of 'foo' ontology" in { + val aComment = "a comment" + + responderManager ! ChangeOntologyMetadataRequestV2( + ontologyIri = fooIri.get.toSmartIri.toOntologySchema(ApiV2Complex), + comment = Some(aComment), + lastModificationDate = fooLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = imagesUser + ) + + val response = expectMsgType[ReadOntologyMetadataV2](timeout) + assert(response.ontologies.size == 1) + val metadata = response.ontologies.head + assert(metadata.ontologyIri == fooIri.get.toSmartIri) + assert(metadata.comment.contains(aComment)) + val newFooLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newFooLastModDate.isAfter(fooLastModDate)) + fooLastModDate = newFooLastModDate + } + "create an empty ontology called 'bar' with a comment" in { responderManager ! CreateOntologyRequestV2( ontologyName = "bar", @@ -133,15 +177,17 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { assert(metadata.ontologyIri.toString == "http://www.knora.org/ontology/00FF/bar") val returnedComment: String = metadata.comment.getOrElse(throw AssertionException("The bar ontology has no comment!")) assert(returnedComment == "some comment") + barIri.set(metadata.ontologyIri.toString) + barLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) } - "change the metadata of 'foo'" in { - val newLabel = "The modified foo ontology" + "change the existing comment in the metadata of 'bar' ontology" in { + val newComment = "a new comment" responderManager ! ChangeOntologyMetadataRequestV2( - ontologyIri = fooIri.get.toSmartIri.toOntologySchema(ApiV2Complex), - label = newLabel, - lastModificationDate = fooLastModDate, + ontologyIri = barIri.get.toSmartIri.toOntologySchema(ApiV2Complex), + comment = Some(newComment), + lastModificationDate = barLastModDate, apiRequestID = UUID.randomUUID, requestingUser = imagesUser ) @@ -149,11 +195,11 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { val response = expectMsgType[ReadOntologyMetadataV2](timeout) assert(response.ontologies.size == 1) val metadata = response.ontologies.head - assert(metadata.ontologyIri == fooIri.get.toSmartIri) - assert(metadata.label.contains(newLabel)) - val newFooLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) - assert(newFooLastModDate.isAfter(fooLastModDate)) - fooLastModDate = newFooLastModDate + assert(metadata.ontologyIri == barIri.get.toSmartIri) + assert(metadata.comment.contains(newComment)) + val newBarLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newBarLastModDate.isAfter(barLastModDate)) + barLastModDate = newBarLastModDate } "not create 'foo' again" in { @@ -2043,7 +2089,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { responderManager ! ChangeOntologyMetadataRequestV2( ontologyIri = AnythingOntologyIri, - label = newLabel, + label = Some(newLabel), lastModificationDate = anythingLastModDate, apiRequestID = UUID.randomUUID, requestingUser = anythingAdminUser