From 55b5d4b1e6494129553a2b70a43af506f09a8d79 Mon Sep 17 00:00:00 2001 From: irinaschubert Date: Thu, 16 Dec 2021 17:59:11 +0100 Subject: [PATCH] fix(candeletecardinalities): return canDoResponse of false instead of throwing an exception for inherited cardinalities (DEV-314) (#1966) * return false instead of throwing exception * add tests * add missing dependencies after reformatting * Update doc * check for usage of inherited properties * remove code smells * Improve code after review * fix failing tests --- .../v2/ontology/Cardinalities.scala | 114 +++-- .../v2/OntologyResponderV2Spec.scala | 392 +++++++++++++++--- 2 files changed, 399 insertions(+), 107 deletions(-) diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/ontology/Cardinalities.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/ontology/Cardinalities.scala index d66803ed85..3769eb5832 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/ontology/Cardinalities.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/ontology/Cardinalities.scala @@ -52,7 +52,6 @@ object Cardinalities { )(implicit ec: ExecutionContext, stringFormatter: StringFormatter, timeout: Timeout): Future[CanDoResponseV2] = { for { cacheData: Cache.OntologyCacheData <- Cache.getCacheData - ontology = cacheData.ontologies(internalOntologyIri) submittedClassDefinition: ClassInfoContentV2 = deleteCardinalitiesFromClassRequest.classInfoContent.toOntologySchema(InternalSchema) @@ -106,9 +105,20 @@ object Cardinalities { cardinalitiesToDelete: Map[SmartIri, Cardinality.KnoraCardinalityInfo] = deleteCardinalitiesFromClassRequest.classInfoContent.toOntologySchema(InternalSchema).directCardinalities - _ = cardinalitiesToDelete.foreach(p => - isCardinalityDefinedOnClass(cacheData, p._1, p._2, internalClassIri, internalOntologyIri) - ) + isDefinedOnClassList: List[Boolean] <- Future + .sequence(cardinalitiesToDelete.map { p => + for { + isDefined: Boolean <- isCardinalityDefinedOnClass( + cacheData, + p._1, + p._2, + internalClassIri, + internalOntologyIri + ) + } yield isDefined + }.toList) + + atLeastOneCardinalityNotDefinedOnClass: Boolean = isDefinedOnClassList.contains(false) // Check if property is used in resources of this class @@ -176,7 +186,9 @@ object Cardinalities { throw BadRequestException(msg) } ) - } yield CanDoResponseV2(!propertyIsUsed) + + // response is true only when property is not used in data and cardinality is defined directly on that class + } yield CanDoResponseV2(!propertyIsUsed && !atLeastOneCardinalityNotDefinedOnClass) } /** @@ -250,9 +262,24 @@ object Cardinalities { cardinalitiesToDelete: Map[SmartIri, Cardinality.KnoraCardinalityInfo] = deleteCardinalitiesFromClassRequest.classInfoContent.toOntologySchema(InternalSchema).directCardinalities - _ = cardinalitiesToDelete.foreach(p => - isCardinalityDefinedOnClass(cacheData, p._1, p._2, internalClassIri, internalOntologyIri) - ) + isDefinedOnClassList: List[Boolean] <- Future + .sequence(cardinalitiesToDelete.map { p => + for { + isDefined: Boolean <- isCardinalityDefinedOnClass( + cacheData, + p._1, + p._2, + internalClassIri, + internalOntologyIri + ) + } yield isDefined + }.toList) + + _ = if (isDefinedOnClassList.contains(false)) { + throw BadRequestException( + "The cardinality is not defined directly on the class and cannot be deleted." + ) + } // Check if property is used in resources of this class @@ -484,25 +511,6 @@ object Cardinalities { .entityInfoContent } yield currentClassDefinition - /** - * Checks if the class is a subclass of `knora-base:Resource`. - * - * @param submittedClassInfoContentV2 the class to check - * @return `true` if the class is a subclass of `knora-base:Resource`, otherwise throws an exception. - */ - def isKnoraResourceClass( - submittedClassInfoContentV2: ClassInfoContentV2 - )(implicit ec: ExecutionContext, stringFormatter: StringFormatter): Future[Boolean] = - if (submittedClassInfoContentV2.subClassOf.contains(KnoraBase.Resource.toSmartIri)) { - FastFuture.successful(true) - } else { - FastFuture.failed( - throw BadRequestException( - s"Class ${submittedClassInfoContentV2.classIri} is not a subclass of ${KnoraBase.Resource.toSmartIri}. $submittedClassInfoContentV2" - ) - ) - } - /** * Check if the cardinality for a property is defined on a class. * @@ -511,7 +519,7 @@ object Cardinalities { * @param cardinalityInfo the cardinality that should be defined for the property. * @param internalClassIri the class we are checking against. * @param internalOntologyIri the ontology containing the class. - * @return `true` if the cardinality is defined on the class, otherwise throws an exception. + * @return `true` if the cardinality is defined on the class, `false` otherwise */ def isCardinalityDefinedOnClass( cacheData: Cache.OntologyCacheData, @@ -521,30 +529,58 @@ object Cardinalities { internalOntologyIri: SmartIri )(implicit ec: ExecutionContext): Future[Boolean] = { val currentOntologyState: ReadOntologyV2 = cacheData.ontologies(internalOntologyIri) - val currentClassState: ClassInfoContentV2 = currentOntologyState.classes + + val readClassInfo: ReadClassInfoV2 = currentOntologyState.classes .getOrElse( internalClassIri, throw BadRequestException( - s"Class ${internalClassIri} does not exist" + s"Class $internalClassIri does not exist" ) ) - .entityInfoContent - val existingCardinality = currentClassState.directCardinalities - .getOrElse( - propertyIri, + + // if cardinality is inherited, it's not directly defined on that class + if (readClassInfo.inheritedCardinalities.keySet.contains(propertyIri)) { + return FastFuture.successful(false) + } + + val currentClassState: ClassInfoContentV2 = readClassInfo.entityInfoContent + val existingCardinality = currentClassState.directCardinalities.get(propertyIri) + existingCardinality match { + case Some(cardinality) => + if (cardinality.cardinality.equals(cardinalityInfo.cardinality)) { + FastFuture.successful(true) + } else { + FastFuture.failed( + throw BadRequestException( + s"Submitted cardinality for property $propertyIri does not match existing cardinality." + ) + ) + } + case None => throw BadRequestException( - s"Cardinality for property ${propertyIri} is not defined." + s"Submitted cardinality for property $propertyIri is not defined for class $internalClassIri." ) - ) - if (existingCardinality.cardinality.equals(cardinalityInfo.cardinality)) { + } + + } + + /** + * Checks if the class is a subclass of `knora-base:Resource`. + * + * @param submittedClassInfoContentV2 the class to check + * @return `true` if the class is a subclass of `knora-base:Resource`, otherwise throws an exception. + */ + def isKnoraResourceClass( + submittedClassInfoContentV2: ClassInfoContentV2 + )(implicit ec: ExecutionContext, stringFormatter: StringFormatter): Future[Boolean] = + if (submittedClassInfoContentV2.subClassOf.contains(KnoraBase.Resource.toSmartIri)) { FastFuture.successful(true) } else { FastFuture.failed( throw BadRequestException( - s"Submitted cardinality for property ${propertyIri} does not match existing cardinality." + s"Class ${submittedClassInfoContentV2.classIri} is not a subclass of ${KnoraBase.Resource.toSmartIri}. $submittedClassInfoContentV2" ) ) } - } } 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 23134ff11e..1ea0626b27 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 @@ -12,7 +12,6 @@ import org.knora.webapi.messages.IriConversions._ import org.knora.webapi.messages.store.triplestoremessages._ import org.knora.webapi.messages.util.KnoraSystemInstances import org.knora.webapi.messages.util.rdf.SparqlSelectResult -import org.knora.webapi.messages.v2.responder.{CanDoResponseV2, SuccessResponseV2} import org.knora.webapi.messages.v2.responder.ontologymessages.Cardinality.KnoraCardinalityInfo import org.knora.webapi.messages.v2.responder.ontologymessages._ import org.knora.webapi.messages.v2.responder.resourcemessages.{ @@ -22,6 +21,7 @@ import org.knora.webapi.messages.v2.responder.resourcemessages.{ ReadResourcesSequenceV2 } import org.knora.webapi.messages.v2.responder.valuemessages.IntegerValueContentV2 +import org.knora.webapi.messages.v2.responder.{CanDoResponseV2, SuccessResponseV2} import org.knora.webapi.messages.{OntologyConstants, SmartIri, StringFormatter} import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.util.MutableTestIri @@ -37,48 +37,65 @@ import scala.language.postfixOps class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { private implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance - + override lazy val rdfDataObjects: Seq[RdfDataObject] = + List(exampleSharedOntology, anythingData, freeTestOntology, freeTestData) private val imagesUser = SharedTestDataADM.imagesUser01 private val imagesProjectIri = SharedTestDataADM.IMAGES_PROJECT_IRI.toSmartIri - private val anythingAdminUser = SharedTestDataADM.anythingAdminUser private val anythingNonAdminUser = SharedTestDataADM.anythingUser1 private val anythingProjectIri = SharedTestDataADM.ANYTHING_PROJECT_IRI.toSmartIri - private val exampleSharedOntology = RdfDataObject( path = "test_data/ontologies/example-box.ttl", name = "http://www.knora.org/ontology/shared/example-box" ) private val anythingData = RdfDataObject(path = "test_data/all_data/anything-data.ttl", name = "http://www.knora.org/data/0001/anything") - private val freeTestOntology = RdfDataObject(path = "test_data/ontologies/freetest-onto.ttl", name = "http://www.knora.org/ontology/0001/freetest") - private val freeTestData = RdfDataObject(path = "test_data/all_data/freetest-data.ttl", name = "http://www.knora.org/data/0001/freetest") - // The default timeout for receiving reply messages from actors. private val timeout = 10.seconds - 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 - private val ExampleSharedOntologyIri = "http://api.knora.org/ontology/shared/example-box/v2".toSmartIri private val IncunabulaOntologyIri = "http://0.0.0.0:3333/ontology/0803/incunabula/v2".toSmartIri private val AnythingOntologyIri = "http://0.0.0.0:3333/ontology/0001/anything/v2".toSmartIri + private val printErrorMessages = false + private var fooLastModDate: Instant = Instant.now + private var barLastModDate: Instant = Instant.now + private var chairLastModDate: Instant = Instant.now private var anythingLastModDate: Instant = Instant.parse("2017-12-19T15:23:42.166Z") private var freetestLastModData: Instant = Instant.parse("2012-12-12T12:12:12.12Z") - private val printErrorMessages = false - - override lazy val rdfDataObjects: Seq[RdfDataObject] = - List(exampleSharedOntology, anythingData, freeTestOntology, freeTestData) + val anythingOntology = "http://0.0.0.0:3333/ontology/0001/anything/v2#" + val anythingThing: IRI = anythingOntology + "Thing" + val anythingHasBoolean: IRI = anythingOntology + "hasBoolean" + val anythingHasColor: IRI = anythingOntology + "hasColor" + val anythingHasDate: IRI = anythingOntology + "hasDate" + val anythingHasDecimal: IRI = anythingOntology + "hasDecimal" + val anythingHasGeometry: IRI = anythingOntology + "hasGeometry" + val anythingHasGeoname: IRI = anythingOntology + "hasGeoname" + val anythingHasInteger: IRI = anythingOntology + "hasInteger" + val anythingHasInterval: IRI = anythingOntology + "hasInterval" + val anythingHasListItem: IRI = anythingOntology + "hasListItem" + val anythingHasName: IRI = anythingOntology + "hasName" + val anythingHasOtherListItem: IRI = anythingOntology + "hasOtherListItem" + val anythingHasOtherThing: IRI = anythingOntology + "hasOtherThing" + val anythingHasOtherThingValue: IRI = anythingOntology + "hasOtherThingValue" + val anythingHasRichtext: IRI = anythingOntology + "hasRichtext" + val anythingHasText: IRI = anythingOntology + "hasText" + val anythingHasThingDocument: IRI = anythingOntology + "hasThingDocument" + val anythingHasThingDocumentValue: IRI = anythingOntology + "hasThingDocumentValue" + val anythingHasThingPicture: IRI = anythingOntology + "hasThingPicture" + val anythingHasThingPictureValue: IRI = anythingOntology + "hasThingPictureValue" + val anythingHasTimeStamp: IRI = anythingOntology + "hasTimeStamp" + val anythingHasUri: IRI = anythingOntology + "hasUri" + val anythingIsPartOfOtherThing: IRI = anythingOntology + "isPartOfOtherThing" + val anythingIsPartOfOtherThingValue: IRI = anythingOntology + "isPartOfOtherThingValue" + val anythingHasStandoffLinkTo: IRI = "http://api.knora.org/ontology/knora-api/v2#hasStandoffLinkTo" + val anythingHasStandoffLinkToValue: IRI = "http://api.knora.org/ontology/knora-api/v2#hasStandoffLinkToValue" private def loadInvalidTestData(rdfDataObjs: List[RdfDataObject]): Unit = { storeManager ! ResetRepositoryContent(rdfDataObjs) @@ -2434,36 +2451,36 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { ) val expectedProperties: Set[SmartIri] = Set( - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasBoolean", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasColor", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasDate", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasDecimal", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasGeometry", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasGeoname", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasInterval", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasListItem", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasName", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasOtherListItem", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasOtherThing", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasOtherThingValue", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasRichtext", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasText", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasThingDocument", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasThingDocumentValue", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasThingPicture", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasThingPictureValue", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasTimeStamp", - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasUri", - "http://0.0.0.0:3333/ontology/0001/anything/v2#isPartOfOtherThing", - "http://0.0.0.0:3333/ontology/0001/anything/v2#isPartOfOtherThingValue", - "http://api.knora.org/ontology/knora-api/v2#hasStandoffLinkTo", - "http://api.knora.org/ontology/knora-api/v2#hasStandoffLinkToValue" + anythingHasBoolean, + anythingHasColor, + anythingHasDate, + anythingHasDecimal, + anythingHasGeometry, + anythingHasGeoname, + anythingHasInteger, + anythingHasInterval, + anythingHasListItem, + anythingHasName, + anythingHasOtherListItem, + anythingHasOtherThing, + anythingHasOtherThingValue, + anythingHasRichtext, + anythingHasText, + anythingHasThingDocument, + anythingHasThingDocumentValue, + anythingHasThingPicture, + anythingHasThingPictureValue, + anythingHasTimeStamp, + anythingHasUri, + anythingIsPartOfOtherThing, + anythingIsPartOfOtherThingValue, + anythingHasStandoffLinkTo, + anythingHasStandoffLinkToValue ).map(_.toSmartIri) val expectedAllBaseClasses: Seq[SmartIri] = Seq( - "http://0.0.0.0:3333/ontology/0001/anything/v2#WildThing".toSmartIri, - "http://0.0.0.0:3333/ontology/0001/anything/v2#Thing".toSmartIri, + (anythingOntology + "WildThing").toSmartIri, + anythingThing.toSmartIri, "http://api.knora.org/ontology/knora-api/v2#Resource".toSmartIri ) @@ -2474,7 +2491,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { readClassInfo.allBaseClasses should ===(expectedAllBaseClasses) readClassInfo.entityInfoContent should ===(classInfoContent) readClassInfo.inheritedCardinalities.keySet - .contains("http://0.0.0.0:3333/ontology/0001/anything/v2#hasInteger".toSmartIri) should ===(false) + .contains(anythingHasInteger.toSmartIri) should ===(false) readClassInfo.allResourcePropertyCardinalities.keySet should ===(expectedProperties) val metadata = externalOntology.ontologyMetadata @@ -2486,6 +2503,248 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { } } + "not allow inherited property to be deleted on subclass" in { + val classIri = AnythingOntologyIri.makeEntityIri("SubThing") + + val classInfoContent = ClassInfoContentV2( + classIri = classIri, + 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("sub thing", Some("en"))) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq(StringLiteralV2("A subclass thing of thing", Some("en"))) + ) + ), + directCardinalities = Map( + AnythingOntologyIri.makeEntityIri("hasName") -> KnoraCardinalityInfo(Cardinality.MayHaveOne) + ), + subClassOf = Set(AnythingOntologyIri.makeEntityIri("Thing")), + ontologySchema = ApiV2Complex + ) + + responderManager ! CreateClassRequestV2( + classInfoContent = classInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + val expectedProperties: Set[SmartIri] = Set( + anythingHasBoolean, + anythingHasColor, + anythingHasDate, + anythingHasDecimal, + anythingHasGeometry, + anythingHasGeoname, + anythingHasInteger, + anythingHasInterval, + anythingHasListItem, + anythingHasName, + anythingHasOtherListItem, + anythingHasOtherThing, + anythingHasOtherThingValue, + anythingHasRichtext, + anythingHasText, + anythingHasThingDocument, + anythingHasThingDocumentValue, + anythingHasThingPicture, + anythingHasThingPictureValue, + anythingHasTimeStamp, + anythingHasUri, + anythingIsPartOfOtherThing, + anythingIsPartOfOtherThingValue, + anythingHasStandoffLinkTo, + anythingHasStandoffLinkToValue + ).map(_.toSmartIri) + + val expectedAllBaseClasses: Seq[SmartIri] = Seq( + (anythingOntology + "SubThing").toSmartIri, + anythingThing.toSmartIri, + "http://api.knora.org/ontology/knora-api/v2#Resource".toSmartIri + ) + + expectMsgPF(timeout) { case msg: ReadOntologyV2 => + val externalOntology = msg.toOntologySchema(ApiV2Complex) + val readClassInfo = externalOntology.classes(classIri) + readClassInfo.allBaseClasses should ===(expectedAllBaseClasses) + readClassInfo.entityInfoContent should ===(classInfoContent) + readClassInfo.inheritedCardinalities.keySet + .contains(anythingHasInteger.toSmartIri) should ===(true) + readClassInfo.allResourcePropertyCardinalities.keySet should ===(expectedProperties) + + 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 + } + + val classInfoContentWithCardinalityToDeleteDontAllow = ClassInfoContentV2( + classIri = classIri, + 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("sub thing", Some("en"))) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq(StringLiteralV2("A subclass thing of thing", Some("en"))) + ) + ), + directCardinalities = Map( + AnythingOntologyIri.makeEntityIri("hasInteger") -> KnoraCardinalityInfo(Cardinality.MayHaveOne) + ), + subClassOf = Set(AnythingOntologyIri.makeEntityIri("Thing")), + ontologySchema = ApiV2Complex + ) + + responderManager ! CanDeleteCardinalitiesFromClassRequestV2( + classInfoContent = classInfoContentWithCardinalityToDeleteDontAllow, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { case msg: CanDoResponseV2 => + assert(!msg.canDo) + } + } + + "allow direct property to be deleted on subclass" in { + val classIri = AnythingOntologyIri.makeEntityIri("OtherSubThing") + + val classInfoContent = ClassInfoContentV2( + classIri = classIri, + 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("other sub thing", Some("en"))) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq(StringLiteralV2("Another subclass thing of thing", Some("en"))) + ) + ), + directCardinalities = Map( + AnythingOntologyIri.makeEntityIri("hasName") -> KnoraCardinalityInfo(Cardinality.MayHaveOne) + ), + subClassOf = Set(AnythingOntologyIri.makeEntityIri("Thing")), + ontologySchema = ApiV2Complex + ) + + responderManager ! CreateClassRequestV2( + classInfoContent = classInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + val expectedProperties: Set[SmartIri] = Set( + anythingHasBoolean, + anythingHasColor, + anythingHasDate, + anythingHasDecimal, + anythingHasGeometry, + anythingHasGeoname, + anythingHasInteger, + anythingHasInterval, + anythingHasListItem, + anythingHasName, + anythingHasOtherListItem, + anythingHasOtherThing, + anythingHasOtherThingValue, + anythingHasRichtext, + anythingHasText, + anythingHasThingDocument, + anythingHasThingDocumentValue, + anythingHasThingPicture, + anythingHasThingPictureValue, + anythingHasTimeStamp, + anythingHasUri, + anythingIsPartOfOtherThing, + anythingIsPartOfOtherThingValue, + anythingHasStandoffLinkTo, + anythingHasStandoffLinkToValue + ).map(_.toSmartIri) + + val expectedAllBaseClasses: Seq[SmartIri] = Seq( + (anythingOntology + "OtherSubThing").toSmartIri, + anythingThing.toSmartIri, + "http://api.knora.org/ontology/knora-api/v2#Resource".toSmartIri + ) + + expectMsgPF(timeout) { case msg: ReadOntologyV2 => + val externalOntology = msg.toOntologySchema(ApiV2Complex) + val readClassInfo = externalOntology.classes(classIri) + readClassInfo.allBaseClasses should ===(expectedAllBaseClasses) + readClassInfo.entityInfoContent should ===(classInfoContent) + readClassInfo.inheritedCardinalities.keySet + .contains(anythingHasName.toSmartIri) should ===(false) + readClassInfo.allResourcePropertyCardinalities.keySet should ===(expectedProperties) + + 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 + } + + val classInfoContentWithCardinalityToDeleteAllow = ClassInfoContentV2( + classIri = classIri, + 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("other sub thing", Some("en"))) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq(StringLiteralV2("Another subclass thing of thing", Some("en"))) + ) + ), + directCardinalities = Map( + AnythingOntologyIri.makeEntityIri("hasName") -> KnoraCardinalityInfo(Cardinality.MayHaveOne) + ), + subClassOf = Set(AnythingOntologyIri.makeEntityIri("Thing")), + ontologySchema = ApiV2Complex + ) + + responderManager ! CanDeleteCardinalitiesFromClassRequestV2( + classInfoContent = classInfoContentWithCardinalityToDeleteAllow, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { case msg: CanDoResponseV2 => + assert(msg.canDo) + } + } + "create a class anything:Nothing with no properties" in { val classIri = AnythingOntologyIri.makeEntityIri("Nothing") @@ -2524,8 +2783,8 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { ) val expectedProperties = Set( - "http://api.knora.org/ontology/knora-api/v2#hasStandoffLinkTo", - "http://api.knora.org/ontology/knora-api/v2#hasStandoffLinkToValue" + anythingHasStandoffLinkTo, + anythingHasStandoffLinkToValue ).map(_.toSmartIri) expectMsgPF(timeout) { case msg: ReadOntologyV2 => @@ -3701,7 +3960,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { ) val expectedAllBaseClasses: Seq[SmartIri] = Seq( - "http://0.0.0.0:3333/ontology/0001/anything/v2#Nothing".toSmartIri, + (anythingOntology + "Nothing").toSmartIri, "http://api.knora.org/ontology/knora-api/v2#Resource".toSmartIri ) @@ -4163,7 +4422,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { ) val expectedAllBaseClasses: Seq[SmartIri] = Seq( - "http://0.0.0.0:3333/ontology/0001/anything/v2#Nothing".toSmartIri, + (anythingOntology + "Nothing").toSmartIri, "http://api.knora.org/ontology/knora-api/v2#Resource".toSmartIri ) @@ -5015,7 +5274,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { objects = Vector(SmartIriLiteralV2(value = "http://www.w3.org/2002/07/owl#Class".toSmartIri)) ) ), - classIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#TestClass".toSmartIri, + classIri = (anythingOntology + "TestClass").toSmartIri, ontologySchema = ApiV2Complex, subClassOf = Set("http://api.knora.org/ontology/knora-api/v2#Resource".toSmartIri) ), @@ -5036,7 +5295,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { responderManager ! CreatePropertyRequestV2( propertyInfoContent = PropertyInfoContentV2( - propertyIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#testTextProp".toSmartIri, + propertyIri = (anythingOntology + "testTextProp").toSmartIri, predicates = Map( "http://www.w3.org/2000/01/rdf-schema#label".toSmartIri -> PredicateInfoV2( predicateIri = "http://www.w3.org/2000/01/rdf-schema#label".toSmartIri, @@ -5049,8 +5308,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { ), "http://api.knora.org/ontology/knora-api/v2#subjectType".toSmartIri -> PredicateInfoV2( predicateIri = "http://api.knora.org/ontology/knora-api/v2#subjectType".toSmartIri, - objects = - Vector(SmartIriLiteralV2(value = "http://0.0.0.0:3333/ontology/0001/anything/v2#TestClass".toSmartIri)) + objects = Vector(SmartIriLiteralV2(value = (anythingOntology + "TestClass").toSmartIri)) ), "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri -> PredicateInfoV2( predicateIri = "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri, @@ -5091,7 +5349,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { responderManager ! CreatePropertyRequestV2( propertyInfoContent = PropertyInfoContentV2( - propertyIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#testIntProp".toSmartIri, + propertyIri = (anythingOntology + "testIntProp").toSmartIri, predicates = Map( "http://www.w3.org/2000/01/rdf-schema#label".toSmartIri -> PredicateInfoV2( predicateIri = "http://www.w3.org/2000/01/rdf-schema#label".toSmartIri, @@ -5104,8 +5362,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { ), "http://api.knora.org/ontology/knora-api/v2#subjectType".toSmartIri -> PredicateInfoV2( predicateIri = "http://api.knora.org/ontology/knora-api/v2#subjectType".toSmartIri, - objects = - Vector(SmartIriLiteralV2(value = "http://0.0.0.0:3333/ontology/0001/anything/v2#TestClass".toSmartIri)) + objects = Vector(SmartIriLiteralV2(value = (anythingOntology + "TestClass").toSmartIri)) ), "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri -> PredicateInfoV2( predicateIri = "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri, @@ -5146,7 +5403,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { responderManager ! CreatePropertyRequestV2( propertyInfoContent = PropertyInfoContentV2( - propertyIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#testLinkProp".toSmartIri, + propertyIri = (anythingOntology + "testLinkProp").toSmartIri, predicates = Map( "http://www.w3.org/2000/01/rdf-schema#label".toSmartIri -> PredicateInfoV2( predicateIri = "http://www.w3.org/2000/01/rdf-schema#label".toSmartIri, @@ -5159,8 +5416,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { ), "http://api.knora.org/ontology/knora-api/v2#subjectType".toSmartIri -> PredicateInfoV2( predicateIri = "http://api.knora.org/ontology/knora-api/v2#subjectType".toSmartIri, - objects = - Vector(SmartIriLiteralV2(value = "http://0.0.0.0:3333/ontology/0001/anything/v2#TestClass".toSmartIri)) + objects = Vector(SmartIriLiteralV2(value = (anythingOntology + "TestClass").toSmartIri)) ), "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri -> PredicateInfoV2( predicateIri = "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri, @@ -5173,8 +5429,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { ), "http://api.knora.org/ontology/knora-api/v2#objectType".toSmartIri -> PredicateInfoV2( predicateIri = "http://api.knora.org/ontology/knora-api/v2#objectType".toSmartIri, - objects = - Vector(SmartIriLiteralV2(value = "http://0.0.0.0:3333/ontology/0001/anything/v2#TestClass".toSmartIri)) + objects = Vector(SmartIriLiteralV2(value = (anythingOntology + "TestClass").toSmartIri)) ), "http://www.w3.org/1999/02/22-rdf-syntax-ns#type".toSmartIri -> PredicateInfoV2( predicateIri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type".toSmartIri, @@ -5207,16 +5462,16 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { objects = Vector(SmartIriLiteralV2(value = "http://www.w3.org/2002/07/owl#Class".toSmartIri)) ) ), - classIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#TestClass".toSmartIri, + classIri = (anythingOntology + "TestClass").toSmartIri, ontologySchema = ApiV2Complex, directCardinalities = Map( - "http://0.0.0.0:3333/ontology/0001/anything/v2#testTextProp".toSmartIri -> KnoraCardinalityInfo( + (anythingOntology + "testTextProp").toSmartIri -> KnoraCardinalityInfo( cardinality = Cardinality.MayHaveOne ), - "http://0.0.0.0:3333/ontology/0001/anything/v2#testIntProp".toSmartIri -> KnoraCardinalityInfo( + (anythingOntology + "testIntProp").toSmartIri -> KnoraCardinalityInfo( cardinality = Cardinality.MayHaveOne ), - "http://0.0.0.0:3333/ontology/0001/anything/v2#testLinkProp".toSmartIri -> KnoraCardinalityInfo( + (anythingOntology + "testLinkProp").toSmartIri -> KnoraCardinalityInfo( cardinality = Cardinality.MayHaveOne ) ) @@ -5244,13 +5499,13 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { objects = Vector(SmartIriLiteralV2(value = "http://www.w3.org/2002/07/owl#Class".toSmartIri)) ) ), - classIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#TestClass".toSmartIri, + classIri = (anythingOntology + "TestClass").toSmartIri, ontologySchema = ApiV2Complex, directCardinalities = Map( - "http://0.0.0.0:3333/ontology/0001/anything/v2#testTextProp".toSmartIri -> KnoraCardinalityInfo( + (anythingOntology + "testTextProp").toSmartIri -> KnoraCardinalityInfo( cardinality = Cardinality.MayHaveOne ), - "http://0.0.0.0:3333/ontology/0001/anything/v2#testIntProp".toSmartIri -> KnoraCardinalityInfo( + (anythingOntology + "testIntProp").toSmartIri -> KnoraCardinalityInfo( cardinality = Cardinality.MayHaveOne ) ) @@ -6087,5 +6342,6 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { loadInvalidTestData(invalidOnto) } + } }