From affee1fc660ca426f035f139b4060b6fcb6bfe84 Mon Sep 17 00:00:00 2001 From: Balduin Landolt <33053745+BalduinLandolt@users.noreply.github.com> Date: Wed, 12 Jun 2024 19:00:01 +0200 Subject: [PATCH] refactor: Remove nested template calls (#3271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Christian Kleinbölting --- .../webapi/messages/StringFormatter.scala | 3 + .../twirl/SparqlTemplateLinkUpdate.scala | 26 ++ .../resources/CreateResourceV2Handler.scala | 300 +++---------- .../repo/service/ResourcesRepoLive.scala | 62 ++- .../sparql/v2/addValueVersion.scala.txt | 216 ++++++++- .../queries/sparql/v2/createClass.scala.txt | 38 +- .../queries/sparql/v2/createLink.scala.txt | 102 ++++- .../sparql/v2/createNewResource.scala.txt | 305 ++++++++++++- .../sparql/v2/createProperty.scala.txt | 84 +++- .../queries/sparql/v2/createValue.scala.txt | 425 +++++++++++++++++- ...ateInsertStatementsForCreateLink.scala.txt | 72 --- ...nsertStatementsForCreateProperty.scala.txt | 31 -- ...teInsertStatementsForCreateValue.scala.txt | 87 ---- ...ateInsertStatementsForPredicates.scala.txt | 53 --- ...InsertStatementsForStandoffLinks.scala.txt | 68 --- ...eInsertStatementsForValueContent.scala.txt | 231 ---------- ...rateWhereStatementsForCreateLink.scala.txt | 78 ---- ...ateWhereStatementsForCreateValue.scala.txt | 154 ------- .../sparql/v2/searchFulltext.scala.txt | 2 - .../repo/service/ResourcesRepoLiveSpec.scala | 279 ++++++++++++ 20 files changed, 1525 insertions(+), 1091 deletions(-) delete mode 100644 webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateLink.scala.txt delete mode 100644 webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateProperty.scala.txt delete mode 100644 webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateValue.scala.txt delete mode 100644 webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForPredicates.scala.txt delete mode 100644 webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForStandoffLinks.scala.txt delete mode 100644 webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForValueContent.scala.txt delete mode 100644 webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateWhereStatementsForCreateLink.scala.txt delete mode 100644 webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateWhereStatementsForCreateValue.scala.txt create mode 100644 webapi/src/test/scala/org/knora/webapi/slice/resources/repo/service/ResourcesRepoLiveSpec.scala diff --git a/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala b/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala index addece5e80..b33db21917 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala @@ -275,6 +275,9 @@ object StringFormatter { def isKnoraOntologyIri(iri: SmartIri): Boolean = iri.isKnoraApiV2DefinitionIri && OntologyConstants.InternalOntologyLabels.contains(iri.getOntologyName) + + def makeValueIri(resourceIri: IRI, uuid: UUID): IRI = + s"$resourceIri/values/${UuidUtil.base64Encode(uuid)}" } /** diff --git a/webapi/src/main/scala/org/knora/webapi/messages/twirl/SparqlTemplateLinkUpdate.scala b/webapi/src/main/scala/org/knora/webapi/messages/twirl/SparqlTemplateLinkUpdate.scala index 8b3cf35c19..68248028ec 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/twirl/SparqlTemplateLinkUpdate.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/twirl/SparqlTemplateLinkUpdate.scala @@ -5,8 +5,12 @@ package org.knora.webapi.messages.twirl +import java.time.Instant +import java.util.UUID + import org.knora.webapi.IRI import org.knora.webapi.messages.SmartIri +import org.knora.webapi.messages.v2.responder.valuemessages.ValueContentV2 /** * Contains instructions that can be given to a SPARQL template for updating direct links and `knora-base:LinkValue` @@ -46,3 +50,25 @@ case class SparqlTemplateLinkUpdate( newLinkValueCreator: IRI, newLinkValuePermissions: String, ) + +final case class NewLinkValueInfo( + linkPropertyIri: IRI, + newLinkValueIri: IRI, + linkTargetIri: IRI, + newReferenceCount: Int, + newLinkValueCreator: IRI, + newLinkValuePermissions: String, + valueUuid: String, +) + +final case class NewValueInfo( + resourceIri: IRI, + propertyIri: IRI, + value: ValueContentV2, + newValueIri: IRI, + newValueUUID: UUID, + valueCreator: IRI, + valuePermissions: String, + creationDate: Instant, + valueHasOrder: Int, +) diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/resources/CreateResourceV2Handler.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/resources/CreateResourceV2Handler.scala index caa8607c43..c418d7f235 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/resources/CreateResourceV2Handler.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/resources/CreateResourceV2Handler.scala @@ -11,6 +11,7 @@ import zio.* import java.time.Instant import dsp.errors.* +import dsp.valueobjects.UuidUtil import org.knora.webapi.* import org.knora.webapi.config.AppConfig import org.knora.webapi.core.MessageRelay @@ -21,8 +22,8 @@ import org.knora.webapi.messages.admin.responder.permissionsmessages.DefaultObje import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionADM import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionType import org.knora.webapi.messages.admin.responder.permissionsmessages.ResourceCreateOperation -import org.knora.webapi.messages.twirl.SparqlTemplateLinkUpdate -import org.knora.webapi.messages.twirl.queries.sparql +import org.knora.webapi.messages.twirl.NewLinkValueInfo +import org.knora.webapi.messages.twirl.NewValueInfo import org.knora.webapi.messages.util.* import org.knora.webapi.messages.util.PermissionUtilADM.AGreaterThanB import org.knora.webapi.messages.util.PermissionUtilADM.PermissionComparisonResult @@ -263,20 +264,14 @@ final case class CreateResourceV2Handler( // Verify that the resource was created. previewOfCreatedResource <- verifyResource( - resourceReadyToCreate = resourceReadyToCreate, + resourceIri = resourceReadyToCreate.resourceIri, requestingUser = createResourceRequestV2.requestingUser, ) } yield previewOfCreatedResource } - private case class GenerateSparqlToCreateMultipleValuesResponseV2( - insertSparql: String, - unverifiedValues: Map[SmartIri, Seq[UnverifiedValueV2]], - hasStandoffLink: Boolean, - ) - /** - * Generates a [[SparqlTemplateResourceToCreate]] describing SPARQL for creating a resource and its values. + * Generates a [[ResourceReadyToCreate]] describing SPARQL for creating a resource and its values. * This method does pre-update checks that have to be done for each new resource individually, even when * multiple resources are being created in a single request. * @@ -427,24 +422,53 @@ final case class CreateResourceV2Handler( requestingUser = requestingUser, ) - // Ask the values responder for SPARQL for generating the values. - sparqlForValuesResponse <- generateSparqlToCreateMultipleValuesV2( - resourceIri = resourceIri, - values = valuesWithValidatedPermissions, - creationDate = creationDate, - requestingUser = requestingUser, - ) + linkUpdates <- generateInsertSparqlForStandoffLinksInMultipleValues( + resourceIri = resourceIri, + values = valuesWithValidatedPermissions.values.flatten, + ) + + valuesWithIndex = valuesWithValidatedPermissions.flatMap { + case (propertyIri: SmartIri, valuesToCreate: Seq[GenerateSparqlForValueInNewResourceV2]) => + valuesToCreate.zipWithIndex.map { + case (valueToCreate: GenerateSparqlForValueInNewResourceV2, valueHasOrder: Int) => + (propertyIri, valueToCreate, valueHasOrder) + } + }.toList + + newValueInfos <- + ZIO.foreach(valuesWithIndex) { case (propertyIri, valueToCreate, valueHasOrder) => + for { + newValueUUID <- + ValuesResponderV2Live.makeNewValueUUID(valueToCreate.customValueIri, valueToCreate.customValueUUID) + newValueIri <- + iriService.checkOrCreateEntityIri( + valueToCreate.customValueIri, + StringFormatter.makeValueIri(resourceIri, newValueUUID), + ) + + // Make a creation date for the value. If a custom creation date is given for a value, consider that otherwise + // use resource creation date for the value. + valueCreationDate: Instant = valueToCreate.customValueCreationDate.getOrElse(creationDate) + } yield NewValueInfo( + resourceIri = resourceIri, + propertyIri = propertyIri.toIri, + value = valueToCreate.valueContent, + newValueIri = newValueIri, + newValueUUID = newValueUUID, + valueCreator = requestingUser.id, + valuePermissions = valueToCreate.permissions, + creationDate = valueCreationDate, + valueHasOrder = valueHasOrder, + ) + } } yield ResourceReadyToCreate( - sparqlTemplateResourceToCreate = SparqlTemplateResourceToCreate( - resourceIri = resourceIri, - permissions = resourcePermissions, - sparqlForValues = sparqlForValuesResponse.insertSparql, - resourceClassIri = internalCreateResource.resourceClassIri.toString, - resourceLabel = internalCreateResource.label, - resourceCreationDate = creationDate, - ), - values = sparqlForValuesResponse.unverifiedValues, - hasStandoffLink = sparqlForValuesResponse.hasStandoffLink, + resourceIri = resourceIri, + resourceClassIri = internalCreateResource.resourceClassIri.toString, + resourceLabel = internalCreateResource.label, + creationDate = creationDate, + permissions = resourcePermissions, + newValueInfos = newValueInfos, + linkUpdates = linkUpdates, ) } @@ -745,16 +769,15 @@ final case class CreateResourceV2Handler( /** * Checks that a resource was created. * - * @param resourceReadyToCreate the resource that should have been created. - * @param projectIri the IRI of the project in which the resource should have been created. - * @param requestingUser the user that attempted to create the resource. + * @param resourceIri the IRI of the resource that should have been created. + * @param projectIri the IRI of the project in which the resource should have been created. + * @param requestingUser the user that attempted to create the resource. * @return a preview of the resource that was created. */ private def verifyResource( - resourceReadyToCreate: ResourceReadyToCreate, + resourceIri: IRI, requestingUser: User, - ): Task[ReadResourcesSequenceV2] = { - val resourceIri = resourceReadyToCreate.sparqlTemplateResourceToCreate.resourceIri + ): Task[ReadResourcesSequenceV2] = getResources .getResourcesV2( resourceIris = Seq(resourceIri), @@ -767,172 +790,11 @@ final case class CreateResourceV2Handler( s"Resource <$resourceIri> was not created. Please report this as a possible bug.", ) } - } - - private def generateSparqlToCreateMultipleValuesV2( - resourceIri: IRI, - values: Map[SmartIri, Seq[GenerateSparqlForValueInNewResourceV2]], - creationDate: Instant, - requestingUser: User, - ): Task[GenerateSparqlToCreateMultipleValuesResponseV2] = - for { - // Generate SPARQL to create links and LinkValues for standoff links in text values. - sparqlForStandoffLinks <- - generateInsertSparqlForStandoffLinksInMultipleValues( - resourceIri = resourceIri, - values = values.values.flatten, - creationDate = creationDate, - ) - - // Generate SPARQL for each value. - sparqlForPropertyValueFutures = - values.map { case (propertyIri: SmartIri, valuesToCreate: Seq[GenerateSparqlForValueInNewResourceV2]) => - val values = ZIO.foreach(valuesToCreate.zipWithIndex) { - case (valueToCreate: GenerateSparqlForValueInNewResourceV2, valueHasOrder: Int) => - generateInsertSparqlWithUnverifiedValue( - resourceIri = resourceIri, - propertyIri = propertyIri, - valueToCreate = valueToCreate, - valueHasOrder = valueHasOrder, - resourceCreationDate = creationDate, - requestingUser = requestingUser, - ) - } - propertyIri -> values - } - - sparqlForPropertyValues <- ZioHelper.sequence(sparqlForPropertyValueFutures) - - // Concatenate all the generated SPARQL. - allInsertSparql: String = - sparqlForPropertyValues.values.flatten - .map(_.insertSparql) - .mkString("\n\n") + "\n\n" + sparqlForStandoffLinks.getOrElse("") - - // Collect all the unverified values. - unverifiedValues: Map[SmartIri, Seq[UnverifiedValueV2]] = - sparqlForPropertyValues.map { case (propertyIri, unverifiedValuesWithSparql) => - propertyIri -> unverifiedValuesWithSparql.map( - _.unverifiedValue, - ) - } - } yield GenerateSparqlToCreateMultipleValuesResponseV2( - insertSparql = allInsertSparql, - unverifiedValues = unverifiedValues, - hasStandoffLink = sparqlForStandoffLinks.isDefined, - ) - - /** - * Represents SPARQL generated to create one of multiple values in a new resource. - * - * @param insertSparql the generated SPARQL. - * @param unverifiedValue an [[UnverifiedValueV2]] representing the value that is to be created. - */ - private case class InsertSparqlWithUnverifiedValue(insertSparql: String, unverifiedValue: UnverifiedValueV2) - - /** - * Generates SPARQL to create one of multiple values in a new resource. - * - * @param resourceIri the IRI of the resource. - * @param propertyIri the IRI of the property that will point to the value. - * @param valueToCreate the value to be created. - * @param valueHasOrder the value's `knora-base:valueHasOrder`. - * @param resourceCreationDate the creation date of the resource. - * @param requestingUser the user making the request. - * @return a [[InsertSparqlWithUnverifiedValue]] containing the generated SPARQL and an [[UnverifiedValueV2]]. - */ - private def generateInsertSparqlWithUnverifiedValue( - resourceIri: IRI, - propertyIri: SmartIri, - valueToCreate: GenerateSparqlForValueInNewResourceV2, - valueHasOrder: Int, - resourceCreationDate: Instant, - requestingUser: User, - ): Task[InsertSparqlWithUnverifiedValue] = - for { - // Make new value UUID. - newValueUUID <- - ValuesResponderV2Live.makeNewValueUUID(valueToCreate.customValueIri, valueToCreate.customValueUUID) - newValueIri <- - iriService.checkOrCreateEntityIri( - valueToCreate.customValueIri, - stringFormatter.makeRandomValueIri(resourceIri, Some(newValueUUID)), - ) - - // Make a creation date for the value. If a custom creation date is given for a value, consider that otherwise - // use resource creation date for the value. - valueCreationDate: Instant = valueToCreate.customValueCreationDate.getOrElse(resourceCreationDate) - - // Generate the SPARQL. - insertSparql: String = - valueToCreate.valueContent match { - case linkValueContentV2: LinkValueContentV2 => - // We're creating a link. - - // Construct a SparqlTemplateLinkUpdate to tell the SPARQL template how to create - // the link and its LinkValue. - val sparqlTemplateLinkUpdate = SparqlTemplateLinkUpdate( - linkPropertyIri = propertyIri.fromLinkValuePropToLinkProp, - directLinkExists = false, - insertDirectLink = true, - deleteDirectLink = false, - linkValueExists = false, - linkTargetExists = linkValueContentV2.referredResourceExists, - newLinkValueIri = newValueIri, - linkTargetIri = linkValueContentV2.referredResourceIri, - currentReferenceCount = 0, - newReferenceCount = 1, - newLinkValueCreator = requestingUser.id, - newLinkValuePermissions = valueToCreate.permissions, - ) - - // Generate SPARQL for the link. - sparql.v2.txt - .generateInsertStatementsForCreateLink( - resourceIri = resourceIri, - linkUpdate = sparqlTemplateLinkUpdate, - creationDate = valueCreationDate, - newValueUUID = newValueUUID, - maybeComment = valueToCreate.valueContent.comment, - maybeValueHasOrder = Some(valueHasOrder), - ) - .toString() - - case otherValueContentV2 => - // We're creating an ordinary value. Generate SPARQL for it. - sparql.v2.txt - .generateInsertStatementsForCreateValue( - resourceIri = resourceIri, - propertyIri = propertyIri, - value = otherValueContentV2, - newValueIri = newValueIri, - newValueUUID = newValueUUID, - linkUpdates = Seq.empty[ - SparqlTemplateLinkUpdate, - ], // This is empty because we have to generate SPARQL for standoff links separately. - valueCreator = requestingUser.id, - valuePermissions = valueToCreate.permissions, - creationDate = valueCreationDate, - maybeValueHasOrder = Some(valueHasOrder), - ) - .toString() - } - } yield InsertSparqlWithUnverifiedValue( - insertSparql = insertSparql, - unverifiedValue = UnverifiedValueV2( - newValueIri = newValueIri, - newValueUUID = newValueUUID, - valueContent = valueToCreate.valueContent.unescape, - permissions = valueToCreate.permissions, - creationDate = valueCreationDate, - ), - ) private def generateInsertSparqlForStandoffLinksInMultipleValues( resourceIri: IRI, values: Iterable[GenerateSparqlForValueInNewResourceV2], - creationDate: Instant, - ): Task[Option[String]] = { + ): Task[Seq[NewLinkValueInfo]] = { // To create LinkValues for the standoff links in the values to be created, we need to compute // the initial reference count of each LinkValue. This is equal to the number of TextValues in the resource // that have standoff links to a particular target resource. @@ -968,40 +830,23 @@ final case class CreateResourceV2Handler( // For each standoff link target IRI, construct a SparqlTemplateLinkUpdate to create a hasStandoffLinkTo property // and one LinkValue with its initial reference count. - val standoffLinkUpdatesFutures: Seq[Task[SparqlTemplateLinkUpdate]] = initialReferenceCounts.toSeq.map { + val standoffLinkUpdatesFutures: Seq[Task[NewLinkValueInfo]] = initialReferenceCounts.toSeq.map { case (targetIri, initialReferenceCount) => for { newValueIri <- makeUnusedValueIri(resourceIri) - } yield SparqlTemplateLinkUpdate( - linkPropertyIri = OntologyConstants.KnoraBase.HasStandoffLinkTo.toSmartIri, - directLinkExists = false, - insertDirectLink = true, - deleteDirectLink = false, - linkValueExists = false, - linkTargetExists = - true, // doesn't matter, the generateInsertStatementsForStandoffLinks template doesn't use it + } yield NewLinkValueInfo( + linkPropertyIri = OntologyConstants.KnoraBase.HasStandoffLinkTo, newLinkValueIri = newValueIri, linkTargetIri = targetIri, - currentReferenceCount = 0, newReferenceCount = initialReferenceCount, newLinkValueCreator = KnoraUserRepo.builtIn.SystemUser.id.value, newLinkValuePermissions = standoffLinkValuePermissions, + valueUuid = UuidUtil.makeRandomBase64EncodedUuid, ) } - for { - standoffLinkUpdates <- ZIO.collectAll(standoffLinkUpdatesFutures) - // Generate SPARQL INSERT statements based on those SparqlTemplateLinkUpdates. - sparqlInsert = - sparql.v2.txt - .generateInsertStatementsForStandoffLinks( - resourceIri = resourceIri, - linkUpdates = standoffLinkUpdates, - creationDate = creationDate, - ) - .toString() - } yield Some(sparqlInsert) + ZIO.collectAll(standoffLinkUpdatesFutures) } else { - ZIO.succeed(None) + ZIO.succeed(Seq.empty[NewLinkValueInfo]) } } @@ -1027,22 +872,3 @@ final case class CreateResourceV2Handler( } } - -/** - * Represents a resource to be created with its index, label, IRI, permissions, and SPARQL for creating its values - * - * @param resourceIri the IRI of the resource to be created. - * @param permissions the permissions user has for creating the new resource. - * @param sparqlForValues the SPARQL for creating the values of the resource. - * @param resourceClassIri the type of the resource to be created. - * @param resourceLabel the label of the resource. - * @param resourceCreationDate the creation date that should be attached to the resource. - */ -case class SparqlTemplateResourceToCreate( - resourceIri: IRI, - permissions: String, - sparqlForValues: String, - resourceClassIri: IRI, - resourceLabel: String, - resourceCreationDate: Instant, -) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/resources/repo/service/ResourcesRepoLive.scala b/webapi/src/main/scala/org/knora/webapi/slice/resources/repo/service/ResourcesRepoLive.scala index 30eca11ce0..9d5f1f96c4 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/resources/repo/service/ResourcesRepoLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/resources/repo/service/ResourcesRepoLive.scala @@ -7,27 +7,24 @@ package org.knora.webapi.slice.resources.repo.service import zio.* +import java.time.Instant + import dsp.constants.SalsahGui.IRI -import org.knora.webapi.messages.SmartIri +import org.knora.webapi.messages.twirl.NewLinkValueInfo +import org.knora.webapi.messages.twirl.NewValueInfo import org.knora.webapi.messages.twirl.queries.sparql -import org.knora.webapi.messages.v2.responder.valuemessages.UnverifiedValueV2 -import org.knora.webapi.responders.v2.resources.SparqlTemplateResourceToCreate import org.knora.webapi.slice.resourceinfo.domain.InternalIri import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update -/** - * Represents a resource that is ready to be created and whose contents can be verified afterwards. - * - * @param sparqlTemplateResourceToCreate a [[SparqlTemplateResourceToCreate]] describing SPARQL for creating - * the resource. - * @param values the resource's values for verification. - * @param hasStandoffLink `true` if the property `knora-base:hasStandoffLinkToValue` was automatically added. - */ case class ResourceReadyToCreate( - sparqlTemplateResourceToCreate: SparqlTemplateResourceToCreate, - values: Map[SmartIri, Seq[UnverifiedValueV2]], - hasStandoffLink: Boolean, + resourceIri: IRI, + resourceClassIri: IRI, + resourceLabel: String, + creationDate: Instant, + permissions: String, + newValueInfos: Seq[NewValueInfo], + linkUpdates: Seq[NewLinkValueInfo], ) trait ResourcesRepo { @@ -48,16 +45,37 @@ final case class ResourcesRepoLive(triplestore: TriplestoreService) extends Reso projectIri: IRI, ): Task[Unit] = triplestore.query( - Update( - sparql.v2.txt.createNewResource( - dataNamedGraph = dataGraphIri.value, - resourceToCreate = resource.sparqlTemplateResourceToCreate, - projectIri = projectIri, - creatorIri = userIri, - ), + ResourcesRepoLive.createNewResourceQuery( + dataGraphIri, + resource, + projectIri, + userIri, ), ) } -object ResourcesRepoLive { val layer = ZLayer.derive[ResourcesRepoLive] } +object ResourcesRepoLive { + val layer = ZLayer.derive[ResourcesRepoLive] + + private[service] def createNewResourceQuery( + dataGraphIri: InternalIri, + resourceToCreate: ResourceReadyToCreate, + projectIri: IRI, + creatorIri: IRI, + ): Update = + Update( + sparql.v2.txt.createNewResource( + dataNamedGraph = dataGraphIri.value, + projectIri = projectIri, + creatorIri = creatorIri, + creationDate = resourceToCreate.creationDate, + resourceIri = resourceToCreate.resourceIri, + resourceClassIri = resourceToCreate.resourceClassIri, + resourceLabel = resourceToCreate.resourceLabel, + permissions = resourceToCreate.permissions, + linkUpdates = resourceToCreate.linkUpdates, + newValueInfos = resourceToCreate.newValueInfos, + ), + ) +} diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/addValueVersion.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/addValueVersion.scala.txt index 3b3cdc479d..89ccdb0a92 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/addValueVersion.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/addValueVersion.scala.txt @@ -3,10 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 *@ +@import dsp.errors.SparqlGenerationException +@import dsp.valueobjects.UuidUtil @import java.time.Instant @import java.util.UUID -@import dsp.valueobjects.UuidUtil -@import org.knora.webapi.IRI +@import org.knora.webapi._ @import org.knora.webapi.messages.SmartIri @import org.knora.webapi.messages.twirl.SparqlTemplateLinkUpdate @import org.knora.webapi.messages.v2.responder.valuemessages._ @@ -93,9 +94,214 @@ DELETE { knora-base:valueHasString """@value.valueHasString""" ; knora-base:valueHasUUID ?currentValueUUID . - @{ - org.knora.webapi.messages.twirl.queries.sparql.v2.txt.generateInsertStatementsForValueContent(value = value, - newValueIri = newValueIri) + + @value match { + + case textValue: TextValueContentV2 => { + + @if(!textValue.valueHasLanguage.isEmpty) { + <@newValueIri> knora-base:valueHasLanguage """@textValue.valueHasLanguage.get""" . + } + + @if(textValue.standoff.nonEmpty) { + + @* Create a Standoff node for each standoff tag. *@ + + @textValue.mappingIri match { + case Some(definedMappingIri) => { + <@newValueIri> knora-base:valueHasMapping <@definedMappingIri> . + } + + case None => {} + } + + <@newValueIri> knora-base:valueHasMaxStandoffStartIndex @textValue.computedMaxStandoffStartIndex.get . + + @for((createStandoff: CreateStandoffTagV2InTriplestore, standoffNodeIndex) <- textValue.prepareForSparqlInsert(newValueIri).zipWithIndex) { + + <@newValueIri> knora-base:valueHasStandoff <@createStandoff.standoffTagInstanceIri> . + + <@createStandoff.standoffTagInstanceIri> + + @* + + Check for optional standoff properties + + *@ + + @if(createStandoff.standoffNode.endIndex.isDefined) { + knora-base:standoffTagHasEndIndex @createStandoff.standoffNode.endIndex.get ; + } + + @if(createStandoff.startParentIri.isDefined) { + knora-base:standoffTagHasStartParent <@createStandoff.startParentIri.get> ; + } + + @if(createStandoff.endParentIri.isDefined) { + knora-base:standoffTagHasEndParent <@createStandoff.endParentIri.get> ; + } + + @if(createStandoff.standoffNode.originalXMLID.isDefined) { + knora-base:standoffTagHasOriginalXMLID """@createStandoff.standoffNode.originalXMLID.get""" ; + } + + @* + + Handle standoff class specific standoff properties + + *@ + @for(createProperty <- createStandoff.standoffNode.attributes) { + + <@createProperty.standoffPropertyIri> @createProperty.rdfValue ; + + } + + knora-base:standoffTagHasStartIndex @createStandoff.standoffNode.startIndex ; + knora-base:standoffTagHasUUID "@{UuidUtil.base64Encode(createStandoff.standoffNode.uuid)}" ; + knora-base:standoffTagHasStart @createStandoff.standoffNode.startPosition ; + knora-base:standoffTagHasEnd @createStandoff.standoffNode.endPosition ; + rdf:type <@createStandoff.standoffNode.standoffTagClassIri> . + + } + + } + } + + + case intValue: IntegerValueContentV2 => { + + <@newValueIri> knora-base:valueHasInteger @intValue.valueHasInteger . + + } + + case decimalValue: DecimalValueContentV2 => { + + <@newValueIri> knora-base:valueHasDecimal "@decimalValue.valueHasDecimal"^^xsd:decimal . + + } + + case booleanValue: BooleanValueContentV2 => { + + <@newValueIri> knora-base:valueHasBoolean @booleanValue.valueHasBoolean . + + } + + case uriValue: UriValueContentV2 => { + + <@newValueIri> knora-base:valueHasUri """@uriValue.valueHasUri"""^^xsd:anyURI . + + } + + case dateValue: DateValueContentV2 => { + + <@newValueIri> knora-base:valueHasStartJDN @dateValue.valueHasStartJDN ; + knora-base:valueHasEndJDN @dateValue.valueHasEndJDN ; + knora-base:valueHasStartPrecision "@dateValue.valueHasStartPrecision" ; + knora-base:valueHasEndPrecision "@dateValue.valueHasEndPrecision" ; + knora-base:valueHasCalendar "@dateValue.valueHasCalendar" . + + } + + case colorValue: ColorValueContentV2 => { + + <@newValueIri> knora-base:valueHasColor """@colorValue.valueHasColor""" . + + } + + case geometryValue: GeomValueContentV2 => { + + <@newValueIri> knora-base:valueHasGeometry """@geometryValue.valueHasGeometry""" . + + } + + case fileValueContentV2: FileValueContentV2 => { + <@newValueIri> knora-base:internalFilename """@fileValueContentV2.fileValue.internalFilename""" ; + knora-base:internalMimeType """@fileValueContentV2.fileValue.internalMimeType""" . + + @fileValueContentV2.fileValue.originalFilename match { + case Some(definedOriginalFilename) => { + <@newValueIri> knora-base:originalFilename """@definedOriginalFilename""" . + } + + case None => {} + } + + @fileValueContentV2.fileValue.originalMimeType match { + case Some(definedOriginalMimeType) => { + <@newValueIri> knora-base:originalMimeType """@definedOriginalMimeType""" . + } + + case None => {} + } + + @fileValueContentV2 match { + case stillImageFileValue: StillImageFileValueContentV2 => { + <@newValueIri> knora-base:dimX @stillImageFileValue.dimX ; + knora-base:dimY @stillImageFileValue.dimY . + } + + case stillImageFileValue: StillImageExternalFileValueContentV2 => { + <@newValueIri> knora-base:externalUrl """@stillImageFileValue.externalUrl.value.toString""" . + } + + case documentFileValue: DocumentFileValueContentV2 => { + @documentFileValue.dimX match { + case Some(definedDimX) => { + <@newValueIri> knora-base:dimX @definedDimX . + } + + case None => {} + } + + @documentFileValue.dimY match { + case Some(definedDimY) => { + <@newValueIri> knora-base:dimY @definedDimY . + } + + case None => {} + } + + @documentFileValue.pageCount match { + case Some(definedPageCount) => { + <@newValueIri> knora-base:pageCount @definedPageCount . + } + + case None => {} + } + } + + case _ => {} + } + } + + case listValue: HierarchicalListValueContentV2 => { + + <@newValueIri> knora-base:valueHasListNode <@listValue.valueHasListNode> . + + } + + case intervalValue: IntervalValueContentV2 => { + + <@newValueIri> knora-base:valueHasIntervalStart "@intervalValue.valueHasIntervalStart"^^xsd:decimal ; + knora-base:valueHasIntervalEnd "@intervalValue.valueHasIntervalEnd"^^xsd:decimal . + + } + + case timeValue: TimeValueContentV2 => { + + <@newValueIri> knora-base:valueHasTimeStamp "@timeValue.valueHasTimeStamp"^^xsd:dateTime . + + } + + case geonameValue: GeonameValueContentV2 => { + + <@newValueIri> knora-base:valueHasGeonameCode """@geonameValue.valueHasGeonameCode""" . + + } + + case other => { + @{throw SparqlGenerationException(s"Value object $other is not supported in this SPARQL template"); ()} + } } @* Insert the value's comment, if given. *@ diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createClass.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createClass.scala.txt index 7f2b0505b6..50f79ec8ff 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createClass.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createClass.scala.txt @@ -42,11 +42,39 @@ DELETE { @* Insert the class. *@ - @{ - org.knora.webapi.messages.twirl.queries.sparql.v2.txt.generateInsertStatementsForPredicates( - entityIri = classDef.classIri, - predicates = classDef.predicates.values - ) + @for(predicate <- classDef.predicates.values) { + + @for(obj <- predicate.objects) { + + @obj match { + + case SmartIriLiteralV2(iri) => { + + <@classDef.classIri> <@predicate.predicateIri> <@iri> . + + } + + case StringLiteralV2(text, Some(lang)) => { + + <@classDef.classIri> <@predicate.predicateIri> """@text"""@@@lang . + + } + + case StringLiteralV2(text, None) => { + + <@classDef.classIri> <@predicate.predicateIri> """@text""" . + + } + + case BooleanLiteralV2(booleanVal) => { + + <@classDef.classIri> <@predicate.predicateIri> "@booleanVal"^^xsd:boolean . + + } + } + + } + } @for(baseClass <- classDef.subClassOf) { diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createLink.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createLink.scala.txt index 718d9d880f..71c6930704 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createLink.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createLink.scala.txt @@ -5,6 +5,8 @@ @import java.time.Instant @import java.util.UUID +@import dsp.errors.SparqlGenerationException +@import dsp.valueobjects.UuidUtil @import org.knora.webapi._ @import org.knora.webapi.messages.StringFormatter @import org.knora.webapi.messages.twirl.SparqlTemplateLinkUpdate @@ -46,15 +48,41 @@ DELETE { @* Update the link source's last modification date. *@ ?resource knora-base:lastModificationDate "@creationDate"^^xsd:dateTime . - @{ - org.knora.webapi.messages.twirl.queries.sparql.v2.txt.generateInsertStatementsForCreateLink(resourceIri = resourceIri, - linkUpdate = linkUpdate, - creationDate = creationDate, - newValueUUID = newValueUUID, - maybeComment = maybeComment, - maybeValueHasOrder = None) + + # Value: @linkUpdate.newLinkValueIri + # Property: @linkUpdate.linkPropertyIri + + @* Insert a direct link between the source and target resources. *@ + @if(linkUpdate.insertDirectLink) { + <@resourceIri> <@linkUpdate.linkPropertyIri> <@linkUpdate.linkTargetIri> . + } else { + @{throw SparqlGenerationException(s"LinkUpdate.insertDirectLink must be true in this SPARQL template (for value @index)"); ()} } + @* Insert a LinkValue describing the link. *@ + <@linkUpdate.newLinkValueIri> rdf:type knora-base:LinkValue ; + rdf:subject <@resourceIri> ; + rdf:predicate <@linkUpdate.linkPropertyIri> ; + rdf:object <@linkUpdate.linkTargetIri> ; + knora-base:valueHasString "@linkUpdate.linkTargetIri"^^xsd:string ; + knora-base:valueHasRefCount @linkUpdate.newReferenceCount ; + @maybeComment match { + case Some(comment) => { + knora-base:valueHasComment """@comment""" ; + } + + case None => {} + } + knora-base:valueHasOrder ?nextOrder ; + knora-base:isDeleted false ; + knora-base:valueHasUUID "@{UuidUtil.base64Encode(newValueUUID)}" ; + knora-base:valueCreationDate "@creationDate"^^xsd:dateTime ; + knora-base:attachedToUser <@linkUpdate.newLinkValueCreator> ; + knora-base:hasPermissions "@linkUpdate.newLinkValuePermissions"^^xsd:string . + + @* Attach the new LinkValue to its containing resource. *@ + <@resourceIri> <@{linkUpdate.linkPropertyIri}Value> <@linkUpdate.newLinkValueIri> . + } } @@ -74,8 +102,62 @@ WHERE { ?resource knora-base:lastModificationDate ?resourceLastModificationDate . } - @{ - org.knora.webapi.messages.twirl.queries.sparql.v2.txt.generateWhereStatementsForCreateLink(resourceIri = resourceIri, - linkUpdate = linkUpdate) + # Value: @linkUpdate.newLinkValueIri + # Property: @linkUpdate.linkPropertyIri + + BIND(IRI("@linkUpdate.linkPropertyIri") AS ?linkProperty) + BIND(IRI("@{linkUpdate.linkPropertyIri}Value") AS ?linkValueProperty) + BIND(IRI("@linkUpdate.newLinkValueIri") AS ?newLinkValue) + BIND(IRI("@linkUpdate.linkTargetIri") AS ?linkTarget) + + @if(linkUpdate.linkTargetExists) { + + @* Make sure the link target is a knora-base:Resource. *@ + + ?linkTarget rdf:type ?linkTargetClass . + ?linkTargetClass rdfs:subClassOf* knora-base:Resource . + + @* Do nothing if the target resource belongs to the wrong OWL class. *@ + + ?linkProperty knora-base:objectClassConstraint ?expectedTargetClass . + ?linkTargetClass rdfs:subClassOf* ?expectedTargetClass . + + @* Do nothing if the target resource doesn't exist or is marked as deleted. *@ + + ?linkTarget knora-base:isDeleted false . + + @* Do nothing if the source resource's OWL class has no cardinality for the link property. *@ + ?resourceClass rdfs:subClassOf* ?restriction . + ?restriction a owl:Restriction . + ?restriction owl:onProperty ?linkProperty . + } + + @if(linkUpdate.directLinkExists) { + @{throw SparqlGenerationException("linkUpdate.directLinkExists must be false in this SPARQL template"); ()} + } + + @if(linkUpdate.linkValueExists) { + @{throw SparqlGenerationException("linkUpdate.linkValueExists must be false in this SPARQL template"); ()} + } + + @* + + Bind a variable for knora-base:valueHasOrder for the new value. + + We have to do a subquery to determine the next available knora-base:valueHasOrder. This works as follows: if the + property already has one or more (non-deleted) values in the resource, find the one with the highest order, add 1 + to its order, and give the resulting order to the new value. Otherwise, give the new value an order of 0. + + *@ + + { + SELECT ((MAX(?order)) AS ?maxOrder) (IF(BOUND(?maxOrder), ?maxOrder + 1, 0) AS ?nextOrder) + WHERE { + <@resourceIri> <@{linkUpdate.linkPropertyIri}Value> ?otherLinkValue . + ?otherLinkValue knora-base:valueHasOrder ?order ; + knora-base:isDeleted false . + } + } + } diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createNewResource.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createNewResource.scala.txt index 30451bf404..f09e9782d9 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createNewResource.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createNewResource.scala.txt @@ -6,20 +6,26 @@ @import java.time.Instant @import org.knora.webapi.IRI @import org.knora.webapi.messages.v2.responder.valuemessages._ -@import org.knora.webapi.responders.v2.resources.SparqlTemplateResourceToCreate +@import org.knora.webapi.messages.twirl.NewLinkValueInfo +@import org.knora.webapi.messages.twirl.NewValueInfo +@import dsp.valueobjects.UuidUtil +@import dsp.errors.SparqlGenerationException +@import java.util.UUID @** - * Creates new resources. - * - * @param dataNamedGraph the named graph in which the project stores its data. - * @param resourcesToCreate a collection of resources to be created. - * @param projectIri the IRI of the project in which the resources are to be created. - * @param creatorIri the IRI of the creator of the resources. + * Creates a new resource. *@ @(dataNamedGraph: IRI, - resourceToCreate: SparqlTemplateResourceToCreate, projectIri: IRI, - creatorIri: IRI) + creatorIri: IRI, + creationDate: Instant, + resourceIri: IRI, + resourceClassIri: IRI, + resourceLabel: String, + permissions: String, + linkUpdates: Seq[NewLinkValueInfo], + newValueInfos: Seq[NewValueInfo] +) PREFIX rdf: PREFIX rdfs: @@ -29,14 +35,285 @@ PREFIX knora-base: INSERT DATA { GRAPH <@dataNamedGraph> { - <@resourceToCreate.resourceIri> rdf:type <@resourceToCreate.resourceClassIri> ; + <@resourceIri> rdf:type <@resourceClassIri> ; knora-base:isDeleted false ; knora-base:attachedToUser <@creatorIri> ; knora-base:attachedToProject <@projectIri> ; - rdfs:label """@resourceToCreate.resourceLabel""" ; - knora-base:hasPermissions "@resourceToCreate.permissions" ; - knora-base:creationDate "@resourceToCreate.resourceCreationDate"^^xsd:dateTime . + rdfs:label """@resourceLabel""" ; + knora-base:hasPermissions "@permissions" ; + knora-base:creationDate "@creationDate"^^xsd:dateTime . - @resourceToCreate.sparqlForValues + @for(newValueInfo <- newValueInfos) { + # Value: @newValueInfo.newValueIri + # Property: @newValueInfo.propertyIri + + @* Construct the value. *@ + <@newValueInfo.newValueIri> rdf:type <@newValueInfo.value.valueType> ; + knora-base:isDeleted false ; + knora-base:valueHasString """@newValueInfo.value.valueHasString""" ; + knora-base:valueHasUUID "@{UuidUtil.base64Encode(newValueInfo.newValueUUID)}" . + + + @newValueInfo.value match { + + case linkValueContentV2: LinkValueContentV2 => { + + <@newValueInfo.resourceIri> <@newValueInfo.propertyIri.stripSuffix("Value")> <@linkValueContentV2.referredResourceIri> . + + <@newValueInfo.newValueIri> rdf:subject <@newValueInfo.resourceIri> ; + rdf:predicate <@newValueInfo.propertyIri.stripSuffix("Value")> ; + rdf:object <@linkValueContentV2.referredResourceIri> ; + knora-base:valueHasRefCount 1 . + + } + + case textValue: TextValueContentV2 => { + + @if(!textValue.valueHasLanguage.isEmpty) { + <@newValueInfo.newValueIri> knora-base:valueHasLanguage """@textValue.valueHasLanguage.get""" . + } + + @if(textValue.standoff.nonEmpty) { + + @* Create a Standoff node for each standoff tag. *@ + + @textValue.mappingIri match { + case Some(definedMappingIri) => { + <@newValueInfo.newValueIri> knora-base:valueHasMapping <@definedMappingIri> . + } + + case None => {} + } + + <@newValueInfo.newValueIri> knora-base:valueHasMaxStandoffStartIndex @textValue.computedMaxStandoffStartIndex.get . + + @for((createStandoff: CreateStandoffTagV2InTriplestore, standoffNodeIndex) <- textValue.prepareForSparqlInsert(newValueInfo.newValueIri).zipWithIndex) { + + <@newValueInfo.newValueIri> knora-base:valueHasStandoff <@createStandoff.standoffTagInstanceIri> . + + <@createStandoff.standoffTagInstanceIri> + + @* + + Check for optional standoff properties + + *@ + + @if(createStandoff.standoffNode.endIndex.isDefined) { + knora-base:standoffTagHasEndIndex @createStandoff.standoffNode.endIndex.get ; + } + + @if(createStandoff.startParentIri.isDefined) { + knora-base:standoffTagHasStartParent <@createStandoff.startParentIri.get> ; + } + + @if(createStandoff.endParentIri.isDefined) { + knora-base:standoffTagHasEndParent <@createStandoff.endParentIri.get> ; + } + + @if(createStandoff.standoffNode.originalXMLID.isDefined) { + knora-base:standoffTagHasOriginalXMLID """@createStandoff.standoffNode.originalXMLID.get""" ; + } + + @* + + Handle standoff class specific standoff properties + + *@ + @for(createProperty <- createStandoff.standoffNode.attributes) { + + <@createProperty.standoffPropertyIri> @createProperty.rdfValue ; + + } + + knora-base:standoffTagHasStartIndex @createStandoff.standoffNode.startIndex ; + knora-base:standoffTagHasUUID "@{UuidUtil.base64Encode(createStandoff.standoffNode.uuid)}" ; + knora-base:standoffTagHasStart @createStandoff.standoffNode.startPosition ; + knora-base:standoffTagHasEnd @createStandoff.standoffNode.endPosition ; + rdf:type <@createStandoff.standoffNode.standoffTagClassIri> . + + } + + } + } + + + case intValue: IntegerValueContentV2 => { + + <@newValueInfo.newValueIri> knora-base:valueHasInteger @intValue.valueHasInteger . + + } + + case decimalValue: DecimalValueContentV2 => { + + <@newValueInfo.newValueIri> knora-base:valueHasDecimal "@decimalValue.valueHasDecimal"^^xsd:decimal . + + } + + case booleanValue: BooleanValueContentV2 => { + + <@newValueInfo.newValueIri> knora-base:valueHasBoolean @booleanValue.valueHasBoolean . + + } + + case uriValue: UriValueContentV2 => { + + <@newValueInfo.newValueIri> knora-base:valueHasUri """@uriValue.valueHasUri"""^^xsd:anyURI . + + } + + case dateValue: DateValueContentV2 => { + + <@newValueInfo.newValueIri> knora-base:valueHasStartJDN @dateValue.valueHasStartJDN ; + knora-base:valueHasEndJDN @dateValue.valueHasEndJDN ; + knora-base:valueHasStartPrecision "@dateValue.valueHasStartPrecision" ; + knora-base:valueHasEndPrecision "@dateValue.valueHasEndPrecision" ; + knora-base:valueHasCalendar "@dateValue.valueHasCalendar" . + + } + + case colorValue: ColorValueContentV2 => { + + <@newValueInfo.newValueIri> knora-base:valueHasColor """@colorValue.valueHasColor""" . + + } + + case geometryValue: GeomValueContentV2 => { + + <@newValueInfo.newValueIri> knora-base:valueHasGeometry """@geometryValue.valueHasGeometry""" . + + } + + case fileValueContentV2: FileValueContentV2 => { + <@newValueInfo.newValueIri> knora-base:internalFilename """@fileValueContentV2.fileValue.internalFilename""" ; + knora-base:internalMimeType """@fileValueContentV2.fileValue.internalMimeType""" . + + @fileValueContentV2.fileValue.originalFilename match { + case Some(definedOriginalFilename) => { + <@newValueInfo.newValueIri> knora-base:originalFilename """@definedOriginalFilename""" . + } + + case None => {} + } + + @fileValueContentV2.fileValue.originalMimeType match { + case Some(definedOriginalMimeType) => { + <@newValueInfo.newValueIri> knora-base:originalMimeType """@definedOriginalMimeType""" . + } + + case None => {} + } + + @fileValueContentV2 match { + case stillImageFileValue: StillImageFileValueContentV2 => { + <@newValueInfo.newValueIri> knora-base:dimX @stillImageFileValue.dimX ; + knora-base:dimY @stillImageFileValue.dimY . + } + + case stillImageFileValue: StillImageExternalFileValueContentV2 => { + <@newValueInfo.newValueIri> knora-base:externalUrl """@stillImageFileValue.externalUrl.value.toString""" . + } + + case documentFileValue: DocumentFileValueContentV2 => { + @documentFileValue.dimX match { + case Some(definedDimX) => { + <@newValueInfo.newValueIri> knora-base:dimX @definedDimX . + } + + case None => {} + } + + @documentFileValue.dimY match { + case Some(definedDimY) => { + <@newValueInfo.newValueIri> knora-base:dimY @definedDimY . + } + + case None => {} + } + + @documentFileValue.pageCount match { + case Some(definedPageCount) => { + <@newValueInfo.newValueIri> knora-base:pageCount @definedPageCount . + } + + case None => {} + } + } + + case _ => {} + } + } + + case listValue: HierarchicalListValueContentV2 => { + + <@newValueInfo.newValueIri> knora-base:valueHasListNode <@listValue.valueHasListNode> . + + } + + case intervalValue: IntervalValueContentV2 => { + + <@newValueInfo.newValueIri> knora-base:valueHasIntervalStart "@intervalValue.valueHasIntervalStart"^^xsd:decimal ; + knora-base:valueHasIntervalEnd "@intervalValue.valueHasIntervalEnd"^^xsd:decimal . + + } + + case timeValue: TimeValueContentV2 => { + + <@newValueInfo.newValueIri> knora-base:valueHasTimeStamp "@timeValue.valueHasTimeStamp"^^xsd:dateTime . + + } + + case geonameValue: GeonameValueContentV2 => { + + <@newValueInfo.newValueIri> knora-base:valueHasGeonameCode """@geonameValue.valueHasGeonameCode""" . + + } + + case other => { + @{throw SparqlGenerationException(s"Value object $other is not supported in this SPARQL template"); ()} + } + } + + @* Insert the value's comment, if given. *@ + @newValueInfo.value.comment match { + case Some(commentStr) => { + <@newValueInfo.newValueIri> knora-base:valueHasComment """@commentStr""" . + } + + case None => {} + } + + <@newValueInfo.newValueIri> knora-base:attachedToUser <@newValueInfo.valueCreator> ; + knora-base:hasPermissions "@newValueInfo.valuePermissions"^^xsd:string ; + knora-base:valueHasOrder @newValueInfo.valueHasOrder ; + knora-base:valueCreationDate "@newValueInfo.creationDate"^^xsd:dateTime . + + + + @* Attach the value to the resource. *@ + <@newValueInfo.resourceIri> <@newValueInfo.propertyIri> <@newValueInfo.newValueIri> . + } + + + @for((linkUpdate, linkValueIndex) <- linkUpdates.zipWithIndex) { + + <@resourceIri> <@linkUpdate.linkPropertyIri> <@linkUpdate.linkTargetIri> . + + @* Insert a LinkValue for the resource reference. *@ + <@linkUpdate.newLinkValueIri> rdf:type knora-base:LinkValue ; + rdf:subject <@resourceIri> ; + rdf:predicate <@linkUpdate.linkPropertyIri> ; + rdf:object <@linkUpdate.linkTargetIri> ; + knora-base:valueHasString "@linkUpdate.linkTargetIri" ; + knora-base:valueHasRefCount @linkUpdate.newReferenceCount ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "@creationDate"^^xsd:dateTime ; + knora-base:attachedToUser <@linkUpdate.newLinkValueCreator> ; + knora-base:hasPermissions "@linkUpdate.newLinkValuePermissions" ; + knora-base:valueHasUUID "@linkUpdate.valueUuid" . + + @* Attach the new LinkValue to its containing resource. *@ + <@resourceIri> <@{linkUpdate.linkPropertyIri}Value> <@linkUpdate.newLinkValueIri> . + } } } diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createProperty.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createProperty.scala.txt index 69132b0601..3dcaf6e201 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createProperty.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createProperty.scala.txt @@ -6,6 +6,8 @@ @import org.knora.webapi._ @import org.knora.webapi.messages.SmartIri @import org.knora.webapi.messages.v2.responder.ontologymessages._ +@import org.knora.webapi.messages.StringFormatter +@import org.knora.webapi.messages.store.triplestoremessages._ @import java.time.Instant @* @@ -42,8 +44,45 @@ DELETE { @* Insert the property. *@ - @{ - org.knora.webapi.messages.twirl.queries.sparql.v2.txt.generateInsertStatementsForCreateProperty(propertyDef) + @for(predicate <- propertyDef.predicates.values) { + + @for(obj <- predicate.objects) { + + @obj match { + + case SmartIriLiteralV2(iri) => { + + <@propertyDef.propertyIri> <@predicate.predicateIri> <@iri> . + + } + + case StringLiteralV2(text, Some(lang)) => { + + <@propertyDef.propertyIri> <@predicate.predicateIri> """@text"""@@@lang . + + } + + case StringLiteralV2(text, None) => { + + <@propertyDef.propertyIri> <@predicate.predicateIri> """@text""" . + + } + + case BooleanLiteralV2(booleanVal) => { + + <@propertyDef.propertyIri> <@predicate.predicateIri> "@booleanVal"^^xsd:boolean . + + } + } + + } + + } + + @for(superProp <- propertyDef.subPropertyOf) { + + <@propertyDef.propertyIri> rdfs:subPropertyOf <@superProp> . + } @* Also insert a link value property if requested. *@ @@ -52,8 +91,45 @@ DELETE { case Some(linkValuePropertyInfoContent) => { - @{ - org.knora.webapi.messages.twirl.queries.sparql.v2.txt.generateInsertStatementsForCreateProperty(linkValuePropertyInfoContent) + @for(predicate <- linkValuePropertyInfoContent.predicates.values) { + + @for(obj <- predicate.objects) { + + @obj match { + + case SmartIriLiteralV2(iri) => { + + <@linkValuePropertyInfoContent.propertyIri> <@predicate.predicateIri> <@iri> . + + } + + case StringLiteralV2(text, Some(lang)) => { + + <@linkValuePropertyInfoContent.propertyIri> <@predicate.predicateIri> """@text"""@@@lang . + + } + + case StringLiteralV2(text, None) => { + + <@linkValuePropertyInfoContent.propertyIri> <@predicate.predicateIri> """@text""" . + + } + + case BooleanLiteralV2(booleanVal) => { + + <@linkValuePropertyInfoContent.propertyIri> <@predicate.predicateIri> "@booleanVal"^^xsd:boolean . + + } + } + + } + + } + + @for(superProp <- linkValuePropertyInfoContent.subPropertyOf) { + + <@linkValuePropertyInfoContent.propertyIri> rdfs:subPropertyOf <@superProp> . + } } diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createValue.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createValue.scala.txt index d21bfaa9ff..d0de49dcac 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createValue.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/createValue.scala.txt @@ -5,6 +5,8 @@ @import java.time.Instant @import java.util.UUID +@import dsp.errors.SparqlGenerationException +@import dsp.valueobjects.UuidUtil @import org.knora.webapi._ @import org.knora.webapi.messages.{SmartIri, StringFormatter} @import org.knora.webapi.messages.twirl.SparqlTemplateLinkUpdate @@ -77,18 +79,288 @@ DELETE { GRAPH ?dataNamedGraph { ?resource knora-base:lastModificationDate "@creationDate"^^xsd:dateTime . - @{ - org.knora.webapi.messages.twirl.queries.sparql.v2.txt.generateInsertStatementsForCreateValue(resourceIri = resourceIri, - propertyIri = propertyIri, - value = value, - newValueIri = newValueIri, - newValueUUID = newValueUUID, - linkUpdates = linkUpdates, - valueCreator = valueCreator, - valuePermissions = valuePermissions, - creationDate = creationDate, - maybeValueHasOrder = None) + # Value: @newValueIri + # Property: @propertyIri + + @* Construct the value. *@ + <@newValueIri> rdf:type <@value.valueType> ; + knora-base:isDeleted false ; + knora-base:valueHasString """@value.valueHasString""" ; + knora-base:valueHasUUID "@{UuidUtil.base64Encode(newValueUUID)}" . + + + @value match { + + case textValue: TextValueContentV2 => { + + @if(!textValue.valueHasLanguage.isEmpty) { + <@newValueIri> knora-base:valueHasLanguage """@textValue.valueHasLanguage.get""" . + } + + @if(textValue.standoff.nonEmpty) { + + @* Create a Standoff node for each standoff tag. *@ + + @textValue.mappingIri match { + case Some(definedMappingIri) => { + <@newValueIri> knora-base:valueHasMapping <@definedMappingIri> . + } + + case None => {} + } + + <@newValueIri> knora-base:valueHasMaxStandoffStartIndex @textValue.computedMaxStandoffStartIndex.get . + + @for((createStandoff: CreateStandoffTagV2InTriplestore, standoffNodeIndex) <- textValue.prepareForSparqlInsert(newValueIri).zipWithIndex) { + + <@newValueIri> knora-base:valueHasStandoff <@createStandoff.standoffTagInstanceIri> . + + <@createStandoff.standoffTagInstanceIri> + + @* + + Check for optional standoff properties + + *@ + + @if(createStandoff.standoffNode.endIndex.isDefined) { + knora-base:standoffTagHasEndIndex @createStandoff.standoffNode.endIndex.get ; + } + + @if(createStandoff.startParentIri.isDefined) { + knora-base:standoffTagHasStartParent <@createStandoff.startParentIri.get> ; + } + + @if(createStandoff.endParentIri.isDefined) { + knora-base:standoffTagHasEndParent <@createStandoff.endParentIri.get> ; + } + + @if(createStandoff.standoffNode.originalXMLID.isDefined) { + knora-base:standoffTagHasOriginalXMLID """@createStandoff.standoffNode.originalXMLID.get""" ; + } + + @* + + Handle standoff class specific standoff properties + + *@ + @for(createProperty <- createStandoff.standoffNode.attributes) { + + <@createProperty.standoffPropertyIri> @createProperty.rdfValue ; + + } + + knora-base:standoffTagHasStartIndex @createStandoff.standoffNode.startIndex ; + knora-base:standoffTagHasUUID "@{UuidUtil.base64Encode(createStandoff.standoffNode.uuid)}" ; + knora-base:standoffTagHasStart @createStandoff.standoffNode.startPosition ; + knora-base:standoffTagHasEnd @createStandoff.standoffNode.endPosition ; + rdf:type <@createStandoff.standoffNode.standoffTagClassIri> . + + } + + } + } + + + case intValue: IntegerValueContentV2 => { + + <@newValueIri> knora-base:valueHasInteger @intValue.valueHasInteger . + + } + + case decimalValue: DecimalValueContentV2 => { + + <@newValueIri> knora-base:valueHasDecimal "@decimalValue.valueHasDecimal"^^xsd:decimal . + + } + + case booleanValue: BooleanValueContentV2 => { + + <@newValueIri> knora-base:valueHasBoolean @booleanValue.valueHasBoolean . + + } + + case uriValue: UriValueContentV2 => { + + <@newValueIri> knora-base:valueHasUri """@uriValue.valueHasUri"""^^xsd:anyURI . + + } + + case dateValue: DateValueContentV2 => { + + <@newValueIri> knora-base:valueHasStartJDN @dateValue.valueHasStartJDN ; + knora-base:valueHasEndJDN @dateValue.valueHasEndJDN ; + knora-base:valueHasStartPrecision "@dateValue.valueHasStartPrecision" ; + knora-base:valueHasEndPrecision "@dateValue.valueHasEndPrecision" ; + knora-base:valueHasCalendar "@dateValue.valueHasCalendar" . + + } + + case colorValue: ColorValueContentV2 => { + + <@newValueIri> knora-base:valueHasColor """@colorValue.valueHasColor""" . + + } + + case geometryValue: GeomValueContentV2 => { + + <@newValueIri> knora-base:valueHasGeometry """@geometryValue.valueHasGeometry""" . + + } + + case fileValueContentV2: FileValueContentV2 => { + <@newValueIri> knora-base:internalFilename """@fileValueContentV2.fileValue.internalFilename""" ; + knora-base:internalMimeType """@fileValueContentV2.fileValue.internalMimeType""" . + + @fileValueContentV2.fileValue.originalFilename match { + case Some(definedOriginalFilename) => { + <@newValueIri> knora-base:originalFilename """@definedOriginalFilename""" . + } + + case None => {} + } + + @fileValueContentV2.fileValue.originalMimeType match { + case Some(definedOriginalMimeType) => { + <@newValueIri> knora-base:originalMimeType """@definedOriginalMimeType""" . + } + + case None => {} + } + + @fileValueContentV2 match { + case stillImageFileValue: StillImageFileValueContentV2 => { + <@newValueIri> knora-base:dimX @stillImageFileValue.dimX ; + knora-base:dimY @stillImageFileValue.dimY . + } + + case stillImageFileValue: StillImageExternalFileValueContentV2 => { + <@newValueIri> knora-base:externalUrl """@stillImageFileValue.externalUrl.value.toString""" . + } + + case documentFileValue: DocumentFileValueContentV2 => { + @documentFileValue.dimX match { + case Some(definedDimX) => { + <@newValueIri> knora-base:dimX @definedDimX . + } + + case None => {} + } + + @documentFileValue.dimY match { + case Some(definedDimY) => { + <@newValueIri> knora-base:dimY @definedDimY . + } + + case None => {} + } + + @documentFileValue.pageCount match { + case Some(definedPageCount) => { + <@newValueIri> knora-base:pageCount @definedPageCount . + } + + case None => {} + } + } + + case _ => {} + } + } + + case listValue: HierarchicalListValueContentV2 => { + + <@newValueIri> knora-base:valueHasListNode <@listValue.valueHasListNode> . + + } + + case intervalValue: IntervalValueContentV2 => { + + <@newValueIri> knora-base:valueHasIntervalStart "@intervalValue.valueHasIntervalStart"^^xsd:decimal ; + knora-base:valueHasIntervalEnd "@intervalValue.valueHasIntervalEnd"^^xsd:decimal . + + } + + case timeValue: TimeValueContentV2 => { + + <@newValueIri> knora-base:valueHasTimeStamp "@timeValue.valueHasTimeStamp"^^xsd:dateTime . + + } + + case geonameValue: GeonameValueContentV2 => { + + <@newValueIri> knora-base:valueHasGeonameCode """@geonameValue.valueHasGeonameCode""" . + + } + + case other => { + @{throw SparqlGenerationException(s"Value object $other is not supported in this SPARQL template"); ()} + } + } + + @* Insert the value's comment, if given. *@ + @value.comment match { + case Some(commentStr) => { + <@newValueIri> knora-base:valueHasComment """@commentStr""" . + } + + case None => {} } + + <@newValueIri> knora-base:attachedToUser <@valueCreator> ; + knora-base:hasPermissions "@valuePermissions"^^xsd:string ; + knora-base:valueHasOrder ?nextOrder ; + knora-base:valueCreationDate "@creationDate"^^xsd:dateTime . + + @* Insert direct links and LinkValues for resource references. *@ + + @for((linkUpdate, linkValueIndex) <- linkUpdates.zipWithIndex) { + @* Insert a direct link for the resource reference if necessary. *@ + @if(linkUpdate.insertDirectLink) { + <@resourceIri> <@linkUpdate.linkPropertyIri> <@linkUpdate.linkTargetIri> . + } + + @* Insert a LinkValue for the resource reference. *@ + <@linkUpdate.newLinkValueIri> rdf:type knora-base:LinkValue ; + rdf:subject <@resourceIri> ; + rdf:predicate <@linkUpdate.linkPropertyIri> ; + rdf:object <@linkUpdate.linkTargetIri> ; + knora-base:valueHasString "@linkUpdate.linkTargetIri" ; + knora-base:valueHasRefCount @linkUpdate.newReferenceCount ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "@creationDate"^^xsd:dateTime . + + <@linkUpdate.newLinkValueIri> knora-base:attachedToUser <@linkUpdate.newLinkValueCreator> ; + knora-base:hasPermissions "@linkUpdate.newLinkValuePermissions" . + + @* + + If this template is being used to create a single text value containing standoff links, the new LinkValue + we are creating may be a new version of an existing LinkValue (linkUpdate.linkValueExists will be true). + In that case, the WHERE clause will have already bound a SPARQL variable ?linkValue@linkValueIndex, + containing the IRI of the existing LinkValue. Therefore, add a triple indicating that the new LinkValue + is a new version of the existing one, and copy the UUID from the existing one. + + If this template is being used to create a new resource with its initial values, there won't be any existing + LinkValues for standoff links, so linkUpdate.linkValueExists will be false. Generate a random UUID for the + LinkValue. + + *@ + @if(linkUpdate.linkValueExists) { + <@linkUpdate.newLinkValueIri> knora-base:previousValue ?linkValue@linkValueIndex ; + knora-base:valueHasUUID ?linkValueUUID@linkValueIndex . + } else { + <@linkUpdate.newLinkValueIri> knora-base:valueHasUUID "@{UuidUtil.base64Encode(UUID.randomUUID)}" . + } + + @* Attach the new LinkValue to its containing resource. *@ + <@resourceIri> <@{linkUpdate.linkPropertyIri}Value> <@linkUpdate.newLinkValueIri> . + } + + + @* Attach the value to the resource. *@ + <@resourceIri> <@propertyIri> <@newValueIri> . + } } @@ -108,11 +380,128 @@ WHERE { ?resource knora-base:lastModificationDate ?resourceLastModificationDate . } - @{ - org.knora.webapi.messages.twirl.queries.sparql.v2.txt.generateWhereStatementsForCreateValue(resourceIri = resourceIri, - propertyIri = propertyIri, - newValueIri = newValueIri, - value = value, - linkUpdates = linkUpdates) - } + + # Value: @newValueIri + # Property: @propertyIri + + BIND(IRI("@propertyIri") AS ?property) + BIND(IRI("@value.valueType") AS ?valueType) + + @* Do nothing if the submitted value has the wrong type. *@ + + ?property knora-base:objectClassConstraint ?propertyRange . + ?valueType rdfs:subClassOf* ?propertyRange . + + @* Do nothing if neither the resource class nor any of its superclasses has a cardinality for this property. *@ + + ?resourceClass rdfs:subClassOf* ?restriction . + ?restriction a owl:Restriction . + ?restriction owl:onProperty ?property . + + @* If this is a list value, make sure the list node that it points to exists and is a ListNode. *@ + + @value match { + + case listValue: HierarchicalListValueContentV2 => { + + <@{listValue.valueHasListNode}> a knora-base:ListNode . + + } + + case _ => {} + + } + + @* + + There may be existing LinkValues that need to be updated for standoff links in the text. For each existing + LinkValue, bind a SPARQL variable ?linkValue@linkValueIndex. This is necessary so that a new version of the + LinkValue can be inserted and point to the previous one. + + *@ + + @for((linkUpdate, linkValueIndex) <- linkUpdates.zipWithIndex) { + + @if(linkUpdate.linkTargetExists) { + + @if(linkUpdate.insertDirectLink) { + @* + + Do nothing if we were asked to insert a direct link to a target that doesn't exist, is marked as deleted, + isn't a knora-base:Resource, or belongs to the wrong OWL class for the link property. + + *@ + + <@linkUpdate.linkTargetIri> rdf:type ?linkTargetClass@linkValueIndex ; + knora-base:isDeleted false . + + ?linkTargetClass@linkValueIndex rdfs:subClassOf* knora-base:Resource . + <@linkUpdate.linkPropertyIri> knora-base:objectClassConstraint ?expectedTargetClass@linkValueIndex . + ?linkTargetClass@linkValueIndex rdfs:subClassOf* ?expectedTargetClass@linkValueIndex . + } + + @if(linkUpdate.directLinkExists) { + @* Make sure the relevant direct link exists between the two resources. *@ + ?resource <@linkUpdate.linkPropertyIri> <@linkUpdate.linkTargetIri> . + } else { + @* Make sure there no such direct link. *@ + MINUS { + ?resource <@linkUpdate.linkPropertyIri> <@linkUpdate.linkTargetIri> . + } + } + + @if(linkUpdate.linkValueExists) { + @* + + Make sure a knora-base:LinkValue exists, describing the relevant direct link + between the source and target resources, and has the correct reference count. + + *@ + ?resource <@{linkUpdate.linkPropertyIri}Value> ?linkValue@linkValueIndex . + ?linkValue@linkValueIndex rdf:type knora-base:LinkValue ; + rdf:subject ?resource ; + rdf:predicate <@linkUpdate.linkPropertyIri> ; + rdf:object <@linkUpdate.linkTargetIri> ; + knora-base:valueHasRefCount @linkUpdate.currentReferenceCount ; + knora-base:isDeleted false ; + knora-base:valueHasUUID ?linkValueUUID@linkValueIndex ; + knora-base:hasPermissions ?linkValuePermissions@linkValueIndex . + } else { + @* Make sure there is no such knora-base:LinkValue. *@ + MINUS { + ?resource <@{linkUpdate.linkPropertyIri}Value> ?linkValue@linkValueIndex . + ?linkValue@linkValueIndex rdf:type knora-base:LinkValue ; + rdf:subject ?resource ; + rdf:predicate <@linkUpdate.linkPropertyIri> ; + rdf:object <@linkUpdate.linkTargetIri> ; + knora-base:isDeleted false . + } + } + } else { + + @{throw SparqlGenerationException(s"linkUpdate.linkTargetExists must be true in this SPARQL template"); ()} + + } + } + + + @* + + Bind a variable for knora-base:valueHasOrder for the new value. + + We have to do a subquery to determine the next available knora-base:valueHasOrder. This works as follows: if the + property already has one or more (non-deleted) values in the resource, find the one with the highest order, add 1 to + its order, and give the resulting order to the new value. Otherwise, give the new value an order of 0. + + *@ + + { + SELECT ((MAX(?order)) AS ?maxOrder) (IF(BOUND(?maxOrder), ?maxOrder + 1, 0) AS ?nextOrder) + WHERE { + <@resourceIri> <@propertyIri> ?otherValue . + ?otherValue knora-base:valueHasOrder ?order ; + knora-base:isDeleted false . + } + } + } diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateLink.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateLink.scala.txt deleted file mode 100644 index 040078b90c..0000000000 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateLink.scala.txt +++ /dev/null @@ -1,72 +0,0 @@ -@* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - *@ - -@import java.time.Instant -@import java.util.UUID -@import org.knora.webapi._ -@import dsp.errors.SparqlGenerationException -@import dsp.valueobjects.UuidUtil -@import org.knora.webapi.messages.twirl.SparqlTemplateLinkUpdate - -@** - * Generates statements to be added to the INSERT clause of a SPARQL update when creating a new link between resources. - * This template is used in two contexts: when creating a single link in an existing resource, and when creating - * a new resource with its initial values. - * - * @param resourceIri the IRI of the resource. - * @param linkUpdate a [[LinkUpdate]] object describing the link to insert. - * @param creationDate an xsd:dateTimeStamp that will be attached to the link value. - * @param newValueUUID the UUID to be attached to the value. - * @param maybeComment an optional comment on the link. - * @param the knora-base:valueHasOrder of the new value. If not provided, the SPARQL variable ?nextOrder will be used. - *@ -@(resourceIri: IRI, - linkUpdate: SparqlTemplateLinkUpdate, - creationDate: Instant, - newValueUUID: UUID, - maybeComment: Option[String], - maybeValueHasOrder: Option[Int]) - - # Value: @linkUpdate.newLinkValueIri - # Property: @linkUpdate.linkPropertyIri - - @* Insert a direct link between the source and target resources. *@ - @if(linkUpdate.insertDirectLink) { - <@resourceIri> <@linkUpdate.linkPropertyIri> <@linkUpdate.linkTargetIri> . - } else { - @{throw SparqlGenerationException(s"LinkUpdate.insertDirectLink must be true in this SPARQL template (for value @index)"); ()} - } - - @* Insert a LinkValue describing the link. *@ - <@linkUpdate.newLinkValueIri> rdf:type knora-base:LinkValue ; - rdf:subject <@resourceIri> ; - rdf:predicate <@linkUpdate.linkPropertyIri> ; - rdf:object <@linkUpdate.linkTargetIri> ; - knora-base:valueHasString "@linkUpdate.linkTargetIri"^^xsd:string ; - knora-base:valueHasRefCount @linkUpdate.newReferenceCount ; - @maybeComment match { - case Some(comment) => { - knora-base:valueHasComment """@comment""" ; - } - - case None => {} - } - @maybeValueHasOrder match { - case Some(order) => { - knora-base:valueHasOrder @order ; - } - - case None => { - knora-base:valueHasOrder ?nextOrder ; - } - } - knora-base:isDeleted false ; - knora-base:valueHasUUID "@{UuidUtil.base64Encode(newValueUUID)}" ; - knora-base:valueCreationDate "@creationDate"^^xsd:dateTime ; - knora-base:attachedToUser <@linkUpdate.newLinkValueCreator> ; - knora-base:hasPermissions "@linkUpdate.newLinkValuePermissions"^^xsd:string . - - @* Attach the new LinkValue to its containing resource. *@ - <@resourceIri> <@{linkUpdate.linkPropertyIri}Value> <@linkUpdate.newLinkValueIri> . diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateProperty.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateProperty.scala.txt deleted file mode 100644 index d8b824778a..0000000000 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateProperty.scala.txt +++ /dev/null @@ -1,31 +0,0 @@ -@* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - *@ - -@import org.knora.webapi._ -@import org.knora.webapi.messages.v2.responder.ontologymessages._ -@import org.knora.webapi.messages.StringFormatter - -@* - * Called by createProperty.scala.txt to generate INSERT statements for creating a property. - * - * @param propertyDef the definition of the property to be created. - *@ -@(propertyDef: PropertyInfoContentV2) - -@defining(StringFormatter.getGeneralInstance) { stringFormatter => - - @{ - org.knora.webapi.messages.twirl.queries.sparql.v2.txt.generateInsertStatementsForPredicates( - entityIri = propertyDef.propertyIri, - predicates = propertyDef.predicates.values - ) - } - - @for(superProp <- propertyDef.subPropertyOf) { - - <@propertyDef.propertyIri> rdfs:subPropertyOf <@superProp> . - - } -} diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateValue.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateValue.scala.txt deleted file mode 100644 index 42c93fffd1..0000000000 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForCreateValue.scala.txt +++ /dev/null @@ -1,87 +0,0 @@ -@* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - *@ - -@import java.time.Instant -@import java.util.UUID -@import dsp.valueobjects.UuidUtil -@import org.knora.webapi._ -@import org.knora.webapi.messages.SmartIri -@import org.knora.webapi.messages.twirl.SparqlTemplateLinkUpdate -@import org.knora.webapi.messages.v2.responder.valuemessages._ - -@** - * Generates statements to be added to the INSERT clause of a SPARQL update when creating a new value of a resource - * property (as opposed to a new version of an existing value). This template is used in two contexts: when creating a - * single value in an existing resource, and when creating a new resource with its initial values. - * - * @param resourceIri the IRI of the resource. - * @param propertyIri the IRI of the property for which the value is being created. - * @param value the value to insert. - * @param newValueIri the IRI of the new value. - * @param newValueUUID the UUID to be attached to the value. - * @param linkUpdates a list of [[SparqlTemplateLinkUpdate]] objects describing links and LinkValues that need to be - * updated for resource references in Standoff text values. This list will be empty if the links and LinkValues - * are being created separately. - * @param valueCreator the IRI of the user who created the value. - * @param valuePermissions the permissions that should be attached to the value. - * @param creationDate an xsd:dateTimeStamp that will be attached to the value. - * @param the knora-base:valueHasOrder of the new value. If not provided, the SPARQL variable ?nextOrder will be used. - *@ -@(resourceIri: IRI, - propertyIri: SmartIri, - value: ValueContentV2, - newValueIri: IRI, - newValueUUID: UUID, - linkUpdates: Seq[SparqlTemplateLinkUpdate], - valueCreator: IRI, - valuePermissions: String, - creationDate: Instant, - maybeValueHasOrder: Option[Int]) - - # Value: @newValueIri - # Property: @propertyIri - - @* Construct the value. *@ - <@newValueIri> rdf:type <@value.valueType> ; - knora-base:isDeleted false ; - knora-base:valueHasString """@value.valueHasString""" ; - knora-base:valueHasUUID "@{UuidUtil.base64Encode(newValueUUID)}" . - - @{ - org.knora.webapi.messages.twirl.queries.sparql.v2.txt.generateInsertStatementsForValueContent(value = value, - newValueIri = newValueIri) - } - - @* Insert the value's comment, if given. *@ - @value.comment match { - case Some(commentStr) => { - <@newValueIri> knora-base:valueHasComment """@commentStr""" . - } - - case None => {} - } - - <@newValueIri> knora-base:attachedToUser <@valueCreator> ; - knora-base:hasPermissions "@valuePermissions"^^xsd:string ; - @maybeValueHasOrder match { - case Some(order) => { - knora-base:valueHasOrder @order ; - } - - case None => { - knora-base:valueHasOrder ?nextOrder ; - } - } - knora-base:valueCreationDate "@creationDate"^^xsd:dateTime . - - @* Insert direct links and LinkValues for resource references. *@ - @{ - org.knora.webapi.messages.twirl.queries.sparql.v2.txt.generateInsertStatementsForStandoffLinks(resourceIri = resourceIri, - linkUpdates = linkUpdates, - creationDate = creationDate) - } - - @* Attach the value to the resource. *@ - <@resourceIri> <@propertyIri> <@newValueIri> . diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForPredicates.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForPredicates.scala.txt deleted file mode 100644 index 9b9f6609a3..0000000000 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForPredicates.scala.txt +++ /dev/null @@ -1,53 +0,0 @@ -@* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - *@ - -@import org.knora.webapi._ -@import org.knora.webapi.messages.SmartIri -@import org.knora.webapi.messages.v2.responder.ontologymessages._ -@import org.knora.webapi.messages.store.triplestoremessages._ - -@* - * Called by other templates to generate INSERT statements for adding predicates to an entity. - * - * @param entityIri the IRI to which the predicates are to be added. - * @param predicates the predicates to be added. - *@ -@(entityIri: SmartIri, - predicates: Iterable[PredicateInfoV2]) - -@for(predicate <- predicates) { - - @for(obj <- predicate.objects) { - - @obj match { - - case SmartIriLiteralV2(iri) => { - - <@entityIri> <@predicate.predicateIri> <@iri> . - - } - - case StringLiteralV2(text, Some(lang)) => { - - <@entityIri> <@predicate.predicateIri> """@text"""@@@lang . - - } - - case StringLiteralV2(text, None) => { - - <@entityIri> <@predicate.predicateIri> """@text""" . - - } - - case BooleanLiteralV2(booleanVal) => { - - <@entityIri> <@predicate.predicateIri> "@booleanVal"^^xsd:boolean . - - } - } - - } - -} diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForStandoffLinks.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForStandoffLinks.scala.txt deleted file mode 100644 index ec2bdeb985..0000000000 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForStandoffLinks.scala.txt +++ /dev/null @@ -1,68 +0,0 @@ -@* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - *@ - -@import java.time.Instant -@import java.util.UUID -@import dsp.valueobjects.UuidUtil -@import org.knora.webapi._ -@import org.knora.webapi.messages.twirl.SparqlTemplateLinkUpdate - -@** - * Generates statements to be added to the INSERT clause of a SPARQL update to insert direct links and LinkValues - * for Standoff resource references, when creating new values (as opposed to new versions of existing values). - * This template is used in two contexts: when creating a single text value in an existing resource, and when - * creating a new resource with its initial values. - * - * @param the IRI of the resource in which values are being created. - * @param linkUpdates a list of [[LinkUpdate]] objects describing links and LinkValues that need to be - * updated for resource references in Standoff text values. - * @param creationDate an xsd:dateTimeStamp that will be attached to the link values. - *@ -@(resourceIri: IRI, - linkUpdates: Seq[SparqlTemplateLinkUpdate], - creationDate: Instant) - - @for((linkUpdate, linkValueIndex) <- linkUpdates.zipWithIndex) { - @* Insert a direct link for the resource reference if necessary. *@ - @if(linkUpdate.insertDirectLink) { - <@resourceIri> <@linkUpdate.linkPropertyIri> <@linkUpdate.linkTargetIri> . - } - - @* Insert a LinkValue for the resource reference. *@ - <@linkUpdate.newLinkValueIri> rdf:type knora-base:LinkValue ; - rdf:subject <@resourceIri> ; - rdf:predicate <@linkUpdate.linkPropertyIri> ; - rdf:object <@linkUpdate.linkTargetIri> ; - knora-base:valueHasString "@linkUpdate.linkTargetIri" ; - knora-base:valueHasRefCount @linkUpdate.newReferenceCount ; - knora-base:isDeleted false ; - knora-base:valueCreationDate "@creationDate"^^xsd:dateTime . - - <@linkUpdate.newLinkValueIri> knora-base:attachedToUser <@linkUpdate.newLinkValueCreator> ; - knora-base:hasPermissions "@linkUpdate.newLinkValuePermissions" . - - @* - - If this template is being used to create a single text value containing standoff links, the new LinkValue - we are creating may be a new version of an existing LinkValue (linkUpdate.linkValueExists will be true). - In that case, the WHERE clause will have already bound a SPARQL variable ?linkValue@linkValueIndex, - containing the IRI of the existing LinkValue. Therefore, add a triple indicating that the new LinkValue - is a new version of the existing one, and copy the UUID from the existing one. - - If this template is being used to create a new resource with its initial values, there won't be any existing - LinkValues for standoff links, so linkUpdate.linkValueExists will be false. Generate a random UUID for the - LinkValue. - - *@ - @if(linkUpdate.linkValueExists) { - <@linkUpdate.newLinkValueIri> knora-base:previousValue ?linkValue@linkValueIndex ; - knora-base:valueHasUUID ?linkValueUUID@linkValueIndex . - } else { - <@linkUpdate.newLinkValueIri> knora-base:valueHasUUID "@{UuidUtil.base64Encode(UUID.randomUUID)}" . - } - - @* Attach the new LinkValue to its containing resource. *@ - <@resourceIri> <@{linkUpdate.linkPropertyIri}Value> <@linkUpdate.newLinkValueIri> . - } diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForValueContent.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForValueContent.scala.txt deleted file mode 100644 index 2490c854eb..0000000000 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateInsertStatementsForValueContent.scala.txt +++ /dev/null @@ -1,231 +0,0 @@ -@* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - *@ - -@import org.knora.webapi._ -@import dsp.errors.SparqlGenerationException -@import dsp.valueobjects.UuidUtil -@import org.knora.webapi.messages.SmartIri -@import org.knora.webapi.messages.twirl.SparqlTemplateLinkUpdate -@import org.knora.webapi.messages.v2.responder.valuemessages._ - -@** - * Generates statements to be added to the INSERT clause of a SPARQL update when creating a new value or value version. - * - * @param value the value to insert. - * @param newValueIri the IRI of the new value. - *@ -@(value: ValueContentV2, - newValueIri: IRI) - - @value match { - - case textValue: TextValueContentV2 => { - - @if(!textValue.valueHasLanguage.isEmpty) { - <@newValueIri> knora-base:valueHasLanguage """@textValue.valueHasLanguage.get""" . - } - - @if(textValue.standoff.nonEmpty) { - - @* Create a Standoff node for each standoff tag. *@ - - @textValue.mappingIri match { - case Some(definedMappingIri) => { - <@newValueIri> knora-base:valueHasMapping <@definedMappingIri> . - } - - case None => {} - } - - <@newValueIri> knora-base:valueHasMaxStandoffStartIndex @textValue.computedMaxStandoffStartIndex.get . - - @for((createStandoff: CreateStandoffTagV2InTriplestore, standoffNodeIndex) <- textValue.prepareForSparqlInsert(newValueIri).zipWithIndex) { - - <@newValueIri> knora-base:valueHasStandoff <@createStandoff.standoffTagInstanceIri> . - - <@createStandoff.standoffTagInstanceIri> - - @* - - Check for optional standoff properties - - *@ - - @if(createStandoff.standoffNode.endIndex.isDefined) { - knora-base:standoffTagHasEndIndex @createStandoff.standoffNode.endIndex.get ; - } - - @if(createStandoff.startParentIri.isDefined) { - knora-base:standoffTagHasStartParent <@createStandoff.startParentIri.get> ; - } - - @if(createStandoff.endParentIri.isDefined) { - knora-base:standoffTagHasEndParent <@createStandoff.endParentIri.get> ; - } - - @if(createStandoff.standoffNode.originalXMLID.isDefined) { - knora-base:standoffTagHasOriginalXMLID """@createStandoff.standoffNode.originalXMLID.get""" ; - } - - @* - - Handle standoff class specific standoff properties - - *@ - @for(createProperty <- createStandoff.standoffNode.attributes) { - - <@createProperty.standoffPropertyIri> @createProperty.rdfValue ; - - } - - knora-base:standoffTagHasStartIndex @createStandoff.standoffNode.startIndex ; - knora-base:standoffTagHasUUID "@{UuidUtil.base64Encode(createStandoff.standoffNode.uuid)}" ; - knora-base:standoffTagHasStart @createStandoff.standoffNode.startPosition ; - knora-base:standoffTagHasEnd @createStandoff.standoffNode.endPosition ; - rdf:type <@createStandoff.standoffNode.standoffTagClassIri> . - - } - - } - } - - - case intValue: IntegerValueContentV2 => { - - <@newValueIri> knora-base:valueHasInteger @intValue.valueHasInteger . - - } - - case decimalValue: DecimalValueContentV2 => { - - <@newValueIri> knora-base:valueHasDecimal "@decimalValue.valueHasDecimal"^^xsd:decimal . - - } - - case booleanValue: BooleanValueContentV2 => { - - <@newValueIri> knora-base:valueHasBoolean @booleanValue.valueHasBoolean . - - } - - case uriValue: UriValueContentV2 => { - - <@newValueIri> knora-base:valueHasUri """@uriValue.valueHasUri"""^^xsd:anyURI . - - } - - case dateValue: DateValueContentV2 => { - - <@newValueIri> knora-base:valueHasStartJDN @dateValue.valueHasStartJDN ; - knora-base:valueHasEndJDN @dateValue.valueHasEndJDN ; - knora-base:valueHasStartPrecision "@dateValue.valueHasStartPrecision" ; - knora-base:valueHasEndPrecision "@dateValue.valueHasEndPrecision" ; - knora-base:valueHasCalendar "@dateValue.valueHasCalendar" . - - } - - case colorValue: ColorValueContentV2 => { - - <@newValueIri> knora-base:valueHasColor """@colorValue.valueHasColor""" . - - } - - case geometryValue: GeomValueContentV2 => { - - <@newValueIri> knora-base:valueHasGeometry """@geometryValue.valueHasGeometry""" . - - } - - case fileValueContentV2: FileValueContentV2 => { - <@newValueIri> knora-base:internalFilename """@fileValueContentV2.fileValue.internalFilename""" ; - knora-base:internalMimeType """@fileValueContentV2.fileValue.internalMimeType""" . - - @fileValueContentV2.fileValue.originalFilename match { - case Some(definedOriginalFilename) => { - <@newValueIri> knora-base:originalFilename """@definedOriginalFilename""" . - } - - case None => {} - } - - @fileValueContentV2.fileValue.originalMimeType match { - case Some(definedOriginalMimeType) => { - <@newValueIri> knora-base:originalMimeType """@definedOriginalMimeType""" . - } - - case None => {} - } - - @fileValueContentV2 match { - case stillImageFileValue: StillImageFileValueContentV2 => { - <@newValueIri> knora-base:dimX @stillImageFileValue.dimX ; - knora-base:dimY @stillImageFileValue.dimY . - } - - case stillImageFileValue: StillImageExternalFileValueContentV2 => { - <@newValueIri> knora-base:externalUrl """@stillImageFileValue.externalUrl.value.toString""" . - } - - case documentFileValue: DocumentFileValueContentV2 => { - @documentFileValue.dimX match { - case Some(definedDimX) => { - <@newValueIri> knora-base:dimX @definedDimX . - } - - case None => {} - } - - @documentFileValue.dimY match { - case Some(definedDimY) => { - <@newValueIri> knora-base:dimY @definedDimY . - } - - case None => {} - } - - @documentFileValue.pageCount match { - case Some(definedPageCount) => { - <@newValueIri> knora-base:pageCount @definedPageCount . - } - - case None => {} - } - } - - case _ => {} - } - } - - case listValue: HierarchicalListValueContentV2 => { - - <@newValueIri> knora-base:valueHasListNode <@listValue.valueHasListNode> . - - } - - case intervalValue: IntervalValueContentV2 => { - - <@newValueIri> knora-base:valueHasIntervalStart "@intervalValue.valueHasIntervalStart"^^xsd:decimal ; - knora-base:valueHasIntervalEnd "@intervalValue.valueHasIntervalEnd"^^xsd:decimal . - - } - - case timeValue: TimeValueContentV2 => { - - <@newValueIri> knora-base:valueHasTimeStamp "@timeValue.valueHasTimeStamp"^^xsd:dateTime . - - } - - case geonameValue: GeonameValueContentV2 => { - - <@newValueIri> knora-base:valueHasGeonameCode """@geonameValue.valueHasGeonameCode""" . - - } - - case other => { - @{throw SparqlGenerationException(s"Value object $other is not supported in this SPARQL template"); ()} - } - } - - @* TODO: add remaining types. *@ diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateWhereStatementsForCreateLink.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateWhereStatementsForCreateLink.scala.txt deleted file mode 100644 index d38a374f18..0000000000 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateWhereStatementsForCreateLink.scala.txt +++ /dev/null @@ -1,78 +0,0 @@ -@* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - *@ - -@import org.knora.webapi._ -@import dsp.errors.SparqlGenerationException -@import org.knora.webapi.messages.twirl.SparqlTemplateLinkUpdate - -@** - * Generates statements to be added to the WHERE clause of a SPARQL update when creating a new link between resources. - * This template is used only when creating a single link in an existing resource. - * - * @param resourceIri the resource that is the source of the link. - * @param linkUpdate a [[LinkUpdate]] object describing the link to insert. - * - * The SPARQL variable `?resourceClass` (the OWL class of the link source) must already be bound. - *@ -@(resourceIri: IRI, - linkUpdate: SparqlTemplateLinkUpdate) - - # Value: @linkUpdate.newLinkValueIri - # Property: @linkUpdate.linkPropertyIri - - BIND(IRI("@linkUpdate.linkPropertyIri") AS ?linkProperty) - BIND(IRI("@{linkUpdate.linkPropertyIri}Value") AS ?linkValueProperty) - BIND(IRI("@linkUpdate.newLinkValueIri") AS ?newLinkValue) - BIND(IRI("@linkUpdate.linkTargetIri") AS ?linkTarget) - - @if(linkUpdate.linkTargetExists) { - - @* Make sure the link target is a knora-base:Resource. *@ - - ?linkTarget rdf:type ?linkTargetClass . - ?linkTargetClass rdfs:subClassOf* knora-base:Resource . - - @* Do nothing if the target resource belongs to the wrong OWL class. *@ - - ?linkProperty knora-base:objectClassConstraint ?expectedTargetClass . - ?linkTargetClass rdfs:subClassOf* ?expectedTargetClass . - - @* Do nothing if the target resource doesn't exist or is marked as deleted. *@ - - ?linkTarget knora-base:isDeleted false . - - @* Do nothing if the source resource's OWL class has no cardinality for the link property. *@ - ?resourceClass rdfs:subClassOf* ?restriction . - ?restriction a owl:Restriction . - ?restriction owl:onProperty ?linkProperty . - - } - - @if(linkUpdate.directLinkExists) { - @{throw SparqlGenerationException("linkUpdate.directLinkExists must be false in this SPARQL template"); ()} - } - - @if(linkUpdate.linkValueExists) { - @{throw SparqlGenerationException("linkUpdate.linkValueExists must be false in this SPARQL template"); ()} - } - - @* - - Bind a variable for knora-base:valueHasOrder for the new value. - - We have to do a subquery to determine the next available knora-base:valueHasOrder. This works as follows: if the - property already has one or more (non-deleted) values in the resource, find the one with the highest order, add 1 - to its order, and give the resulting order to the new value. Otherwise, give the new value an order of 0. - - *@ - - { - SELECT ((MAX(?order)) AS ?maxOrder) (IF(BOUND(?maxOrder), ?maxOrder + 1, 0) AS ?nextOrder) - WHERE { - <@resourceIri> <@{linkUpdate.linkPropertyIri}Value> ?otherLinkValue . - ?otherLinkValue knora-base:valueHasOrder ?order ; - knora-base:isDeleted false . - } - } diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateWhereStatementsForCreateValue.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateWhereStatementsForCreateValue.scala.txt deleted file mode 100644 index 2084444482..0000000000 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/generateWhereStatementsForCreateValue.scala.txt +++ /dev/null @@ -1,154 +0,0 @@ -@* - * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - *@ - -@import org.knora.webapi._ -@import dsp.errors.SparqlGenerationException -@import org.knora.webapi.messages.SmartIri -@import org.knora.webapi.messages.twirl.SparqlTemplateLinkUpdate -@import org.knora.webapi.messages.v2.responder.valuemessages._ - -@** - * Generates statements to be added to the WHERE clause of a SPARQL update when creating a new value of a resource - * property (as opposed to a new version of an existing value). This template is used only when creating a - * single value in an existing resource. - * - * @param resourceIri the IRI of the resource being updated. - * @param propertyIri the resource property to update. - * @param newValueIri the new value IRI. - * @param valueTypeIri the type of the new value. - * @param linkUpdates a list of [[LinkUpdate]] objects describing links and LinkValues that need to be - * updated for resource references in Standoff text values. This list will be empty if the links and LinkValues - * are being created separately. - * - * The SPARQL variables ?resource and ?resourceClass must already be bound. - *@ -@(resourceIri: IRI, - propertyIri: SmartIri, - newValueIri: IRI, - value: ValueContentV2, - linkUpdates: Seq[SparqlTemplateLinkUpdate]) - - # Value: @newValueIri - # Property: @propertyIri - - BIND(IRI("@propertyIri") AS ?property) - BIND(IRI("@value.valueType") AS ?valueType) - - @* Do nothing if the submitted value has the wrong type. *@ - - ?property knora-base:objectClassConstraint ?propertyRange . - ?valueType rdfs:subClassOf* ?propertyRange . - - @* Do nothing if neither the resource class nor any of its superclasses has a cardinality for this property. *@ - - ?resourceClass rdfs:subClassOf* ?restriction . - ?restriction a owl:Restriction . - ?restriction owl:onProperty ?property . - - @* If this is a list value, make sure the list node that it points to exists and is a ListNode. *@ - - @value match { - - case listValue: HierarchicalListValueContentV2 => { - - <@{listValue.valueHasListNode}> a knora-base:ListNode . - - } - - case _ => {} - - } - - @* - - There may be existing LinkValues that need to be updated for standoff links in the text. For each existing - LinkValue, bind a SPARQL variable ?linkValue@linkValueIndex. This is necessary so that a new version of the - LinkValue can be inserted and point to the previous one. - - *@ - - @for((linkUpdate, linkValueIndex) <- linkUpdates.zipWithIndex) { - - @if(linkUpdate.linkTargetExists) { - - @if(linkUpdate.insertDirectLink) { - @* - - Do nothing if we were asked to insert a direct link to a target that doesn't exist, is marked as deleted, - isn't a knora-base:Resource, or belongs to the wrong OWL class for the link property. - - *@ - - <@linkUpdate.linkTargetIri> rdf:type ?linkTargetClass@linkValueIndex ; - knora-base:isDeleted false . - - ?linkTargetClass@linkValueIndex rdfs:subClassOf* knora-base:Resource . - <@linkUpdate.linkPropertyIri> knora-base:objectClassConstraint ?expectedTargetClass@linkValueIndex . - ?linkTargetClass@linkValueIndex rdfs:subClassOf* ?expectedTargetClass@linkValueIndex . - } - - @if(linkUpdate.directLinkExists) { - @* Make sure the relevant direct link exists between the two resources. *@ - ?resource <@linkUpdate.linkPropertyIri> <@linkUpdate.linkTargetIri> . - } else { - @* Make sure there no such direct link. *@ - MINUS { - ?resource <@linkUpdate.linkPropertyIri> <@linkUpdate.linkTargetIri> . - } - } - - @if(linkUpdate.linkValueExists) { - @* - - Make sure a knora-base:LinkValue exists, describing the relevant direct link - between the source and target resources, and has the correct reference count. - - *@ - ?resource <@{linkUpdate.linkPropertyIri}Value> ?linkValue@linkValueIndex . - ?linkValue@linkValueIndex rdf:type knora-base:LinkValue ; - rdf:subject ?resource ; - rdf:predicate <@linkUpdate.linkPropertyIri> ; - rdf:object <@linkUpdate.linkTargetIri> ; - knora-base:valueHasRefCount @linkUpdate.currentReferenceCount ; - knora-base:isDeleted false ; - knora-base:valueHasUUID ?linkValueUUID@linkValueIndex ; - knora-base:hasPermissions ?linkValuePermissions@linkValueIndex . - } else { - @* Make sure there is no such knora-base:LinkValue. *@ - MINUS { - ?resource <@{linkUpdate.linkPropertyIri}Value> ?linkValue@linkValueIndex . - ?linkValue@linkValueIndex rdf:type knora-base:LinkValue ; - rdf:subject ?resource ; - rdf:predicate <@linkUpdate.linkPropertyIri> ; - rdf:object <@linkUpdate.linkTargetIri> ; - knora-base:isDeleted false . - } - } - } else { - - @{throw SparqlGenerationException(s"linkUpdate.linkTargetExists must be true in this SPARQL template"); ()} - - } - } - - - @* - - Bind a variable for knora-base:valueHasOrder for the new value. - - We have to do a subquery to determine the next available knora-base:valueHasOrder. This works as follows: if the - property already has one or more (non-deleted) values in the resource, find the one with the highest order, add 1 to - its order, and give the resulting order to the new value. Otherwise, give the new value an order of 0. - - *@ - - { - SELECT ((MAX(?order)) AS ?maxOrder) (IF(BOUND(?maxOrder), ?maxOrder + 1, 0) AS ?nextOrder) - WHERE { - <@resourceIri> <@propertyIri> ?otherValue . - ?otherValue knora-base:valueHasOrder ?order ; - knora-base:isDeleted false . - } - } diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/searchFulltext.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/searchFulltext.scala.txt index 1adab5ab38..96117a32af 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/searchFulltext.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/searchFulltext.scala.txt @@ -14,8 +14,6 @@ * The number of rows returned per matching resource is equal to the number of values that matched in the resource, * plus one if the resource's label matched. * - * This template is used only by searchFulltext.scala.txt. - * * @param searchTerms search terms. * @param limitToProject limit search to the given project. * @param limitToResourceClass limit search to given resource class. diff --git a/webapi/src/test/scala/org/knora/webapi/slice/resources/repo/service/ResourcesRepoLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/resources/repo/service/ResourcesRepoLiveSpec.scala new file mode 100644 index 0000000000..2839995b21 --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/slice/resources/repo/service/ResourcesRepoLiveSpec.scala @@ -0,0 +1,279 @@ +/* + * Copyright © 2021 - 2024 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.resources.repo.service + +import zio.* +import zio.test.* + +import java.time.Instant +import java.util.UUID + +import dsp.valueobjects.UuidUtil +import org.knora.webapi.ApiV2Complex +import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.messages.twirl.NewLinkValueInfo +import org.knora.webapi.messages.twirl.NewValueInfo +import org.knora.webapi.messages.v2.responder.valuemessages.IntegerValueContentV2 +import org.knora.webapi.slice.resourceinfo.domain.InternalIri +import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update + +object ResourcesRepoLiveSpec extends ZIOSpecDefault { + def spec: Spec[Environment & (TestEnvironment & Scope), Any] = tests.provide(StringFormatter.test) + + val tests: Spec[StringFormatter, Nothing] = + suite("ResourcesRepoLiveSpec")( + test("Create new resource query without values") { + val tripleQuotes = "\"\"\"" + + val graphIri = InternalIri("fooGraph") + val projectIri = "fooProject" + val userIri = "fooUser" + val resourceIri = "fooResource" + val resourceClassIri = "fooClass" + val label = "fooLabel" + val creationDate = Instant.parse("2024-01-01T10:00:00.673298Z") + val permissions = "fooPermissions" + + val resourceDefinition = ResourceReadyToCreate( + resourceIri = resourceIri, + resourceClassIri = resourceClassIri, + resourceLabel = label, + creationDate = creationDate, + permissions = permissions, + newValueInfos = Seq.empty, + linkUpdates = Seq.empty, + ) + + val expected = + Update(s"""| + |PREFIX rdf: + |PREFIX rdfs: + |PREFIX owl: + |PREFIX xsd: + |PREFIX knora-base: + | + |INSERT DATA { + | GRAPH <${graphIri.value}> { + | <$resourceIri> rdf:type <$resourceClassIri> ; + | knora-base:isDeleted false ; + | knora-base:attachedToUser <$userIri> ; + | knora-base:attachedToProject <$projectIri> ; + | rdfs:label $tripleQuotes$label$tripleQuotes ; + | knora-base:hasPermissions "$permissions" ; + | knora-base:creationDate "$creationDate"^^xsd:dateTime . + | + | + | + | + | + | } + |} + |""".stripMargin) + val result = ResourcesRepoLive.createNewResourceQuery( + dataGraphIri = graphIri, + resourceToCreate = resourceDefinition, + projectIri = projectIri, + creatorIri = userIri, + ) + + assertTrue(expected.sparql == result.sparql) + }, + test("Create new resource query with values") { + val graphIri = InternalIri("fooGraph") + val projectIri = "fooProject" + val userIri = "fooUser" + val resourceIri = "fooResource" + val resourceClassIri = "fooClass" + val label = "fooLabel" + val creationDate = Instant.parse("2024-01-01T10:00:00.673298Z") + val permissions = "fooPermissions" + + val valueIri = "fooValue" + val valueCreator = "fooCreator" + val valuePermissions = "fooValuePermissions" + val valueCreationDate = Instant.parse("2024-01-01T10:00:00.673298Z") + + val uuid = UUID.randomUUID() + val uuidEncoded = UuidUtil.base64Encode(uuid) + + val resourceDefinition = ResourceReadyToCreate( + resourceIri = resourceIri, + resourceClassIri = resourceClassIri, + resourceLabel = label, + creationDate = creationDate, + permissions = permissions, + newValueInfos = Seq( + NewValueInfo( + resourceIri = resourceIri, + propertyIri = "fooProperty", + value = IntegerValueContentV2(ApiV2Complex, 42), + newValueIri = valueIri, + newValueUUID = uuid, + valueCreator = valueCreator, + valuePermissions = valuePermissions, + creationDate = valueCreationDate, + valueHasOrder = 1, + ), + ), + linkUpdates = Seq.empty, + ) + + val expected = + Update(s"""| + |PREFIX rdf: + |PREFIX rdfs: + |PREFIX owl: + |PREFIX xsd: + |PREFIX knora-base: + | + |INSERT DATA { + | GRAPH { + | rdf:type ; + | knora-base:isDeleted false ; + | knora-base:attachedToUser ; + | knora-base:attachedToProject ; + | rdfs:label \"\"\"fooLabel\"\"\" ; + | knora-base:hasPermissions \"fooPermissions\" ; + | knora-base:creationDate \"2024-01-01T10:00:00.673298Z\"^^xsd:dateTime . + | + | + | # Value: fooValue + | # Property: fooProperty + | + | + | rdf:type ; + | knora-base:isDeleted false ; + | knora-base:valueHasString \"\"\"42\"\"\" ; + | knora-base:valueHasUUID \"$uuidEncoded\" . + | + | + | + | + | knora-base:valueHasInteger 42 . + | + | + | + | + | + | + | knora-base:attachedToUser ; + | knora-base:hasPermissions \"fooValuePermissions\"^^xsd:string ; + | knora-base:valueHasOrder 1 ; + | knora-base:valueCreationDate \"2024-01-01T10:00:00.673298Z\"^^xsd:dateTime . + | + | + | + | + | . + | + | + | + | + | } + |} + |""".stripMargin) + val result = ResourcesRepoLive.createNewResourceQuery( + dataGraphIri = graphIri, + resourceToCreate = resourceDefinition, + projectIri = projectIri, + creatorIri = userIri, + ) + + assertTrue(expected.sparql == result.sparql) + }, + test("Create new resource query with links") { + val graphIri = InternalIri("fooGraph") + val projectIri = "fooProject" + val userIri = "fooUser" + val resourceIri = "fooResource" + val resourceClassIri = "fooClass" + val label = "fooLabel" + val creationDate = Instant.parse("2024-01-01T10:00:00.673298Z") + val permissions = "fooPermissions" + + val linkPropertyIri = "fooLinkProperty" + val linkTargetIri = "fooLinkTarget" + val linkValueIri = "fooLinkValue" + val linkCreator = "fooLinkCreator" + val linkPermissions = "fooLinkPermissions" + val valueUuid = UuidUtil.makeRandomBase64EncodedUuid + + val resourceDefinition = ResourceReadyToCreate( + resourceIri = resourceIri, + resourceClassIri = resourceClassIri, + resourceLabel = label, + creationDate = creationDate, + permissions = permissions, + newValueInfos = Seq.empty, + linkUpdates = Seq( + NewLinkValueInfo( + linkPropertyIri = linkPropertyIri, + newLinkValueIri = linkValueIri, + linkTargetIri = linkTargetIri, + newReferenceCount = 1, + newLinkValueCreator = linkCreator, + newLinkValuePermissions = linkPermissions, + valueUuid = valueUuid, + ), + ), + ) + + val expected = + Update(s"""| + |PREFIX rdf: + |PREFIX rdfs: + |PREFIX owl: + |PREFIX xsd: + |PREFIX knora-base: + | + |INSERT DATA { + | GRAPH { + | rdf:type ; + | knora-base:isDeleted false ; + | knora-base:attachedToUser ; + | knora-base:attachedToProject ; + | rdfs:label \"\"\"fooLabel\"\"\" ; + | knora-base:hasPermissions "fooPermissions" ; + | knora-base:creationDate "2024-01-01T10:00:00.673298Z"^^xsd:dateTime . + | + | + | + | + | + | + | . + | + | + | rdf:type knora-base:LinkValue ; + | rdf:subject ; + | rdf:predicate ; + | rdf:object ; + | knora-base:valueHasString "fooLinkTarget" ; + | knora-base:valueHasRefCount 1 ; + | knora-base:isDeleted false ; + | knora-base:valueCreationDate "2024-01-01T10:00:00.673298Z"^^xsd:dateTime ; + | knora-base:attachedToUser ; + | knora-base:hasPermissions "fooLinkPermissions" ; + | knora-base:valueHasUUID "$valueUuid" . + | + | + | . + | + | } + |} + |""".stripMargin) + val result = ResourcesRepoLive.createNewResourceQuery( + dataGraphIri = graphIri, + resourceToCreate = resourceDefinition, + projectIri = projectIri, + creatorIri = userIri, + ) + + assertTrue(expected.sparql == result.sparql) + }, + ) + +}