Skip to content

Commit

Permalink
fix(ontology): DSP-API creates wrong partOfValue property (DEV-216) (#…
Browse files Browse the repository at this point in the history
…1978)

* Create test for partOf property

* add partOfValue to the creation of value properties

* remove unused code

* add documentation about partOf and seqnum

* rename test description
  • Loading branch information
irinaschubert committed Jan 14, 2022
1 parent 729689c commit 27b5c86
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 3 deletions.
19 changes: 17 additions & 2 deletions docs/02-knora-ontologies/knora-base.md
Expand Up @@ -569,8 +569,23 @@ containing metadata about the link. We can visualise the result as the following
![Figure 2](knora-base-fig2.dot.png "Figure 2")

Knora allows a user to see a link if the requesting user has permission to see the source and target resources as well
as the
`kb:LinkValue`.
as the `kb:LinkValue`.

### Part-of (part-whole) relation between resources
A special case of linked resources are _part-of related resources_, i.e. a resource consisting of several other resources.
In order to create a part-of relation between two resources, the resource that is part of another resource needs to have
a property that is a subproperty of `kb:isPartOf`. This property needs to point to the resource class it is part of via
its predicate `knora-api:objectType`.
`kb:isPartOf` itself is a subproperty of `kb:hasLinkTo`. Same as described above for link properties, a corresponding
part-of value property is created automatically. This value property has the same name as the part-of property with
`Value` appended. For example, if in an ontology `data` a property `data:partOf` was defined, the corresponding value
property would be named `data:partOfValue`. This newly created property `data:partOfValue` is defined as a subproperty
of `kb:isPartOfValue`.

Part-of relations are recommended for resources of type `StillImageRepresentation`. In that case, the resource that is
part of another resource needs to have a property that is a subproperty of `knora-api:seqnum` with an integer as value.
A client can then use this information to leaf through the parts of the compound resource (p.ex. to leaf through the
pages of a book like in [this](https://docs.dasch.swiss/DSP-API/01-introduction/example-project/#resource-classes) example).

### Text with Standoff Markup

Expand Down
Expand Up @@ -215,6 +215,8 @@ object OntologyConstants {
val LinkObj: IRI = KnoraBasePrefixExpansion + "LinkObj"
val HasLinkTo: IRI = KnoraBasePrefixExpansion + "hasLinkTo"
val HasLinkToValue: IRI = KnoraBasePrefixExpansion + "hasLinkToValue"
val IsPartOf: IRI = KnoraBasePrefixExpansion + "isPartOf"
val IsPartOfValue: IRI = KnoraBasePrefixExpansion + "isPartOfValue"
val Region: IRI = KnoraBasePrefixExpansion + "Region"
val IsRegionOf: IRI = KnoraBasePrefixExpansion + "isRegionOf"

Expand Down
Expand Up @@ -1950,10 +1950,18 @@ object OntologyHelpers {
objects = Seq(SmartIriLiteralV2(OntologyConstants.KnoraBase.LinkValue.toSmartIri))
))

val subPropertyOf: SmartIri = internalPropertyDef.subPropertyOf match {
case subProps if subProps.contains(OntologyConstants.KnoraBase.IsPartOf.toSmartIri) =>
OntologyConstants.KnoraBase.IsPartOfValue.toSmartIri
case subProps if subProps.contains(OntologyConstants.KnoraBase.HasLinkTo.toSmartIri) =>
OntologyConstants.KnoraBase.HasLinkToValue.toSmartIri
case _ => OntologyConstants.KnoraBase.HasLinkToValue.toSmartIri
}

internalPropertyDef.copy(
propertyIri = linkValuePropIri,
predicates = newPredicates,
subPropertyOf = Set(OntologyConstants.KnoraBase.HasLinkToValue.toSmartIri)
subPropertyOf = Set(subPropertyOf)
)
}

Expand Down
Expand Up @@ -2366,6 +2366,183 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender {
}
}

"create classes anything:wholeThing and anything:partThing with a isPartOf relation and its corresponding value property" in {

// Create class partThing

val partThingClassIri = AnythingOntologyIri.makeEntityIri("partThing")

val partThingClassInfoContent = ClassInfoContentV2(
classIri = partThingClassIri,
predicates = Map(
OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.Rdf.Type.toSmartIri,
objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.Class.toSmartIri))
),
OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.Rdfs.Label.toSmartIri,
objects = Seq(StringLiteralV2("Thing as part", Some("en")))
),
OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri,
objects = Seq(StringLiteralV2("Thing that is part of something else", Some("en")))
)
),
subClassOf = Set(OntologyConstants.KnoraApiV2Complex.Resource.toSmartIri),
ontologySchema = ApiV2Complex
)

responderManager ! CreateClassRequestV2(
classInfoContent = partThingClassInfoContent,
lastModificationDate = anythingLastModDate,
apiRequestID = UUID.randomUUID,
featureFactoryConfig = defaultFeatureFactoryConfig,
requestingUser = anythingAdminUser
)

expectMsgPF(timeout) { case msg: ReadOntologyV2 =>
val externalOntology = msg.toOntologySchema(ApiV2Complex)
val metadata = externalOntology.ontologyMetadata
val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(
throw AssertionException(s"${metadata.ontologyIri} has no last modification date")
)
anythingLastModDate = newAnythingLastModDate
}

