Skip to content
Permalink
Browse files
feat(ontology): allow deleting comments of classes (DEV-804) (#2048)
* 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

* update documentation

* add tests

* add route to delete comment of class
  • Loading branch information
irinaschubert committed Apr 26, 2022
1 parent 985c5fd commit eca92066bdb6d48033f9f4590f45dba774bc4be2
@@ -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
@@ -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 ;
@@ -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,
@@ -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
}

}

0 comments on commit eca9206

Please sign in to comment.