diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE
index 386c7df498..037e8147bc 100644
--- a/CHANGES_NEXT_RELEASE
+++ b/CHANGES_NEXT_RELEASE
@@ -4,6 +4,7 @@
- Fix: wrong date values should not allowed in subscription's expires field (#4541)
- Fix: do not raise DB alarm in case of wrong GeoJSON in client request
- Fix: registrations with more tha one contextRegistration element (not possible in NGSIv2) are logged as Runtime Errors
+- Fix: metadata modifications are not considered as change (with regards to subscription alterationTypes) if notifyOnMetadataChange is false (#4605)
- Upgrade cjexl version from 0.3.0 to 0.4.0 (new transformations: now, getTime and toIsoString)
- Upgrade Debian version from 12.4 to 12.6 in Dockerfile
- Fix: invalid date in expires field of subscription (#2303)
diff --git a/README.md b/README.md
index c3a2834151..61f25363ff 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,7 @@ recommended to have a look to the brief
### Introductory presentations
- Orion Context Broker
- [(en)](https://www.slideshare.net/slideshow/orion-context-broker-introduction-20240604/269503234)
+ [(en)](https://www.slideshare.net/slideshow/orion-context-broker-introduction-20240911/271735060)
[(jp)](https://www.slideshare.net/slideshow/orion-context-broker-introduction-20240605/269515246)
- NGSIv2 Overview for Developers That Already Know NGSIv1
[(en)](https://www.slideshare.net/fermingalan/orion-context-broker-ngsiv2-overview-for-developers-that-already-know-ngsiv1-20220523)
diff --git a/doc/manuals.jp/devel/README.md b/doc/manuals.jp/devel/README.md
index bfbf13e1d1..b1e9228816 100644
--- a/doc/manuals.jp/devel/README.md
+++ b/doc/manuals.jp/devel/README.md
@@ -1,6 +1,6 @@
# 開発マニュアル
-*注 : このドキュメントでは、リリース 4.0.x の Orion Context Broker について説明しています。*
+*注 : このドキュメントでは、リリース 4.1.x の Orion Context Broker について説明しています。*
## 対象読者
diff --git a/doc/manuals/devel/README.md b/doc/manuals/devel/README.md
index dd6d690c79..3f7f9c0af0 100644
--- a/doc/manuals/devel/README.md
+++ b/doc/manuals/devel/README.md
@@ -1,6 +1,6 @@
# Development Manual
-*Note: This document describes Orion Context Broker as of release 4.0.x.*
+*Note: This document describes Orion Context Broker as of release 4.1.x.*
## Intended audience
The intended audience of this manual is developers that need to understand the internals of the Orion Context Broker
diff --git a/src/lib/apiTypesV2/Subscription.cpp b/src/lib/apiTypesV2/Subscription.cpp
index 254bb4fecd..564237f480 100644
--- a/src/lib/apiTypesV2/Subscription.cpp
+++ b/src/lib/apiTypesV2/Subscription.cpp
@@ -42,7 +42,7 @@ ngsiv2::SubAltType parseAlterationType(const std::string& altType)
{
if (altType == "entityChange")
{
- return ngsiv2::SubAltType::EntityChange;
+ return ngsiv2::SubAltType::EntityChangeBothValueAndMetadata;
}
else if (altType == "entityUpdate")
{
@@ -64,13 +64,26 @@ ngsiv2::SubAltType parseAlterationType(const std::string& altType)
+/* ****************************************************************************
+*
+* isChangeAltType -
+*/
+bool isChangeAltType(ngsiv2::SubAltType altType)
+{
+ return (altType == ngsiv2::SubAltType::EntityChangeBothValueAndMetadata) ||
+ (altType == ngsiv2::SubAltType::EntityChangeOnlyMetadata) ||
+ (altType == ngsiv2::SubAltType::EntityChangeOnlyValue);
+}
+
+
+
/* ****************************************************************************
*
* subAltType2string -
*/
std::string subAltType2string(ngsiv2::SubAltType altType)
{
- if (altType == ngsiv2::SubAltType::EntityChange)
+ if (isChangeAltType(altType))
{
return "entityChange";
}
diff --git a/src/lib/apiTypesV2/Subscription.h b/src/lib/apiTypesV2/Subscription.h
index 1a97fd57fe..7ca649dde6 100644
--- a/src/lib/apiTypesV2/Subscription.h
+++ b/src/lib/apiTypesV2/Subscription.h
@@ -55,7 +55,11 @@ typedef enum NotificationType
*/
typedef enum SubAltType
{
- EntityChange,
+ // EntityChange has been specialized into three sub-types in order to solve #4605
+ // (EntityChangeBothValueAndMetadata is thre reference one used in parsing/rendering logic)
+ EntityChangeBothValueAndMetadata,
+ EntityChangeOnlyValue,
+ EntityChangeOnlyMetadata,
EntityUpdate,
EntityCreate,
EntityDelete,
@@ -190,4 +194,12 @@ extern std::string subAltType2string(ngsiv2::SubAltType altType);
+/* ****************************************************************************
+*
+* isChangeAltType -
+*/
+extern bool isChangeAltType(ngsiv2::SubAltType altType);
+
+
+
#endif // SRC_LIB_APITYPESV2_SUBSCRIPTION_H_
diff --git a/src/lib/cache/subCache.cpp b/src/lib/cache/subCache.cpp
index 2074fbc4fa..53d29aca1c 100644
--- a/src/lib/cache/subCache.cpp
+++ b/src/lib/cache/subCache.cpp
@@ -400,7 +400,7 @@ static bool matchAltType(CachedSubscription* cSubP, ngsiv2::SubAltType targetAlt
// If subAltTypeV size == 0 default alteration types are update with change and create
if (cSubP->subAltTypeV.size() == 0)
{
- if ((targetAltType == ngsiv2::SubAltType::EntityChange) || (targetAltType == ngsiv2::SubAltType::EntityCreate))
+ if ((isChangeAltType(targetAltType)) || (targetAltType == ngsiv2::SubAltType::EntityCreate))
{
return true;
}
@@ -414,9 +414,9 @@ static bool matchAltType(CachedSubscription* cSubP, ngsiv2::SubAltType targetAlt
ngsiv2::SubAltType altType = cSubP->subAltTypeV[ix];
// EntityUpdate is special, it is a "sub-type" of EntityChange
- if (targetAltType == ngsiv2::SubAltType::EntityChange)
+ if (isChangeAltType(targetAltType))
{
- if ((altType == ngsiv2::SubAltType::EntityUpdate) || (altType == ngsiv2::SubAltType::EntityChange))
+ if ((altType == ngsiv2::SubAltType::EntityUpdate) || (isChangeAltType(altType)))
{
return true;
}
@@ -449,6 +449,12 @@ static bool subMatch
ngsiv2::SubAltType targetAltType
)
{
+ // If notifyOnMetadataChange is false and only metadata has been changed, we "downgrade" to ngsiv2::EntityUpdate
+ if (!cSubP->notifyOnMetadataChange && (targetAltType == ngsiv2::EntityChangeOnlyMetadata))
+ {
+ targetAltType = ngsiv2::EntityUpdate;
+ }
+
// Check alteration type
if (!matchAltType(cSubP, targetAltType))
{
@@ -487,7 +493,6 @@ static bool subMatch
return false;
}
-
//
// If one of the attribute names in the scope vector
// of the subscription has the same name as the incoming attribute. there is a match.
@@ -504,10 +509,13 @@ static bool subMatch
return false;
}
}
- else if ((targetAltType == ngsiv2::EntityChange) || (targetAltType == ngsiv2::EntityCreate))
+ else if ((isChangeAltType(targetAltType)) || (targetAltType == ngsiv2::EntityCreate))
{
- if (!attributeMatch(cSubP, attrsWithModifiedValue) &&
- !(cSubP->notifyOnMetadataChange && attributeMatch(cSubP, attrsWithModifiedMd)))
+ // No match if: 1) there is no change in the *value* of attributes listed in conditions.attrs and 2) there is no change
+ // in the *metadata* of the attributes listed in conditions.attrs (the 2) only if notifyOnMetadataChange is true)
+ bool b1 = attributeMatch(cSubP, attrsWithModifiedValue);
+ bool b2 = attributeMatch(cSubP, attrsWithModifiedMd);
+ if (!b1 && !(cSubP->notifyOnMetadataChange && b2))
{
LM_T(LmtSubCacheMatch, ("No match due to attributes"));
return false;
diff --git a/src/lib/mongoBackend/MongoCommonUpdate.cpp b/src/lib/mongoBackend/MongoCommonUpdate.cpp
index 24def43268..04c17fced6 100644
--- a/src/lib/mongoBackend/MongoCommonUpdate.cpp
+++ b/src/lib/mongoBackend/MongoCommonUpdate.cpp
@@ -1490,7 +1490,7 @@ static bool matchAltType(orion::BSONObj sub, ngsiv2::SubAltType targetAltType)
// change and create. Maybe this could be check at MongoDB query stage, but seems be more complex
if (altTypeStrings.size() == 0)
{
- if ((targetAltType == ngsiv2::SubAltType::EntityChange) || (targetAltType == ngsiv2::SubAltType::EntityCreate))
+ if ((isChangeAltType(targetAltType)) || (targetAltType == ngsiv2::SubAltType::EntityCreate))
{
return true;
}
@@ -1510,9 +1510,9 @@ static bool matchAltType(orion::BSONObj sub, ngsiv2::SubAltType targetAltType)
else
{
// EntityUpdate is special, it is a "sub-type" of EntityChange
- if (targetAltType == ngsiv2::SubAltType::EntityChange)
+ if (isChangeAltType(targetAltType))
{
- if ((altType == ngsiv2::SubAltType::EntityUpdate) || (altType == ngsiv2::SubAltType::EntityChange))
+ if ((altType == ngsiv2::SubAltType::EntityUpdate) || (isChangeAltType(altType)))
{
return true;
}
@@ -1638,15 +1638,21 @@ static bool addTriggeredSubscriptions_noCache
if (subs.count(subIdStr) == 0)
{
+ // Early extraction of fiedl from DB document. The rest of fields are got later
+ bool notifyOnMetadataChange = sub.hasField(CSUB_NOTIFYONMETADATACHANGE)? getBoolFieldF(sub, CSUB_NOTIFYONMETADATACHANGE) : true;
+
+ // If notifyOnMetadataChange is false and only metadata has been changed, we "downgrade" to ngsiv2::EntityUpdate
+ if (!notifyOnMetadataChange && (targetAltType == ngsiv2::EntityChangeOnlyMetadata))
+ {
+ targetAltType = ngsiv2::EntityUpdate;
+ }
+
// Check alteration type
if (!matchAltType(sub, targetAltType))
{
continue;
}
- // Early extraction of fiedl from DB document. The rest of fields are got later
- bool notifyOnMetadataChange = sub.hasField(CSUB_NOTIFYONMETADATACHANGE)? getBoolFieldF(sub, CSUB_NOTIFYONMETADATACHANGE) : true;
-
// Depending of the alteration type, we use the list of attributes in the request or the list
// with effective modifications. Note that EntityDelete doesn't check the list
if (targetAltType == ngsiv2::EntityUpdate)
@@ -1656,11 +1662,14 @@ static bool addTriggeredSubscriptions_noCache
continue;
}
}
- else if ((targetAltType == ngsiv2::EntityChange) || (targetAltType == ngsiv2::EntityCreate))
+ else if ((isChangeAltType(targetAltType)) || (targetAltType == ngsiv2::EntityCreate))
{
// Skip if: 1) there is no change in the *value* of attributes listed in conditions.attrs and 2) there is no change
- // in the *metadata* of the attributes listed in conditions.attrs (the 2) only if notifyOnMetadtaChange is true)
- if (!condValueAttrMatch(sub, attrsWithModifiedValue) && !(notifyOnMetadataChange && condValueAttrMatch(sub, attrsWithModifiedMd)))
+ // in the *metadata* of the attributes listed in conditions.attrs (the 2) only if notifyOnMetadataChange is true)
+ bool b1 = condValueAttrMatch(sub, attrsWithModifiedValue);
+ bool b2 = condValueAttrMatch(sub, attrsWithModifiedMd);
+
+ if (!b1 && !(notifyOnMetadataChange && b2))
{
continue;
}
@@ -2722,24 +2731,34 @@ static bool processContextAttributeVector
{
attrsWithModifiedValue.push_back(ca->name);
attrsWithModifiedMd.push_back(ca->name);
+ targetAltType = ngsiv2::SubAltType::EntityChangeBothValueAndMetadata;
}
else if (changeType == CHANGE_ONLY_VALUE)
{
attrsWithModifiedValue.push_back(ca->name);
+ if ((targetAltType == ngsiv2::SubAltType::EntityChangeBothValueAndMetadata) || (targetAltType == ngsiv2::SubAltType::EntityChangeOnlyMetadata))
+ {
+ targetAltType = ngsiv2::SubAltType::EntityChangeBothValueAndMetadata;
+ }
+ else
+ {
+ targetAltType = ngsiv2::SubAltType::EntityChangeOnlyValue;
+ }
}
else if (changeType == CHANGE_ONLY_MD)
{
attrsWithModifiedMd.push_back(ca->name);
+ if ((targetAltType == ngsiv2::SubAltType::EntityChangeBothValueAndMetadata) || (targetAltType == ngsiv2::SubAltType::EntityChangeOnlyValue))
+ {
+ targetAltType = ngsiv2::SubAltType::EntityChangeBothValueAndMetadata;
+ }
+ else
+ {
+ targetAltType = ngsiv2::SubAltType::EntityChangeOnlyMetadata;
+ }
}
attributes.push_back(ca->name);
-
- /* If actual update then targetAltType changes from EntityUpdate (the value used to initialize
- * the variable) to EntityChange */
- if (changeType != NO_CHANGE)
- {
- targetAltType = ngsiv2::SubAltType::EntityChange;
- }
}
/* Add triggered subscriptions */
@@ -3895,7 +3914,7 @@ static unsigned int updateEntity
/* Send notifications for each one of the subscriptions accumulated by
* previous addTriggeredSubscriptions() invocations. Before that, we add
* builtin attributes and metadata */
- addBuiltins(notifyCerP, subAltType2string(ngsiv2::SubAltType::EntityChange));
+ addBuiltins(notifyCerP, subAltType2string(ngsiv2::SubAltType::EntityChangeBothValueAndMetadata));
unsigned int notifSent = processSubscriptions(subsToNotify,
notifyCerP,
tenant,
diff --git a/test/functionalTest/cases/4605_alterationtype_entityupdate_with_notifyonmetadatachange/alterationtype_entityupdate_with_notifyonmetadatachange.test b/test/functionalTest/cases/4605_alterationtype_entityupdate_with_notifyonmetadatachange/alterationtype_entityupdate_with_notifyonmetadatachange.test
new file mode 100644
index 0000000000..bbfb1f476a
--- /dev/null
+++ b/test/functionalTest/cases/4605_alterationtype_entityupdate_with_notifyonmetadatachange/alterationtype_entityupdate_with_notifyonmetadatachange.test
@@ -0,0 +1,333 @@
+# 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
+
+--NAME--
+alterationType entityUpdate in combination with notifyOnMetadataChange
+
+--SHELL-INIT--
+dbInit CB
+brokerStart CB
+accumulatorStart --pretty-print
+
+--SHELL--
+
+#
+# 01. Create sub with alterationType entityUpdate+entityCreate and notifyOnMetadataChange false
+# 02. Create entity dummy-device-01 with two attributes
+# 03. Update entity dummy-device-01 metadata in two attributes
+# 04. Update entity dummy-device-01 without actual update
+# 05. Dump accumulator and see three notifications
+#
+
+
+echo "01. Create sub with alterationType entityUpdate+entityCreate and notifyOnMetadataChange false"
+echo "============================================================================================="
+payload='{
+ "subject": {
+ "entities": [
+ {
+ "idPattern": ".*",
+ "type": "DummyDevice"
+ }
+ ],
+ "condition": {
+ "attrs": [
+ "temperature",
+ "humidity"
+ ],
+ "notifyOnMetadataChange": false,
+ "alterationTypes": [
+ "entityUpdate",
+ "entityCreate"
+ ]
+ }
+ },
+ "notification": {
+ "attrs": [
+ "temperature",
+ "humidity"
+ ],
+ "onlyChangedAttrs": true,
+ "attrsFormat": "normalized",
+ "http": {
+ "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify"
+ },
+ "metadata": [
+ "TimeInstant"
+ ]
+ }
+}'
+orionCurl --url /v2/subscriptions --payload "$payload"
+echo
+echo
+
+
+echo "02. Create entity dummy-device-01 with two attributes"
+echo "====================================================="
+payload='
+{
+ "id": "dummy-device-01",
+ "type": "DummyDevice",
+ "temperature": {
+ "type": "Number",
+ "value": 20,
+ "metadata": {
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-08-20T00:00:00.000Z"
+ }
+ }
+ },
+ "humidity": {
+ "type": "Number",
+ "value": 50,
+ "metadata": {
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-08-20T00:00:00.000Z"
+ }
+ }
+ }
+}'
+orionCurl --url /v2/entities --payload "$payload"
+echo
+echo
+
+
+echo "03. Update entity dummy-device-01 metadata in two attributes"
+echo "============================================================"
+payload='{
+ "temperature": {
+ "type": "Number",
+ "value": 20,
+ "metadata": {
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-08-20T00:10:00.000Z"
+ }
+ }
+ },
+ "humidity": {
+ "type": "Number",
+ "value": 50,
+ "metadata": {
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-08-20T00:10:00.000Z"
+ }
+ }
+ }
+}'
+orionCurl --url /v2/entities/dummy-device-01/attrs -X POST --payload "$payload"
+echo
+echo
+
+
+echo "04. Update entity dummy-device-01 without actual update"
+echo "======================================================="
+payload='{
+ "temperature": {
+ "type": "Number",
+ "value": 20
+ },
+ "humidity": {
+ "type": "Number",
+ "value": 50
+ }
+}'
+orionCurl --url /v2/entities/dummy-device-01/attrs -X POST --payload "$payload"
+echo
+echo
+
+
+echo "05. Dump accumulator and see three notifications"
+echo "================================================"
+accumulatorDump
+echo
+echo
+
+
+--REGEXPECT--
+01. Create sub with alterationType entityUpdate+entityCreate and notifyOnMetadataChange false
+=============================================================================================
+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 dummy-device-01 with two attributes
+=====================================================
+HTTP/1.1 201 Created
+Date: REGEX(.*)
+Fiware-Correlator: REGEX([0-9a-f\-]{36})
+Location: /v2/entities/dummy-device-01?type=DummyDevice
+Content-Length: 0
+
+
+
+03. Update entity dummy-device-01 metadata in two attributes
+============================================================
+HTTP/1.1 204 No Content
+Date: REGEX(.*)
+Fiware-Correlator: REGEX([0-9a-f\-]{36})
+
+
+
+04. Update entity dummy-device-01 without actual update
+=======================================================
+HTTP/1.1 204 No Content
+Date: REGEX(.*)
+Fiware-Correlator: REGEX([0-9a-f\-]{36})
+
+
+
+05. Dump accumulator and see three notifications
+================================================
+POST http://127.0.0.1:REGEX(\d+)/notify
+Fiware-Servicepath: /
+Content-Length: 347
+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": [
+ {
+ "humidity": {
+ "metadata": {
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-08-20T00:00:00.000Z"
+ }
+ },
+ "type": "Number",
+ "value": 50
+ },
+ "id": "dummy-device-01",
+ "temperature": {
+ "metadata": {
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-08-20T00:00:00.000Z"
+ }
+ },
+ "type": "Number",
+ "value": 20
+ },
+ "type": "DummyDevice"
+ }
+ ],
+ "subscriptionId": "REGEX([0-9a-f]{24})"
+}
+=======================================
+POST http://127.0.0.1:REGEX(\d+)/notify
+Fiware-Servicepath: /
+Content-Length: 347
+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": [
+ {
+ "humidity": {
+ "metadata": {
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-08-20T00:10:00.000Z"
+ }
+ },
+ "type": "Number",
+ "value": 50
+ },
+ "id": "dummy-device-01",
+ "temperature": {
+ "metadata": {
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-08-20T00:10:00.000Z"
+ }
+ },
+ "type": "Number",
+ "value": 20
+ },
+ "type": "DummyDevice"
+ }
+ ],
+ "subscriptionId": "REGEX([0-9a-f]{24})"
+}
+=======================================
+POST http://127.0.0.1:REGEX(\d+)/notify
+Fiware-Servicepath: /
+Content-Length: 347
+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": [
+ {
+ "humidity": {
+ "metadata": {
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-08-20T00:10:00.000Z"
+ }
+ },
+ "type": "Number",
+ "value": 50
+ },
+ "id": "dummy-device-01",
+ "temperature": {
+ "metadata": {
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-08-20T00:10:00.000Z"
+ }
+ },
+ "type": "Number",
+ "value": 20
+ },
+ "type": "DummyDevice"
+ }
+ ],
+ "subscriptionId": "REGEX([0-9a-f]{24})"
+}
+=======================================
+
+
+--TEARDOWN--
+brokerStop CB
+dbDrop CB
+accumulatorStop