Skip to content

Commit

Permalink
fix: Allow setting a cardinality in a three tier class hierarchy
Browse files Browse the repository at this point in the history
When traversing the super-classes for a set of given subclasses for each super-class branch we have to stop the search at the branch from which the search begun so that we do not include the class for which the cardinality should be set.
  • Loading branch information
seakayone committed Mar 22, 2023
1 parent 230fca5 commit 4a4703a
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 26 deletions.
Expand Up @@ -10,8 +10,6 @@ import zio.ZIO
import zio.ZLayer
import zio.macros.accessible

import org.knora.webapi.messages.SmartIri
import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.messages._
import org.knora.webapi.messages.v2.responder.ontologymessages.ClassInfoContentV2
import org.knora.webapi.messages.v2.responder.ontologymessages.ReadClassInfoV2
Expand Down Expand Up @@ -194,7 +192,7 @@ final case class CardinalityServiceLive(
val subclasses =
for {
subclasses <- ontologyRepo.findAllSubclassesBy(check.classIri)
superClasses <- ontologyRepo.findAllSuperClassesBy(toClassIris(subclasses))
superClasses <- ontologyRepo.findAllSuperClassesBy(toClassIris(subclasses), upToClass = check.classIri)
} yield subclasses ::: superClasses
val subclassCardinalityIsNotIncluded = (other: Cardinality) => other.isNotIncludedIn(check.newCardinality)
canSetCheckFor(subclasses, check.propertyIri, subclassCardinalityIsNotIncluded, SubclassCheckFailure)
Expand Down
Expand Up @@ -28,6 +28,15 @@ trait OntologyRepo extends Repository[ReadOntologyV2, InternalIri] {

def findAllSuperClassesBy(classIris: List[InternalIri]): Task[List[ReadClassInfoV2]]

/**
* Finds all super-classes of a particular class up to the given class in a hierarchy.
*
* @param classIris the classes to find the super-classes for
* @param upToClass the class to stop the search at for the particular branch in the class hierarchy
* @return all the super-classes of all other branches in the class hierarchy
*/
def findAllSuperClassesBy(classIris: List[InternalIri], upToClass: InternalIri): Task[List[ReadClassInfoV2]]

def findDirectSubclassesBy(classIri: InternalIri): Task[List[ReadClassInfoV2]]

def findAllSubclassesBy(classIri: InternalIri): Task[List[ReadClassInfoV2]]
Expand Down
Expand Up @@ -102,17 +102,33 @@ final case class OntologyRepoLive(private val converter: IriConverter, private v
override def findAllSuperClassesBy(classIris: List[InternalIri]): Task[List[ReadClassInfoV2]] =
smartIrisMapCache(classIris)((iris, cache) => findAllSuperClassesBy(iris, List.empty, cache))

override def findAllSuperClassesBy(
classIris: List[InternalIri],
upToClass: InternalIri
): Task[List[ReadClassInfoV2]] =
for {
upToClassIri <- toSmartIri(upToClass)
result <- smartIrisMapCache(classIris)((iris, cache) =>
findAllSuperClassesBy(iris, List.empty, cache, Some(upToClassIri))
)
} yield result.distinct

@tailrec
private def findAllSuperClassesBy(
classIris: List[SmartIri],
acc: List[ReadClassInfoV2],
cache: OntologyCacheData
cache: OntologyCacheData,
upToClassIri: Option[SmartIri] = None
): List[ReadClassInfoV2] = {
val superClassesWithSelf = findDirectSuperClassesBy(classIris, cache)
val superClasses = superClassesWithSelf.filter(it => !classIris.contains(it.entityInfoContent.classIri))
superClasses match {
val filteredSuperClasses = upToClassIri match {
case Some(iri) => superClasses.filter(_.entityInfoContent.classIri != iri)
case None => superClasses
}
filteredSuperClasses match {
case Nil => acc
case classes => findAllSuperClassesBy(toClassIris(classes), acc ::: classes, cache)
case classes => findAllSuperClassesBy(toClassIris(classes), acc ::: classes, cache, upToClassIri)
}
}
}
Expand Down
Expand Up @@ -110,7 +110,7 @@ object CardinalityServiceLiveSpec extends ZIOSpecDefault {
.addClassInfo(
ReadClassInfoV2Builder
.builder(classIri)
.setDirectCardinalities(cardinalities)
.addProperty(propertyIri.toInternalIri, cardinality)
)
.addClassInfo(
ReadClassInfoV2Builder
Expand Down Expand Up @@ -139,7 +139,6 @@ object CardinalityServiceLiveSpec extends ZIOSpecDefault {
subClassIri: SmartIri,
propertyIri: SmartIri
): DataCreated = {
val cardinalities = OntologyCacheDataBuilder.cardinalitiesMap(propertyIri, cardinality)
val data =
OntologyCacheDataBuilder.builder
.addOntology(
Expand All @@ -153,7 +152,7 @@ object CardinalityServiceLiveSpec extends ZIOSpecDefault {
ReadClassInfoV2Builder
.builder(subClassIri)
.addSuperClass(classIri)
.setDirectCardinalities(cardinalities)
.addProperty(propertyIri.toInternalIri, cardinality)
)
)
.build
Expand Down Expand Up @@ -202,7 +201,7 @@ object CardinalityServiceLiveSpec extends ZIOSpecDefault {
.addClassInfo(
ReadClassInfoV2Builder
.builder(classIri)
.setDirectCardinalities(OntologyCacheDataBuilder.cardinalitiesMap(propertyIri, ExactlyOne))
.addProperty(propertyIri.toInternalIri, ExactlyOne)
)
)
.build
Expand Down Expand Up @@ -421,16 +420,14 @@ object CardinalityServiceLiveSpec extends ZIOSpecDefault {
|then this is NOT possible because the new cardinality does violate the
|number of times the property is used on each instance
""".stripMargin) {
val propertyCardinality =
OntologyCacheDataBuilder.cardinalitiesMap(Anything.Property.hasOtherThing, Unbounded)
val data = OntologyCacheDataBuilder.builder
.addOntology(
ReadOntologyV2Builder
.builder(Anything.Ontology)
.addClassInfo(
ReadClassInfoV2Builder
.builder(Anything.Class.Thing)
.setDirectCardinalities(propertyCardinality)
.addProperty(Anything.Property.hasOtherThing, Unbounded)
)
)
check(cardinalitiesGen(ZeroOrOne, ExactlyOne)) { newCardinality =>
Expand Down Expand Up @@ -477,16 +474,14 @@ object CardinalityServiceLiveSpec extends ZIOSpecDefault {
),
suite("canSetCardinality with deleted object in property reference")(
test("given a deleted property was used") {
val propertyCardinality =
OntologyCacheDataBuilder.cardinalitiesMap(Anything.Property.hasOtherThing, Unbounded)
val data = OntologyCacheDataBuilder.builder
.addOntology(
ReadOntologyV2Builder
.builder(Anything.Ontology)
.addClassInfo(
ReadClassInfoV2Builder
.builder(Anything.Class.Thing)
.setDirectCardinalities(propertyCardinality)
.addProperty(Anything.Property.hasOtherThing, Unbounded)
)
)
check(cardinalitiesGen(AtLeastOne)) { newCardinality =>
Expand Down Expand Up @@ -526,16 +521,14 @@ object CardinalityServiceLiveSpec extends ZIOSpecDefault {
|then this is possible because the new cardinality does not violate the
|number of times the property is used on the instance
""".stripMargin) {
val propertyCardinality =
OntologyCacheDataBuilder.cardinalitiesMap(Anything.Property.hasOtherThing, Unbounded)
val data = OntologyCacheDataBuilder.builder
.addOntology(
ReadOntologyV2Builder
.builder(Anything.Ontology)
.addClassInfo(
ReadClassInfoV2Builder
.builder(Anything.Class.Thing)
.setDirectCardinalities(propertyCardinality)
.addProperty(Anything.Property.hasOtherThing, Unbounded)
)
)
check(cardinalitiesGen(AtLeastOne, ZeroOrOne, ExactlyOne)) { newCardinality =>
Expand All @@ -561,6 +554,51 @@ object CardinalityServiceLiveSpec extends ZIOSpecDefault {
| <${Anything.Property.hasOtherThing.value}> false .
|
|""".stripMargin)
),
suite("CardinalityServiceLive persistence check")(test(s"""
|Given a three tier subclass hierarchy
|setting required for the middle class property
|should be possible""".stripMargin) {
val ontologyCacheData = OntologyCacheDataBuilder.builder
.addOntology(
ReadOntologyV2Builder
.builder(Biblio.Ontology)
.addClassInfo(ReadClassInfoV2Builder.builder(Biblio.Class.Publication))
.addClassInfo(
ReadClassInfoV2Builder
.builder(Biblio.Class.Article)
.addSuperClass(Biblio.Class.Publication)
.addProperty(Biblio.Property.hasTitle, Unbounded)
)
.addClassInfo(
ReadClassInfoV2Builder
.builder(Biblio.Class.JournalArticle)
.addSuperClass(Biblio.Class.Article)
.addProperty(Biblio.Property.hasTitle, AtLeastOne)
)
)
.build
for {
_ <- OntologyCacheFake.set(ontologyCacheData)
result <- CardinalityService.canSetCardinality(Biblio.Class.Article, Biblio.Property.hasTitle, AtLeastOne)
} yield assertTrue(result.isRight)
}).provide(
commonLayers,
datasetLayerFromTurtle(s"""
|@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|<http://aPublication>
| a <${Biblio.Class.Publication.value}> .
|
|<http://anArticle>
| a <${Biblio.Class.Article.value}> ;
| <${Biblio.Property.hasTitle.value}> "article title" .
|
|<http://aJournalArticle>
| a <${Biblio.Class.JournalArticle.value}> ;
| <${Biblio.Property.hasTitle.value}> "journal article title" .
|""".stripMargin)
)
)
}
Expand Up @@ -15,7 +15,6 @@ import org.knora.webapi.messages.v2.responder.ontologymessages.OntologyMetadataV
import org.knora.webapi.messages.v2.responder.ontologymessages.OwlCardinality.KnoraCardinalityInfo
import org.knora.webapi.messages.v2.responder.ontologymessages.ReadClassInfoV2
import org.knora.webapi.messages.v2.responder.ontologymessages.ReadOntologyV2
import org.knora.webapi.slice.ontology.domain.ReadClassInfoV2Builder.ClassInfoContentV2Builder.BuilderClassInfoContentV2Builder
import org.knora.webapi.slice.ontology.domain.SmartIriConversion.BetterSmartIri
import org.knora.webapi.slice.ontology.domain.SmartIriConversion.BetterSmartIriKeyMap
import org.knora.webapi.slice.ontology.domain.SmartIriConversion.TestSmartIriFromInternalIri
Expand Down Expand Up @@ -116,9 +115,6 @@ object ReadClassInfoV2Builder {

def setEntityInfoContent(builder: ClassInfoContentV2Builder.Builder): Builder = setEntityInfoContent(builder.build)

def setDirectCardinalities(c: Map[SmartIri, KnoraCardinalityInfo]): Builder =
setEntityInfoContent(rci.entityInfoContent.toBuilder.setDirectCardinalities(c))

def setInheritedCardinalities(c: Map[SmartIri, KnoraCardinalityInfo]): Builder =
copy(rci = rci.copy(inheritedCardinalities = c.internal))

Expand All @@ -127,6 +123,15 @@ object ReadClassInfoV2Builder {

def addSuperClass(classIri: SmartIri): Builder =
copy(rci = rci.copy(allBaseClasses = rci.allBaseClasses.prepended(classIri)))

def addProperty(propertyIri: InternalIri, cardinality: Cardinality): Builder =
copy(rci =
rci.copy(entityInfoContent =
rci.entityInfoContent.copy(directCardinalities =
rci.entityInfoContent.directCardinalities + (propertyIri.smartIri -> KnoraCardinalityInfo(cardinality))
)
)
)
}

implicit class BuilderReadClassInfoV2(rci: ReadClassInfoV2) {
Expand All @@ -143,9 +148,6 @@ object ReadClassInfoV2Builder {

case class Builder(cic: ClassInfoContentV2) {
def build: ClassInfoContentV2 = cic

def setDirectCardinalities(c: Map[SmartIri, KnoraCardinalityInfo]): Builder =
copy(cic = cic.copy(directCardinalities = c.internal))
}

implicit class BuilderClassInfoContentV2Builder(cic: ClassInfoContentV2) {
Expand Down

0 comments on commit 4a4703a

Please sign in to comment.