Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add isSequenceOf to knora-base ontology (DEV-745) #2061

Merged
merged 59 commits into from Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
efd60f8
add isSequenceOf to knora-base ontology
BalduinLandolt Apr 28, 2022
15b9ed9
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt May 9, 2022
c056212
add hasSequenceBounds to knora-base
BalduinLandolt May 9, 2022
e688ccb
bump knora-base version
BalduinLandolt May 9, 2022
2238cc0
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt May 10, 2022
c226b6e
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt May 19, 2022
0d4df70
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt May 30, 2022
be7de69
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt Jul 11, 2022
05dc3a8
better error messages
BalduinLandolt Jul 12, 2022
3e403b6
test: write test to create isSequenceOf resource classes for video re…
BalduinLandolt Jul 13, 2022
9a6117b
update testdata according to modified knora-base ontology
BalduinLandolt Jul 13, 2022
51c9520
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt Jul 13, 2022
786e850
test: add test for audio sequence in ontology
BalduinLandolt Jul 13, 2022
5459a4f
make ontology model slightly slimmer
BalduinLandolt Jul 13, 2022
e64d0ff
add sequence bounds property to ontology tests
BalduinLandolt Jul 13, 2022
165983d
fix minor oversights in knora-base
BalduinLandolt Jul 14, 2022
053f2b1
minor refactorings
BalduinLandolt Jul 14, 2022
e687c7e
start working on a test ontology and dataset for sequences
BalduinLandolt Jul 14, 2022
2e8b30e
add ontology constants
BalduinLandolt Jul 14, 2022
c688cea
add test for dynamically creating a video resource
BalduinLandolt Jul 14, 2022
30447b7
bump API version again
BalduinLandolt Jul 14, 2022
b7ebfc6
add audio (including subclasses of isSequenceOf etc.) to sequences on…
BalduinLandolt Jul 14, 2022
754e5fb
add audio resource to sequences data
BalduinLandolt Jul 14, 2022
d0dc2c7
add tests for audio sequences with ontology-specific sub-props of isS…
BalduinLandolt Jul 14, 2022
25b0744
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt Jul 14, 2022
20b60e4
refactor: remove some code duplication
BalduinLandolt Jul 14, 2022
7c68bfc
create client test data
BalduinLandolt Jul 14, 2022
111da4b
add newly created test data to list of expected test data
BalduinLandolt Jul 14, 2022
384ec70
add noop upgrade plugin
BalduinLandolt Jul 18, 2022
f63d2e5
use Validation in OntologyHelpers
BalduinLandolt Jul 18, 2022
08a3875
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt Jul 18, 2022
af494c8
replace more throws with validations
BalduinLandolt Jul 19, 2022
cabec10
fix validation
BalduinLandolt Jul 19, 2022
7d0cea1
createPropertyCommand value object in schema slice
BalduinLandolt Jul 19, 2022
8458dec
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt Jul 19, 2022
05c2820
remove client test data
BalduinLandolt Jul 20, 2022
6b7443f
add isSequenceOf to anything ontology
BalduinLandolt Jul 20, 2022
7862db2
update test data to change anything ontology
BalduinLandolt Jul 20, 2022
3004db1
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt Jul 25, 2022
7a376de
add unit tests to ontology part of the changes
BalduinLandolt Jul 25, 2022
e489f1e
add documentation
BalduinLandolt Jul 25, 2022
cdfa1d5
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt Jul 26, 2022
e5a02a3
first improvements according to review
BalduinLandolt Jul 26, 2022
cd3f5ab
Update LangString.scala
BalduinLandolt Jul 26, 2022
236c10e
improve LangString value object
BalduinLandolt Jul 26, 2022
9e6309c
add tests for LangString value object
BalduinLandolt Jul 26, 2022
fe5d934
move language code error messages in the right place
BalduinLandolt Jul 26, 2022
b2c4ef4
add documentation to SchemaCommands
BalduinLandolt Jul 26, 2022
ad86d69
add tests for SchemaCommands
BalduinLandolt Jul 26, 2022
9a6778f
rename V3 smartIri in OntologiesRoute
BalduinLandolt Jul 26, 2022
45b94a8
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt Jul 27, 2022
a7d5fe5
apply feedback from review
BalduinLandolt Jul 27, 2022
87b563e
Merge branch 'main' into wip/DEV-745-add-issequenceof
subotic Jul 29, 2022
287e2e1
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt Aug 2, 2022
ddd13cb
fix problems after merge conflicts
BalduinLandolt Aug 2, 2022
95a8532
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt Aug 2, 2022
10f1a4d
add logging to unsafeMake
BalduinLandolt Aug 3, 2022
fedaadc
Merge branch 'main' into wip/DEV-745-add-issequenceof
BalduinLandolt Aug 3, 2022
74e20de
tidy up language code stuff
BalduinLandolt Aug 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 21 additions & 1 deletion build.sbt
Expand Up @@ -202,7 +202,7 @@ lazy val webapi: Project = Project(id = "webapi", base = file("webapi"))
),
buildInfoPackage := "org.knora.webapi.http.version"
)
.dependsOn(shared)
.dependsOn(shared, schemaCore)

