diff --git a/webapi/src/main/scala/org/knora/webapi/messages/util/MessageUtil.scala b/webapi/src/main/scala/org/knora/webapi/messages/util/MessageUtil.scala index ff2bbcf16c..c007c2b210 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/util/MessageUtil.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/util/MessageUtil.scala @@ -34,7 +34,13 @@ import scala.reflect.runtime.{universe => ru} object MessageUtil { // Set of case class field names to skip. - private val fieldsToSkip = Set("stringFormatter", "base64Decoder", "knoraIdUtil", "standoffLinkTagTargetResourceIris") + private val fieldsToSkip = + Set("stringFormatter", + "base64Decoder", + "knoraIdUtil", + "standoffLinkTagTargetResourceIris", + "knoraSettings", + "featureFactoryConfig") /** * Recursively converts a Scala object to Scala source code for constructing the object (with named parameters). This is useful @@ -142,7 +148,7 @@ object MessageUtil { val members: Iterable[String] = objType.members.filter(member => !member.isMethod).flatMap { member => val memberName = member.name.toString.trim - if (!(memberName.contains("$") || memberName.endsWith("Format"))) { + if (!(memberName.contains("$") || memberName.endsWith("Format") || fieldsToSkip.contains(memberName))) { val fieldMirror = try { instanceMirror.reflectField(member.asTerm) } catch { diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/replaceClassCardinalities.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/replaceClassCardinalities.scala.txt index cf9cb48c36..f843d8a352 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/replaceClassCardinalities.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/replaceClassCardinalities.scala.txt @@ -49,32 +49,54 @@ PREFIX xsd: PREFIX knora-base: PREFIX salsah-gui: +@* Delete the existing cardinalities and insert the new ones in separate update operations, + because the WHERE clause for deleting the existing ones returns several solutions. + If the INSERT was done in the same update, it would be run once for each solution, + which would cause redundant blank nodes to be inserted. *@ + DELETE { - GRAPH ?ontologyNamedGraph { - ?ontology knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . - ?class rdfs:subClassOf ?restriction . + GRAPH <@ontologyNamedGraphIri> { + <@classIri> rdfs:subClassOf ?restriction . ?restriction ?restrictionPred ?restrictionObj . } -} INSERT { - GRAPH ?ontologyNamedGraph { - ?ontology knora-base:lastModificationDate "@currentTime"^^xsd:dateTime . +} +@* Ensure that inference is not used in the WHERE clause of this update. *@ +@if(triplestore.startsWith("graphdb")) { + USING +} +WHERE { + GRAPH <@ontologyNamedGraphIri> { + <@ontologyIri> rdf:type owl:Ontology ; + knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . + <@classIri> rdf:type owl:Class . + + OPTIONAL { + <@classIri> rdfs:subClassOf ?restriction . + FILTER isBlank(?restriction) + ?restriction rdf:type owl:Restriction ; + ?restrictionPred ?restrictionObj . + } + } +}; +INSERT { + GRAPH <@ontologyNamedGraphIri> { @for((propertyIri, knoraCardinality) <- newCardinalities) { @defining(Cardinality.knoraCardinality2OwlCardinality(knoraCardinality)) { owlCardinalityInfo => - ?class rdfs:subClassOf [ rdf:type owl:Restriction ; - owl:onProperty <@propertyIri> ; + <@classIri> rdfs:subClassOf [ rdf:type owl:Restriction ; + owl:onProperty <@propertyIri> ; - @owlCardinalityInfo.guiOrder match { - case Some(guiOrder) => { - salsah-gui:guiOrder "@guiOrder"^^xsd:nonNegativeInteger ; - } + @owlCardinalityInfo.guiOrder match { + case Some(guiOrder) => { + salsah-gui:guiOrder "@guiOrder"^^xsd:nonNegativeInteger ; + } - case None => {} - } + case None => {} + } - <@owlCardinalityInfo.owlCardinalityIri> "@owlCardinalityInfo.owlCardinalityValue"^^xsd:nonNegativeInteger ] . + <@owlCardinalityInfo.owlCardinalityIri> "@owlCardinalityInfo.owlCardinalityValue"^^xsd:nonNegativeInteger ] . } } @@ -85,21 +107,29 @@ DELETE { USING } WHERE { - BIND(IRI("@ontologyNamedGraphIri") AS ?ontologyNamedGraph) - BIND(IRI("@ontologyIri") AS ?ontology) - BIND(IRI("@classIri") AS ?class) - - GRAPH ?ontologyNamedGraph { - ?ontology rdf:type owl:Ontology ; + GRAPH <@ontologyNamedGraphIri> { + <@ontologyIri> rdf:type owl:Ontology ; knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . - ?class rdf:type owl:Class . - - OPTIONAL { - ?class rdfs:subClassOf ?restriction . - FILTER isBlank(?restriction) - ?restriction rdf:type owl:Restriction ; - ?restrictionPred ?restrictionObj . - } + <@classIri> rdf:type owl:Class . + } +}; +DELETE { + GRAPH <@ontologyNamedGraphIri> { + <@ontologyIri> knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . + } +} INSERT { + GRAPH <@ontologyNamedGraphIri> { + <@ontologyIri> knora-base:lastModificationDate "@currentTime"^^xsd:dateTime . + } +} +@* Ensure that inference is not used in the WHERE clause of this update. *@ +@if(triplestore.startsWith("graphdb")) { + USING +} +WHERE { + GRAPH <@ontologyNamedGraphIri> { + <@ontologyIri> rdf:type owl:Ontology ; + knora-base:lastModificationDate "@lastModificationDate"^^xsd:dateTime . } } diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala index 1edbda620f..ebeac6d93b 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala @@ -1847,9 +1847,329 @@ class OntologyV2R2RSpec extends R2RSpec { responseAsInput.properties should ===(paramsAsInput.properties) // Check that the ontology's last modification date was updated. - val newAnythingLastModDate = responseAsInput.ontologyMetadata.lastModificationDate.get - assert(newAnythingLastModDate.isAfter(uselessLastModDate)) - uselessLastModDate = newAnythingLastModDate + val newLastModDate = responseAsInput.ontologyMetadata.lastModificationDate.get + assert(newLastModDate.isAfter(uselessLastModDate)) + uselessLastModDate = newLastModDate + } + } + + "create a class with several cardinalities, then remove one of the cardinalities" in { + // Create a class with no cardinalities. + + val createClassRequestJson = + s"""{ + | "@id" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}", + | "@type" : "owl:Ontology", + | "knora-api:lastModificationDate" : { + | "@type" : "xsd:dateTimeStamp", + | "@value" : "$anythingLastModDate" + | }, + | "@graph" : [ { + | "@id" : "anything:TestClass", + | "@type" : "owl:Class", + | "rdfs:label" : { + | "@language" : "en", + | "@value" : "test class" + | }, + | "rdfs:comment" : { + | "@language" : "en", + | "@value" : "A test class" + | }, + | "rdfs:subClassOf" : [ + | { + | "@id": "knora-api:Resource" + | } + | ] + | } ], + | "@context" : { + | "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + | "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", + | "salsah-gui" : "http://api.knora.org/ontology/salsah-gui/v2#", + | "owl" : "http://www.w3.org/2002/07/owl#", + | "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + | "xsd" : "http://www.w3.org/2001/XMLSchema#", + | "anything" : "http://0.0.0.0:3333/ontology/0001/anything/v2#" + | } + |}""".stripMargin + + Post("/v2/ontologies/classes", HttpEntity(RdfMediaTypes.`application/ld+json`, createClassRequestJson)) ~> addCredentials( + BasicHttpCredentials(anythingUsername, password)) ~> ontologiesPath ~> check { + assert(status == StatusCodes.OK, response.toString) + val responseJsonDoc = responseToJsonLDDocument(response) + + val responseAsInput: InputOntologyV2 = + InputOntologyV2.fromJsonLD(responseJsonDoc, parsingMode = TestResponseParsingModeV2).unescape + + anythingLastModDate = responseAsInput.ontologyMetadata.lastModificationDate.get + } + + // Create a text property. + + val createTestTextPropRequestJson = + s"""{ + | "@id" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}", + | "@type" : "owl:Ontology", + | "knora-api:lastModificationDate" : { + | "@type" : "xsd:dateTimeStamp", + | "@value" : "$anythingLastModDate" + | }, + | "@graph" : [ { + | "@id" : "anything:testTextProp", + | "@type" : "owl:ObjectProperty", + | "knora-api:subjectType" : { + | "@id" : "anything:TestClass" + | }, + | "knora-api:objectType" : { + | "@id" : "knora-api:TextValue" + | }, + | "rdfs:comment" : { + | "@language" : "en", + | "@value" : "A test text property" + | }, + | "rdfs:label" : { + | "@language" : "en", + | "@value" : "test text property" + | }, + | "rdfs:subPropertyOf" : { + | "@id" : "knora-api:hasValue" + | } + | } ], + | "@context" : { + | "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + | "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", + | "salsah-gui" : "http://api.knora.org/ontology/salsah-gui/v2#", + | "owl" : "http://www.w3.org/2002/07/owl#", + | "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + | "xsd" : "http://www.w3.org/2001/XMLSchema#", + | "anything" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}#" + | } + |}""".stripMargin + + Post("/v2/ontologies/properties", HttpEntity(RdfMediaTypes.`application/ld+json`, createTestTextPropRequestJson)) ~> addCredentials( + BasicHttpCredentials(anythingUsername, password)) ~> ontologiesPath ~> check { + val responseStr = responseAs[String] + assert(status == StatusCodes.OK, responseStr) + val responseJsonDoc = JsonLDUtil.parseJsonLD(responseStr) + + val responseAsInput: InputOntologyV2 = + InputOntologyV2.fromJsonLD(responseJsonDoc, parsingMode = TestResponseParsingModeV2).unescape + anythingLastModDate = responseAsInput.ontologyMetadata.lastModificationDate.get + + } + + // Create an integer property. + + val createTestIntegerPropRequestJson = + s"""{ + | "@id" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}", + | "@type" : "owl:Ontology", + | "knora-api:lastModificationDate" : { + | "@type" : "xsd:dateTimeStamp", + | "@value" : "$anythingLastModDate" + | }, + | "@graph" : [ { + | "@id" : "anything:testIntProp", + | "@type" : "owl:ObjectProperty", + | "knora-api:subjectType" : { + | "@id" : "anything:TestClass" + | }, + | "knora-api:objectType" : { + | "@id" : "knora-api:IntValue" + | }, + | "rdfs:comment" : { + | "@language" : "en", + | "@value" : "A test int property" + | }, + | "rdfs:label" : { + | "@language" : "en", + | "@value" : "test int property" + | }, + | "rdfs:subPropertyOf" : { + | "@id" : "knora-api:hasValue" + | } + | } ], + | "@context" : { + | "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + | "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", + | "salsah-gui" : "http://api.knora.org/ontology/salsah-gui/v2#", + | "owl" : "http://www.w3.org/2002/07/owl#", + | "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + | "xsd" : "http://www.w3.org/2001/XMLSchema#", + | "anything" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}#" + | } + |}""".stripMargin + + Post("/v2/ontologies/properties", + HttpEntity(RdfMediaTypes.`application/ld+json`, createTestIntegerPropRequestJson)) ~> addCredentials( + BasicHttpCredentials(anythingUsername, password)) ~> ontologiesPath ~> check { + val responseStr = responseAs[String] + assert(status == StatusCodes.OK, responseStr) + val responseJsonDoc = JsonLDUtil.parseJsonLD(responseStr) + + val responseAsInput: InputOntologyV2 = + InputOntologyV2.fromJsonLD(responseJsonDoc, parsingMode = TestResponseParsingModeV2).unescape + anythingLastModDate = responseAsInput.ontologyMetadata.lastModificationDate.get + } + + // Create a link property. + + val createTestLinkPropRequestJson = + s"""{ + | "@id" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}", + | "@type" : "owl:Ontology", + | "knora-api:lastModificationDate" : { + | "@type" : "xsd:dateTimeStamp", + | "@value" : "$anythingLastModDate" + | }, + | "@graph" : [ { + | "@id" : "anything:testLinkProp", + | "@type" : "owl:ObjectProperty", + | "knora-api:subjectType" : { + | "@id" : "anything:TestClass" + | }, + | "knora-api:objectType" : { + | "@id" : "anything:TestClass" + | }, + | "rdfs:comment" : { + | "@language" : "en", + | "@value" : "A test link property" + | }, + | "rdfs:label" : { + | "@language" : "en", + | "@value" : "test link property" + | }, + | "rdfs:subPropertyOf" : { + | "@id" : "knora-api:hasLinkTo" + | } + | } ], + | "@context" : { + | "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + | "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", + | "salsah-gui" : "http://api.knora.org/ontology/salsah-gui/v2#", + | "owl" : "http://www.w3.org/2002/07/owl#", + | "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + | "xsd" : "http://www.w3.org/2001/XMLSchema#", + | "anything" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}#" + | } + |}""".stripMargin + + Post("/v2/ontologies/properties", HttpEntity(RdfMediaTypes.`application/ld+json`, createTestLinkPropRequestJson)) ~> addCredentials( + BasicHttpCredentials(anythingUsername, password)) ~> ontologiesPath ~> check { + val responseStr = responseAs[String] + assert(status == StatusCodes.OK, responseStr) + val responseJsonDoc = JsonLDUtil.parseJsonLD(responseStr) + + val responseAsInput: InputOntologyV2 = + InputOntologyV2.fromJsonLD(responseJsonDoc, parsingMode = TestResponseParsingModeV2).unescape + anythingLastModDate = responseAsInput.ontologyMetadata.lastModificationDate.get + } + + // Add cardinalities to the class. + + val addCardinalitiesRequestJson = + s""" + |{ + | "@id" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}", + | "@type" : "owl:Ontology", + | "knora-api:lastModificationDate" : { + | "@type" : "xsd:dateTimeStamp", + | "@value" : "$anythingLastModDate" + | }, + | "@graph" : [ { + | "@id" : "anything:TestClass", + | "@type" : "owl:Class", + | "rdfs:subClassOf" : [ { + | "@type": "owl:Restriction", + | "owl:maxCardinality" : 1, + | "owl:onProperty" : { + | "@id" : "anything:testTextProp" + | } + | }, + | { + | "@type": "owl:Restriction", + | "owl:maxCardinality" : 1, + | "owl:onProperty" : { + | "@id" : "anything:testIntProp" + | } + | }, + | { + | "@type": "owl:Restriction", + | "owl:maxCardinality" : 1, + | "owl:onProperty" : { + | "@id" : "anything:testLinkProp" + | } + | } ] + | } ], + | "@context" : { + | "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + | "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", + | "owl" : "http://www.w3.org/2002/07/owl#", + | "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + | "xsd" : "http://www.w3.org/2001/XMLSchema#", + | "anything" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}#" + | } + |} + """.stripMargin + + Post("/v2/ontologies/cardinalities", HttpEntity(RdfMediaTypes.`application/ld+json`, addCardinalitiesRequestJson)) ~> addCredentials( + BasicHttpCredentials(anythingUsername, password)) ~> ontologiesPath ~> check { + val responseStr = responseAs[String] + assert(status == StatusCodes.OK, response.toString) + val responseJsonDoc = JsonLDUtil.parseJsonLD(responseStr) + + val responseAsInput: InputOntologyV2 = + InputOntologyV2.fromJsonLD(responseJsonDoc, parsingMode = TestResponseParsingModeV2).unescape + anythingLastModDate = responseAsInput.ontologyMetadata.lastModificationDate.get + } + + // Remove the link value cardinality from to the class. + val params = + s""" + |{ + | "@id" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}", + | "@type" : "owl:Ontology", + | "knora-api:lastModificationDate" : { + | "@type" : "xsd:dateTimeStamp", + | "@value" : "$anythingLastModDate" + | }, + | "@graph" : [ { + | "@id" : "anything:TestClass", + | "@type" : "owl:Class", + | "rdfs:subClassOf" : [ { + | "@type": "owl:Restriction", + | "owl:maxCardinality" : 1, + | "owl:onProperty" : { + | "@id" : "anything:testTextProp" + | } + | }, + | { + | "@type": "owl:Restriction", + | "owl:maxCardinality" : 1, + | "owl:onProperty" : { + | "@id" : "anything:testIntProp" + | } + | } ] + | } ], + | "@context" : { + | "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + | "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", + | "owl" : "http://www.w3.org/2002/07/owl#", + | "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + | "xsd" : "http://www.w3.org/2001/XMLSchema#", + | "anything" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}#" + | } + |} + """.stripMargin + + Put("/v2/ontologies/cardinalities", HttpEntity(RdfMediaTypes.`application/ld+json`, params)) ~> addCredentials( + BasicHttpCredentials(anythingUsername, password)) ~> ontologiesPath ~> check { + val responseStr = responseAs[String] + assert(status == StatusCodes.OK, response.toString) + val responseJsonDoc = JsonLDUtil.parseJsonLD(responseStr) + + val responseAsInput: InputOntologyV2 = + InputOntologyV2.fromJsonLD(responseJsonDoc, parsingMode = TestResponseParsingModeV2).unescape + anythingLastModDate = responseAsInput.ontologyMetadata.lastModificationDate.get } } } 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 be782644d0..0efc94cb19 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 @@ -28,6 +28,7 @@ import org.knora.webapi.exceptions._ 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.SuccessResponseV2 import org.knora.webapi.messages.v2.responder.ontologymessages.Cardinality.KnoraCardinalityInfo import org.knora.webapi.messages.v2.responder.ontologymessages._ @@ -4691,6 +4692,306 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { } } + "create a class with several cardinalities, then remove one of the cardinalities" in { + // Create a class with no cardinalities. + + responderManager ! CreateClassRequestV2( + classInfoContent = ClassInfoContentV2( + predicates = Map( + "http://www.w3.org/2000/01/rdf-schema#label".toSmartIri -> PredicateInfoV2( + predicateIri = "http://www.w3.org/2000/01/rdf-schema#label".toSmartIri, + objects = Vector( + StringLiteralV2( + value = "test class", + language = Some("en") + )) + ), + "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri -> PredicateInfoV2( + predicateIri = "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri, + objects = Vector( + StringLiteralV2( + value = "A test class", + language = Some("en") + )) + ), + "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, + 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, + ontologySchema = ApiV2Complex, + subClassOf = Set("http://api.knora.org/ontology/knora-api/v2#Resource".toSmartIri) + ), + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyV2 => + val newAnythingLastModDate = msg.ontologyMetadata.lastModificationDate + .getOrElse(throw AssertionException(s"${msg.ontologyMetadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + + // Create a text property. + + responderManager ! CreatePropertyRequestV2( + propertyInfoContent = PropertyInfoContentV2( + propertyIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#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, + objects = Vector( + StringLiteralV2( + value = "test text property", + language = Some("en") + )) + ), + "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)) + ), + "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri -> PredicateInfoV2( + predicateIri = "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri, + objects = Vector( + StringLiteralV2( + value = "A test text property", + language = Some("en") + )) + ), + "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://api.knora.org/ontology/knora-api/v2#TextValue".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, + objects = Vector(SmartIriLiteralV2(value = "http://www.w3.org/2002/07/owl#ObjectProperty".toSmartIri)) + ) + ), + subPropertyOf = Set("http://api.knora.org/ontology/knora-api/v2#hasValue".toSmartIri), + ontologySchema = ApiV2Complex + ), + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyV2 => + val newAnythingLastModDate = msg.ontologyMetadata.lastModificationDate + .getOrElse(throw AssertionException(s"${msg.ontologyMetadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + + // Create an integer property. + + responderManager ! CreatePropertyRequestV2( + propertyInfoContent = PropertyInfoContentV2( + propertyIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#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, + objects = Vector( + StringLiteralV2( + value = "test int property", + language = Some("en") + )) + ), + "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)) + ), + "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri -> PredicateInfoV2( + predicateIri = "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri, + objects = Vector( + StringLiteralV2( + value = "A test int property", + language = Some("en") + )) + ), + "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://api.knora.org/ontology/knora-api/v2#IntValue".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, + objects = Vector(SmartIriLiteralV2(value = "http://www.w3.org/2002/07/owl#ObjectProperty".toSmartIri)) + ) + ), + subPropertyOf = Set("http://api.knora.org/ontology/knora-api/v2#hasValue".toSmartIri), + ontologySchema = ApiV2Complex + ), + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyV2 => + val newAnythingLastModDate = msg.ontologyMetadata.lastModificationDate + .getOrElse(throw AssertionException(s"${msg.ontologyMetadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + + // Create a link property. + + responderManager ! CreatePropertyRequestV2( + propertyInfoContent = PropertyInfoContentV2( + propertyIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#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, + objects = Vector( + StringLiteralV2( + value = "test link property", + language = Some("en") + )) + ), + "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)) + ), + "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri -> PredicateInfoV2( + predicateIri = "http://www.w3.org/2000/01/rdf-schema#comment".toSmartIri, + objects = Vector( + StringLiteralV2( + value = "A test link property", + language = Some("en") + )) + ), + "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)) + ), + "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, + objects = Vector(SmartIriLiteralV2(value = "http://www.w3.org/2002/07/owl#ObjectProperty".toSmartIri)) + ) + ), + subPropertyOf = Set("http://api.knora.org/ontology/knora-api/v2#hasLinkTo".toSmartIri), + ontologySchema = ApiV2Complex + ), + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyV2 => + val newAnythingLastModDate = msg.ontologyMetadata.lastModificationDate + .getOrElse(throw AssertionException(s"${msg.ontologyMetadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + + // Add cardinalities to the class. + + responderManager ! AddCardinalitiesToClassRequestV2( + classInfoContent = ClassInfoContentV2( + predicates = Map( + "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, + 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, + ontologySchema = ApiV2Complex, + directCardinalities = Map( + "http://0.0.0.0:3333/ontology/0001/anything/v2#testTextProp".toSmartIri -> KnoraCardinalityInfo( + cardinality = Cardinality.MayHaveOne + ), + "http://0.0.0.0:3333/ontology/0001/anything/v2#testIntProp".toSmartIri -> KnoraCardinalityInfo( + cardinality = Cardinality.MayHaveOne + ), + "http://0.0.0.0:3333/ontology/0001/anything/v2#testLinkProp".toSmartIri -> KnoraCardinalityInfo( + cardinality = Cardinality.MayHaveOne + ) + ), + ), + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyV2 => + val newAnythingLastModDate = msg.ontologyMetadata.lastModificationDate + .getOrElse(throw AssertionException(s"${msg.ontologyMetadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + + // Remove the link value cardinality from the class. + + responderManager ! ChangeCardinalitiesRequestV2( + classInfoContent = ClassInfoContentV2( + predicates = Map( + "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, + 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, + ontologySchema = ApiV2Complex, + directCardinalities = Map( + "http://0.0.0.0:3333/ontology/0001/anything/v2#testTextProp".toSmartIri -> KnoraCardinalityInfo( + cardinality = Cardinality.MayHaveOne + ), + "http://0.0.0.0:3333/ontology/0001/anything/v2#testIntProp".toSmartIri -> KnoraCardinalityInfo( + cardinality = Cardinality.MayHaveOne + ) + ), + ), + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyV2 => + val newAnythingLastModDate = msg.ontologyMetadata.lastModificationDate + .getOrElse(throw AssertionException(s"${msg.ontologyMetadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + + // Check that the correct blank nodes were stored for the cardinalities. + + storeManager ! SparqlSelectRequest( + """PREFIX rdfs: + |PREFIX owl: + | + |SELECT ?cardinalityProp + |WHERE { + | rdfs:subClassOf ?restriction . + | FILTER isBlank(?restriction) + | ?restriction owl:onProperty ?cardinalityProp . + |}""".stripMargin) + + expectMsgPF(timeout) { + case msg: SparqlSelectResult => + assert( + msg.results.bindings.map(_.rowMap("cardinalityProp")).sorted == Seq( + "http://www.knora.org/ontology/0001/anything#testIntProp", + "http://www.knora.org/ontology/0001/anything#testTextProp")) + } + } + "not load an ontology that has no knora-base:attachedToProject" in { val invalidOnto = List( RdfDataObject(