Skip to content

Commit

Permalink
docs: replace/canset cardinality documentation (DEV-1564 & DEV-1563) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
seakayone committed Feb 4, 2023
1 parent 84e2ead commit adf1a34
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 49 deletions.
20 changes: 8 additions & 12 deletions docs/02-dsp-ontologies/knora-base.md
Original file line number Diff line number Diff line change
Expand Up @@ -981,21 +981,17 @@ only if the value's class has some cardinality for that property.

Knora supports, and attempts to enforce, the following cardinality constraints:

`owl:cardinality 1`
* `owl:cardinality 1`
: _Exactly One `1`_ - A resource of this class must have exactly one instance of the specified property.

: A resource of this class must have exactly one instance of the specified property.
* `owl:minCardinality 1`
: _At Least One `1-n`_ - A resource of this class must have at least one instance of the specified property.

`owl:minCardinality 1`
* `owl:maxCardinality 1`
: _Zero Or One `0-1`_ - A resource of this class must have either zero or one instance of the specified property.

: A resource of this class must have at least one instance of the specified property.

`owl:maxCardinality 1`

: A resource of this class may have zero or one instance of the specified property.

`owl:minCardinality 0`

: A resource of this class may have zero or more instances of the specified property.
* `owl:minCardinality 0`
: _Unbounded `0-n`_ - A resource of this class may have zero or more instances of the specified property.

Knora requires cardinalities to be defined using blank nodes, as in the following example from `knora-base`:

Expand Down
60 changes: 50 additions & 10 deletions docs/03-endpoints/api-v2/ontology-information.md
Original file line number Diff line number Diff line change
Expand Up @@ -1644,13 +1644,21 @@ definition (but not any of the other entities in the ontology).

### Replacing the Cardinalities of a Class

This removes all the cardinalities from the class and replaces them with
the submitted cardinalities. If no cardinalities are submitted (i.e. the
request contains no `rdfs:subClassOf`), the class is left with no
cardinalities.
It is possible to replace all cardinalities on properties used by a class.
If it succeeds the request will effectively replace all direct cardinalities of the class as specified.
That is, it removes all the cardinalities from the class and replaces them with the submitted cardinalities.
Meaning that, if no cardinalities are submitted (i.e. the request contains no `rdfs:subClassOf`), the class is left with no cardinalities.

This operation is not permitted if the class is used in data, or if it
has a subclass.
The request will fail if any of the "Pre-Update Checks" fails.
A partial update of the ontology will not be performed.

#### Pre-Update Checks

* _Ontology Check_
* Any given cardinality on a property must be included in any of the existing cardinalities for the same property of the super-classes.
* Any given cardinality on a property must include the effective cardinalities for the same property of all subclasses, taking into account the respective inherited cardinalities from the class hierarchy of the subclasses.
* _Consistency Check with existing data_
* Given that instances of the class or any of its subclasses exist then these instances are checked if they conform to the given cardinality.

