diff --git a/src/lib/common/JsonHelper.cpp b/src/lib/common/JsonHelper.cpp index 3bd6fd5d2a..9adc597fc2 100644 --- a/src/lib/common/JsonHelper.cpp +++ b/src/lib/common/JsonHelper.cpp @@ -138,7 +138,7 @@ std::string objectToJson(std::map& list) * * JsonObjectHelper - */ -JsonObjectHelper::JsonObjectHelper(): empty(true), closed(false) +JsonObjectHelper::JsonObjectHelper(): empty(true) { ss += '{'; } @@ -280,15 +280,17 @@ void JsonObjectHelper::addNull(const std::string& key) * * JsonObjectHelper::str - */ -std::string JsonObjectHelper::str() +std::string JsonObjectHelper::str(bool closed) { - // This check allows to call str() several times (needed when this is used in ExprContext) - if (!closed) + // closed == false used in ExprContext logic + if (closed) { - ss += '}'; - closed = true; + return ss + '}'; + } + else + { + return ss; } - return ss; } @@ -297,7 +299,7 @@ std::string JsonObjectHelper::str() * * JsonVectorHelper - */ -JsonVectorHelper::JsonVectorHelper(): empty(true), closed(false) +JsonVectorHelper::JsonVectorHelper(): empty(true) { ss += '['; } @@ -425,14 +427,17 @@ void JsonVectorHelper::addNull(void) /* **************************************************************************** * * JsonVectorHelper::str - +* FIXME PR: bool closed probably unneded in vectors */ -std::string JsonVectorHelper::str() +std::string JsonVectorHelper::str(bool closed) { - // This check allows to call str() several times (needed when this is used in ExprContext) - if (!closed) + // closed == false used in ExprContext logic + if (closed) { - ss += ']'; - closed = true; + return ss + ']'; + } + else + { + return ss; } - return ss; } diff --git a/src/lib/common/JsonHelper.h b/src/lib/common/JsonHelper.h index bcb8aaeaf2..5b2633e0ca 100644 --- a/src/lib/common/JsonHelper.h +++ b/src/lib/common/JsonHelper.h @@ -45,12 +45,11 @@ class JsonObjectHelper void addBool(const std::string& key, bool b); void addNull(const std::string& key); - std::string str(); + std::string str(bool closed = true); private: std::string ss; bool empty; - bool closed; }; @@ -68,12 +67,11 @@ class JsonVectorHelper void addNull(void); - std::string str(); + std::string str(bool closed = true); private: std::string ss; bool empty; - bool closed; }; diff --git a/src/lib/common/errorMessages.h b/src/lib/common/errorMessages.h index d50bf8acf7..a5f36a2f3a 100644 --- a/src/lib/common/errorMessages.h +++ b/src/lib/common/errorMessages.h @@ -87,7 +87,7 @@ #define ERROR_DESC_BAD_REQUEST_FORMAT_INVALID "invalid render format for notifications" #define ERROR_DESC_BAD_REQUEST_SERVICE_NOT_FOUND "Service not found. Check your URL as probably it is wrong." #define ERROR_DESC_BAD_REQUEST_WRONG_GEOJSON "Wrong GeoJson" -#define ERROR_DESC_BAD_REQUEST_METADATA_NOT_ALLOWED_CUSTOM_NOTIF "metadata are not allowed in ngsi field in custom notifications" +#define ERROR_DESC_BAD_REQUEST_METADATA_NOT_ALLOWED_CUSTOM_NOTIF "only evalPriority metadata is allowed in ngsi field in custom notifications" #define ERROR_NOT_FOUND "NotFound" #define ERROR_DESC_NOT_FOUND_ENTITY "The requested entity has not been found. Check type and id" diff --git a/src/lib/common/limits.h b/src/lib/common/limits.h index 39295ec64d..9a903f7953 100644 --- a/src/lib/common/limits.h +++ b/src/lib/common/limits.h @@ -229,4 +229,14 @@ +/* **************************************************************************** +* +* MIX_PRIORITY and MAX_PRIORITY +* +*/ +#define MIN_PRIORITY 1 +#define MAX_PRIORITY LLONG_MAX + + + #endif // SRC_LIB_COMMON_LIMITS_H_ diff --git a/src/lib/expressions/ExprContext.cpp b/src/lib/expressions/ExprContext.cpp index 80148399b3..94bf607a74 100644 --- a/src/lib/expressions/ExprContext.cpp +++ b/src/lib/expressions/ExprContext.cpp @@ -47,7 +47,7 @@ ExprContextObject::ExprContextObject(bool _basic) */ std::string ExprContextObject::getJexlContext(void) { - return jh.str(); + return jh.str(false) + '}'; } @@ -83,7 +83,14 @@ void ExprContextObject::add(const std::string &key, const std::string &_value, b else { LM_T(LmtExpr, ("adding to JEXL expression context object (string): %s=%s", key.c_str(), _value.c_str())); - jh.addString(key, _value); + if (raw) + { + jh.addRaw(key, _value); + } + else + { + jh.addString(key, _value); + } } } diff --git a/src/lib/jsonParseV2/parseSubscription.cpp b/src/lib/jsonParseV2/parseSubscription.cpp index 593161b202..7dd54c9b65 100644 --- a/src/lib/jsonParseV2/parseSubscription.cpp +++ b/src/lib/jsonParseV2/parseSubscription.cpp @@ -554,10 +554,19 @@ static std::string parseCustomPayload return badInput(ciP, r); } - // metadadata are now allowed in this case - if (caP->metadataVector.size() > 0) + // only evalPriority metadadata is allowed in this case + for (unsigned ix = 0; ix < caP->metadataVector.size(); ix++) { - return badInput(ciP, ERROR_DESC_BAD_REQUEST_METADATA_NOT_ALLOWED_CUSTOM_NOTIF); + if (caP->metadataVector[ix]->name != NGSI_MD_EVAL_PRIORITY) + { + return badInput(ciP, ERROR_DESC_BAD_REQUEST_METADATA_NOT_ALLOWED_CUSTOM_NOTIF); + } + else + { + // priority must be a number + // priority must be between MIX_PRIORITY and MAX_PRIORITY + // FIXME: PR (include a .test to asses the checkings works) + } } } } diff --git a/src/lib/ngsi/ContextAttribute.cpp b/src/lib/ngsi/ContextAttribute.cpp index 79ce3f57fc..5fa1f9f78c 100644 --- a/src/lib/ngsi/ContextAttribute.cpp +++ b/src/lib/ngsi/ContextAttribute.cpp @@ -763,6 +763,26 @@ bool ContextAttribute::getLocation(orion::BSONObj* attrsP) const +/* **************************************************************************** +* +* getEvalPriority - +*/ +double ContextAttribute::getEvalPriority(void) +{ + for (unsigned int ix = 0; ix < metadataVector.size(); ix++) + { + if (metadataVector[ix]->name == NGSI_MD_EVAL_PRIORITY) + { + return metadataVector[ix]->numberValue; + } + } + + // if the attribute doesn't have evalPriority metadata, then max priority is assumed + return MAX_PRIORITY; +} + + + /* **************************************************************************** * * toJsonV1AsObject - @@ -1140,9 +1160,10 @@ std::string ContextAttribute::toJson(const std::vector& metadataFi * toJsonValue - * * To be used by options=values and options=unique renderings +* Also used by the ngsi expression logic * */ -std::string ContextAttribute::toJsonValue(void) +std::string ContextAttribute::toJsonValue(ExprContextObject* exprContextObjectP) { if (compoundValueP != NULL) { @@ -1164,10 +1185,10 @@ std::string ContextAttribute::toJsonValue(void) } else if (valueType == orion::ValueTypeString) { - std::string out = "\""; - out += toJsonString(stringValue); - out += '"'; - return out; + //std::string out = "\""; + //out += toJsonString(stringValue); + //out += '"'; + return smartStringValue(stringValue, exprContextObjectP, "null"); } else if (valueType == orion::ValueTypeBoolean) { diff --git a/src/lib/ngsi/ContextAttribute.h b/src/lib/ngsi/ContextAttribute.h index f242bf101d..63e4551690 100644 --- a/src/lib/ngsi/ContextAttribute.h +++ b/src/lib/ngsi/ContextAttribute.h @@ -95,6 +95,8 @@ typedef struct ContextAttribute /* Check if attribute means a location */ bool getLocation(orion::BSONObj* attrsP) const; + double getEvalPriority(void); + std::string toJsonV1(bool asJsonObject, RequestType request, const std::vector& metadataFilter, @@ -110,7 +112,7 @@ typedef struct ContextAttribute std::string toJson(const std::vector& metadataFilter, bool renderNgsiField = false, ExprContextObject* exprContextObjectP = NULL); - std::string toJsonValue(void); + std::string toJsonValue(ExprContextObject* exprContextObjectP = NULL); std::string toJsonAsValue(ApiVersion apiVersion, bool acceptedTextPlain, diff --git a/src/lib/ngsi/Metadata.h b/src/lib/ngsi/Metadata.h index 591df92cb1..e1fcdab163 100644 --- a/src/lib/ngsi/Metadata.h +++ b/src/lib/ngsi/Metadata.h @@ -46,6 +46,7 @@ * * Metadata interpreted by Orion Context Broker, i.e. not custom metadata */ +#define NGSI_MD_EVAL_PRIORITY "evalPriority" #define NGSI_MD_IGNORE_TYPE "ignoreType" #define NGSI_MD_PREVIOUSVALUE "previousValue" // Special metadata #define NGSI_MD_ACTIONTYPE "actionType" // Special metadata diff --git a/src/lib/ngsiNotify/Notifier.cpp b/src/lib/ngsiNotify/Notifier.cpp index 14724c9a1e..6fd17245da 100644 --- a/src/lib/ngsiNotify/Notifier.cpp +++ b/src/lib/ngsiNotify/Notifier.cpp @@ -193,6 +193,51 @@ static bool setJsonPayload +/* **************************************************************************** +* +* orderByPriority - +* +* Return false if some problem occur +*/ +static void orderByPriority +( + const Entity& ngsi, + std::vector* orderedAttrs +) +{ + // Add all the attributes to a temporal vector + std::vector attrs; + + for (unsigned ix = 0; ix < ngsi.attributeVector.size(); ix++) + { + attrs.push_back(ngsi.attributeVector[ix]); + } + + // Pass the content of attrs to orderedAttrs based in the evalPriority element + while (!attrs.empty()) + { + ContextAttribute* selectedAttr = attrs[0]; + unsigned int selectedIx = 0; + double prio = selectedAttr->getEvalPriority(); + + for (unsigned ix = 0; ix < attrs.size(); ix++) + { + double newPrio = attrs[ix]->getEvalPriority(); + if (newPrio < prio) + { + selectedAttr = attrs[ix]; + selectedIx = ix; + prio = newPrio; + } + } + + orderedAttrs->push_back(selectedAttr); + attrs.erase(attrs.begin() + selectedIx); + } +} + + + /* **************************************************************************** * * setNgsiPayload - @@ -209,7 +254,8 @@ static bool setNgsiPayload bool blacklist, const std::vector& metadataFilter, std::string* payloadP, - RenderFormat renderFormat + RenderFormat renderFormat, + bool basic // used by TIME_EXPR_CTXBLD_START/STOP macros ) { NotifyContextRequest ncr; @@ -239,10 +285,16 @@ static bool setNgsiPayload cer.entity.fill(effectiveId, effectiveType, en.isPattern, en.servicePath); - // First we add attributes in the ngsi field - for (unsigned int ix = 0; ix < ngsi.attributeVector.size(); ix++) + // First we add attributes in the ngsi field, adding calculated expressions to context in order of priority + std::vector orderedNgsiAttrs; + orderByPriority(ngsi, &orderedNgsiAttrs); + + for (unsigned int ix = 0; ix < orderedNgsiAttrs.size(); ix++) { - cer.entity.attributeVector.push_back(new ContextAttribute(ngsi.attributeVector[ix], false, true)); + cer.entity.attributeVector.push_back(new ContextAttribute(orderedNgsiAttrs[ix], false, true)); + TIME_EXPR_CTXBLD_START(); + exprContextObjectP->add(orderedNgsiAttrs[ix]->name, orderedNgsiAttrs[ix]->toJsonValue(exprContextObjectP), true); + TIME_EXPR_CTXBLD_STOP(); } // Next, other attributes in the original entity not already added for (unsigned int ix = 0; ix < en.attributeVector.size(); ix++) @@ -393,7 +445,7 @@ static SenderThreadParams* buildSenderParamsCustom { // Important to use const& for Entity here. Otherwise problems may occur in the object release logic const Entity& ngsi = (notification.type == ngsiv2::HttpNotification ? notification.httpInfo.ngsi : notification.mqttInfo.ngsi); - if (!setNgsiPayload(ngsi, subscriptionId, en, &exprContext, attrsFilter, blacklist, metadataFilter, &payload, renderFormat)) + if (!setNgsiPayload(ngsi, subscriptionId, en, &exprContext, attrsFilter, blacklist, metadataFilter, &payload, renderFormat, basic)) { // Warning already logged in macroSubstitute() return NULL; diff --git a/test/functionalTest/cases/4556_eval_priority_in_expressions/eval_priority_in_expressions.test b/test/functionalTest/cases/4556_eval_priority_in_expressions/eval_priority_in_expressions.test new file mode 100644 index 0000000000..5df812c7a1 --- /dev/null +++ b/test/functionalTest/cases/4556_eval_priority_in_expressions/eval_priority_in_expressions.test @@ -0,0 +1,262 @@ +# Copyright 2024 Telefonica Investigacion y Desarrollo, S.A.U +# +# This file is part of Orion Context Broker. +# +# Orion 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 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 Context Broker. If not, see http://www.gnu.org/licenses/. +# +# For those usages not covered by this license please contact with +# iot_support at tid dot es + +# VALGRIND_READY - to mark the test ready for valgrindTestSuite.sh +# JEXL_EXPR_FLAVOUR - to mark the test has to execute only when contextBroker includes jexl-expr flavour + +--NAME-- +Evaluation priority in expressions + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with expressions P1=A+1, P2=P1+1, P3=P2+1, S=P3+1 (P3 not notified) +# 02. Create entity E1 with A=1 +# 03. Update entity E1 with A=2.1 +# 04. Dump accumulator and see two notifications (P1:2, P2:3, S:5) (P1:3.1, P2:4.1, S:6.1) +# + + +echo "01. Create custom sub with expressions P1=A+1, P2=P1+1, P3=P2+1, S=P3+1 (P3 not notified)" +echo "=========================================================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "P2": { + "value": "${P1+1}", + "type": "Calculated", + "metadata": { + "evalPriority": { + "value": 2, + "type": "Number" + } + } + }, + "P1": { + "value": "${A+1}", + "type": "Calculated", + "metadata": { + "evalPriority": { + "value": 1, + "type": "Number" + } + } + }, + "P3": { + "value": "${P2+1}", + "type": "Calculated", + "metadata": { + "evalPriority": { + "value": 3, + "type": "Number" + } + } + }, + "S": { + "value": "${P3+1}", + "type": "Calculated" + } + } + }, + "attrs": [ "P1", "P2", "S" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A=1" +echo "=============================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": 1, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E1 with A=2.1" +echo "===============================" +payload='{ + "A": { + "value": 2.1, + "type": "Number" + } +}' +orionCurl --url /v2/entities/E1/attrs -X PATCH --payload "$payload" +echo +echo + + +echo "Dump accumulator and see two notifications (P1:2, P2:3, S:5) (P1:3.1, P2:4.1, S:6.1)" +echo "====================================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with expressions P1=A+1, P2=P1+1, P3=P2+1, S=P3+1 (P3 not notified) +========================================================================================= +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/subscriptions/REGEX([0-9a-f]{24}) +Content-Length: 0 + + + +02. Create entity E1 with A=1 +============================= +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E1?type=T +Content-Length: 0 + + + +03. Update entity E1 with A=2.1 +=============================== +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +Dump accumulator and see two notifications (P1:2, P2:3, S:5) (P1:3.1, P2:4.1, S:6.1) +==================================================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 313 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: normalized +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: application/json; charset=utf-8 +Fiware-Correlator: REGEX([0-9a-f\-]{36}); cbnotif=1 + +{ + "data": [ + { + "P1": { + "metadata": { + "evalPriority": { + "type": "Number", + "value": 1 + } + }, + "type": "Calculated", + "value": 2 + }, + "P2": { + "metadata": { + "evalPriority": { + "type": "Number", + "value": 2 + } + }, + "type": "Calculated", + "value": 3 + }, + "S": { + "metadata": {}, + "type": "Calculated", + "value": 5 + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 319 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: normalized +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: application/json; charset=utf-8 +Fiware-Correlator: REGEX([0-9a-f\-]{36}); cbnotif=1 + +{ + "data": [ + { + "P1": { + "metadata": { + "evalPriority": { + "type": "Number", + "value": 1 + } + }, + "type": "Calculated", + "value": 3.1 + }, + "P2": { + "metadata": { + "evalPriority": { + "type": "Number", + "value": 2 + } + }, + "type": "Calculated", + "value": 4.1 + }, + "S": { + "metadata": {}, + "type": "Calculated", + "value": 6.1 + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop