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 41 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
19 changes: 15 additions & 4 deletions docs/02-knora-ontologies/knora-base.md
Expand Up @@ -571,22 +571,33 @@ 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 resource that have a time dimension by using `kb:isSequenceOf`. This mostly makes sense for video or audio resources that are subtypes of `kb:MovingImageRepresentation` and `kb:AudioRepresentation`.
BalduinLandolt marked this conversation as resolved.
Show resolved Hide resolved

`kb:isSequenceOf` is intended to be used in combination with `kb:hasSequenceBounds` which points to a `kb:IntervalValue` that defines the start and end point of the subseqence in relation to the entire audio/video resource. A dedicated frontend behaviour is planned, if these properties are used in combination.
BalduinLandolt marked this conversation as resolved.
Show resolved Hide resolved

The main difference to `kb:isPartOf` is where actual resoure(s) live: In `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`, and the parts only define which sub-sequence of this representation they are.
BalduinLandolt marked this conversation as resolved.
Show resolved Hide resolved

### 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,49 @@
package dsp.schema.domain

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

import java.time.Instant

case class SmartIri(value: String)

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, // TODO: make SmartIri a ValueObject at some point
BalduinLandolt marked this conversation as resolved.
Show resolved Hide resolved
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,
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
) {}
)
}
49 changes: 49 additions & 0 deletions dsp-shared/src/main/scala/dsp/valueobjects/LangString.scala
@@ -0,0 +1,49 @@
package dsp.valueobjects

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

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

object LangString {
def make(language: LanguageCode, value: String): Validation[ValidationException, LangString] =
if (value.isBlank()) {
Validation.fail(ValidationException(LangStringErrorMessages.LangStringValueEmpty))
} else {
Validation.succeed(new LangString(language, value) {})
}

def make(language: String, value: String): Validation[ValidationException, LangString] =
BalduinLandolt marked this conversation as resolved.
Show resolved Hide resolved
BalduinLandolt marked this conversation as resolved.
Show resolved Hide resolved
for {
languageCode <- LanguageCode.make(language)
langString <- LangString.make(languageCode, value)
} yield langString

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(UserErrorMessages.LanguageCodeMissing))
} else if (!V2.SupportedLanguageCodes.contains(value)) {
Validation.fail(ValidationException(UserErrorMessages.LanguageCodeInvalid))
} 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."
}
20 changes: 3 additions & 17 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 Down
7 changes: 4 additions & 3 deletions dsp-shared/src/test/scala/dsp/valueobjects/UserSpec.scala
Expand Up @@ -5,10 +5,11 @@

package dsp.valueobjects

import dsp.errors.BadRequestException
BalduinLandolt marked this conversation as resolved.
Show resolved Hide resolved
import dsp.errors.ValidationException
import dsp.valueobjects.User._
import zio.prelude.Validation
import zio.test._
import dsp.errors.BadRequestException

/**
* This spec is used to test the [[dsp.valueobjects.User]] value objects creation.
Expand Down Expand Up @@ -190,13 +191,13 @@ object UserSpec extends ZIOSpecDefault {
private val languageCodeTest = suite("LanguageCode")(
test("pass an empty value and return an error") {
assertTrue(
LanguageCode.make("") == Validation.fail(BadRequestException(UserErrorMessages.LanguageCodeMissing))
LanguageCode.make("") == Validation.fail(ValidationException(UserErrorMessages.LanguageCodeMissing))
)
},
test("pass an invalid value and return an error") {
assertTrue(
LanguageCode.make(invalidLanguageCode) == Validation.fail(
BadRequestException(UserErrorMessages.LanguageCodeInvalid)
ValidationException(UserErrorMessages.LanguageCodeInvalid)
)
)
},
Expand Down
19 changes: 10 additions & 9 deletions dsp-user/core/src/main/scala/dsp/user/domain/UserDomain.scala
Expand Up @@ -5,12 +5,13 @@

package dsp.user.domain

import dsp.errors.ValidationException
import dsp.valueobjects.Id.UserId
import dsp.valueobjects.LanguageCode
import dsp.valueobjects.User._
import zio.prelude.Validation

import java.util.UUID
import dsp.errors.BadRequestException

/**
* Represents the user domain object.
Expand Down Expand Up @@ -48,7 +49,7 @@ sealed abstract case class User private (
* @param newValue the new username
* @return the updated [[User]]
*/
def updateUsername(newValue: Username): Validation[BadRequestException, User] =
def updateUsername(newValue: Username): Validation[ValidationException, User] =
User.make(
self.id,
self.givenName,
Expand All @@ -66,7 +67,7 @@ sealed abstract case class User private (
* @param newValue the new email
* @return the updated [[User]]
*/
def updateEmail(newValue: Email): Validation[BadRequestException, User] =
def updateEmail(newValue: Email): Validation[ValidationException, User] =
User.make(
self.id,
self.givenName,
Expand All @@ -84,7 +85,7 @@ sealed abstract case class User private (
* @param newValue the new given name
* @return the updated [[User]]
*/
def updateGivenName(newValue: GivenName): Validation[BadRequestException, User] =
def updateGivenName(newValue: GivenName): Validation[ValidationException, User] =
User.make(
self.id,
newValue,
Expand All @@ -102,7 +103,7 @@ sealed abstract case class User private (
* @param newValue the new family name
* @return the updated [[User]]
*/
def updateFamilyName(newValue: FamilyName): Validation[BadRequestException, User] =
def updateFamilyName(newValue: FamilyName): Validation[ValidationException, User] =
User.make(
self.id,
self.givenName,
Expand All @@ -120,7 +121,7 @@ sealed abstract case class User private (
* @param newValue the new password
* @return the updated [[User]]
*/
def updatePassword(newValue: PasswordHash): Validation[BadRequestException, User] =
def updatePassword(newValue: PasswordHash): Validation[ValidationException, User] =
User.make(
self.id,
self.givenName,
Expand All @@ -138,7 +139,7 @@ sealed abstract case class User private (
* @param newValue the new language
* @return the updated [[User]]
*/
def updateLanguage(newValue: LanguageCode): Validation[BadRequestException, User] =
def updateLanguage(newValue: LanguageCode): Validation[ValidationException, User] =
User.make(
self.id,
self.givenName,
Expand All @@ -157,7 +158,7 @@ sealed abstract case class User private (
* @param newValue the new status
* @return the updated [[User]]
*/
def updateStatus(newValue: UserStatus): Validation[BadRequestException, User] =
def updateStatus(newValue: UserStatus): Validation[ValidationException, User] =
User.make(
self.id,
self.givenName,
Expand All @@ -181,7 +182,7 @@ object User {
language: LanguageCode,
status: UserStatus
//role: Role
): Validation[BadRequestException, User] =
): Validation[ValidationException, User] =
Validation.succeed(new User(id, givenName, familyName, username, email, password, language, status) {})

}
Expand Up @@ -7,8 +7,9 @@ package dsp.user.domain

import dsp.user.domain.User
import dsp.user.domain._
import dsp.valueobjects.User._
import dsp.user.sharedtestdata.SharedTestData
import dsp.valueobjects.LanguageCode
import dsp.valueobjects.User._
import zio.ZLayer
import zio._
import zio.test._
Expand Down
Expand Up @@ -5,11 +5,12 @@

package dsp.user.sharedtestdata

import dsp.valueobjects.User._
import dsp.user.domain.User
import dsp.errors.BadRequestException
import zio.prelude.Validation
import dsp.user.domain.User
import dsp.valueobjects.Id
import dsp.valueobjects.LanguageCode
import dsp.valueobjects.User._
import zio.prelude.Validation

object SharedTestData {
val passwordStrength = PasswordStrength.make(12)
Expand Down
Expand Up @@ -8,16 +8,17 @@ package dsp.user.handler
import dsp.errors.BadRequestException
import dsp.errors.DuplicateValueException
import dsp.errors.ForbiddenException
import dsp.errors.KnoraException
import dsp.errors.NotFoundException
import dsp.errors.RequestRejectedException
import dsp.user.api.UserRepo
import dsp.user.domain.User
import dsp.valueobjects.Id.UserId
import dsp.valueobjects.LanguageCode
import dsp.valueobjects.User._
import zio._

import java.util.UUID
import dsp.errors.KnoraException

/**
* The user handler.
Expand Down