```
HTTP PUT to http://host/v2/ontologies/cardinalities
Expand Down Expand Up @@ -1694,17 +1702,47 @@ When a cardinality on a link property is submitted, an identical cardinality
on the corresponding link value property is automatically added (see
[Links Between Resources](../../02-dsp-ontologies/knora-base.md#links-between-resources)).

A successful response will be a JSON-LD document providing the new class
definition (but not any of the other entities in the ontology).
A successful response will be a JSON-LD document providing the new class definition (but not any of the other entities in the ontology).
If any of the "Pre-Update Checks" fail the endpoint will respond with a _400 Bad Request_ containing the reasons why the update failed.

To check whether a class's cardinalities can be replaced:
The "Pre-Update Checks" are available on a dedicated endpoint.
For a check whether a particular cardinality can be set on a class/property combination, use the following request:

```
HTTP GET to http://host/v2/ontologies/canreplacecardinalities/CLASS_IRI
HTTP GET to http://host/v2/ontologies/canreplacecardinalities/CLASS_IRI?propertyIri=PROPERTY_IRI&newCardinality=[0-1|1|1-n|0-n]
```

The response will look like this:

Failure:
```json
{
"knora-api:canDo": false,
"knora-api:cannotDoReason": "An explanation, understandable to humans, why the update cannot be carried out.",
"@context": {
"knora-api": "http://api.knora.org/ontology/knora-api/v2#"
}
}
```

Success:
```json
{
"knora-api:canDo": true,
"@context": {
"knora-api": "http://api.knora.org/ontology/knora-api/v2#"
}
}
```

_Note_: The following check is still available but deprecated - use the more detailed check above.

To check whether all class's cardinalities can be replaced:
```
HTTP GET to http://host/v2/ontologies/canreplacecardinalities/CLASS_IRI
```
The response will look like this:

```json
{
"knora-api:canDo": false,
Expand All @@ -1714,6 +1752,8 @@ The response will look like this:
}
```

The `ontologies/canreplacecardinalities/CLASS_IRI` request is only checking if the class is in use.


### Delete a single cardinality from a class

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4647,7 +4647,7 @@ class OntologyResponderV2Spec extends CoreSpec with ImplicitSender {
ontologySchema = ApiV2Complex
)

appActor ! ReplaceCardinalitiesRequestV2(
appActor ! ReplaceClassCardinalitiesRequestV2(
classInfoContent = classInfoContent,
lastModificationDate = anythingLastModDate,
apiRequestID = UUID.randomUUID,
Expand Down Expand Up @@ -4749,7 +4749,7 @@ class OntologyResponderV2Spec extends CoreSpec with ImplicitSender {
ontologySchema = ApiV2Complex
)

appActor ! ReplaceCardinalitiesRequestV2(
appActor ! ReplaceClassCardinalitiesRequestV2(
classInfoContent = classInfoContent,
lastModificationDate = anythingLastModDate,
apiRequestID = UUID.randomUUID,
Expand Down Expand Up @@ -4852,7 +4852,7 @@ class OntologyResponderV2Spec extends CoreSpec with ImplicitSender {
ontologySchema = ApiV2Complex
)

appActor ! ReplaceCardinalitiesRequestV2(
appActor ! ReplaceClassCardinalitiesRequestV2(
classInfoContent = classInfoContent,
lastModificationDate = anythingLastModDate,
apiRequestID = UUID.randomUUID,
Expand All @@ -4879,7 +4879,7 @@ class OntologyResponderV2Spec extends CoreSpec with ImplicitSender {
ontologySchema = ApiV2Complex
)

appActor ! ReplaceCardinalitiesRequestV2(
appActor ! ReplaceClassCardinalitiesRequestV2(
classInfoContent = classInfoContent,
lastModificationDate = anythingLastModDate,
apiRequestID = UUID.randomUUID,
Expand Down Expand Up @@ -5801,7 +5801,7 @@ class OntologyResponderV2Spec extends CoreSpec with ImplicitSender {

// Remove the link value cardinality from the class.

appActor ! ReplaceCardinalitiesRequestV2(
appActor ! ReplaceClassCardinalitiesRequestV2(
classInfoContent = ClassInfoContentV2(
predicates = Map(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type".toSmartIri -> PredicateInfoV2(
Expand Down Expand Up @@ -6402,7 +6402,7 @@ class OntologyResponderV2Spec extends CoreSpec with ImplicitSender {
ontologySchema = ApiV2Complex
)

appActor ! ReplaceCardinalitiesRequestV2(
appActor ! ReplaceClassCardinalitiesRequestV2(
classInfoContent = classChangeInfoContent,
lastModificationDate = anythingLastModDate,
apiRequestID = UUID.randomUUID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -617,35 +617,35 @@ object AddCardinalitiesToClassRequestV2 extends KnoraJsonLDRequestReaderV2[AddCa
* @param apiRequestID the ID of the API request.
* @param requestingUser the user making the request.
*/
case class ReplaceCardinalitiesRequestV2(
case class ReplaceClassCardinalitiesRequestV2(
classInfoContent: ClassInfoContentV2,
lastModificationDate: Instant,
apiRequestID: UUID,
requestingUser: UserADM
) extends OntologiesResponderRequestV2

/**
* Constructs instances of [[ReplaceCardinalitiesRequestV2]] based on JSON-LD input.
* Constructs instances of [[ReplaceClassCardinalitiesRequestV2]] based on JSON-LD input.
*/
object ReplaceCardinalitiesRequestV2 extends KnoraJsonLDRequestReaderV2[ReplaceCardinalitiesRequestV2] {
object ReplaceClassCardinalitiesRequestV2 extends KnoraJsonLDRequestReaderV2[ReplaceClassCardinalitiesRequestV2] {

/**
* Converts JSON-LD input into a [[ReplaceCardinalitiesRequestV2]].
* Converts JSON-LD input into a [[ReplaceClassCardinalitiesRequestV2]].
*
* @param jsonLDDocument the JSON-LD input.
* @param apiRequestID the UUID of the API request.
* @param requestingUser the user making the request.
* @param appActor a reference to the application actor.
* @param log a logging adapter.
* @return a [[ReplaceCardinalitiesRequestV2]] representing the input.
* @return a [[ReplaceClassCardinalitiesRequestV2]] representing the input.
*/
override def fromJsonLD(
jsonLDDocument: JsonLDDocument,
apiRequestID: UUID,
requestingUser: UserADM,
appActor: ActorRef,
log: Logger
)(implicit timeout: Timeout, executionContext: ExecutionContext): Future[ReplaceCardinalitiesRequestV2] =
)(implicit timeout: Timeout, executionContext: ExecutionContext): Future[ReplaceClassCardinalitiesRequestV2] =
Future {
fromJsonLDSync(
jsonLDDocument = jsonLDDocument,
Expand All @@ -658,13 +658,13 @@ object ReplaceCardinalitiesRequestV2 extends KnoraJsonLDRequestReaderV2[ReplaceC
jsonLDDocument: JsonLDDocument,
apiRequestID: UUID,
requestingUser: UserADM
): ReplaceCardinalitiesRequestV2 = {
): ReplaceClassCardinalitiesRequestV2 = {
val inputOntologiesV2 = InputOntologyV2.fromJsonLD(jsonLDDocument)
val classUpdateInfo = OntologyUpdateHelper.getClassDef(inputOntologiesV2)
val classInfoContent = classUpdateInfo.classInfoContent
val lastModificationDate = classUpdateInfo.lastModificationDate

ReplaceCardinalitiesRequestV2(
ReplaceClassCardinalitiesRequestV2(
classInfoContent = classInfoContent,
lastModificationDate = lastModificationDate,
apiRequestID = apiRequestID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ final case class OntologyResponderV2(
changeClassLabelsOrComments(changeClassLabelsOrCommentsRequest)
case addCardinalitiesToClassRequest: AddCardinalitiesToClassRequestV2 =>
addCardinalitiesToClass(addCardinalitiesToClassRequest)
case replaceCardinalityRequest: ReplaceCardinalitiesRequestV2 =>
replaceClassCardinalities(replaceCardinalityRequest)
case r: ReplaceClassCardinalitiesRequestV2 => replaceClassCardinalities(r)
case canDeleteCardinalitiesFromClassRequestV2: CanDeleteCardinalitiesFromClassRequestV2 =>
canDeleteCardinalitiesFromClass(canDeleteCardinalitiesFromClassRequestV2)
case deleteCardinalitiesFromClassRequest: DeleteCardinalitiesFromClassRequestV2 =>
Expand Down Expand Up @@ -1473,12 +1472,14 @@ final case class OntologyResponderV2(
}

/**
* Replaces a class's cardinalities with new ones.
* Replace cardinalities of a particular class.
*
* @param request the [[ReplaceCardinalitiesRequestV2]] defining the cardinalities.
* Fails if any of the new cardinalities is not consistent with the ontology or if persistent data is not compatible.
*
* @param request the [[ReplaceClassCardinalitiesRequestV2]] defining the cardinalities.
* @return a [[ReadOntologyV2]] in the internal schema, containing the new class definition.
*/
def replaceClassCardinalities(request: ReplaceCardinalitiesRequestV2): Future[ReadOntologyV2] = {
private def replaceClassCardinalities(request: ReplaceClassCardinalitiesRequestV2): Future[ReadOntologyV2] = {
val taskFuture: () => Future[ReadOntologyV2] = () =>
for {
newModel <- makeUpdatedClassModel(request)
Expand All @@ -1501,7 +1502,7 @@ final case class OntologyResponderV2(
// Make an updated class definition.
// Check that the new cardinalities are valid, and don't add any inherited cardinalities.
// Check that the class definition doesn't refer to any non-shared ontologies in other projects.
private def makeUpdatedClassModel(request: ReplaceCardinalitiesRequestV2): Future[ReadClassInfoV2] = {
private def makeUpdatedClassModel(request: ReplaceClassCardinalitiesRequestV2): Future[ReadClassInfoV2] = {
val newClassInfo = checkRdfTypeOfClassIsClass(request.classInfoContent.toOntologySchema(InternalSchema))
val classIriExternal = newClassInfo.classIri
val classIri = classIriExternal.toOntologySchema(InternalSchema)
Expand Down Expand Up @@ -1577,7 +1578,7 @@ final case class OntologyResponderV2(
}

private def checkLastModificationDateAndCanCardinalitiesBeSet(
request: ReplaceCardinalitiesRequestV2,
request: ReplaceClassCardinalitiesRequestV2,
newModel: ReadClassInfoV2
): Future[ReadClassInfoV2] = {
val classIriExternal = request.classInfoContent.classIri
Expand Down Expand Up @@ -1612,7 +1613,7 @@ final case class OntologyResponderV2(
}

private def replaceClassCardinalitiesInPersistence(
request: ReplaceCardinalitiesRequestV2,
request: ReplaceClassCardinalitiesRequestV2,
newReadClassInfo: ReadClassInfoV2
): Future[ReadOntologyV2] = {
val timeOfUpdate = Instant.now()
Expand All @@ -1630,7 +1631,7 @@ final case class OntologyResponderV2(
}

private def replaceClassCardinalitiesInTripleStore(
request: ReplaceCardinalitiesRequestV2,
request: ReplaceClassCardinalitiesRequestV2,
newReadClassInfo: ReadClassInfoV2,
timeOfUpdate: Instant
): Future[Unit] = {
Expand Down Expand Up @@ -1663,7 +1664,7 @@ final case class OntologyResponderV2(
}

private def replaceClassCardinalitiesInOntologyCache(
request: ReplaceCardinalitiesRequestV2,
request: ReplaceClassCardinalitiesRequestV2,
newReadClassInfo: ReadClassInfoV2,
timeOfUpdate: Instant
): Future[Unit] = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.responders.v2.ontology

import scala.concurrent.ExecutionContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,6 @@ class OntologiesRouteV2(routeData: KnoraRouteData, implicit val runtime: zio.Run
}
}

// Replaces all cardinalities with what was sent. Deleting means send empty
// replace request.
private def replaceCardinalities(): Route =
path(ontologiesBasePath / "cardinalities") {
put {
Expand All @@ -463,7 +461,7 @@ class OntologiesRouteV2(routeData: KnoraRouteData, implicit val runtime: zio.Run
val messageF = for {
user <- getUserADM(requestContext, appConfig)
document = JsonLDUtil.parseJsonLD(reqBody)
msg <- ReplaceCardinalitiesRequestV2.fromJsonLD(document, randomUUID, user, appActor, log)
msg <- ReplaceClassCardinalitiesRequestV2.fromJsonLD(document, randomUUID, user, appActor, log)
} yield msg
val options = RouteUtilV2.getSchemaOptions(requestContext)
RouteUtilV2.runRdfRouteWithFuture(messageF, requestContext, appConfig, appActor, log, ApiV2Complex, options)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.slice.ontology.domain.service

import zio.Task
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.slice.ontology.repo.service

import zio.Task
Expand Down
5 changes: 5 additions & 0 deletions webapi/src/main/scala/org/knora/webapi/util/EitherUtil.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.util

object EitherUtil {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.slice.ontology.domain

import org.knora.webapi.ApiV2Complex
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.slice.ontology.domain.service
import org.apache.jena.query.Dataset
import zio.Ref
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.slice.resourceinfo.api

import zio.test._
Expand Down

0 comments on commit adf1a34

Please sign in to comment.