From 74a14e1eb5c475b307ee573042b5384f6c1f562a Mon Sep 17 00:00:00 2001 From: irinaschubert Date: Mon, 18 Jul 2022 13:11:16 +0200 Subject: [PATCH] fix(ontology): Don't accept list values without gui attribute (DEV-775) (#2089) * add test data * fix bug and add test * add missing import * fix typo in test data * add checks for all gui elements that need a gui attribute * update freetest data * fix typo in test data * check more values * add tests * remove prints * wip * remove featureFactoryConfig from new tests * refactor schema domain * remove make() from option * bump zio prelude version * rename decision file * rename file * add value objects * move salsah-gui constants to shared project * rename SalsahGuiApiV2WithValueObjects * improve value objects * improve validation * use val instead of string * small improvements * add more tests * improve docs * add r2r test * update expected client test data * Update expected-client-test-data.txt * Update expected-client-test-data.txt * make unit tests more readable * add new error type for validation fails * improve error messages --- docs/03-apis/api-v2/editing-resources.md | 6 +- docs/03-apis/api-v2/editing-values.md | 2 +- docs/03-apis/api-v2/index.md | 4 +- ...ain.scala => SchemaFunctionalDomain.scala} | 29 +- .../main/scala/dsp/constants/SalsahGui.scala | 119 +++++ .../src/main/scala/dsp/errors/Errors.scala | 8 + .../src/main/scala/dsp/valueobjects/Iri.scala | 22 + .../main/scala/dsp/valueobjects/Schema.scala | 201 ++++++++ .../scala/dsp/valueobjects/SchemaSpec.scala | 442 ++++++++++++++++++ project/Dependencies.scala | 2 +- test_data/all_data/freetest-data.ttl | 28 ++ test_data/ontologies/freetest-onto.ttl | 23 + webapi/scripts/expected-client-test-data.txt | 2 + .../webapi/messages/OntologyConstants.scala | 65 +-- .../webapi/messages/StringFormatter.scala | 46 +- .../resourcemessages/ResourceMessagesV1.scala | 39 +- .../ontologymessages/OntologyMessagesV2.scala | 120 ++--- .../responders/v1/OntologyResponderV1.scala | 17 +- .../responders/v1/ResourcesResponderV1.scala | 17 +- .../responders/v2/OntologyResponderV2.scala | 44 +- .../v2/ontology/OntologyHelpers.scala | 21 +- .../webapi/routing/v2/OntologiesRouteV2.scala | 168 ++++++- .../webapi/e2e/v2/OntologyV2R2RSpec.scala | 57 ++- .../webapi/messages/StringFormatterSpec.scala | 19 +- ...hangePropertyGuiElementRequestV2Spec.scala | 88 ---- .../v2/OntologyResponderV2Spec.scala | 82 ++-- .../responders/v2/ontology/CacheSpec.scala | 17 +- .../sharedtestdata/SharedTestDataADM.scala | 3 +- 28 files changed, 1273 insertions(+), 418 deletions(-) rename dsp-schema/core/src/main/scala/dsp/schema/domain/{SchemaDomain.scala => SchemaFunctionalDomain.scala} (93%) create mode 100644 dsp-shared/src/main/scala/dsp/constants/SalsahGui.scala create mode 100644 dsp-shared/src/main/scala/dsp/valueobjects/Schema.scala create mode 100644 dsp-shared/src/test/scala/dsp/valueobjects/SchemaSpec.scala delete mode 100644 webapi/src/test/scala/org/knora/webapi/messages/v2/responder/ontologymessages/ChangePropertyGuiElementRequestV2Spec.scala diff --git a/docs/03-apis/api-v2/editing-resources.md b/docs/03-apis/api-v2/editing-resources.md index 444562ece7..0e762822a1 100644 --- a/docs/03-apis/api-v2/editing-resources.md +++ b/docs/03-apis/api-v2/editing-resources.md @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 --> -# Editing Resources +# Creating and Editing Resources ## Creating a Resource @@ -17,7 +17,7 @@ The body of the request is a JSON-LD document in the [complex API schema](introduction.md#api-schema), specifying the type,`rdfs:label`, and its Knora resource properties and their values. The representation of the resource is the same as when it is returned in a `GET` request, except that its `knora-api:attachedToUser` is not given, and the resource IRI and those of its values can be optionally specified. -The format of the values submitted is described in [Editing Values](editing-values.md). If there are multiple values for +The format of the values submitted is described in [Creating and Editing Values](editing-values.md). If there are multiple values for a property, these must be given in an array. For example, here is a request to create a resource with various value types: @@ -222,7 +222,7 @@ of the resource. ## Modifying a Resource's Values -See [Editing Values](editing-values.md). +See [Creating and Editing Values](editing-values.md). ## Modifying a Resource's Metadata diff --git a/docs/03-apis/api-v2/editing-values.md b/docs/03-apis/api-v2/editing-values.md index 269a985fb4..83883c9733 100644 --- a/docs/03-apis/api-v2/editing-values.md +++ b/docs/03-apis/api-v2/editing-values.md @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 --> -# Editing Values +# Creating and Editing Values ## Creating a Value diff --git a/docs/03-apis/api-v2/index.md b/docs/03-apis/api-v2/index.md index a138f57e34..88f5d3421c 100644 --- a/docs/03-apis/api-v2/index.md +++ b/docs/03-apis/api-v2/index.md @@ -13,8 +13,8 @@ - [Getting Lists](getting-lists.md) - [XML to Standoff Mapping](xml-to-standoff-mapping.md) - [Gravsearch: Virtual Graph Search](query-language.md) -- [Editing Resources](editing-resources.md) -- [Editing Values](editing-values.md) +- [Creating and Editing Resources](editing-resources.md) +- [Creating and Editing Values](editing-values.md) - [Querying, Creating, and Updating Ontologies](ontology-information.md) - [TEI/XML](tei-xml.md) - [Permalinks](permalinks.md) diff --git a/dsp-schema/core/src/main/scala/dsp/schema/domain/SchemaDomain.scala b/dsp-schema/core/src/main/scala/dsp/schema/domain/SchemaFunctionalDomain.scala similarity index 93% rename from dsp-schema/core/src/main/scala/dsp/schema/domain/SchemaDomain.scala rename to dsp-schema/core/src/main/scala/dsp/schema/domain/SchemaFunctionalDomain.scala index da9cb214a0..25108a13cb 100644 --- a/dsp-schema/core/src/main/scala/dsp/schema/domain/SchemaDomain.scala +++ b/dsp-schema/core/src/main/scala/dsp/schema/domain/SchemaFunctionalDomain.scala @@ -1,7 +1,6 @@ /* * Copyright © 2021 - 2022 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. * SPDX-License-Identifier: Apache-2.0 - * */ package dsp.schema.domain @@ -11,11 +10,11 @@ import zio.prelude.Validation object SchemaDomain extends App { // implicitly["".type =:= "".type] - type IRI = String - type UserID = String + type IRI = String + type UserID = String type UserProfile = String - type SchemaID = String - type Schema = String + type SchemaID = String + type Schema = String final case class OntologyInfo(name: String, projectIri: IRI, label: String, comment: String) final case class OntologyClass[A <: Singleton with String](name: A, label: String, comment: String) { self => @@ -62,19 +61,19 @@ object SchemaDomain extends App { ct: CardinalityType ): WithTags[oc.Tag, op.Tag] = new Cardinality { - type ClassTag = oc.Tag + type ClassTag = oc.Tag type PropertyTag = op.Tag - val ontologyClass = oc + val ontologyClass = oc val ontologyProperty = op - val cardinalityType = ct + val cardinalityType = ct } } sealed trait CardinalityType object CardinalityType { - case object MaxCardinalityOne extends CardinalityType - case object MinCardinalityOne extends CardinalityType + case object MaxCardinalityOne extends CardinalityType + case object MinCardinalityOne extends CardinalityType case object MinCardinalityZero extends CardinalityType } @@ -214,16 +213,16 @@ object SchemaDomain extends App { //trying it out val ontoInfo = OntologyInfo("test", "http://example.org/test", "Test", "Test") - val classOne = OntologyClass("ClassOne", "Class One", "Class One") + val classOne = OntologyClass("ClassOne", "Class One", "Class One") val propertyOne = OntologyProperty("PropertyOne", "Property One", "Property One", "http://example.org/test") - val classTwo = OntologyClass("ClassTwo", "Class Two", "Class Two") + val classTwo = OntologyClass("ClassTwo", "Class Two", "Class Two") val propertyTwo = OntologyProperty("PropertyTwo", "Property Two", "Property Two", "http://example.org/test") - val cardOne = Cardinality(classOne, propertyOne, CardinalityType.MinCardinalityOne) - val cardTwo = Cardinality(classTwo, propertyTwo, CardinalityType.MinCardinalityOne) + val cardOne = Cardinality(classOne, propertyOne, CardinalityType.MinCardinalityOne) + val cardTwo = Cardinality(classTwo, propertyTwo, CardinalityType.MinCardinalityOne) val cardThree = Cardinality(classOne, propertyTwo, CardinalityType.MinCardinalityOne) - val cardFour = Cardinality(classTwo, propertyOne, CardinalityType.MinCardinalityOne) + val cardFour = Cardinality(classTwo, propertyOne, CardinalityType.MinCardinalityOne) val exampleOnto: Ontology[Any with "ClassOne" with "ClassTwo", Any with "PropertyOne"] = Ontology diff --git a/dsp-shared/src/main/scala/dsp/constants/SalsahGui.scala b/dsp-shared/src/main/scala/dsp/constants/SalsahGui.scala new file mode 100644 index 0000000000..f8c225614a --- /dev/null +++ b/dsp-shared/src/main/scala/dsp/constants/SalsahGui.scala @@ -0,0 +1,119 @@ +/* + * Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package dsp.constants + +import dsp.errors._ + +/** + * Contains string constants for IRIs from ontologies used by the application. + */ +object SalsahGui { + + /** + * `IRI` is a synonym for `String`, used to improve code readability. + */ + type IRI = String + + val InternalOntologyStart = "http://www.knora.org/ontology" + + val SalsahGuiOntologyLabel: String = "salsah-gui" + val SalsahGuiOntologyIri: IRI = InternalOntologyStart + "/" + SalsahGuiOntologyLabel + val SalsahGuiPrefixExpansion: IRI = SalsahGuiOntologyIri + "#" + + val GuiAttribute: IRI = SalsahGuiPrefixExpansion + "guiAttribute" + val GuiAttributeDefinition: IRI = SalsahGuiPrefixExpansion + "guiAttributeDefinition" + val GuiOrder: IRI = SalsahGuiPrefixExpansion + "guiOrder" + val GuiElementProp: IRI = SalsahGuiPrefixExpansion + "guiElement" + val GuiElementClass: IRI = SalsahGuiPrefixExpansion + "Guielement" + + val SimpleText: IRI = SalsahGuiPrefixExpansion + "SimpleText" + val Textarea: IRI = SalsahGuiPrefixExpansion + "Textarea" + val Pulldown: IRI = SalsahGuiPrefixExpansion + "Pulldown" + val Slider: IRI = SalsahGuiPrefixExpansion + "Slider" + val Spinbox: IRI = SalsahGuiPrefixExpansion + "Spinbox" + val Searchbox: IRI = SalsahGuiPrefixExpansion + "Searchbox" + val Date: IRI = SalsahGuiPrefixExpansion + "Date" + val Geometry: IRI = SalsahGuiPrefixExpansion + "Geometry" + val Colorpicker: IRI = SalsahGuiPrefixExpansion + "Colorpicker" + val List: IRI = SalsahGuiPrefixExpansion + "List" + val Radio: IRI = SalsahGuiPrefixExpansion + "Radio" + val Checkbox: IRI = SalsahGuiPrefixExpansion + "Checkbox" + val Richtext: IRI = SalsahGuiPrefixExpansion + "Richtext" + val Interval: IRI = SalsahGuiPrefixExpansion + "Interval" + val TimeStamp: IRI = SalsahGuiPrefixExpansion + "TimeStamp" + val Geonames: IRI = SalsahGuiPrefixExpansion + "Geonames" + val Fileupload: IRI = SalsahGuiPrefixExpansion + "Fileupload" + + val GuiElements = scala.collection.immutable.List( + SimpleText, + Textarea, + Pulldown, + Slider, + Spinbox, + Searchbox, + Date, + Geometry, + Colorpicker, + List, + Radio, + Checkbox, + Richtext, + Interval, + TimeStamp, + Geonames, + Fileupload + ) + + val GuiAttributes = scala.collection.immutable.List( + "ncolors", + "hlist", + "numprops", + "size", + "maxlength", + "min", + "max", + "cols", + "rows", + "width", + "wrap" + ) + + object SalsahGuiAttributeType extends Enumeration { + + val Integer: Value = Value(0, "integer") + val Percent: Value = Value(1, "percent") + val Decimal: Value = Value(2, "decimal") + val Str: Value = Value(3, "string") + val Iri: Value = Value(4, "iri") + + val valueMap: Map[String, Value] = values.map(v => (v.toString, v)).toMap + + def lookup(name: String): Value = + valueMap.get(name) match { + case Some(value) => value + case None => throw InconsistentRepositoryDataException(s"salsah-gui attribute type not found: $name") + } + } + + object External { + // external representation of salsah-gui entities of the form: http://api.knora.org/ontology/salsah-gui/v2#... + val ApiOntologyHostname: String = "http://api.knora.org" + val ApiOntologyStart: String = ApiOntologyHostname + "/ontology/" + val VersionSegment = "/v2" + val SalsahGuiOntologyIri: IRI = ApiOntologyStart + SalsahGui.SalsahGuiOntologyLabel + VersionSegment + val SalsahGuiPrefixExpansion: IRI = SalsahGuiOntologyIri + "#" + + val GuiAttribute: IRI = SalsahGuiPrefixExpansion + "guiAttribute" + val GuiOrder: IRI = SalsahGuiPrefixExpansion + "guiOrder" + val GuiElementProp: IRI = SalsahGuiPrefixExpansion + "guiElement" + val GuiAttributeDefinition: IRI = SalsahGuiPrefixExpansion + "guiAttributeDefinition" + val GuiElementClass: IRI = SalsahGuiPrefixExpansion + "Guielement" + val Geometry: IRI = SalsahGuiPrefixExpansion + "Geometry" + val Colorpicker: IRI = SalsahGuiPrefixExpansion + "Colorpicker" + val Fileupload: IRI = SalsahGuiPrefixExpansion + "Fileupload" + val Richtext: IRI = SalsahGuiPrefixExpansion + "Richtext" + } +} diff --git a/dsp-shared/src/main/scala/dsp/errors/Errors.scala b/dsp-shared/src/main/scala/dsp/errors/Errors.scala index 8be4ef9d72..812a0372cb 100644 --- a/dsp-shared/src/main/scala/dsp/errors/Errors.scala +++ b/dsp-shared/src/main/scala/dsp/errors/Errors.scala @@ -168,6 +168,14 @@ case class InvalidJsonLDException(msg: String, cause: Throwable = null) extends */ case class InvalidRdfException(msg: String, cause: Throwable = null) extends RequestRejectedException(msg, cause) +/** + * An exception indication that the validation of one or more values submitted to the API v2 failed. + * + * @param msg a description of the error. + * @param cause the cause for the error + */ +case class ValidationException(msg: String, cause: Throwable = null) extends RequestRejectedException(msg, cause) + /** * An abstract class for exceptions indicating that something went wrong and it's not the client's fault. * diff --git a/dsp-shared/src/main/scala/dsp/valueobjects/Iri.scala b/dsp-shared/src/main/scala/dsp/valueobjects/Iri.scala index 996035c235..873ccf9294 100644 --- a/dsp-shared/src/main/scala/dsp/valueobjects/Iri.scala +++ b/dsp-shared/src/main/scala/dsp/valueobjects/Iri.scala @@ -137,6 +137,27 @@ object Iri { } } } + + /** + * PropertyIri value object. + */ + sealed abstract case class PropertyIri private (value: String) extends Iri + object PropertyIri { + def make(value: String): Validation[Throwable, PropertyIri] = + if (value.isEmpty) { + Validation.fail(BadRequestException(IriErrorMessages.PropertyIriMissing)) + } else { + // TODO all the following needs to be checked when validating a property iri (see string formatter for the implementations of these methods) + // if ( + // !(propertyIri.isKnoraApiV2EntityIri && + // propertyIri.getOntologySchema.contains(ApiV2Complex) && + // propertyIri.getOntologyFromEntity == externalOntologyIri) + // ) { + // throw BadRequestException(s"Invalid property IRI: $propertyIri") + // } + Validation.succeed(new PropertyIri(value) {}) + } + } } object IriErrorMessages { @@ -149,4 +170,5 @@ object IriErrorMessages { val UserIriMissing = "User IRI cannot be empty." val UserIriInvalid = "User IRI is invalid." val UuidVersionInvalid = "Invalid UUID used to create IRI. Only versions 4 and 5 are supported." + val PropertyIriMissing = "Property IRI cannot be empty." } diff --git a/dsp-shared/src/main/scala/dsp/valueobjects/Schema.scala b/dsp-shared/src/main/scala/dsp/valueobjects/Schema.scala new file mode 100644 index 0000000000..e82762cff6 --- /dev/null +++ b/dsp-shared/src/main/scala/dsp/valueobjects/Schema.scala @@ -0,0 +1,201 @@ +/* + * Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package dsp.valueobjects + +import dsp.constants.SalsahGui +import dsp.errors.ValidationException +import zio.prelude.Subtype +import zio.prelude.Validation + +object Schema { + + /** + * GuiObject value object. + */ + sealed abstract case class GuiObject private (guiAttributes: List[GuiAttribute], guiElement: Option[GuiElement]) + object GuiObject { + def make(guiAttributes: List[GuiAttribute], guiElement: Option[GuiElement]): Validation[Throwable, GuiObject] = { + + // check that there are no duplicated gui attributes + val guiAttributeKeys: List[String] = guiAttributes.map { guiAttribute: GuiAttribute => guiAttribute.k } + if (guiAttributeKeys.toSet.size < guiAttributes.size) { + return Validation.fail( + ValidationException( + s"Duplicate gui attributes for salsah-gui:guiElement $guiElement." + ) + ) + } + + // If the gui element is a list, radio, pulldown or slider, check if a gui attribute (which is mandatory in these cases) is provided + val guiElementList = SalsahGui.List + val guiElementRadio = SalsahGui.Radio + val guiElementPulldown = SalsahGui.Pulldown + val guiElementSlider = SalsahGui.Slider + + val guiElementsPointingToList: Set[SalsahGui.IRI] = Set(guiElementList, guiElementRadio, guiElementPulldown) + + val needsGuiAttribute: Boolean = guiElement match { + case None => false + case Some(guiElement) => + guiElement.value == guiElementSlider || + guiElementsPointingToList.contains(guiElement.value) + } + + if (needsGuiAttribute) { + if (guiAttributes.isEmpty) { + return Validation.fail(ValidationException(SchemaErrorMessages.GuiAttributesMissing)) + } + val validatedGuiAttributes: Validation[Throwable, List[GuiAttribute]] = guiElement match { + // gui element is a list, radio or pulldown, so it needs a gui attribute that points to a list + case Some(guiElement) if guiElementsPointingToList.contains(guiElement.value) => + validateGuiObjectsPointingToList(guiElement, guiAttributes).fold( + e => Validation.fail(e.head), + v => Validation.succeed(v) + ) + + // gui element is a slider, so it needs two gui attributes min and max + case Some(guiElement) if guiElement.value == guiElementSlider => + validateGuiObjectSlider(guiElement, guiAttributes).fold( + e => Validation.fail(e.head), + v => Validation.succeed(v) + ) + + case _ => + Validation.fail( + ValidationException( + s"Unable to validate gui attributes. Unknown value for salsah-gui:guiElement: $guiElement." + ) + ) + } + + return validatedGuiAttributes.fold( + e => Validation.fail(e.head), + v => Validation.succeed(new GuiObject(v, guiElement) {}) + ) + + } + + Validation.succeed(new GuiObject(guiAttributes, guiElement) {}) + } + } + + /** + * GuiAttribute value object. + * + * @param k the key parameter of the gui attribute ('min', 'hlist', 'size' etc.) + * @param v the value parameter of the gui attribute (an int, a list's IRI etc.) + */ + sealed abstract case class GuiAttribute private (k: String, v: String) { + val value = k + "=" + v + } + object GuiAttribute { + def make(keyValue: String): Validation[Throwable, GuiAttribute] = { + val k: String = keyValue.split("=").head.trim() + // TODO also check the type of the value (integer, string etc.) + val v: String = keyValue.split("=").last.trim() + + if (keyValue.isEmpty) { + Validation.fail(ValidationException(SchemaErrorMessages.GuiAttributeMissing)) + } else if (!SalsahGui.GuiAttributes.contains(k)) { + Validation.fail(ValidationException(SchemaErrorMessages.GuiAttributeUnknown(k))) + } else { + Validation.succeed(new GuiAttribute(k, v) {}) + } + } + } + + /** + * GuiElement value object. + */ + sealed abstract case class GuiElement private (value: String) + object GuiElement { + def make(value: String): Validation[Throwable, GuiElement] = + if (value.isEmpty) { + Validation.fail(ValidationException(SchemaErrorMessages.GuiElementMissing)) + } else if (!SalsahGui.GuiElements.contains(value)) { + Validation.fail(ValidationException(SchemaErrorMessages.GuiElementUnknown)) + } else { + Validation.succeed(new GuiElement(value) {}) + } + } + + /** + * Validates if gui elements that require pointing to a list (List, Radio, Pulldown) actually point to a list + * + * @param guiElement the gui element that needs to be validated + * @param guiAttributes the gui attributes that need to be validated + * + * @return either the validated list of gui attributes or a [[dsp.errors.ValidationException]] + */ + private[valueobjects] def validateGuiObjectsPointingToList( + guiElement: GuiElement, + guiAttributes: List[GuiAttribute] + ): Validation[ValidationException, List[GuiAttribute]] = { + // gui element can have only one gui attribute + if (guiAttributes.size > 1) { + return Validation.fail( + ValidationException( + s"Wrong number of gui attributes. salsah-gui:guiElement $guiElement needs a salsah-gui:guiAttribute referencing a list of the form 'hlist=', but found $guiAttributes." + ) + ) + } + // gui attribute needs to point to a list + if (guiAttributes.head.k != ("hlist")) { + return Validation.fail( + ValidationException( + s"salsah-gui:guiAttribute for salsah-gui:guiElement $guiElement has to be a list reference of the form 'hlist=', but found ${guiAttributes.head}." + ) + ) + } else { + return Validation.succeed(guiAttributes) + } + } + + /** + * Validates if gui element Slider has the correct gui attributes + * + * @param guiElement the gui element that needs to be validated + * @param guiAttributes the gui attributes that need to be validated + * + * @return either the validated list of gui attributes or a [[dsp.errors.ValidationException]] + */ + private[valueobjects] def validateGuiObjectSlider( + guiElement: GuiElement, + guiAttributes: List[GuiAttribute] + ): Validation[ValidationException, List[GuiAttribute]] = { + // gui element needs two gui attributes + if (guiAttributes.size != 2) { + return Validation.fail( + ValidationException( + s"Wrong number of gui attributes. salsah-gui:guiElement $guiElement needs 2 salsah-gui:guiAttribute 'min' and 'max', but found ${guiAttributes.size}: $guiAttributes." + ) + ) + } + // gui element needs to have gui attributes 'min' and 'max' + val validGuiAttributes = scala.collection.immutable.List("min", "max") + guiAttributes.map { guiAttribute: GuiAttribute => + if (!validGuiAttributes.contains(guiAttribute.k)) { + return Validation.fail( + ValidationException( + s"Incorrect gui attributes. salsah-gui:guiElement $guiElement needs two salsah-gui:guiAttribute 'min' and 'max', but found $guiAttributes." + ) + ) + } + } + return Validation.succeed(guiAttributes) + } +} + +object SchemaErrorMessages { + val GuiAttributeMissing = "gui attribute cannot be empty." + def GuiAttributeUnknown(guiAttribute: String): String = + s"gui attribute '$guiAttribute' is unknown. Needs to be one of: ${SalsahGui.GuiAttributes.foreach(value => value)}" + val GuiElementMissing = "gui element cannot be empty." + def GuiElementInvalid(guiElement: String): String = s"gui element '$guiElement' is invalid." + val GuiElementUnknown = s"gui element is unknown. Needs to be one of: ${SalsahGui.GuiElements}" + val GuiObjectMissing = "gui object cannot be empty." + val GuiAttributesMissing = "gui attributes cannot be empty." +} diff --git a/dsp-shared/src/test/scala/dsp/valueobjects/SchemaSpec.scala b/dsp-shared/src/test/scala/dsp/valueobjects/SchemaSpec.scala new file mode 100644 index 0000000000..f4414613c6 --- /dev/null +++ b/dsp-shared/src/test/scala/dsp/valueobjects/SchemaSpec.scala @@ -0,0 +1,442 @@ +/* + * Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package dsp.valueobjects + +import dsp.constants.SalsahGui +import dsp.errors.ValidationException +import dsp.valueobjects.User._ +import zio.prelude.Validation +import zio.test._ + +/** + * This spec is used to test the [[dsp.valueobjects.User]] value objects creation. + */ +object SchemaSpec extends ZIOSpecDefault { + + private val guiAttributeSize = Schema.GuiAttribute.make("size=80").fold(e => throw e.head, v => v) + private val guiAttributeHlist = + Schema.GuiAttribute + .make("hlist=http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA") + .fold(e => throw e.head, v => v) + private val guiAttributeMin = Schema.GuiAttribute.make("min=1").fold(e => throw e.head, v => v) + private val guiAttributeMax = Schema.GuiAttribute.make("max=10").fold(e => throw e.head, v => v) + + private val guiElementList = Schema.GuiElement.make(SalsahGui.List).fold(e => throw e.head, v => v) + private val guiElementPulldown = Schema.GuiElement.make(SalsahGui.Pulldown).fold(e => throw e.head, v => v) + private val guiElementRadio = Schema.GuiElement.make(SalsahGui.Radio).fold(e => throw e.head, v => v) + private val guiElementSlider = Schema.GuiElement.make(SalsahGui.Slider).fold(e => throw e.head, v => v) + private val guiElementCheckbox = Schema.GuiElement.make(SalsahGui.Checkbox).fold(e => throw e.head, v => v) + + def spec = ( + guiAttributeTest + + guiElementTest + + guiObjectTest + + validateGuiObjectsPointingToListTest + + validateGuiObjectSliderTest + + guiObjectListTest + + guiObjectRadioTest + + guiObjectPulldownTest + + guiObjectSliderTest + + guiObjectCheckboxTest + ) + + private val guiAttributeTest = suite("gui attribute")( + test("pass an empty value and return an error") { + assertTrue( + Schema.GuiAttribute.make("") == Validation.fail(ValidationException(SchemaErrorMessages.GuiAttributeMissing)) + ) + }, + test("pass an invalid value and return an error") { + assertTrue( + Schema.GuiAttribute.make("invalid") == Validation.fail( + ValidationException(SchemaErrorMessages.GuiAttributeUnknown("invalid")) + ) + ) + }, + test("pass an unknown value and return an error") { + assertTrue( + Schema.GuiAttribute.make("unknown=10") == Validation.fail( + ValidationException(SchemaErrorMessages.GuiAttributeUnknown("unknown")) + ) + ) + }, + test("pass a valid value with whitespace and successfully create value object") { + val guiAttributeWithWhitespace = " size = 80 " + assertTrue(Schema.GuiAttribute.make(guiAttributeWithWhitespace).toOption.get.k == "size") && + assertTrue(Schema.GuiAttribute.make(guiAttributeWithWhitespace).toOption.get.v == "80") + }, + test("pass a valid value and successfully create value object") { + val guiAttributeSizeString = "size=80" + assertTrue(Schema.GuiAttribute.make(guiAttributeSizeString).toOption.get.k == "size") && + assertTrue(Schema.GuiAttribute.make(guiAttributeSizeString).toOption.get.v == "80") && + assertTrue(Schema.GuiAttribute.make(guiAttributeSizeString).toOption.get.value == "size=80") + } + ) + + private val guiElementTest = suite("gui element")( + test("pass an empty value and return an error") { + assertTrue( + Schema.GuiElement.make("") == Validation.fail(ValidationException(SchemaErrorMessages.GuiElementMissing)) + ) + }, + test("pass an unknown value and return an error") { + assertTrue( + Schema.GuiElement.make("http://www.knora.org/ontology/salsah-gui#Unknown") == Validation.fail( + ValidationException(SchemaErrorMessages.GuiElementUnknown) + ) + ) + }, + test("pass a valid value and successfully create value object") { + assertTrue(Schema.GuiElement.make(SalsahGui.List).toOption.get.value == SalsahGui.List) + } + ) + + private val validateGuiObjectsPointingToListTest = suite("validateGuiObjectsPointingToList")( + test( + "pass gui element 'salsah-gui#List' with gui attribute 'hlist' and successfully create value object" + ) { + val guiElement = guiElementList + val guiAttributes = scala.collection.immutable.List(guiAttributeHlist) + val result = Schema.validateGuiObjectsPointingToList(guiElement, guiAttributes) + assertTrue(result == Validation.succeed(guiAttributes)) + }, + test( + "pass a gui element that points to a list but has a misfitting gui attribute and return an error" + ) { + val guiElement = guiElementList + val guiAttributes = scala.collection.immutable.List(guiAttributeSize) + assertTrue( + Schema.validateGuiObjectsPointingToList(guiElement, guiAttributes) == Validation.fail( + ValidationException( + "salsah-gui:guiAttribute for salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#List) has to be a list reference of the form 'hlist=', but found GuiAttribute(size,80)." + ) + ) + ) + }, + test( + "pass gui element 'salsah-gui#List' with too many gui attributes 'min=1','hlist=http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA' and return an error" + ) { + val guiElement = guiElementList + val guiAttributes = scala.collection.immutable.List(guiAttributeMin, guiAttributeHlist) + assertTrue( + Schema.validateGuiObjectsPointingToList(guiElement, guiAttributes) == Validation.fail( + ValidationException( + "Wrong number of gui attributes. salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#List) needs a salsah-gui:guiAttribute referencing a list of the form 'hlist=', but found List(GuiAttribute(min,1), GuiAttribute(hlist,http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA))." + ) + ) + ) + } + ) + + private val validateGuiObjectSliderTest = suite("validateGuiObjectSlider")( + test( + "pass gui element 'salsah-gui#Slider' with gui attributes 'min=1' and 'max=10' and successfully create value object" + ) { + val guiElement = guiElementSlider + val guiAttributes = scala.collection.immutable.List(guiAttributeMin, guiAttributeMax) + val result = Schema.validateGuiObjectSlider(guiElement, guiAttributes) + assertTrue( + result == Validation.succeed(guiAttributes) + ) + }, + test( + "pass gui element 'salsah-gui#Slider' with too many gui attributes 'min=1','max=10', and 'min=80' and return an error" + ) { + val guiElement = guiElementSlider + val guiAttributes = scala.collection.immutable.List(guiAttributeMin, guiAttributeMax, guiAttributeSize) + assertTrue( + Schema.validateGuiObjectSlider(guiElement, guiAttributes) == Validation.fail( + ValidationException( + "Wrong number of gui attributes. salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#Slider) needs 2 salsah-gui:guiAttribute 'min' and 'max', but found 3: List(GuiAttribute(min,1), GuiAttribute(max,10), GuiAttribute(size,80))." + ) + ) + ) + }, + test( + "pass gui element 'salsah-gui#Slider' with too many gui attributes 'min=1','hlist=http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA' and return an error" + ) { + val guiElement = guiElementSlider + val guiAttributes = scala.collection.immutable.List(guiAttributeSize) + assertTrue( + Schema.validateGuiObjectSlider(guiElement, guiAttributes) == Validation.fail( + ValidationException( + "Wrong number of gui attributes. salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#Slider) needs 2 salsah-gui:guiAttribute 'min' and 'max', but found 1: List(GuiAttribute(size,80))." + ) + ) + ) + } + ) + + private val guiObjectTest = suite("gui object")( + test( + "pass valid gui element with duplicated gui attributes and return an error" + ) { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeMin, guiAttributeMin), + Some(guiElementSlider) + ) == Validation.fail( + ValidationException( + "Duplicate gui attributes for salsah-gui:guiElement Some(GuiElement(http://www.knora.org/ontology/salsah-gui#Slider))." + ) + ) + ) + } + ) + + private val guiObjectListTest = suite("gui object - List")( + test( + "pass gui element 'salsah-gui#List' with gui attribute 'hlist' and successfully create value object" + ) { + val guiObject = Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeHlist), + Some(guiElementList) + ) + .fold(e => throw e.head, v => v) + + assertTrue(guiObject.guiAttributes == scala.collection.immutable.List(guiAttributeHlist)) && + assertTrue(guiObject.guiElement == Some(guiElementList)) + }, + test( + "pass gui element 'salsah-gui#List' without gui attribute and return an error" + ) { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(), + Some(guiElementList) + ) == Validation.fail( + ValidationException( + SchemaErrorMessages.GuiAttributesMissing + ) + ) + ) + }, + test( + "pass gui element 'salsah-gui#List' with too many gui attributes 'min=1','hlist=http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA' and return an error" + ) { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeMin, guiAttributeHlist), + Some(guiElementList) + ) == Validation.fail( + ValidationException( + "Wrong number of gui attributes. salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#List) needs a salsah-gui:guiAttribute referencing a list of the form 'hlist=', but found List(GuiAttribute(min,1), GuiAttribute(hlist,http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA))." + ) + ) + ) + }, + test("pass gui element 'salsah-gui#List' with misfitting gui attribute 'size=80' and return an error") { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeSize), + Some(guiElementList) + ) == Validation.fail( + ValidationException( + "salsah-gui:guiAttribute for salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#List) has to be a list reference of the form 'hlist=', but found GuiAttribute(size,80)." + ) + ) + ) + } + ) + + private val guiObjectRadioTest = suite("gui object - Radio")( + test( + "pass gui element 'salsah-gui#Radio' with gui attribute 'hlist' and successfully create value object" + ) { + val guiObject = Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeHlist), + Some(guiElementRadio) + ) + .fold(e => throw e.head, v => v) + + assertTrue(guiObject.guiAttributes == scala.collection.immutable.List(guiAttributeHlist)) && + assertTrue(guiObject.guiElement == Some(guiElementRadio)) + }, + test( + "pass gui element 'salsah-gui#Radio' without gui attribute and return an error" + ) { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(), + Some(guiElementRadio) + ) == Validation.fail( + ValidationException( + SchemaErrorMessages.GuiAttributesMissing + ) + ) + ) + }, + test( + "pass gui element 'salsah-gui#Radio' with too many gui attributes 'min=1','hlist=http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA' and return an error" + ) { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeMin, guiAttributeHlist), + Some(guiElementRadio) + ) == Validation.fail( + ValidationException( + "Wrong number of gui attributes. salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#Radio) needs a salsah-gui:guiAttribute referencing a list of the form 'hlist=', but found List(GuiAttribute(min,1), GuiAttribute(hlist,http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA))." + ) + ) + ) + }, + test("pass gui element 'salsah-gui#Radio' with misfitting gui attribute 'size=80' and return an error") { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeSize), + Some(guiElementRadio) + ) == Validation.fail( + ValidationException( + "salsah-gui:guiAttribute for salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#Radio) has to be a list reference of the form 'hlist=', but found GuiAttribute(size,80)." + ) + ) + ) + } + ) + + private val guiObjectPulldownTest = suite("gui object - Pulldown")( + test( + "pass gui element 'salsah-gui#Pulldown' with gui attribute 'hlist' and successfully create value object" + ) { + val guiObject = Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeHlist), + Some(guiElementPulldown) + ) + .fold(e => throw e.head, v => v) + + assertTrue(guiObject.guiAttributes == scala.collection.immutable.List(guiAttributeHlist)) && + assertTrue(guiObject.guiElement == Some(guiElementPulldown)) + }, + test( + "pass gui element 'salsah-gui#Pulldown' without gui attribute and return an error" + ) { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(), + Some(guiElementPulldown) + ) == Validation.fail( + ValidationException( + SchemaErrorMessages.GuiAttributesMissing + ) + ) + ) + }, + test( + "pass gui element 'salsah-gui#Pulldown' with too many gui attributes 'min=1','hlist=http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA' and return an error" + ) { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeMin, guiAttributeHlist), + Some(guiElementPulldown) + ) == Validation.fail( + ValidationException( + "Wrong number of gui attributes. salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#Pulldown) needs a salsah-gui:guiAttribute referencing a list of the form 'hlist=', but found List(GuiAttribute(min,1), GuiAttribute(hlist,http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA))." + ) + ) + ) + }, + test("pass gui element 'salsah-gui#Pulldown' with misfitting gui attribute 'size=80' and return an error") { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeSize), + Some(guiElementPulldown) + ) == Validation.fail( + ValidationException( + "salsah-gui:guiAttribute for salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#Pulldown) has to be a list reference of the form 'hlist=', but found GuiAttribute(size,80)." + ) + ) + ) + } + ) + + private val guiObjectSliderTest = suite("gui object - Slider")( + test( + "pass gui element 'salsah-gui#Slider' with gui attributes 'min=1' and 'max=10' and successfully create value object" + ) { + val guiObject = Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeMin, guiAttributeMax), + Some(guiElementSlider) + ) + .fold(e => throw e.head, v => v) + + assertTrue(guiObject.guiAttributes == scala.collection.immutable.List(guiAttributeMin, guiAttributeMax)) && + assertTrue(guiObject.guiElement == Some(guiElementSlider)) + }, + test( + "pass gui element 'salsah-gui#Slider' without gui attribute and return an error" + ) { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(), + Some(guiElementSlider) + ) == Validation.fail( + ValidationException( + SchemaErrorMessages.GuiAttributesMissing + ) + ) + ) + }, + test( + "pass gui element 'salsah-gui#Slider' with too many gui attributes 'min=1','max=10', and 'min=80' and return an error" + ) { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeMin, guiAttributeMax, guiAttributeSize), + Some(guiElementSlider) + ) == Validation.fail( + ValidationException( + "Wrong number of gui attributes. salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#Slider) needs 2 salsah-gui:guiAttribute 'min' and 'max', but found 3: List(GuiAttribute(min,1), GuiAttribute(max,10), GuiAttribute(size,80))." + ) + ) + ) + }, + test("pass gui element 'salsah-gui#Slider' with misfitting gui attribute 'size=80' and return an error") { + assertTrue( + Schema.GuiObject + .make( + scala.collection.immutable.List(guiAttributeSize), + Some(guiElementSlider) + ) == Validation.fail( + ValidationException( + "Wrong number of gui attributes. salsah-gui:guiElement GuiElement(http://www.knora.org/ontology/salsah-gui#Slider) needs 2 salsah-gui:guiAttribute 'min' and 'max', but found 1: List(GuiAttribute(size,80))." + ) + ) + ) + } + ) + + private val guiObjectCheckboxTest = suite("gui object - Checkbox")( + test( + "pass gui element 'salsah-gui#Checkbox' without gui attributes and successfully create value object" + ) { + val guiObject = Schema.GuiObject + .make( + scala.collection.immutable.List(), + Some(guiElementCheckbox) + ) + .fold(e => throw e.head, v => v) + + assertTrue(guiObject.guiAttributes == scala.collection.immutable.List()) && + assertTrue(guiObject.guiElement == Some(guiElementCheckbox)) + } + ) +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 0e5a6e9f5d..373552163a 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -27,7 +27,7 @@ object Dependencies { val ZioSchemaVersion = "0.2.0" val ZioLoggingVersion = "2.0.0" val ZioZmxVersion = "2.0.0-RC4" - val ZioPreludeVersion = "1.0.0-RC13" + val ZioPreludeVersion = "1.0.0-RC15" // ZIO - all Scala 3 compatible val zio = "dev.zio" %% "zio" % ZioVersion diff --git a/test_data/all_data/freetest-data.ttl b/test_data/all_data/freetest-data.ttl index 5c870aafe8..6ea99f1788 100644 --- a/test_data/all_data/freetest-data.ttl +++ b/test_data/all_data/freetest-data.ttl @@ -10,6 +10,34 @@ @prefix freetest: . @prefix standoff: . +# List + + a knora-base:ListNode ; + knora-base:isRootNode true ; + knora-base:listNodeName "freetestlist" ; + rdfs:label "Free Test List Rootnode"@en ; + rdfs:comment "Free Test List"@en ; + knora-base:attachedToProject ; + knora-base:hasSubListNode . + + + a knora-base:ListNode ; + knora-base:listNodeName "freetestnode01" ; + knora-base:hasRootNode ; + knora-base:listNodePosition 0 ; + rdfs:label "Free Test Node 01"@en . + +# Resources and Values + + a freetest:FreeTestWithListValue ; + knora-base:attachedToUser ; + knora-base:attachedToProject ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember" ; + knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime ; + freetest:hasListValue ; + rdfs:label "an object with a list value"@en ; + knora-base:isDeleted false . + a freetest:FreeTest ; knora-base:attachedToUser ; diff --git a/test_data/ontologies/freetest-onto.ttl b/test_data/ontologies/freetest-onto.ttl index 7c9cc4d0aa..a9ba3514c3 100644 --- a/test_data/ontologies/freetest-onto.ttl +++ b/test_data/ontologies/freetest-onto.ttl @@ -89,6 +89,18 @@ +:hasListValue + rdf:type owl:ObjectProperty ; + rdfs:subPropertyOf knora-base:hasValue ; + rdfs:label "Listenwert"@de, + "List value"@en ; + knora-base:subjectClassConstraint :FreeTestWithListValue ; + knora-base:objectClassConstraint knora-base:ListValue ; + salsah-gui:guiElement salsah-gui:List ; + salsah-gui:guiAttribute "hlist=" . + + + :FreeTest rdf:type owl:Class ; rdfs:subClassOf knora-base:Resource, @@ -407,3 +419,14 @@ knora-base:subjectClassConstraint :Book ; knora-base:objectClassConstraint knora-base:LinkValue ; salsah-gui:guiElement salsah-gui:Searchbox . + +:FreeTestWithListValue + rdf:type owl:Class ; + rdfs:subClassOf knora-base:Resource , + [ rdf:type owl:Restriction ; + owl:onProperty :hasListValue ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ] ; + rdfs:label "Listenobjekt"@de, + "Object with a list value"@en ; + rdfs:comment """A comment for object with a list value"""@en . diff --git a/webapi/scripts/expected-client-test-data.txt b/webapi/scripts/expected-client-test-data.txt index 3449a91634..9e493ea9e0 100644 --- a/webapi/scripts/expected-client-test-data.txt +++ b/webapi/scripts/expected-client-test-data.txt @@ -213,6 +213,8 @@ test-data/v2/ontologies/get-property-textValue-response.json test-data/v2/ontologies/incunabula-ontology.json test-data/v2/ontologies/knora-api-ontology.json test-data/v2/ontologies/minimal-ontology.json +test-data/v2/ontologies/not-change-property-guielement-request.json +test-data/v2/ontologies/not-change-property-guielement-response.json test-data/v2/ontologies/remove-class-cardinalities-request.json test-data/v2/ontologies/remove-property-cardinality-request.json test-data/v2/ontologies/remove-property-guielement-request.json diff --git a/webapi/src/main/scala/org/knora/webapi/messages/OntologyConstants.scala b/webapi/src/main/scala/org/knora/webapi/messages/OntologyConstants.scala index 068136524e..adfc62546d 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/OntologyConstants.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/OntologyConstants.scala @@ -6,6 +6,7 @@ package org.knora.webapi package messages +import dsp.constants.SalsahGui import dsp.errors._ /** @@ -594,53 +595,6 @@ object OntologyConstants { val StandoffStyleElementTag: IRI = StandoffPrefixExpansion + "StandoffStyleTag" } - object SalsahGui { - val SalsahGuiOntologyLabel: String = "salsah-gui" - val SalsahGuiOntologyIri: IRI = KnoraInternal.InternalOntologyStart + "/" + SalsahGuiOntologyLabel - val SalsahGuiPrefixExpansion: IRI = SalsahGuiOntologyIri + "#" - - val GuiAttribute: IRI = SalsahGuiPrefixExpansion + "guiAttribute" - val GuiAttributeDefinition: IRI = SalsahGuiPrefixExpansion + "guiAttributeDefinition" - val GuiOrder: IRI = SalsahGuiPrefixExpansion + "guiOrder" - val GuiElementProp: IRI = SalsahGuiPrefixExpansion + "guiElement" - val GuiElementClass: IRI = SalsahGuiPrefixExpansion + "Guielement" - val SimpleText: IRI = SalsahGuiPrefixExpansion + "SimpleText" - val Textarea: IRI = SalsahGuiPrefixExpansion + "Textarea" - val Pulldown: IRI = SalsahGuiPrefixExpansion + "Pulldown" - val Slider: IRI = SalsahGuiPrefixExpansion + "Slider" - val Spinbox: IRI = SalsahGuiPrefixExpansion + "Spinbox" - val Searchbox: IRI = SalsahGuiPrefixExpansion + "Searchbox" - val Date: IRI = SalsahGuiPrefixExpansion + "Date" - val Geometry: IRI = SalsahGuiPrefixExpansion + "Geometry" - val Colorpicker: IRI = SalsahGuiPrefixExpansion + "Colorpicker" - val List: IRI = SalsahGuiPrefixExpansion + "List" - val Radio: IRI = SalsahGuiPrefixExpansion + "Radio" - val Checkbox: IRI = SalsahGuiPrefixExpansion + "Checkbox" - val Richtext: IRI = SalsahGuiPrefixExpansion + "Richtext" - val Interval: IRI = SalsahGuiPrefixExpansion + "Interval" - val TimeStamp: IRI = SalsahGuiPrefixExpansion + "TimeStamp" - val Geonames: IRI = SalsahGuiPrefixExpansion + "Geonames" - val Fileupload: IRI = SalsahGuiPrefixExpansion + "Fileupload" - - object SalsahGuiAttributeType extends Enumeration { - - val Integer: Value = Value(0, "integer") - val Percent: Value = Value(1, "percent") - val Decimal: Value = Value(2, "decimal") - val Str: Value = Value(3, "string") - val Iri: Value = Value(4, "iri") - - val valueMap: Map[String, Value] = values.map(v => (v.toString, v)).toMap - - def lookup(name: String): Value = - valueMap.get(name) match { - case Some(value) => value - case None => throw InconsistentRepositoryDataException(s"salsah-gui attribute type not found: $name") - } - } - - } - object Ontotext { val LuceneFulltext = "http://www.ontotext.com/owlim/lucene#fullTextSearchIndex" } @@ -972,23 +926,6 @@ object OntologyConstants { val UseInference: IRI = KnoraApiV2PrefixExpansion + "useInference" } - object SalsahGuiApiV2WithValueObjects { - val SalsahGuiOntologyIri: IRI = - KnoraApi.ApiOntologyStart + SalsahGui.SalsahGuiOntologyLabel + KnoraApiV2Complex.VersionSegment - val SalsahGuiPrefixExpansion: IRI = SalsahGuiOntologyIri + "#" - - val GuiAttribute: IRI = SalsahGuiPrefixExpansion + "guiAttribute" - val GuiOrder: IRI = SalsahGuiPrefixExpansion + "guiOrder" - val GuiElementProp: IRI = SalsahGuiPrefixExpansion + "guiElement" - val GuiAttributeDefinition: IRI = SalsahGuiPrefixExpansion + "guiAttributeDefinition" - val GuiElementClass: IRI = SalsahGuiPrefixExpansion + "Guielement" - - val Geometry: IRI = SalsahGuiPrefixExpansion + "Geometry" - val Colorpicker: IRI = SalsahGuiPrefixExpansion + "Colorpicker" - val Fileupload: IRI = SalsahGuiPrefixExpansion + "Fileupload" - val Richtext: IRI = SalsahGuiPrefixExpansion + "Richtext" - } - object KnoraApiV2Simple { val VersionSegment = "/simple/v2" 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 2dd1687a70..12e2125ce3 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala @@ -6,17 +6,18 @@ package org.knora.webapi.messages import akka.actor.ActorRef -import com.typesafe.scalalogging.Logger import akka.http.scaladsl.util.FastFuture import akka.pattern._ import akka.util.Timeout import com.google.gwt.safehtml.shared.UriUtils._ +import com.typesafe.scalalogging.Logger +import dsp.constants.SalsahGui +import dsp.errors._ +import dsp.valueobjects.IriErrorMessages import org.apache.commons.lang3.StringUtils import org.apache.commons.validator.routines.UrlValidator import org.knora.webapi._ -import dsp.errors._ import org.knora.webapi.messages.IriConversions._ -import org.knora.webapi.messages.OntologyConstants.SalsahGui import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM import org.knora.webapi.messages.store.triplestoremessages.SparqlAskRequest import org.knora.webapi.messages.store.triplestoremessages.SparqlAskResponse @@ -46,7 +47,6 @@ import scala.util.Success import scala.util.Try import scala.util.control.Exception._ import scala.util.matching.Regex -import dsp.valueobjects.IriErrorMessages /** * Provides instances of [[StringFormatter]], as well as string formatting constants. @@ -193,7 +193,7 @@ object StringFormatter { case class SalsahGuiAttributeDefinition( attributeName: String, isRequired: Boolean, - allowedType: OntologyConstants.SalsahGui.SalsahGuiAttributeType.Value, + allowedType: SalsahGui.SalsahGuiAttributeType.Value, enumeratedValues: Set[String] = Set.empty[String], unparsedString: String ) @@ -210,7 +210,7 @@ object StringFormatter { * Represents a parsed value of an attribute that is the object of the property `salsah-gui:guiAttribute`. */ sealed trait SalsahGuiAttributeValue { - def attributeType: OntologyConstants.SalsahGui.SalsahGuiAttributeType.Value + def attributeType: SalsahGui.SalsahGuiAttributeType.Value } /** @@ -219,8 +219,8 @@ object StringFormatter { * @param value the integer value. */ case class SalsahGuiIntegerAttributeValue(value: Int) extends SalsahGuiAttributeValue { - override val attributeType: OntologyConstants.SalsahGui.SalsahGuiAttributeType.Value = - OntologyConstants.SalsahGui.SalsahGuiAttributeType.Integer + override val attributeType: SalsahGui.SalsahGuiAttributeType.Value = + SalsahGui.SalsahGuiAttributeType.Integer } /** @@ -229,8 +229,8 @@ object StringFormatter { * @param value the percent value. */ case class SalsahGuiPercentAttributeValue(value: Int) extends SalsahGuiAttributeValue { - override val attributeType: OntologyConstants.SalsahGui.SalsahGuiAttributeType.Value = - OntologyConstants.SalsahGui.SalsahGuiAttributeType.Percent + override val attributeType: SalsahGui.SalsahGuiAttributeType.Value = + SalsahGui.SalsahGuiAttributeType.Percent } /** @@ -239,8 +239,8 @@ object StringFormatter { * @param value the decimal value. */ case class SalsahGuiDecimalAttributeValue(value: BigDecimal) extends SalsahGuiAttributeValue { - override val attributeType: OntologyConstants.SalsahGui.SalsahGuiAttributeType.Value = - OntologyConstants.SalsahGui.SalsahGuiAttributeType.Decimal + override val attributeType: SalsahGui.SalsahGuiAttributeType.Value = + SalsahGui.SalsahGuiAttributeType.Decimal } /** @@ -249,8 +249,8 @@ object StringFormatter { * @param value the string value. */ case class SalsahGuiStringAttributeValue(value: String) extends SalsahGuiAttributeValue { - override val attributeType: OntologyConstants.SalsahGui.SalsahGuiAttributeType.Value = - OntologyConstants.SalsahGui.SalsahGuiAttributeType.Str + override val attributeType: SalsahGui.SalsahGuiAttributeType.Value = + SalsahGui.SalsahGuiAttributeType.Str } /** @@ -259,8 +259,8 @@ object StringFormatter { * @param value the IRI value. */ case class SalsahGuiIriAttributeValue(value: IRI) extends SalsahGuiAttributeValue { - override val attributeType: OntologyConstants.SalsahGui.SalsahGuiAttributeType.Value = - OntologyConstants.SalsahGui.SalsahGuiAttributeType.Iri + override val attributeType: SalsahGui.SalsahGuiAttributeType.Value = + SalsahGui.SalsahGuiAttributeType.Iri } /* @@ -1828,11 +1828,11 @@ class StringFormatter private ( s match { case SalsahGuiAttributeDefinitionRegex(attributeName, required, allowedTypeStr, _, enumeratedValuesStr) => val allowedType: SalsahGui.SalsahGuiAttributeType.Value = - OntologyConstants.SalsahGui.SalsahGuiAttributeType.lookup(allowedTypeStr) + SalsahGui.SalsahGuiAttributeType.lookup(allowedTypeStr) val enumeratedValues: Set[String] = Option(enumeratedValuesStr) match { case Some(enumeratedValuesStr) => - if (allowedType != OntologyConstants.SalsahGui.SalsahGuiAttributeType.Str) { + if (allowedType != SalsahGui.SalsahGuiAttributeType.Str) { errorFun } @@ -1874,13 +1874,13 @@ class StringFormatter private ( // Try to parse the value as the type given in the attribute definition. val maybeParsedAttrValue: Option[SalsahGuiAttributeValue] = attributeDef.allowedType match { - case OntologyConstants.SalsahGui.SalsahGuiAttributeType.Integer => + case SalsahGui.SalsahGuiAttributeType.Integer => catching(classOf[NumberFormatException]).opt(attributeValue.toInt).map(SalsahGuiIntegerAttributeValue) - case OntologyConstants.SalsahGui.SalsahGuiAttributeType.Decimal => + case SalsahGui.SalsahGuiAttributeType.Decimal => catching(classOf[NumberFormatException]).opt(BigDecimal(attributeValue)).map(SalsahGuiDecimalAttributeValue) - case OntologyConstants.SalsahGui.SalsahGuiAttributeType.Percent => + case SalsahGui.SalsahGuiAttributeType.Percent => if (attributeValue.endsWith("%")) { val intStr = attributeValue.stripSuffix("%") catching(classOf[NumberFormatException]).opt(intStr.toInt).map(SalsahGuiPercentAttributeValue) @@ -1888,7 +1888,7 @@ class StringFormatter private ( None } - case OntologyConstants.SalsahGui.SalsahGuiAttributeType.Iri => + case SalsahGui.SalsahGuiAttributeType.Iri => if (attributeValue.startsWith("<") && attributeValue.endsWith(">")) { val iriWithoutAngleBrackets = attributeValue.substring(1, attributeValue.length - 1) @@ -1899,7 +1899,7 @@ class StringFormatter private ( None } - case OntologyConstants.SalsahGui.SalsahGuiAttributeType.Str => + case SalsahGui.SalsahGuiAttributeType.Str => if (attributeDef.enumeratedValues.nonEmpty && !attributeDef.enumeratedValues.contains(attributeValue)) { errorFun } diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v1/responder/resourcemessages/ResourceMessagesV1.scala b/webapi/src/main/scala/org/knora/webapi/messages/v1/responder/resourcemessages/ResourceMessagesV1.scala index 6c4627f3f4..9979e71ce9 100755 --- a/webapi/src/main/scala/org/knora/webapi/messages/v1/responder/resourcemessages/ResourceMessagesV1.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v1/responder/resourcemessages/ResourceMessagesV1.scala @@ -6,15 +6,16 @@ package org.knora.webapi.messages.v1.responder.resourcemessages import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport -import org.knora.webapi._ +import dsp.constants.SalsahGui import dsp.errors.BadRequestException import dsp.errors.DataConversionException import dsp.errors.InconsistentRepositoryDataException import dsp.errors.InvalidApiJsonException +import org.knora.webapi._ import org.knora.webapi.messages.OntologyConstants +import org.knora.webapi.messages.ResponderRequest.KnoraRequestV1 import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM import org.knora.webapi.messages.admin.responder.usersmessages.UserADM -import org.knora.webapi.messages.ResponderRequest.KnoraRequestV1 import org.knora.webapi.messages.v1.responder.KnoraResponseV1 import org.knora.webapi.messages.v1.responder.valuemessages._ import org.knora.webapi.messages.v2.responder.UpdateResultInProject @@ -802,23 +803,23 @@ object SalsahGuiConversions { * A [[Map]] of Knora IRIs to SALSAH GUI element names. */ private val iris2SalsahGuiElements: Map[IRI, IRI] = Map( - OntologyConstants.SalsahGui.SimpleText -> "text", - OntologyConstants.SalsahGui.Textarea -> "textarea", - OntologyConstants.SalsahGui.Pulldown -> "pulldown", - OntologyConstants.SalsahGui.Slider -> "slider", - OntologyConstants.SalsahGui.Spinbox -> "spinbox", - OntologyConstants.SalsahGui.Searchbox -> "searchbox", - OntologyConstants.SalsahGui.Date -> "date", - OntologyConstants.SalsahGui.Geometry -> "geometry", - OntologyConstants.SalsahGui.Colorpicker -> "colorpicker", - OntologyConstants.SalsahGui.List -> "hlist", - OntologyConstants.SalsahGui.Radio -> "radio", - OntologyConstants.SalsahGui.Checkbox -> "checkbox", - OntologyConstants.SalsahGui.Richtext -> "richtext", - OntologyConstants.SalsahGui.Interval -> "interval", - OntologyConstants.SalsahGui.TimeStamp -> "timestamp", - OntologyConstants.SalsahGui.Geonames -> "geoname", - OntologyConstants.SalsahGui.Fileupload -> "fileupload" + SalsahGui.SimpleText -> "text", + SalsahGui.Textarea -> "textarea", + SalsahGui.Pulldown -> "pulldown", + SalsahGui.Slider -> "slider", + SalsahGui.Spinbox -> "spinbox", + SalsahGui.Searchbox -> "searchbox", + SalsahGui.Date -> "date", + SalsahGui.Geometry -> "geometry", + SalsahGui.Colorpicker -> "colorpicker", + SalsahGui.List -> "hlist", + SalsahGui.Radio -> "radio", + SalsahGui.Checkbox -> "checkbox", + SalsahGui.Richtext -> "richtext", + SalsahGui.Interval -> "interval", + SalsahGui.TimeStamp -> "timestamp", + SalsahGui.Geonames -> "geoname", + SalsahGui.Fileupload -> "fileupload" ) /** diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala index 258ccac0d3..561c1a00d8 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala @@ -6,33 +6,36 @@ package org.knora.webapi.messages.v2.responder.ontologymessages import akka.actor.ActorRef -import com.typesafe.scalalogging.Logger import akka.util.Timeout +import com.typesafe.scalalogging.Logger +import dsp.constants.SalsahGui +import dsp.errors.AssertionException +import dsp.errors.BadRequestException +import dsp.errors.DataConversionException +import dsp.errors.InconsistentRepositoryDataException +import dsp.valueobjects.Iri +import dsp.valueobjects.Schema import org.apache.commons.lang3.builder.HashCodeBuilder - -import java.time.Instant -import java.util.UUID -import scala.concurrent.ExecutionContext -import scala.concurrent.Future - import org.knora.webapi._ -import dsp.errors.{ - AssertionException, - BadRequestException, - DataConversionException, - InconsistentRepositoryDataException -} -import org.knora.webapi.messages.ResponderRequest.KnoraRequestV2 import org.knora.webapi.messages.IriConversions._ +import org.knora.webapi.messages.OntologyConstants +import org.knora.webapi.messages.ResponderRequest.KnoraRequestV2 +import org.knora.webapi.messages.SmartIri +import org.knora.webapi.messages.StringFormatter import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.store.triplestoremessages._ import org.knora.webapi.messages.util.rdf._ import org.knora.webapi.messages.v2.responder._ -import org.knora.webapi.messages.v2.responder.ontologymessages.Cardinality.{KnoraCardinalityInfo, OwlCardinalityInfo} +import org.knora.webapi.messages.v2.responder.ontologymessages.Cardinality.KnoraCardinalityInfo +import org.knora.webapi.messages.v2.responder.ontologymessages.Cardinality.OwlCardinalityInfo import org.knora.webapi.messages.v2.responder.standoffmessages.StandoffDataTypeClasses -import org.knora.webapi.messages.{OntologyConstants, SmartIri, StringFormatter} import org.knora.webapi.settings.KnoraSettingsImpl +import java.time.Instant +import java.util.UUID +import scala.concurrent.ExecutionContext +import scala.concurrent.Future + /** * An abstract trait for messages that can be sent to `ResourcesResponderV2`. */ @@ -896,16 +899,14 @@ sealed trait ChangeLabelsOrCommentsRequest { * Requests that the `salsah-gui:guiElement` and `salsah-gui:guiAttribute` of a property are changed. * * @param propertyIri the IRI of the property to be changed. - * @param newGuiElement the new GUI element to be used with the property, or `None` if no GUI element should be specified. - * @param newGuiAttributes the new GUI attributes to be used with the property, or `None` if no GUI element should be specified. + * @param newGuiObject the GUI object with the new GUI element and/or GUI attributes. * @param lastModificationDate the ontology's last modification date. * @param apiRequestID the ID of the API request. * @param requestingUser the user making the request. */ case class ChangePropertyGuiElementRequest( - propertyIri: SmartIri, - newGuiElement: Option[SmartIri], - newGuiAttributes: Set[String], + propertyIri: Iri.PropertyIri, + newGuiObject: Schema.GuiObject, lastModificationDate: Instant, apiRequestID: UUID, requestingUser: UserADM @@ -922,11 +923,12 @@ object ChangePropertyGuiElementRequest extends KnoraJsonLDRequestReaderV2[Change * @param jsonLDDocument the JSON-LD input. * @param apiRequestID the UUID of the API request. * @param requestingUser the user making the request. - * @param appActror a reference to the application actor. + * @param appActor a reference to the application actor. * @param settings the application settings. * @param log a logging adapter. * @return a [[ChangePropertyLabelsOrCommentsRequestV2]] representing the input. */ + @deprecated override def fromJsonLD( jsonLDDocument: JsonLDDocument, apiRequestID: UUID, @@ -936,58 +938,10 @@ object ChangePropertyGuiElementRequest extends KnoraJsonLDRequestReaderV2[Change log: Logger )(implicit timeout: Timeout, executionContext: ExecutionContext): Future[ChangePropertyGuiElementRequest] = Future { - fromJsonLDSync( - jsonLDDocument = jsonLDDocument, - apiRequestID = apiRequestID, - requestingUser = requestingUser + throw BadRequestException( + "Deprecated method fromJsonLD() for ChangePropertyGuiElementRequest. Please report this as a bug." ) } - - private def fromJsonLDSync( - jsonLDDocument: JsonLDDocument, - apiRequestID: UUID, - requestingUser: UserADM - ): ChangePropertyGuiElementRequest = { - implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance - - val inputOntologiesV2 = InputOntologyV2.fromJsonLD(jsonLDDocument) - val propertyUpdateInfo = OntologyUpdateHelper.getPropertyDef(inputOntologiesV2) - val propertyInfoContent = propertyUpdateInfo.propertyInfoContent - val lastModificationDate = propertyUpdateInfo.lastModificationDate - - val newGuiElement: Option[SmartIri] = - propertyInfoContent.predicates - .get( - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri - ) - .map { predicateInfoV2: PredicateInfoV2 => - predicateInfoV2.objects.head match { - case iriLiteralV2: SmartIriLiteralV2 => iriLiteralV2.value.toOntologySchema(InternalSchema) - case other => - throw BadRequestException(s"Unexpected object for salsah-gui:guiElement: $other") - } - } - - val newGuiAttributes: Set[String] = - propertyInfoContent.predicates - .get(OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri) - .map { predicateInfoV2: PredicateInfoV2 => - predicateInfoV2.objects.map { - case stringLiteralV2: StringLiteralV2 => stringLiteralV2.value - case other => throw BadRequestException(s"Unexpected object for salsah-gui:guiAttribute: $other") - }.toSet - } - .getOrElse(Set.empty[String]) - - ChangePropertyGuiElementRequest( - propertyIri = propertyInfoContent.propertyIri, - newGuiElement = newGuiElement, - newGuiAttributes = newGuiAttributes, - lastModificationDate = lastModificationDate, - apiRequestID = apiRequestID, - requestingUser = requestingUser - ) - } } /** @@ -1761,7 +1715,7 @@ case class ReadOntologyV2( val salsahGuiPrefix: Option[(String, String)] = targetSchema match { case ApiV2Complex => Some( - OntologyConstants.SalsahGui.SalsahGuiOntologyLabel -> OntologyConstants.SalsahGuiApiV2WithValueObjects.SalsahGuiPrefixExpansion + SalsahGui.SalsahGuiOntologyLabel -> SalsahGui.External.SalsahGuiPrefixExpansion ) case _ => None @@ -1771,7 +1725,7 @@ case class ReadOntologyV2( val otherKnoraOntologiesUsed: Set[SmartIri] = (knoraOntologiesFromClasses ++ knoraOntologiesFromProperties).filterNot { ontology => ontology.getOntologyName == OntologyConstants.KnoraApi.KnoraApiOntologyLabel || - ontology.getOntologyName == OntologyConstants.SalsahGui.SalsahGuiOntologyLabel + ontology.getOntologyName == SalsahGui.SalsahGuiOntologyLabel } // Make the JSON-LD context. @@ -2875,7 +2829,7 @@ case class ReadClassInfoV2( val guiOrderStatement = targetSchema match { case ApiV2Complex => cardinalityInfo.guiOrder.map { guiOrder => - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiOrder -> JsonLDInt(guiOrder) + SalsahGui.External.GuiOrder -> JsonLDInt(guiOrder) } case _ => None @@ -3065,9 +3019,9 @@ case class ReadPropertyInfoV2( val guiElementStatement: Option[(IRI, JsonLDObject)] = if (targetSchema == ApiV2Complex) { entityInfoContent - .getPredicateIriObject(OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri) + .getPredicateIriObject(SalsahGui.External.GuiElementProp.toSmartIri) .map { obj => - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp -> JsonLDUtil.iriToJsonLDObject(obj.toString) + SalsahGui.External.GuiElementProp -> JsonLDUtil.iriToJsonLDObject(obj.toString) } } else { None @@ -3075,11 +3029,11 @@ case class ReadPropertyInfoV2( val guiAttributeStatement = if (targetSchema == ApiV2Complex) { entityInfoContent.getPredicateStringLiteralObjectsWithoutLang( - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri + SalsahGui.External.GuiAttribute.toSmartIri ) match { case objs if objs.nonEmpty => Some( - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute -> JsonLDArray( + SalsahGui.External.GuiAttribute -> JsonLDArray( objs.toArray.sorted.map(JsonLDString).toIndexedSeq ) ) @@ -3281,7 +3235,7 @@ object ClassInfoContentV2 { OntologyConstants.Owl.MinCardinality, OntologyConstants.Owl.MaxCardinality, OntologyConstants.Owl.OnProperty, - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiOrder + SalsahGui.External.GuiOrder ) /** @@ -3395,7 +3349,7 @@ object ClassInfoContentV2 { val onProperty = restriction.requireIriInObject(OntologyConstants.Owl.OnProperty, stringFormatter.toSmartIriWithErr) - val guiOrder = restriction.maybeInt(OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiOrder) + val guiOrder = restriction.maybeInt(SalsahGui.External.GuiOrder) val owlCardinalityInfo = OwlCardinalityInfo( owlCardinalityIri = owlCardinalityIri, @@ -3590,8 +3544,8 @@ object PropertyInfoContentV2 { OntologyConstants.Rdfs.SubPropertyOf, OntologyConstants.Rdfs.Label, OntologyConstants.Rdfs.Comment, - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp, - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute + SalsahGui.External.GuiElementProp, + SalsahGui.External.GuiAttribute ) /** diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v1/OntologyResponderV1.scala b/webapi/src/main/scala/org/knora/webapi/responders/v1/OntologyResponderV1.scala index bccb498258..366be1f3ec 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v1/OntologyResponderV1.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v1/OntologyResponderV1.scala @@ -27,6 +27,7 @@ import org.knora.webapi.responders.Responder import org.knora.webapi.responders.Responder.handleUnexpectedMessage import scala.concurrent.Future +import dsp.constants.SalsahGui /** * Handles requests for information about ontology entities. @@ -256,7 +257,7 @@ class OntologyResponderV1(responderData: ResponderData) extends Responder(respon attributes = valueUtilV1.makeAttributeString( entityInfo .getPredicateStringObjectsWithoutLang( - OntologyConstants.SalsahGui.GuiAttribute + SalsahGui.GuiAttribute ) + valueUtilV1 .makeAttributeRestype( entityInfo @@ -272,7 +273,7 @@ class OntologyResponderV1(responderData: ResponderData) extends Responder(respon ), gui_name = entityInfo .getPredicateObject( - OntologyConstants.SalsahGui.GuiElementProp + SalsahGui.GuiElementProp ) .map(iri => SalsahGuiConversions.iri2SalsahGuiElement(iri) @@ -312,12 +313,12 @@ class OntologyResponderV1(responderData: ResponderData) extends Responder(respon ), attributes = valueUtilV1.makeAttributeString( entityInfo.getPredicateStringObjectsWithoutLang( - OntologyConstants.SalsahGui.GuiAttribute + SalsahGui.GuiAttribute ) ), gui_name = entityInfo .getPredicateObject( - OntologyConstants.SalsahGui.GuiElementProp + SalsahGui.GuiElementProp ) .map(iri => SalsahGuiConversions.iri2SalsahGuiElement(iri) @@ -629,7 +630,7 @@ class OntologyResponderV1(responderData: ResponderData) extends Responder(respon valuetype_id = OntologyConstants.KnoraBase.LinkValue, attributes = valueUtilV1.makeAttributeString( entityInfo - .getPredicateStringObjectsWithoutLang(OntologyConstants.SalsahGui.GuiAttribute) + valueUtilV1 + .getPredicateStringObjectsWithoutLang(SalsahGui.GuiAttribute) + valueUtilV1 .makeAttributeRestype( entityInfo .getPredicateObject(OntologyConstants.KnoraBase.ObjectClassConstraint) @@ -641,7 +642,7 @@ class OntologyResponderV1(responderData: ResponderData) extends Responder(respon ) ), gui_name = entityInfo - .getPredicateObject(OntologyConstants.SalsahGui.GuiElementProp) + .getPredicateObject(SalsahGui.GuiElementProp) .map(iri => SalsahGuiConversions.iri2SalsahGuiElement(iri)) ) @@ -666,10 +667,10 @@ class OntologyResponderV1(responderData: ResponderData) extends Responder(respon ) ), attributes = valueUtilV1.makeAttributeString( - entityInfo.getPredicateStringObjectsWithoutLang(OntologyConstants.SalsahGui.GuiAttribute) + entityInfo.getPredicateStringObjectsWithoutLang(SalsahGui.GuiAttribute) ), gui_name = entityInfo - .getPredicateObject(OntologyConstants.SalsahGui.GuiElementProp) + .getPredicateObject(SalsahGui.GuiElementProp) .map(iri => SalsahGuiConversions.iri2SalsahGuiElement(iri)) ) diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v1/ResourcesResponderV1.scala b/webapi/src/main/scala/org/knora/webapi/responders/v1/ResourcesResponderV1.scala index 189d50ec01..1e34334c84 100755 --- a/webapi/src/main/scala/org/knora/webapi/responders/v1/ResourcesResponderV1.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v1/ResourcesResponderV1.scala @@ -53,6 +53,7 @@ import scala.concurrent.Future import scala.util.Failure import scala.util.Success import scala.util.Try +import dsp.constants.SalsahGui /** * Responds to requests for information about resources, and returns responses in Knora API v1 format. @@ -865,7 +866,7 @@ class ResourcesResponderV1(responderData: ResponderData) extends Responder(respo valuetype_id = Some(OntologyConstants.KnoraBase.LinkValue), guiorder = guiOrder, guielement = propertyEntityInfo - .getPredicateObject(OntologyConstants.SalsahGui.GuiElementProp) + .getPredicateObject(SalsahGui.GuiElementProp) .map(guiElementIri => SalsahGuiConversions.iri2SalsahGuiElement(guiElementIri) ), @@ -875,7 +876,7 @@ class ResourcesResponderV1(responderData: ResponderData) extends Responder(respo ), occurrence = Some(propsAndCardinalities(propertyIri).cardinality.toString), attributes = (propertyEntityInfo.getPredicateStringObjectsWithoutLang( - OntologyConstants.SalsahGui.GuiAttribute + SalsahGui.GuiAttribute ) + valueUtilV1.makeAttributeRestype( propertyEntityInfo .getPredicateObject(OntologyConstants.KnoraBase.ObjectClassConstraint) @@ -896,7 +897,7 @@ class ResourcesResponderV1(responderData: ResponderData) extends Responder(respo ), guiorder = guiOrder, guielement = propertyEntityInfo - .getPredicateObject(OntologyConstants.SalsahGui.GuiElementProp) + .getPredicateObject(SalsahGui.GuiElementProp) .map(guiElementIri => SalsahGuiConversions.iri2SalsahGuiElement(guiElementIri) ), @@ -907,7 +908,7 @@ class ResourcesResponderV1(responderData: ResponderData) extends Responder(respo occurrence = Some(propsAndCardinalities(propertyIri).cardinality.toString), attributes = propertyEntityInfo .getPredicateStringObjectsWithoutLang( - OntologyConstants.SalsahGui.GuiAttribute + SalsahGui.GuiAttribute ) .mkString(";"), value_rights = Nil @@ -922,7 +923,7 @@ class ResourcesResponderV1(responderData: ResponderData) extends Responder(respo pid = "__location__", valuetype_id = Some("-1"), guiorder = Some(Int.MaxValue), - guielement = Some(SalsahGuiConversions.iri2SalsahGuiElement(OntologyConstants.SalsahGui.Fileupload)), + guielement = Some(SalsahGuiConversions.iri2SalsahGuiElement(SalsahGui.Fileupload)), values = Vector(IntegerValueV1(0)), value_ids = Vector("0"), comments = Vector(None), @@ -3219,7 +3220,7 @@ class ResourcesResponderV1(responderData: ResponderData) extends Responder(respo }, guiorder = propertyCardinality.flatMap(_.guiOrder), guielement = propertyEntityInfo.flatMap( - _.getPredicateObject(OntologyConstants.SalsahGui.GuiElementProp).map(guiElementIri => + _.getPredicateObject(SalsahGui.GuiElementProp).map(guiElementIri => SalsahGuiConversions.iri2SalsahGuiElement(guiElementIri) ) ), @@ -3233,7 +3234,7 @@ class ResourcesResponderV1(responderData: ResponderData) extends Responder(respo attributes = propertyEntityInfo match { case Some(entityInfo) => if (entityInfo.isLinkProp) { - (entityInfo.getPredicateStringObjectsWithoutLang(OntologyConstants.SalsahGui.GuiAttribute) + valueUtilV1 + (entityInfo.getPredicateStringObjectsWithoutLang(SalsahGui.GuiAttribute) + valueUtilV1 .makeAttributeRestype( entityInfo .getPredicateObject(OntologyConstants.KnoraBase.ObjectClassConstraint) @@ -3244,7 +3245,7 @@ class ResourcesResponderV1(responderData: ResponderData) extends Responder(respo ) )).mkString(";") } else { - entityInfo.getPredicateStringObjectsWithoutLang(OntologyConstants.SalsahGui.GuiAttribute).mkString(";") + entityInfo.getPredicateStringObjectsWithoutLang(SalsahGui.GuiAttribute).mkString(";") } case None => "" }, diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala index e5c7277a14..58f61fc23f 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala @@ -7,8 +7,9 @@ package org.knora.webapi.responders.v2 import akka.http.scaladsl.util.FastFuture import akka.pattern._ -import org.knora.webapi._ +import dsp.constants.SalsahGui import dsp.errors._ +import org.knora.webapi._ import org.knora.webapi.messages.IriConversions._ import org.knora.webapi.messages.OntologyConstants import org.knora.webapi.messages.SmartIri @@ -16,12 +17,13 @@ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetRequ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetResponseADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM import org.knora.webapi.messages.admin.responder.usersmessages.UserADM +import org.knora.webapi.messages.store.triplestoremessages.OntologyLiteralV2 import org.knora.webapi.messages.store.triplestoremessages.SmartIriLiteralV2 import org.knora.webapi.messages.store.triplestoremessages.SparqlUpdateRequest import org.knora.webapi.messages.store.triplestoremessages.SparqlUpdateResponse import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 -import scala.concurrent.duration._ import org.knora.webapi.messages.util.ErrorHandlingMap +import org.knora.webapi.messages.util.KnoraSystemInstances import org.knora.webapi.messages.util.ResponderData import org.knora.webapi.messages.v2.responder.CanDoResponseV2 import org.knora.webapi.messages.v2.responder.SuccessResponseV2 @@ -37,9 +39,9 @@ import org.knora.webapi.responders.v2.ontology.OntologyHelpers import org.knora.webapi.util._ import java.time.Instant -import scala.concurrent.Future import scala.concurrent.Await -import org.knora.webapi.messages.util.KnoraSystemInstances +import scala.concurrent.Future +import scala.concurrent.duration._ /** * Responds to requests dealing with ontologies. @@ -2632,6 +2634,12 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon currentTime: Instant = Instant.now + newGuiElementIri = + changePropertyGuiElementRequest.newGuiObject.guiElement.map(guiElement => guiElement.value.toSmartIri) + + newGuiAttributeIris = + changePropertyGuiElementRequest.newGuiObject.guiAttributes.map(guiAttribute => guiAttribute.value) + updateSparql = org.knora.webapi.messages.twirl.queries.sparql.v2.txt .changePropertyGuiElement( ontologyNamedGraphIri = internalOntologyIri, @@ -2639,8 +2647,8 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon propertyIri = internalPropertyIri, maybeLinkValuePropertyIri = maybeCurrentLinkValueReadPropertyInfo.map(_.entityInfoContent.propertyIri), - maybeNewGuiElement = changePropertyGuiElementRequest.newGuiElement, - newGuiAttributes = changePropertyGuiElementRequest.newGuiAttributes, + maybeNewGuiElement = newGuiElementIri, + newGuiAttributes = newGuiAttributeIris.toSet, lastModificationDate = changePropertyGuiElementRequest.lastModificationDate, currentTime = currentTime ) @@ -2667,19 +2675,19 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon ) maybeNewGuiElementPredicate: Option[(SmartIri, PredicateInfoV2)] = - changePropertyGuiElementRequest.newGuiElement.map { guiElement: SmartIri => - OntologyConstants.SalsahGui.GuiElementProp.toSmartIri -> PredicateInfoV2( - predicateIri = OntologyConstants.SalsahGui.GuiElementProp.toSmartIri, + newGuiElementIri.map { guiElement: SmartIri => + SalsahGui.GuiElementProp.toSmartIri -> PredicateInfoV2( + predicateIri = SalsahGui.GuiElementProp.toSmartIri, objects = Seq(SmartIriLiteralV2(guiElement)) ) } maybeUnescapedNewGuiAttributePredicate: Option[(SmartIri, PredicateInfoV2)] = - if (changePropertyGuiElementRequest.newGuiAttributes.nonEmpty) { + if (newGuiAttributeIris.nonEmpty) { Some( - OntologyConstants.SalsahGui.GuiAttribute.toSmartIri -> PredicateInfoV2( - predicateIri = OntologyConstants.SalsahGui.GuiAttribute.toSmartIri, - objects = changePropertyGuiElementRequest.newGuiAttributes.map(StringLiteralV2(_)).toSeq + SalsahGui.GuiAttribute.toSmartIri -> PredicateInfoV2( + predicateIri = SalsahGui.GuiAttribute.toSmartIri, + objects = newGuiAttributeIris.map(StringLiteralV2(_)).toSeq ) ) } else { @@ -2689,8 +2697,8 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon unescapedNewPropertyDef: PropertyInfoContentV2 = currentReadPropertyInfo.entityInfoContent.copy( predicates = currentReadPropertyInfo.entityInfoContent.predicates - - OntologyConstants.SalsahGui.GuiElementProp.toSmartIri - - OntologyConstants.SalsahGui.GuiAttribute.toSmartIri ++ + SalsahGui.GuiElementProp.toSmartIri - + SalsahGui.GuiAttribute.toSmartIri ++ maybeNewGuiElementPredicate ++ maybeUnescapedNewGuiAttributePredicate ) @@ -2717,8 +2725,8 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon maybeLoadedLinkValuePropertyDef.map { loadedLinkValuePropertyDef => val unescapedNewLinkPropertyDef = maybeCurrentLinkValueReadPropertyInfo.get.entityInfoContent.copy( predicates = maybeCurrentLinkValueReadPropertyInfo.get.entityInfoContent.predicates - - OntologyConstants.SalsahGui.GuiElementProp.toSmartIri - - OntologyConstants.SalsahGui.GuiAttribute.toSmartIri ++ + SalsahGui.GuiElementProp.toSmartIri - + SalsahGui.GuiAttribute.toSmartIri ++ maybeNewGuiElementPredicate ++ maybeUnescapedNewGuiAttributePredicate ) @@ -2776,7 +2784,7 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon for { requestingUser <- FastFuture.successful(changePropertyGuiElementRequest.requestingUser) - externalPropertyIri = changePropertyGuiElementRequest.propertyIri + externalPropertyIri = changePropertyGuiElementRequest.propertyIri.value.toSmartIri externalOntologyIri = externalPropertyIri.getOntologyFromEntity _ <- OntologyHelpers.checkOntologyAndEntityIrisForUpdate( diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/ontology/OntologyHelpers.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/ontology/OntologyHelpers.scala index f3aee1dbd6..c52f40ff07 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/ontology/OntologyHelpers.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/ontology/OntologyHelpers.scala @@ -38,6 +38,7 @@ import java.time.Instant import scala.collection.immutable import scala.concurrent.ExecutionContext import scala.concurrent.Future +import dsp.constants.SalsahGui object OntologyHelpers { @@ -787,12 +788,12 @@ object OntologyHelpers { allIndividuals: Map[SmartIri, IndividualInfoContentV2] )(implicit stringFormatter: StringFormatter): Map[SmartIri, Set[SalsahGuiAttributeDefinition]] = { val guiElementIndividuals: Map[SmartIri, IndividualInfoContentV2] = allIndividuals.filter { case (_, individual) => - individual.getRdfType.toString == OntologyConstants.SalsahGui.GuiElementClass + individual.getRdfType.toString == SalsahGui.GuiElementClass } guiElementIndividuals.map { case (guiElementIri, guiElementIndividual) => val attributeDefs: Set[SalsahGuiAttributeDefinition] = - guiElementIndividual.predicates.get(OntologyConstants.SalsahGui.GuiAttributeDefinition.toSmartIri) match { + guiElementIndividual.predicates.get(SalsahGui.GuiAttributeDefinition.toSmartIri) match { case Some(predicateInfo) => predicateInfo.objects.map { case StringLiteralV2(attributeDefStr, None) => @@ -833,11 +834,11 @@ object OntologyHelpers { // Find out which salsah-gui:Guielement the property uses, if any. val maybeGuiElementPred: Option[PredicateInfoV2] = - predicates.get(OntologyConstants.SalsahGui.GuiElementProp.toSmartIri) + predicates.get(SalsahGui.GuiElementProp.toSmartIri) val maybeGuiElementIri: Option[SmartIri] = maybeGuiElementPred.map( _.requireIriObject( throw InconsistentRepositoryDataException( - s"Property $propertyIri has an invalid object for ${OntologyConstants.SalsahGui.GuiElementProp}" + s"Property $propertyIri has an invalid object for ${SalsahGui.GuiElementProp}" ) ) ) @@ -855,7 +856,7 @@ object OntologyHelpers { // If the property has the predicate salsah-gui:guiAttribute, syntactically validate the objects of that predicate. val guiAttributes: Set[SalsahGuiAttribute] = - predicates.get(OntologyConstants.SalsahGui.GuiAttribute.toSmartIri) match { + predicates.get(SalsahGui.GuiAttribute.toSmartIri) match { case Some(guiAttributePred) => val guiElementIri = maybeGuiElementIri.getOrElse( errorFun(s"Property $propertyIri has salsah-gui:guiAttribute, but no salsah-gui:guiElement") @@ -1245,7 +1246,7 @@ object OntologyHelpers { ) // salsah-gui:guiOrder isn't allowed here. - if (otherPreds.contains(OntologyConstants.SalsahGui.GuiOrder.toSmartIri)) { + if (otherPreds.contains(SalsahGui.GuiOrder.toSmartIri)) { throw InconsistentRepositoryDataException(s"Property $propertyIri contains salsah-gui:guiOrder") } @@ -1705,24 +1706,24 @@ object OntologyHelpers { ) } - val guiOrder: Option[Int] = blankNode.get(OntologyConstants.SalsahGui.GuiOrder.toSmartIri) match { + val guiOrder: Option[Int] = blankNode.get(SalsahGui.GuiOrder.toSmartIri) match { case Some(Seq(IntLiteralV2(intVal))) => Some(intVal) case None => None case other => throw InconsistentRepositoryDataException( - s"Expected one integer object for predicate ${OntologyConstants.SalsahGui.GuiOrder} in blank node '${blankNodeID.value}', got $other" + s"Expected one integer object for predicate ${SalsahGui.GuiOrder} in blank node '${blankNodeID.value}', got $other" ) } // salsah-gui:guiElement and salsah-gui:guiAttribute aren't allowed here. - if (blankNode.contains(OntologyConstants.SalsahGui.GuiElementProp.toSmartIri)) { + if (blankNode.contains(SalsahGui.GuiElementProp.toSmartIri)) { throw InconsistentRepositoryDataException( s"Class $classIri contains salsah-gui:guiElement in an owl:Restriction" ) } - if (blankNode.contains(OntologyConstants.SalsahGui.GuiAttribute.toSmartIri)) { + if (blankNode.contains(SalsahGui.GuiAttribute.toSmartIri)) { throw InconsistentRepositoryDataException( s"Class $classIri contains salsah-gui:guiAttribute in an owl:Restriction" ) diff --git a/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala b/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala index 81335802ef..9601c46136 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala @@ -5,22 +5,39 @@ package org.knora.webapi.routing.v2 +import akka.actor.Status import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.PathMatcher import akka.http.scaladsl.server.Route - -import java.util.UUID -import scala.concurrent.Future - +import dsp.constants.SalsahGui import dsp.errors.BadRequestException - +import dsp.valueobjects.Iri._ +import dsp.valueobjects.Schema._ +import dsp.valueobjects.V2 +import org.knora.webapi.ApiV2Complex import org.knora.webapi._ import org.knora.webapi.messages.IriConversions._ -import org.knora.webapi.messages.util.rdf.{JsonLDDocument, JsonLDUtil} +import org.knora.webapi.messages.OntologyConstants +import org.knora.webapi.messages.SmartIri +import org.knora.webapi.messages.admin.responder.usersmessages.UserADM +import org.knora.webapi.messages.store.triplestoremessages.SmartIriLiteralV2 +import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 +import org.knora.webapi.messages.util.rdf.JsonLDDocument +import org.knora.webapi.messages.util.rdf.JsonLDUtil import org.knora.webapi.messages.v2.responder.ontologymessages._ -import org.knora.webapi.messages.{OntologyConstants, SmartIri} -import org.knora.webapi.routing.{Authenticator, KnoraRoute, KnoraRouteData, RouteUtilV2} -import org.knora.webapi.ApiV2Complex +import org.knora.webapi.routing.Authenticator +import org.knora.webapi.routing.KnoraRoute +import org.knora.webapi.routing.KnoraRouteData +import org.knora.webapi.routing.RouteUtilV2 +import zio.ZIO +import zio.prelude.Validation + +import java.util.UUID +import javax.validation.Valid +import scala.concurrent.Future +import scala.util.Failure +import scala.util.Success +import scala.util.Try object OntologiesRouteV2 { val OntologiesBasePath: PathMatcher[Unit] = PathMatcher("v2" / "ontologies") @@ -786,9 +803,9 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) entity(as[String]) { jsonRequest => requestContext => { val requestMessageFuture: Future[CreatePropertyRequestV2] = for { - requestingUser <- getUserADM( - requestContext = requestContext - ) + requestingUser: UserADM <- getUserADM( + requestContext = requestContext + ) requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest) @@ -800,6 +817,53 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) settings = settings, log = log ) + + // get gui related values from request and validate them by making value objects from it + + // get ontology info from request + inputOntology: InputOntologyV2 = InputOntologyV2.fromJsonLD(requestDoc) + propertyUpdateInfo: PropertyUpdateInfo = OntologyUpdateHelper.getPropertyDef(inputOntology) + propertyInfoContent: PropertyInfoContentV2 = propertyUpdateInfo.propertyInfoContent + + // get the (optional) gui element + maybeGuiElement: Option[SmartIri] = + propertyInfoContent.predicates + .get(SalsahGui.External.GuiElementProp.toSmartIri) + .map { predicateInfoV2: PredicateInfoV2 => + predicateInfoV2.objects.head match { + case guiElement: SmartIriLiteralV2 => guiElement.value.toOntologySchema(InternalSchema) + case other => throw BadRequestException(s"Unexpected object for salsah-gui:guiElement: $other") + } + } + + // validate the gui element by creating value object + validatedGuiElement = maybeGuiElement match { + case Some(guiElement) => GuiElement.make(guiElement.toString()).map(Some(_)) + case None => Validation.succeed(None) + } + + // get the gui attribute(s) + maybeGuiAttributes: List[String] = + propertyInfoContent.predicates + .get(SalsahGui.External.GuiAttribute.toSmartIri) + .map { predicateInfoV2: PredicateInfoV2 => + predicateInfoV2.objects.map { + case guiAttribute: StringLiteralV2 => guiAttribute.value + case other => throw BadRequestException(s"Unexpected object for salsah-gui:guiAttribute: $other") + }.toList + } + .getOrElse(List()) + + // validate the gui attributes by creating value objects + guiAttributes = maybeGuiAttributes.map(guiAttribute => GuiAttribute.make(guiAttribute)).toList + + validatedGuiAttributes = Validation.validateAll(guiAttributes) + + // validate the combination of gui element and gui attribute by creating a GuiObject value object + guiObject: GuiObject = Validation + .validate(validatedGuiAttributes, validatedGuiElement) + .flatMap(values => GuiObject.make(values._1, values._2)) + .fold(e => throw e.head, v => v) } yield requestMessage RouteUtilV2.runRdfRouteWithFuture( @@ -904,21 +968,77 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) entity(as[String]) { jsonRequest => requestContext => { val requestMessageFuture: Future[ChangePropertyGuiElementRequest] = for { - requestingUser <- getUserADM( - requestContext = requestContext - ) + + requestingUser: UserADM <- getUserADM( + requestContext = requestContext + ) requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest) - requestMessage: ChangePropertyGuiElementRequest <- ChangePropertyGuiElementRequest - .fromJsonLD( - jsonLDDocument = requestDoc, - apiRequestID = UUID.randomUUID, - requestingUser = requestingUser, - appActor = appActor, - settings = settings, - log = log - ) + // get ontology info from request + inputOntology: InputOntologyV2 = InputOntologyV2.fromJsonLD(requestDoc) + + // get property info from request - in OntologyUpdateHelper.getPropertyDef a lot of validation of the property iri is done + propertyUpdateInfo: PropertyUpdateInfo = OntologyUpdateHelper.getPropertyDef(inputOntology) + + propertyInfoContent: PropertyInfoContentV2 = propertyUpdateInfo.propertyInfoContent + lastModificationDate = propertyUpdateInfo.lastModificationDate + + // get all values from request and validate them by making value objects from it + + // get and validate property IRI + propertyIri = PropertyIri.make(propertyInfoContent.propertyIri.toString) + + // get the (optional) new gui element + newGuiElement: Option[SmartIri] = + propertyInfoContent.predicates + .get(SalsahGui.External.GuiElementProp.toSmartIri) + .map { predicateInfoV2: PredicateInfoV2 => + predicateInfoV2.objects.head match { + case guiElement: SmartIriLiteralV2 => guiElement.value.toOntologySchema(InternalSchema) + case other => throw BadRequestException(s"Unexpected object for salsah-gui:guiElement: $other") + } + } + + // validate the new gui element by creating value object + validatedNewGuiElement = newGuiElement match { + case Some(guiElement) => GuiElement.make(guiElement.toString()).map(Some(_)) + case None => Validation.succeed(None) + } + + // get the new gui attribute(s) + newGuiAttributes: List[String] = + propertyInfoContent.predicates + .get(SalsahGui.External.GuiAttribute.toSmartIri) + .map { predicateInfoV2: PredicateInfoV2 => + predicateInfoV2.objects.map { + case guiAttribute: StringLiteralV2 => guiAttribute.value + case other => throw BadRequestException(s"Unexpected object for salsah-gui:guiAttribute: $other") + }.toList + } + .getOrElse(List()) + + // validate the new gui attributes by creating value objects + guiAttributes = newGuiAttributes.map(guiAttribute => GuiAttribute.make(guiAttribute)) + + validatedGuiAttributes = Validation.validateAll(guiAttributes) + + // validate the combination of gui element and gui attribute by creating a GuiObject value object + guiObject = + Validation + .validate(validatedGuiAttributes, validatedNewGuiElement) + .flatMap(values => GuiObject.make(values._1, values._2)) + .fold(e => throw e.head, v => v) + + // create the request message with the validated gui object + requestMessage = ChangePropertyGuiElementRequest( + propertyIri = propertyIri.fold(e => throw e.head, v => v), + newGuiObject = guiObject, + lastModificationDate = lastModificationDate, + apiRequestID = UUID.randomUUID, + requestingUser = requestingUser + ) + } yield requestMessage RouteUtilV2.runRdfRouteWithFuture( diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala index e73fc0d2ff..edf4878060 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala @@ -33,6 +33,7 @@ import java.nio.file.Path import java.nio.file.Paths import java.time.Instant import scala.concurrent.ExecutionContextExecutor +import dsp.constants.SalsahGui object OntologyV2R2RSpec { private val anythingUserProfile = SharedTestDataADM.anythingAdminUser @@ -1181,21 +1182,21 @@ class OntologyV2R2RSpec extends R2RSpec { InputOntologyV2.fromJsonLD(responseJsonDoc, parsingMode = TestResponseParsingModeV2).unescape responseAsInput.properties.head._2 - .predicates(OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri) + .predicates(SalsahGui.External.GuiElementProp.toSmartIri) .objects .toSet should ===( paramsAsInput.properties.head._2 - .predicates(OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri) + .predicates(SalsahGui.External.GuiElementProp.toSmartIri) .objects .toSet ) responseAsInput.properties.head._2 - .predicates(OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri) + .predicates(SalsahGui.External.GuiAttribute.toSmartIri) .objects .toSet should ===( paramsAsInput.properties.head._2 - .predicates(OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri) + .predicates(SalsahGui.External.GuiAttribute.toSmartIri) .objects .toSet ) @@ -1207,6 +1208,50 @@ class OntologyV2R2RSpec extends R2RSpec { } } + "not change the salsah-gui:guiElement and salsah-gui:guiAttribute of a property if their combination is invalid" in { + val params = + s"""{ + | "@id" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}", + | "@type" : "owl:Ontology", + | "knora-api:lastModificationDate" : { + | "@type" : "xsd:dateTimeStamp", + | "@value" : "$anythingLastModDate" + | }, + | "@graph" : [ { + | "@id" : "anything:hasName", + | "@type" : "owl:ObjectProperty", + | "salsah-gui:guiElement" : { + | "@id" : "salsah-gui:List" + | }, + | "salsah-gui:guiAttribute" : [ "cols=80", "rows=24" ] + | } ], + | "@context" : { + | "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + | "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", + | "salsah-gui" : "http://api.knora.org/ontology/salsah-gui/v2#", + | "owl" : "http://www.w3.org/2002/07/owl#", + | "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + | "xsd" : "http://www.w3.org/2001/XMLSchema#", + | "anything" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}#" + | } + |}""".stripMargin + + CollectClientTestData("not-change-property-guielement-request", params) + + // Convert the submitted JSON-LD to an InputOntologyV2, without SPARQL-escaping, so we can compare it to the response. + val paramsAsInput: InputOntologyV2 = InputOntologyV2.fromJsonLD(JsonLDUtil.parseJsonLD(params)).unescape + + Put( + "/v2/ontologies/properties/guielement", + HttpEntity(RdfMediaTypes.`application/ld+json`, params) + ) ~> addCredentials(BasicHttpCredentials(anythingUsername, password)) ~> ontologiesPath ~> check { + val responseStr = responseAs[String] + assert(status == StatusCodes.BadRequest, responseStr) + + CollectClientTestData("not-change-property-guielement-response", responseStr) + } + } + "remove the salsah-gui:guiElement and salsah-gui:guiAttribute from a property" in { val params = s"""{ @@ -1247,12 +1292,12 @@ class OntologyV2R2RSpec extends R2RSpec { assert( !responseAsInput.properties.head._2.predicates - .contains(OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri) + .contains(SalsahGui.External.GuiElementProp.toSmartIri) ) assert( !responseAsInput.properties.head._2.predicates - .contains(OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri) + .contains(SalsahGui.External.GuiAttribute.toSmartIri) ) // Check that the ontology's last modification date was updated. diff --git a/webapi/src/test/scala/org/knora/webapi/messages/StringFormatterSpec.scala b/webapi/src/test/scala/org/knora/webapi/messages/StringFormatterSpec.scala index 754bd4a247..7d0a94944a 100644 --- a/webapi/src/test/scala/org/knora/webapi/messages/StringFormatterSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/messages/StringFormatterSpec.scala @@ -16,6 +16,7 @@ import org.knora.webapi.sharedtestdata.SharedTestDataV1 import java.time.Instant import java.util.UUID +import dsp.constants.SalsahGui /** * Tests [[StringFormatter]]. @@ -1073,7 +1074,7 @@ class StringFormatterSpec extends CoreSpec() { SalsahGuiAttributeDefinition( attributeName = "hlist", isRequired = true, - allowedType = OntologyConstants.SalsahGui.SalsahGuiAttributeType.Iri, + allowedType = SalsahGui.SalsahGuiAttributeType.Iri, unparsedString = hlistDef ) ) @@ -1084,7 +1085,7 @@ class StringFormatterSpec extends CoreSpec() { SalsahGuiAttributeDefinition( attributeName = "numprops", isRequired = false, - allowedType = OntologyConstants.SalsahGui.SalsahGuiAttributeType.Integer, + allowedType = SalsahGui.SalsahGuiAttributeType.Integer, unparsedString = numpropsDef ) ) @@ -1095,7 +1096,7 @@ class StringFormatterSpec extends CoreSpec() { SalsahGuiAttributeDefinition( attributeName = "size", isRequired = false, - allowedType = OntologyConstants.SalsahGui.SalsahGuiAttributeType.Integer, + allowedType = SalsahGui.SalsahGuiAttributeType.Integer, unparsedString = sizeDef ) ) @@ -1106,7 +1107,7 @@ class StringFormatterSpec extends CoreSpec() { SalsahGuiAttributeDefinition( attributeName = "maxlength", isRequired = false, - allowedType = OntologyConstants.SalsahGui.SalsahGuiAttributeType.Integer, + allowedType = SalsahGui.SalsahGuiAttributeType.Integer, unparsedString = maxlengthDef ) ) @@ -1117,7 +1118,7 @@ class StringFormatterSpec extends CoreSpec() { SalsahGuiAttributeDefinition( attributeName = "max", isRequired = true, - allowedType = OntologyConstants.SalsahGui.SalsahGuiAttributeType.Decimal, + allowedType = SalsahGui.SalsahGuiAttributeType.Decimal, unparsedString = maxDef ) ) @@ -1128,7 +1129,7 @@ class StringFormatterSpec extends CoreSpec() { SalsahGuiAttributeDefinition( attributeName = "min", isRequired = true, - allowedType = OntologyConstants.SalsahGui.SalsahGuiAttributeType.Decimal, + allowedType = SalsahGui.SalsahGuiAttributeType.Decimal, unparsedString = minDef ) ) @@ -1139,7 +1140,7 @@ class StringFormatterSpec extends CoreSpec() { SalsahGuiAttributeDefinition( attributeName = "width", isRequired = false, - allowedType = OntologyConstants.SalsahGui.SalsahGuiAttributeType.Percent, + allowedType = SalsahGui.SalsahGuiAttributeType.Percent, unparsedString = widthDef ) ) @@ -1150,7 +1151,7 @@ class StringFormatterSpec extends CoreSpec() { SalsahGuiAttributeDefinition( attributeName = "rows", isRequired = false, - allowedType = OntologyConstants.SalsahGui.SalsahGuiAttributeType.Integer, + allowedType = SalsahGui.SalsahGuiAttributeType.Integer, unparsedString = rowsDef ) ) @@ -1161,7 +1162,7 @@ class StringFormatterSpec extends CoreSpec() { SalsahGuiAttributeDefinition( attributeName = "wrap", isRequired = false, - allowedType = OntologyConstants.SalsahGui.SalsahGuiAttributeType.Str, + allowedType = SalsahGui.SalsahGuiAttributeType.Str, enumeratedValues = Set("soft", "hard"), unparsedString = wrapDef ) diff --git a/webapi/src/test/scala/org/knora/webapi/messages/v2/responder/ontologymessages/ChangePropertyGuiElementRequestV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/messages/v2/responder/ontologymessages/ChangePropertyGuiElementRequestV2Spec.scala deleted file mode 100644 index 1886c81223..0000000000 --- a/webapi/src/test/scala/org/knora/webapi/messages/v2/responder/ontologymessages/ChangePropertyGuiElementRequestV2Spec.scala +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright © 2021 - 2022 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.messages.v2.responder.ontologymessages - -import akka.util.Timeout -import org.knora.webapi.CoreSpec - -import org.knora.webapi.messages.util.rdf.JsonLDDocument -import org.knora.webapi.messages.util.rdf.JsonLDUtil -import org.knora.webapi.sharedtestdata.SharedOntologyTestDataADM -import org.knora.webapi.sharedtestdata.SharedTestDataADM - -import java.util.UUID -import scala.concurrent.Future - -/** - * Tests [[ChangePropertyGuiElementRequestV2Spec]]. - */ -class ChangePropertyGuiElementRequestV2Spec extends CoreSpec { - - "ChangePropertyGuiElementRequest" should { - "should parse the request message correctly" in { - val jsonRequest = - s"""{ - | "@id" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}", - | "@type" : "owl:Ontology", - | "knora-api:lastModificationDate" : { - | "@type" : "xsd:dateTimeStamp", - | "@value" : "2021-08-17T12:04:29.756311Z" - | }, - | "@graph" : [ { - | "@id" : "anything:hasName", - | "@type" : "owl:ObjectProperty", - | "salsah-gui:guiElement" : { - | "@id" : "salsah-gui:Textarea" - | }, - | "salsah-gui:guiAttribute" : [ "cols=80", "rows=24" ] - | } ], - | "@context" : { - | "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", - | "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", - | "salsah-gui" : "http://api.knora.org/ontology/salsah-gui/v2#", - | "owl" : "http://www.w3.org/2002/07/owl#", - | "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", - | "xsd" : "http://www.w3.org/2001/XMLSchema#", - | "anything" : "${SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI_LocalHost}#" - | } - |}""".stripMargin - - implicit val timeout: Timeout = settings.defaultTimeout - - val requestingUser = SharedTestDataADM.anythingUser1 - - val requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest) - - val requestMessageFuture: Future[ChangePropertyGuiElementRequest] = for { - - requestMessage: ChangePropertyGuiElementRequest <- - ChangePropertyGuiElementRequest - .fromJsonLD( - jsonLDDocument = requestDoc, - apiRequestID = UUID.randomUUID, - requestingUser = requestingUser, - appActor = appActor, - settings = settings, - log = log - ) - } yield requestMessage - - requestMessageFuture map { changePropertyGuiElementRequestMessage => - changePropertyGuiElementRequestMessage.propertyIri.toString should equal( - "http://0.0.0.0:3333/ontology/0001/anything/v2#hasName" - ) - changePropertyGuiElementRequestMessage.newGuiElement.get.toString should equal( - "http://www.knora.org/ontology/salsah-gui#Textarea" - ) - changePropertyGuiElementRequestMessage.newGuiAttributes should equal(Set("cols=80", "rows=24")) - changePropertyGuiElementRequestMessage.lastModificationDate.toString should equal("2021-08-17T12:04:29.756311Z") - changePropertyGuiElementRequestMessage.requestingUser.username should equal("anything.user01") - } - - } - } - -} diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala index 58e2205cf9..f4c4a575f5 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala @@ -31,6 +31,9 @@ import java.time.Instant import java.util.UUID import scala.concurrent.duration._ import scala.language.postfixOps +import dsp.valueobjects.Iri +import dsp.valueobjects.Schema +import dsp.constants.SalsahGui /** * Tests [[OntologyResponderV2]]. @@ -2048,12 +2051,12 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { StringLiteralV2("An invalid property definition", Some("en")) ) ), - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri -> PredicateInfoV2( - predicateIri = OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri, + SalsahGui.External.GuiElementProp.toSmartIri -> PredicateInfoV2( + predicateIri = SalsahGui.External.GuiElementProp.toSmartIri, objects = Seq(SmartIriLiteralV2("http://api.knora.org/ontology/salsah-gui/v2#Textarea".toSmartIri)) ), - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri -> PredicateInfoV2( - predicateIri = OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri, + SalsahGui.External.GuiAttribute.toSmartIri -> PredicateInfoV2( + predicateIri = SalsahGui.External.GuiAttribute.toSmartIri, objects = Seq(StringLiteralV2("rows=10"), StringLiteralV2("cols=80.5")) ) ), @@ -2105,12 +2108,12 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { StringLiteralV2("An invalid property definition", Some("en")) ) ), - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri -> PredicateInfoV2( - predicateIri = OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri, + SalsahGui.External.GuiElementProp.toSmartIri -> PredicateInfoV2( + predicateIri = SalsahGui.External.GuiElementProp.toSmartIri, objects = Seq(SmartIriLiteralV2("http://api.knora.org/ontology/salsah-gui/v2#Textarea".toSmartIri)) ), - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri -> PredicateInfoV2( - predicateIri = OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri, + SalsahGui.External.GuiAttribute.toSmartIri -> PredicateInfoV2( + predicateIri = SalsahGui.External.GuiAttribute.toSmartIri, objects = Seq(StringLiteralV2("rows=10"), StringLiteralV2("cols=80"), StringLiteralV2("wrap=wrong")) ) ), @@ -2784,8 +2787,8 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { StringLiteralV2("Repräsentiert eine Teil-Ganzes-Beziehung", Some("de")) ) ), - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri -> PredicateInfoV2( - predicateIri = OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri, + SalsahGui.External.GuiElementProp.toSmartIri -> PredicateInfoV2( + predicateIri = SalsahGui.External.GuiElementProp.toSmartIri, objects = Seq(SmartIriLiteralV2("http://api.knora.org/ontology/salsah-gui/v2#Searchbox".toSmartIri)) ) ), @@ -3931,8 +3934,8 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { StringLiteralV2("Anzeigt, ob ein Nichts Nichtsein hat", Some("de")) ) ), - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri -> PredicateInfoV2( - predicateIri = OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri, + SalsahGui.External.GuiElementProp.toSmartIri -> PredicateInfoV2( + predicateIri = SalsahGui.External.GuiElementProp.toSmartIri, objects = Seq(SmartIriLiteralV2("http://api.knora.org/ontology/salsah-gui/v2#Checkbox".toSmartIri)) ) ), @@ -3963,12 +3966,29 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { } "change the salsah-gui:guiElement and salsah-gui:guiAttribute of anything:hasNothingness" in { - val propertyIri = AnythingOntologyIri.makeEntityIri("hasNothingness") + val propertyIri = + Iri.PropertyIri + .make(AnythingOntologyIri.makeEntityIri("hasNothingness").toString()) + .fold(e => throw e.head, v => v) + val guiElement = + Schema.GuiElement + .make("http://www.knora.org/ontology/salsah-gui#SimpleText") + .fold(e => throw e.head, v => Some(v)) + val guiAttributes = + List("size=80") + .map(attribute => + Schema.GuiAttribute + .make(attribute) + .fold(e => throw e.head, v => v) + ) + val guiObject = + Schema.GuiObject + .make(guiAttributes, guiElement) + .fold(e => throw e.head, v => v) appActor ! ChangePropertyGuiElementRequest( propertyIri = propertyIri, - newGuiElement = Some("http://www.knora.org/ontology/salsah-gui#SimpleText".toSmartIri), - newGuiAttributes = Set("size=80"), + newGuiObject = guiObject, lastModificationDate = anythingLastModDate, apiRequestID = UUID.randomUUID, requestingUser = anythingAdminUser @@ -3976,7 +3996,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { expectMsgPF(timeout) { case msg: ReadOntologyV2 => msg.properties.head._2.entityInfoContent.predicates - .get(stringFormatter.toSmartIri(OntologyConstants.SalsahGui.GuiElementProp)) match { + .get(stringFormatter.toSmartIri(SalsahGui.GuiElementProp)) match { case Some(predicateInfo) => val guiElementTypeFromMessage = predicateInfo.objects.head.asInstanceOf[SmartIriLiteralV2] val guiElementTypeInternal = guiElementTypeFromMessage.toOntologySchema(InternalSchema) @@ -3987,25 +4007,25 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { // Check that the salsah-gui:guiElement from the message is as expected val externalOntology = msg.toOntologySchema(ApiV2Complex) assert(externalOntology.properties.size == 1) - val property = externalOntology.properties(propertyIri) + val property = externalOntology.properties(propertyIri.value.toSmartIri) val guiElementPropComplex = property.entityInfoContent.predicates( - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri + SalsahGui.External.GuiElementProp.toSmartIri ) val guiElementPropComplexExpected = PredicateInfoV2( - predicateIri = OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri, + predicateIri = SalsahGui.External.GuiElementProp.toSmartIri, objects = Seq(SmartIriLiteralV2("http://api.knora.org/ontology/salsah-gui/v2#SimpleText".toSmartIri)) ) guiElementPropComplex should equal(guiElementPropComplexExpected) val guiAttributeComplex = property.entityInfoContent.predicates( - OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri + SalsahGui.External.GuiAttribute.toSmartIri ) val guiAttributeComplexExpected = PredicateInfoV2( - predicateIri = OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri, + predicateIri = SalsahGui.External.GuiAttribute.toSmartIri, objects = Seq(StringLiteralV2("size=80")) ) @@ -4021,12 +4041,20 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { } "delete the salsah-gui:guiElement and salsah-gui:guiAttribute of anything:hasNothingness" in { - val propertyIri = AnythingOntologyIri.makeEntityIri("hasNothingness") + val propertyIri = + Iri.PropertyIri + .make(AnythingOntologyIri.makeEntityIri("hasNothingness").toString()) + .fold(e => throw e.head, v => v) + val guiElement = None + val guiAttributes = List() + val guiObject = + Schema.GuiObject + .make(guiAttributes, guiElement) + .fold(e => throw e.head, v => v) appActor ! ChangePropertyGuiElementRequest( propertyIri = propertyIri, - newGuiElement = None, - newGuiAttributes = Set.empty, + newGuiObject = guiObject, lastModificationDate = anythingLastModDate, apiRequestID = UUID.randomUUID, requestingUser = anythingAdminUser @@ -4035,13 +4063,13 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { expectMsgPF(timeout) { case msg: ReadOntologyV2 => val externalOntology = msg.toOntologySchema(ApiV2Complex) assert(externalOntology.properties.size == 1) - val property = externalOntology.properties(propertyIri) + val property = externalOntology.properties(propertyIri.value.toSmartIri) property.entityInfoContent.predicates - .get(OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiElementProp.toSmartIri) should ===(None) + .get(SalsahGui.External.GuiElementProp.toSmartIri) should ===(None) property.entityInfoContent.predicates - .get(OntologyConstants.SalsahGuiApiV2WithValueObjects.GuiAttribute.toSmartIri) should ===(None) + .get(SalsahGui.External.GuiAttribute.toSmartIri) should ===(None) val metadata = externalOntology.ontologyMetadata val newAnythingLastModDate = metadata.lastModificationDate.getOrElse( diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/ontology/CacheSpec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/ontology/CacheSpec.scala index 9b47d9f163..52dad61310 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/ontology/CacheSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/ontology/CacheSpec.scala @@ -29,6 +29,7 @@ import scala.concurrent.Future import scala.concurrent.duration._ import scala.language.postfixOps import akka.util.Timeout +import dsp.constants.SalsahGui /** * This spec is used to test [[org.knora.webapi.responders.v2.ontology.Cache]]. @@ -157,12 +158,12 @@ class CacheSpec extends CoreSpec { predicateIri = stringFormatter.toSmartIri(OntologyConstants.KnoraBase.ObjectClassConstraint), Seq(SmartIriLiteralV2(stringFormatter.toSmartIri(OntologyConstants.KnoraBase.TextValue))) ), - stringFormatter.toSmartIri(OntologyConstants.SalsahGui.GuiElementClass) -> PredicateInfoV2( - predicateIri = stringFormatter.toSmartIri(OntologyConstants.SalsahGui.GuiElementClass), - Seq(SmartIriLiteralV2(stringFormatter.toSmartIri(OntologyConstants.SalsahGui.SimpleText))) + stringFormatter.toSmartIri(SalsahGui.GuiElementClass) -> PredicateInfoV2( + predicateIri = stringFormatter.toSmartIri(SalsahGui.GuiElementClass), + Seq(SmartIriLiteralV2(stringFormatter.toSmartIri(SalsahGui.SimpleText))) ), - stringFormatter.toSmartIri(OntologyConstants.SalsahGui.GuiAttribute) -> PredicateInfoV2( - predicateIri = stringFormatter.toSmartIri(OntologyConstants.SalsahGui.GuiAttribute), + stringFormatter.toSmartIri(SalsahGui.GuiAttribute) -> PredicateInfoV2( + predicateIri = stringFormatter.toSmartIri(SalsahGui.GuiAttribute), Seq( StringLiteralV2("size=80"), StringLiteralV2("maxlength=255") @@ -250,9 +251,9 @@ class CacheSpec extends CoreSpec { predicateIri = stringFormatter.toSmartIri(OntologyConstants.KnoraBase.ObjectClassConstraint), Seq(SmartIriLiteralV2(pagePropertyIri)) ), - stringFormatter.toSmartIri(OntologyConstants.SalsahGui.GuiElementClass) -> PredicateInfoV2( - predicateIri = stringFormatter.toSmartIri(OntologyConstants.SalsahGui.GuiElementClass), - Seq(SmartIriLiteralV2(stringFormatter.toSmartIri(OntologyConstants.SalsahGui.Searchbox))) + stringFormatter.toSmartIri(SalsahGui.GuiElementClass) -> PredicateInfoV2( + predicateIri = stringFormatter.toSmartIri(SalsahGui.GuiElementClass), + Seq(SmartIriLiteralV2(stringFormatter.toSmartIri(SalsahGui.Searchbox))) ) ), subPropertyOf = Set(stringFormatter.toSmartIri(OntologyConstants.KnoraBase.HasLinkTo)), diff --git a/webapi/src/test/scala/org/knora/webapi/sharedtestdata/SharedTestDataADM.scala b/webapi/src/test/scala/org/knora/webapi/sharedtestdata/SharedTestDataADM.scala index 615f80c3df..fdc9b06ed9 100644 --- a/webapi/src/test/scala/org/knora/webapi/sharedtestdata/SharedTestDataADM.scala +++ b/webapi/src/test/scala/org/knora/webapi/sharedtestdata/SharedTestDataADM.scala @@ -16,6 +16,7 @@ import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 import org.knora.webapi.messages.util.KnoraSystemInstances import java.time.Instant +import dsp.constants.SalsahGui /** * This object holds the same user which are loaded with 'test_data/all_data/admin-data.ttl'. Using this object @@ -170,7 +171,7 @@ object SharedTestDataADM { ontologies = Seq( OntologyConstants.KnoraBase.KnoraBaseOntologyIri, OntologyConstants.KnoraAdmin.KnoraAdminOntologyIri, - OntologyConstants.SalsahGui.SalsahGuiOntologyIri, + SalsahGui.SalsahGuiOntologyIri, OntologyConstants.Standoff.StandoffOntologyIri ), status = true,