diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 1301a0c442..61e4901c8a 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1,5 +1,6 @@ ## Fixed Issues: - #XXXX: Fixed XXX + #1706: Fixed a bug in the logic merging attributes from the URL parameter (attrs) with attributes from registrations + #XXXX: Fixed a bug for system attributes which were sometimes included for sub-attributes in responses even when not requested ## New Features: #XXXX: The context cache now ignores the protocol (http, https) in the URLs and thus avoids storing two copies of the very same @context diff --git a/src/lib/orionld/regMatch/regMatchAttributesForGet.cpp b/src/lib/orionld/regMatch/regMatchAttributesForGet.cpp index df2293169f..7c3ae801b8 100644 --- a/src/lib/orionld/regMatch/regMatchAttributesForGet.cpp +++ b/src/lib/orionld/regMatch/regMatchAttributesForGet.cpp @@ -95,8 +95,37 @@ StringArray* regMatchAttributesForGet { bool allAttributes = (propertyNamesP == NULL) && (relationshipNamesP == NULL); +#ifdef DEBUG LM_T(LmtDistOpAttributes, ("Creating the union of attributes GET URL-Param vs Registered Attributes")); + if (attrListP != NULL) + { + LM_T(LmtDistOpAttributes, ("URI param 'attrs':")); + for (int ix = 0; ix < attrListP->items; ix++) + { + LM_T(LmtDistOpAttributes, (" o %s", attrListP->array[ix])); + } + } + + if (propertyNamesP != NULL) + { + LM_T(LmtDistOpAttributes, ("Reg 'propertyNames':")); + for (KjNode* propertyP = propertyNamesP->value.firstChildP; propertyP != NULL; propertyP = propertyP->next) + { + LM_T(LmtDistOpAttributes, (" o %s", propertyP->value.s)); + } + } + + if (relationshipNamesP != NULL) + { + LM_T(LmtDistOpAttributes, ("Reg 'relationshipNames':")); + for (KjNode* relationshipP = relationshipNamesP->value.firstChildP; relationshipP != NULL; relationshipP = relationshipP->next) + { + LM_T(LmtDistOpAttributes, (" o %s", relationshipP->value.s)); + } + } +#endif + if (allAttributes == true) { // @@ -118,7 +147,7 @@ StringArray* regMatchAttributesForGet StringArray* sList = (StringArray*) kaAlloc(&orionldState.kalloc, sizeof(StringArray)); int items = 0; - if (allAttributes == true) // We know that attrListP == NULL (otherwise it would have returned already a few lines up) + if (allAttributes == true) // We know that attrListP == NULL (otherwise this function would have ended already a few lines up) { // Everything matches - return an empty array sList->items = 0; @@ -127,7 +156,10 @@ StringArray* regMatchAttributesForGet return sList; } else if ((attrListP != NULL) && (attrListP->items > 0)) + { items = attrListP->items; + LM_T(LmtDistOpAttributes, ("%d attributes in URL param", items)); + } else { // Count items in propertyNamesP + relationshipNamesP @@ -168,24 +200,34 @@ StringArray* regMatchAttributesForGet else { int matches = 0; + LM_T(LmtDistOpAttributes, ("Matching %d URL attrs", attrListP->items)); for (int ix = 0; ix < attrListP->items; ix++) { bool match = false; + LM_T(LmtDistOpAttributes, ("Matching URL attr '%s' with propertyNames", attrListP->array[ix])); if (propertyNamesP != NULL) match = (kjStringValueLookupInArray(propertyNamesP, attrListP->array[ix]) != NULL); + LM_T(LmtDistOpAttributes, ("Matching URL attr '%s' with relationshipNames", attrListP->array[ix])); if ((match == false) && (relationshipNamesP != NULL)) match = (kjStringValueLookupInArray(relationshipNamesP, attrListP->array[ix]) != NULL); if (match == false) + { + LM_T(LmtDistOpAttributes, ("%s is not a match", attrListP->array[ix])); continue; + } LM_T(LmtDistOpAttributes, ("Adding '%s' to the attrList of the DistOp", attrListP->array[ix])); sList->array[matches++] = attrListP->array[ix]; if (regP->mode == RegModeExclusive) + { stringArrayRemoveItem(attrListP, ix); + --ix; // Compensating for the item in attrListP that was just removed + } + LM_T(LmtDistOpAttributes, ("Matching %d (ix is %d) URL attrs", attrListP->items, ix)); } if (matches == 0) diff --git a/src/lib/orionld/serviceRoutines/orionldGetEntitiesPage.cpp b/src/lib/orionld/serviceRoutines/orionldGetEntitiesPage.cpp index cb249be4dc..3aa779f75b 100644 --- a/src/lib/orionld/serviceRoutines/orionldGetEntitiesPage.cpp +++ b/src/lib/orionld/serviceRoutines/orionldGetEntitiesPage.cpp @@ -57,38 +57,44 @@ extern "C" // static void cleanupSysAttrs(void) { - LM_T(LmtSR, ("In cleanupSysAttrs: orionldState.responseTree: %p", orionldState.responseTree)); + LM_T(LmtSysAttrs, ("orionldState.responseTree: %p", orionldState.responseTree)); for (KjNode* entityP = orionldState.responseTree->value.firstChildP; entityP != NULL; entityP = entityP->next) { - KjNode* attrP = entityP->value.firstChildP; - KjNode* nextAttrP; + KjNode* idP = kjLookup(entityP, "id"); + const char* id = (idP != NULL)? idP->value.s : "unidentified"; - while (attrP != NULL) + LM_T(LmtSysAttrs, ("Removing sysAttrs for the entity '%s'", id)); + + KjNode* createdAtP = kjLookup(entityP, "createdAt"); + KjNode* modifiedAtP = kjLookup(entityP, "modifiedAt"); + if (createdAtP != NULL) kjChildRemove(entityP, createdAtP); + if (modifiedAtP != NULL) kjChildRemove(entityP, modifiedAtP); + + for (KjNode* attrP = entityP->value.firstChildP; attrP != NULL; attrP = attrP->next) { - nextAttrP = attrP->next; + if (attrP->type != KjObject) + continue; - if (strcmp(attrP->name, "createdAt") == 0) kjChildRemove(entityP, attrP); - else if (strcmp(attrP->name, "modifiedAt") == 0) kjChildRemove(entityP, attrP); - else if (attrP->type == KjObject) - { - // It's an attribute + LM_T(LmtSysAttrs, ("Removing sysAttrs for the attribute '%s'", attrP->name)); - KjNode* subAttrP = attrP->value.firstChildP; - KjNode* nextSubAttrP; + KjNode* createdAtP = kjLookup(attrP, "createdAt"); + KjNode* modifiedAtP = kjLookup(attrP, "modifiedAt"); + if (createdAtP != NULL) kjChildRemove(attrP, createdAtP); + if (modifiedAtP != NULL) kjChildRemove(attrP, modifiedAtP); - while (subAttrP != NULL) - { - nextSubAttrP = subAttrP->next; + for (KjNode* subAttrP = attrP->value.firstChildP; subAttrP != NULL; subAttrP = subAttrP->next) + { + if (subAttrP->type != KjObject) + continue; - if (strcmp(subAttrP->name, "createdAt") == 0) kjChildRemove(attrP, subAttrP); - else if (strcmp(subAttrP->name, "modifiedAt") == 0) kjChildRemove(attrP, subAttrP); + LM_T(LmtSysAttrs, ("Removing sysAttrs for the sub-attribute '%s'", subAttrP->name)); - subAttrP = nextSubAttrP; - } + KjNode* createdAtP = kjLookup(subAttrP, "createdAt"); + KjNode* modifiedAtP = kjLookup(subAttrP, "modifiedAt"); + if (createdAtP != NULL) kjChildRemove(subAttrP, createdAtP); + if (modifiedAtP != NULL) kjChildRemove(subAttrP, modifiedAtP); } - - attrP = nextAttrP; } } } diff --git a/test/functionalTest/cases/0000_ngsild/ngsild_forwarding_issue_1706.test b/test/functionalTest/cases/0000_ngsild/ngsild_forwarding_issue_1706.test new file mode 100644 index 0000000000..a76d0db961 --- /dev/null +++ b/test/functionalTest/cases/0000_ngsild/ngsild_forwarding_issue_1706.test @@ -0,0 +1,353 @@ +# Copyright 2025 FIWARE Foundation e.V. +# +# This file is part of Orion-LD Context Broker. +# +# Orion-LD Context Broker is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# Orion-LD Context Broker is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero +# General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Orion-LD Context Broker. If not, see http://www.gnu.org/licenses/. +# +# For those usages not covered by this license please contact with +# orionld at fiware dot org + +# VALGRIND_READY - to mark the test ready for valgrindTestSuite.sh + +--NAME-- +Replication of an entity + +--SHELL-INIT-- +dbInit CB +dbInit CP1 +orionldStart CB -experimental -forwarding -wip entityMaps +orionldStart CP1 -experimental + +--SHELL-- +# +# 01. Create and entity urn:E1 in CP1 with three attributes: heartRate, location, and X +# 02. Create an exclusive registration in CB with CP1 as endpoint, with all three attributes +# 03. GET entities from CB without attrs - see all three attributes +# 04. GET entities from CB without attrs BUT with sysAttrs - see all three attributes (with all sysAttrs) +# 05. GET entities from CB with attrs=heartRate,location - see both attributes +# 06. GET entities from CB with attrs=heartRate,location AND sysAttrs - see both attributes (with all sysAttrs) +# + +echo "01. Create and entity urn:E1 in CP1 with three attributes: heartRate, location, and X" +echo "=====================================================================================" +payload='{ + "id": "urn:E1", + "type": "Animal", + "heartRate": { + "type": "Property", + "value": 10.4, + "unitCode": "5K", + "observedAt": "2024-02-02T15:00:00.000Z", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Device:cow001" + } + }, + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [13.404, 52.47] + }, + "observedAt": "2024-02-02T15:00:00.000Z", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Device:cow001" + } + }, + "X": 1 +}' +orionCurl --url /ngsi-ld/v1/entities --payload "$payload" --port $CP1_PORT +echo +echo + + +echo "02. Create an exclusive registration in CB with CP1 as endpoint, with all three attributes" +echo "==========================================================================================" +payload='{ + "id": "urn:R1", + "type": "ContextSourceRegistration", + "information": [ + { + "entities": [ + { + "type": "Animal", + "id": "urn:E1" + } + ], + "propertyNames": [ + "heartRate", + "location", + "X" + ] + } + ], + "mode": "exclusive", + "operations": [ "retrieveOps" ], + "endpoint": "http://localhost:'$CP1_PORT'" +}' +orionCurl --url /ngsi-ld/v1/csourceRegistrations --payload "$payload" +echo +echo + + +echo "03. GET entities from CB without attrs - see all three attributes" +echo "=================================================================" +orionCurl --url '/ngsi-ld/v1/entities?type=Animal' +echo +echo + + +echo "04. GET entities from CB without attrs BUT with sysAttrs - see all three attributes (with all sysAttrs)" +echo "=======================================================================================================" +orionCurl --url '/ngsi-ld/v1/entities?type=Animal&options=sysAttrs' +echo +echo + +echo "05. GET entities from CB with attrs=heartRate,location - see both attributes" +echo "============================================================================" +orionCurl --url '/ngsi-ld/v1/entities?type=Animal&attrs=location,heartRate' +echo +echo + + +echo "06. GET entities from CB with attrs=heartRate,location AND sysAttrs - see both attributes (with all sysAttrs)" +echo "=============================================================================================================" +orionCurl --url '/ngsi-ld/v1/entities?type=Animal&attrs=location,heartRate&options=sysAttrs' +echo +echo + + +--REGEXPECT-- +01. Create and entity urn:E1 in CP1 with three attributes: heartRate, location, and X +===================================================================================== +HTTP/1.1 201 Created +Content-Length: 0 +Date: REGEX(.*) +Location: /ngsi-ld/v1/entities/urn:E1 + + + +02. Create an exclusive registration in CB with CP1 as endpoint, with all three attributes +========================================================================================== +HTTP/1.1 201 Created +Content-Length: 0 +Date: REGEX(.*) +Location: /ngsi-ld/v1/csourceRegistrations/urn:R1 + + + +03. GET entities from CB without attrs - see all three attributes +================================================================= +HTTP/1.1 200 OK +Content-Length: 444 +Content-Type: application/json +Date: REGEX(.*) +Link: