diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml
index 212ac25cb..774cd05f8 100644
--- a/.github/workflows/anchore.yml
+++ b/.github/workflows/anchore.yml
@@ -16,10 +16,10 @@ jobs:
with:
distribution: 'temurin'
java-version: '21'
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v3
- name: Assemble project
- uses: gradle/gradle-build-action@v2
- with:
- arguments: assemble
+ run: ./gradlew assemble
- name: Upload jars artifacts
uses: actions/upload-artifact@v4
with:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8da0f1101..b8dd8f45a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -29,12 +29,8 @@ jobs:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- - name: Cache Gradle packages
- uses: actions/cache@v4
- with:
- path: ~/.gradle/caches
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
- restore-keys: ${{ runner.os }}-gradle
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v3
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml
index df2061db0..6a1a33d35 100644
--- a/.github/workflows/cla.yml
+++ b/.github/workflows/cla.yml
@@ -12,7 +12,7 @@ jobs:
- name: "CLA Assistant"
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
# Beta Release
- uses: cla-assistant/github-action@v2.3.1
+ uses: cla-assistant/github-action@v2.3.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# the below token should have repo scope and must be manually added by you in the repository's secret
diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml
index b22c88cd0..c759bf2c0 100644
--- a/.github/workflows/pr-labeler.yml
+++ b/.github/workflows/pr-labeler.yml
@@ -13,6 +13,6 @@ jobs:
pull-requests: write # for TimonVS/pr-labeler-action to add labels in PR
runs-on: ubuntu-latest
steps:
- - uses: TimonVS/pr-labeler-action@v4.1.1
+ - uses: TimonVS/pr-labeler-action@v5.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
index 58e777757..2123aaf27 100644
--- a/.github/workflows/release-drafter.yml
+++ b/.github/workflows/release-drafter.yml
@@ -12,6 +12,6 @@ jobs:
runs-on: ubuntu-latest
steps:
# Drafts your next Release notes as Pull Requests are merged into "master"
- - uses: release-drafter/release-drafter@v5
+ - uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/api-gateway/build.gradle.kts b/api-gateway/build.gradle.kts
index c15910165..5c3a1d244 100644
--- a/api-gateway/build.gradle.kts
+++ b/api-gateway/build.gradle.kts
@@ -13,7 +13,7 @@ dependencies {
testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
- detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.4")
+ detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.5")
}
springBoot {
diff --git a/build.gradle.kts b/build.gradle.kts
index 347bc2bd8..162340232 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -18,13 +18,13 @@ plugins {
`kotlin-dsl`
// only apply the plugin in the subprojects requiring it because it expects a Spring Boot app
// and the shared lib is obviously not one
- id("org.springframework.boot") version "3.2.1" apply false
+ id("org.springframework.boot") version "3.2.4" apply false
id("io.spring.dependency-management") version "1.1.4" apply false
- id("org.graalvm.buildtools.native") version "0.9.28"
- kotlin("jvm") version "1.9.22" apply false
- kotlin("plugin.spring") version "1.9.22" apply false
- id("com.google.cloud.tools.jib") version "3.4.0" apply false
- id("io.gitlab.arturbosch.detekt") version "1.23.4" apply false
+ id("org.graalvm.buildtools.native") version "0.10.1"
+ kotlin("jvm") version "1.9.23" apply false
+ kotlin("plugin.spring") version "1.9.23" apply false
+ id("com.google.cloud.tools.jib") version "3.4.1" apply false
+ id("io.gitlab.arturbosch.detekt") version "1.23.5" apply false
id("org.sonarqube") version "4.4.1.3373"
jacoco
}
@@ -64,10 +64,10 @@ subprojects {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
- implementation("com.apicatalog:titanium-json-ld:1.3.3")
+ implementation("com.apicatalog:titanium-json-ld:1.4.0")
implementation("org.glassfish:jakarta.json:2.0.1")
- implementation("io.arrow-kt:arrow-fx-coroutines:1.2.1")
+ implementation("io.arrow-kt:arrow-fx-coroutines:1.2.3")
implementation("org.locationtech.jts.io:jts-io-common:1.19.0")
@@ -104,7 +104,7 @@ subprojects {
configurations.matching { it.name == "detekt" }.all {
resolutionStrategy.eachDependency {
if (requested.group == "org.jetbrains.kotlin") {
- useVersion("1.9.21")
+ useVersion("1.9.22")
}
}
}
diff --git a/search-service/build.gradle.kts b/search-service/build.gradle.kts
index b62d9400f..c36f7f389 100644
--- a/search-service/build.gradle.kts
+++ b/search-service/build.gradle.kts
@@ -20,10 +20,10 @@ dependencies {
// implementation (and not runtime) because we are using the native jsonb encoding provided by PG
implementation("org.postgresql:r2dbc-postgresql")
implementation("com.github.stellio-hub:json-merge:0.1.0")
- implementation("org.json:json:20231013")
+ implementation("org.json:json:20240303")
implementation(project(":shared"))
- detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.4")
+ detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.5")
developmentOnly("org.springframework.boot:spring-boot-devtools")
diff --git a/search-service/config/detekt/baseline.xml b/search-service/config/detekt/baseline.xml
index b10aa1709..124c05513 100644
--- a/search-service/config/detekt/baseline.xml
+++ b/search-service/config/detekt/baseline.xml
@@ -17,9 +17,7 @@
LongMethod:V0_29__JsonLd_migration.kt$V0_29__JsonLd_migration$override fun migrate(context: Context)
LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( temporalEntityAttribute: UUID, instanceId: URI = generateRandomInstanceId(), timeAndProperty: Pair<ZonedDateTime, TemporalProperty>, value: Triple<String?, Double?, WKTCoordinates?>, payload: ExpandedAttributeInstance, sub: String? )
LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( temporalEntityAttribute: UUID, instanceId: URI = generateRandomInstanceId(), timeProperty: TemporalProperty? = TemporalProperty.OBSERVED_AT, modifiedAt: ZonedDateTime? = null, attributeMetadata: AttributeMetadata, payload: ExpandedAttributeInstance, time: ZonedDateTime, sub: String? = null )
- LongParameterList:EntityEventService.kt$EntityEventService$( sub: String?, entityId: URI, attributeName: ExpandedTerm, datasetId: URI? = null, deleteAll: Boolean, contexts: List<String> )
- LongParameterList:EntityEventService.kt$EntityEventService$( sub: String?, entityId: URI, jsonLdAttributes: Map<String, Any>, updateResult: UpdateResult, overwrite: Boolean, contexts: List<String> )
- LongParameterList:EntityEventService.kt$EntityEventService$( updatedDetails: UpdatedDetails, sub: String?, tenantName: String, entityId: URI, entityTypesAndPayload: Pair<List<ExpandedTerm>, String>, serializedAttribute: Pair<ExpandedTerm, String>, overwrite: Boolean, contexts: List<String> )
+ LongParameterList:EntityEventService.kt$EntityEventService$( updatedDetails: UpdatedDetails, sub: String?, tenantName: String, entityId: URI, entityTypesAndPayload: Pair<List<ExpandedTerm>, String>, serializedAttribute: Pair<ExpandedTerm, String>, overwrite: Boolean )
LongParameterList:TemporalEntityAttributeService.kt$TemporalEntityAttributeService$( entityId: URI, attributeName: ExpandedTerm, attributeMetadata: AttributeMetadata, createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? )
LongParameterList:TemporalEntityAttributeService.kt$TemporalEntityAttributeService$( entityUri: URI, ngsiLdAttributes: List<NgsiLdAttribute>, expandedAttributes: ExpandedAttributes, createdAt: ZonedDateTime, observedAt: ZonedDateTime?, sub: Sub? )
LongParameterList:TemporalEntityAttributeService.kt$TemporalEntityAttributeService$( entityUri: URI, ngsiLdAttributes: List<NgsiLdAttribute>, expandedAttributes: ExpandedAttributes, disallowOverwrite: Boolean, createdAt: ZonedDateTime, sub: Sub? )
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/listener/ObservationEventListener.kt b/search-service/src/main/kotlin/com/egm/stellio/search/listener/ObservationEventListener.kt
index dde5c44b3..f04afeeaa 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/listener/ObservationEventListener.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/listener/ObservationEventListener.kt
@@ -78,8 +78,7 @@ class ObservationEventListener(
entityEventService.publishEntityCreateEvent(
observationEvent.sub,
observationEvent.entityId,
- expandJsonLdTerms(observationEvent.entityTypes, observationEvent.contexts),
- observationEvent.contexts
+ expandJsonLdTerms(observationEvent.entityTypes, observationEvent.contexts)
)
}
}.writeContextAndSubscribe(tenantName, observationEvent)
@@ -115,8 +114,7 @@ class ObservationEventListener(
observationEvent.entityId,
expandedAttribute.toExpandedAttributes(),
it,
- false,
- observationEvent.contexts
+ false
)
}
}
@@ -153,8 +151,7 @@ class ObservationEventListener(
observationEvent.entityId,
expandedAttribute.toExpandedAttributes(),
it,
- observationEvent.overwrite,
- observationEvent.contexts
+ observationEvent.overwrite
)
}
}
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeInstance.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeInstance.kt
index 65ea363d8..afdfe71fc 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeInstance.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeInstance.kt
@@ -1,12 +1,12 @@
package com.egm.stellio.search.model
+import com.egm.stellio.search.util.toJson
import com.egm.stellio.shared.model.ExpandedAttributeInstance
import com.egm.stellio.shared.model.WKTCoordinates
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_INSTANCE_ID_PROPERTY
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_MODIFIED_AT_PROPERTY
import com.egm.stellio.shared.util.JsonLdUtils.buildNonReifiedPropertyValue
import com.egm.stellio.shared.util.JsonLdUtils.buildNonReifiedTemporalValue
-import com.egm.stellio.shared.util.JsonUtils.serializeObject
import com.egm.stellio.shared.util.toUri
import io.r2dbc.postgresql.codec.Json
import java.net.URI
@@ -66,8 +66,6 @@ data class AttributeInstance private constructor(
sub = sub
)
- private fun ExpandedAttributeInstance.toJson(): Json = Json.of(serializeObject(this))
-
private fun ExpandedAttributeInstance.composePayload(
instanceId: URI,
modifiedAt: ZonedDateTime? = null
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityPayload.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityPayload.kt
index 3a182299e..5f8d986d7 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityPayload.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityPayload.kt
@@ -21,9 +21,6 @@ data class EntityPayload(
val scopes: List? = null,
val createdAt: ZonedDateTime,
val modifiedAt: ZonedDateTime? = null,
- // creation time contexts
- // FIXME only stored because needed to compact types at deletion time...
- val contexts: List,
val payload: Json,
val specificAccessPolicy: SpecificAccessPolicy? = null
) {
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt
index 16ceefd97..7655065e2 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt
@@ -21,7 +21,8 @@ data class AttributeInfo(
enum class AttributeType(val key: String) {
Property("Property"),
Relationship("Relationship"),
- GeoProperty("GeoProperty");
+ GeoProperty("GeoProperty"),
+ JsonProperty("JsonProperty");
companion object {
fun forKey(key: String): AttributeType =
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt
index 189aec73b..badf90d3a 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt
@@ -2,7 +2,12 @@ package com.egm.stellio.search.model
import com.egm.stellio.shared.model.ExpandedTerm
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_TYPE
+import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_VALUES
+import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_TYPE
+import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUES
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_TYPE
+import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_VALUES
+import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_OBJECTS
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_TYPE
import io.r2dbc.postgresql.codec.Json
import org.springframework.data.annotation.Id
@@ -32,19 +37,33 @@ data class TemporalEntityAttribute(
DATETIME,
DATE,
TIME,
- URI
+ URI,
+ JSON
}
enum class AttributeType {
Property,
Relationship,
- GeoProperty;
+ GeoProperty,
+ JsonProperty;
fun toExpandedName(): String =
when (this) {
Property -> NGSILD_PROPERTY_TYPE.uri
Relationship -> NGSILD_RELATIONSHIP_TYPE.uri
GeoProperty -> NGSILD_GEOPROPERTY_TYPE.uri
+ JsonProperty -> NGSILD_JSONPROPERTY_TYPE.uri
+ }
+
+ /**
+ * Returns the key of the member for the simplified representation of the attribute, as defined in 4.5.9
+ */
+ fun toSimpliedRepresentationKey(): String =
+ when (this) {
+ Property -> NGSILD_PROPERTY_VALUES
+ Relationship -> NGSILD_RELATIONSHIP_OBJECTS
+ GeoProperty -> NGSILD_GEOPROPERTY_VALUES
+ JsonProperty -> NGSILD_JSONPROPERTY_VALUES
}
}
}
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityEventService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityEventService.kt
index a12267577..e84f688a5 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityEventService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityEventService.kt
@@ -38,8 +38,7 @@ class EntityEventService(
suspend fun publishEntityCreateEvent(
sub: String?,
entityId: URI,
- entityTypes: List,
- contexts: List
+ entityTypes: List
): Job {
val tenantName = getTenantFromContext()
val entity = getSerializedEntity(entityId)
@@ -47,7 +46,7 @@ class EntityEventService(
logger.debug("Sending create event for entity {} in tenant {}", entityId, tenantName)
entity.onRight {
publishEntityEvent(
- EntityCreateEvent(sub, tenantName, entityId, entityTypes, it.second, contexts)
+ EntityCreateEvent(sub, tenantName, entityId, entityTypes, it.second, emptyList())
)
}.logEntityEvent(EventsType.ENTITY_CREATE, entityId, tenantName)
}
@@ -56,8 +55,7 @@ class EntityEventService(
suspend fun publishEntityReplaceEvent(
sub: String?,
entityId: URI,
- entityTypes: List,
- contexts: List
+ entityTypes: List
): Job {
val tenantName = getTenantFromContext()
val entity = getSerializedEntity(entityId)
@@ -65,7 +63,7 @@ class EntityEventService(
logger.debug("Sending replace event for entity {} in tenant {}", entityId, tenantName)
entity.onRight {
publishEntityEvent(
- EntityReplaceEvent(sub, tenantName, entityId, entityTypes, it.second, contexts)
+ EntityReplaceEvent(sub, tenantName, entityId, entityTypes, it.second, emptyList())
)
}.logEntityEvent(EventsType.ENTITY_REPLACE, entityId, tenantName)
}
@@ -73,8 +71,7 @@ class EntityEventService(
suspend fun publishEntityDeleteEvent(
sub: String?,
- entityPayload: EntityPayload,
- contexts: List
+ entityPayload: EntityPayload
): Job {
val tenantName = getTenantFromContext()
return coroutineScope.launch {
@@ -86,7 +83,7 @@ class EntityEventService(
entityPayload.entityId,
entityPayload.types,
entityPayload.payload.asString(),
- contexts
+ emptyList()
)
)
}
@@ -97,8 +94,7 @@ class EntityEventService(
entityId: URI,
jsonLdAttributes: Map,
updateResult: UpdateResult,
- overwrite: Boolean,
- contexts: List
+ overwrite: Boolean
): Job {
val tenantName = getTenantFromContext()
val entity = getSerializedEntity(entityId)
@@ -116,8 +112,7 @@ class EntityEventService(
entityId,
it,
serializedAttribute,
- overwrite,
- contexts
+ overwrite
)
}
}.logAttributeEvent("Attribute Change", entityId, tenantName)
@@ -131,8 +126,7 @@ class EntityEventService(
entityId: URI,
entityTypesAndPayload: Pair, String>,
serializedAttribute: Pair,
- overwrite: Boolean,
- contexts: List
+ overwrite: Boolean
) {
when (updatedDetails.updateOperationResult) {
UpdateOperationResult.APPENDED ->
@@ -147,7 +141,7 @@ class EntityEventService(
overwrite,
serializedAttribute.second,
entityTypesAndPayload.second,
- contexts
+ emptyList()
)
)
@@ -162,7 +156,7 @@ class EntityEventService(
updatedDetails.datasetId,
serializedAttribute.second,
entityTypesAndPayload.second,
- contexts
+ emptyList()
)
)
@@ -177,7 +171,7 @@ class EntityEventService(
updatedDetails.datasetId,
serializedAttribute.second,
entityTypesAndPayload.second,
- contexts
+ emptyList()
)
)
@@ -194,8 +188,7 @@ class EntityEventService(
entityId: URI,
attributeName: ExpandedTerm,
datasetId: URI? = null,
- deleteAll: Boolean,
- contexts: List
+ deleteAll: Boolean
): Job {
val tenantName = getTenantFromContext()
val entity = getSerializedEntity(entityId)
@@ -216,7 +209,7 @@ class EntityEventService(
it.first,
attributeName,
it.second,
- contexts
+ emptyList()
)
)
else
@@ -229,7 +222,7 @@ class EntityEventService(
attributeName,
datasetId,
it.second,
- contexts
+ emptyList()
)
)
}.logAttributeEvent("Attribute Delete", entityId, tenantName)
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityOperationService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityOperationService.kt
index 9187d4b40..baedc1590 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityOperationService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityOperationService.kt
@@ -202,7 +202,7 @@ class EntityOperationService(
sub: Sub?
): Either = either {
val (jsonLdEntity, ngsiLdEntity) = entity
- temporalEntityAttributeService.deleteTemporalAttributesOfEntity(ngsiLdEntity.id)
+ temporalEntityAttributeService.deleteTemporalAttributesOfEntity(ngsiLdEntity.id).bind()
val updateResult = entityPayloadService.appendAttributes(
ngsiLdEntity.id,
jsonLdEntity.getAttributes(),
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityPayloadService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityPayloadService.kt
index a9b3e1cda..2179d79b3 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityPayloadService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityPayloadService.kt
@@ -64,8 +64,8 @@ class EntityPayloadService(
val specificAccessPolicy = ngsiLdEntity.getSpecificAccessPolicy()?.bind()
databaseClient.sql(
"""
- INSERT INTO entity_payload (entity_id, types, scopes, created_at, payload, contexts, specific_access_policy)
- VALUES (:entity_id, :types, :scopes, :created_at, :payload, :contexts, :specific_access_policy)
+ INSERT INTO entity_payload (entity_id, types, scopes, created_at, payload, specific_access_policy)
+ VALUES (:entity_id, :types, :scopes, :created_at, :payload, :specific_access_policy)
""".trimIndent()
)
.bind("entity_id", ngsiLdEntity.id)
@@ -73,7 +73,6 @@ class EntityPayloadService(
.bind("scopes", ngsiLdEntity.scopes?.toTypedArray())
.bind("created_at", createdAt)
.bind("payload", Json.of(serializeObject(expandedEntity.populateCreationTimeDate(createdAt).members)))
- .bind("contexts", ngsiLdEntity.contexts.toTypedArray())
.bind("specific_access_policy", specificAccessPolicy?.toString())
.execute()
.map {
@@ -155,8 +154,7 @@ class EntityPayloadService(
scopes = :scopes,
modified_at = :modified_at,
payload = :payload,
- specific_access_policy = :specific_access_policy,
- contexts = :contexts
+ specific_access_policy = :specific_access_policy
WHERE entity_id = :entity_id
""".trimIndent()
)
@@ -165,7 +163,6 @@ class EntityPayloadService(
.bind("scopes", ngsiLdEntity.scopes?.toTypedArray())
.bind("modified_at", replacedAt)
.bind("payload", Json.of(serializedPayload))
- .bind("contexts", expandedEntity.contexts.toTypedArray())
.bind("specific_access_policy", specificAccessPolicy?.toString())
.execute()
.map {
@@ -210,7 +207,6 @@ class EntityPayloadService(
scopes = toOptionalList(row["scopes"]),
createdAt = toZonedDateTime(row["created_at"]),
modifiedAt = toOptionalZonedDateTime(row["modified_at"]),
- contexts = toList(row["contexts"]),
payload = toJson(row["payload"]),
specificAccessPolicy = toOptionalEnum(row["specific_access_policy"])
)
@@ -421,58 +417,46 @@ class EntityPayloadService(
newTypes: List,
modifiedAt: ZonedDateTime,
allowEmptyListOfTypes: Boolean = true
- ): Either =
- either {
- val entityPayload = retrieve(entityId).bind()
- val currentTypes = entityPayload.types
- // when dealing with an entity update, list of types can be empty if no change of type is requested
- if (currentTypes.sorted() == newTypes.sorted() || newTypes.isEmpty() && allowEmptyListOfTypes)
- return@either UpdateResult(emptyList(), emptyList())
- if (!newTypes.containsAll(currentTypes)) {
- val removedTypes = currentTypes.minus(newTypes)
- return@either updateResultFromDetailedResult(
+ ): Either = either {
+ val entityPayload = retrieve(entityId).bind()
+ val currentTypes = entityPayload.types
+ // when dealing with an entity update, list of types can be empty if no change of type is requested
+ if (currentTypes.sorted() == newTypes.sorted() || newTypes.isEmpty() && allowEmptyListOfTypes)
+ return@either UpdateResult(emptyList(), emptyList())
+
+ val updatedTypes = currentTypes.union(newTypes)
+ val updatedPayload = entityPayload.payload.deserializeExpandedPayload()
+ .mapValues {
+ if (it.key == JSONLD_TYPE)
+ updatedTypes
+ else it
+ }
+
+ databaseClient.sql(
+ """
+ UPDATE entity_payload
+ SET types = :types,
+ modified_at = :modified_at,
+ payload = :payload
+ WHERE entity_id = :entity_id
+ """.trimIndent()
+ )
+ .bind("entity_id", entityId)
+ .bind("modified_at", modifiedAt)
+ .bind("types", updatedTypes.toTypedArray())
+ .bind("payload", Json.of(serializeObject(updatedPayload)))
+ .execute()
+ .map {
+ updateResultFromDetailedResult(
listOf(
UpdateAttributeResult(
attributeName = JSONLD_TYPE,
- updateOperationResult = UpdateOperationResult.FAILED,
- errorMessage = "A type cannot be removed from an entity: $removedTypes have been removed"
+ updateOperationResult = UpdateOperationResult.APPENDED
)
)
)
- }
-
- val updatedPayload = entityPayload.payload.deserializeExpandedPayload()
- .mapValues {
- if (it.key == JSONLD_TYPE)
- newTypes
- else it
- }
-
- databaseClient.sql(
- """
- UPDATE entity_payload
- SET types = :types,
- modified_at = :modified_at,
- payload = :payload
- WHERE entity_id = :entity_id
- """.trimIndent()
- )
- .bind("entity_id", entityId)
- .bind("types", newTypes.toTypedArray())
- .bind("modified_at", modifiedAt)
- .bind("payload", Json.of(serializeObject(updatedPayload)))
- .execute()
- .map {
- updateResultFromDetailedResult(
- listOf(
- UpdateAttributeResult(
- attributeName = JSONLD_TYPE,
- updateOperationResult = UpdateOperationResult.APPENDED
- )
- )
- )
- }.bind()
- }
+ }.bind()
+ }
@Transactional
suspend fun appendAttributes(
@@ -483,7 +467,7 @@ class EntityPayloadService(
): Either = either {
val (coreAttrs, otherAttrs) =
expandedAttributes.toList().partition { JSONLD_EXPANDED_ENTITY_SPECIFIC_MEMBERS.contains(it.first) }
- val createdAt = ZonedDateTime.now(ZoneOffset.UTC)
+ val createdAt = ngsiLdDateTime()
val operationType =
if (disallowOverwrite) APPEND_ATTRIBUTES
@@ -512,50 +496,48 @@ class EntityPayloadService(
entityUri: URI,
expandedAttributes: ExpandedAttributes,
sub: Sub?
- ): Either =
- either {
- val (coreAttrs, otherAttrs) =
- expandedAttributes.toList().partition { JSONLD_EXPANDED_ENTITY_SPECIFIC_MEMBERS.contains(it.first) }
- val createdAt = ZonedDateTime.now(ZoneOffset.UTC)
+ ): Either = either {
+ val (coreAttrs, otherAttrs) =
+ expandedAttributes.toList().partition { JSONLD_EXPANDED_ENTITY_SPECIFIC_MEMBERS.contains(it.first) }
+ val createdAt = ngsiLdDateTime()
- val coreUpdateResult = updateCoreAttributes(entityUri, coreAttrs, createdAt, UPDATE_ATTRIBUTES).bind()
- val attrsUpdateResult = temporalEntityAttributeService.updateEntityAttributes(
- entityUri,
- otherAttrs.toMap().toNgsiLdAttributes().bind(),
- expandedAttributes,
- createdAt,
- sub
- ).bind()
-
- val updateResult = coreUpdateResult.mergeWith(attrsUpdateResult)
- // update modifiedAt in entity if at least one attribute has been added
- if (updateResult.hasSuccessfulUpdate()) {
- val teas = temporalEntityAttributeService.getForEntity(entityUri, emptySet())
- updateState(entityUri, createdAt, teas).bind()
- }
- updateResult
+ val coreUpdateResult = updateCoreAttributes(entityUri, coreAttrs, createdAt, UPDATE_ATTRIBUTES).bind()
+ val attrsUpdateResult = temporalEntityAttributeService.updateEntityAttributes(
+ entityUri,
+ otherAttrs.toMap().toNgsiLdAttributes().bind(),
+ expandedAttributes,
+ createdAt,
+ sub
+ ).bind()
+
+ val updateResult = coreUpdateResult.mergeWith(attrsUpdateResult)
+ // update modifiedAt in entity if at least one attribute has been added
+ if (updateResult.hasSuccessfulUpdate()) {
+ val teas = temporalEntityAttributeService.getForEntity(entityUri, emptySet())
+ updateState(entityUri, createdAt, teas).bind()
}
+ updateResult
+ }
@Transactional
suspend fun partialUpdateAttribute(
entityId: URI,
expandedAttribute: ExpandedAttribute,
sub: Sub?
- ): Either =
- either {
- val modifiedAt = ZonedDateTime.now(ZoneOffset.UTC)
- val updateResult = temporalEntityAttributeService.partialUpdateEntityAttribute(
- entityId,
- expandedAttribute,
- modifiedAt,
- sub
- ).bind()
- if (updateResult.isSuccessful()) {
- val teas = temporalEntityAttributeService.getForEntity(entityId, emptySet())
- updateState(entityId, modifiedAt, teas).bind()
- }
- updateResult
+ ): Either = either {
+ val modifiedAt = ngsiLdDateTime()
+ val updateResult = temporalEntityAttributeService.partialUpdateEntityAttribute(
+ entityId,
+ expandedAttribute,
+ modifiedAt,
+ sub
+ ).bind()
+ if (updateResult.isSuccessful()) {
+ val teas = temporalEntityAttributeService.getForEntity(entityId, emptySet())
+ updateState(entityId, modifiedAt, teas).bind()
}
+ updateResult
+ }
@Transactional
suspend fun upsertAttributes(
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityTypeService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityTypeService.kt
index b8369eda1..40b6c1092 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityTypeService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityTypeService.kt
@@ -58,11 +58,11 @@ class EntityTypeService(
val result = databaseClient.sql(
"""
WITH entities AS (
- SELECT entity_id
+ SELECT entity_id
FROM entity_payload
WHERE :type_name = any (types)
)
- SELECT attribute_name, attribute_type, count(distinct(entity_id)) as count_entity
+ SELECT attribute_name, attribute_type, (select count(entity_id) from entities) as entity_count
FROM temporal_entity_attribute
WHERE entity_id IN (SELECT entity_id FROM entities)
GROUP BY attribute_name, attribute_type
@@ -77,7 +77,7 @@ class EntityTypeService(
return EntityTypeInfo(
id = toUri(typeName),
typeName = compactTerm(typeName, contexts),
- entityCount = toInt(result.first()["count_entity"]),
+ entityCount = toInt(result.first()["entity_count"]),
attributeDetails = result.map {
AttributeInfo(
id = toUri(it["attribute_name"]),
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt
index 2e3559ea9..33b490858 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt
@@ -13,6 +13,7 @@ import com.egm.stellio.shared.model.*
import com.egm.stellio.shared.util.*
import com.egm.stellio.shared.util.AttributeType
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE
+import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUE
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_OBSERVED_AT_PROPERTY
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PREFIX
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_OBJECT
@@ -212,41 +213,40 @@ class TemporalEntityAttributeService(
createdAt: ZonedDateTime,
attributePayload: ExpandedAttributeInstance,
sub: Sub?
- ): Either =
- either {
- logger.debug(
- "Replacing attribute {} ({}) in entity {}",
- ngsiLdAttribute.name,
- attributeMetadata.datasetId,
- temporalEntityAttribute.entityId
- )
- updateOnReplace(
- temporalEntityAttribute.id,
- attributeMetadata,
- createdAt,
- serializeObject(attributePayload)
- ).bind()
+ ): Either = either {
+ logger.debug(
+ "Replacing attribute {} ({}) in entity {}",
+ ngsiLdAttribute.name,
+ attributeMetadata.datasetId,
+ temporalEntityAttribute.entityId
+ )
+ updateOnReplace(
+ temporalEntityAttribute.id,
+ attributeMetadata,
+ createdAt,
+ serializeObject(attributePayload)
+ ).bind()
+
+ val attributeInstance = AttributeInstance(
+ temporalEntityAttribute = temporalEntityAttribute.id,
+ timeProperty = AttributeInstance.TemporalProperty.MODIFIED_AT,
+ time = createdAt,
+ attributeMetadata = attributeMetadata,
+ payload = attributePayload,
+ sub = sub
+ )
+ attributeInstanceService.create(attributeInstance).bind()
- val attributeInstance = AttributeInstance(
+ if (attributeMetadata.observedAt != null) {
+ val attributeObservedAtInstance = AttributeInstance(
temporalEntityAttribute = temporalEntityAttribute.id,
- timeProperty = AttributeInstance.TemporalProperty.MODIFIED_AT,
- time = createdAt,
+ time = attributeMetadata.observedAt,
attributeMetadata = attributeMetadata,
- payload = attributePayload,
- sub = sub
+ payload = attributePayload
)
- attributeInstanceService.create(attributeInstance).bind()
-
- if (attributeMetadata.observedAt != null) {
- val attributeObservedAtInstance = AttributeInstance(
- temporalEntityAttribute = temporalEntityAttribute.id,
- time = attributeMetadata.observedAt,
- attributeMetadata = attributeMetadata,
- payload = attributePayload
- )
- attributeInstanceService.create(attributeObservedAtInstance).bind()
- }
+ attributeInstanceService.create(attributeObservedAtInstance).bind()
}
+ }
@Transactional
suspend fun mergeAttribute(
@@ -857,6 +857,12 @@ class TemporalEntityAttributeService(
null,
WKTCoordinates(attributePayload.getPropertyValue()!! as String)
)
+ TemporalEntityAttribute.AttributeType.JsonProperty ->
+ Triple(
+ serializeObject(attributePayload.getMemberValue(NGSILD_JSONPROPERTY_VALUE)!!),
+ null,
+ null
+ )
}
suspend fun mergeAttributePayload(
@@ -865,11 +871,21 @@ class TemporalEntityAttributeService(
): Pair {
val jsonSourceObject = JSONObject(tea.payload.asString())
val jsonUpdateObject = JSONObject(expandedAttributeInstance)
+ // if the attribute is a JsonProperty, preserve its JSON value to avoid it being merged
+ // (the whole JSON value shall be replaced)
+ val preservedJsonValue = if (tea.attributeType == TemporalEntityAttribute.AttributeType.JsonProperty)
+ expandedAttributeInstance[NGSILD_JSONPROPERTY_VALUE]
+ else null
val jsonMerger = JsonMerger(
arrayMergeMode = JsonMerger.ArrayMergeMode.REPLACE_ARRAY,
objectMergeMode = JsonMerger.ObjectMergeMode.MERGE_OBJECT
)
val jsonTargetObject = jsonMerger.merge(jsonSourceObject, jsonUpdateObject)
+ .let {
+ if (preservedJsonValue != null)
+ it.put(NGSILD_JSONPROPERTY_VALUE, preservedJsonValue)
+ else it
+ }
val updatedAttributeInstance = jsonTargetObject.toMap() as ExpandedAttributeInstance
return Pair(jsonTargetObject, updatedAttributeInstance)
}
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt
index a7252aa04..20a3cc80e 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt
@@ -59,6 +59,12 @@ fun NgsiLdAttributeInstance.toTemporalAttributeMetadata(): Either
+ Triple(
+ TemporalEntityAttribute.AttributeType.JsonProperty,
+ AttributeValueType.JSON,
+ Triple(serializeObject(this.json), null, null)
+ )
}
if (attributeValue == Triple(null, null, null)) {
logger.warn("Unable to get a value from attribute: $this")
@@ -85,6 +91,7 @@ fun guessAttributeValueType(
guessPropertyValueType(expandedAttributeInstance.getPropertyValue()!!).first
TemporalEntityAttribute.AttributeType.Relationship -> AttributeValueType.URI
TemporalEntityAttribute.AttributeType.GeoProperty -> AttributeValueType.GEOMETRY
+ TemporalEntityAttribute.AttributeType.JsonProperty -> AttributeValueType.JSON
}
fun guessPropertyValueType(
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/DBAggregationUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/DBAggregationUtils.kt
index fbe6e4f56..c930c9597 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/util/DBAggregationUtils.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/DBAggregationUtils.kt
@@ -17,6 +17,7 @@ fun aggrMethodToSqlAggregate(
AttributeValueType.TIME -> sqlAggregateForTime(aggregate)
AttributeValueType.URI -> sqlAggregateForURI(aggregate)
AttributeValueType.GEOMETRY -> "null"
+ AttributeValueType.JSON -> "null"
}
fun sqlAggregationForJsonString(aggregate: TemporalQuery.Aggregate): String = when (aggregate) {
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/DBConversionUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/DBConversionUtils.kt
index fd44e28fa..d81b48770 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/util/DBConversionUtils.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/DBConversionUtils.kt
@@ -1,7 +1,9 @@
package com.egm.stellio.search.util
+import com.egm.stellio.shared.model.ExpandedAttributeInstance
import com.egm.stellio.shared.util.JsonUtils.deserializeAsMap
import com.egm.stellio.shared.util.JsonUtils.deserializeExpandedPayload
+import com.egm.stellio.shared.util.JsonUtils.serializeObject
import com.egm.stellio.shared.util.toUri
import io.r2dbc.postgresql.codec.Json
import java.net.URI
@@ -29,3 +31,5 @@ fun toInt(entry: Any?): Int = (entry as Long).toInt()
fun Json.deserializeExpandedPayload(): Map> = this.asString().deserializeExpandedPayload()
fun Json.deserializeAsMap(): Map = this.asString().deserializeAsMap()
+
+fun ExpandedAttributeInstance.toJson(): Json = Json.of(serializeObject(this))
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt
index f606d6ac6..c64e3980f 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt
@@ -5,20 +5,20 @@ import com.egm.stellio.search.scope.TemporalScopeBuilder
import com.egm.stellio.shared.model.ExpandedEntity
import com.egm.stellio.shared.model.ExpandedTerm
import com.egm.stellio.shared.util.AuthContextModel.AUTH_PROP_SUB
+import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_JSON
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE_TERM
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATASET_ID_PROPERTY
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_TYPE
-import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_VALUES
+import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUE
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PREFIX
-import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_VALUES
-import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_OBJECTS
import com.egm.stellio.shared.util.JsonLdUtils.buildExpandedPropertyValue
import com.egm.stellio.shared.util.JsonLdUtils.buildExpandedTemporalValue
import com.egm.stellio.shared.util.JsonLdUtils.buildNonReifiedPropertyValue
import com.egm.stellio.shared.util.JsonLdUtils.buildNonReifiedTemporalValue
-import com.egm.stellio.shared.util.JsonUtils
+import com.egm.stellio.shared.util.JsonUtils.deserializeListOfObjects
+import com.egm.stellio.shared.util.JsonUtils.deserializeObject
import com.egm.stellio.shared.util.wktToGeoJson
typealias SimplifiedTemporalAttribute = Map
@@ -74,7 +74,7 @@ object TemporalEntityBuilder {
mergeFullTemporalAttributesOnAttributeName(attributeAndResultsMap)
.mapValues { (_, attributeInstanceResults) ->
attributeInstanceResults.map { attributeInstanceResult ->
- JsonUtils.deserializeObject(attributeInstanceResult.payload)
+ deserializeObject(attributeInstanceResult.payload)
.let { instancePayload ->
injectSub(temporalEntitiesQuery, attributeInstanceResult, instancePayload)
}.let { instancePayload ->
@@ -121,19 +121,33 @@ object TemporalEntityBuilder {
it.key.datasetId?.let { datasetId ->
attributeInstance[NGSILD_DATASET_ID_PROPERTY] = buildNonReifiedPropertyValue(datasetId.toString())
}
- val valuesKey =
- when (it.key.attributeType) {
- TemporalEntityAttribute.AttributeType.Property -> NGSILD_PROPERTY_VALUES
- TemporalEntityAttribute.AttributeType.Relationship -> NGSILD_RELATIONSHIP_OBJECTS
- TemporalEntityAttribute.AttributeType.GeoProperty -> NGSILD_GEOPROPERTY_VALUES
- }
+ val valuesKey = it.key.attributeType.toSimpliedRepresentationKey()
attributeInstance[valuesKey] =
buildExpandedTemporalValue(it.value) { attributeInstanceResult ->
attributeInstanceResult as SimplifiedAttributeInstanceResult
- listOf(
- mapOf(JSONLD_VALUE to attributeInstanceResult.value),
- mapOf(JSONLD_VALUE to attributeInstanceResult.time)
- )
+ if (it.key.attributeType == TemporalEntityAttribute.AttributeType.JsonProperty) {
+ // flaky way to know if the serialized value is a JSON object or an array of JSON objects
+ val deserializedJsonValue: Any =
+ if ((attributeInstanceResult.value as String).startsWith("["))
+ deserializeListOfObjects(attributeInstanceResult.value)
+ else deserializeObject(attributeInstanceResult.value)
+ listOf(
+ mapOf(
+ NGSILD_JSONPROPERTY_VALUE to listOf(
+ mapOf(
+ JSONLD_TYPE to JSONLD_JSON,
+ JSONLD_VALUE to deserializedJsonValue
+ )
+ )
+ ),
+ mapOf(JSONLD_VALUE to attributeInstanceResult.time)
+ )
+ } else {
+ listOf(
+ mapOf(JSONLD_VALUE to attributeInstanceResult.value),
+ mapOf(JSONLD_VALUE to attributeInstanceResult.time)
+ )
+ }
}
attributeInstance.toMap()
}
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityHandler.kt
index 161a45669..610538e05 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityHandler.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityHandler.kt
@@ -67,8 +67,7 @@ class EntityHandler(
entityEventService.publishEntityCreateEvent(
sub.getOrNull(),
ngsiLdEntity.id,
- ngsiLdEntity.types,
- contexts
+ ngsiLdEntity.types
)
ResponseEntity.status(HttpStatus.CREATED)
@@ -115,8 +114,7 @@ class EntityHandler(
entityId,
expandedAttributes,
updateResult,
- true,
- contexts
+ true
)
}
@@ -165,8 +163,7 @@ class EntityHandler(
entityEventService.publishEntityReplaceEvent(
sub.getOrNull(),
ngsiLdEntity.id,
- ngsiLdEntity.types,
- contexts
+ ngsiLdEntity.types
)
ResponseEntity.status(HttpStatus.NO_CONTENT).build()
@@ -277,7 +274,7 @@ class EntityHandler(
entityPayloadService.deleteEntity(entityId).bind()
authorizationService.removeRightsOnEntity(entityId).bind()
- entityEventService.publishEntityDeleteEvent(sub.getOrNull(), entity, entity.contexts)
+ entityEventService.publishEntityDeleteEvent(sub.getOrNull(), entity)
ResponseEntity.status(HttpStatus.NO_CONTENT).build()
}.fold(
@@ -319,8 +316,7 @@ class EntityHandler(
entityId,
expandedAttributes,
updateResult,
- true,
- contexts
+ true
)
}
@@ -369,8 +365,7 @@ class EntityHandler(
entityId,
expandedAttributes,
updateResult,
- true,
- contexts
+ true
)
}
@@ -426,8 +421,7 @@ class EntityHandler(
entityId,
expandedAttribute.toExpandedAttributes(),
it,
- false,
- contexts
+ false
)
ResponseEntity.status(HttpStatus.NO_CONTENT).build().right()
@@ -473,8 +467,7 @@ class EntityHandler(
entityId,
expandedAttrId,
datasetId,
- deleteAll,
- contexts
+ deleteAll
)
ResponseEntity.status(HttpStatus.NO_CONTENT).build()
}.fold(
@@ -514,8 +507,7 @@ class EntityHandler(
entityId,
expandedAttribute.toExpandedAttributes(),
it,
- false,
- contexts
+ false
)
ResponseEntity.status(HttpStatus.NO_CONTENT).build().right()
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt
index 896c5f75d..4a145e974 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt
@@ -244,8 +244,7 @@ class EntityOperationHandler(
val entity = entitiesBeforeDelete.find { it.entityId == uri }!!
entityEventService.publishEntityDeleteEvent(
sub.getOrNull(),
- entity,
- entity.contexts
+ entity
)
}
@@ -354,8 +353,7 @@ class EntityOperationHandler(
entityEventService.publishEntityCreateEvent(
sub.getOrNull(),
ngsiLdEntity.id,
- ngsiLdEntity.types,
- ngsiLdEntity.contexts
+ ngsiLdEntity.types
)
}
batchOperationResult.errors.addAll(createOperationResult.errors)
@@ -372,8 +370,7 @@ class EntityOperationHandler(
entityEventService.publishEntityReplaceEvent(
sub,
it.entityId(),
- it.second.types,
- it.second.contexts
+ it.second.types
)
}
@@ -383,7 +380,7 @@ class EntityOperationHandler(
jsonLdNgsiLdEntities: List
) {
updateBatchOperationResult.success.forEach {
- val (jsonLdEntity, ngsiLdEntity) = jsonLdNgsiLdEntities.find { jsonLdNgsiLdEntity ->
+ val (jsonLdEntity, _) = jsonLdNgsiLdEntities.find { jsonLdNgsiLdEntity ->
jsonLdNgsiLdEntity.entityId() == it.entityId
}!!
entityEventService.publishAttributeChangeEvents(
@@ -391,8 +388,7 @@ class EntityOperationHandler(
it.entityId,
jsonLdEntity.members,
it.updateResult!!,
- true,
- ngsiLdEntity.contexts
+ true
)
}
}
diff --git a/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt b/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt
index bd1f1d7dc..abca87bd7 100644
--- a/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt
+++ b/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt
@@ -207,8 +207,9 @@ class V0_29__JsonLd_migration : BaseJavaMigration() {
is Either.Left ->
logger.warn("Unable to process attribute $attributeName ($datasetId) from entity $entityId")
is Either.Right -> {
- val createdAt = ngsiLdAttributeInstance.createdAt ?: defaultCreatedAt
- val modifiedAt = ngsiLdAttributeInstance.modifiedAt
+ val createdAt =
+ attributePayload.getMemberValueAsDateTime(NGSILD_CREATED_AT_PROPERTY) ?: defaultCreatedAt
+ val modifiedAt = attributePayload.getMemberValueAsDateTime(NGSILD_MODIFIED_AT_PROPERTY)
val atributeType = temporalAttributesMetadata.value.type
val attributeValueType = temporalAttributesMetadata.value.valueType
val serializedAttributePayload = serializeObject(attributePayload)
@@ -259,14 +260,16 @@ class V0_29__JsonLd_migration : BaseJavaMigration() {
ngsiLdAttributeInstance: NgsiLdAttributeInstance,
defaultCreatedAt: ZonedDateTime
) {
- val createdAt = ngsiLdAttributeInstance.createdAt ?: defaultCreatedAt
- val modifiedAt = ngsiLdAttributeInstance.modifiedAt
+ val createdAt =
+ attributePayload.getMemberValueAsDateTime(NGSILD_CREATED_AT_PROPERTY) ?: defaultCreatedAt
+ val modifiedAt = attributePayload.getMemberValueAsDateTime(NGSILD_MODIFIED_AT_PROPERTY)
val serializedAttributePayload = serializeObject(attributePayload)
val valueType = when (ngsiLdAttributeInstance) {
is NgsiLdPropertyInstance -> guessPropertyValueType(ngsiLdAttributeInstance).first
is NgsiLdRelationshipInstance -> TemporalEntityAttribute.AttributeValueType.URI
is NgsiLdGeoPropertyInstance -> TemporalEntityAttribute.AttributeValueType.GEOMETRY
+ is NgsiLdJsonPropertyInstance -> TemporalEntityAttribute.AttributeValueType.OBJECT
}
jdbcTemplate.execute(
diff --git a/search-service/src/main/resources/db/migration/V0_39__remove_contexts_column_from_entity_payload.sql b/search-service/src/main/resources/db/migration/V0_39__remove_contexts_column_from_entity_payload.sql
new file mode 100644
index 000000000..fb2d78a20
--- /dev/null
+++ b/search-service/src/main/resources/db/migration/V0_39__remove_contexts_column_from_entity_payload.sql
@@ -0,0 +1,2 @@
+alter table entity_payload
+ drop column contexts;
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/listener/ObservationEventListenerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/listener/ObservationEventListenerTests.kt
index e7d289095..ff5c18f1b 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/listener/ObservationEventListenerTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/listener/ObservationEventListenerTests.kt
@@ -9,7 +9,10 @@ import com.egm.stellio.search.service.EntityEventService
import com.egm.stellio.search.service.EntityPayloadService
import com.egm.stellio.shared.model.ExpandedEntity
import com.egm.stellio.shared.model.NgsiLdEntity
-import com.egm.stellio.shared.util.*
+import com.egm.stellio.shared.util.BEEHIVE_TYPE
+import com.egm.stellio.shared.util.TEMPERATURE_PROPERTY
+import com.egm.stellio.shared.util.loadSampleData
+import com.egm.stellio.shared.util.toUri
import com.ninjasquad.springmockk.MockkBean
import io.mockk.*
import kotlinx.coroutines.Job
@@ -43,7 +46,7 @@ class ObservationEventListenerTests {
coEvery {
entityPayloadService.createEntity(any(), any(), any())
} returns Unit.right()
- coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) } returns Job()
+ coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any()) } returns Job()
observationEventListener.dispatchObservationMessage(observationEvent)
@@ -59,8 +62,7 @@ class ObservationEventListenerTests {
entityEventService.publishEntityCreateEvent(
eq("0123456789-1234-5678-987654321"),
eq(expectedEntityId),
- eq(listOf(BEEHIVE_TYPE)),
- eq(APIC_COMPOUND_CONTEXTS)
+ eq(listOf(BEEHIVE_TYPE))
)
}
}
@@ -83,7 +85,7 @@ class ObservationEventListenerTests {
).right()
coEvery {
- entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any(), any())
+ entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any())
} returns Job()
observationEventListener.dispatchObservationMessage(observationEvent)
@@ -106,8 +108,7 @@ class ObservationEventListenerTests {
it.updated[0].datasetId == expectedTemperatureDatasetId &&
it.updated[0].updateOperationResult == UpdateOperationResult.UPDATED
},
- eq(false),
- eq(APIC_COMPOUND_CONTEXTS)
+ eq(false)
)
}
}
@@ -147,7 +148,7 @@ class ObservationEventListenerTests {
val mockedExpandedEntity = mockkClass(ExpandedEntity::class, relaxed = true)
every { mockedExpandedEntity.types } returns listOf(BEEHIVE_TYPE)
coEvery {
- entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any(), any())
+ entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any())
} returns Job()
observationEventListener.dispatchObservationMessage(observationEvent)
@@ -173,8 +174,7 @@ class ObservationEventListenerTests {
it.updated[0].attributeName == TEMPERATURE_PROPERTY &&
it.updated[0].datasetId == expectedTemperatureDatasetId
},
- eq(true),
- eq(APIC_COMPOUND_CONTEXTS)
+ eq(true)
)
}
}
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/model/EntityModelTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/model/EntityModelTests.kt
index 7ecf93b3f..89d30032a 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/model/EntityModelTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/model/EntityModelTests.kt
@@ -1,7 +1,10 @@
package com.egm.stellio.search.model
import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD
-import com.egm.stellio.shared.util.*
+import com.egm.stellio.shared.util.AuthContextModel
+import com.egm.stellio.shared.util.BEEHIVE_TYPE
+import com.egm.stellio.shared.util.JsonLdUtils
+import com.egm.stellio.shared.util.toUri
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
@@ -17,8 +20,7 @@ class EntityModelTests {
types = listOf(BEEHIVE_TYPE),
createdAt = now,
modifiedAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = NGSILD_TEST_CORE_CONTEXTS
+ payload = EMPTY_JSON_PAYLOAD
)
@Test
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/scope/TemporalScopeBuilderTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/scope/TemporalScopeBuilderTests.kt
index c5467fd66..03492fdb7 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/scope/TemporalScopeBuilderTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/scope/TemporalScopeBuilderTests.kt
@@ -1,12 +1,14 @@
package com.egm.stellio.search.scope
import com.egm.stellio.search.model.AttributeInstance.TemporalProperty
-import com.egm.stellio.search.model.EntityPayload
import com.egm.stellio.search.model.TemporalEntitiesQuery
import com.egm.stellio.search.model.TemporalQuery
-import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD
import com.egm.stellio.search.support.buildDefaultQueryParams
-import com.egm.stellio.shared.util.*
+import com.egm.stellio.search.support.gimmeEntityPayload
+import com.egm.stellio.shared.util.JsonUtils
+import com.egm.stellio.shared.util.assertJsonPayloadsAreEqual
+import com.egm.stellio.shared.util.loadSampleData
+import com.egm.stellio.shared.util.toUri
import org.junit.jupiter.api.Test
import org.springframework.test.context.ActiveProfiles
import java.time.Instant
@@ -16,13 +18,11 @@ import java.time.ZonedDateTime
@ActiveProfiles("test")
class TemporalScopeBuilderTests {
- private val now = ngsiLdDateTime()
-
private val entityId = "urn:ngsi-ld:Beehive:1234".toUri()
@Test
fun `it should build an aggregated temporal representation of scopes`() {
- val entityPayload = gimmeEntityPayload()
+ val entityPayload = gimmeEntityPayload(entityId)
val scopeInstances = listOf(
AggregatedScopeInstanceResult(
entityId = entityId,
@@ -86,7 +86,7 @@ class TemporalScopeBuilderTests {
@Test
fun `it should build a temporal values representation of scopes`() {
- val entityPayload = gimmeEntityPayload()
+ val entityPayload = gimmeEntityPayload(entityId)
val scopeInstances = listOf(
SimplifiedScopeInstanceResult(
entityId = entityId,
@@ -124,7 +124,7 @@ class TemporalScopeBuilderTests {
@Test
fun `it should build a full representation of scopes`() {
- val entityPayload = gimmeEntityPayload()
+ val entityPayload = gimmeEntityPayload(entityId)
val scopeInstances = listOf(
FullScopeInstanceResult(
entityId = entityId,
@@ -161,13 +161,4 @@ class TemporalScopeBuilderTests {
JsonUtils.serializeObject(scopeHistory)
)
}
-
- private fun gimmeEntityPayload(): EntityPayload =
- EntityPayload(
- entityId = entityId,
- types = listOf(BEEHIVE_TYPE),
- createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = APIC_COMPOUND_CONTEXTS
- )
}
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/AggregatedQueryServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/AggregatedQueryServiceTests.kt
index 3510dce8e..6e2f1842b 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/service/AggregatedQueryServiceTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/AggregatedQueryServiceTests.kt
@@ -71,7 +71,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
fun `it should correctly aggregate on JSON Number values`(aggrMethod: String, expectedValue: String) = runTest {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.NUMBER)
(1..10).forEach { i ->
- val attributeInstance = gimmeAttributeInstance(teaUuid)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(measuredValue = i.toDouble())
attributeInstanceService.create(attributeInstance)
}
@@ -100,7 +100,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
fun `it should correctly aggregate on JSON String values`(aggrMethod: String, expectedValue: String?) = runTest {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.STRING)
(1..10).forEach { i ->
- val attributeInstance = gimmeAttributeInstance(teaUuid)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(measuredValue = null, value = "a$i")
attributeInstanceService.create(attributeInstance)
}
@@ -129,7 +129,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
fun `it should correctly aggregate on JSON Object values`(aggrMethod: String, expectedValue: String?) = runTest {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.OBJECT)
(1..10).forEach { i ->
- val attributeInstance = gimmeAttributeInstance(teaUuid)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(
measuredValue = null,
value = """
@@ -165,7 +165,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
fun `it should correctly aggregate on JSON Array values`(aggrMethod: String, expectedValue: String?) = runTest {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.ARRAY)
(1..10).forEach { i ->
- val attributeInstance = gimmeAttributeInstance(teaUuid)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(
measuredValue = null,
value = """
@@ -199,7 +199,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
fun `it should correctly aggregate on JSON Boolean values`(aggrMethod: String, expectedValue: String?) = runTest {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.BOOLEAN)
(1..10).forEach { i ->
- val attributeInstance = gimmeAttributeInstance(teaUuid)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(
measuredValue = null,
value = if (i % 2 == 0) "true" else "false"
@@ -232,7 +232,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.DATETIME)
val baseDateTime = ZonedDateTime.parse("2023-03-05T00:01:01Z")
(1..10).forEach { i ->
- val attributeInstance = gimmeAttributeInstance(teaUuid)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(
measuredValue = null,
value = baseDateTime.plusHours(i.toLong()).toString()
@@ -265,7 +265,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.DATE)
val baseDateTime = LocalDate.parse("2023-03-05")
(1..10).forEach { i ->
- val attributeInstance = gimmeAttributeInstance(teaUuid)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(
measuredValue = null,
value = baseDateTime.plusDays(i.toLong()).toString()
@@ -298,7 +298,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.TIME)
val baseDateTime = OffsetTime.parse("00:00:01Z")
(1..10).forEach { i ->
- val attributeInstance = gimmeAttributeInstance(teaUuid)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(
measuredValue = null,
value = baseDateTime.plusHours(i.toLong()).toString()
@@ -330,7 +330,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
fun `it should correctly aggregate on URI values`(aggrMethod: String, expectedValue: String?) = runTest {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.URI)
(1..10).forEach { i ->
- val attributeInstance = gimmeAttributeInstance(teaUuid)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(
measuredValue = null,
value = "urn:ngsi-ld:Entity:$i"
@@ -352,7 +352,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
fun `it should aggregate on the whole time range if no aggrPeriodDuration is given`() = runTest {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.NUMBER)
(1..10).forEach { i ->
- val attributeInstance = gimmeAttributeInstance(teaUuid)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(measuredValue = i.toDouble())
attributeInstanceService.create(attributeInstance)
}
@@ -389,7 +389,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.NUMBER)
val startTimestamp = ZonedDateTime.parse("2023-12-28T12:00:00Z")
(1..10).forEach { i ->
- val attributeInstance = gimmeAttributeInstance(teaUuid)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(time = startTimestamp.plusDays(i.toLong()))
attributeInstanceService.create(attributeInstance)
}
@@ -411,10 +411,10 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer {
fun `it should handle aggregates for an attribute having different types of values in history`() = runTest {
val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.ARRAY)
(1..10).forEach { i ->
- val attributeInstanceWithArrayValue = gimmeAttributeInstance(teaUuid)
+ val attributeInstanceWithArrayValue = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(measuredValue = null, value = "[ $i ]")
attributeInstanceService.create(attributeInstanceWithArrayValue)
- val attributeInstanceWithStringValue = gimmeAttributeInstance(teaUuid)
+ val attributeInstanceWithStringValue = gimmeNumericPropertyAttributeInstance(teaUuid)
.copy(measuredValue = null, value = "$i")
attributeInstanceService.create(attributeInstanceWithStringValue)
}
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt
index ef46b42c5..fb09f23c5 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt
@@ -11,6 +11,7 @@ import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATE_TIME_TYPE
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_INSTANCE_ID_PROPERTY
+import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUE
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_MODIFIED_AT_PROPERTY
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_OBSERVED_AT_PROPERTY
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_VALUE
@@ -55,6 +56,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
private lateinit var incomingTemporalEntityAttribute: TemporalEntityAttribute
private lateinit var outgoingTemporalEntityAttribute: TemporalEntityAttribute
+ private lateinit var jsonTemporalEntityAttribute: TemporalEntityAttribute
val entityId = "urn:ngsi-ld:BeeHive:TESTC".toUri()
@@ -83,6 +85,18 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
runBlocking {
temporalEntityAttributeService.create(outgoingTemporalEntityAttribute)
}
+
+ jsonTemporalEntityAttribute = TemporalEntityAttribute(
+ entityId = entityId,
+ attributeName = LUMINOSITY_JSONPROPERTY,
+ attributeValueType = TemporalEntityAttribute.AttributeValueType.JSON,
+ createdAt = now,
+ payload = SAMPLE_JSON_PROPERTY_PAYLOAD
+ )
+
+ runBlocking {
+ temporalEntityAttributeService.create(jsonTemporalEntityAttribute)
+ }
}
@AfterEach
@@ -100,7 +114,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should retrieve a full instance if temporalValues are not asked for`() = runTest {
- val observation = gimmeAttributeInstance(incomingTemporalEntityAttribute.id).copy(
+ val observation = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id).copy(
time = now,
measuredValue = 12.4
)
@@ -122,7 +136,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should retrieve an instance having the corresponding time property value`() = runTest {
- val observation = gimmeAttributeInstance(incomingTemporalEntityAttribute.id).copy(
+ val observation = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id).copy(
timeProperty = AttributeInstance.TemporalProperty.CREATED_AT,
time = now,
measuredValue = 12.4
@@ -145,7 +159,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should retrieve an instance with audit info if time property is not observedAt`() = runTest {
- val observation = gimmeAttributeInstance(incomingTemporalEntityAttribute.id).copy(
+ val observation = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id).copy(
timeProperty = AttributeInstance.TemporalProperty.CREATED_AT,
time = now,
measuredValue = 12.4,
@@ -170,7 +184,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should not retrieve an instance not having the corresponding time property value`() = runTest {
- val observation = gimmeAttributeInstance(incomingTemporalEntityAttribute.id).copy(
+ val observation = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id).copy(
timeProperty = AttributeInstance.TemporalProperty.CREATED_AT,
time = now,
measuredValue = 12.4
@@ -194,7 +208,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should retrieve all full instances if temporalValues are not asked for`() = runTest {
(1..10).forEach { _ ->
- attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id))
+ attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id))
}
val temporalEntitiesQuery = gimmeTemporalEntitiesQuery(
@@ -213,7 +227,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should retrieve all instances when no timerel and time parameters are provided`() = runTest {
(1..10).forEach { _ ->
- attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id))
+ attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id))
}
attributeInstanceService.search(gimmeTemporalEntitiesQuery(TemporalQuery()), incomingTemporalEntityAttribute)
@@ -274,7 +288,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
fun `it should set the start time to the oldest value if asking for no timerel`() = runTest {
(1..9).forEachIndexed { index, _ ->
val attributeInstance =
- gimmeAttributeInstance(incomingTemporalEntityAttribute.id)
+ gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)
.copy(
measuredValue = index.toDouble(),
time = ZonedDateTime.parse("2022-07-0${index + 1}T00:00:00Z")
@@ -301,7 +315,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should only return the last n instances asked in the temporal query`() = runTest {
(1..10).forEach { _ ->
- val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)
.copy(measuredValue = 1.0)
attributeInstanceService.create(attributeInstance)
}
@@ -333,10 +347,10 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
temporalEntityAttributeService.create(temporalEntityAttribute2)
(1..10).forEach { _ ->
- attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id))
+ attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id))
}
(1..5).forEach { _ ->
- attributeInstanceService.create(gimmeAttributeInstance(temporalEntityAttribute2.id))
+ attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(temporalEntityAttribute2.id))
}
val temporalEntitiesQuery = gimmeTemporalEntitiesQuery(
@@ -358,7 +372,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should not retrieve any instance if temporal entity does not match`() = runTest {
(1..10).forEach { _ ->
- attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id))
+ attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id))
}
val temporalEntitiesQuery = gimmeTemporalEntitiesQuery(
@@ -379,7 +393,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should not retrieve any instance if there is no value in the time interval`() = runTest {
(1..10).forEach { _ ->
- attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id))
+ attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id))
}
val temporalEntitiesQuery = gimmeTemporalEntitiesQuery(
@@ -397,7 +411,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should update an existing attribute instance with same observation date`() = runTest {
- val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)
attributeInstanceService.create(attributeInstance)
@@ -541,8 +555,8 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
}
@Test
- fun `it should modify attribute instance`() = runTest {
- val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id)
+ fun `it should modify attribute instance for a property`() = runTest {
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)
attributeInstanceService.create(attributeInstance)
val instanceTemporalFragment =
@@ -577,9 +591,48 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
}
}
+ @Test
+ fun `it should modify attribute instance for a JSON property`() = runTest {
+ val attributeInstance = gimmeJsonPropertyAttributeInstance(jsonTemporalEntityAttribute.id)
+ attributeInstanceService.create(attributeInstance)
+
+ val instanceTemporalFragment =
+ loadSampleData("fragments/temporal_instance_json_fragment.jsonld")
+ val attributeInstancePayload =
+ mapOf(LUMINOSITY_COMPACT_JSONPROPERTY to instanceTemporalFragment.deserializeAsMap())
+ val jsonLdAttribute = JsonLdUtils.expandJsonLdFragment(
+ attributeInstancePayload,
+ APIC_COMPOUND_CONTEXTS
+ ) as ExpandedAttributes
+
+ val temporalEntitiesQuery = gimmeTemporalEntitiesQuery(
+ TemporalQuery(
+ timerel = TemporalQuery.Timerel.AFTER,
+ timeAt = ZonedDateTime.parse("1970-01-01T00:00:00Z")
+ )
+ )
+
+ attributeInstanceService.modifyAttributeInstance(
+ entityId,
+ LUMINOSITY_JSONPROPERTY,
+ attributeInstance.instanceId,
+ jsonLdAttribute.entries.first().value
+ ).shouldSucceed()
+
+ attributeInstanceService.search(temporalEntitiesQuery, jsonTemporalEntityAttribute)
+ .shouldSucceedWith {
+ (it as List).single { result ->
+ result.time == ZonedDateTime.parse("2023-03-13T12:33:06Z") &&
+ result.payload.deserializeAsMap().containsKey(NGSILD_MODIFIED_AT_PROPERTY) &&
+ result.payload.deserializeAsMap().containsKey(NGSILD_INSTANCE_ID_PROPERTY) &&
+ result.payload.deserializeAsMap().containsKey(NGSILD_JSONPROPERTY_VALUE)
+ }
+ }
+ }
+
@Test
fun `it should delete attribute instance`() = runTest {
- val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)
attributeInstanceService.create(attributeInstance).shouldSucceed()
attributeInstanceService.deleteInstance(
@@ -597,7 +650,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should not delete attribute instance if attribute name is not found`() = runTest {
- val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)
attributeInstanceService.create(attributeInstance).shouldSucceed()
attributeInstanceService.deleteInstance(
@@ -618,7 +671,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
@Test
fun `it should not delete attribute instance if instanceID is not found`() = runTest {
- val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id)
+ val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)
val instanceId = "urn:ngsi-ld:Instance:notFound".toUri()
attributeInstanceService.create(attributeInstance).shouldSucceed()
@@ -642,10 +695,12 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
fun `it should delete all instances of an entity`() = runTest {
(1..10).forEachIndexed { index, _ ->
if (index % 2 == 0)
- attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id))
+ attributeInstanceService.create(
+ gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)
+ )
else
attributeInstanceService.create(
- gimmeAttributeInstance(
+ gimmeNumericPropertyAttributeInstance(
teaUuid = incomingTemporalEntityAttribute.id,
timeProperty = AttributeInstance.TemporalProperty.CREATED_AT
)
@@ -680,11 +735,11 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
(1..10).forEachIndexed { index, _ ->
if (index % 2 == 0)
attributeInstanceService.create(
- gimmeAttributeInstance(teaUuid = incomingTemporalEntityAttribute.id)
+ gimmeNumericPropertyAttributeInstance(teaUuid = incomingTemporalEntityAttribute.id)
)
else
attributeInstanceService.create(
- gimmeAttributeInstance(teaUuid = outgoingTemporalEntityAttribute.id)
+ gimmeNumericPropertyAttributeInstance(teaUuid = outgoingTemporalEntityAttribute.id)
)
}
@@ -707,11 +762,11 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer
(1..10).forEachIndexed { index, _ ->
if (index % 2 == 0)
attributeInstanceService.create(
- gimmeAttributeInstance(teaUuid = incomingTemporalEntityAttribute.id)
+ gimmeNumericPropertyAttributeInstance(teaUuid = incomingTemporalEntityAttribute.id)
)
else
attributeInstanceService.create(
- gimmeAttributeInstance(teaUuid = outgoingTemporalEntityAttribute.id)
+ gimmeNumericPropertyAttributeInstance(teaUuid = outgoingTemporalEntityAttribute.id)
)
}
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeServiceTests.kt
index 703e83ddd..324692660 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeServiceTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeServiceTests.kt
@@ -6,6 +6,7 @@ import com.egm.stellio.search.model.AttributeType
import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD
import com.egm.stellio.search.support.WithKafkaContainer
import com.egm.stellio.search.support.WithTimescaleContainer
+import com.egm.stellio.search.support.gimmeEntityPayload
import com.egm.stellio.search.util.execute
import com.egm.stellio.search.util.toUri
import com.egm.stellio.shared.model.APIException
@@ -43,9 +44,9 @@ class AttributeServiceTests : WithTimescaleContainer, WithKafkaContainer {
private val now = Instant.now().atZone(ZoneOffset.UTC)
- private val entityPayload1 = newEntityPayload("urn:ngsi-ld:BeeHive:TESTA", listOf(BEEHIVE_TYPE, SENSOR_TYPE))
- private val entityPayload2 = newEntityPayload("urn:ngsi-ld:Sensor:TESTB", listOf(SENSOR_TYPE))
- private val entityPayload3 = newEntityPayload("urn:ngsi-ld:Apiary:TESTC", listOf(APIARY_TYPE))
+ private val entityPayload1 = gimmeEntityPayload("urn:ngsi-ld:BeeHive:TESTA", listOf(BEEHIVE_TYPE, SENSOR_TYPE))
+ private val entityPayload2 = gimmeEntityPayload("urn:ngsi-ld:Sensor:TESTB", listOf(SENSOR_TYPE))
+ private val entityPayload3 = gimmeEntityPayload("urn:ngsi-ld:Apiary:TESTC", listOf(APIARY_TYPE))
private val temporalEntityAttribute1 = newTemporalEntityAttribute(
"urn:ngsi-ld:BeeHive:TESTA",
INCOMING_PROPERTY,
@@ -229,17 +230,4 @@ class AttributeServiceTests : WithTimescaleContainer, WithKafkaContainer {
.bind("types", entityPayload.types.toTypedArray())
.execute()
}
-
- private fun newEntityPayload(
- id: String,
- types: List,
- contexts: List = APIC_COMPOUND_CONTEXTS
- ): EntityPayload =
- EntityPayload(
- entityId = toUri(id),
- types = types,
- createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = contexts
- )
}
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityEventServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityEventServiceTests.kt
index 37e24a183..851f242ef 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityEventServiceTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityEventServiceTests.kt
@@ -7,10 +7,12 @@ import com.egm.stellio.search.model.UpdateResult
import com.egm.stellio.search.model.UpdatedDetails
import com.egm.stellio.search.support.EMPTY_PAYLOAD
import com.egm.stellio.shared.model.*
-import com.egm.stellio.shared.util.*
+import com.egm.stellio.shared.util.AQUAC_COMPOUND_CONTEXT
import com.egm.stellio.shared.util.JsonLdUtils.expandAttribute
import com.egm.stellio.shared.util.JsonLdUtils.expandAttributes
import com.egm.stellio.shared.util.JsonUtils.serializeObject
+import com.egm.stellio.shared.util.matchContent
+import com.egm.stellio.shared.util.toUri
import com.egm.stellio.shared.web.DEFAULT_TENANT_NAME
import com.ninjasquad.springmockk.MockkBean
import com.ninjasquad.springmockk.SpykBean
@@ -87,8 +89,7 @@ class EntityEventServiceTests {
entityEventService.publishEntityCreateEvent(
null,
breedingServiceUri,
- listOf(breedingServiceType),
- listOf(AQUAC_COMPOUND_CONTEXT)
+ listOf(breedingServiceType)
).join()
coVerify { entityEventService.getSerializedEntity(eq(breedingServiceUri)) }
@@ -105,8 +106,7 @@ class EntityEventServiceTests {
entityEventService.publishEntityReplaceEvent(
null,
breedingServiceUri,
- listOf(breedingServiceType),
- listOf(AQUAC_COMPOUND_CONTEXT)
+ listOf(breedingServiceType)
).join()
coVerify { entityEventService.getSerializedEntity(eq(breedingServiceUri)) }
@@ -120,11 +120,7 @@ class EntityEventServiceTests {
}
every { kafkaTemplate.send(any(), any(), any()) } returns CompletableFuture()
- entityEventService.publishEntityDeleteEvent(
- null,
- entityPayload,
- listOf(AQUAC_COMPOUND_CONTEXT)
- ).join()
+ entityEventService.publishEntityDeleteEvent(null, entityPayload).join()
verify { kafkaTemplate.send("cim.entity._CatchAll", breedingServiceUri.toString(), any()) }
}
@@ -145,8 +141,7 @@ class EntityEventServiceTests {
listOf(UpdatedDetails(fishNumberProperty, null, UpdateOperationResult.APPENDED)),
emptyList()
),
- true,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ true
).join()
verify {
@@ -159,7 +154,7 @@ class EntityEventServiceTests {
it.attributeName == fishNumberProperty &&
it.datasetId == null &&
it.operationPayload.matchContent(serializedAttributePayload(expandedAttribute)) &&
- it.contexts == listOf(AQUAC_COMPOUND_CONTEXT)
+ it.contexts.isEmpty()
}
)
}
@@ -181,8 +176,7 @@ class EntityEventServiceTests {
listOf(UpdatedDetails(fishNumberProperty, null, UpdateOperationResult.REPLACED)),
emptyList()
),
- true,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ true
).join()
verify {
@@ -195,7 +189,7 @@ class EntityEventServiceTests {
it.attributeName == fishNumberProperty &&
it.datasetId == null &&
it.operationPayload.matchContent(serializedAttributePayload(expandedAttribute)) &&
- it.contexts == listOf(AQUAC_COMPOUND_CONTEXT)
+ it.contexts.isEmpty()
}
)
}
@@ -229,8 +223,7 @@ class EntityEventServiceTests {
breedingServiceUri,
jsonLdAttributes,
appendResult,
- true,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ true
).join()
verify {
@@ -246,7 +239,7 @@ class EntityEventServiceTests {
it.operationPayload.matchContent(
serializedAttributePayload(jsonLdAttributes)
) &&
- it.contexts == listOf(AQUAC_COMPOUND_CONTEXT)
+ it.contexts.isEmpty()
}
is AttributeReplaceEvent ->
@@ -258,7 +251,7 @@ class EntityEventServiceTests {
it.operationPayload.matchContent(
serializedAttributePayload(jsonLdAttributes, fishNameProperty)
) &&
- it.contexts == listOf(AQUAC_COMPOUND_CONTEXT)
+ it.contexts.isEmpty()
}
else -> false
@@ -295,8 +288,7 @@ class EntityEventServiceTests {
breedingServiceUri,
jsonLdAttributes,
updateResult,
- true,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ true
).join()
verify {
@@ -314,7 +306,7 @@ class EntityEventServiceTests {
serializedAttributePayload(jsonLdAttributes, fishNameProperty)
)
) &&
- it.contexts == listOf(AQUAC_COMPOUND_CONTEXT)
+ it.contexts.isEmpty()
}
}
)
@@ -355,8 +347,7 @@ class EntityEventServiceTests {
breedingServiceUri,
jsonLdAttributes,
updateResult,
- true,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ true
).join()
verify {
@@ -376,7 +367,7 @@ class EntityEventServiceTests {
serializedAttributePayload(jsonLdAttributes, fishNameProperty, 1)
)
) &&
- it.contexts == listOf(AQUAC_COMPOUND_CONTEXT)
+ it.contexts.isEmpty()
}
}
)
@@ -404,8 +395,7 @@ class EntityEventServiceTests {
breedingServiceUri,
expandedAttribute.toExpandedAttributes(),
UpdateResult(updatedDetails, emptyList()),
- false,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ false
).join()
verify {
@@ -417,7 +407,7 @@ class EntityEventServiceTests {
it.attributeName == fishNameProperty &&
it.datasetId == fishName1DatasetUri &&
it.operationPayload.matchContent(serializedAttributePayload(expandedAttribute)) &&
- it.contexts == listOf(AQUAC_COMPOUND_CONTEXT)
+ it.contexts.isEmpty()
}
)
}
@@ -436,8 +426,7 @@ class EntityEventServiceTests {
breedingServiceUri,
fishNameProperty,
null,
- true,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ true
).join()
verify {
@@ -448,7 +437,7 @@ class EntityEventServiceTests {
it.entityId == breedingServiceUri &&
it.entityTypes == listOf(breedingServiceType) &&
it.attributeName == fishNameProperty &&
- it.contexts == listOf(AQUAC_COMPOUND_CONTEXT)
+ it.contexts.isEmpty()
}
}
)
@@ -467,8 +456,7 @@ class EntityEventServiceTests {
breedingServiceUri,
fishNameProperty,
fishName1DatasetUri,
- false,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ false
).join()
verify {
@@ -480,7 +468,7 @@ class EntityEventServiceTests {
it.entityTypes == listOf(breedingServiceType) &&
it.attributeName == fishNameProperty &&
it.datasetId == fishName1DatasetUri &&
- it.contexts == listOf(AQUAC_COMPOUND_CONTEXT)
+ it.contexts.isEmpty()
}
}
)
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityOperationServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityOperationServiceTests.kt
index 2bc9952fc..e424251a5 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityOperationServiceTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityOperationServiceTests.kt
@@ -271,6 +271,9 @@ class EntityOperationServiceTests {
@Test
fun `it should ask to replace entities`() = runTest {
+ coEvery {
+ temporalEntityAttributeService.deleteTemporalAttributesOfEntity(any())
+ } returns Unit.right()
coEvery {
entityPayloadService.appendAttributes(any(), any(), any(), any())
} returns EMPTY_UPDATE_RESULT.right()
@@ -302,6 +305,9 @@ class EntityOperationServiceTests {
@Test
fun `it should count as error an replace which raises a BadRequestDataException`() = runTest {
+ coEvery {
+ temporalEntityAttributeService.deleteTemporalAttributesOfEntity(any())
+ } returns Unit.right()
coEvery {
entityPayloadService.appendAttributes(firstEntityURI, any(), any(), any())
} returns EMPTY_UPDATE_RESULT.right()
@@ -326,6 +332,9 @@ class EntityOperationServiceTests {
@Test
fun `it should count as error not replaced entities in entities`() = runTest {
+ coEvery {
+ temporalEntityAttributeService.deleteTemporalAttributesOfEntity(any())
+ } returns Unit.right()
coEvery {
entityPayloadService.appendAttributes(firstEntityURI, any(), any(), any())
} returns EMPTY_UPDATE_RESULT.right()
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityPayloadServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityPayloadServiceTests.kt
index b8b2a055e..0264dc0bd 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityPayloadServiceTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityPayloadServiceTests.kt
@@ -157,7 +157,8 @@ class EntityPayloadServiceTests : WithTimescaleContainer, WithKafkaContainer {
entityPayloadService.retrieve(beehiveTestCId)
.shouldSucceedWith {
- assertEquals(APIC_COMPOUND_CONTEXTS, it.contexts)
+ assertEquals("urn:ngsi-ld:BeeHive:TESTC".toUri(), it.entityId)
+ assertEquals(listOf(BEEHIVE_TYPE), it.types)
}
coVerify {
@@ -437,7 +438,6 @@ class EntityPayloadServiceTests : WithTimescaleContainer, WithKafkaContainer {
.hasFieldOrPropertyWithValue("types", listOf(BEEHIVE_TYPE))
.hasFieldOrPropertyWithValue("createdAt", now)
.hasFieldOrPropertyWithValue("modifiedAt", null)
- .hasFieldOrPropertyWithValue("contexts", listOf(NGSILD_TEST_CORE_CONTEXT))
.hasFieldOrPropertyWithValue("specificAccessPolicy", null)
}
}
@@ -576,7 +576,7 @@ class EntityPayloadServiceTests : WithTimescaleContainer, WithKafkaContainer {
}
@Test
- fun `it should not add a type if existing types are not in the list of types to add`() = runTest {
+ fun `it should add a type to an entity even if existing types are not in the list of types to add`() = runTest {
loadMinimalEntity(entity01Uri, setOf(BEEHIVE_TYPE))
.sampleDataToNgsiLdEntity()
.map {
@@ -586,16 +586,15 @@ class EntityPayloadServiceTests : WithTimescaleContainer, WithKafkaContainer {
now
)
}
-
entityPayloadService.updateTypes(entity01Uri, listOf(APIARY_TYPE), ngsiLdDateTime(), false)
+ .shouldSucceed()
+
+ entityPayloadService.retrieve(entity01Uri)
.shouldSucceedWith {
- assertFalse(it.isSuccessful())
- assertEquals(1, it.notUpdated.size)
- val notUpdatedDetails = it.notUpdated[0]
- assertEquals(JSONLD_TYPE, notUpdatedDetails.attributeName)
+ assertEquals(listOf(BEEHIVE_TYPE, APIARY_TYPE), it.types)
assertEquals(
- "A type cannot be removed from an entity: [$BEEHIVE_TYPE] have been removed",
- notUpdatedDetails.reason
+ listOf(BEEHIVE_TYPE, APIARY_TYPE),
+ it.payload.asString().deserializeExpandedPayload()[JSONLD_TYPE]
)
}
}
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt
index fea6cb905..77fcdf6d0 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt
@@ -6,6 +6,7 @@ import com.egm.stellio.search.model.AttributeType
import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD
import com.egm.stellio.search.support.WithKafkaContainer
import com.egm.stellio.search.support.WithTimescaleContainer
+import com.egm.stellio.search.support.gimmeEntityPayload
import com.egm.stellio.search.util.execute
import com.egm.stellio.search.util.toUri
import com.egm.stellio.shared.model.APIException
@@ -15,9 +16,9 @@ import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LOCATION_PROPERTY
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LOCATION_TERM
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
+import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
@@ -43,33 +44,39 @@ class EntityTypeServiceTests : WithTimescaleContainer, WithKafkaContainer {
private val now = Instant.now().atZone(ZoneOffset.UTC)
- private val entityPayload1 = newEntityPayload("urn:ngsi-ld:BeeHive:TESTA", listOf(BEEHIVE_TYPE, SENSOR_TYPE))
- private val entityPayload2 = newEntityPayload("urn:ngsi-ld:Sensor:TESTB", listOf(SENSOR_TYPE))
- private val entityPayload3 = newEntityPayload("urn:ngsi-ld:Apiary:TESTC", listOf(APIARY_TYPE))
- private val temporalEntityAttribute1 = newTemporalEntityAttribute(
+ private val entityPayload1 = gimmeEntityPayload("urn:ngsi-ld:BeeHive:TESTA", listOf(BEEHIVE_TYPE, SENSOR_TYPE))
+ private val entityPayload2 = gimmeEntityPayload("urn:ngsi-ld:Sensor:TESTB", listOf(SENSOR_TYPE))
+ private val entityPayload3 = gimmeEntityPayload("urn:ngsi-ld:Apiary:TESTC", listOf(APIARY_TYPE))
+ private val incomingProperty = newTemporalEntityAttribute(
"urn:ngsi-ld:BeeHive:TESTA",
INCOMING_PROPERTY,
TemporalEntityAttribute.AttributeType.Property,
TemporalEntityAttribute.AttributeValueType.NUMBER
)
- private val temporalEntityAttribute2 = newTemporalEntityAttribute(
+ private val managedByRelationship = newTemporalEntityAttribute(
"urn:ngsi-ld:BeeHive:TESTA",
MANAGED_BY_RELATIONSHIP,
TemporalEntityAttribute.AttributeType.Relationship,
TemporalEntityAttribute.AttributeValueType.STRING
)
- private val temporalEntityAttribute3 = newTemporalEntityAttribute(
+ private val locationGeoProperty = newTemporalEntityAttribute(
"urn:ngsi-ld:Apiary:TESTC",
NGSILD_LOCATION_PROPERTY,
TemporalEntityAttribute.AttributeType.GeoProperty,
TemporalEntityAttribute.AttributeValueType.GEOMETRY
)
- private val temporalEntityAttribute4 = newTemporalEntityAttribute(
+ private val outgoingProperty = newTemporalEntityAttribute(
"urn:ngsi-ld:Sensor:TESTB",
OUTGOING_PROPERTY,
TemporalEntityAttribute.AttributeType.Property,
TemporalEntityAttribute.AttributeValueType.GEOMETRY
)
+ private val luminosityJsonProperty = newTemporalEntityAttribute(
+ "urn:ngsi-ld:Sensor:TESTB",
+ LUMINOSITY_JSONPROPERTY,
+ TemporalEntityAttribute.AttributeType.JsonProperty,
+ TemporalEntityAttribute.AttributeValueType.OBJECT
+ )
@AfterEach
fun clearPreviousTemporalEntityAttributesAndObservations() {
@@ -86,36 +93,35 @@ class EntityTypeServiceTests : WithTimescaleContainer, WithKafkaContainer {
createEntityPayload(entityPayload1)
createEntityPayload(entityPayload2)
createEntityPayload(entityPayload3)
- createTemporalEntityAttribute(temporalEntityAttribute1)
- createTemporalEntityAttribute(temporalEntityAttribute2)
- createTemporalEntityAttribute(temporalEntityAttribute3)
- createTemporalEntityAttribute(temporalEntityAttribute4)
+ createTemporalEntityAttribute(incomingProperty)
+ createTemporalEntityAttribute(managedByRelationship)
+ createTemporalEntityAttribute(locationGeoProperty)
+ createTemporalEntityAttribute(outgoingProperty)
+ createTemporalEntityAttribute(luminosityJsonProperty)
}
@Test
- fun `it should return an EntityTypeList`() = runTest {
+ fun `it should return a list of all known entity types`() = runTest {
val entityTypes = entityTypeService.getEntityTypeList(APIC_COMPOUND_CONTEXTS)
- assertTrue(
- entityTypes.typeList == listOf(APIARY_COMPACT_TYPE, BEEHIVE_COMPACT_TYPE, SENSOR_COMPACT_TYPE)
- )
+ assertEquals(listOf(APIARY_COMPACT_TYPE, BEEHIVE_COMPACT_TYPE, SENSOR_COMPACT_TYPE), entityTypes.typeList)
}
@Test
- fun `it should return an empty list of types if no entity was found`() = runTest {
+ fun `it should return an empty list of types if no entity exists`() = runTest {
clearPreviousTemporalEntityAttributesAndObservations()
val entityTypes = entityTypeService.getEntityTypeList(listOf(AQUAC_COMPOUND_CONTEXT))
- assert(entityTypes.typeList.isEmpty())
+ assertThat(entityTypes.typeList).isEmpty()
}
@Test
- fun `it should return a list of EntityType`() = runTest {
+ fun `it should return all known entity types with details`() = runTest {
val entityTypes = entityTypeService.getEntityTypes(APIC_COMPOUND_CONTEXTS)
- assertTrue(entityTypes.size == 3)
- assertTrue(
- entityTypes.containsAll(
+ assertEquals(3, entityTypes.size)
+ assertThat(entityTypes)
+ .containsAll(
listOf(
EntityType(
id = toUri(APIARY_TYPE),
@@ -132,59 +138,67 @@ class EntityTypeServiceTests : WithTimescaleContainer, WithKafkaContainer {
typeName = SENSOR_COMPACT_TYPE,
attributeNames = listOf(
INCOMING_COMPACT_PROPERTY,
+ LUMINOSITY_COMPACT_JSONPROPERTY,
MANAGED_BY_COMPACT_RELATIONSHIP,
OUTGOING_COMPACT_PROPERTY
)
)
)
)
- )
}
@Test
- fun `it should return an empty list of EntityTypes if no entity was found`() = runTest {
+ fun `it should return an empty list of detailed entity types if no entity exists`() = runTest {
clearPreviousTemporalEntityAttributesAndObservations()
val entityTypes = entityTypeService.getEntityTypes(listOf(AQUAC_COMPOUND_CONTEXT))
- assert(entityTypes.isEmpty())
+ assertThat(entityTypes).isEmpty()
}
@Test
- fun `it should return an EntityTypeInfo for a specific type`() = runTest {
- val entityTypeInfo = entityTypeService.getEntityTypeInfoByType(
- BEEHIVE_TYPE,
- APIC_COMPOUND_CONTEXTS
- )
+ fun `it should return entity type info for a specific type`() = runTest {
+ val entityTypeInfo = entityTypeService.getEntityTypeInfoByType(SENSOR_TYPE, APIC_COMPOUND_CONTEXTS)
entityTypeInfo.shouldSucceedWith {
- EntityTypeInfo(
- id = toUri(BEEHIVE_TYPE),
- typeName = BEEHIVE_COMPACT_TYPE,
- entityCount = 1,
- attributeDetails = listOf(
- AttributeInfo(
- id = toUri(INCOMING_PROPERTY),
- attributeName = INCOMING_COMPACT_PROPERTY,
- attributeTypes = listOf(AttributeType.Property)
- ),
- AttributeInfo(
- id = toUri(MANAGED_BY_RELATIONSHIP),
- attributeName = MANAGED_BY_COMPACT_RELATIONSHIP,
- attributeTypes = listOf(AttributeType.Relationship)
+ assertEquals(
+ EntityTypeInfo(
+ id = toUri(SENSOR_TYPE),
+ typeName = SENSOR_COMPACT_TYPE,
+ entityCount = 2,
+ attributeDetails = listOf(
+ AttributeInfo(
+ id = toUri(INCOMING_PROPERTY),
+ attributeName = INCOMING_COMPACT_PROPERTY,
+ attributeTypes = listOf(AttributeType.Property)
+ ),
+ AttributeInfo(
+ id = toUri(LUMINOSITY_JSONPROPERTY),
+ attributeName = LUMINOSITY_COMPACT_JSONPROPERTY,
+ attributeTypes = listOf(AttributeType.JsonProperty)
+ ),
+ AttributeInfo(
+ id = toUri(MANAGED_BY_RELATIONSHIP),
+ attributeName = MANAGED_BY_COMPACT_RELATIONSHIP,
+ attributeTypes = listOf(AttributeType.Relationship)
+ ),
+ AttributeInfo(
+ id = toUri(OUTGOING_PROPERTY),
+ attributeName = OUTGOING_COMPACT_PROPERTY,
+ attributeTypes = listOf(AttributeType.Property)
+ )
)
- )
+ ),
+ it
)
}
}
@Test
- fun `it should error when type doesn't exist`() = runTest {
- val entityTypeInfo =
- entityTypeService.getEntityTypeInfoByType(TEMPERATURE_PROPERTY, APIC_COMPOUND_CONTEXTS)
-
- entityTypeInfo.shouldFail {
- assertEquals(ResourceNotFoundException(typeNotFoundMessage(TEMPERATURE_PROPERTY)), it)
- }
+ fun `it should return an error when entity type doesn't exist`() = runTest {
+ entityTypeService.getEntityTypeInfoByType(TEMPERATURE_PROPERTY, APIC_COMPOUND_CONTEXTS)
+ .shouldFail {
+ assertEquals(ResourceNotFoundException(typeNotFoundMessage(TEMPERATURE_PROPERTY)), it)
+ }
}
private fun createTemporalEntityAttribute(
@@ -237,17 +251,4 @@ class EntityTypeServiceTests : WithTimescaleContainer, WithKafkaContainer {
.bind("types", entityPayload.types.toTypedArray())
.execute()
}
-
- private fun newEntityPayload(
- id: String,
- types: List,
- contexts: List = APIC_COMPOUND_CONTEXTS
- ): EntityPayload =
- EntityPayload(
- entityId = toUri(id),
- types = types,
- createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = contexts
- )
}
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt
index 05eb66203..fa53a9398 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt
@@ -8,6 +8,7 @@ import com.egm.stellio.search.scope.ScopeService
import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD
import com.egm.stellio.search.support.EMPTY_PAYLOAD
import com.egm.stellio.search.support.buildDefaultQueryParams
+import com.egm.stellio.search.support.gimmeEntityPayload
import com.egm.stellio.shared.model.PaginationQuery
import com.egm.stellio.shared.model.ResourceNotFoundException
import com.egm.stellio.shared.util.*
@@ -16,7 +17,6 @@ import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_CREATED_AT_TERM
import com.ninjasquad.springmockk.MockkBean
import io.mockk.coEvery
import io.mockk.coVerify
-import io.r2dbc.postgresql.codec.Json
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.assertj.core.api.Assertions.assertThat
@@ -413,12 +413,9 @@ class QueryServiceTests {
})
}
- private fun gimmeEntityPayload(): EntityPayload =
- EntityPayload(
+ private fun gimmeEntityPayload() =
+ gimmeEntityPayload(
entityId = entityUri,
- types = listOf(BEEHIVE_TYPE),
- createdAt = now,
- contexts = APIC_COMPOUND_CONTEXTS,
- payload = Json.of(loadSampleData("beehive_expanded.jsonld"))
+ payload = loadSampleData("beehive_expanded.jsonld")
)
}
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeServiceTests.kt
index b65534660..bda5d024e 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeServiceTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeServiceTests.kt
@@ -5,6 +5,7 @@ import com.egm.stellio.search.model.*
import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD
import com.egm.stellio.search.support.WithKafkaContainer
import com.egm.stellio.search.support.WithTimescaleContainer
+import com.egm.stellio.search.util.toJson
import com.egm.stellio.shared.model.ResourceNotFoundException
import com.egm.stellio.shared.model.toNgsiLdAttribute
import com.egm.stellio.shared.model.toNgsiLdAttributes
@@ -56,7 +57,6 @@ class TemporalEntityAttributeServiceTests : WithTimescaleContainer, WithKafkaCon
entityId = beehiveTestCId,
types = listOf(BEEHIVE_TYPE),
createdAt = Instant.now().atZone(UTC),
- contexts = APIC_COMPOUND_CONTEXTS,
payload = EMPTY_JSON_PAYLOAD
)
).block()
@@ -66,7 +66,6 @@ class TemporalEntityAttributeServiceTests : WithTimescaleContainer, WithKafkaCon
entityId = beehiveTestDId,
types = listOf(BEEHIVE_TYPE),
createdAt = Instant.now().atZone(UTC),
- contexts = APIC_COMPOUND_CONTEXTS,
payload = EMPTY_JSON_PAYLOAD
)
).block()
@@ -282,6 +281,65 @@ class TemporalEntityAttributeServiceTests : WithTimescaleContainer, WithKafkaCon
}
}
+ @Test
+ fun `it should merge an attribute payload for a JsonProperty`() = runTest {
+ val initialJsonValue = """
+ {
+ "incoming": {
+ "type": "JsonProperty",
+ "json": { "id": 1, "b": null, "c": 12.4 },
+ "observedAt": "2022-12-24T14:01:22.066Z",
+ "subAttribute": {
+ "type": "Property",
+ "value": "subAttribute"
+ }
+ }
+ }
+ """.trimIndent()
+ val temporalEntityAttribute = TemporalEntityAttribute(
+ entityId = beehiveTestCId,
+ attributeName = INCOMING_PROPERTY,
+ attributeType = TemporalEntityAttribute.AttributeType.JsonProperty,
+ attributeValueType = TemporalEntityAttribute.AttributeValueType.JSON,
+ createdAt = ngsiLdDateTime(),
+ payload = expandAttribute(initialJsonValue, APIC_COMPOUND_CONTEXTS).second[0].toJson()
+ )
+
+ val newJsonValue = """
+ {
+ "incoming": {
+ "type": "JsonProperty",
+ "json": { "id": 2, "b": "something" },
+ "observedAt": "2023-12-24T14:01:22.066Z"
+ }
+ }
+ """.trimIndent()
+ val newJsonExpandedAttribute = expandAttribute(newJsonValue, APIC_COMPOUND_CONTEXTS).second[0]
+ val (_, expandedAttributeInstance) = temporalEntityAttributeService.mergeAttributePayload(
+ temporalEntityAttribute,
+ newJsonExpandedAttribute
+ )
+
+ val expectedJsonValue = """
+ {
+ "incoming": {
+ "type": "JsonProperty",
+ "json": { "id": 2, "b": "something" },
+ "observedAt": "2023-12-24T14:01:22.066Z",
+ "subAttribute": {
+ "type": "Property",
+ "value": "subAttribute"
+ }
+ }
+ }
+ """.trimIndent()
+ val expectedJsonExpandedAttribute = expandAttribute(expectedJsonValue, APIC_COMPOUND_CONTEXTS).second[0]
+ assertJsonPayloadsAreEqual(
+ serializeObject(expectedJsonExpandedAttribute),
+ serializeObject(expandedAttributeInstance)
+ )
+ }
+
@Test
fun `it should merge an entity attribute`() = runTest {
val rawEntity = loadSampleData()
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt b/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt
index 9c4cc2090..80097011a 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt
@@ -1,16 +1,36 @@
package com.egm.stellio.search.support
import com.egm.stellio.search.model.*
+import com.egm.stellio.shared.model.ExpandedTerm
import com.egm.stellio.shared.model.PaginationQuery
import com.egm.stellio.shared.model.addNonReifiedTemporalProperty
import com.egm.stellio.shared.model.getSingleEntry
-import com.egm.stellio.shared.util.APIC_COMPOUND_CONTEXTS
-import com.egm.stellio.shared.util.JsonLdUtils
-import com.egm.stellio.shared.util.ngsiLdDateTime
+import com.egm.stellio.shared.util.*
+import io.r2dbc.postgresql.codec.Json
+import java.net.URI
import java.util.UUID
import kotlin.random.Random
-fun gimmeAttributeInstance(
+fun gimmeEntityPayload(
+ entityId: String,
+ types: List = listOf(BEEHIVE_TYPE),
+ payload: String = EMPTY_PAYLOAD
+): EntityPayload =
+ gimmeEntityPayload(entityId.toUri(), types, payload)
+
+fun gimmeEntityPayload(
+ entityId: URI,
+ types: List = listOf(BEEHIVE_TYPE),
+ payload: String = EMPTY_PAYLOAD
+): EntityPayload =
+ EntityPayload(
+ entityId = entityId,
+ types = types,
+ createdAt = ngsiLdDateTime(),
+ payload = Json.of(payload)
+ )
+
+fun gimmeNumericPropertyAttributeInstance(
teaUuid: UUID,
timeProperty: AttributeInstance.TemporalProperty = AttributeInstance.TemporalProperty.OBSERVED_AT
): AttributeInstance {
@@ -36,6 +56,32 @@ fun gimmeAttributeInstance(
)
}
+fun gimmeJsonPropertyAttributeInstance(
+ teaUuid: UUID,
+ timeProperty: AttributeInstance.TemporalProperty = AttributeInstance.TemporalProperty.OBSERVED_AT
+): AttributeInstance {
+ val attributeMetadata = AttributeMetadata(
+ measuredValue = null,
+ value = SAMPLE_JSON_PROPERTY_PAYLOAD.asString(),
+ geoValue = null,
+ valueType = TemporalEntityAttribute.AttributeValueType.JSON,
+ datasetId = null,
+ type = TemporalEntityAttribute.AttributeType.JsonProperty,
+ observedAt = ngsiLdDateTime()
+ )
+ val payload = JsonLdUtils.buildExpandedPropertyValue(attributeMetadata.value!!)
+ .addNonReifiedTemporalProperty(JsonLdUtils.NGSILD_OBSERVED_AT_PROPERTY, attributeMetadata.observedAt!!)
+ .getSingleEntry()
+
+ return AttributeInstance(
+ temporalEntityAttribute = teaUuid,
+ time = attributeMetadata.observedAt!!,
+ attributeMetadata = attributeMetadata,
+ timeProperty = timeProperty,
+ payload = payload
+ )
+}
+
fun gimmeTemporalEntitiesQuery(
temporalQuery: TemporalQuery,
withTemporalValues: Boolean = false,
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt b/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt
index 518b59801..7c31f6346 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt
@@ -50,3 +50,8 @@ suspend fun buildSapAttribute(specificAccessPolicy: AuthContextModel.SpecificAcc
const val EMPTY_PAYLOAD = "{}"
val EMPTY_JSON_PAYLOAD = Json.of(EMPTY_PAYLOAD)
+val SAMPLE_JSON_PROPERTY_PAYLOAD = Json.of(
+ """
+ { "id": "123", "stringValue": "value", "nullValue": null }
+ """.trimIndent()
+)
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/util/AttributeInstanceUtilsTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/util/AttributeInstanceUtilsTests.kt
index a44cb9e32..8329bc3dc 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/util/AttributeInstanceUtilsTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/util/AttributeInstanceUtilsTests.kt
@@ -134,6 +134,22 @@ class AttributeInstanceUtilsTests {
)
}
+ @Test
+ fun `it should guess the value type of a JSON property`() = runTest {
+ val expandedJsonProperty = expandAttribute(
+ "jsonProperty",
+ mapOf(
+ "type" to "JsonProperty",
+ "json" to mapOf("key" to "value")
+ ),
+ NGSILD_TEST_CORE_CONTEXTS
+ )
+ assertEquals(
+ TemporalEntityAttribute.AttributeValueType.JSON,
+ guessAttributeValueType(TemporalEntityAttribute.AttributeType.JsonProperty, expandedJsonProperty.second[0])
+ )
+ }
+
@Test
fun `it should guess the value type of a relationship`() = runTest {
val expandedGeoRelationship = expandAttribute(
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntitiesParameterizedSource.kt b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntitiesParameterizedSource.kt
index cb709ed23..8dca9b1bc 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntitiesParameterizedSource.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntitiesParameterizedSource.kt
@@ -3,7 +3,10 @@ package com.egm.stellio.search.util
import com.egm.stellio.search.model.*
import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD
import com.egm.stellio.search.support.buildAttributeInstancePayload
-import com.egm.stellio.shared.util.*
+import com.egm.stellio.shared.util.BEEHIVE_TYPE
+import com.egm.stellio.shared.util.JsonLdUtils
+import com.egm.stellio.shared.util.loadSampleData
+import com.egm.stellio.shared.util.toUri
import org.junit.jupiter.params.provider.Arguments
import java.time.Instant
import java.time.ZoneOffset
@@ -24,8 +27,7 @@ class TemporalEntitiesParameterizedSource {
entityId = "urn:ngsi-ld:BeeHive:TESTC".toUri(),
types = listOf(BEEHIVE_TYPE),
createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = APIC_COMPOUND_CONTEXTS
+ payload = EMPTY_JSON_PAYLOAD
),
emptyList(),
mapOf(
@@ -49,8 +51,7 @@ class TemporalEntitiesParameterizedSource {
entityId = "urn:ngsi-ld:BeeHive:TESTD".toUri(),
types = listOf(BEEHIVE_TYPE),
createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = APIC_COMPOUND_CONTEXTS
+ payload = EMPTY_JSON_PAYLOAD
),
emptyList(),
mapOf(
@@ -78,8 +79,7 @@ class TemporalEntitiesParameterizedSource {
entityId = "urn:ngsi-ld:BeeHive:TESTC".toUri(),
types = listOf(BEEHIVE_TYPE),
createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = APIC_COMPOUND_CONTEXTS
+ payload = EMPTY_JSON_PAYLOAD
),
emptyList(),
mapOf(
@@ -110,8 +110,7 @@ class TemporalEntitiesParameterizedSource {
entityId = "urn:ngsi-ld:BeeHive:TESTD".toUri(),
types = listOf(BEEHIVE_TYPE),
createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = APIC_COMPOUND_CONTEXTS
+ payload = EMPTY_JSON_PAYLOAD
),
emptyList(),
mapOf(
@@ -146,8 +145,7 @@ class TemporalEntitiesParameterizedSource {
entityId = "urn:ngsi-ld:BeeHive:TESTC".toUri(),
types = listOf(BEEHIVE_TYPE),
createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = APIC_COMPOUND_CONTEXTS
+ payload = EMPTY_JSON_PAYLOAD
),
emptyList(),
mapOf(
@@ -185,8 +183,7 @@ class TemporalEntitiesParameterizedSource {
entityId = "urn:ngsi-ld:BeeHive:TESTD".toUri(),
types = listOf(BEEHIVE_TYPE),
createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = APIC_COMPOUND_CONTEXTS
+ payload = EMPTY_JSON_PAYLOAD
),
emptyList(),
mapOf(
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityBuilderTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityBuilderTests.kt
index 8c7651b4e..f03433213 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityBuilderTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityBuilderTests.kt
@@ -37,8 +37,7 @@ class TemporalEntityBuilderTests {
entityId = "urn:ngsi-ld:Beehive:1234".toUri(),
types = listOf(BEEHIVE_TYPE),
createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = APIC_COMPOUND_CONTEXTS
+ payload = EMPTY_JSON_PAYLOAD
)
val temporalEntity = TemporalEntityBuilder.buildTemporalEntity(
EntityTemporalResult(entityPayload, emptyList(), attributeAndResultsMap),
@@ -71,8 +70,7 @@ class TemporalEntityBuilderTests {
entityId = "urn:ngsi-ld:BeeHive:TESTC".toUri(),
types = listOf(BEEHIVE_TYPE),
createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = APIC_COMPOUND_CONTEXTS
+ payload = EMPTY_JSON_PAYLOAD
)
val temporalEntity = TemporalEntityBuilder.buildTemporalEntity(
@@ -177,8 +175,7 @@ class TemporalEntityBuilderTests {
entityId = "urn:ngsi-ld:Beehive:1234".toUri(),
types = listOf(BEEHIVE_TYPE),
createdAt = now,
- payload = EMPTY_JSON_PAYLOAD,
- contexts = APIC_COMPOUND_CONTEXTS
+ payload = EMPTY_JSON_PAYLOAD
)
val temporalEntity = TemporalEntityBuilder.buildTemporalEntity(
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt
index d617e2fd3..3ec633e69 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt
@@ -5,6 +5,7 @@ import com.egm.stellio.search.scope.FullScopeInstanceResult
import com.egm.stellio.search.scope.ScopeInstanceResult
import com.egm.stellio.search.scope.SimplifiedScopeInstanceResult
import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD
+import com.egm.stellio.search.support.SAMPLE_JSON_PROPERTY_PAYLOAD
import com.egm.stellio.search.support.buildAttributeInstancePayload
import com.egm.stellio.shared.util.JsonLdUtils
import com.egm.stellio.shared.util.loadSampleData
@@ -539,6 +540,41 @@ class TemporalEntityParameterizedSource {
loadSampleData("expectations/beehive_scope_multi_instances.jsonld")
)
+ private val beehiveJsonPropertyTemporalValues =
+ Arguments.arguments(
+ emptyList(),
+ mapOf(
+ TemporalEntityAttribute(
+ entityId = entityId,
+ attributeName = "https://ontology.eglobalmark.com/apic#luminosity",
+ attributeType = TemporalEntityAttribute.AttributeType.JsonProperty,
+ attributeValueType = TemporalEntityAttribute.AttributeValueType.JSON,
+ datasetId = null,
+ createdAt = now,
+ payload = SAMPLE_JSON_PROPERTY_PAYLOAD
+ ) to
+ listOf(
+ SimplifiedAttributeInstanceResult(
+ temporalEntityAttribute = UUID.randomUUID(),
+ value = """
+ { "id": "123", "stringValue": "value", "nullValue": null }
+ """,
+ time = ZonedDateTime.parse("2020-03-25T08:29:17.965206Z")
+ ),
+ SimplifiedAttributeInstanceResult(
+ temporalEntityAttribute = UUID.randomUUID(),
+ value = """
+ { "id": "456", "stringValue": "anotherValue" }
+ """,
+ time = ZonedDateTime.parse("2020-03-25T08:33:17.965206Z")
+ )
+ )
+ ),
+ true,
+ false,
+ loadSampleData("expectations/beehive_json_property_temporal_values.jsonld")
+ )
+
@JvmStatic
fun rawResultsProvider(): Stream {
return Stream.of(
@@ -554,7 +590,8 @@ class TemporalEntityParameterizedSource {
beehivePropertyMultiInstancesWithoutDatasetIdStringValuesTemporalValues,
beehiveRelationshipMultiInstancesTemporalValues,
beehiveScopeMultiInstancesTemporalValues,
- beehiveScopeMultiInstances
+ beehiveScopeMultiInstances,
+ beehiveJsonPropertyTemporalValues
)
}
}
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityHandlerTests.kt
index 989ef4144..05cc710cb 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityHandlerTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityHandlerTests.kt
@@ -100,7 +100,7 @@ class EntityHandlerTests {
entityPayloadService.createEntity(any(), any(), any())
} returns Unit.right()
coEvery { authorizationService.createAdminRight(any(), any()) } returns Unit.right()
- coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) } returns Job()
+ coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any()) } returns Job()
webClient.post()
.uri("/ngsi-ld/v1/entities")
@@ -125,8 +125,7 @@ class EntityHandlerTests {
entityEventService.publishEntityCreateEvent(
eq("60AAEBA3-C0C7-42B6-8CB0-0D30857F210E"),
eq(breedingServiceId),
- eq(listOf(breedingServiceType)),
- eq(listOf(AQUAC_COMPOUND_CONTEXT))
+ eq(listOf(breedingServiceType))
)
}
}
@@ -1153,7 +1152,7 @@ class EntityHandlerTests {
coEvery {
entityPayloadService.replaceEntity(any(), any(), any(), any())
} returns Unit.right()
- coEvery { entityEventService.publishEntityReplaceEvent(any(), any(), any(), any()) } returns Job()
+ coEvery { entityEventService.publishEntityReplaceEvent(any(), any(), any()) } returns Job()
webClient.put()
.uri("/ngsi-ld/v1/entities/$breedingServiceId")
@@ -1177,8 +1176,7 @@ class EntityHandlerTests {
entityEventService.publishEntityReplaceEvent(
eq("60AAEBA3-C0C7-42B6-8CB0-0D30857F210E"),
eq(breedingServiceId),
- eq(listOf(breedingServiceType)),
- eq(listOf(AQUAC_COMPOUND_CONTEXT))
+ eq(listOf(breedingServiceType))
)
}
}
@@ -1274,7 +1272,7 @@ class EntityHandlerTests {
authorizationService.userCanUpdateEntity(any(), sub)
} returns Unit.right()
coEvery {
- entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any())
+ entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true)
} returns Job()
}
@@ -1322,8 +1320,7 @@ class EntityHandlerTests {
eq(entityId),
any(),
appendResult,
- true,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ true
)
}
}
@@ -1379,8 +1376,7 @@ class EntityHandlerTests {
eq(entityId),
any(),
appendResult,
- true,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ true
)
}
}
@@ -1422,8 +1418,7 @@ class EntityHandlerTests {
eq(entityId),
any(),
appendTypeResult,
- true,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ true
)
}
}
@@ -1471,8 +1466,7 @@ class EntityHandlerTests {
eq(entityId),
any(),
appendTypeResult.mergeWith(appendResult),
- true,
- listOf(AQUAC_COMPOUND_CONTEXT)
+ true
)
}
}
@@ -1599,7 +1593,7 @@ class EntityHandlerTests {
entityPayloadService.partialUpdateAttribute(any(), any(), any())
} returns updateResult.right()
coEvery {
- entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any(), any())
+ entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any())
} returns Job()
webClient.patch()
@@ -1621,8 +1615,7 @@ class EntityHandlerTests {
eq(entityId),
any(),
eq(updateResult),
- eq(false),
- eq(listOf(AQUAC_COMPOUND_CONTEXT))
+ eq(false)
)
}
}
@@ -1744,7 +1737,7 @@ class EntityHandlerTests {
entityPayloadService.mergeEntity(any(), any(), any(), any())
} returns updateResult.right()
coEvery {
- entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any())
+ entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true)
} returns Job()
webClient.patch()
@@ -1766,8 +1759,7 @@ class EntityHandlerTests {
eq(entityId),
any(),
eq(updateResult),
- true,
- eq(listOf(AQUAC_COMPOUND_CONTEXT))
+ true
)
}
}
@@ -1798,7 +1790,7 @@ class EntityHandlerTests {
entityPayloadService.mergeEntity(any(), any(), any(), any())
} returns updateResult.right()
coEvery {
- entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any())
+ entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true)
} returns Job()
webClient.patch()
@@ -1825,8 +1817,7 @@ class EntityHandlerTests {
eq(entityId),
any(),
eq(updateResult),
- true,
- eq(listOf(AQUAC_COMPOUND_CONTEXT))
+ true
)
}
}
@@ -1951,7 +1942,7 @@ class EntityHandlerTests {
entityPayloadService.updateAttributes(any(), any(), any())
} returns updateResult.right()
coEvery {
- entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any())
+ entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true)
} returns Job()
webClient.patch()
@@ -1973,8 +1964,7 @@ class EntityHandlerTests {
eq(entityId),
any(),
eq(updateResult),
- true,
- eq(listOf(AQUAC_COMPOUND_CONTEXT))
+ true
)
}
}
@@ -1997,7 +1987,7 @@ class EntityHandlerTests {
notUpdated = arrayListOf(notUpdatedAttribute)
).right()
coEvery {
- entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any())
+ entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true)
} returns Job()
webClient.patch()
@@ -2024,7 +2014,7 @@ class EntityHandlerTests {
notUpdated = listOf(NotUpdatedDetails("type", "A type cannot be removed"))
).right()
coEvery {
- entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any())
+ entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true)
} returns Job()
webClient.patch()
@@ -2065,7 +2055,7 @@ class EntityHandlerTests {
{
"type": "https://uri.etsi.org/ngsi-ld/errors/LdContextNotAvailable",
"title": "A remote JSON-LD @context referenced in a request cannot be retrieved by the NGSI-LD Broker and expansion or compaction cannot be performed",
- "detail": "Unable to load remote context (cause was: JsonLdError[code=There was a problem encountered loading a remote context [code=LOADING_REMOTE_CONTEXT_FAILED]., message=There was a problem encountered loading a remote context [code=LOADING_REMOTE_CONTEXT_FAILED].])"
+ "detail": "Unable to load remote context (cause was: JsonLdError[code=There was a problem encountered loading a remote context [code=LOADING_REMOTE_CONTEXT_FAILED]., message=There wa a problem encountered loading a remote context [https://easyglobalmarket.com/contexts/diat.jsonld]])"
}
""".trimIndent()
)
@@ -2135,11 +2125,10 @@ class EntityHandlerTests {
coEvery { entityPayloadService.checkEntityExistence(beehiveId) } returns Unit.right()
coEvery { entityPayloadService.retrieve(any()) } returns entity.right()
every { entity.types } returns listOf(BEEHIVE_TYPE)
- every { entity.contexts } returns APIC_COMPOUND_CONTEXTS
coEvery { authorizationService.userCanAdminEntity(beehiveId, sub) } returns Unit.right()
coEvery { entityPayloadService.deleteEntity(any()) } returns Unit.right()
coEvery { authorizationService.removeRightsOnEntity(any()) } returns Unit.right()
- coEvery { entityEventService.publishEntityDeleteEvent(any(), any(), any()) } returns Job()
+ coEvery { entityEventService.publishEntityDeleteEvent(any(), any()) } returns Job()
webClient.delete()
.uri("/ngsi-ld/v1/entities/$beehiveId")
@@ -2157,8 +2146,7 @@ class EntityHandlerTests {
coVerify {
entityEventService.publishEntityDeleteEvent(
eq("60AAEBA3-C0C7-42B6-8CB0-0D30857F210E"),
- eq(entity),
- eq(APIC_COMPOUND_CONTEXTS)
+ eq(entity)
)
}
}
@@ -2242,7 +2230,7 @@ class EntityHandlerTests {
coEvery { entityPayloadService.getTypes(any()) } returns listOf(BEEHIVE_TYPE).right()
coEvery { authorizationService.userCanUpdateEntity(any(), sub) } returns Unit.right()
coEvery {
- entityEventService.publishAttributeDeleteEvent(any(), any(), any(), any(), any(), any())
+ entityEventService.publishAttributeDeleteEvent(any(), any(), any(), any(), any())
} returns Job()
}
@@ -2275,8 +2263,7 @@ class EntityHandlerTests {
eq(beehiveId),
eq(TEMPERATURE_PROPERTY),
isNull(),
- eq(false),
- eq(APIC_COMPOUND_CONTEXTS)
+ eq(false)
)
}
}
@@ -2310,8 +2297,7 @@ class EntityHandlerTests {
eq(beehiveId),
eq(TEMPERATURE_PROPERTY),
isNull(),
- eq(true),
- eq(APIC_COMPOUND_CONTEXTS)
+ eq(true)
)
}
}
@@ -2345,8 +2331,7 @@ class EntityHandlerTests {
eq(beehiveId),
eq(TEMPERATURE_PROPERTY),
eq(datasetId.toUri()),
- eq(false),
- eq(APIC_COMPOUND_CONTEXTS)
+ eq(false)
)
}
}
@@ -2450,7 +2435,7 @@ class EntityHandlerTests {
coEvery { entityPayloadService.checkEntityExistence(any()) } returns Unit.right()
coEvery { authorizationService.userCanUpdateEntity(any(), sub) } returns Unit.right()
coEvery {
- entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any(), any())
+ entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any())
} returns Job()
}
diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityOperationHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityOperationHandlerTests.kt
index d07ae2e0b..01492e202 100644
--- a/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityOperationHandlerTests.kt
+++ b/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityOperationHandlerTests.kt
@@ -91,29 +91,29 @@ class EntityOperationHandlerTests {
mockedTemperatureSensorEntity = mockkClass(NgsiLdEntity::class) {
every { id } returns temperatureSensorUri
every { types } returns listOf(SENSOR_TYPE)
- every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT)
}
mockedTemperatureSensorExpandedEntity = mockkClass(ExpandedEntity::class) {
every { id } returns temperatureSensorUri.toString()
every { members } returns emptyMap()
+ every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT)
}
mockedDissolvedOxygenSensorEntity = mockkClass(NgsiLdEntity::class) {
every { id } returns dissolvedOxygenSensorUri
every { types } returns listOf(SENSOR_TYPE)
- every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT)
}
mockedDissolvedOxygenSensorExpandedEntity = mockkClass(ExpandedEntity::class) {
every { id } returns dissolvedOxygenSensorUri.toString()
every { members } returns emptyMap()
+ every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT)
}
mockedDeviceEntity = mockkClass(NgsiLdEntity::class) {
every { id } returns deviceUri
every { types } returns listOf(DEVICE_TYPE)
- every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT)
}
mockedDeviceExpandedEntity = mockkClass(ExpandedEntity::class) {
every { id } returns deviceUri.toString()
every { members } returns emptyMap()
+ every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT)
}
}
@@ -129,8 +129,6 @@ class EntityOperationHandlerTests {
private val batchDeleteEndpoint = "/ngsi-ld/v1/entityOperations/delete"
private val queryEntitiesEndpoint = "/ngsi-ld/v1/entityOperations/query"
- private val hcmrContext = listOf(AQUAC_COMPOUND_CONTEXT)
-
@Test
fun `update batch entity should return a 204 if JSON-LD payload is correct`() = runTest {
val jsonLdFile = ClassPathResource("/ngsild/hcmr/HCMR_test_file.json")
@@ -331,8 +329,7 @@ class EntityOperationHandlerTests {
entityEventService.publishEntityCreateEvent(
any(),
capture(capturedEntitiesIds),
- capture(capturedEntityTypes),
- any()
+ capture(capturedEntityTypes)
)
} returns Job()
@@ -349,7 +346,7 @@ class EntityOperationHandlerTests {
coVerify { authorizationService.createAdminRights(allEntitiesUris, sub) }
coVerify(timeout = 1000, exactly = 3) {
- entityEventService.publishEntityCreateEvent(any(), any(), any(), any())
+ entityEventService.publishEntityCreateEvent(any(), any(), any())
}
capturedEntitiesIds.forEach { assertTrue(it in allEntitiesUris) }
assertTrue(capturedEntityTypes.captured[0] in listOf(SENSOR_TYPE, DEVICE_TYPE))
@@ -389,8 +386,7 @@ class EntityOperationHandlerTests {
entityEventService.publishEntityCreateEvent(
any(),
capture(capturedEntitiesIds),
- capture(capturedEntityTypes),
- any()
+ capture(capturedEntityTypes)
)
} returns Job()
@@ -418,7 +414,7 @@ class EntityOperationHandlerTests {
coVerify { authorizationService.createAdminRights(createdEntitiesIds, sub) }
coVerify(timeout = 1000, exactly = 2) {
- entityEventService.publishEntityCreateEvent(any(), any(), any(), any())
+ entityEventService.publishEntityCreateEvent(any(), any(), any())
}
capturedEntitiesIds.forEach { assertTrue(it in createdEntitiesIds) }
assertTrue(capturedEntityTypes.captured[0] in listOf(SENSOR_TYPE, DEVICE_TYPE))
@@ -456,8 +452,7 @@ class EntityOperationHandlerTests {
entityEventService.publishEntityCreateEvent(
any(),
capture(capturedEntitiesIds),
- capture(capturedEntityTypes),
- any()
+ capture(capturedEntityTypes)
)
} returns Job()
@@ -572,12 +567,12 @@ class EntityOperationHandlerTests {
coEvery { authorizationService.userCanCreateEntities(any()) } returns Unit.right()
coEvery { entityOperationService.create(any(), any()) } returns createdBatchResult
coEvery { authorizationService.createAdminRights(any(), eq(sub)) } returns Unit.right()
- coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) } returns Job()
+ coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any()) } returns Job()
coEvery { authorizationService.userCanUpdateEntity(any(), sub) } returns Unit.right()
coEvery { entityOperationService.update(any(), any(), any()) } returns updatedBatchResult
coEvery {
- entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any())
+ entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true)
} returns Job()
webClient.post()
@@ -593,8 +588,7 @@ class EntityOperationHandlerTests {
entityEventService.publishEntityCreateEvent(
eq(sub.value),
match { it in createdEntitiesIds },
- eq(listOf(SENSOR_TYPE)),
- eq(hcmrContext)
+ eq(listOf(SENSOR_TYPE))
)
}
coVerify(timeout = 1000, exactly = 2) {
@@ -603,8 +597,7 @@ class EntityOperationHandlerTests {
match { it in updatedEntitiesIds },
any(),
match { it in updatedBatchResult.success.map { it.updateResult } },
- true,
- hcmrContext
+ true
)
}
}
@@ -695,7 +688,7 @@ class EntityOperationHandlerTests {
)
coVerify { authorizationService.createAdminRights(listOf(deviceUri), sub) }
- coVerify(exactly = 1) { entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) }
+ coVerify(exactly = 1) { entityEventService.publishEntityCreateEvent(any(), any(), any()) }
}
@Test
@@ -719,7 +712,7 @@ class EntityOperationHandlerTests {
entitiesIds.map { BatchEntitySuccess(it) }.toMutableList(),
arrayListOf()
)
- coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) } returns Job()
+ coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any()) } returns Job()
webClient.post()
.uri(batchUpsertEndpoint)
@@ -734,8 +727,7 @@ class EntityOperationHandlerTests {
entityEventService.publishEntityReplaceEvent(
eq(sub.value),
match { it in entitiesIds },
- eq(listOf(SENSOR_TYPE)),
- eq(hcmrContext)
+ eq(listOf(SENSOR_TYPE))
)
}
}
@@ -833,8 +825,7 @@ class EntityOperationHandlerTests {
entityEventService.publishEntityReplaceEvent(
eq(sub.value),
eq(temperatureSensorUri),
- eq(listOf(SENSOR_TYPE)),
- eq(hcmrContext)
+ eq(listOf(SENSOR_TYPE))
)
}
}
@@ -885,20 +876,17 @@ class EntityOperationHandlerTests {
mockkClass(EntityPayload::class, relaxed = true) {
every { entityId } returns dissolvedOxygenSensorUri
every { types } returns listOf(SENSOR_TYPE)
- every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT)
},
mockkClass(EntityPayload::class, relaxed = true) {
every { entityId } returns temperatureSensorUri
every { types } returns listOf(SENSOR_TYPE)
- every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT)
},
mockkClass(EntityPayload::class, relaxed = true) {
every { entityId } returns deviceUri
every { types } returns listOf(DEVICE_TYPE)
- every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT)
}
)
- coEvery { entityEventService.publishEntityDeleteEvent(any(), any(), any()) } returns Job()
+ coEvery { entityEventService.publishEntityDeleteEvent(any(), any()) } returns Job()
webClient.post()
.uri(batchDeleteEndpoint)
@@ -909,8 +897,7 @@ class EntityOperationHandlerTests {
coVerify(timeout = 1000, exactly = 3) {
entityEventService.publishEntityDeleteEvent(
eq(sub.value),
- any(),
- eq(listOf(AQUAC_COMPOUND_CONTEXT))
+ any()
)
}
}
diff --git a/search-service/src/test/resources/ngsild/expectations/beehive_json_property_temporal_values.jsonld b/search-service/src/test/resources/ngsild/expectations/beehive_json_property_temporal_values.jsonld
new file mode 100644
index 000000000..cb2cd5634
--- /dev/null
+++ b/search-service/src/test/resources/ngsild/expectations/beehive_json_property_temporal_values.jsonld
@@ -0,0 +1,56 @@
+{
+ "@id": "urn:ngsi-ld:BeeHive:TESTC",
+ "@type": [
+ "https://ontology.eglobalmark.com/apic#BeeHive"
+ ],
+ "https://ontology.eglobalmark.com/apic#luminosity": [
+ {
+ "@type": [
+ "https://uri.etsi.org/ngsi-ld/JsonProperty"
+ ],
+ "https://uri.etsi.org/ngsi-ld/jsons": [
+ {
+ "@list": [
+ {
+ "@list": [
+ {
+ "https://uri.etsi.org/ngsi-ld/hasJSON": [
+ {
+ "@type": "@json",
+ "@value": {
+ "id": "123",
+ "stringValue": "value",
+ "nullValue": null
+ }
+ }
+ ]
+ },
+ {
+ "@value": "2020-03-25T08:29:17.965206Z"
+ }
+ ]
+ },
+ {
+ "@list": [
+ {
+ "https://uri.etsi.org/ngsi-ld/hasJSON": [
+ {
+ "@type": "@json",
+ "@value": {
+ "id": "456",
+ "stringValue": "anotherValue"
+ }
+ }
+ ]
+ },
+ {
+ "@value": "2020-03-25T08:33:17.965206Z"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/search-service/src/test/resources/ngsild/fragments/temporal_instance_json_fragment.jsonld b/search-service/src/test/resources/ngsild/fragments/temporal_instance_json_fragment.jsonld
new file mode 100644
index 000000000..ed94b972f
--- /dev/null
+++ b/search-service/src/test/resources/ngsild/fragments/temporal_instance_json_fragment.jsonld
@@ -0,0 +1,8 @@
+{
+ "type": "JsonProperty",
+ "json": {
+ "key": "value",
+ "type": "unknown"
+ },
+ "observedAt": "2023-03-13T12:33:06Z"
+}
diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts
index 112b58960..62070e515 100644
--- a/shared/build.gradle.kts
+++ b/shared/build.gradle.kts
@@ -26,7 +26,7 @@ dependencies {
testFixturesImplementation("org.springframework.security:spring-security-oauth2-jose")
testFixturesImplementation("org.springframework.security:spring-security-test")
testFixturesImplementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
- testFixturesImplementation("io.arrow-kt:arrow-fx-coroutines:1.2.1")
+ testFixturesImplementation("io.arrow-kt:arrow-fx-coroutines:1.2.3")
testFixturesImplementation("org.springframework.boot:spring-boot-starter-test") {
// to ensure we are using mocks and spies from springmockk lib instead
exclude(module = "mockito-core")
@@ -34,7 +34,7 @@ dependencies {
testFixturesImplementation("org.mock-server:mockserver-netty-no-dependencies:5.15.0")
testFixturesImplementation("org.mock-server:mockserver-client-java-no-dependencies:5.15.0")
- detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.4")
+ detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.5")
testFixturesApi("org.testcontainers:testcontainers")
testFixturesApi("org.testcontainers:junit-jupiter")
diff --git a/shared/config/detekt/baseline.xml b/shared/config/detekt/baseline.xml
index 4f8418074..c3deb4468 100644
--- a/shared/config/detekt/baseline.xml
+++ b/shared/config/detekt/baseline.xml
@@ -3,14 +3,10 @@
CyclomaticComplexMethod:ExceptionHandler.kt$ExceptionHandler$@ExceptionHandler fun transformErrorResponse(throwable: Throwable): ResponseEntity<ProblemDetail>
- LongMethod:ExpandedEntityTests.kt$ExpandedEntityTests$@Test fun `it should return simplified GeoJSON entities`()
+ LongMethod:CompactedEntityTests.kt$CompactedEntityTests$@Test fun `it should return the GeoJSON representation of simplified entities`()
LongMethod:QueryUtils.kt$private fun transformQQueryToSqlJsonPath( mainAttributePath: List<ExpandedTerm>, trailingAttributePath: List<ExpandedTerm>, operator: String, value: String )
LongParameterList:ApiResponses.kt$( body: String, count: Int, resourceUrl: String, paginationQuery: PaginationQuery, requestParams: MultiValueMap<String, String>, mediaType: MediaType, contexts: List<String> )
LongParameterList:ApiResponses.kt$( entities: Any, count: Int, resourceUrl: String, paginationQuery: PaginationQuery, requestParams: MultiValueMap<String, String>, mediaType: MediaType, contexts: List<String> )
- LongParameterList:NgsiLdEntity.kt$NgsiLdEntity$( val id: URI, val types: List<ExpandedTerm>, val scopes: List<String>?, val relationships: List<NgsiLdRelationship>, val properties: List<NgsiLdProperty>, val geoProperties: List<NgsiLdGeoProperty>, val contexts: List<String> )
- LongParameterList:NgsiLdEntity.kt$NgsiLdGeoPropertyInstance$( val coordinates: WKTCoordinates, createdAt: ZonedDateTime?, modifiedAt: ZonedDateTime?, observedAt: ZonedDateTime?, datasetId: URI?, properties: List<NgsiLdProperty>, relationships: List<NgsiLdRelationship> )
- LongParameterList:NgsiLdEntity.kt$NgsiLdPropertyInstance$( val value: Any, val unitCode: String?, createdAt: ZonedDateTime?, modifiedAt: ZonedDateTime?, observedAt: ZonedDateTime?, datasetId: URI?, properties: List<NgsiLdProperty>, relationships: List<NgsiLdRelationship> )
- LongParameterList:NgsiLdEntity.kt$NgsiLdRelationshipInstance$( val objectId: URI, createdAt: ZonedDateTime?, modifiedAt: ZonedDateTime?, observedAt: ZonedDateTime?, datasetId: URI?, properties: List<NgsiLdProperty>, relationships: List<NgsiLdRelationship> )
SpreadOperator:EntityEvent.kt$EntityEvent$( *[ JsonSubTypes.Type(value = EntityCreateEvent::class), JsonSubTypes.Type(value = EntityReplaceEvent::class), JsonSubTypes.Type(value = EntityDeleteEvent::class), JsonSubTypes.Type(value = AttributeAppendEvent::class), JsonSubTypes.Type(value = AttributeReplaceEvent::class), JsonSubTypes.Type(value = AttributeUpdateEvent::class), JsonSubTypes.Type(value = AttributeDeleteEvent::class), JsonSubTypes.Type(value = AttributeDeleteAllInstancesEvent::class) ] )
SwallowedException:JsonLdUtils.kt$JsonLdUtils$e: JsonLdError
TooManyFunctions:JsonLdUtils.kt$JsonLdUtils
diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt
index cbbcc998f..781701e73 100644
--- a/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt
+++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt
@@ -3,12 +3,13 @@ package com.egm.stellio.shared.model
import com.egm.stellio.shared.util.*
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_CONTEXT
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_ID_TERM
+import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_JSON_TERM
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_OBJECT
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE_TERM
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE_TERM
-import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_TERM
-import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_TERM
-import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_TERM
+import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATASET_ID_TERM
+import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATASET_TERM
+import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_NONE_TERM
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_SYSATTRS_TERMS
typealias CompactedEntity = Map
@@ -21,23 +22,36 @@ private fun simplifyRepresentation(value: Any): Any =
// an attribute with a single instance
is Map<*, *> -> simplifyValue(value as Map)
// an attribute with multiple instances
- is List<*> -> value.map {
- when (it) {
- is Map<*, *> -> simplifyValue(it as Map)
+ is List<*> -> {
+ when (value.first()) {
+ is Map<*, *> -> simplifyMultiInstanceAttribute(value as List