Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add support for the format parameter #1299

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions search-service/config/detekt/baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@
<ID>LongMethod:EnabledAuthorizationServiceTests.kt$EnabledAuthorizationServiceTests$@Test fun `it should return serialized access control entities with other rigths if user is owner`()</ID>
<ID>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&lt;String&gt;, @AllowedParameters @RequestParam queryParams: MultiValueMap&lt;String, String&gt; ): ResponseEntity&lt;*&gt;</ID>
<ID>LongMethod:EntityEventService.kt$EntityEventService$private fun publishAttributeChangeEvent( sub: String?, tenantName: String, entityId: URI, entityTypesAndPayload: Pair&lt;List&lt;ExpandedTerm&gt;, String&gt;, attributeOperationResult: SucceededAttributeOperationResult )</ID>
<ID>LongMethod:EntityHandler.kt$EntityHandler$@GetMapping("/{entityId}", produces = [APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE, GEO_JSON_CONTENT_TYPE]) suspend fun getByURI( @RequestHeader httpHeaders: HttpHeaders, @PathVariable entityId: URI, @AllowedParameters( implemented = [ QP.OPTIONS, QP.TYPE, QP.ATTRS, QP.GEOMETRY_PROPERTY, QP.LANG, QP.CONTAINED_BY, QP.JOIN, QP.JOIN_LEVEL, QP.DATASET_ID, ], notImplemented = [QP.FORMAT, QP.PICK, QP.OMIT, QP.ENTITY_MAP, QP.LOCAL, QP.VIA] ) @RequestParam queryParams: MultiValueMap&lt;String, String&gt; ): ResponseEntity&lt;*&gt;</ID>
<ID>LongMethod:EntityHandler.kt$EntityHandler$@GetMapping(produces = [APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE, GEO_JSON_CONTENT_TYPE]) suspend fun getEntities( @RequestHeader httpHeaders: HttpHeaders, @AllowedParameters( implemented = [ QP.OPTIONS, QP.COUNT, QP.OFFSET, QP.LIMIT, QP.ID, QP.TYPE, QP.ID_PATTERN, QP.ATTRS, QP.Q, QP.GEOMETRY, QP.GEOREL, QP.COORDINATES, QP.GEOPROPERTY, QP.GEOMETRY_PROPERTY, QP.LANG, QP.SCOPEQ, QP.CONTAINED_BY, QP.JOIN, QP.JOIN_LEVEL, QP.DATASET_ID, ], notImplemented = [QP.FORMAT, QP.PICK, QP.OMIT, QP.EXPAND_VALUES, QP.CSF, QP.ENTITY_MAP, QP.LOCAL, QP.VIA] ) @RequestParam queryParams: MultiValueMap&lt;String, String&gt; ): ResponseEntity&lt;*&gt;</ID>
<ID>LongMethod:EntityHandler.kt$EntityHandler$@GetMapping("/{entityId}", produces = [APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE, GEO_JSON_CONTENT_TYPE]) suspend fun getByURI( @RequestHeader httpHeaders: HttpHeaders, @PathVariable entityId: URI, @AllowedParameters( implemented = [ QP.OPTIONS, QP.FORMAT, QP.TYPE, QP.ATTRS, QP.GEOMETRY_PROPERTY, QP.LANG, QP.CONTAINED_BY, QP.JOIN, QP.JOIN_LEVEL, QP.DATASET_ID, ], notImplemented = [QP.PICK, QP.OMIT, QP.ENTITY_MAP, QP.LOCAL, QP.VIA] ) @RequestParam queryParams: MultiValueMap&lt;String, String&gt; ): ResponseEntity&lt;*&gt;</ID>
<ID>LongMethod:EntityHandler.kt$EntityHandler$@GetMapping(produces = [APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE, GEO_JSON_CONTENT_TYPE]) suspend fun getEntities( @RequestHeader httpHeaders: HttpHeaders, @AllowedParameters( implemented = [ QP.OPTIONS, QP.FORMAT, QP.COUNT, QP.OFFSET, QP.LIMIT, QP.ID, QP.TYPE, QP.ID_PATTERN, QP.ATTRS, QP.Q, QP.GEOMETRY, QP.GEOREL, QP.COORDINATES, QP.GEOPROPERTY, QP.GEOMETRY_PROPERTY, QP.LANG, QP.SCOPEQ, QP.CONTAINED_BY, QP.JOIN, QP.JOIN_LEVEL, QP.DATASET_ID, ], notImplemented = [QP.PICK, QP.OMIT, QP.EXPAND_VALUES, QP.CSF, QP.ENTITY_MAP, QP.LOCAL, QP.VIA] ) @RequestParam queryParams: MultiValueMap&lt;String, String&gt; ): ResponseEntity&lt;*&gt;</ID>
<ID>LongMethod:LinkedEntityServiceTests.kt$LinkedEntityServiceTests$@Test fun `it should inline entities up to the asked 2nd level`()</ID>
<ID>LongMethod:PatchAttributeTests.kt$PatchAttributeTests.Companion$@JvmStatic fun mergePatchProvider(): Stream&lt;Arguments&gt;</ID>
<ID>LongMethod:PatchAttributeTests.kt$PatchAttributeTests.Companion$@JvmStatic fun partialUpdatePatchProvider(): Stream&lt;Arguments&gt;</ID>
<ID>LongMethod:TemporalQueryServiceTests.kt$TemporalQueryServiceTests$@Test fun `it should query temporal entities as requested by query params`()</ID>
<ID>LongMethod:TemporalQueryServiceTests.kt$TemporalQueryServiceTests$@Test fun `it should return an empty list for an attribute if it has no temporal values`()</ID>
<ID>LongMethod:TemporalScopeBuilderTests.kt$TemporalScopeBuilderTests$@Test fun `it should build an aggregated temporal representation of scopes`()</ID>
<ID>LongMethod:V0_29__JsonLd_migration.kt$V0_29__JsonLd_migration$override fun migrate(context: Context)</ID>
<ID>LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( attributeUuid: UUID, instanceId: URI = generateRandomInstanceId(), timeAndProperty: Pair&lt;ZonedDateTime, TemporalProperty&gt;, value: Triple&lt;String?, Double?, WKTCoordinates?&gt;, payload: ExpandedAttributeInstance, sub: String? )</ID>
<ID>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 )</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class EntityAccessControlHandler(

val compactedEntities = compactEntities(entities, contexts)

val ngsiLdDataRepresentation = parseRepresentations(queryParams, mediaType)
val ngsiLdDataRepresentation = parseRepresentations(queryParams, mediaType).bind()
buildQueryResponse(
compactedEntities.toFinalRepresentation(ngsiLdDataRepresentation),
count,
Expand Down Expand Up @@ -150,7 +150,7 @@ class EntityAccessControlHandler(

val compactedEntities = compactEntities(entities, contexts)

val ngsiLdDataRepresentation = parseRepresentations(params, mediaType)
val ngsiLdDataRepresentation = parseRepresentations(params, mediaType).bind()
buildQueryResponse(
compactedEntities.toFinalRepresentation(ngsiLdDataRepresentation),
count,
Expand Down Expand Up @@ -196,7 +196,7 @@ class EntityAccessControlHandler(

val compactedEntities = compactEntities(entities, contexts)

val ngsiLdDataRepresentation = parseRepresentations(params, mediaType)
val ngsiLdDataRepresentation = parseRepresentations(params, mediaType).bind()
buildQueryResponse(
compactedEntities.toFinalRepresentation(ngsiLdDataRepresentation),
count,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class EntityAttributeService(
private val logger = LoggerFactory.getLogger(javaClass)

@Transactional
suspend fun create(attribute: Attribute): Either<APIException, Unit> =
suspend fun create(attribute: Attribute): Either<APIException, UUID> =
databaseClient.sql(
"""
INSERT INTO temporal_entity_attribute
Expand All @@ -110,6 +110,7 @@ class EntityAttributeService(
attribute_value_type = :attribute_value_type,
modified_at = :created_at,
payload = :payload
RETURNING id
""".trimIndent()
)
.bind("id", attribute.id)
Expand All @@ -120,7 +121,7 @@ class EntityAttributeService(
.bind("created_at", attribute.createdAt)
.bind("dataset_id", attribute.datasetId)
.bind("payload", attribute.payload)
.execute()
.oneToResult { row -> toUuid(row["id"]) }

@Transactional
suspend fun updateOnUpdate(
Expand Down Expand Up @@ -215,10 +216,10 @@ class EntityAttributeService(
createdAt = createdAt,
payload = Json.of(serializeObject(attributePayload))
)
create(attribute).bind()
val attributeUuid = create(attribute).bind()

val attributeInstance = AttributeInstance(
attributeUuid = attribute.id,
attributeUuid = attributeUuid,
timeProperty = AttributeInstance.TemporalProperty.CREATED_AT,
time = createdAt,
attributeMetadata = attributeMetadata,
Expand All @@ -229,7 +230,7 @@ class EntityAttributeService(

if (attributeMetadata.observedAt != null) {
val attributeObservedAtInstance = AttributeInstance(
attributeUuid = attribute.id,
attributeUuid = attributeUuid,
time = attributeMetadata.observedAt,
attributeMetadata = attributeMetadata,
payload = attributePayload
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,11 @@ class EntityHandler(
@RequestHeader httpHeaders: HttpHeaders,
@AllowedParameters(
implemented = [
QP.OPTIONS, QP.COUNT, QP.OFFSET, QP.LIMIT, QP.ID, QP.TYPE, QP.ID_PATTERN, QP.ATTRS, QP.Q,
QP.OPTIONS, QP.FORMAT, QP.COUNT, QP.OFFSET, QP.LIMIT, QP.ID, QP.TYPE, QP.ID_PATTERN, QP.ATTRS, QP.Q,
QP.GEOMETRY, QP.GEOREL, QP.COORDINATES, QP.GEOPROPERTY, QP.GEOMETRY_PROPERTY,
QP.LANG, QP.SCOPEQ, QP.CONTAINED_BY, QP.JOIN, QP.JOIN_LEVEL, QP.DATASET_ID,
],
notImplemented = [QP.FORMAT, QP.PICK, QP.OMIT, QP.EXPAND_VALUES, QP.CSF, QP.ENTITY_MAP, QP.LOCAL, QP.VIA]
notImplemented = [QP.PICK, QP.OMIT, QP.EXPAND_VALUES, QP.CSF, QP.ENTITY_MAP, QP.LOCAL, QP.VIA]
)
@RequestParam queryParams: MultiValueMap<String, String>
): ResponseEntity<*> = either {
Expand Down Expand Up @@ -264,7 +264,7 @@ class EntityHandler(
mergedEntities ?: emptyList()
}

val ngsiLdDataRepresentation = parseRepresentations(queryParams, mediaType)
val ngsiLdDataRepresentation = parseRepresentations(queryParams, mediaType).bind()
buildQueryResponse(
mergedEntities.toFinalRepresentation(ngsiLdDataRepresentation),
maxCount,
Expand All @@ -288,10 +288,10 @@ class EntityHandler(
@PathVariable entityId: URI,
@AllowedParameters(
implemented = [
QP.OPTIONS, QP.TYPE, QP.ATTRS, QP.GEOMETRY_PROPERTY,
QP.OPTIONS, QP.FORMAT, QP.TYPE, QP.ATTRS, QP.GEOMETRY_PROPERTY,
QP.LANG, QP.CONTAINED_BY, QP.JOIN, QP.JOIN_LEVEL, QP.DATASET_ID,
],
notImplemented = [QP.FORMAT, QP.PICK, QP.OMIT, QP.ENTITY_MAP, QP.LOCAL, QP.VIA]
notImplemented = [QP.PICK, QP.OMIT, QP.ENTITY_MAP, QP.LOCAL, QP.VIA]
)
@RequestParam queryParams: MultiValueMap<String, String>
): ResponseEntity<*> = either {
Expand Down Expand Up @@ -358,7 +358,7 @@ class EntityHandler(
val mergedEntityWithLinkedEntities =
linkedEntityService.processLinkedEntities(mergedEntity, entitiesQuery, sub.getOrNull()).bind()

val ngsiLdDataRepresentation = parseRepresentations(queryParams, mediaType)
val ngsiLdDataRepresentation = parseRepresentations(queryParams, mediaType).bind()
prepareGetSuccessResponseHeaders(mediaType, contexts)
.let {
val body = if (mergedEntityWithLinkedEntities.size == 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ class EntityOperationHandler(

val compactedEntities = compactEntities(filteredEntities, contexts)

val ngsiLdDataRepresentation = parseRepresentations(queryParams, mediaType)
val ngsiLdDataRepresentation = parseRepresentations(queryParams, mediaType).bind()
.copy(languageFilter = query.lang)

buildQueryResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.egm.stellio.search.entity.model.SucceededAttributeOperationResult
import com.egm.stellio.search.temporal.model.AttributeInstance.TemporalProperty
import com.egm.stellio.search.temporal.model.TemporalEntitiesQuery
import com.egm.stellio.search.temporal.model.TemporalQuery
import com.egm.stellio.search.temporal.util.TemporalRepresentation
import com.egm.stellio.search.temporal.util.WHOLE_TIME_RANGE_DURATION
import com.egm.stellio.search.temporal.util.composeAggregationSelectClause
import com.egm.stellio.shared.model.APIException
Expand Down Expand Up @@ -136,7 +137,7 @@ class ScopeService(

if (temporalEntitiesQuery.isAggregatedWithDefinedDuration())
sqlQueryBuilder.append(" GROUP BY entity_id, start")
else if (temporalEntitiesQuery.withAggregatedValues)
else if (temporalEntitiesQuery.temporalRepresentation == TemporalRepresentation.AGGREGATED_VALUES)
sqlQueryBuilder.append(" GROUP BY entity_id")
if (temporalQuery.hasLastN())
// in order to get first or last instances, need to order by time
Expand All @@ -161,7 +162,7 @@ class ScopeService(
temporalEntitiesQuery: TemporalEntitiesQuery,
origin: ZonedDateTime?
): String = when {
temporalEntitiesQuery.withAggregatedValues -> {
temporalEntitiesQuery.temporalRepresentation == TemporalRepresentation.AGGREGATED_VALUES -> {
val temporalQuery = temporalEntitiesQuery.temporalQuery
val aggrPeriodDuration = temporalQuery.aggrPeriodDuration
val allAggregates = temporalQuery.aggrMethods?.composeAggregationSelectClause(AttributeValueType.ARRAY)
Expand Down Expand Up @@ -215,7 +216,7 @@ class ScopeService(
row: Map<String, Any>,
temporalEntitiesQuery: TemporalEntitiesQuery
): ScopeInstanceResult =
if (temporalEntitiesQuery.withAggregatedValues) {
if (temporalEntitiesQuery.temporalRepresentation == TemporalRepresentation.AGGREGATED_VALUES) {
val startDateTime = toZonedDateTime(row["start"])
val endDateTime =
if (!temporalEntitiesQuery.isAggregatedWithDefinedDuration())
Expand All @@ -231,7 +232,7 @@ class ScopeService(
entityId = toUri(row["entity_id"]),
values = values
)
} else if (temporalEntitiesQuery.withTemporalValues) {
} else if (temporalEntitiesQuery.temporalRepresentation == TemporalRepresentation.TEMPORAL_VALUES) {
SimplifiedScopeInstanceResult(
entityId = toUri(row["entity_id"]),
scopes = toList(row["value"]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.egm.stellio.search.scope
import com.egm.stellio.search.entity.model.Entity
import com.egm.stellio.search.temporal.model.TemporalEntitiesQuery
import com.egm.stellio.search.temporal.model.TemporalQuery
import com.egm.stellio.search.temporal.util.TemporalRepresentation
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_LIST
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE
import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE
Expand All @@ -27,12 +28,12 @@ object TemporalScopeBuilder {
// if no history but entity has a scope, add an empty scope list (no history in the given time range)
else if (scopeInstances.isEmpty())
mapOf(NGSILD_SCOPE_PROPERTY to emptyList<String>())
else if (temporalEntitiesQuery.withAggregatedValues)
else if (temporalEntitiesQuery.temporalRepresentation == TemporalRepresentation.AGGREGATED_VALUES)
buildScopeAggregatedRepresentation(
scopeInstances,
temporalEntitiesQuery.temporalQuery.aggrMethods!!
)
else if (temporalEntitiesQuery.withTemporalValues)
else if (temporalEntitiesQuery.temporalRepresentation == TemporalRepresentation.TEMPORAL_VALUES)
buildScopeSimplifiedRepresentation(scopeInstances)
else
buildScopeFullRepresentation(scopeInstances)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ package com.egm.stellio.search.temporal.model
import com.egm.stellio.search.entity.model.EntitiesQuery
import com.egm.stellio.search.entity.model.EntitiesQueryFromGet
import com.egm.stellio.search.entity.model.EntitiesQueryFromPost
import com.egm.stellio.search.temporal.util.TemporalRepresentation
import java.time.Duration
import java.time.Period
import java.time.temporal.TemporalAmount

sealed class TemporalEntitiesQuery(
open val entitiesQuery: EntitiesQuery,
open val temporalQuery: TemporalQuery,
open val withTemporalValues: Boolean,
open val withAudit: Boolean,
open val withAggregatedValues: Boolean
open val temporalRepresentation: TemporalRepresentation,
open val withAudit: Boolean
) {
fun isAggregatedWithDefinedDuration(): Boolean =
withAggregatedValues &&
temporalRepresentation == TemporalRepresentation.AGGREGATED_VALUES &&
temporalQuery.aggrPeriodDuration != null &&
temporalQuery.aggrPeriodDuration != "PT0S"

Expand All @@ -36,15 +36,13 @@ sealed class TemporalEntitiesQuery(
data class TemporalEntitiesQueryFromGet(
override val entitiesQuery: EntitiesQueryFromGet,
override val temporalQuery: TemporalQuery,
override val withTemporalValues: Boolean,
override val withAudit: Boolean,
override val withAggregatedValues: Boolean
) : TemporalEntitiesQuery(entitiesQuery, temporalQuery, withTemporalValues, withAudit, withAggregatedValues)
override val temporalRepresentation: TemporalRepresentation,
override val withAudit: Boolean
) : TemporalEntitiesQuery(entitiesQuery, temporalQuery, temporalRepresentation, withAudit)

data class TemporalEntitiesQueryFromPost(
override val entitiesQuery: EntitiesQueryFromPost,
override val temporalQuery: TemporalQuery,
override val withTemporalValues: Boolean,
override val withAudit: Boolean,
override val withAggregatedValues: Boolean
) : TemporalEntitiesQuery(entitiesQuery, temporalQuery, withTemporalValues, withAudit, withAggregatedValues)
override val temporalRepresentation: TemporalRepresentation,
override val withAudit: Boolean
) : TemporalEntitiesQuery(entitiesQuery, temporalQuery, temporalRepresentation, withAudit)
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.egm.stellio.search.temporal.model.SimplifiedAttributeInstanceResult
import com.egm.stellio.search.temporal.model.TemporalEntitiesQuery
import com.egm.stellio.search.temporal.model.TemporalQuery
import com.egm.stellio.search.temporal.model.TemporalQuery.Timerel
import com.egm.stellio.search.temporal.util.TemporalRepresentation
import com.egm.stellio.search.temporal.util.WHOLE_TIME_RANGE_DURATION
import com.egm.stellio.search.temporal.util.composeAggregationSelectClause
import com.egm.stellio.shared.model.APIException
Expand Down Expand Up @@ -174,7 +175,7 @@ class AttributeInstanceService(

sqlQueryBuilder.append(composeSearchSelectStatement(temporalQuery, attributes, origin))

if (!temporalEntitiesQuery.withTemporalValues && !temporalEntitiesQuery.withAggregatedValues)
if (temporalEntitiesQuery.temporalRepresentation == TemporalRepresentation.NORMALIZED)
sqlQueryBuilder.append(", payload")

if (temporalQuery.timeproperty == OBSERVED_AT)
Expand Down Expand Up @@ -204,7 +205,7 @@ class AttributeInstanceService(

if (temporalEntitiesQuery.isAggregatedWithDefinedDuration())
sqlQueryBuilder.append(" GROUP BY temporal_entity_attribute, start")
else if (temporalEntitiesQuery.withAggregatedValues)
else if (temporalEntitiesQuery.temporalRepresentation == TemporalRepresentation.AGGREGATED_VALUES)
sqlQueryBuilder.append(" GROUP BY temporal_entity_attribute")

if (temporalQuery.hasLastN())
Expand Down Expand Up @@ -319,7 +320,7 @@ class AttributeInstanceService(
row: Map<String, Any>,
temporalEntitiesQuery: TemporalEntitiesQuery
): AttributeInstanceResult =
if (temporalEntitiesQuery.withAggregatedValues) {
if (temporalEntitiesQuery.temporalRepresentation == TemporalRepresentation.AGGREGATED_VALUES) {
val startDateTime = toZonedDateTime(row["start"])
val endDateTime =
if (!temporalEntitiesQuery.isAggregatedWithDefinedDuration())
Expand All @@ -335,7 +336,7 @@ class AttributeInstanceService(
attributeUuid = toUuid(row["temporal_entity_attribute"]),
values = values
)
} else if (temporalEntitiesQuery.withTemporalValues)
} else if (temporalEntitiesQuery.temporalRepresentation == TemporalRepresentation.TEMPORAL_VALUES)
SimplifiedAttributeInstanceResult(
attributeUuid = toUuid(row["temporal_entity_attribute"]),
// the type of the value of a property may have changed in the history (e.g., from number to string)
Expand Down
Loading
Loading