diff --git a/search-service/config/detekt/baseline.xml b/search-service/config/detekt/baseline.xml
index 0ee11ab05..c392b1a73 100644
--- a/search-service/config/detekt/baseline.xml
+++ b/search-service/config/detekt/baseline.xml
@@ -5,28 +5,27 @@
ClassNaming:V0_29_JsonLd_migrationTests.kt$V0_29_JsonLd_migrationTests
ClassNaming:V0_29__JsonLd_migration.kt$V0_29__JsonLd_migration : BaseJavaMigration
ComplexCondition:EntitiesQueryUtils.kt$geoQuery == null && q.isNullOrEmpty() && typeSelection.isNullOrEmpty() && attrs.isEmpty()
- ComplexCondition:EntityPayloadService.kt$EntityPayloadService$it && !inverse || !it && inverse
+ ComplexCondition:EntityQueryService.kt$EntityQueryService$it && !inverse || !it && inverse
Filename:V0_29__JsonLd_migration.kt$db.migration.V0_29__JsonLd_migration.kt
LongMethod:AttributeInstanceService.kt$AttributeInstanceService$@Transactional suspend fun create(attributeInstance: AttributeInstance): Either<APIException, Unit>
LongMethod:EnabledAuthorizationServiceTests.kt$EnabledAuthorizationServiceTests$@Test fun `it should return serialized access control entities with other rigths if user is owner`()
LongMethod:EntityAccessControlHandler.kt$EntityAccessControlHandler$@PostMapping("/{subjectId}/attrs", consumes = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE]) suspend fun addRightsOnEntities( @RequestHeader httpHeaders: HttpHeaders, @PathVariable subjectId: String, @RequestBody requestBody: Mono<String> ): ResponseEntity<*>
LongMethod:PatchAttributeTests.kt$PatchAttributeTests.Companion$@JvmStatic fun mergePatchProvider(): Stream<Arguments>
LongMethod:PatchAttributeTests.kt$PatchAttributeTests.Companion$@JvmStatic fun partialUpdatePatchProvider(): Stream<Arguments>
- LongMethod:QueryServiceTests.kt$QueryServiceTests$@Test fun `it should query temporal entities as requested by query params`()
- LongMethod:QueryServiceTests.kt$QueryServiceTests$@Test fun `it should return an empty list for an attribute if it has no temporal values`()
+ LongMethod:TemporalQueryServiceTests.kt$TemporalQueryServiceTests$@Test fun `it should query temporal entities as requested by query params`()
+ LongMethod:TemporalQueryServiceTests.kt$TemporalQueryServiceTests$@Test fun `it should return an empty list for an attribute if it has no temporal values`()
LongMethod:TemporalScopeBuilderTests.kt$TemporalScopeBuilderTests$@Test fun `it should build an aggregated temporal representation of scopes`()
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:AttributeInstance.kt$AttributeInstance.Companion$( attributeUuid: UUID, instanceId: URI = generateRandomInstanceId(), timeAndProperty: Pair<ZonedDateTime, TemporalProperty>, value: Triple<String?, Double?, WKTCoordinates?>, payload: ExpandedAttributeInstance, sub: String? )
+ LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( attributeUuid: UUID, instanceId: URI = generateRandomInstanceId(), timeProperty: TemporalProperty? = TemporalProperty.OBSERVED_AT, modifiedAt: ZonedDateTime? = null, attributeMetadata: AttributeMetadata, payload: ExpandedAttributeInstance, time: ZonedDateTime, sub: String? = null )
+ LongParameterList:EntityAttributeService.kt$EntityAttributeService$( attribute: Attribute, attributeName: ExpandedTerm, attributeMetadata: AttributeMetadata, mergedAt: ZonedDateTime, observedAt: ZonedDateTime?, attributePayload: ExpandedAttributeInstance, sub: Sub? )
+ LongParameterList:EntityAttributeService.kt$EntityAttributeService$( attribute: Attribute, ngsiLdAttribute: NgsiLdAttribute, attributeMetadata: AttributeMetadata, createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? )
+ LongParameterList:EntityAttributeService.kt$EntityAttributeService$( entityId: URI, attributeName: ExpandedTerm, attributeMetadata: AttributeMetadata, createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? )
+ LongParameterList:EntityAttributeService.kt$EntityAttributeService$( entityUri: URI, ngsiLdAttributes: List<NgsiLdAttribute>, expandedAttributes: ExpandedAttributes, createdAt: ZonedDateTime, observedAt: ZonedDateTime?, sub: Sub? )
+ LongParameterList:EntityAttributeService.kt$EntityAttributeService$( entityUri: URI, ngsiLdAttributes: List<NgsiLdAttribute>, expandedAttributes: ExpandedAttributes, disallowOverwrite: Boolean, createdAt: ZonedDateTime, sub: Sub? )
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? )
- LongParameterList:TemporalEntityAttributeService.kt$TemporalEntityAttributeService$( tea: TemporalEntityAttribute, attributeName: ExpandedTerm, attributeMetadata: AttributeMetadata, mergedAt: ZonedDateTime, observedAt: ZonedDateTime?, attributePayload: ExpandedAttributeInstance, sub: Sub? )
- LongParameterList:TemporalEntityAttributeService.kt$TemporalEntityAttributeService$( temporalEntityAttribute: TemporalEntityAttribute, ngsiLdAttribute: NgsiLdAttribute, attributeMetadata: AttributeMetadata, createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? )
LongParameterList:V0_29__JsonLd_migration.kt$V0_29__JsonLd_migration$( entityId: URI, attributeName: ExpandedTerm, datasetId: URI?, attributePayload: ExpandedAttributeInstance, ngsiLdAttributeInstance: NgsiLdAttributeInstance, defaultCreatedAt: ZonedDateTime )
NestedBlockDepth:V0_29__JsonLd_migration.kt$V0_29__JsonLd_migration$override fun migrate(context: Context)
- SwallowedException:EntitiesQueryUtils.kt$e: IllegalArgumentException
- TooManyFunctions:EntityPayloadService.kt$EntityPayloadService
+ SwallowedException:TemporalQueryUtils.kt$e: IllegalArgumentException
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/SearchServiceApplication.kt b/search-service/src/main/kotlin/com/egm/stellio/search/SearchServiceApplication.kt
index 3067df6df..f0b10e6a5 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/SearchServiceApplication.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/SearchServiceApplication.kt
@@ -5,7 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication
@SpringBootApplication(scanBasePackages = ["com.egm.stellio.search", "com.egm.stellio.shared"])
-@ConfigurationPropertiesScan("com.egm.stellio.search.config", "com.egm.stellio.shared.config")
+@ConfigurationPropertiesScan("com.egm.stellio.search.common.config", "com.egm.stellio.shared.config")
class SearchServiceApplication
@Suppress("SpreadOperator")
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/listener/IAMListener.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/listener/IAMListener.kt
similarity index 85%
rename from search-service/src/main/kotlin/com/egm/stellio/search/listener/IAMListener.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/listener/IAMListener.kt
index ad68c4960..a124254ad 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/listener/IAMListener.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/listener/IAMListener.kt
@@ -1,17 +1,16 @@
-package com.egm.stellio.search.listener
+package com.egm.stellio.search.authorization.listener
import arrow.core.Either
import arrow.core.flattenOption
import arrow.core.left
import arrow.core.raise.either
import arrow.core.right
-import com.egm.stellio.search.authorization.EntityAccessRightsService
-import com.egm.stellio.search.authorization.SubjectReferential
-import com.egm.stellio.search.authorization.SubjectReferentialService
-import com.egm.stellio.search.authorization.toSubjectInfo
-import com.egm.stellio.search.config.SearchProperties
-import com.egm.stellio.search.service.EntityEventService
-import com.egm.stellio.search.service.EntityPayloadService
+import com.egm.stellio.search.authorization.model.SubjectReferential
+import com.egm.stellio.search.authorization.model.toSubjectInfo
+import com.egm.stellio.search.authorization.service.EntityAccessRightsService
+import com.egm.stellio.search.authorization.service.SubjectReferentialService
+import com.egm.stellio.search.common.config.SearchProperties
+import com.egm.stellio.search.entity.service.EntityService
import com.egm.stellio.shared.model.*
import com.egm.stellio.shared.util.AuthContextModel.AUTH_TERM_IS_MEMBER_OF
import com.egm.stellio.shared.util.AuthContextModel.AUTH_TERM_ROLES
@@ -39,8 +38,7 @@ class IAMListener(
private val subjectReferentialService: SubjectReferentialService,
private val searchProperties: SearchProperties,
private val entityAccessRightsService: EntityAccessRightsService,
- private val entityPayloadService: EntityPayloadService,
- private val entityEventService: EntityEventService
+ private val entityService: EntityService
) {
private val logger = LoggerFactory.getLogger(javaClass)
@@ -114,19 +112,15 @@ class IAMListener(
val subjectType = SubjectType.valueOf(entityDeleteEvent.entityTypes.first().uppercase())
val sub = entityDeleteEvent.entityId.extractSub()
mono {
+ // delete the entities owned by the user while the user still exists
+ // (if it no longer exists, it fails because of access rights checks)
+ if (searchProperties.onOwnerDeleteCascadeEntities && subjectType == SubjectType.USER) {
+ entityAccessRightsService.getEntitiesIdsOwnedBySubject(sub).getOrNull()?.forEach { entityId ->
+ entityService.deleteEntity(entityId, sub)
+ }
+ Unit.right()
+ } else Unit.right()
subjectReferentialService.delete(entityDeleteEvent.entityId.extractSub())
- val result: Either =
- if (searchProperties.onOwnerDeleteCascadeEntities && subjectType == SubjectType.USER) {
- val entitiesIds = entityAccessRightsService.getEntitiesIdsOwnedBySubject(sub).getOrNull()
- entitiesIds?.let { entityAccessRightsService.deleteAllAccessRightsOnEntities(it) }
- entitiesIds?.forEach { entityId ->
- entityPayloadService.deleteEntity(entityId).getOrNull()?.also {
- entityEventService.publishEntityDeleteEvent(null, it)
- }
- }
- Unit.right()
- } else Unit.right()
- result
}.writeContextAndSubscribe(tenantName, entityDeleteEvent)
}
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EntityAccessRights.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/EntityAccessRights.kt
similarity index 98%
rename from search-service/src/main/kotlin/com/egm/stellio/search/authorization/EntityAccessRights.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/EntityAccessRights.kt
index f222d051c..022110962 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EntityAccessRights.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/EntityAccessRights.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.authorization
+package com.egm.stellio.search.authorization.model
import com.egm.stellio.shared.model.ExpandedAttributeInstances
import com.egm.stellio.shared.model.ExpandedTerm
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/Group.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/Group.kt
similarity index 95%
rename from search-service/src/main/kotlin/com/egm/stellio/search/authorization/Group.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/Group.kt
index a664c6bf1..c9599753c 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/Group.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/Group.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.authorization
+package com.egm.stellio.search.authorization.model
import com.egm.stellio.shared.util.AuthContextModel.AUTH_PROP_NAME
import com.egm.stellio.shared.util.AuthContextModel.AUTH_REL_IS_MEMBER_OF
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/SubjectAccessRight.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/SubjectAccessRight.kt
similarity index 80%
rename from search-service/src/main/kotlin/com/egm/stellio/search/authorization/SubjectAccessRight.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/SubjectAccessRight.kt
index c9e163335..363e757a8 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/SubjectAccessRight.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/SubjectAccessRight.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.authorization
+package com.egm.stellio.search.authorization.model
import com.egm.stellio.shared.util.AccessRight
import java.net.URI
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/SubjectReferential.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/SubjectReferential.kt
similarity index 92%
rename from search-service/src/main/kotlin/com/egm/stellio/search/authorization/SubjectReferential.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/SubjectReferential.kt
index 98b15672f..e781e628c 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/SubjectReferential.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/SubjectReferential.kt
@@ -1,6 +1,6 @@
-package com.egm.stellio.search.authorization
+package com.egm.stellio.search.authorization.model
-import com.egm.stellio.search.util.deserializeAsMap
+import com.egm.stellio.search.common.util.deserializeAsMap
import com.egm.stellio.shared.util.GlobalRole
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE_TERM
import com.egm.stellio.shared.util.JsonUtils
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/User.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/User.kt
similarity index 97%
rename from search-service/src/main/kotlin/com/egm/stellio/search/authorization/User.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/User.kt
index 8999a36fd..46cebf398 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/User.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/model/User.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.authorization
+package com.egm.stellio.search.authorization.model
import com.egm.stellio.shared.util.AuthContextModel.AUTH_PROP_FAMILY_NAME
import com.egm.stellio.shared.util.AuthContextModel.AUTH_PROP_GIVEN_NAME
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/AuthorizationService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/AuthorizationService.kt
similarity index 93%
rename from search-service/src/main/kotlin/com/egm/stellio/search/authorization/AuthorizationService.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/AuthorizationService.kt
index ec2bc3c5c..1aead2e42 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/AuthorizationService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/AuthorizationService.kt
@@ -1,8 +1,8 @@
-package com.egm.stellio.search.authorization
+package com.egm.stellio.search.authorization.service
import arrow.core.Either
import arrow.core.Option
-import com.egm.stellio.search.model.EntitiesQuery
+import com.egm.stellio.search.entity.model.EntitiesQuery
import com.egm.stellio.shared.model.APIException
import com.egm.stellio.shared.model.ExpandedEntity
import com.egm.stellio.shared.util.Sub
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/DisabledAuthorizationService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/DisabledAuthorizationService.kt
similarity index 95%
rename from search-service/src/main/kotlin/com/egm/stellio/search/authorization/DisabledAuthorizationService.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/DisabledAuthorizationService.kt
index dff262042..21da63b1e 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/DisabledAuthorizationService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/DisabledAuthorizationService.kt
@@ -1,9 +1,9 @@
-package com.egm.stellio.search.authorization
+package com.egm.stellio.search.authorization.service
import arrow.core.Either
import arrow.core.Option
import arrow.core.right
-import com.egm.stellio.search.model.EntitiesQuery
+import com.egm.stellio.search.entity.model.EntitiesQuery
import com.egm.stellio.shared.model.APIException
import com.egm.stellio.shared.model.ExpandedEntity
import com.egm.stellio.shared.util.Sub
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EnabledAuthorizationService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/EnabledAuthorizationService.kt
similarity index 98%
rename from search-service/src/main/kotlin/com/egm/stellio/search/authorization/EnabledAuthorizationService.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/EnabledAuthorizationService.kt
index dc21a84eb..4c927d91a 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EnabledAuthorizationService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/EnabledAuthorizationService.kt
@@ -1,9 +1,9 @@
-package com.egm.stellio.search.authorization
+package com.egm.stellio.search.authorization.service
import arrow.core.*
import arrow.core.raise.either
import arrow.fx.coroutines.parMap
-import com.egm.stellio.search.model.EntitiesQuery
+import com.egm.stellio.search.entity.model.EntitiesQuery
import com.egm.stellio.shared.model.APIException
import com.egm.stellio.shared.model.AccessDeniedException
import com.egm.stellio.shared.model.ExpandedEntity
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EntityAccessRightsService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/EntityAccessRightsService.kt
similarity index 88%
rename from search-service/src/main/kotlin/com/egm/stellio/search/authorization/EntityAccessRightsService.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/EntityAccessRightsService.kt
index f7921a402..6a375dc56 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/EntityAccessRightsService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/EntityAccessRightsService.kt
@@ -1,10 +1,10 @@
-package com.egm.stellio.search.authorization
+package com.egm.stellio.search.authorization.service
import arrow.core.*
import arrow.core.raise.either
-import com.egm.stellio.search.authorization.EntityAccessRights.SubjectRightInfo
-import com.egm.stellio.search.service.EntityPayloadService
-import com.egm.stellio.search.util.*
+import com.egm.stellio.search.authorization.model.EntityAccessRights
+import com.egm.stellio.search.authorization.model.EntityAccessRights.SubjectRightInfo
+import com.egm.stellio.search.common.util.*
import com.egm.stellio.shared.config.ApplicationProperties
import com.egm.stellio.shared.model.*
import com.egm.stellio.shared.util.*
@@ -30,8 +30,7 @@ import java.net.URI
class EntityAccessRightsService(
private val applicationProperties: ApplicationProperties,
private val databaseClient: DatabaseClient,
- private val subjectReferentialService: SubjectReferentialService,
- private val entityPayloadService: EntityPayloadService
+ private val subjectReferentialService: SubjectReferentialService
) {
@Transactional
suspend fun setReadRoleOnEntity(sub: Sub, entityId: URI): Either =
@@ -143,7 +142,7 @@ class EntityAccessRightsService(
subjectReferentialService.hasStellioAdminRole(subjectUuids)
.flatMap {
if (!it)
- entityPayloadService.hasSpecificAccessPolicies(entityId, specificAccessPolicies)
+ hasSpecificAccessPolicies(entityId, specificAccessPolicies)
else true.right()
}.flatMap {
if (!it)
@@ -152,6 +151,26 @@ class EntityAccessRightsService(
}.bind()
}
+ suspend fun hasSpecificAccessPolicies(
+ entityId: URI,
+ specificAccessPolicies: List
+ ): Either {
+ if (specificAccessPolicies.isEmpty())
+ return either { false }
+
+ return databaseClient.sql(
+ """
+ SELECT count(entity_id) as count
+ FROM entity_payload
+ WHERE entity_id = :entity_id
+ AND specific_access_policy IN (:specific_access_policies)
+ """.trimIndent()
+ )
+ .bind("entity_id", entityId)
+ .bind("specific_access_policies", specificAccessPolicies.map { it.toString() })
+ .oneToResult { it["count"] as Long > 0 }
+ }
+
private suspend fun hasDirectAccessRightOnEntity(
uuids: List,
entityId: URI,
@@ -337,6 +356,35 @@ class EntityAccessRightsService(
.allToMappedList { toUri(it["entity_id"]) }
}
+ suspend fun updateSpecificAccessPolicy(
+ entityId: URI,
+ ngsiLdAttribute: NgsiLdAttribute
+ ): Either = either {
+ val specificAccessPolicy = ngsiLdAttribute.getSpecificAccessPolicy().bind()
+ databaseClient.sql(
+ """
+ UPDATE entity_payload
+ SET specific_access_policy = :specific_access_policy
+ WHERE entity_id = :entity_id
+ """.trimIndent()
+ )
+ .bind("entity_id", entityId)
+ .bind("specific_access_policy", specificAccessPolicy.toString())
+ .execute()
+ .bind()
+ }
+
+ suspend fun removeSpecificAccessPolicy(entityId: URI): Either =
+ databaseClient.sql(
+ """
+ UPDATE entity_payload
+ SET specific_access_policy = null
+ WHERE entity_id = :entity_id
+ """.trimIndent()
+ )
+ .bind("entity_id", entityId)
+ .execute()
+
@Transactional
suspend fun delete(sub: Sub): Either =
databaseClient
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/SubjectReferentialService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/SubjectReferentialService.kt
similarity index 97%
rename from search-service/src/main/kotlin/com/egm/stellio/search/authorization/SubjectReferentialService.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/SubjectReferentialService.kt
index ba9bbc8dc..5cd0c844f 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/authorization/SubjectReferentialService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/service/SubjectReferentialService.kt
@@ -1,10 +1,13 @@
-package com.egm.stellio.search.authorization
+package com.egm.stellio.search.authorization.service
import arrow.core.Either
import arrow.core.Option
import arrow.core.Some
import arrow.core.getOrElse
-import com.egm.stellio.search.util.*
+import com.egm.stellio.search.authorization.model.Group
+import com.egm.stellio.search.authorization.model.SubjectReferential
+import com.egm.stellio.search.authorization.model.User
+import com.egm.stellio.search.common.util.*
import com.egm.stellio.shared.model.APIException
import com.egm.stellio.shared.model.AccessDeniedException
import com.egm.stellio.shared.util.*
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityAccessControlHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/web/EntityAccessControlHandler.kt
similarity index 94%
rename from search-service/src/main/kotlin/com/egm/stellio/search/web/EntityAccessControlHandler.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/authorization/web/EntityAccessControlHandler.kt
index 1e4532ebf..bb1784eac 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityAccessControlHandler.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/authorization/web/EntityAccessControlHandler.kt
@@ -1,11 +1,14 @@
-package com.egm.stellio.search.web
+package com.egm.stellio.search.authorization.web
import arrow.core.*
import arrow.core.raise.either
-import com.egm.stellio.search.authorization.*
-import com.egm.stellio.search.model.*
-import com.egm.stellio.search.service.EntityPayloadService
-import com.egm.stellio.search.util.composeEntitiesQuery
+import com.egm.stellio.search.authorization.service.AuthorizationService
+import com.egm.stellio.search.authorization.service.EntityAccessRightsService
+import com.egm.stellio.search.entity.model.NotUpdatedDetails
+import com.egm.stellio.search.entity.model.UpdateAttributeResult
+import com.egm.stellio.search.entity.model.UpdateOperationResult
+import com.egm.stellio.search.entity.model.updateResultFromDetailedResult
+import com.egm.stellio.search.entity.util.composeEntitiesQuery
import com.egm.stellio.shared.config.ApplicationProperties
import com.egm.stellio.shared.model.*
import com.egm.stellio.shared.util.*
@@ -36,7 +39,6 @@ import kotlin.collections.flatten
class EntityAccessControlHandler(
private val applicationProperties: ApplicationProperties,
private val entityAccessRightsService: EntityAccessRightsService,
- private val entityPayloadService: EntityPayloadService,
private val authorizationService: AuthorizationService
) : BaseHandler() {
@@ -298,7 +300,7 @@ class EntityAccessControlHandler(
val ngsiLdAttribute = expandedAttribute.toNgsiLdAttribute().bind()
- entityPayloadService.updateSpecificAccessPolicy(entityId, ngsiLdAttribute).bind()
+ entityAccessRightsService.updateSpecificAccessPolicy(entityId, ngsiLdAttribute).bind()
ResponseEntity.status(HttpStatus.NO_CONTENT).build()
}.fold(
@@ -314,7 +316,7 @@ class EntityAccessControlHandler(
authorizationService.userCanAdminEntity(entityId, sub).bind()
- entityPayloadService.removeSpecificAccessPolicy(entityId).bind()
+ entityAccessRightsService.removeSpecificAccessPolicy(entityId).bind()
ResponseEntity.status(HttpStatus.NO_CONTENT).build()
}.fold(
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/config/KafkaConfig.kt b/search-service/src/main/kotlin/com/egm/stellio/search/common/config/KafkaConfig.kt
similarity index 95%
rename from search-service/src/main/kotlin/com/egm/stellio/search/config/KafkaConfig.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/common/config/KafkaConfig.kt
index dc315993f..1f82df6b5 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/config/KafkaConfig.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/common/config/KafkaConfig.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.config
+package com.egm.stellio.search.common.config
import org.springframework.boot.autoconfigure.kafka.ConcurrentKafkaListenerContainerFactoryConfigurer
import org.springframework.context.annotation.Bean
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/config/SearchProperties.kt b/search-service/src/main/kotlin/com/egm/stellio/search/common/config/SearchProperties.kt
similarity index 83%
rename from search-service/src/main/kotlin/com/egm/stellio/search/config/SearchProperties.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/common/config/SearchProperties.kt
index 8de9ac5f1..9c7528233 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/config/SearchProperties.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/common/config/SearchProperties.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.config
+package com.egm.stellio.search.common.config
import org.springframework.boot.context.properties.ConfigurationProperties
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/config/WebConfig.kt b/search-service/src/main/kotlin/com/egm/stellio/search/common/config/WebConfig.kt
similarity index 93%
rename from search-service/src/main/kotlin/com/egm/stellio/search/config/WebConfig.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/common/config/WebConfig.kt
index b55387b80..8e98dbc65 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/config/WebConfig.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/common/config/WebConfig.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.config
+package com.egm.stellio.search.common.config
import org.springframework.context.annotation.Configuration
import org.springframework.http.codec.ServerCodecConfigurer
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/Query.kt b/search-service/src/main/kotlin/com/egm/stellio/search/common/model/Query.kt
similarity index 97%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/Query.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/common/model/Query.kt
index 4c7167977..32ab3d5e5 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/Query.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/common/model/Query.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.common.model
import arrow.core.Either
import arrow.core.left
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/support/ApiTestsBootstrapper.kt b/search-service/src/main/kotlin/com/egm/stellio/search/common/support/ApiTestsBootstrapper.kt
similarity index 95%
rename from search-service/src/main/kotlin/com/egm/stellio/search/support/ApiTestsBootstrapper.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/common/support/ApiTestsBootstrapper.kt
index 9ef861aa3..cfd605c61 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/support/ApiTestsBootstrapper.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/common/support/ApiTestsBootstrapper.kt
@@ -1,7 +1,7 @@
-package com.egm.stellio.search.support
+package com.egm.stellio.search.common.support
-import com.egm.stellio.search.authorization.SubjectReferential
-import com.egm.stellio.search.authorization.SubjectReferentialService
+import com.egm.stellio.search.authorization.model.SubjectReferential
+import com.egm.stellio.search.authorization.service.SubjectReferentialService
import com.egm.stellio.shared.util.GlobalRole
import com.egm.stellio.shared.util.SubjectType
import io.r2dbc.postgresql.codec.Json
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/tenant/DatabaseMigration.kt b/search-service/src/main/kotlin/com/egm/stellio/search/common/tenant/DatabaseMigration.kt
similarity index 97%
rename from search-service/src/main/kotlin/com/egm/stellio/search/tenant/DatabaseMigration.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/common/tenant/DatabaseMigration.kt
index 355724f2c..1d9647a4b 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/tenant/DatabaseMigration.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/common/tenant/DatabaseMigration.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.tenant
+package com.egm.stellio.search.common.tenant
import com.egm.stellio.shared.config.ApplicationProperties
import jakarta.annotation.PostConstruct
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/tenant/DatabaseTenantConfig.kt b/search-service/src/main/kotlin/com/egm/stellio/search/common/tenant/DatabaseTenantConfig.kt
similarity index 98%
rename from search-service/src/main/kotlin/com/egm/stellio/search/tenant/DatabaseTenantConfig.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/common/tenant/DatabaseTenantConfig.kt
index e4186f54f..755e56a03 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/tenant/DatabaseTenantConfig.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/common/tenant/DatabaseTenantConfig.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.tenant
+package com.egm.stellio.search.common.tenant
import com.egm.stellio.shared.config.ApplicationProperties
import io.r2dbc.spi.ConnectionFactories
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/tenant/DatabaseTenantConnectionFactory.kt b/search-service/src/main/kotlin/com/egm/stellio/search/common/tenant/DatabaseTenantConnectionFactory.kt
similarity index 96%
rename from search-service/src/main/kotlin/com/egm/stellio/search/tenant/DatabaseTenantConnectionFactory.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/common/tenant/DatabaseTenantConnectionFactory.kt
index 2f2198326..c90cf5865 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/tenant/DatabaseTenantConnectionFactory.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/common/tenant/DatabaseTenantConnectionFactory.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.tenant
+package com.egm.stellio.search.common.tenant
import com.egm.stellio.shared.config.ApplicationProperties
import com.egm.stellio.shared.model.NonexistentTenantException
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/common/util/DBConversionUtils.kt
similarity index 82%
rename from search-service/src/main/kotlin/com/egm/stellio/search/util/DBConversionUtils.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/common/util/DBConversionUtils.kt
index d81b48770..b90fa6abc 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/util/DBConversionUtils.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/common/util/DBConversionUtils.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.util
+package com.egm.stellio.search.common.util
import com.egm.stellio.shared.model.ExpandedAttributeInstance
import com.egm.stellio.shared.util.JsonUtils.deserializeAsMap
@@ -33,3 +33,17 @@ fun Json.deserializeExpandedPayload(): Map> = this.asString().
fun Json.deserializeAsMap(): Map = this.asString().deserializeAsMap()
fun ExpandedAttributeInstance.toJson(): Json = Json.of(serializeObject(this))
+
+fun valueToDoubleOrNull(value: Any): Double? =
+ when (value) {
+ is Double -> value
+ is Int -> value.toDouble()
+ else -> null
+ }
+
+fun valueToStringOrNull(value: Any): String? =
+ when (value) {
+ is String -> value
+ is Boolean -> value.toString()
+ else -> null
+ }
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/DBQueryUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/common/util/DBQueryUtils.kt
similarity index 98%
rename from search-service/src/main/kotlin/com/egm/stellio/search/util/DBQueryUtils.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/common/util/DBQueryUtils.kt
index 2114dab2b..cab99490c 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/util/DBQueryUtils.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/common/util/DBQueryUtils.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.util
+package com.egm.stellio.search.common.util
import arrow.core.Either
import arrow.core.left
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeDetails.kt b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/AttributeDetails.kt
similarity index 78%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeDetails.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/AttributeDetails.kt
index ac7f6f5ee..c7705ac82 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeDetails.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/AttributeDetails.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.discovery.model
import java.net.URI
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeList.kt b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/AttributeList.kt
similarity index 84%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeList.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/AttributeList.kt
index 42814cbd9..7cd511b88 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeList.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/AttributeList.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.discovery.model
import com.egm.stellio.shared.util.toUri
import java.net.URI
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeTypeInfo.kt b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/AttributeTypeInfo.kt
similarity index 83%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeTypeInfo.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/AttributeTypeInfo.kt
index 24048d0ce..388387bda 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeTypeInfo.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/AttributeTypeInfo.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.discovery.model
import java.net.URI
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityType.kt b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/EntityType.kt
similarity index 77%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/EntityType.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/EntityType.kt
index cade8ab18..14f4cf3b5 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityType.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/EntityType.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.discovery.model
import java.net.URI
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/discovery/model/EntityTypeInfo.kt
similarity index 94%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/EntityTypeInfo.kt
index 0f940695c..9b5c3eb1f 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/EntityTypeInfo.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.discovery.model
import java.net.URI
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeList.kt b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/EntityTypeList.kt
similarity index 84%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeList.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/EntityTypeList.kt
index 8a6f092a3..c6c2041e5 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeList.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/model/EntityTypeList.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.discovery.model
import com.egm.stellio.shared.util.toUri
import java.net.URI
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/AttributeService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/service/AttributeService.kt
similarity index 88%
rename from search-service/src/main/kotlin/com/egm/stellio/search/service/AttributeService.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/discovery/service/AttributeService.kt
index 363cdf661..eb9aecf56 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/service/AttributeService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/service/AttributeService.kt
@@ -1,17 +1,17 @@
-package com.egm.stellio.search.service
+package com.egm.stellio.search.discovery.service
import arrow.core.Either
import arrow.core.flatten
import arrow.core.left
import arrow.core.right
-import com.egm.stellio.search.model.AttributeDetails
-import com.egm.stellio.search.model.AttributeList
-import com.egm.stellio.search.model.AttributeType
-import com.egm.stellio.search.model.AttributeTypeInfo
-import com.egm.stellio.search.util.allToMappedList
-import com.egm.stellio.search.util.toInt
-import com.egm.stellio.search.util.toList
-import com.egm.stellio.search.util.toUri
+import com.egm.stellio.search.common.util.allToMappedList
+import com.egm.stellio.search.common.util.toInt
+import com.egm.stellio.search.common.util.toList
+import com.egm.stellio.search.common.util.toUri
+import com.egm.stellio.search.discovery.model.AttributeDetails
+import com.egm.stellio.search.discovery.model.AttributeList
+import com.egm.stellio.search.discovery.model.AttributeType
+import com.egm.stellio.search.discovery.model.AttributeTypeInfo
import com.egm.stellio.shared.model.APIException
import com.egm.stellio.shared.model.ExpandedTerm
import com.egm.stellio.shared.model.ResourceNotFoundException
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/discovery/service/EntityTypeService.kt
similarity index 93%
rename from search-service/src/main/kotlin/com/egm/stellio/search/service/EntityTypeService.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/discovery/service/EntityTypeService.kt
index 40b6c1092..b1a515eac 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityTypeService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/service/EntityTypeService.kt
@@ -1,12 +1,12 @@
-package com.egm.stellio.search.service
+package com.egm.stellio.search.discovery.service
import arrow.core.Either
import arrow.core.left
import arrow.core.right
-import com.egm.stellio.search.model.*
-import com.egm.stellio.search.util.allToMappedList
-import com.egm.stellio.search.util.toInt
-import com.egm.stellio.search.util.toUri
+import com.egm.stellio.search.common.util.allToMappedList
+import com.egm.stellio.search.common.util.toInt
+import com.egm.stellio.search.common.util.toUri
+import com.egm.stellio.search.discovery.model.*
import com.egm.stellio.shared.model.APIException
import com.egm.stellio.shared.model.ExpandedTerm
import com.egm.stellio.shared.model.ResourceNotFoundException
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/web/AttributeHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/AttributeHandler.kt
similarity index 95%
rename from search-service/src/main/kotlin/com/egm/stellio/search/web/AttributeHandler.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/AttributeHandler.kt
index 29a05e0b1..06783a2b2 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/web/AttributeHandler.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/AttributeHandler.kt
@@ -1,7 +1,7 @@
-package com.egm.stellio.search.web
+package com.egm.stellio.search.discovery.web
import arrow.core.raise.either
-import com.egm.stellio.search.service.AttributeService
+import com.egm.stellio.search.discovery.service.AttributeService
import com.egm.stellio.shared.config.ApplicationProperties
import com.egm.stellio.shared.util.*
import com.egm.stellio.shared.util.JsonLdUtils.expandJsonLdTerm
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityTypeHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/EntityTypeHandler.kt
similarity index 95%
rename from search-service/src/main/kotlin/com/egm/stellio/search/web/EntityTypeHandler.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/EntityTypeHandler.kt
index 01a15cc0f..7b079329d 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityTypeHandler.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/discovery/web/EntityTypeHandler.kt
@@ -1,7 +1,7 @@
-package com.egm.stellio.search.web
+package com.egm.stellio.search.discovery.web
import arrow.core.raise.either
-import com.egm.stellio.search.service.EntityTypeService
+import com.egm.stellio.search.discovery.service.EntityTypeService
import com.egm.stellio.shared.config.ApplicationProperties
import com.egm.stellio.shared.util.*
import com.egm.stellio.shared.util.JsonLdUtils.expandJsonLdTerm
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/entity/listener/ObservationEventListener.kt
similarity index 94%
rename from search-service/src/main/kotlin/com/egm/stellio/search/listener/ObservationEventListener.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/listener/ObservationEventListener.kt
index f04afeeaa..7ea31de4f 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/listener/ObservationEventListener.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/listener/ObservationEventListener.kt
@@ -1,10 +1,10 @@
-package com.egm.stellio.search.listener
+package com.egm.stellio.search.entity.listener
import arrow.core.Either
import arrow.core.left
import arrow.core.raise.either
-import com.egm.stellio.search.service.EntityEventService
-import com.egm.stellio.search.service.EntityPayloadService
+import com.egm.stellio.search.entity.service.EntityEventService
+import com.egm.stellio.search.entity.service.EntityService
import com.egm.stellio.shared.model.*
import com.egm.stellio.shared.util.JsonLdUtils.expandAttribute
import com.egm.stellio.shared.util.JsonLdUtils.expandJsonLdEntity
@@ -23,7 +23,7 @@ import reactor.core.publisher.Mono
@Component
class ObservationEventListener(
- private val entityPayloadService: EntityPayloadService,
+ private val entityService: EntityService,
private val entityEventService: EntityEventService
) {
private val logger = LoggerFactory.getLogger(javaClass)
@@ -70,7 +70,7 @@ class ObservationEventListener(
val ngsiLdEntity = expandedEntity.toNgsiLdEntity().bind()
mono {
- entityPayloadService.createEntity(
+ entityService.createEntity(
ngsiLdEntity,
expandedEntity,
observationEvent.sub
@@ -95,7 +95,7 @@ class ObservationEventListener(
)
mono {
- entityPayloadService.partialUpdateAttribute(
+ entityService.partialUpdateAttribute(
observationEvent.entityId,
expandedAttribute,
observationEvent.sub
@@ -132,7 +132,7 @@ class ObservationEventListener(
)
mono {
- entityPayloadService.appendAttributes(
+ entityService.appendAttributes(
observationEvent.entityId,
expandedAttribute.toExpandedAttributes(),
!observationEvent.overwrite,
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/entity/model/Attribute.kt
similarity index 95%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/model/Attribute.kt
index a04f1da97..bca913e99 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/Attribute.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.entity.model
import com.egm.stellio.shared.model.ExpandedTerm
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_TYPE
@@ -19,7 +19,7 @@ import java.net.URI
import java.time.ZonedDateTime
import java.util.UUID
-data class TemporalEntityAttribute(
+data class Attribute(
@Id
val id: UUID = UUID.randomUUID(),
val entityId: URI,
@@ -66,7 +66,7 @@ data class TemporalEntityAttribute(
/**
* Returns the key of the member for the simplified representation of the attribute, as defined in 4.5.9
*/
- fun toSimpliedRepresentationKey(): String =
+ fun toSimplifiedRepresentationKey(): String =
when (this) {
Property -> NGSILD_PROPERTY_VALUES
Relationship -> NGSILD_RELATIONSHIP_OBJECTS
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeMetadata.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/AttributeMetadata.kt
similarity index 65%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeMetadata.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/model/AttributeMetadata.kt
index e7c60d060..8797978c3 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeMetadata.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/AttributeMetadata.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.entity.model
import com.egm.stellio.shared.model.WKTCoordinates
import java.net.URI
@@ -8,8 +8,8 @@ data class AttributeMetadata(
val measuredValue: Double?,
val value: String?,
val geoValue: WKTCoordinates?,
- val valueType: TemporalEntityAttribute.AttributeValueType,
+ val valueType: Attribute.AttributeValueType,
val datasetId: URI?,
- val type: TemporalEntityAttribute.AttributeType,
+ val type: Attribute.AttributeType,
val observedAt: ZonedDateTime?
)
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntitiesQuery.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/EntitiesQuery.kt
similarity index 93%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/EntitiesQuery.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/model/EntitiesQuery.kt
index d7d3706a5..9a1e80113 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntitiesQuery.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/EntitiesQuery.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.entity.model
import com.egm.stellio.shared.model.EntityTypeSelection
import com.egm.stellio.shared.model.ExpandedTerm
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/entity/model/Entity.kt
similarity index 96%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/EntityPayload.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/model/Entity.kt
index 5f8d986d7..e81816479 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityPayload.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/Entity.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.entity.model
import com.egm.stellio.shared.model.ExpandedTerm
import com.egm.stellio.shared.util.AuthContextModel
@@ -15,7 +15,7 @@ import io.r2dbc.postgresql.codec.Json
import java.net.URI
import java.time.ZonedDateTime
-data class EntityPayload(
+data class Entity(
val entityId: URI,
val types: List,
val scopes: List? = null,
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/OperationType.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/OperationType.kt
similarity index 89%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/OperationType.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/model/OperationType.kt
index b1a0045c0..f7c824f11 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/OperationType.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/OperationType.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.entity.model
/**
* Used to manage the different types of updates on existing attributes depending on the current operation.
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/UpdateResult.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/UpdateResult.kt
similarity index 98%
rename from search-service/src/main/kotlin/com/egm/stellio/search/model/UpdateResult.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/model/UpdateResult.kt
index ddb61f626..9ccf6a575 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/model/UpdateResult.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/UpdateResult.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.model
+package com.egm.stellio.search.entity.model
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonValue
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/entity/service/EntityAttributeService.kt
similarity index 84%
rename from search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityAttributeService.kt
index bbcc52997..e4e7f02e7 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityAttributeService.kt
@@ -1,4 +1,4 @@
-package com.egm.stellio.search.service
+package com.egm.stellio.search.entity.service
import arrow.core.Either
import arrow.core.flatMap
@@ -7,8 +7,17 @@ import arrow.core.raise.either
import arrow.core.raise.ensure
import arrow.core.right
import arrow.fx.coroutines.parMap
-import com.egm.stellio.search.model.*
-import com.egm.stellio.search.util.*
+import com.egm.stellio.search.common.util.*
+import com.egm.stellio.search.entity.model.*
+import com.egm.stellio.search.entity.model.Attribute
+import com.egm.stellio.search.entity.util.guessAttributeValueType
+import com.egm.stellio.search.entity.util.mergePatch
+import com.egm.stellio.search.entity.util.partialUpdatePatch
+import com.egm.stellio.search.entity.util.prepareAttributes
+import com.egm.stellio.search.entity.util.toAttributeMetadata
+import com.egm.stellio.search.entity.util.toExpandedAttributeInstance
+import com.egm.stellio.search.temporal.model.AttributeInstance
+import com.egm.stellio.search.temporal.service.AttributeInstanceService
import com.egm.stellio.shared.model.*
import com.egm.stellio.shared.util.*
import com.egm.stellio.shared.util.AttributeType
@@ -35,7 +44,7 @@ import java.time.ZonedDateTime
import java.util.UUID
@Service
-class TemporalEntityAttributeService(
+class EntityAttributeService(
private val databaseClient: DatabaseClient,
private val attributeInstanceService: AttributeInstanceService
) {
@@ -43,7 +52,7 @@ class TemporalEntityAttributeService(
private val logger = LoggerFactory.getLogger(javaClass)
@Transactional
- suspend fun create(temporalEntityAttribute: TemporalEntityAttribute): Either =
+ suspend fun create(attribute: Attribute): Either =
databaseClient.sql(
"""
INSERT INTO temporal_entity_attribute
@@ -54,19 +63,19 @@ class TemporalEntityAttributeService(
:payload)
""".trimIndent()
)
- .bind("id", temporalEntityAttribute.id)
- .bind("entity_id", temporalEntityAttribute.entityId)
- .bind("attribute_name", temporalEntityAttribute.attributeName)
- .bind("attribute_type", temporalEntityAttribute.attributeType.toString())
- .bind("attribute_value_type", temporalEntityAttribute.attributeValueType.toString())
- .bind("created_at", temporalEntityAttribute.createdAt)
- .bind("dataset_id", temporalEntityAttribute.datasetId)
- .bind("payload", temporalEntityAttribute.payload)
+ .bind("id", attribute.id)
+ .bind("entity_id", attribute.entityId)
+ .bind("attribute_name", attribute.attributeName)
+ .bind("attribute_type", attribute.attributeType.toString())
+ .bind("attribute_value_type", attribute.attributeValueType.toString())
+ .bind("created_at", attribute.createdAt)
+ .bind("dataset_id", attribute.datasetId)
+ .bind("payload", attribute.payload)
.execute()
@Transactional
suspend fun updateOnReplace(
- teaUUID: UUID,
+ attributeUUID: UUID,
attributeMetadata: AttributeMetadata,
modifiedAt: ZonedDateTime,
payload: String
@@ -82,7 +91,7 @@ class TemporalEntityAttributeService(
WHERE id = :id
""".trimIndent()
)
- .bind("id", teaUUID)
+ .bind("id", attributeUUID)
.bind("attribute_type", attributeMetadata.type.toString())
.bind("attribute_value_type", attributeMetadata.valueType.toString())
.bind("modified_at", modifiedAt)
@@ -91,8 +100,8 @@ class TemporalEntityAttributeService(
@Transactional
suspend fun updateOnUpdate(
- teaUUID: UUID,
- valueType: TemporalEntityAttribute.AttributeValueType,
+ attributeUUID: UUID,
+ valueType: Attribute.AttributeValueType,
modifiedAt: ZonedDateTime,
payload: String
): Either =
@@ -102,10 +111,10 @@ class TemporalEntityAttributeService(
SET payload = :payload,
attribute_value_type = :attribute_value_type,
modified_at = :modified_at
- WHERE id = :tea_uuid
+ WHERE id = :attribute_uuid
""".trimIndent()
)
- .bind("tea_uuid", teaUUID)
+ .bind("attribute_uuid", attributeUUID)
.bind("payload", Json.of(payload))
.bind("attribute_value_type", valueType.toString())
.bind("modified_at", modifiedAt)
@@ -117,7 +126,7 @@ class TemporalEntityAttributeService(
* To be removed at some point later.
*/
@Transactional
- suspend fun createEntityTemporalReferences(
+ suspend fun createAttributes(
payload: String,
contexts: List,
sub: String? = null
@@ -125,14 +134,14 @@ class TemporalEntityAttributeService(
val createdAt = ZonedDateTime.now(ZoneOffset.UTC)
val expandedEntity = expandJsonLdEntity(payload, contexts)
val ngsiLdEntity = expandedEntity.toNgsiLdEntity().bind()
- ngsiLdEntity.prepareTemporalAttributes()
+ ngsiLdEntity.prepareAttributes()
.map {
- createEntityTemporalReferences(ngsiLdEntity, expandedEntity, it, createdAt, sub).bind()
+ createAttributes(ngsiLdEntity, expandedEntity, it, createdAt, sub).bind()
}.bind()
}
@Transactional
- suspend fun createEntityTemporalReferences(
+ suspend fun createAttributes(
ngsiLdEntity: NgsiLdEntity,
expandedEntity: ExpandedEntity,
attributesMetadata: List>,
@@ -174,7 +183,7 @@ class TemporalEntityAttributeService(
): Either =
either {
logger.debug("Adding attribute {} to entity {}", attributeName, entityId)
- val temporalEntityAttribute = TemporalEntityAttribute(
+ val attribute = Attribute(
entityId = entityId,
attributeName = attributeName,
attributeType = attributeMetadata.type,
@@ -183,10 +192,10 @@ class TemporalEntityAttributeService(
createdAt = createdAt,
payload = Json.of(serializeObject(attributePayload))
)
- create(temporalEntityAttribute).bind()
+ create(attribute).bind()
val attributeInstance = AttributeInstance(
- temporalEntityAttribute = temporalEntityAttribute.id,
+ attributeUuid = attribute.id,
timeProperty = AttributeInstance.TemporalProperty.CREATED_AT,
time = createdAt,
attributeMetadata = attributeMetadata,
@@ -197,7 +206,7 @@ class TemporalEntityAttributeService(
if (attributeMetadata.observedAt != null) {
val attributeObservedAtInstance = AttributeInstance(
- temporalEntityAttribute = temporalEntityAttribute.id,
+ attributeUuid = attribute.id,
time = attributeMetadata.observedAt,
attributeMetadata = attributeMetadata,
payload = attributePayload
@@ -208,7 +217,7 @@ class TemporalEntityAttributeService(
@Transactional
suspend fun replaceAttribute(
- temporalEntityAttribute: TemporalEntityAttribute,
+ attribute: Attribute,
ngsiLdAttribute: NgsiLdAttribute,
attributeMetadata: AttributeMetadata,
createdAt: ZonedDateTime,
@@ -219,17 +228,17 @@ class TemporalEntityAttributeService(
"Replacing attribute {} ({}) in entity {}",
ngsiLdAttribute.name,
attributeMetadata.datasetId,
- temporalEntityAttribute.entityId
+ attribute.entityId
)
updateOnReplace(
- temporalEntityAttribute.id,
+ attribute.id,
attributeMetadata,
createdAt,
serializeObject(attributePayload)
).bind()
val attributeInstance = AttributeInstance(
- temporalEntityAttribute = temporalEntityAttribute.id,
+ attributeUuid = attribute.id,
timeProperty = AttributeInstance.TemporalProperty.MODIFIED_AT,
time = createdAt,
attributeMetadata = attributeMetadata,
@@ -240,7 +249,7 @@ class TemporalEntityAttributeService(
if (attributeMetadata.observedAt != null) {
val attributeObservedAtInstance = AttributeInstance(
- temporalEntityAttribute = temporalEntityAttribute.id,
+ attributeUuid = attribute.id,
time = attributeMetadata.observedAt,
attributeMetadata = attributeMetadata,
payload = attributePayload
@@ -251,7 +260,7 @@ class TemporalEntityAttributeService(
@Transactional
suspend fun mergeAttribute(
- tea: TemporalEntityAttribute,
+ attribute: Attribute,
attributeName: ExpandedTerm,
attributeMetadata: AttributeMetadata,
mergedAt: ZonedDateTime,
@@ -263,26 +272,26 @@ class TemporalEntityAttributeService(
"Merging attribute {} ({}) in entity {}",
attributeName,
attributeMetadata.datasetId,
- tea.entityId
+ attribute.entityId
)
val (processedAttributePayload, processedAttributeMetadata) = processObservedAtInMergeOperation(
- tea,
+ attribute,
attributePayload,
attributeMetadata,
observedAt
)
val (jsonTargetObject, updatedAttributeInstance) =
- mergePatch(tea.payload.toExpandedAttributeInstance(), processedAttributePayload)
- val value = getValueFromPartialAttributePayload(tea, updatedAttributeInstance)
- updateOnUpdate(tea.id, processedAttributeMetadata.valueType, mergedAt, jsonTargetObject).bind()
+ mergePatch(attribute.payload.toExpandedAttributeInstance(), processedAttributePayload)
+ val value = getValueFromPartialAttributePayload(attribute, updatedAttributeInstance)
+ updateOnUpdate(attribute.id, processedAttributeMetadata.valueType, mergedAt, jsonTargetObject).bind()
val attributeInstance =
- createContextualAttributeInstance(tea, updatedAttributeInstance, value, mergedAt, sub)
+ createContextualAttributeInstance(attribute, updatedAttributeInstance, value, mergedAt, sub)
attributeInstanceService.create(attributeInstance).bind()
}
@Transactional
- suspend fun deleteTemporalAttributesOfEntity(entityId: URI): Either {
+ suspend fun deleteAttributes(entityId: URI): Either {
val uuids = databaseClient.sql(
"""
DELETE FROM temporal_entity_attribute
@@ -301,7 +310,7 @@ class TemporalEntityAttributeService(
}
@Transactional
- suspend fun deleteTemporalAttribute(
+ suspend fun deleteAttribute(
entityId: URI,
attributeName: String,
datasetId: URI?,
@@ -311,15 +320,15 @@ class TemporalEntityAttributeService(
logger.debug("Deleting attribute {} from entity {} (all: {})", attributeName, entityId, deleteAll)
if (deleteAll) {
attributeInstanceService.deleteAllInstancesOfAttribute(entityId, attributeName).bind()
- deleteTemporalAttributeAllInstancesReferences(entityId, attributeName).bind()
+ deleteAllInstances(entityId, attributeName).bind()
} else {
attributeInstanceService.deleteInstancesOfAttribute(entityId, attributeName, datasetId).bind()
- deleteTemporalAttributeReferences(entityId, attributeName, datasetId).bind()
+ deleteSpecificInstance(entityId, attributeName, datasetId).bind()
}
}
@Transactional
- suspend fun deleteTemporalAttributeReferences(
+ suspend fun deleteSpecificInstance(
entityId: URI,
attributeName: String,
datasetId: URI?
@@ -341,7 +350,7 @@ class TemporalEntityAttributeService(
.execute()
@Transactional
- suspend fun deleteTemporalAttributeAllInstancesReferences(
+ suspend fun deleteAllInstances(
entityId: URI,
attributeName: String
): Either =
@@ -356,10 +365,10 @@ class TemporalEntityAttributeService(
.bind("attribute_name", attributeName)
.execute()
- suspend fun getForTemporalEntities(
+ suspend fun getForEntities(
entitiesIds: List,
entitiesQuery: EntitiesQuery
- ): List {
+ ): List {
val filterOnAttributes =
if (entitiesQuery.attrs.isNotEmpty())
" AND " + entitiesQuery.attrs.joinToString(
@@ -390,10 +399,10 @@ class TemporalEntityAttributeService(
return databaseClient
.sql(selectQuery)
.bind("entities_ids", entitiesIds)
- .allToMappedList { rowToTemporalEntityAttribute(it) }
+ .allToMappedList { rowToAttribute(it) }
}
- suspend fun getForEntity(id: URI, attrs: Set, datasetIds: Set): List {
+ suspend fun getForEntity(id: URI, attrs: Set, datasetIds: Set): List {
val filterOnAttributes =
if (attrs.isNotEmpty())
" AND " + attrs.joinToString(
@@ -423,14 +432,14 @@ class TemporalEntityAttributeService(
return databaseClient
.sql(selectQuery)
.bind("entity_id", id)
- .allToMappedList { rowToTemporalEntityAttribute(it) }
+ .allToMappedList { rowToAttribute(it) }
}
suspend fun getForEntityAndAttribute(
id: URI,
attributeName: String,
datasetId: URI? = null
- ): Either {
+ ): Either {
val selectQuery =
"""
SELECT *
@@ -449,7 +458,7 @@ class TemporalEntityAttributeService(
else it
}
.oneToResult {
- rowToTemporalEntityAttribute(it)
+ rowToAttribute(it)
}
}
@@ -480,13 +489,13 @@ class TemporalEntityAttributeService(
}
}
- private fun rowToTemporalEntityAttribute(row: Map) =
- TemporalEntityAttribute(
+ private fun rowToAttribute(row: Map) =
+ Attribute(
id = toUuid(row["id"]),
entityId = toUri(row["entity_id"]),
attributeName = row["attribute_name"] as ExpandedTerm,
- attributeType = TemporalEntityAttribute.AttributeType.valueOf(row["attribute_type"] as String),
- attributeValueType = TemporalEntityAttribute.AttributeValueType.valueOf(
+ attributeType = Attribute.AttributeType.valueOf(row["attribute_type"] as String),
+ attributeValueType = Attribute.AttributeValueType.valueOf(
row["attribute_value_type"] as String
),
datasetId = toOptionalUri(row["dataset_id"]),
@@ -536,7 +545,7 @@ class TemporalEntityAttributeService(
}
@Transactional
- suspend fun appendEntityAttributes(
+ suspend fun appendAttributes(
entityUri: URI,
ngsiLdAttributes: List,
expandedAttributes: ExpandedAttributes,
@@ -547,15 +556,15 @@ class TemporalEntityAttributeService(
val attributeInstances = ngsiLdAttributes.flatOnInstances()
attributeInstances.parMap { (ngsiLdAttribute, ngsiLdAttributeInstance) ->
logger.debug("Appending attribute {} in entity {}", ngsiLdAttribute.name, entityUri)
- val currentTea =
+ val currentAttribute =
getForEntityAndAttribute(entityUri, ngsiLdAttribute.name, ngsiLdAttributeInstance.datasetId)
.fold({ null }, { it })
- val attributeMetadata = ngsiLdAttributeInstance.toTemporalAttributeMetadata().bind()
+ val attributeMetadata = ngsiLdAttributeInstance.toAttributeMetadata().bind()
val attributePayload = expandedAttributes.getAttributeFromExpandedAttributes(
ngsiLdAttribute.name,
ngsiLdAttributeInstance.datasetId
)!!
- if (currentTea == null) {
+ if (currentAttribute == null) {
addAttribute(
entityUri,
ngsiLdAttribute.name,
@@ -582,7 +591,7 @@ class TemporalEntityAttributeService(
).right().bind()
} else {
replaceAttribute(
- currentTea,
+ currentAttribute,
ngsiLdAttribute,
attributeMetadata,
createdAt,
@@ -601,7 +610,7 @@ class TemporalEntityAttributeService(
}.fold({ it.left() }, { updateResultFromDetailedResult(it).right() })
@Transactional
- suspend fun updateEntityAttributes(
+ suspend fun updateAttributes(
entityUri: URI,
ngsiLdAttributes: List,
expandedAttributes: ExpandedAttributes,
@@ -611,17 +620,17 @@ class TemporalEntityAttributeService(
val attributeInstances = ngsiLdAttributes.flatOnInstances()
attributeInstances.parMap { (ngsiLdAttribute, ngsiLdAttributeInstance) ->
logger.debug("Updating attribute {} in entity {}", ngsiLdAttribute.name, entityUri)
- val currentTea =
+ val currentAttribute =
getForEntityAndAttribute(entityUri, ngsiLdAttribute.name, ngsiLdAttributeInstance.datasetId)
.fold({ null }, { it })
- val attributeMetadata = ngsiLdAttributeInstance.toTemporalAttributeMetadata().bind()
+ val attributeMetadata = ngsiLdAttributeInstance.toAttributeMetadata().bind()
val attributePayload = expandedAttributes.getAttributeFromExpandedAttributes(
ngsiLdAttribute.name,
ngsiLdAttributeInstance.datasetId
)!!
- if (currentTea != null) {
+ if (currentAttribute != null) {
replaceAttribute(
- currentTea,
+ currentAttribute,
ngsiLdAttribute,
attributeMetadata,
createdAt,
@@ -656,7 +665,7 @@ class TemporalEntityAttributeService(
}.fold({ it.left() }, { updateResultFromDetailedResult(it).right() })
@Transactional
- suspend fun partialUpdateEntityAttribute(
+ suspend fun partialUpdateAttribute(
entityId: URI,
expandedAttribute: ExpandedAttribute,
modifiedAt: ZonedDateTime,
@@ -676,21 +685,21 @@ class TemporalEntityAttributeService(
val updateAttributeResult =
if (exists) {
// first update payload in temporal entity attribute
- val tea = getForEntityAndAttribute(entityId, attributeName, datasetId).bind()
+ val attribute = getForEntityAndAttribute(entityId, attributeName, datasetId).bind()
attributeValues[JSONLD_TYPE]?.let {
- ensure(isAttributeOfType(attributeValues, AttributeType(NGSILD_PREFIX + tea.attributeType))) {
+ ensure(isAttributeOfType(attributeValues, AttributeType(NGSILD_PREFIX + attribute.attributeType))) {
BadRequestDataException("The type of the attribute has to be the same as the existing one")
}
}
val (jsonTargetObject, updatedAttributeInstance) =
- partialUpdatePatch(tea.payload.toExpandedAttributeInstance(), attributeValues)
- val value = getValueFromPartialAttributePayload(tea, updatedAttributeInstance)
- val attributeValueType = guessAttributeValueType(tea.attributeType, attributeValues)
- updateOnUpdate(tea.id, attributeValueType, modifiedAt, jsonTargetObject).bind()
+ partialUpdatePatch(attribute.payload.toExpandedAttributeInstance(), attributeValues)
+ val value = getValueFromPartialAttributePayload(attribute, updatedAttributeInstance)
+ val attributeValueType = guessAttributeValueType(attribute.attributeType, attributeValues)
+ updateOnUpdate(attribute.id, attributeValueType, modifiedAt, jsonTargetObject).bind()
// then update attribute instance
val attributeInstance = createContextualAttributeInstance(
- tea,
+ attribute,
updatedAttributeInstance,
value,
modifiedAt,
@@ -717,7 +726,7 @@ class TemporalEntityAttributeService(
}
@Transactional
- suspend fun upsertEntityAttributes(
+ suspend fun upsertAttributes(
entityUri: URI,
ngsiLdAttribute: NgsiLdAttribute,
expandedAttributes: ExpandedAttributes,
@@ -726,16 +735,16 @@ class TemporalEntityAttributeService(
): Either = either {
val ngsiLdAttributeInstance = ngsiLdAttribute.getAttributeInstances()[0]
logger.debug("Upserting temporal attribute {} in entity {}", ngsiLdAttribute.name, entityUri)
- val currentTea =
+ val currentAttribute =
getForEntityAndAttribute(entityUri, ngsiLdAttribute.name, ngsiLdAttributeInstance.datasetId)
.fold({ null }, { it })
- val attributeMetadata = ngsiLdAttributeInstance.toTemporalAttributeMetadata().bind()
+ val attributeMetadata = ngsiLdAttributeInstance.toAttributeMetadata().bind()
val attributePayload = expandedAttributes.getAttributeFromExpandedAttributes(
ngsiLdAttribute.name,
ngsiLdAttributeInstance.datasetId
)!!
- if (currentTea == null) {
+ if (currentAttribute == null) {
logger.debug(
"Creating attribute and instance for attribute {} in entity {}",
ngsiLdAttribute.name,
@@ -750,17 +759,17 @@ class TemporalEntityAttributeService(
sub
).bind()
} else {
- logger.debug("Adding instance to attribute {} to entity {}", currentTea.attributeName, entityUri)
+ logger.debug("Adding instance to attribute {} to entity {}", currentAttribute.attributeName, entityUri)
attributeInstanceService.addAttributeInstance(
- currentTea.id,
+ currentAttribute.id,
attributeMetadata,
- expandedAttributes[currentTea.attributeName]!!.first()
+ expandedAttributes[currentAttribute.attributeName]!!.first()
).bind()
}
}
@Transactional
- suspend fun mergeEntityAttributes(
+ suspend fun mergeAttributes(
entityUri: URI,
ngsiLdAttributes: List,
expandedAttributes: ExpandedAttributes,
@@ -771,16 +780,16 @@ class TemporalEntityAttributeService(
val attributeInstances = ngsiLdAttributes.flatOnInstances()
attributeInstances.parMap { (ngsiLdAttribute, ngsiLdAttributeInstance) ->
logger.debug("Merging attribute {} in entity {}", ngsiLdAttribute.name, entityUri)
- val currentTea =
+ val currentAttribute =
getForEntityAndAttribute(entityUri, ngsiLdAttribute.name, ngsiLdAttributeInstance.datasetId)
.fold({ null }, { it })
- val attributeMetadata = ngsiLdAttributeInstance.toTemporalAttributeMetadata().bind()
+ val attributeMetadata = ngsiLdAttributeInstance.toAttributeMetadata().bind()
val attributePayload = expandedAttributes.getAttributeFromExpandedAttributes(
ngsiLdAttribute.name,
ngsiLdAttributeInstance.datasetId
)!!
- if (currentTea == null) {
+ if (currentAttribute == null) {
addAttribute(
entityUri,
ngsiLdAttribute.name,
@@ -798,7 +807,7 @@ class TemporalEntityAttributeService(
}.bind()
} else {
mergeAttribute(
- currentTea,
+ currentAttribute,
ngsiLdAttribute.name,
attributeMetadata,
createdAt,
@@ -818,7 +827,7 @@ class TemporalEntityAttributeService(
}.fold({ it.left() }, { updateResultFromDetailedResult(it).right() })
@Transactional
- suspend fun replaceEntityAttribute(
+ suspend fun replaceAttribute(
entityId: URI,
ngsiLdAttribute: NgsiLdAttribute,
expandedAttribute: ExpandedAttribute,
@@ -830,7 +839,7 @@ class TemporalEntityAttributeService(
val datasetId = ngsiLdAttributeInstance.datasetId
val currentTea =
getForEntityAndAttribute(entityId, attributeName, datasetId).fold({ null }, { it })
- val attributeMetadata = ngsiLdAttributeInstance.toTemporalAttributeMetadata().bind()
+ val attributeMetadata = ngsiLdAttributeInstance.toAttributeMetadata().bind()
val updateAttributeResult =
if (currentTea == null) {
UpdateAttributeResult(
@@ -861,41 +870,41 @@ class TemporalEntityAttributeService(
}
suspend fun getValueFromPartialAttributePayload(
- tea: TemporalEntityAttribute,
+ attribute: Attribute,
attributePayload: ExpandedAttributeInstance
): Triple =
- when (tea.attributeType) {
- TemporalEntityAttribute.AttributeType.Property ->
+ when (attribute.attributeType) {
+ Attribute.AttributeType.Property ->
Triple(
valueToStringOrNull(attributePayload.getPropertyValue()!!),
valueToDoubleOrNull(attributePayload.getPropertyValue()!!),
null
)
- TemporalEntityAttribute.AttributeType.Relationship ->
+ Attribute.AttributeType.Relationship ->
Triple(
attributePayload.getMemberValue(NGSILD_RELATIONSHIP_OBJECT)!! as String,
null,
null
)
- TemporalEntityAttribute.AttributeType.GeoProperty ->
+ Attribute.AttributeType.GeoProperty ->
Triple(
null,
null,
WKTCoordinates(attributePayload.getPropertyValue()!! as String)
)
- TemporalEntityAttribute.AttributeType.JsonProperty ->
+ Attribute.AttributeType.JsonProperty ->
Triple(
serializeObject(attributePayload.getMemberValue(NGSILD_JSONPROPERTY_VALUE)!!),
null,
null
)
- TemporalEntityAttribute.AttributeType.LanguageProperty ->
+ Attribute.AttributeType.LanguageProperty ->
Triple(
serializeObject(attributePayload.getMemberValue(NGSILD_LANGUAGEPROPERTY_VALUE)!!),
null,
null
)
- TemporalEntityAttribute.AttributeType.VocabProperty ->
+ Attribute.AttributeType.VocabProperty ->
Triple(
serializeObject(attributePayload.getMemberValue(NGSILD_VOCABPROPERTY_VALUE)!!),
null,
@@ -904,7 +913,7 @@ class TemporalEntityAttributeService(
}
private fun createContextualAttributeInstance(
- tea: TemporalEntityAttribute,
+ attribute: Attribute,
expandedAttributeInstance: ExpandedAttributeInstance,
value: Triple,
modifiedAt: ZonedDateTime,
@@ -920,7 +929,7 @@ class TemporalEntityAttributeService(
Pair(modifiedAt, AttributeInstance.TemporalProperty.MODIFIED_AT)
return AttributeInstance(
- temporalEntityAttribute = tea.id,
+ attributeUuid = attribute.id,
timeAndProperty = timeAndProperty,
value = value,
payload = expandedAttributeInstance,
@@ -935,14 +944,14 @@ class TemporalEntityAttributeService(
* "observedAt" sub-Attribute.
*/
internal fun processObservedAtInMergeOperation(
- tea: TemporalEntityAttribute,
+ attribute: Attribute,
attributePayload: ExpandedAttributeInstance,
attributeMetadata: AttributeMetadata,
observedAt: ZonedDateTime?
): Pair =
if (
observedAt != null &&
- tea.payload.deserializeAsMap().containsKey(NGSILD_OBSERVED_AT_PROPERTY) &&
+ attribute.payload.deserializeAsMap().containsKey(NGSILD_OBSERVED_AT_PROPERTY) &&
!attributePayload.containsKey(NGSILD_OBSERVED_AT_PROPERTY)
)
Pair(
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/entity/service/EntityEventService.kt
similarity index 94%
rename from search-service/src/main/kotlin/com/egm/stellio/search/service/EntityEventService.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityEventService.kt
index e84f688a5..0627084f6 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityEventService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityEventService.kt
@@ -1,10 +1,10 @@
-package com.egm.stellio.search.service
+package com.egm.stellio.search.entity.service
import arrow.core.Either
-import com.egm.stellio.search.model.EntityPayload
-import com.egm.stellio.search.model.UpdateOperationResult
-import com.egm.stellio.search.model.UpdateResult
-import com.egm.stellio.search.model.UpdatedDetails
+import com.egm.stellio.search.entity.model.Entity
+import com.egm.stellio.search.entity.model.UpdateOperationResult
+import com.egm.stellio.search.entity.model.UpdateResult
+import com.egm.stellio.search.entity.model.UpdatedDetails
import com.egm.stellio.shared.model.*
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE
import com.egm.stellio.shared.util.JsonUtils.serializeObject
@@ -21,7 +21,7 @@ import java.net.URI
@Component
class EntityEventService(
private val kafkaTemplate: KafkaTemplate,
- private val entityPayloadService: EntityPayloadService
+ private val entityQueryService: EntityQueryService
) {
private val catchAllTopic = "cim.entity._CatchAll"
@@ -71,18 +71,18 @@ class EntityEventService(
suspend fun publishEntityDeleteEvent(
sub: String?,
- entityPayload: EntityPayload
+ entity: Entity
): Job {
val tenantName = getTenantFromContext()
return coroutineScope.launch {
- logger.debug("Sending delete event for entity {} in tenant {}", entityPayload.entityId, tenantName)
+ logger.debug("Sending delete event for entity {} in tenant {}", entity.entityId, tenantName)
publishEntityEvent(
EntityDeleteEvent(
sub,
tenantName,
- entityPayload.entityId,
- entityPayload.types,
- entityPayload.payload.asString(),
+ entity.entityId,
+ entity.types,
+ entity.payload.asString(),
emptyList()
)
)
@@ -232,7 +232,7 @@ class EntityEventService(
internal suspend fun getSerializedEntity(
entityId: URI
): Either, String>> =
- entityPayloadService.retrieve(entityId)
+ entityQueryService.retrieve(entityId)
.map {
Pair(it.types, it.payload.asString())
}
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/entity/service/EntityOperationService.kt
similarity index 73%
rename from search-service/src/main/kotlin/com/egm/stellio/search/service/EntityOperationService.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityOperationService.kt
index 809a2c7f1..284339302 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityOperationService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityOperationService.kt
@@ -1,13 +1,11 @@
-package com.egm.stellio.search.service
+package com.egm.stellio.search.entity.service
import arrow.core.Either
import arrow.core.left
import arrow.core.raise.either
import arrow.core.right
-import com.egm.stellio.search.authorization.AuthorizationService
-import com.egm.stellio.search.model.EntityPayload
-import com.egm.stellio.search.model.UpdateResult
-import com.egm.stellio.search.web.*
+import com.egm.stellio.search.entity.model.UpdateResult
+import com.egm.stellio.search.entity.web.*
import com.egm.stellio.shared.model.APIException
import com.egm.stellio.shared.model.BadRequestDataException
import com.egm.stellio.shared.util.Sub
@@ -20,10 +18,9 @@ import java.net.URI
*/
@Component
class EntityOperationService(
- private val entityPayloadService: EntityPayloadService,
- private val temporalEntityAttributeService: TemporalEntityAttributeService,
- private val authorizationService: AuthorizationService,
- private val entityEventService: EntityEventService
+ private val entityService: EntityService,
+ private val entityQueryService: EntityQueryService,
+ private val entityAttributeService: EntityAttributeService,
) {
/**
@@ -49,7 +46,7 @@ class EntityOperationService(
extractIdFunc: (T) -> URI
): Pair, List> {
val existingEntitiesIds =
- entityPayloadService.filterExistingEntitiesAsIds(entities.map { extractIdFunc.invoke(it) })
+ entityQueryService.filterExistingEntitiesAsIds(entities.map { extractIdFunc.invoke(it) })
return entities.partition { existingEntitiesIds.contains(extractIdFunc.invoke(it)) }
}
@@ -86,18 +83,11 @@ class EntityOperationService(
): BatchOperationResult {
val creationResults = entities.map { jsonLdNgsiLdEntity ->
either {
- entityPayloadService.createEntity(jsonLdNgsiLdEntity.second, jsonLdNgsiLdEntity.first, sub)
- .onRight {
- entityEventService.publishEntityCreateEvent(
- sub,
- jsonLdNgsiLdEntity.second.id,
- jsonLdNgsiLdEntity.second.types
- )
- }.map {
- BatchEntitySuccess(jsonLdNgsiLdEntity.entityId())
- }.mapLeft { apiException ->
- BatchEntityError(jsonLdNgsiLdEntity.entityId(), arrayListOf(apiException.message))
- }.bind()
+ entityService.createEntity(jsonLdNgsiLdEntity.second, jsonLdNgsiLdEntity.first, sub).map {
+ BatchEntitySuccess(jsonLdNgsiLdEntity.entityId())
+ }.mapLeft { apiException ->
+ BatchEntityError(jsonLdNgsiLdEntity.entityId(), arrayListOf(apiException.message))
+ }.bind()
}
}.fold(
initial = Pair(listOf(), listOf()),
@@ -112,22 +102,15 @@ class EntityOperationService(
return BatchOperationResult(creationResults.second.toMutableList(), creationResults.first.toMutableList())
}
- suspend fun delete(entities: Set, sub: Sub?): BatchOperationResult {
- val deletionResults = entities.map { entity ->
- val entityId = entity.entityId
+ suspend fun delete(entitiesId: List, sub: Sub?): BatchOperationResult {
+ val deletionResults = entitiesId.map { id ->
either {
- entityPayloadService.deleteEntity(entityId)
- .onRight {
- authorizationService.removeRightsOnEntity(entityId)
- }
- .onRight {
- entityEventService.publishEntityDeleteEvent(sub, entity)
- }
+ entityService.deleteEntity(id, sub)
.map {
- BatchEntitySuccess(entityId)
+ BatchEntitySuccess(id)
}
.mapLeft { apiException ->
- BatchEntityError(entityId, arrayListOf(apiException.message))
+ BatchEntityError(id, arrayListOf(apiException.message))
}.bind()
}
}.fold(
@@ -155,6 +138,42 @@ class EntityOperationService(
suspend fun replace(entities: List, sub: Sub?): BatchOperationResult =
processEntities(entities, false, sub, ::replaceEntity)
+ /**
+ * Upsert a batch of [entities]
+ *
+ * @return a [BatchOperationResult] with list of replaced ids and list of errors.
+ */
+ @Transactional
+ suspend fun upsert(
+ entities: List,
+ options: String?,
+ sub: Sub?
+ ): Pair> {
+ val (existingEntities, newEntities) = splitEntitiesByExistence(entities)
+
+ val (newUniqueEntities, duplicatedEntities) = splitEntitiesByUniqueness(newEntities)
+ val existingOrDuplicatedEntities = existingEntities.plus(duplicatedEntities)
+ val batchOperationResult = BatchOperationResult()
+
+ val createdIds = if (newUniqueEntities.isNotEmpty()) {
+ val createOperationResult = create(newUniqueEntities, sub)
+ batchOperationResult.errors.addAll(createOperationResult.errors)
+ batchOperationResult.success.addAll(createOperationResult.success)
+ createOperationResult.success.map { it.entityId }
+ } else emptyList()
+
+ if (existingOrDuplicatedEntities.isNotEmpty()) {
+ val updateOperationResult = when (options) {
+ "update" -> update(existingOrDuplicatedEntities, false, sub)
+ else -> replace(existingOrDuplicatedEntities, sub)
+ }
+
+ batchOperationResult.errors.addAll(updateOperationResult.errors)
+ batchOperationResult.success.addAll(updateOperationResult.success)
+ }
+ return batchOperationResult to createdIds
+ }
+
/**
* Updates a batch of [entities]
*
@@ -234,19 +253,13 @@ class EntityOperationService(
sub: Sub?
): Either = either {
val (jsonLdEntity, ngsiLdEntity) = entity
- temporalEntityAttributeService.deleteTemporalAttributesOfEntity(ngsiLdEntity.id).bind()
- entityPayloadService.appendAttributes(
+ entityAttributeService.deleteAttributes(ngsiLdEntity.id).bind()
+ entityService.appendAttributes(
ngsiLdEntity.id,
jsonLdEntity.getModifiableMembers(),
disallowOverwrite,
sub
- ).bind().also {
- entityEventService.publishEntityReplaceEvent(
- sub,
- ngsiLdEntity.id,
- ngsiLdEntity.types
- )
- }
+ ).bind()
}
suspend fun updateEntity(
@@ -255,20 +268,12 @@ class EntityOperationService(
sub: Sub?
): Either = either {
val (jsonLdEntity, ngsiLdEntity) = entity
- entityPayloadService.appendAttributes(
+ entityService.appendAttributes(
ngsiLdEntity.id,
jsonLdEntity.getModifiableMembers(),
disallowOverwrite,
sub
- ).bind().also {
- entityEventService.publishAttributeChangeEvents(
- sub,
- ngsiLdEntity.id,
- jsonLdEntity.members,
- it,
- true
- )
- }
+ ).bind()
}
@SuppressWarnings("UnusedParameter")
@@ -278,19 +283,11 @@ class EntityOperationService(
sub: Sub?
): Either = either {
val (jsonLdEntity, ngsiLdEntity) = entity
- entityPayloadService.mergeEntity(
+ entityService.mergeEntity(
ngsiLdEntity.id,
jsonLdEntity.getModifiableMembers(),
null,
sub
- ).bind().also {
- entityEventService.publishAttributeChangeEvents(
- sub,
- ngsiLdEntity.id,
- jsonLdEntity.members,
- it,
- true
- )
- }
+ ).bind()
}
}
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityQueryService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityQueryService.kt
new file mode 100644
index 000000000..e7961bc90
--- /dev/null
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityQueryService.kt
@@ -0,0 +1,225 @@
+package com.egm.stellio.search.entity.service
+
+import arrow.core.*
+import arrow.core.raise.either
+import com.egm.stellio.search.authorization.service.AuthorizationService
+import com.egm.stellio.search.common.util.*
+import com.egm.stellio.search.entity.model.EntitiesQuery
+import com.egm.stellio.search.entity.model.Entity
+import com.egm.stellio.search.entity.util.rowToEntity
+import com.egm.stellio.shared.model.APIException
+import com.egm.stellio.shared.model.AlreadyExistsException
+import com.egm.stellio.shared.model.ExpandedEntity
+import com.egm.stellio.shared.model.ResourceNotFoundException
+import com.egm.stellio.shared.util.*
+import org.springframework.r2dbc.core.DatabaseClient
+import org.springframework.stereotype.Service
+import java.net.URI
+
+@Service
+class EntityQueryService(
+ private val databaseClient: DatabaseClient,
+ private val authorizationService: AuthorizationService
+) {
+ suspend fun queryEntity(
+ entityId: URI,
+ sub: Sub? = null
+ ): Either = either {
+ checkEntityExistence(entityId).bind()
+ authorizationService.userCanReadEntity(entityId, sub.toOption()).bind()
+
+ val entityPayload = retrieve(entityId).bind()
+ toJsonLdEntity(entityPayload)
+ }
+
+ suspend fun queryEntities(
+ entitiesQuery: EntitiesQuery,
+ sub: Sub? = null
+ ): Either, Int>> = either {
+ val accessRightFilter = authorizationService.computeAccessRightFilter(sub.toOption())
+
+ val entitiesIds = queryEntities(entitiesQuery, accessRightFilter)
+ val count = queryEntitiesCount(entitiesQuery, accessRightFilter).bind()
+
+ // we can have an empty list of entities with a non-zero count (e.g., offset too high)
+ if (entitiesIds.isEmpty())
+ return@either Pair, Int>(emptyList(), count)
+
+ val entitiesPayloads = retrieve(entitiesIds).map { toJsonLdEntity(it) }
+
+ Pair(entitiesPayloads, count).right().bind()
+ }
+
+ private fun toJsonLdEntity(entity: Entity): ExpandedEntity {
+ val deserializedEntity = entity.payload.deserializeAsMap()
+ return ExpandedEntity(deserializedEntity)
+ }
+
+ suspend fun queryEntities(
+ entitiesQuery: EntitiesQuery,
+ accessRightFilter: () -> String?
+ ): List {
+ val filterQuery = buildFullEntitiesFilter(entitiesQuery, accessRightFilter)
+
+ val selectQuery =
+ """
+ SELECT DISTINCT(entity_payload.entity_id)
+ FROM entity_payload
+ LEFT JOIN temporal_entity_attribute tea
+ ON tea.entity_id = entity_payload.entity_id
+ WHERE $filterQuery
+ ORDER BY entity_id
+ LIMIT :limit
+ OFFSET :offset
+ """.trimIndent()
+
+ return databaseClient
+ .sql(selectQuery)
+ .bind("limit", entitiesQuery.paginationQuery.limit)
+ .bind("offset", entitiesQuery.paginationQuery.offset)
+ .allToMappedList { toUri(it["entity_id"]) }
+ }
+
+ suspend fun queryEntitiesCount(
+ entitiesQuery: EntitiesQuery,
+ accessRightFilter: () -> String?
+ ): Either {
+ val filterQuery = buildFullEntitiesFilter(entitiesQuery, accessRightFilter)
+
+ val countQuery =
+ """
+ SELECT count(distinct(entity_payload.entity_id)) as count_entity
+ FROM entity_payload
+ LEFT JOIN temporal_entity_attribute tea
+ ON tea.entity_id = entity_payload.entity_id
+ WHERE $filterQuery
+ """.trimIndent()
+
+ return databaseClient
+ .sql(countQuery)
+ .oneToResult { it["count_entity"] as Long }
+ .map { it.toInt() }
+ }
+
+ private fun buildFullEntitiesFilter(entitiesQuery: EntitiesQuery, accessRightFilter: () -> String?): String =
+ buildEntitiesQueryFilter(
+ entitiesQuery,
+ accessRightFilter
+ ).let {
+ if (entitiesQuery.q != null)
+ it.wrapToAndClause(buildQQuery(entitiesQuery.q, entitiesQuery.contexts))
+ else it
+ }.let {
+ if (entitiesQuery.scopeQ != null)
+ it.wrapToAndClause(buildScopeQQuery(entitiesQuery.scopeQ))
+ else it
+ }.let {
+ if (entitiesQuery.geoQuery != null)
+ it.wrapToAndClause(buildGeoQuery(entitiesQuery.geoQuery))
+ else it
+ }
+
+ fun buildEntitiesQueryFilter(
+ entitiesQuery: EntitiesQuery,
+ accessRightFilter: () -> String?
+ ): String {
+ val formattedIds =
+ if (entitiesQuery.ids.isNotEmpty())
+ entitiesQuery.ids.joinToString(
+ separator = ",",
+ prefix = "entity_payload.entity_id in(",
+ postfix = ")"
+ ) { "'$it'" }
+ else null
+ val formattedIdPattern =
+ if (!entitiesQuery.idPattern.isNullOrEmpty())
+ "entity_payload.entity_id ~ '${entitiesQuery.idPattern}'"
+ else null
+ val formattedType = entitiesQuery.typeSelection?.let { "(" + buildTypeQuery(it) + ")" }
+ val formattedAttrs =
+ if (entitiesQuery.attrs.isNotEmpty())
+ entitiesQuery.attrs.joinToString(
+ separator = ",",
+ prefix = "attribute_name in (",
+ postfix = ")"
+ ) { "'$it'" }
+ else null
+
+ val queryFilter =
+ listOfNotNull(
+ formattedIds,
+ formattedIdPattern,
+ formattedType,
+ formattedAttrs,
+ accessRightFilter()
+ )
+
+ return queryFilter.joinToString(separator = " AND ")
+ }
+
+ suspend fun retrieve(entityId: URI): Either =
+ databaseClient.sql(
+ """
+ SELECT * from entity_payload
+ WHERE entity_id = :entity_id
+ """.trimIndent()
+ )
+ .bind("entity_id", entityId)
+ .oneToResult { it.rowToEntity() }
+
+ suspend fun retrieve(entitiesIds: List): List =
+ databaseClient.sql(
+ """
+ SELECT * from entity_payload
+ WHERE entity_id IN (:entities_ids)
+ """.trimIndent()
+ )
+ .bind("entities_ids", entitiesIds)
+ .allToMappedList { it.rowToEntity() }
+
+ suspend fun checkEntityExistence(
+ entityId: URI,
+ inverse: Boolean = false
+ ): Either {
+ val selectQuery =
+ """
+ select
+ exists(
+ select 1
+ from entity_payload
+ where entity_id = :entity_id
+ ) as entityExists;
+ """.trimIndent()
+
+ return databaseClient
+ .sql(selectQuery)
+ .bind("entity_id", entityId)
+ .oneToResult { it["entityExists"] as Boolean }
+ .flatMap {
+ if (it && !inverse || !it && inverse)
+ Unit.right()
+ else if (it)
+ AlreadyExistsException(entityAlreadyExistsMessage(entityId.toString())).left()
+ else
+ ResourceNotFoundException(entityNotFoundMessage(entityId.toString())).left()
+ }
+ }
+
+ suspend fun filterExistingEntitiesAsIds(entitiesIds: List): List {
+ if (entitiesIds.isEmpty()) {
+ return emptyList()
+ }
+
+ val query =
+ """
+ select entity_id
+ from entity_payload
+ where entity_id in (:entities_ids)
+ """.trimIndent()
+
+ return databaseClient
+ .sql(query)
+ .bind("entities_ids", entitiesIds)
+ .allToMappedList { toUri(it["entity_id"]) }
+ }
+}
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/entity/service/EntityService.kt
similarity index 51%
rename from search-service/src/main/kotlin/com/egm/stellio/search/service/EntityPayloadService.kt
rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityService.kt
index 58c9bc2d5..eeffa1164 100644
--- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityPayloadService.kt
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/service/EntityService.kt
@@ -1,17 +1,17 @@
-package com.egm.stellio.search.service
+package com.egm.stellio.search.entity.service
-import arrow.core.Either
-import arrow.core.flatMap
-import arrow.core.left
+import arrow.core.*
import arrow.core.raise.either
-import arrow.core.right
-import com.egm.stellio.search.model.*
-import com.egm.stellio.search.model.OperationType.*
+import com.egm.stellio.search.authorization.service.AuthorizationService
+import com.egm.stellio.search.common.util.*
+import com.egm.stellio.search.entity.model.*
+import com.egm.stellio.search.entity.model.Attribute
+import com.egm.stellio.search.entity.model.OperationType.*
+import com.egm.stellio.search.entity.util.prepareAttributes
+import com.egm.stellio.search.entity.util.rowToEntity
import com.egm.stellio.search.scope.ScopeService
-import com.egm.stellio.search.util.*
import com.egm.stellio.shared.model.*
import com.egm.stellio.shared.util.*
-import com.egm.stellio.shared.util.AuthContextModel.SpecificAccessPolicy
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_EXPANDED_ENTITY_SPECIFIC_MEMBERS
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE
import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_SCOPE_PROPERTY
@@ -27,10 +27,13 @@ import java.time.ZoneOffset
import java.time.ZonedDateTime
@Service
-class EntityPayloadService(
+class EntityService(
private val databaseClient: DatabaseClient,
- private val temporalEntityAttributeService: TemporalEntityAttributeService,
- private val scopeService: ScopeService
+ private val entityQueryService: EntityQueryService,
+ private val entityAttributeService: EntityAttributeService,
+ private val scopeService: ScopeService,
+ private val entityEventService: EntityEventService,
+ private val authorizationService: AuthorizationService
) {
private val logger = LoggerFactory.getLogger(javaClass)
@@ -38,20 +41,30 @@ class EntityPayloadService(
suspend fun createEntity(
ngsiLdEntity: NgsiLdEntity,
expandedEntity: ExpandedEntity,
- sub: String? = null
+ sub: Sub? = null
): Either = either {
+ authorizationService.userCanCreateEntities(sub.toOption()).bind()
+ entityQueryService.checkEntityExistence(ngsiLdEntity.id, true).bind()
+
val createdAt = ZonedDateTime.now(ZoneOffset.UTC)
- val attributesMetadata = ngsiLdEntity.prepareTemporalAttributes().bind()
+ val attributesMetadata = ngsiLdEntity.prepareAttributes().bind()
logger.debug("Creating entity {}", ngsiLdEntity.id)
- createEntityPayload(ngsiLdEntity, expandedEntity, createdAt, sub = sub).bind()
- temporalEntityAttributeService.createEntityTemporalReferences(
+ createEntityPayload(ngsiLdEntity, expandedEntity, createdAt, sub).bind()
+ entityAttributeService.createAttributes(
ngsiLdEntity,
expandedEntity,
attributesMetadata,
createdAt,
sub
).bind()
+ authorizationService.createOwnerRight(ngsiLdEntity.id, sub.toOption()).bind()
+
+ entityEventService.publishEntityCreateEvent(
+ sub,
+ ngsiLdEntity.id,
+ ngsiLdEntity.types
+ )
}
@Transactional
@@ -85,16 +98,18 @@ class EntityPayloadService(
entityId: URI,
expandedAttributes: ExpandedAttributes,
observedAt: ZonedDateTime?,
- sub: Sub?
+ sub: Sub? = null
): Either = either {
- logger.debug("Merging entity {}", entityId)
+ entityQueryService.checkEntityExistence(entityId).bind()
+ authorizationService.userCanUpdateEntity(entityId, sub.toOption()).bind()
val (coreAttrs, otherAttrs) =
expandedAttributes.toList().partition { JSONLD_EXPANDED_ENTITY_SPECIFIC_MEMBERS.contains(it.first) }
val mergedAt = ngsiLdDateTime()
+ logger.debug("Merging entity {}", entityId)
val coreUpdateResult = updateCoreAttributes(entityId, coreAttrs, mergedAt, MERGE_ENTITY).bind()
- val attrsUpdateResult = temporalEntityAttributeService.mergeEntityAttributes(
+ val attrsUpdateResult = entityAttributeService.mergeAttributes(
entityId,
otherAttrs.toMap().toNgsiLdAttributes().bind(),
expandedAttributes,
@@ -106,9 +121,20 @@ class EntityPayloadService(
val updateResult = coreUpdateResult.mergeWith(attrsUpdateResult)
// update modifiedAt in entity if at least one attribute has been merged
if (updateResult.hasSuccessfulUpdate()) {
- val teas = temporalEntityAttributeService.getForEntity(entityId, emptySet(), emptySet())
- updateState(entityId, mergedAt, teas).bind()
+ val attributes = entityAttributeService.getForEntity(entityId, emptySet(), emptySet())
+ updateState(entityId, mergedAt, attributes).bind()
+ }
+
+ if (updateResult.updated.isNotEmpty()) {
+ entityEventService.publishAttributeChangeEvents(
+ sub,
+ entityId,
+ expandedAttributes,
+ updateResult,
+ true
+ )
}
+
updateResult
}
@@ -117,22 +143,31 @@ class EntityPayloadService(
entityId: URI,
ngsiLdEntity: NgsiLdEntity,
expandedEntity: ExpandedEntity,
- sub: String? = null
+ sub: Sub? = null
): Either = either {
+ entityQueryService.checkEntityExistence(entityId).bind()
+ authorizationService.userCanUpdateEntity(entityId, sub.toOption()).bind()
+
val replacedAt = ngsiLdDateTime()
- val attributesMetadata = ngsiLdEntity.prepareTemporalAttributes().bind()
+ val attributesMetadata = ngsiLdEntity.prepareAttributes().bind()
logger.debug("Replacing entity {}", ngsiLdEntity.id)
- temporalEntityAttributeService.deleteTemporalAttributesOfEntity(entityId)
+ entityAttributeService.deleteAttributes(entityId)
replaceEntityPayload(ngsiLdEntity, expandedEntity, replacedAt, sub).bind()
- temporalEntityAttributeService.createEntityTemporalReferences(
+ entityAttributeService.createAttributes(
ngsiLdEntity,
expandedEntity,
attributesMetadata,
replacedAt,
sub
).bind()
+
+ entityEventService.publishEntityReplaceEvent(
+ sub,
+ ngsiLdEntity.id,
+ ngsiLdEntity.types
+ )
}
@Transactional
@@ -170,7 +205,7 @@ class EntityPayloadService(
}
}
- suspend fun retrieveCreatedAt(entityId: URI): Either =
+ private suspend fun retrieveCreatedAt(entityId: URI): Either =
databaseClient.sql(
"""
SELECT created_at from entity_payload
@@ -180,221 +215,6 @@ class EntityPayloadService(
.bind("entity_id", entityId)
.oneToResult { toZonedDateTime(it["created_at"]) }
- suspend fun retrieve(entityId: URI): Either =
- databaseClient.sql(
- """
- SELECT * from entity_payload
- WHERE entity_id = :entity_id
- """.trimIndent()
- )
- .bind("entity_id", entityId)
- .oneToResult { rowToEntityPaylaod(it) }
-
- suspend fun retrieve(entitiesIds: List): List =
- databaseClient.sql(
- """
- SELECT * from entity_payload
- WHERE entity_id IN (:entities_ids)
- """.trimIndent()
- )
- .bind("entities_ids", entitiesIds)
- .allToMappedList { rowToEntityPaylaod(it) }
-
- private fun rowToEntityPaylaod(row: Map): EntityPayload =
- EntityPayload(
- entityId = toUri(row["entity_id"]),
- types = toList(row["types"]),
- scopes = toOptionalList(row["scopes"]),
- createdAt = toZonedDateTime(row["created_at"]),
- modifiedAt = toOptionalZonedDateTime(row["modified_at"]),
- payload = toJson(row["payload"]),
- specificAccessPolicy = toOptionalEnum(row["specific_access_policy"])
- )
-
- suspend fun checkEntityExistence(
- entityId: URI,
- inverse: Boolean = false
- ): Either {
- val selectQuery =
- """
- select
- exists(
- select 1
- from entity_payload
- where entity_id = :entity_id
- ) as entityExists;
- """.trimIndent()
-
- return databaseClient
- .sql(selectQuery)
- .bind("entity_id", entityId)
- .oneToResult { it["entityExists"] as Boolean }
- .flatMap {
- if (it && !inverse || !it && inverse)
- Unit.right()
- else if (it)
- AlreadyExistsException(entityAlreadyExistsMessage(entityId.toString())).left()
- else
- ResourceNotFoundException(entityNotFoundMessage(entityId.toString())).left()
- }
- }
-
- suspend fun queryEntities(
- entitiesQuery: EntitiesQuery,
- accessRightFilter: () -> String?
- ): List {
- val filterQuery = buildFullEntitiesFilter(entitiesQuery, accessRightFilter)
-
- val selectQuery =
- """
- SELECT DISTINCT(entity_payload.entity_id)
- FROM entity_payload
- LEFT JOIN temporal_entity_attribute tea
- ON tea.entity_id = entity_payload.entity_id
- WHERE $filterQuery
- ORDER BY entity_id
- LIMIT :limit
- OFFSET :offset
- """.trimIndent()
-
- return databaseClient
- .sql(selectQuery)
- .bind("limit", entitiesQuery.paginationQuery.limit)
- .bind("offset", entitiesQuery.paginationQuery.offset)
- .allToMappedList { toUri(it["entity_id"]) }
- }
-
- suspend fun queryEntitiesCount(
- entitiesQuery: EntitiesQuery,
- accessRightFilter: () -> String?
- ): Either {
- val filterQuery = buildFullEntitiesFilter(entitiesQuery, accessRightFilter)
-
- val countQuery =
- """
- SELECT count(distinct(entity_payload.entity_id)) as count_entity
- FROM entity_payload
- LEFT JOIN temporal_entity_attribute tea
- ON tea.entity_id = entity_payload.entity_id
- WHERE $filterQuery
- """.trimIndent()
-
- return databaseClient
- .sql(countQuery)
- .oneToResult { it["count_entity"] as Long }
- .map { it.toInt() }
- }
-
- private fun buildFullEntitiesFilter(entitiesQuery: EntitiesQuery, accessRightFilter: () -> String?): String =
- buildEntitiesQueryFilter(
- entitiesQuery,
- accessRightFilter
- ).let {
- if (entitiesQuery.q != null)
- it.wrapToAndClause(buildQQuery(entitiesQuery.q, entitiesQuery.contexts))
- else it
- }.let {
- if (entitiesQuery.scopeQ != null)
- it.wrapToAndClause(buildScopeQQuery(entitiesQuery.scopeQ))
- else it
- }.let {
- if (entitiesQuery.geoQuery != null)
- it.wrapToAndClause(buildGeoQuery(entitiesQuery.geoQuery))
- else it
- }
-
- fun buildEntitiesQueryFilter(
- entitiesQuery: EntitiesQuery,
- accessRightFilter: () -> String?
- ): String {
- val formattedIds =
- if (entitiesQuery.ids.isNotEmpty())
- entitiesQuery.ids.joinToString(
- separator = ",",
- prefix = "entity_payload.entity_id in(",
- postfix = ")"
- ) { "'$it'" }
- else null
- val formattedIdPattern =
- if (!entitiesQuery.idPattern.isNullOrEmpty())
- "entity_payload.entity_id ~ '${entitiesQuery.idPattern}'"
- else null
- val formattedType = entitiesQuery.typeSelection?.let { "(" + buildTypeQuery(it) + ")" }
- val formattedAttrs =
- if (entitiesQuery.attrs.isNotEmpty())
- entitiesQuery.attrs.joinToString(
- separator = ",",
- prefix = "attribute_name in (",
- postfix = ")"
- ) { "'$it'" }
- else null
-
- val queryFilter =
- listOfNotNull(
- formattedIds,
- formattedIdPattern,
- formattedType,
- formattedAttrs,
- accessRightFilter()
- )
-
- return queryFilter.joinToString(separator = " AND ")
- }
-
- suspend fun hasSpecificAccessPolicies(
- entityId: URI,
- specificAccessPolicies: List
- ): Either {
- if (specificAccessPolicies.isEmpty())
- return either { false }
-
- return databaseClient.sql(
- """
- SELECT count(entity_id) as count
- FROM entity_payload
- WHERE entity_id = :entity_id
- AND specific_access_policy IN (:specific_access_policies)
- """.trimIndent()
- )
- .bind("entity_id", entityId)
- .bind("specific_access_policies", specificAccessPolicies.map { it.toString() })
- .oneToResult { it["count"] as Long > 0 }
- }
-
- suspend fun filterExistingEntitiesAsIds(entitiesIds: List): List {
- if (entitiesIds.isEmpty()) {
- return emptyList()
- }
-
- val query =
- """
- select entity_id
- from entity_payload
- where entity_id in (:entities_ids)
- """.trimIndent()
-
- return databaseClient
- .sql(query)
- .bind("entities_ids", entitiesIds)
- .allToMappedList { toUri(it["entity_id"]) }
- }
-
- suspend fun getTypes(entityId: URI): Either> {
- val selectQuery =
- """
- SELECT types
- FROM entity_payload
- WHERE entity_id = :entity_id
- """.trimIndent()
-
- return databaseClient
- .sql(selectQuery)
- .bind("entity_id", entityId)
- .oneToResult(ResourceNotFoundException(entityNotFoundMessage(entityId.toString()))) {
- (it["types"] as Array).toList()
- }
- }
-
@Transactional
suspend fun updateCoreAttributes(
entityId: URI,
@@ -424,7 +244,7 @@ class EntityPayloadService(
modifiedAt: ZonedDateTime,
allowEmptyListOfTypes: Boolean = true
): Either = either {
- val entityPayload = retrieve(entityId).bind()
+ val entityPayload = entityQueryService.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)
@@ -466,11 +286,14 @@ class EntityPayloadService(
@Transactional
suspend fun appendAttributes(
- entityUri: URI,
+ entityId: URI,
expandedAttributes: ExpandedAttributes,
disallowOverwrite: Boolean,
- sub: Sub?
+ sub: Sub? = null
): Either = either {
+ entityQueryService.checkEntityExistence(entityId).bind()
+ authorizationService.userCanUpdateEntity(entityId, sub.toOption()).bind()
+
val (coreAttrs, otherAttrs) =
expandedAttributes.toList().partition { JSONLD_EXPANDED_ENTITY_SPECIFIC_MEMBERS.contains(it.first) }
val createdAt = ngsiLdDateTime()
@@ -478,9 +301,9 @@ class EntityPayloadService(
val operationType =
if (disallowOverwrite) APPEND_ATTRIBUTES
else APPEND_ATTRIBUTES_OVERWRITE_ALLOWED
- val coreUpdateResult = updateCoreAttributes(entityUri, coreAttrs, createdAt, operationType).bind()
- val attrsUpdateResult = temporalEntityAttributeService.appendEntityAttributes(
- entityUri,
+ val coreUpdateResult = updateCoreAttributes(entityId, coreAttrs, createdAt, operationType).bind()
+ val attrsUpdateResult = entityAttributeService.appendAttributes(
+ entityId,
otherAttrs.toMap().toNgsiLdAttributes().bind(),
expandedAttributes,
disallowOverwrite,
@@ -491,25 +314,39 @@ class EntityPayloadService(
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(), emptySet())
- updateState(entityUri, createdAt, teas).bind()
+ val attributes = entityAttributeService.getForEntity(entityId, emptySet(), emptySet())
+ updateState(entityId, createdAt, attributes).bind()
+ }
+
+ if (updateResult.hasSuccessfulUpdate()) {
+ entityEventService.publishAttributeChangeEvents(
+ sub,
+ entityId,
+ expandedAttributes,
+ updateResult,
+ true
+ )
}
+
updateResult
}
@Transactional
suspend fun updateAttributes(
- entityUri: URI,
+ entityId: URI,
expandedAttributes: ExpandedAttributes,
- sub: Sub?
+ sub: Sub? = null
): Either = either {
+ entityQueryService.checkEntityExistence(entityId).bind()
+ authorizationService.userCanUpdateEntity(entityId, sub.toOption()).bind()
+
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,
+ val coreUpdateResult = updateCoreAttributes(entityId, coreAttrs, createdAt, UPDATE_ATTRIBUTES).bind()
+ val attrsUpdateResult = entityAttributeService.updateAttributes(
+ entityId,
otherAttrs.toMap().toNgsiLdAttributes().bind(),
expandedAttributes,
createdAt,
@@ -519,9 +356,20 @@ class EntityPayloadService(
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(), emptySet())
- updateState(entityUri, createdAt, teas).bind()
+ val attributes = entityAttributeService.getForEntity(entityId, emptySet(), emptySet())
+ updateState(entityId, createdAt, attributes).bind()
}
+
+ if (updateResult.updated.isNotEmpty()) {
+ entityEventService.publishAttributeChangeEvents(
+ sub,
+ entityId,
+ expandedAttributes,
+ updateResult,
+ true
+ )
+ }
+
updateResult
}
@@ -529,19 +377,34 @@ class EntityPayloadService(
suspend fun partialUpdateAttribute(
entityId: URI,
expandedAttribute: ExpandedAttribute,
- sub: Sub?
+ sub: Sub? = null
): Either = either {
+ entityQueryService.checkEntityExistence(entityId).bind()
+ authorizationService.userCanUpdateEntity(entityId, sub.toOption()).bind()
+
val modifiedAt = ngsiLdDateTime()
- val updateResult = temporalEntityAttributeService.partialUpdateEntityAttribute(
+
+ val updateResult = entityAttributeService.partialUpdateAttribute(
entityId,
expandedAttribute,
modifiedAt,
sub
).bind()
+
if (updateResult.isSuccessful()) {
- val teas = temporalEntityAttributeService.getForEntity(entityId, emptySet(), emptySet())
- updateState(entityId, modifiedAt, teas).bind()
+ val attributes = entityAttributeService.getForEntity(entityId, emptySet(), emptySet())
+ updateState(entityId, modifiedAt, attributes).bind()
}
+
+ if (updateResult.updated.isNotEmpty())
+ entityEventService.publishAttributeChangeEvents(
+ sub,
+ entityId,
+ expandedAttribute.toExpandedAttributes(),
+ updateResult,
+ false
+ )
+
updateResult
}
@@ -549,41 +412,43 @@ class EntityPayloadService(
suspend fun upsertAttributes(
entityId: URI,
expandedAttributes: ExpandedAttributes,
- sub: Sub?
- ): Either =
- either {
- val createdAt = ZonedDateTime.now(ZoneOffset.UTC)
- expandedAttributes.forEach { (attributeName, expandedAttributeInstances) ->
- expandedAttributeInstances.forEach { expandedAttributeInstance ->
- val jsonLdAttribute = mapOf(attributeName to listOf(expandedAttributeInstance))
- val ngsiLdAttribute = jsonLdAttribute.toNgsiLdAttributes().bind()[0]
-
- temporalEntityAttributeService.upsertEntityAttributes(
- entityId,
- ngsiLdAttribute,
- jsonLdAttribute,
- createdAt,
- sub
- ).bind()
- }
+ sub: Sub? = null
+ ): Either = either {
+ val createdAt = ZonedDateTime.now(ZoneOffset.UTC)
+ expandedAttributes.forEach { (attributeName, expandedAttributeInstances) ->
+ expandedAttributeInstances.forEach { expandedAttributeInstance ->
+ val jsonLdAttribute = mapOf(attributeName to listOf(expandedAttributeInstance))
+ val ngsiLdAttribute = jsonLdAttribute.toNgsiLdAttributes().bind()[0]
+
+ entityAttributeService.upsertAttributes(
+ entityId,
+ ngsiLdAttribute,
+ jsonLdAttribute,
+ createdAt,
+ sub
+ ).bind()
}
- updateState(
- entityId,
- createdAt,
- temporalEntityAttributeService.getForEntity(entityId, emptySet(), emptySet())
- ).bind()
}
+ updateState(
+ entityId,
+ createdAt,
+ entityAttributeService.getForEntity(entityId, emptySet(), emptySet())
+ ).bind()
+ }
@Transactional
suspend fun replaceAttribute(
entityId: URI,
expandedAttribute: ExpandedAttribute,
- sub: Sub?
+ sub: Sub? = null
): Either = either {
+ entityQueryService.checkEntityExistence(entityId).bind()
+ authorizationService.userCanUpdateEntity(entityId, sub.toOption()).bind()
+
val ngsiLdAttribute = listOf(expandedAttribute).toMap().toNgsiLdAttributes().bind()[0]
val replacedAt = ngsiLdDateTime()
- val updateResult = temporalEntityAttributeService.replaceEntityAttribute(
+ val updateResult = entityAttributeService.replaceAttribute(
entityId,
ngsiLdAttribute,
expandedAttribute,
@@ -593,9 +458,19 @@ class EntityPayloadService(
// update modifiedAt in entity if at least one attribute has been added
if (updateResult.hasSuccessfulUpdate()) {
- val teas = temporalEntityAttributeService.getForEntity(entityId, emptySet(), emptySet())
- updateState(entityId, replacedAt, teas).bind()
+ val attributes = entityAttributeService.getForEntity(entityId, emptySet(), emptySet())
+ updateState(entityId, replacedAt, attributes).bind()
}
+
+ if (updateResult.updated.isNotEmpty())
+ entityEventService.publishAttributeChangeEvents(
+ sub,
+ entityId,
+ expandedAttribute.toExpandedAttributes(),
+ updateResult,
+ false
+ )
+
updateResult
}
@@ -603,12 +478,12 @@ class EntityPayloadService(
suspend fun updateState(
entityUri: URI,
modifiedAt: ZonedDateTime,
- temporalEntityAttributes: List
+ attributes: List
): Either =
- retrieve(entityUri)
+ entityQueryService.retrieve(entityUri)
.map { entityPayload ->
val payload = buildJsonLdEntity(
- temporalEntityAttributes,
+ attributes,
entityPayload.copy(modifiedAt = modifiedAt)
)
databaseClient.sql(
@@ -626,18 +501,18 @@ class EntityPayloadService(
}
private fun buildJsonLdEntity(
- temporalEntityAttributes: List,
- entityPayload: EntityPayload
+ attributes: List,
+ entity: Entity
): Map {
- val entityCoreAttributes = entityPayload.serializeProperties()
- val expandedAttributes = temporalEntityAttributes
- .groupBy { tea ->
- tea.attributeName
+ val entityCoreAttributes = entity.serializeProperties()
+ val expandedAttributes = attributes
+ .groupBy { attribute ->
+ attribute.attributeName
}
- .mapValues { (_, teas) ->
- teas.map { tea ->
- tea.payload.deserializeExpandedPayload()
- .addSysAttrs(withSysAttrs = true, tea.createdAt, tea.modifiedAt)
+ .mapValues { (_, attributes) ->
+ attributes.map { attribute ->
+ attribute.payload.deserializeExpandedPayload()
+ .addSysAttrs(withSysAttrs = true, attribute.createdAt, attribute.modifiedAt)
}
}
@@ -659,7 +534,21 @@ class EntityPayloadService(
.execute()
@Transactional
- suspend fun deleteEntity(entityId: URI): Either = either {
+ suspend fun deleteEntity(entityId: URI, sub: Sub? = null): Either = either {
+ entityQueryService.checkEntityExistence(entityId).bind()
+ authorizationService.userCanAdminEntity(entityId, sub.toOption()).bind()
+
+ val entity = deleteEntityPayload(entityId).bind()
+
+ entityAttributeService.deleteAttributes(entityId).bind()
+ scopeService.deleteHistory(entityId).bind()
+ authorizationService.removeRightsOnEntity(entityId).bind()
+
+ entityEventService.publishEntityDeleteEvent(sub, entity)
+ }
+
+ @Transactional
+ suspend fun deleteEntityPayload(entityId: URI): Either = either {
val entity = databaseClient.sql(
"""
DELETE FROM entity_payload
@@ -669,12 +558,9 @@ class EntityPayloadService(
)
.bind("entity_id", entityId)
.oneToResult {
- rowToEntityPaylaod(it)
+ it.rowToEntity()
}
.bind()
-
- temporalEntityAttributeService.deleteTemporalAttributesOfEntity(entityId).bind()
- scopeService.deleteHistory(entityId).bind()
entity
}
@@ -683,17 +569,20 @@ class EntityPayloadService(
entityId: URI,
attributeName: ExpandedTerm,
datasetId: URI?,
- deleteAll: Boolean = false
+ deleteAll: Boolean = false,
+ sub: Sub? = null
): Either = either {
+ authorizationService.userCanUpdateEntity(entityId, sub.toOption()).bind()
+
when (attributeName) {
NGSILD_SCOPE_PROPERTY -> scopeService.delete(entityId).bind()
else -> {
- temporalEntityAttributeService.checkEntityAndAttributeExistence(
+ entityAttributeService.checkEntityAndAttributeExistence(
entityId,
attributeName,
datasetId
).bind()
- temporalEntityAttributeService.deleteTemporalAttribute(
+ entityAttributeService.deleteAttribute(
entityId,
attributeName,
datasetId,
@@ -704,36 +593,15 @@ class EntityPayloadService(
updateState(
entityId,
ngsiLdDateTime(),
- temporalEntityAttributeService.getForEntity(entityId, emptySet(), emptySet())
+ entityAttributeService.getForEntity(entityId, emptySet(), emptySet())
).bind()
- }
- suspend fun updateSpecificAccessPolicy(
- entityId: URI,
- ngsiLdAttribute: NgsiLdAttribute
- ): Either = either {
- val specificAccessPolicy = ngsiLdAttribute.getSpecificAccessPolicy().bind()
- databaseClient.sql(
- """
- UPDATE entity_payload
- SET specific_access_policy = :specific_access_policy
- WHERE entity_id = :entity_id
- """.trimIndent()
+ entityEventService.publishAttributeDeleteEvent(
+ sub,
+ entityId,
+ attributeName,
+ datasetId,
+ deleteAll
)
- .bind("entity_id", entityId)
- .bind("specific_access_policy", specificAccessPolicy.toString())
- .execute()
- .bind()
}
-
- suspend fun removeSpecificAccessPolicy(entityId: URI): Either =
- databaseClient.sql(
- """
- UPDATE entity_payload
- SET specific_access_policy = null
- WHERE entity_id = :entity_id
- """.trimIndent()
- )
- .bind("entity_id", entityId)
- .execute()
}
diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/util/AttributeUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/util/AttributeUtils.kt
new file mode 100644
index 000000000..32d2c2c35
--- /dev/null
+++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/util/AttributeUtils.kt
@@ -0,0 +1,190 @@
+package com.egm.stellio.search.entity.util
+
+import arrow.core.Either
+import arrow.core.left
+import arrow.core.raise.either
+import arrow.core.right
+import com.egm.stellio.search.common.util.deserializeAsMap
+import com.egm.stellio.search.common.util.valueToDoubleOrNull
+import com.egm.stellio.search.entity.model.Attribute
+import com.egm.stellio.search.entity.model.AttributeMetadata
+import com.egm.stellio.shared.model.APIException
+import com.egm.stellio.shared.model.BadRequestDataException
+import com.egm.stellio.shared.model.ExpandedAttributeInstance
+import com.egm.stellio.shared.model.NgsiLdAttributeInstance
+import com.egm.stellio.shared.model.NgsiLdEntity
+import com.egm.stellio.shared.model.NgsiLdGeoPropertyInstance
+import com.egm.stellio.shared.model.NgsiLdJsonPropertyInstance
+import com.egm.stellio.shared.model.NgsiLdLanguagePropertyInstance
+import com.egm.stellio.shared.model.NgsiLdPropertyInstance
+import com.egm.stellio.shared.model.NgsiLdRelationshipInstance
+import com.egm.stellio.shared.model.NgsiLdVocabPropertyInstance
+import com.egm.stellio.shared.model.WKTCoordinates
+import com.egm.stellio.shared.model.getPropertyValue
+import com.egm.stellio.shared.util.JsonLdUtils
+import com.egm.stellio.shared.util.JsonUtils
+import com.egm.stellio.shared.util.JsonUtils.deserializeAsMap
+import com.savvasdalkitsis.jsonmerger.JsonMerger
+import io.r2dbc.postgresql.codec.Json
+import java.time.LocalDate
+import java.time.LocalTime
+import java.time.ZonedDateTime
+
+fun NgsiLdEntity.prepareAttributes(): Either>> {
+ val ngsiLdEntity = this
+ return either {
+ ngsiLdEntity.attributes
+ .flatMap { ngsiLdAttribute ->
+ ngsiLdAttribute.getAttributeInstances().map { Pair(ngsiLdAttribute, it) }
+ }
+ .map {
+ Pair(it.first.name, it.second.toAttributeMetadata().bind())
+ }
+ }
+}
+
+fun NgsiLdAttributeInstance.toAttributeMetadata(): Either {
+ val (attributeType, attributeValueType, attributeValue) = when (this) {
+ is NgsiLdPropertyInstance ->
+ guessPropertyValueType(this).let {
+ Triple(Attribute.AttributeType.Property, it.first, it.second)
+ }
+ is NgsiLdRelationshipInstance ->
+ Triple(
+ Attribute.AttributeType.Relationship,
+ Attribute.AttributeValueType.URI,
+ Triple(this.objectId.toString(), null, null)
+ )
+ is NgsiLdGeoPropertyInstance ->
+ Triple(
+ Attribute.AttributeType.GeoProperty,
+ Attribute.AttributeValueType.GEOMETRY,
+ Triple(null, null, this.coordinates)
+ )
+ is NgsiLdJsonPropertyInstance ->
+ Triple(
+ Attribute.AttributeType.JsonProperty,
+ Attribute.AttributeValueType.JSON,
+ Triple(JsonUtils.serializeObject(this.json), null, null)
+ )
+ is NgsiLdLanguagePropertyInstance ->
+ Triple(
+ Attribute.AttributeType.LanguageProperty,
+ Attribute.AttributeValueType.ARRAY,
+ Triple(JsonUtils.serializeObject(this.languageMap), null, null)
+ )
+ is NgsiLdVocabPropertyInstance ->
+ Triple(
+ Attribute.AttributeType.VocabProperty,
+ Attribute.AttributeValueType.ARRAY,
+ Triple(JsonUtils.serializeObject(this.vocab), null, null)
+ )
+ }
+ if (attributeValue == Triple(null, null, null)) {
+ JsonLdUtils.logger.warn("Unable to get a value from attribute: $this")
+ return BadRequestDataException("Unable to get a value from attribute: $this").left()
+ }
+
+ return AttributeMetadata(
+ measuredValue = attributeValue.second,
+ value = attributeValue.first,
+ geoValue = attributeValue.third,
+ valueType = attributeValueType,
+ datasetId = this.datasetId,
+ type = attributeType,
+ observedAt = this.observedAt
+ ).right()
+}
+
+fun guessAttributeValueType(
+ attributeType: Attribute.AttributeType,
+ expandedAttributeInstance: ExpandedAttributeInstance
+): Attribute.AttributeValueType =
+ when (attributeType) {
+ Attribute.AttributeType.Property ->
+ guessPropertyValueType(expandedAttributeInstance.getPropertyValue()!!).first
+ Attribute.AttributeType.Relationship -> Attribute.AttributeValueType.URI
+ Attribute.AttributeType.GeoProperty -> Attribute.AttributeValueType.GEOMETRY
+ Attribute.AttributeType.JsonProperty -> Attribute.AttributeValueType.JSON
+ Attribute.AttributeType.LanguageProperty -> Attribute.AttributeValueType.ARRAY
+ Attribute.AttributeType.VocabProperty -> Attribute.AttributeValueType.ARRAY
+ }
+
+fun guessPropertyValueType(
+ ngsiLdPropertyInstance: NgsiLdPropertyInstance
+): Pair> =
+ guessPropertyValueType(ngsiLdPropertyInstance.value)
+
+fun guessPropertyValueType(
+ value: Any
+): Pair> =
+ when (value) {
+ is Double -> Pair(Attribute.AttributeValueType.NUMBER, Triple(null, valueToDoubleOrNull(value), null))
+ is Int -> Pair(Attribute.AttributeValueType.NUMBER, Triple(null, valueToDoubleOrNull(value), null))
+ is Map<*, *> -> Pair(Attribute.AttributeValueType.OBJECT, Triple(JsonUtils.serializeObject(value), null, null))
+ is List<*> -> Pair(Attribute.AttributeValueType.ARRAY, Triple(JsonUtils.serializeObject(value), null, null))
+ is String -> Pair(Attribute.AttributeValueType.STRING, Triple(value, null, null))
+ is Boolean -> Pair(Attribute.AttributeValueType.BOOLEAN, Triple(value.toString(), null, null))
+ is LocalDate -> Pair(Attribute.AttributeValueType.DATE, Triple(value.toString(), null, null))
+ is ZonedDateTime -> Pair(Attribute.AttributeValueType.DATETIME, Triple(value.toString(), null, null))
+ is LocalTime -> Pair(Attribute.AttributeValueType.TIME, Triple(value.toString(), null, null))
+ else -> Pair(Attribute.AttributeValueType.STRING, Triple(value.toString(), null, null))
+ }
+
+fun Json.toExpandedAttributeInstance(): ExpandedAttributeInstance =
+ this.deserializeAsMap() as ExpandedAttributeInstance
+
+fun partialUpdatePatch(
+ source: ExpandedAttributeInstance,
+ update: ExpandedAttributeInstance
+): Pair {
+ val target = source.plus(update)
+ return Pair(JsonUtils.serializeObject(target), target)
+}
+
+fun mergePatch(
+ source: ExpandedAttributeInstance,
+ update: ExpandedAttributeInstance
+): Pair {
+ val target = source.toMutableMap()
+ update.forEach { (attrName, attrValue) ->
+ if (!source.containsKey(attrName)) {
+ target[attrName] = attrValue
+ } else if (
+ listOf(
+ JsonLdUtils.NGSILD_JSONPROPERTY_VALUE,
+ JsonLdUtils.NGSILD_VOCABPROPERTY_VALUE,
+ JsonLdUtils.NGSILD_PROPERTY_VALUE
+ ).contains(attrName)
+ ) {
+ if (attrValue.size > 1) {
+ // a Property holding an array of value or a JsonPropery holding an array of JSON objects
+ // cannot be safely merged patch, so copy the whole value from the update
+ target[attrName] = attrValue
+ } else {
+ target[attrName] = listOf(
+ JsonMerger().merge(
+ JsonUtils.serializeObject(source[attrName]!![0]),
+ JsonUtils.serializeObject(attrValue[0])
+ ).deserializeAsMap()
+ )
+ }
+ } else if (listOf(JsonLdUtils.NGSILD_LANGUAGEPROPERTY_VALUE).contains(attrName)) {
+ val sourceLangEntries = source[attrName] as List