// Create class wholeThing

val wholeThingClassIri = AnythingOntologyIri.makeEntityIri("wholeThing")

val wholeThingClassInfoContent = ClassInfoContentV2(
classIri = wholeThingClassIri,
predicates = Map(
OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.Rdf.Type.toSmartIri,
objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.Class.toSmartIri))
),
OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.Rdfs.Label.toSmartIri,
objects = Seq(StringLiteralV2("Thing as a whole", Some("en")))
),
OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri,
objects = Seq(StringLiteralV2("A thing that has multiple parts", Some("en")))
)
),
subClassOf = Set(OntologyConstants.KnoraApiV2Complex.Resource.toSmartIri),
ontologySchema = ApiV2Complex
)

responderManager ! CreateClassRequestV2(
classInfoContent = wholeThingClassInfoContent,
lastModificationDate = anythingLastModDate,
apiRequestID = UUID.randomUUID,
featureFactoryConfig = defaultFeatureFactoryConfig,
requestingUser = anythingAdminUser
)

expectMsgPF(timeout) { case msg: ReadOntologyV2 =>
val externalOntology = msg.toOntologySchema(ApiV2Complex)
val metadata = externalOntology.ontologyMetadata
val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(
throw AssertionException(s"${metadata.ontologyIri} has no last modification date")
)
anythingLastModDate = newAnythingLastModDate
}

// Create property partOf with subject partThing and object wholeThing

val partOfPropertyIri = AnythingOntologyIri.makeEntityIri("partOf")

val partOfPropertyInfoContent = PropertyInfoContentV2(
propertyIri = partOfPropertyIri,
predicates = Map(
OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.Rdf.Type.toSmartIri,
objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.ObjectProperty.toSmartIri))
),
OntologyConstants.KnoraApiV2Complex.SubjectType.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.KnoraApiV2Complex.SubjectType.toSmartIri,
objects = Seq(SmartIriLiteralV2(AnythingOntologyIri.makeEntityIri("partThing")))
),
OntologyConstants.KnoraApiV2Complex.ObjectType.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.KnoraApiV2Complex.ObjectType.toSmartIri,
objects = Seq(SmartIriLiteralV2(AnythingOntologyIri.makeEntityIri("wholeThing")))
),
OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.Rdfs.Label.toSmartIri,
objects = Seq(
StringLiteralV2("is part of", Some("en")),
StringLiteralV2("ist Teil von", Some("de"))
)
),
OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri,
objects = Seq(
StringLiteralV2("Represents a part of a whole relation", Some("en")),
StringLiteralV2("Repräsentiert eine Teil-Ganzes-Beziehung", Some("de"))
)
),
OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri -> PredicateInfoV2(
predicateIri = OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri,
objects = Seq(SmartIriLiteralV2("http://api.knora.org/ontology/salsah-gui/v2#Searchbox".toSmartIri))
)
),
subPropertyOf = Set(OntologyConstants.KnoraBase.IsPartOf.toSmartIri),
ontologySchema = ApiV2Complex
)

responderManager ! CreatePropertyRequestV2(
propertyInfoContent = partOfPropertyInfoContent,
lastModificationDate = anythingLastModDate,
apiRequestID = UUID.randomUUID,
featureFactoryConfig = defaultFeatureFactoryConfig,
requestingUser = anythingAdminUser
)

expectMsgPF(timeout) { case msg: ReadOntologyV2 =>
val externalOntology = msg.toOntologySchema(ApiV2Complex)
assert(externalOntology.properties.size == 1)
val property = externalOntology.properties(partOfPropertyIri)
// check that partOf is a subproperty of knora-api:isPartOf
property.entityInfoContent.subPropertyOf.contains(
OntologyConstants.KnoraApiV2Complex.IsPartOf.toSmartIri
) should ===(true)
val metadata = externalOntology.ontologyMetadata
val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(
throw AssertionException(s"${metadata.ontologyIri} has no last modification date")
)
assert(newAnythingLastModDate.isAfter(anythingLastModDate))
anythingLastModDate = newAnythingLastModDate
}

// Check that the corresponding partOfValue was created
val partOfValuePropertyIri = AnythingOntologyIri.makeEntityIri("partOfValue")

val partOfValuePropGetRequest = PropertiesGetRequestV2(
propertyIris = Set(partOfValuePropertyIri),
allLanguages = true,
requestingUser = anythingAdminUser
)

responderManager ! partOfValuePropGetRequest

expectMsgPF(timeout) { case msg: ReadOntologyV2 =>
val externalOntology = msg.toOntologySchema(ApiV2Complex)
assert(externalOntology.properties.size == 1)
val property = externalOntology.properties(partOfValuePropertyIri)
// check that partOfValue is a subproperty of knora-api:isPartOfValue
property.entityInfoContent.subPropertyOf.contains(
OntologyConstants.KnoraApiV2Complex.IsPartOfValue.toSmartIri
) should ===(true)
val metadata = externalOntology.ontologyMetadata
val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(
throw AssertionException(s"${metadata.ontologyIri} has no last modification date")
)
anythingLastModDate = newAnythingLastModDate
}
}

"change the metadata of the 'anything' ontology" in {
val newLabel = "The modified anything ontology"

Expand Down

0 comments on commit 27b5c86

Please sign in to comment.