diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt index 541990854..0f940695c 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt @@ -23,7 +23,8 @@ enum class AttributeType(val key: String) { Relationship("Relationship"), GeoProperty("GeoProperty"), JsonProperty("JsonProperty"), - LanguageProperty("LanguageProperty"); + LanguageProperty("LanguageProperty"), + VocabProperty("VocabProperty"); companion object { fun forKey(key: String): AttributeType = diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt index b47ae52ce..a04f1da97 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt @@ -11,6 +11,8 @@ import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_TYPE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_VALUES import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_OBJECTS import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_TYPE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_VOCABPROPERTY_TYPE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_VOCABPROPERTY_VALUES import io.r2dbc.postgresql.codec.Json import org.springframework.data.annotation.Id import java.net.URI @@ -48,7 +50,8 @@ data class TemporalEntityAttribute( Relationship, GeoProperty, JsonProperty, - LanguageProperty; + LanguageProperty, + VocabProperty; fun toExpandedName(): String = when (this) { @@ -57,6 +60,7 @@ data class TemporalEntityAttribute( GeoProperty -> NGSILD_GEOPROPERTY_TYPE.uri JsonProperty -> NGSILD_JSONPROPERTY_TYPE.uri LanguageProperty -> NGSILD_LANGUAGEPROPERTY_TYPE.uri + VocabProperty -> NGSILD_VOCABPROPERTY_TYPE.uri } /** @@ -69,6 +73,7 @@ data class TemporalEntityAttribute( GeoProperty -> NGSILD_GEOPROPERTY_VALUES JsonProperty -> NGSILD_JSONPROPERTY_VALUES LanguageProperty -> NGSILD_LANGUAGEPROPERTY_VALUES + VocabProperty -> NGSILD_VOCABPROPERTY_VALUES } } } diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt index b2dfa564a..0721ddf1a 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt @@ -18,6 +18,7 @@ import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LANGUAGEPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_OBSERVED_AT_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PREFIX import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_OBJECT +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_VOCABPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.buildNonReifiedTemporalValue import com.egm.stellio.shared.util.JsonLdUtils.expandJsonLdEntity import com.egm.stellio.shared.util.JsonUtils.serializeObject @@ -874,6 +875,12 @@ class TemporalEntityAttributeService( null, null ) + TemporalEntityAttribute.AttributeType.VocabProperty -> + Triple( + serializeObject(attributePayload.getMemberValue(NGSILD_VOCABPROPERTY_VALUE)!!), + null, + null + ) } private fun createContextualAttributeInstance( diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt index adaf2d293..c26cea08b 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt @@ -12,6 +12,7 @@ import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_LANGUAGE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LANGUAGEPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_VALUE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_VOCABPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.logger import com.egm.stellio.shared.util.JsonUtils.deserializeAsMap import com.egm.stellio.shared.util.JsonUtils.serializeObject @@ -78,6 +79,12 @@ fun NgsiLdAttributeInstance.toTemporalAttributeMetadata(): Either + Triple( + TemporalEntityAttribute.AttributeType.VocabProperty, + AttributeValueType.ARRAY, + Triple(serializeObject(this.vocab), null, null) + ) } if (attributeValue == Triple(null, null, null)) { logger.warn("Unable to get a value from attribute: $this") @@ -106,6 +113,7 @@ fun guessAttributeValueType( TemporalEntityAttribute.AttributeType.GeoProperty -> AttributeValueType.GEOMETRY TemporalEntityAttribute.AttributeType.JsonProperty -> AttributeValueType.JSON TemporalEntityAttribute.AttributeType.LanguageProperty -> AttributeValueType.ARRAY + TemporalEntityAttribute.AttributeType.VocabProperty -> AttributeValueType.ARRAY } fun guessPropertyValueType( @@ -148,7 +156,9 @@ fun mergePatch( update.forEach { (attrName, attrValue) -> if (!source.containsKey(attrName)) { target[attrName] = attrValue - } else if (listOf(NGSILD_JSONPROPERTY_VALUE, NGSILD_PROPERTY_VALUE).contains(attrName)) { + } else if ( + listOf(NGSILD_JSONPROPERTY_VALUE, NGSILD_VOCABPROPERTY_VALUE, 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 diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt index 91f9adfb7..e396c052d 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt @@ -14,6 +14,7 @@ import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_TYPE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LANGUAGEPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PREFIX +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_VOCABPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.buildExpandedPropertyValue import com.egm.stellio.shared.util.JsonLdUtils.buildExpandedTemporalValue import com.egm.stellio.shared.util.JsonLdUtils.buildNonReifiedPropertyValue @@ -124,36 +125,49 @@ object TemporalEntityBuilder { attributeInstance[valuesKey] = buildExpandedTemporalValue(it.value) { attributeInstanceResult -> attributeInstanceResult as SimplifiedAttributeInstanceResult - if (it.key.attributeType == TemporalEntityAttribute.AttributeType.JsonProperty) { - // flaky way to know if the serialized value is a JSON object or an array of JSON objects - val deserializedJsonValue: Any = - if ((attributeInstanceResult.value as String).startsWith("[")) - deserializeListOfObjects(attributeInstanceResult.value) - else deserializeObject(attributeInstanceResult.value) - listOf( - mapOf( - NGSILD_JSONPROPERTY_VALUE to listOf( - mapOf( - JSONLD_TYPE to JSONLD_JSON, - JSONLD_VALUE to deserializedJsonValue + when (it.key.attributeType) { + TemporalEntityAttribute.AttributeType.JsonProperty -> { + // flaky way to know if the serialized value is a JSON object or an array of JSON objects + val deserializedJsonValue: Any = + if ((attributeInstanceResult.value as String).startsWith("[")) + deserializeListOfObjects(attributeInstanceResult.value) + else deserializeObject(attributeInstanceResult.value) + listOf( + mapOf( + NGSILD_JSONPROPERTY_VALUE to listOf( + mapOf( + JSONLD_TYPE to JSONLD_JSON, + JSONLD_VALUE to deserializedJsonValue + ) ) - ) - ), - mapOf(JSONLD_VALUE to attributeInstanceResult.time) - ) - } else if (it.key.attributeType == TemporalEntityAttribute.AttributeType.LanguageProperty) { - listOf( - mapOf( - NGSILD_LANGUAGEPROPERTY_VALUE to - deserializeListOfObjects(attributeInstanceResult.value as String) - ), - mapOf(JSONLD_VALUE to attributeInstanceResult.time) - ) - } else { - listOf( - mapOf(JSONLD_VALUE to attributeInstanceResult.value), - mapOf(JSONLD_VALUE to attributeInstanceResult.time) - ) + ), + mapOf(JSONLD_VALUE to attributeInstanceResult.time) + ) + } + TemporalEntityAttribute.AttributeType.LanguageProperty -> { + listOf( + mapOf( + NGSILD_LANGUAGEPROPERTY_VALUE to + deserializeListOfObjects(attributeInstanceResult.value as String) + ), + mapOf(JSONLD_VALUE to attributeInstanceResult.time) + ) + } + TemporalEntityAttribute.AttributeType.VocabProperty -> { + listOf( + mapOf( + NGSILD_VOCABPROPERTY_VALUE to + deserializeListOfObjects(attributeInstanceResult.value as String) + ), + mapOf(JSONLD_VALUE to attributeInstanceResult.time) + ) + } + else -> { + listOf( + mapOf(JSONLD_VALUE to attributeInstanceResult.value), + mapOf(JSONLD_VALUE to attributeInstanceResult.time) + ) + } } } attributeInstance.toMap() diff --git a/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt b/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt index 5f595e5cf..badc28eb9 100644 --- a/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt +++ b/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt @@ -271,6 +271,7 @@ class V0_29__JsonLd_migration : BaseJavaMigration() { is NgsiLdGeoPropertyInstance -> TemporalEntityAttribute.AttributeValueType.GEOMETRY is NgsiLdJsonPropertyInstance -> TemporalEntityAttribute.AttributeValueType.OBJECT is NgsiLdLanguagePropertyInstance -> TemporalEntityAttribute.AttributeValueType.ARRAY + is NgsiLdVocabPropertyInstance -> TemporalEntityAttribute.AttributeValueType.ARRAY } jdbcTemplate.execute( diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt index 69f4cb661..62efd15b3 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt @@ -7,16 +7,19 @@ import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.model.addNonReifiedTemporalProperty import com.egm.stellio.shared.model.getSingleEntry import com.egm.stellio.shared.util.* +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_ID import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_LANGUAGE import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATE_TIME_TYPE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DEFAULT_VOCAB import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_INSTANCE_ID_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LANGUAGEPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_MODIFIED_AT_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_OBSERVED_AT_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_VALUE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_VOCABPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.buildExpandedPropertyValue import com.egm.stellio.shared.util.JsonUtils.deserializeAsMap import io.mockk.spyk @@ -60,6 +63,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer private lateinit var outgoingTemporalEntityAttribute: TemporalEntityAttribute private lateinit var jsonTemporalEntityAttribute: TemporalEntityAttribute private lateinit var languageTemporalEntityAttribute: TemporalEntityAttribute + private lateinit var vocabTemporalEntityAttribute: TemporalEntityAttribute val entityId = "urn:ngsi-ld:BeeHive:TESTC".toUri() @@ -112,6 +116,18 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer runBlocking { temporalEntityAttributeService.create(languageTemporalEntityAttribute) } + + vocabTemporalEntityAttribute = TemporalEntityAttribute( + entityId = entityId, + attributeName = CATEGORY_VOCAPPROPERTY, + attributeValueType = TemporalEntityAttribute.AttributeValueType.ARRAY, + createdAt = now, + payload = SAMPLE_VOCAB_PROPERTY_PAYLOAD + ) + + runBlocking { + temporalEntityAttributeService.create(vocabTemporalEntityAttribute) + } } @AfterEach @@ -689,6 +705,51 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer } } + @Test + fun `it should modify attribute instance for a VocabProperty property`() = runTest { + val attributeInstance = gimmeVocabPropertyAttributeInstance(vocabTemporalEntityAttribute.id) + attributeInstanceService.create(attributeInstance) + + val instanceTemporalFragment = + loadSampleData("fragments/temporal_instance_vocab_fragment.jsonld") + val attributeInstancePayload = + mapOf(CATEGORY_COMPACT_VOCABPROPERTY to instanceTemporalFragment.deserializeAsMap()) + val jsonLdAttribute = JsonLdUtils.expandJsonLdFragment( + attributeInstancePayload, + APIC_COMPOUND_CONTEXTS + ) as ExpandedAttributes + + val temporalEntitiesQuery = gimmeTemporalEntitiesQuery( + TemporalQuery( + timerel = TemporalQuery.Timerel.AFTER, + timeAt = ZonedDateTime.parse("1970-01-01T00:00:00Z") + ) + ) + + attributeInstanceService.modifyAttributeInstance( + entityId, + CATEGORY_VOCAPPROPERTY, + attributeInstance.instanceId, + jsonLdAttribute.entries.first().value + ).shouldSucceed() + + attributeInstanceService.search(temporalEntitiesQuery, vocabTemporalEntityAttribute) + .shouldSucceedWith { + (it as List).single { result -> + val deserializedPayload = result.payload.deserializeAsMap() + result.time == ZonedDateTime.parse("2023-03-13T12:33:06Z") && + deserializedPayload.containsKey(NGSILD_MODIFIED_AT_PROPERTY) && + deserializedPayload.containsKey(NGSILD_INSTANCE_ID_PROPERTY) && + deserializedPayload.containsKey(NGSILD_VOCABPROPERTY_VALUE) && + (deserializedPayload[NGSILD_VOCABPROPERTY_VALUE] as List>) + .all { entry -> + entry[JSONLD_ID] == "${NGSILD_DEFAULT_VOCAB}stellio" || + entry[JSONLD_ID] == "${NGSILD_DEFAULT_VOCAB}egm" + } + } + } + } + @Test fun `it should delete attribute instance`() = runTest { val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt index 7628e3002..7e1adb21e 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt @@ -81,6 +81,12 @@ class EntityTypeServiceTests : WithTimescaleContainer, WithKafkaContainer { TemporalEntityAttribute.AttributeType.LanguageProperty, TemporalEntityAttribute.AttributeValueType.OBJECT ) + private val categoryVocabProperty = newTemporalEntityAttribute( + "urn:ngsi-ld:Apiary:TESTC", + CATEGORY_VOCAPPROPERTY, + TemporalEntityAttribute.AttributeType.VocabProperty, + TemporalEntityAttribute.AttributeValueType.ARRAY + ) @AfterEach fun clearPreviousTemporalEntityAttributesAndObservations() { @@ -103,6 +109,7 @@ class EntityTypeServiceTests : WithTimescaleContainer, WithKafkaContainer { createTemporalEntityAttribute(outgoingProperty) createTemporalEntityAttribute(luminosityJsonProperty) createTemporalEntityAttribute(friendlyNameLanguageProperty) + createTemporalEntityAttribute(categoryVocabProperty) } @Test @@ -131,7 +138,10 @@ class EntityTypeServiceTests : WithTimescaleContainer, WithKafkaContainer { EntityType( id = toUri(APIARY_TYPE), typeName = APIARY_COMPACT_TYPE, - attributeNames = listOf(NGSILD_LOCATION_TERM) + attributeNames = listOf( + CATEGORY_COMPACT_VOCABPROPERTY, + NGSILD_LOCATION_TERM + ) ), EntityType( id = toUri(BEEHIVE_TYPE), diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt b/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt index 9c1496407..508c350f6 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt @@ -108,6 +108,32 @@ fun gimmeLanguagePropertyAttributeInstance( ) } +fun gimmeVocabPropertyAttributeInstance( + teaUuid: UUID, + timeProperty: AttributeInstance.TemporalProperty = AttributeInstance.TemporalProperty.OBSERVED_AT +): AttributeInstance { + val attributeMetadata = AttributeMetadata( + measuredValue = null, + value = SAMPLE_VOCAB_PROPERTY_PAYLOAD.asString(), + geoValue = null, + valueType = TemporalEntityAttribute.AttributeValueType.ARRAY, + datasetId = null, + type = TemporalEntityAttribute.AttributeType.VocabProperty, + observedAt = ngsiLdDateTime() + ) + val payload = JsonLdUtils.buildExpandedPropertyValue(attributeMetadata.value!!) + .addNonReifiedTemporalProperty(JsonLdUtils.NGSILD_OBSERVED_AT_PROPERTY, attributeMetadata.observedAt!!) + .getSingleEntry() + + return AttributeInstance( + temporalEntityAttribute = teaUuid, + time = attributeMetadata.observedAt!!, + attributeMetadata = attributeMetadata, + timeProperty = timeProperty, + payload = payload + ) +} + fun gimmeTemporalEntitiesQuery( temporalQuery: TemporalQuery, withTemporalValues: Boolean = false, diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt b/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt index 7d7ef1043..2b381827d 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt @@ -52,7 +52,11 @@ const val EMPTY_PAYLOAD = "{}" val EMPTY_JSON_PAYLOAD = Json.of(EMPTY_PAYLOAD) val SAMPLE_JSON_PROPERTY_PAYLOAD = Json.of( """ - { "id": "123", "stringValue": "value", "nullValue": null } + { + "id": "123", + "stringValue": "value", + "nullValue": null + } """.trimIndent() ) val SAMPLE_LANGUAGE_PROPERTY_PAYLOAD = Json.of( @@ -74,3 +78,20 @@ val SAMPLE_LANGUAGE_PROPERTY_PAYLOAD = Json.of( } """.trimIndent() ) +val SAMPLE_VOCAB_PROPERTY_PAYLOAD = Json.of( + """ + { + "https://uri.etsi.org/ngsi-ld/hasVocab": [ + { + "@id": "https://uri.etsi.org/ngsi-ld/default-context/stellio" + }, + { + "@id": "https://uri.etsi.org/ngsi-ld/default-context/egm" + } + ], + "@type": [ + "https://uri.etsi.org/ngsi-ld/VocabProperty" + ] + } + """.trimIndent() +) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/util/PatchAttributeTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/util/PatchAttributeTests.kt index 96c00ebd3..acb08d941 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/util/PatchAttributeTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/util/PatchAttributeTests.kt @@ -83,6 +83,32 @@ class PatchAttributeTests { } } """.trimIndent() + ), + Arguments.of( + """ + { + "attribute": { + "type": "VocabProperty", + "vocab": "stellio" + } + } + """.trimIndent(), + """ + { + "attribute": { + "type": "VocabProperty", + "vocab": "egm" + } + } + """.trimIndent(), + """ + { + "attribute": { + "type": "VocabProperty", + "vocab": "egm" + } + } + """.trimIndent() ) ) } @@ -261,6 +287,32 @@ class PatchAttributeTests { } } """.trimIndent() + ), + Arguments.of( + """ + { + "attribute": { + "type": "VocabProperty", + "vocab": "stellio" + } + } + """.trimIndent(), + """ + { + "attribute": { + "type": "VocabProperty", + "vocab": "egm" + } + } + """.trimIndent(), + """ + { + "attribute": { + "type": "VocabProperty", + "vocab": "egm" + } + } + """.trimIndent() ) ) } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt index ecde867a3..a6c2d87d0 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt @@ -4,10 +4,7 @@ import com.egm.stellio.search.model.* import com.egm.stellio.search.scope.FullScopeInstanceResult import com.egm.stellio.search.scope.ScopeInstanceResult import com.egm.stellio.search.scope.SimplifiedScopeInstanceResult -import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD -import com.egm.stellio.search.support.SAMPLE_JSON_PROPERTY_PAYLOAD -import com.egm.stellio.search.support.SAMPLE_LANGUAGE_PROPERTY_PAYLOAD -import com.egm.stellio.search.support.buildAttributeInstancePayload +import com.egm.stellio.search.support.* import com.egm.stellio.shared.util.JsonLdUtils import com.egm.stellio.shared.util.loadSampleData import com.egm.stellio.shared.util.toUri @@ -625,6 +622,48 @@ class TemporalEntityParameterizedSource { loadSampleData("expectations/beehive_language_property_temporal_values.jsonld") ) + private val beehiveVocabPropertyTemporalValues = + Arguments.arguments( + emptyList(), + mapOf( + TemporalEntityAttribute( + entityId = entityId, + attributeName = "https://ontology.eglobalmark.com/apic#category", + attributeType = TemporalEntityAttribute.AttributeType.VocabProperty, + attributeValueType = TemporalEntityAttribute.AttributeValueType.ARRAY, + datasetId = null, + createdAt = now, + payload = SAMPLE_VOCAB_PROPERTY_PAYLOAD + ) to + listOf( + SimplifiedAttributeInstanceResult( + temporalEntityAttribute = UUID.randomUUID(), + value = """ + [{ + "@id": "https://uri.etsi.org/ngsi-ld/default-context/stellio" + }, + { + "@id": "https://uri.etsi.org/ngsi-ld/default-context/egm" + }] + """, + time = ZonedDateTime.parse("2020-03-25T08:29:17.965206Z") + ), + SimplifiedAttributeInstanceResult( + temporalEntityAttribute = UUID.randomUUID(), + value = """ + [{ + "@id": "https://uri.etsi.org/ngsi-ld/default-context/stellio" + }] + """, + time = ZonedDateTime.parse("2020-03-25T08:33:17.965206Z") + ) + ) + ), + true, + false, + loadSampleData("expectations/beehive_vocab_property_temporal_values.jsonld") + ) + @JvmStatic fun rawResultsProvider(): Stream { return Stream.of( @@ -642,7 +681,8 @@ class TemporalEntityParameterizedSource { beehiveScopeMultiInstancesTemporalValues, beehiveScopeMultiInstances, beehiveJsonPropertyTemporalValues, - beehiveLanguagePropertyTemporalValues + beehiveLanguagePropertyTemporalValues, + beehiveVocabPropertyTemporalValues ) } } diff --git a/search-service/src/test/resources/ngsild/expectations/beehive_vocab_property_temporal_values.jsonld b/search-service/src/test/resources/ngsild/expectations/beehive_vocab_property_temporal_values.jsonld new file mode 100644 index 000000000..dbaecc31a --- /dev/null +++ b/search-service/src/test/resources/ngsild/expectations/beehive_vocab_property_temporal_values.jsonld @@ -0,0 +1,50 @@ +{ + "@id": "urn:ngsi-ld:BeeHive:TESTC", + "@type": [ + "https://ontology.eglobalmark.com/apic#BeeHive" + ], + "https://ontology.eglobalmark.com/apic#category": [ + { + "@type": [ + "https://uri.etsi.org/ngsi-ld/VocabProperty" + ], + "https://uri.etsi.org/ngsi-ld/hasVocabs": [ + { + "@list": [ + { + "@list": [ + { + "https://uri.etsi.org/ngsi-ld/hasVocab": [ + { + "@id": "https://uri.etsi.org/ngsi-ld/default-context/stellio" + }, + { + "@id": "https://uri.etsi.org/ngsi-ld/default-context/egm" + } + ] + }, + { + "@value": "2020-03-25T08:29:17.965206Z" + } + ] + }, + { + "@list": [ + { + "https://uri.etsi.org/ngsi-ld/hasVocab": [ + { + "@id": "https://uri.etsi.org/ngsi-ld/default-context/stellio" + } + ] + }, + { + "@value": "2020-03-25T08:33:17.965206Z" + } + ] + } + ] + } + ] + } + ] +} diff --git a/search-service/src/test/resources/ngsild/fragments/temporal_instance_vocab_fragment.jsonld b/search-service/src/test/resources/ngsild/fragments/temporal_instance_vocab_fragment.jsonld new file mode 100644 index 000000000..052f42c0b --- /dev/null +++ b/search-service/src/test/resources/ngsild/fragments/temporal_instance_vocab_fragment.jsonld @@ -0,0 +1,5 @@ +{ + "type": "VocabProperty", + "vocab": "stellio", + "observedAt": "2023-03-13T12:33:06Z" +} diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt index fc38b4fdf..d9dd98216 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt @@ -9,6 +9,7 @@ import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_LANGUAGEMAP_TERM import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_OBJECT import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE_TERM import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE_TERM +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VOCAB_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATASET_ID_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATASET_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_TERM @@ -19,6 +20,7 @@ import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_NONE_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_SYSATTRS_TERMS +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_VOCABPROPERTY_TERM import java.util.Locale typealias CompactedEntity = Map @@ -48,6 +50,7 @@ private fun simplifyAttribute(value: Map, transformationParameters: RELATIONSHIP -> value.getOrDefault(JSONLD_OBJECT, value) JSONPROPERTY -> mapOf(JSONLD_JSON_TERM to value.getOrDefault(JSONLD_JSON_TERM, value)) LANGUAGEPROPERTY -> mapOf(JSONLD_LANGUAGEMAP_TERM to value.getOrDefault(JSONLD_LANGUAGEMAP_TERM, value)) + VOCABPROPERTY -> mapOf(JSONLD_VOCAB_TERM to value.getOrDefault(JSONLD_VOCAB_TERM, value)) } } @@ -179,7 +182,8 @@ enum class AttributeCompactedType(val key: String) { RELATIONSHIP(NGSILD_RELATIONSHIP_TERM), GEOPROPERTY(NGSILD_GEOPROPERTY_TERM), JSONPROPERTY(NGSILD_JSONPROPERTY_TERM), - LANGUAGEPROPERTY(NGSILD_LANGUAGEPROPERTY_TERM); + LANGUAGEPROPERTY(NGSILD_LANGUAGEPROPERTY_TERM), + VOCABPROPERTY(NGSILD_VOCABPROPERTY_TERM); companion object { fun forKey(key: String): AttributeCompactedType? = diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdEntity.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdEntity.kt index 7931e49a8..572d5e146 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdEntity.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdEntity.kt @@ -28,6 +28,8 @@ import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_OBJECT import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_TYPE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_SCOPE_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_UNIT_CODE_PROPERTY +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_VOCABPROPERTY_TYPE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_VOCABPROPERTY_VALUE import com.egm.stellio.shared.util.toUri import java.net.URI import java.time.ZonedDateTime @@ -69,6 +71,7 @@ class NgsiLdEntity private constructor( val geoProperties = getAttributesOfType() val jsonProperties = getAttributesOfType() val languageProperties = getAttributesOfType() + val vocabProperties = getAttributesOfType() } sealed class NgsiLdAttribute(val name: ExpandedTerm) { @@ -200,6 +203,31 @@ class NgsiLdLanguageProperty private constructor( override fun getAttributeInstances(): List = instances } +class NgsiLdVocabProperty private constructor( + name: ExpandedTerm, + val instances: List +) : NgsiLdAttribute(name) { + companion object { + suspend fun create( + name: ExpandedTerm, + instances: ExpandedAttributeInstances + ): Either = either { + checkInstancesAreOfSameType(name, instances, NGSILD_VOCABPROPERTY_TYPE).bind() + + val ngsiLdVocabPropertyInstances = instances.parMap { instance -> + NgsiLdVocabPropertyInstance.create(name, instance).bind() + } + + checkAttributeDefaultInstance(name, ngsiLdVocabPropertyInstances).bind() + checkAttributeDuplicateDatasetId(name, ngsiLdVocabPropertyInstances).bind() + + NgsiLdVocabProperty(name, ngsiLdVocabPropertyInstances) + } + } + + override fun getAttributeInstances(): List = instances +} + sealed class NgsiLdAttributeInstance( val observedAt: ZonedDateTime?, val datasetId: URI?, @@ -410,6 +438,47 @@ class NgsiLdLanguagePropertyInstance private constructor( override fun toString(): String = "NgsiLdLanguagePropertyInstance(languageMap=$languageMap)" } +class NgsiLdVocabPropertyInstance private constructor( + val vocab: Any, + observedAt: ZonedDateTime?, + datasetId: URI?, + attributes: List +) : NgsiLdAttributeInstance(observedAt, datasetId, attributes) { + companion object { + suspend fun create( + name: ExpandedTerm, + values: ExpandedAttributeInstance + ): Either = either { + val vocab = values[NGSILD_VOCABPROPERTY_VALUE] + ensureNotNull(vocab) { + BadRequestDataException("VocabProperty $name has an instance without a vocab member") + } + ensure(vocab is List<*> && vocab.all { it is Map<*, *> && it.size == 1 && it.containsKey(JSONLD_ID) }) { + BadRequestDataException( + "VocabProperty $name has a vocab member that is not a string, nor an array of string" + ) + } + + val observedAt = values.getMemberValueAsDateTime(NGSILD_OBSERVED_AT_PROPERTY) + val datasetId = values.getDatasetId() + + checkAttributeHasNoForbiddenMembers(name, values, NGSILD_VOCABPROPERTIES_FORBIDDEN_MEMBERS).bind() + + val rawAttributes = getNonCoreMembers(values, NGSILD_VOCABPROPERTIES_CORE_MEMBERS) + val attributes = parseAttributes(rawAttributes).bind() + + NgsiLdVocabPropertyInstance( + vocab, + observedAt, + datasetId, + attributes + ) + } + } + + override fun toString(): String = "NgsiLdVocabPropertyInstance(vocab=$vocab)" +} + @JvmInline value class WKTCoordinates(val value: String) @@ -436,6 +505,7 @@ private suspend fun parseAttributes( NGSILD_GEOPROPERTY_TYPE.uri -> NgsiLdGeoProperty.create(it.first, it.second) NGSILD_JSONPROPERTY_TYPE.uri -> NgsiLdJsonProperty.create(it.first, it.second) NGSILD_LANGUAGEPROPERTY_TYPE.uri -> NgsiLdLanguageProperty.create(it.first, it.second) + NGSILD_VOCABPROPERTY_TYPE.uri -> NgsiLdVocabProperty.create(it.first, it.second) else -> BadRequestDataException("Attribute ${it.first} has an unknown type: $attributeType").left() } }.let { l -> @@ -513,6 +583,8 @@ suspend fun ExpandedAttributeInstances.toNgsiLdAttribute( NgsiLdJsonProperty.create(attributeName, this) isAttributeOfType(this[0], NGSILD_LANGUAGEPROPERTY_TYPE) -> NgsiLdLanguageProperty.create(attributeName, this) + isAttributeOfType(this[0], NGSILD_VOCABPROPERTY_TYPE) -> + NgsiLdVocabProperty.create(attributeName, this) else -> BadRequestDataException("Unrecognized type for $attributeName").left() } @@ -589,3 +661,13 @@ val NGSILD_LANGUAGEPROPERTIES_FORBIDDEN_MEMBERS = listOf( NGSILD_PROPERTY_VALUE, NGSILD_UNIT_CODE_PROPERTY ) + +val NGSILD_VOCABPROPERTIES_CORE_MEMBERS = listOf( + NGSILD_VOCABPROPERTY_VALUE +).plus(NGSILD_ATTRIBUTES_CORE_MEMBERS) + +val NGSILD_VOCABPROPERTIES_FORBIDDEN_MEMBERS = listOf( + NGSILD_RELATIONSHIP_OBJECT, + NGSILD_PROPERTY_VALUE, + NGSILD_UNIT_CODE_PROPERTY +) diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt index b45b5d9aa..6e66bc8bd 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt @@ -44,12 +44,16 @@ object JsonLdUtils { const val NGSILD_LANGUAGEPROPERTY_TERM = "LanguageProperty" val NGSILD_LANGUAGEPROPERTY_TYPE = AttributeType("https://uri.etsi.org/ngsi-ld/LanguageProperty") const val NGSILD_LANGUAGEPROPERTY_VALUE = "https://uri.etsi.org/ngsi-ld/hasLanguageMap" + const val NGSILD_VOCABPROPERTY_TERM = "VocabProperty" + val NGSILD_VOCABPROPERTY_TYPE = AttributeType("https://uri.etsi.org/ngsi-ld/VocabProperty") + const val NGSILD_VOCABPROPERTY_VALUE = "https://uri.etsi.org/ngsi-ld/hasVocab" const val NGSILD_PROPERTY_VALUES = "https://uri.etsi.org/ngsi-ld/hasValues" const val NGSILD_GEOPROPERTY_VALUES = "https://uri.etsi.org/ngsi-ld/hasValues" const val NGSILD_RELATIONSHIP_OBJECTS = "https://uri.etsi.org/ngsi-ld/hasObjects" const val NGSILD_JSONPROPERTY_VALUES = "https://uri.etsi.org/ngsi-ld/jsons" const val NGSILD_LANGUAGEPROPERTY_VALUES = "https://uri.etsi.org/ngsi-ld/hasLanguageMaps" + const val NGSILD_VOCABPROPERTY_VALUES = "https://uri.etsi.org/ngsi-ld/hasVocabs" private const val JSONLD_GRAPH = "@graph" const val JSONLD_ID_TERM = "id" @@ -63,6 +67,8 @@ object JsonLdUtils { const val JSONLD_JSON_TERM = "json" const val JSONLD_LANGUAGE = "@language" const val JSONLD_LANGUAGEMAP_TERM = "languageMap" + const val JSONLD_VOCAB = "@vocab" + const val JSONLD_VOCAB_TERM = "vocab" const val JSONLD_JSON = "@json" const val JSONLD_CONTEXT = "@context" const val NGSILD_SCOPE_TERM = "scope" diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/model/CompactedEntityTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/model/CompactedEntityTests.kt index c0fe63663..c623dd6fc 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/model/CompactedEntityTests.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/model/CompactedEntityTests.kt @@ -785,4 +785,62 @@ class CompactedEntityTests { assertJsonPayloadsAreEqual(expectedSimplifiedRepresentation, serializeObject(simplifiedRepresentation)) } + + @Test + fun `it should return the simplified representation of a VocabProperty - string value`() { + val compactedEntity = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + "vocabProperty": { + "type": "VocabProperty", + "vocab": "stellio" + } + } + """.trimIndent() + .deserializeAsMap() + + val simplifiedRepresentation = compactedEntity.toSimplifiedAttributes() + + val expectedSimplifiedRepresentation = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + "vocabProperty": { + "vocab": "stellio" + } + } + """.trimIndent() + + assertJsonPayloadsAreEqual(expectedSimplifiedRepresentation, serializeObject(simplifiedRepresentation)) + } + + @Test + fun `it should return the simplified representation of a VocabProperty - array of strings value`() { + val compactedEntity = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + "vocabProperty": { + "type": "VocabProperty", + "vocab": ["stellio", "egm"] + } + } + """.trimIndent() + .deserializeAsMap() + + val simplifiedRepresentation = compactedEntity.toSimplifiedAttributes() + + val expectedSimplifiedRepresentation = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + "vocabProperty": { + "vocab": ["stellio", "egm"] + } + } + """.trimIndent() + + assertJsonPayloadsAreEqual(expectedSimplifiedRepresentation, serializeObject(simplifiedRepresentation)) + } } diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/model/NgsiLdEntityTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/model/NgsiLdEntityTests.kt index a9bfaf748..93db489ce 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/model/NgsiLdEntityTests.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/model/NgsiLdEntityTests.kt @@ -1,5 +1,6 @@ package com.egm.stellio.shared.model +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_ID import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DEFAULT_VOCAB import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LOCATION_PROPERTY @@ -1010,4 +1011,118 @@ class NgsiLdEntityTests { expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS) } } + + @Test + fun `it should parse an entity with a VocabProperty - array of strings value`() = runTest { + val rawEntity = + """ + { + "id":"urn:ngsi-ld:Device:01234", + "type":"Device", + "vocabProperty": { + "type": "VocabProperty", + "vocab": ["stellio", "egm"] + } + } + """.trimIndent() + + val ngsiLdEntity = expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() + .shouldSucceedAndResult() + + val vocabProperty = ngsiLdEntity.vocabProperties.first() + assertNotNull(vocabProperty) + assertEquals("${NGSILD_DEFAULT_VOCAB}vocabProperty", vocabProperty.name) + assertEquals(1, vocabProperty.instances.size) + val vocabPropertyInstance = vocabProperty.instances[0] + assertEquals( + listOf( + mapOf(JSONLD_ID to "${NGSILD_DEFAULT_VOCAB}stellio"), + mapOf(JSONLD_ID to "${NGSILD_DEFAULT_VOCAB}egm") + ), + vocabPropertyInstance.vocab + ) + } + + @Test + fun `it should parse an entity with a VocabProperty - string value`() = runTest { + val rawEntity = + """ + { + "id":"urn:ngsi-ld:Device:01234", + "type":"Device", + "vocabProperty": { + "type": "VocabProperty", + "vocab": "stellio" + } + } + """.trimIndent() + + val ngsiLdEntity = expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() + .shouldSucceedAndResult() + + val vocabProperty = ngsiLdEntity.vocabProperties.first() + assertNotNull(vocabProperty) + assertEquals("${NGSILD_DEFAULT_VOCAB}vocabProperty", vocabProperty.name) + assertEquals(1, vocabProperty.instances.size) + val vocabPropertyInstance = vocabProperty.instances[0] + assertEquals( + listOf( + mapOf(JSONLD_ID to "${NGSILD_DEFAULT_VOCAB}stellio") + ), + vocabPropertyInstance.vocab + ) + } + + @Test + fun `it should not parse an entity with a VocabProperty without a vocab member`() = runTest { + val rawEntity = + """ + { + "id":"urn:ngsi-ld:Device:01234", + "type":"Device", + "vocabProperty": { + "type": "VocabProperty", + "value": "stellio" + } + } + """.trimIndent() + + expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() + .shouldFail { + assertInstanceOf(BadRequestDataException::class.java, it) + assertEquals( + "VocabProperty ${NGSILD_DEFAULT_VOCAB}vocabProperty has an instance " + + "without a vocab member", + it.message + ) + } + } + + @Test + fun `it should not parse an entity with a VocabProperty with an invalid vocab member`() = runTest { + val rawEntity = + """ + { + "id":"urn:ngsi-ld:Device:01234", + "type":"Device", + "vocabProperty": { + "type": "VocabProperty", + "vocab": { + "name": "stellio", + "company": "EGM" + } + } + } + """.trimIndent() + + expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() + .shouldFail { + assertInstanceOf(BadRequestDataException::class.java, it) + assertEquals( + "VocabProperty ${NGSILD_DEFAULT_VOCAB}vocabProperty has a vocab member " + + "that is not a string, nor an array of string", + it.message + ) + } + } } diff --git a/shared/src/testFixtures/kotlin/com/egm/stellio/shared/util/JsonLdContextUtils.kt b/shared/src/testFixtures/kotlin/com/egm/stellio/shared/util/JsonLdContextUtils.kt index b021d45ff..1fae07482 100644 --- a/shared/src/testFixtures/kotlin/com/egm/stellio/shared/util/JsonLdContextUtils.kt +++ b/shared/src/testFixtures/kotlin/com/egm/stellio/shared/util/JsonLdContextUtils.kt @@ -37,6 +37,8 @@ const val LUMINOSITY_COMPACT_JSONPROPERTY = "luminosity" const val LUMINOSITY_JSONPROPERTY = "https://ontology.eglobalmark.com/apic#$LUMINOSITY_COMPACT_JSONPROPERTY" const val FRIENDLYNAME_COMPACT_LANGUAGEPROPERTY = "friendlyName" const val FRIENDLYNAME_LANGUAGEPROPERTY = "https://ontology.eglobalmark.com/apic#$FRIENDLYNAME_COMPACT_LANGUAGEPROPERTY" +const val CATEGORY_COMPACT_VOCABPROPERTY = "category" +const val CATEGORY_VOCAPPROPERTY = "https://ontology.eglobalmark.com/apic#$CATEGORY_COMPACT_VOCABPROPERTY" const val MANAGED_BY_COMPACT_RELATIONSHIP = "managedBy" const val MANAGED_BY_RELATIONSHIP = "https://ontology.eglobalmark.com/egm#$MANAGED_BY_COMPACT_RELATIONSHIP" diff --git a/shared/src/testFixtures/resources/jsonld-contexts/apic.jsonld b/shared/src/testFixtures/resources/jsonld-contexts/apic.jsonld index 0b79db69e..e439e5257 100644 --- a/shared/src/testFixtures/resources/jsonld-contexts/apic.jsonld +++ b/shared/src/testFixtures/resources/jsonld-contexts/apic.jsonld @@ -34,6 +34,7 @@ "movementCount": "https://ontology.eglobalmark.com/apic#movementCount", "hornetCount": "https://ontology.eglobalmark.com/apic#hornetCount", "dateOfFirstBee": "https://ontology.eglobalmark.com/apic#dateOfFirstBee", - "friendlyName": "https://ontology.eglobalmark.com/apic#friendlyName" + "friendlyName": "https://ontology.eglobalmark.com/apic#friendlyName", + "category": "https://ontology.eglobalmark.com/apic#category" } }