lazy val webapiJavaRunOptions = Seq(
// "-showversion",
Expand Down Expand Up @@ -312,6 +312,26 @@ lazy val userCore = project
)
.dependsOn(shared)

// schema projects

lazy val schemaCore = project
.in(file("dsp-schema/core"))
.settings(
scalacOptions ++= Seq(
"-feature",
"-unchecked",
"-deprecation",
"-Yresolve-term-conflict:package",
"-Ymacro-annotations"
),
name := "schemaCore",
libraryDependencies ++= Dependencies.schemaCoreLibraryDependencies,
testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework"))
)
.dependsOn(shared)

// shared

lazy val shared = project
.in(file("dsp-shared"))
.settings(
Expand Down
25 changes: 21 additions & 4 deletions docs/02-knora-ontologies/knora-base.md
Expand Up @@ -571,22 +571,39 @@ containing metadata about the link. We can visualise the result as the following
Knora allows a user to see a link if the requesting user has permission to see the source and target resources as well
as the `kb:LinkValue`.

### Part-of (part-whole) relation between resources
### Part-Whole-Relations between Resources

#### isPartOf

A special case of linked resources are _part-of related resources_, i.e. a resource consisting of several other resources.
In order to create a part-of relation between two resources, the resource that is part of another resource needs to have
a property that is a subproperty of `kb:isPartOf`. This property needs to point to the resource class it is part of via
its predicate `knora-api:objectType`.
its predicate `kb:objectType`.
`kb:isPartOf` itself is a subproperty of `kb:hasLinkTo`. Same as described above for link properties, a corresponding
part-of value property is created automatically. This value property has the same name as the part-of property with
`Value` appended. For example, if in an ontology `data` a property `data:partOf` was defined, the corresponding value
property would be named `data:partOfValue`. This newly created property `data:partOfValue` is defined as a subproperty
of `kb:isPartOfValue`.

Part-of relations are recommended for resources of type `StillImageRepresentation`. In that case, the resource that is
part of another resource needs to have a property that is a subproperty of `knora-api:seqnum` with an integer as value.
Part-of relations are recommended for resources of type `kb:StillImageRepresentation`. In that case, the resource that is
part of another resource needs to have a property that is a subproperty of `kb:seqnum` with an integer as value.
A client can then use this information to leaf through the parts of the compound resource (p.ex. to leaf through the
pages of a book like in [this](https://docs.dasch.swiss/DSP-API/01-introduction/example-project/#resource-classes) example).

#### isSequenceOf

Similar to `kb:isPartOf` for `kb:StillImageRepresentations`, part-whole-relations can be defined for resources that have a time
dimension by using `kb:isSequenceOf`. You can use it for video or audio resources that are subtypes of `kb:MovingImageRepresentation`
and `kb:AudioRepresentation`.

`kb:isSequenceOf` is intended to be used in combination with the property `kb:hasSequenceBounds` which points to a `kb:IntervalValue`.
This defines the start and end point of the subseqence in relation to the entire audio/video resource as an [interval](#intervalvalue).
A dedicated frontend behaviour is planned, if these properties are used in combination.

There is an important difference between `kb:isSequenceOf` and `kb:isPartOf`: For `kb:isPartOf`, each part *is a* `kb:StillImageRepresentation` and
the whole consists of multiple such parts. In `kb:isSequenceOf` on the other hand, the whole is one `kb:MovingImageRepresentation` or `kb:AudioRepresentation`.
The parts only define which sub-sequence of this representation they are.

### Text with Standoff Markup

DSP-API is designed to be able to store text with markup, which can indicate formatting and structure, as well as the
Expand Down
@@ -0,0 +1,59 @@
package dsp.schema.domain

import dsp.errors.ValidationException
import dsp.valueobjects.LangString
import dsp.valueobjects.Schema
import zio.prelude.Validation

import java.time.Instant

/**
* SmartIri placeholder value object.
* WARNING: don't use this in production code. First find a solution how we deal with SmartIri in the new codebase.
*
* // TODO: this is only a placeholder for SmartIri - eventually we need a proper solution for IRI value objects.
*/
case class SmartIri(value: String)

/**
* Command/Value object representing a command to create a property on a schema/ontology.
* WARNING: This should not be used in production code before the SmartIri value object is propertly implemented.
*/
sealed abstract case class CreatePropertyCommand private (
BalduinLandolt marked this conversation as resolved.
Show resolved Hide resolved
BalduinLandolt marked this conversation as resolved.
Show resolved Hide resolved
ontologyIri: SmartIri,
lastModificationDate: Instant,
propertyIri: SmartIri,
subjectType: Option[SmartIri],
objectType: SmartIri, // must be `kb:Value`, unless it's a link property, then it should be a `kb:Resource`
label: LangString,
comment: Option[LangString],
superProperties: List[SmartIri],
guiObject: Schema.GuiObject
)

object CreatePropertyCommand {
def make(
ontologyIri: SmartIri, // TODO: should eventally be schemaId value object, etc.
lastModificationDate: Instant,
propertyIri: SmartIri,
subjectType: Option[SmartIri],
objectType: SmartIri,
label: LangString,
comment: Option[LangString],
superProperties: List[SmartIri],
guiObject: Schema.GuiObject
): Validation[ValidationException, CreatePropertyCommand] =
Validation.succeed(
new CreatePropertyCommand(
ontologyIri = ontologyIri,
lastModificationDate = lastModificationDate,
propertyIri = propertyIri,
subjectType = subjectType,
objectType = objectType,
label = label,
comment = comment,
superProperties = superProperties,
guiObject = guiObject
) {}
)
}
@@ -0,0 +1,51 @@
package dsp.schema.domain

import dsp.constants.SalsahGui
import dsp.valueobjects.LangString
import dsp.valueobjects.LanguageCode
import dsp.valueobjects.Schema
import zio._
import zio.prelude.Validation
import zio.test.Assertion._
import zio.test._

import java.time.Instant

/**
* This spec is used to test [[dsp.schema.domain.SchemaCommands]].
*/
object SchemaCommandsSpec extends ZIOSpecDefault {

def spec = (createPropertyCommandTest)

private val createPropertyCommandTest = suite("CreatePropertyCommand")(
test("create a createPropertyCommand") {
val ontologyIri = SmartIri("Ontology IRI")
val lastModificationDate = Instant.now()
val propertyIri = SmartIri("")
val subjectType = None
val objectType = SmartIri("Object Type")
val superProperties = List(SmartIri("Super Property IRI"))
(for {
label <- LangString.make(LanguageCode.en, "some label")
commentLangString <- LangString.make(LanguageCode.en, "some comment")
comment = Some(commentLangString)
guiAttribute <- Schema.GuiAttribute.make("hlist=<http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA>")
guiElement <- Schema.GuiElement.make(SalsahGui.List)
guiObject <- Schema.GuiObject.make(guiAttributes = List(guiAttribute), guiElement = Some(guiElement))
command = CreatePropertyCommand.make(
ontologyIri = ontologyIri,
lastModificationDate = lastModificationDate,
propertyIri = propertyIri,
subjectType = subjectType,
objectType = objectType,
label = label,
comment = comment,
superProperties = superProperties,
guiObject = guiObject
)
} yield assert(command.toEither)(isRight)).toZIO
}
)

}
79 changes: 79 additions & 0 deletions dsp-shared/src/main/scala/dsp/valueobjects/LangString.scala
@@ -0,0 +1,79 @@
package dsp.valueobjects

import dsp.errors.ValidationException
import zio.prelude.Validation

/**
* LangString value object
*/
sealed abstract case class LangString private (language: LanguageCode, value: String)
BalduinLandolt marked this conversation as resolved.
Show resolved Hide resolved

object LangString {

/**
* Creates a [[zio.prelude.Validation]] that either fails with a ValidationException or succeeds with a LangString value object.
*
* @param language a [[LanguageCode]] value object representing the language of the LangString.
* @param value the string value of the LangString.
* @return a Validation of a LangString value object.
*/
def make(language: LanguageCode, value: String): Validation[ValidationException, LangString] =
if (value.isBlank()) {
Validation.fail(ValidationException(LangStringErrorMessages.LangStringValueEmpty))
} else {
Validation.succeed(new LangString(language, value) {})
}

/**
* Creates a [[zio.prelude.Validation]] that either fails with a ValidationException or succeeds with a LangString value object.
*
* @param language a two-digit language code string representing the language of the LangString.
* @param value the string value of the LangString.
* @return a Validation of a LangString value object.
*/
def makeFromStrings(language: String, value: String): Validation[ValidationException, LangString] =
for {
languageCode <- LanguageCode.make(language)
langString <- LangString.make(languageCode, value)
} yield langString

/**
* Unsafely creates a LangString value object.
* Warning: skips all validation. Should not be used unless there is no possibility for the data to be invalid.
*
* @param languagea [[LanguageCode]] value object representing the language of the LangString.
* @param value the string value of the LangString.
* @return a LanguageCode value object
*/
def unsafeMake(language: LanguageCode, value: String): LangString =

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you actually use this somewhere? If not, I would not implement it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right, it's not used anymore. I'll delete it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, no, I use it after all... I put it on the "to discuss" list.

new LangString(language, value) {}
}

/**
* LanguageCode value object.
*/
sealed abstract case class LanguageCode private (value: String)
object LanguageCode { self =>
def make(value: String): Validation[ValidationException, LanguageCode] =
if (value.isEmpty) {
Validation.fail(ValidationException(LanguageCodeErrorMessages.LanguageCodeMissing))
} else if (!V2.SupportedLanguageCodes.contains(value)) {
Validation.fail(ValidationException(LanguageCodeErrorMessages.LanguageCodeInvalid(value)))
} else {
Validation.succeed(new LanguageCode(value) {})
}

lazy val en: LanguageCode = new LanguageCode(V2.EN) {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the language codes from V2 be moved to the right place? I think the language codes could even live inside this file (.../valueobjects/LangString.scala) but I am not 100% sure if this is the best location.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right. But I'll wait with that for the thursday meeting.

lazy val de: LanguageCode = new LanguageCode(V2.DE) {}
lazy val fr: LanguageCode = new LanguageCode(V2.FR) {}
lazy val it: LanguageCode = new LanguageCode(V2.IT) {}
lazy val rm: LanguageCode = new LanguageCode(V2.RM) {}
}

object LangStringErrorMessages {
val LangStringValueEmpty = "String value cannot be empty."
}
object LanguageCodeErrorMessages {
val LanguageCodeMissing = "LanguageCode cannot be empty."
def LanguageCodeInvalid(lang: String) = s"LanguageCode '$lang' is invalid."
}
22 changes: 3 additions & 19 deletions dsp-shared/src/main/scala/dsp/valueobjects/User.scala
Expand Up @@ -6,13 +6,14 @@
package dsp.valueobjects

import dsp.errors.BadRequestException
BalduinLandolt marked this conversation as resolved.
Show resolved Hide resolved
import dsp.errors.ValidationException
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder
import zio._
import zio.prelude.Validation

import scala.util.matching.Regex
import java.security.SecureRandom
import scala.util.matching.Regex

object User {

Expand Down Expand Up @@ -198,27 +199,12 @@ object User {
Validation.succeed(new UserStatus(value) {})
}

/**
* LanguageCode value object.
*/
sealed abstract case class LanguageCode private (value: String)
object LanguageCode { self =>
def make(value: String): Validation[Throwable, LanguageCode] =
if (value.isEmpty) {
Validation.fail(BadRequestException(UserErrorMessages.LanguageCodeMissing))
} else if (!V2.SupportedLanguageCodes.contains(value)) {
Validation.fail(BadRequestException(UserErrorMessages.LanguageCodeInvalid))
} else {
Validation.succeed(new LanguageCode(value) {})
}
}

/**
* SystemAdmin value object.
*/
sealed abstract case class SystemAdmin private (value: Boolean)
object SystemAdmin {
def make(value: Boolean): Validation[Throwable, SystemAdmin] =
def make(value: Boolean): Validation[ValidationException, SystemAdmin] =
Validation.succeed(new SystemAdmin(value) {})
}
}
Expand All @@ -236,6 +222,4 @@ object UserErrorMessages {
val GivenNameInvalid = "GivenName is invalid."
val FamilyNameMissing = "FamilyName cannot be empty."
val FamilyNameInvalid = "FamilyName is invalid."
val LanguageCodeMissing = "LanguageCode cannot be empty."
val LanguageCodeInvalid = "LanguageCode is invalid."
}