diff --git a/.github/workflows/functional.yml b/.github/workflows/functional.yml index 7b6b61eeea..b8b4bcb597 100644 --- a/.github/workflows/functional.yml +++ b/.github/workflows/functional.yml @@ -60,4 +60,4 @@ jobs: - name: Run functional test run: | - docker run --network host -t --rm ${{ matrix.payload.range }} -v $(pwd):/opt/fiware-orion ${{ env.TEST_IMAGE_NAME }} build -miqts functional + docker run --network host -t --rm -e REPO_ACCESS_TOKEN=${{ secrets.REPO_ACCESS_TOKEN }} ${{ matrix.payload.range }} -v $(pwd):/opt/fiware-orion ${{ env.TEST_IMAGE_NAME }} build -miqts functional diff --git a/.github/workflows/publishimage.yml b/.github/workflows/publishimage-fiware.yml similarity index 89% rename from .github/workflows/publishimage.yml rename to .github/workflows/publishimage-fiware.yml index af56a99bae..07f10beb01 100644 --- a/.github/workflows/publishimage.yml +++ b/.github/workflows/publishimage-fiware.yml @@ -1,6 +1,6 @@ -name: Publish pre Image from master +name: Publish pre Image from master in FIWARE dockerhub -# The workflow will push PRE images from master on every merge. The images will be tagged with PRE and the next minor increase on the +# The workflow will push PRE images from master on every merge to the fiware dockerhub organization. The images will be tagged with PRE and the next minor increase on the # semver(based on the github releases) # It will NOT produce releases and release images or the 'latest' tag from master. Both (releases and 'latest' tag) rely on the # dockerhub autobuild feature diff --git a/.github/workflows/publishimage-master.yml b/.github/workflows/publishimage-master.yml new file mode 100644 index 0000000000..64ceb47bcf --- /dev/null +++ b/.github/workflows/publishimage-master.yml @@ -0,0 +1,32 @@ +name: Publish Docker image (master) + +# The workflow will push images for master on every merge +# Ideally, this should be done at dockerhub, but it doesn't support secrets (see https://stackoverflow.com/questions/78446824/use-environment-variables-with-sensible-information-in-docker-hub-autobuild) + +on: + push: + branches: + - master + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_TEF_USERNAME }} + password: ${{ secrets.DOCKERHUB_TEF_TOKEN }} + + - name: Build Docker image + run: docker build -t telefonicaiot/fiware-orion:latest --build-arg GIT_REV_ORION=master --build-arg REPO_ACCESS_TOKEN=${{ secrets.REPO_ACCESS_TOKEN }} --no-cache -f docker/Dockerfile . + + - name: Push Docker image + run: docker push telefonicaiot/fiware-orion:latest diff --git a/.github/workflows/publishimage-tag.yml b/.github/workflows/publishimage-tag.yml new file mode 100644 index 0000000000..5dde6e8d27 --- /dev/null +++ b/.github/workflows/publishimage-tag.yml @@ -0,0 +1,36 @@ +name: Publish Docker image (tag) + +# The workflow will push images on every tag in the format x.y.z +# Ideally, this should be done at dockerhub, but it doesn't support secrets (see https://stackoverflow.com/questions/78446824/use-environment-variables-with-sensible-information-in-docker-hub-autobuild) + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_TEF_USERNAME }} + password: ${{ secrets.DOCKERHUB_TEF_TOKEN }} + + - name: Extract version from tag + id: extract_version + run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + + - name: Build Docker image + run: docker build -t telefonicaiot/fiware-orion:${{ env.VERSION }} --build-arg GIT_REV_ORION=${{ env.VERSION }} --build-arg REPO_ACCESS_TOKEN=${{ secrets.REPO_ACCESS_TOKEN }} --no-cache -f docker/Dockerfile . + + - name: Push Docker image + run: docker push telefonicaiot/fiware-orion:${{ env.VERSION }} diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 722a7bb8db..fa1c722db5 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -31,4 +31,4 @@ jobs: - name: Run unit tests run: | - docker run --network host -t --rm -v $(pwd):/opt/fiware-orion ${{ env.TEST_IMAGE_NAME }} build -miqts unit + docker run --network host -t --rm -e REPO_ACCESS_TOKEN=${{ secrets.REPO_ACCESS_TOKEN }} -v $(pwd):/opt/fiware-orion ${{ env.TEST_IMAGE_NAME }} build -miqts unit diff --git a/.github/workflows/valgrind-nocache.yml b/.github/workflows/valgrind-nocache.yml index ea86291e59..e8ff06e31b 100644 --- a/.github/workflows/valgrind-nocache.yml +++ b/.github/workflows/valgrind-nocache.yml @@ -58,4 +58,4 @@ jobs: - name: Run valgrind test run: | - docker run --privileged --network host -t --rm ${{ matrix.payload.range }} -v $(pwd):/opt/fiware-orion ${{ env.TEST_IMAGE_NAME }} build -miqts valgrind + docker run --privileged --network host -t --rm -e REPO_ACCESS_TOKEN=${{ secrets.REPO_ACCESS_TOKEN }} ${{ matrix.payload.range }} -v $(pwd):/opt/fiware-orion ${{ env.TEST_IMAGE_NAME }} build -miqts valgrind diff --git a/.github/workflows/valgrind.yml b/.github/workflows/valgrind.yml index 67940e7d17..33010b755d 100644 --- a/.github/workflows/valgrind.yml +++ b/.github/workflows/valgrind.yml @@ -55,4 +55,4 @@ jobs: - name: Run valgrind test run: | - docker run --privileged --network host -t --rm ${{ matrix.payload.range }} -v $(pwd):/opt/fiware-orion ${{ env.TEST_IMAGE_NAME }} build -miqts valgrind + docker run --privileged --network host -t --rm -e REPO_ACCESS_TOKEN=${{ secrets.REPO_ACCESS_TOKEN }} ${{ matrix.payload.range }} -v $(pwd):/opt/fiware-orion ${{ env.TEST_IMAGE_NAME }} build -miqts valgrind diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index e8ee65ca95..c3acdc183d 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1,2 +1,6 @@ +- Add: JEXL expression support in custom notification macro replacement (using cjexl 0.2.0) (#4004) +- Add: expression context build and evaluation counters in timing section in GET /statistics (#4004) +- Fix: use null for non existing attributes in custom covered notifications macro substitution (instead of empty string) to make behaviour more consistent (#4004) +- Fix: use null for non existing attributes in macro substitution applied to "paylaod" field (instead of empty string) to make behaviour more consistent (#4004) - Fix: simplified GET /version operation, without including "libversions" field (add ?options=libVersions to get it) - Fix: lighter operation to get databases list from MongoDB (#4517) diff --git a/CMakeLists.txt b/CMakeLists.txt index 42a046ec8d..0118dbef19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,7 @@ SET (ORION_LIBS common alarmMgr metricsMgr + expressions logSummary lm pa @@ -229,10 +230,20 @@ SET (BOOST_MT # SET for common static libs. We use 1.24.3 as reference version. find_package (mongoc-1.0 1.24.3 EXACT) +# Is cjexl lib available? +find_library (HAVE_CJEXL cjexl PATHS /usr/lib /usr/lib64 /usr/local/lib64 /usr/local/lib) +if (HAVE_CJEXL) + message("Using cjexl") +else (HAVE_CJEXL) + message("Not using cjexl") + add_definitions(-DEXPR_BASIC) +endif (HAVE_CJEXL) + # Static libs common to contextBroker and unitTest binaries SET (COMMON_STATIC_LIBS microhttpd.a mosquitto.a + ${HAVE_CJEXL} mongo::mongoc_static ) @@ -264,7 +275,6 @@ endif (UNIT_TEST) # include_directories("/usr/include") - # Needed for the new C driver include_directories("/usr/local/include/libmongoc-1.0") include_directories("/usr/local/include/libbson-1.0") @@ -319,6 +329,7 @@ if (error EQUAL 0) ADD_SUBDIRECTORY(src/lib/cache) ADD_SUBDIRECTORY(src/lib/alarmMgr) ADD_SUBDIRECTORY(src/lib/metricsMgr) + ADD_SUBDIRECTORY(src/lib/expressions) ADD_SUBDIRECTORY(src/lib/logSummary) ADD_SUBDIRECTORY(src/lib/mqtt) ADD_SUBDIRECTORY(src/app/contextBroker) diff --git a/ci/deb/build.sh b/ci/deb/build.sh index aea9ed627d..b0e5027b8a 100755 --- a/ci/deb/build.sh +++ b/ci/deb/build.sh @@ -172,6 +172,12 @@ echo "===================================== PREPARE ============================ echo "Builder: create temp folders" rm -Rf /tmp/builder || true && mkdir -p /tmp/builder/{db1,db2,db,bu} +if [ -z "${REPO_ACCESS_TOKEN}" ]; then + echo "Builder: no REPO_ACCESS_TOKEN, skipping cjexl lib download" +else + bash /opt/fiware-orion/get_cjexl.sh 0.2.0 $REPO_ACCESS_TOKEN +fi + if [ -n "${branch}" ]; then echo "===================================== CLONE ============================================" diff --git a/doc/manuals/admin/build_source.md b/doc/manuals/admin/build_source.md index 64064030e4..3292e71d32 100644 --- a/doc/manuals/admin/build_source.md +++ b/doc/manuals/admin/build_source.md @@ -4,6 +4,8 @@ Orion Context Broker reference distribution is Debian 12. This doesn't mean that You can also have a look to [3.1 Building in not official distributions](../../../docker/README.md#31-building-in-not-official-distributions) section in the Docker documentation to check how to build Docker containers images in distributions other than the official one. +*NOTE:* the build process described in this document does not include the cjexl library, as it is considered optional from the point of view of the basic building process. + ## Debian 12 (officially supported) The Orion Context Broker uses the following libraries as build dependencies: diff --git a/doc/manuals/admin/statistics.md b/doc/manuals/admin/statistics.md index 768368691f..a44bafcb74 100644 --- a/doc/manuals/admin/statistics.md +++ b/doc/manuals/admin/statistics.md @@ -129,20 +129,22 @@ Provides timing information, i.e. the time that CB passes executing in different "timing": { "accumulated": { "jsonV1Parse": 7.860908311, - "mongoBackend": 416.796091597, - "mongoReadWait": 4656.924425628, - "mongoWriteWait": 259.347915990, - "mongoCommandWait": 0.514811318, - "render": 108.162782114, - "total": 6476.593504743 - }, + "jsonV2Parse": 120.680244446, + "mongoBackend": 12778.52734375, + "mongoReadWait": 7532.301757812, + "mongoWriteWait": 3619.282226562, + "mongoCommandWait": 0.120559767, + "exprJexlCtxBld": 27.092681885, + "exprJexlEval": 124.217208862, + "render": 44.540554047, + "total": 25051.384765625 + }, "last": { - "mongoBackend": 0.014752309, - "mongoReadWait": 0.012018445, - "mongoWriteWait": 0.000574611, - "render": 0.000019136, - "total": 0.015148915 - } + "mongoBackend": 0.003775352, + "mongoReadWait": 0.0013743, + "render": 0.000286864, + "total": 0.00440685 + } } ... } @@ -167,10 +169,15 @@ The particular counters are as follows: `last` includes the accumulation for all of them. In the case of mongoReadWait, only the time used to get the results cursor is taken into account, but not the time to process cursors results (which is time that belongs to mongoBackend counters). +* `exprJexlCtxBld`: time passed building context for custom notification expression evaluation (see [macro substitution](../orion-api.md#macro-substitution) and [JEXL support](../orion-api.md#jexl-support)) +* `exprJexlEval`: time passed evaluating custom notification expressions (see [macro substitution](../orion-api.md#macro-substitution) and [JEXL support](../orion-api.md#jexl-support)) + +*NOTE*: if Orion binary is build without using cjexl and only basic replacement is available, then `exprBasicCtxtBld` and `exprBasicEval` +fields appear instead of `exprJexlCtxBld` and `exprJexlEval`. Times are measured from the point in time in which a particular thread request starts using a module until it finishes using it. Thus, if the thread is stopped for some reason (e.g. the kernel decides to give priority to another thread based on its -scheculing policy) the time that the thread was sleeping, waiting to execute again is included in the measurement and thus, the measurement is not accurate. That is why we say *pseudo* selt/end-to-end time. However, +scheculing policy) the time that the thread was sleeping, waiting to execute again is included in the measurement and thus, the measurement is not accurate. That is why we say *pseudo* self/end-to-end time. However, under low load conditions this situation is not expected to have a significant impact. ### NotifQueue block diff --git a/doc/manuals/devel/sourceCode.md b/doc/manuals/devel/sourceCode.md index c9227740b0..a829aa1f41 100644 --- a/doc/manuals/devel/sourceCode.md +++ b/doc/manuals/devel/sourceCode.md @@ -24,6 +24,7 @@ * [src/lib/cache/](#srclibcache) (Subscription cache implementation) * [src/lib/logSummary/](#srcliblogsummary) (Log Summary implementation) * [src/lib/metricsMgr/](#srclibmetricsmgr) (Metrics Manager implementation) +* [src/lib/expressions/](#srclibexpressions) (Custom notification expressions support) ## src/app/contextBroker/ The main program is found in `contextBroker.cpp` and its purpose it to: @@ -551,3 +552,8 @@ This Metrics Manager resides in the library **metricsMgr**. For information about the metrics, please refer to [this document](../admin/metrics_api.md). [Top](#top) + +## src/lib/expressions/ +Provides support to the [macro substition logic used by custom notifications](../orion-api.md#macro-substitution). This library provides an abstraction for expression evaluation, providing two implementations: JEXL based and basic replacement based (the implementation to use is choosen at building time, based on the availability of the cjex library). + +[Top](#top) \ No newline at end of file diff --git a/doc/manuals/orion-api.md b/doc/manuals/orion-api.md index 7b66992569..0d81391493 100644 --- a/doc/manuals/orion-api.md +++ b/doc/manuals/orion-api.md @@ -73,6 +73,35 @@ - [NGSI payload patching](#ngsi-payload-patching) - [Omitting payload](#omitting-payload) - [Additional considerations](#additional-considerations) + - [JEXL Support](#jexl-support) + - [JEXL usage example](#jexl-usage-example) + - [Available Transformations](#available-transformations) + - [`uppercase`](#uppercase) + - [`lowercase`](#lowercase) + - [`split`](#split) + - [`indexOf`](#indexOf) + - [`len`](#len) + - [`trim`](#trim) + - [`substring`](#substring) + - [`includes`](#includes) + - [`isNaN`](#isNaN) + - [`parseInt`](#parseInt) + - [`parseFloat`](#parseFloat) + - [`typeOf`](#typeOf) + - [`toString`](#toString) + - [`floor`](#floor) + - [`ceil`](#ceil) + - [`round`](#round) + - [`toFixed`](#toFixed) + - [`log`](#log) + - [`log10`](#log10) + - [`log2`](#log2) + - [`sqrt`](#sqrt) + - [`replaceStr`](#replaceStr) + - [`mapper`](#mapper) + - [`thMapper`](#thmapper) + - [Failsafe cases](#failsafe-cases) + - [Known limitations](#known-limitations) - [Oneshot Subscriptions](#oneshot-subscriptions) - [Covered Subscriptions](#covered-subscriptions) - [Subscriptions based in alteration type](#subscriptions-based-in-alteration-type) @@ -398,11 +427,12 @@ the following will be used (note that "%25" is the encoding for "%"). GET /v2/entities/E%253C01%253E ``` -There are some exception cases in which the above restrictions do not apply. In particular, in the following fields: +There are some exception cases in which the above restrictions do not apply. In particular, in the following cases: * URL parameter `q` allows the special characters needed by the [Simple Query Language](#simple-query-language) * URL parameter `mq` allows the special characters needed by the [Simple Query Language](#simple-query-language) * URL parameter `georel` and `coords` allow `;` +* Within `ngsi` (i.e. `id`, `type` and attribute values) in [NGSI Payload patching](#ngsi-payload-patching) (to support characters used in the [JEXL expression syntax](#jexl-support)) * Whichever attribute value which uses `TextUnrestricted` as attribute type (see [Special Attribute Types](#special-attribute-types) section) ## Identifiers syntax restrictions @@ -428,6 +458,8 @@ In addition, the [General syntax restrictions](#general-syntax-restrictions) als In case a client attempts to use a field that is invalid from a syntax point of view, the client gets a "Bad Request" error response, explaining the cause. +Note that although `:` and `-` are allowed in identifiers, they are strongly discouraged, as they collide with the [JEXL syntax](#jexl-support). In particular, `-` is used for subtraction operation (e.g. `${A-B}`) and `:` is used in the ternary operator (eg. `A?'A is true':'A is false`). Thus, an attribute name `lower-temperature` in an expression `${lower-temperature}` would be interpreted as the value of `lower` attribute minus `temperature` attribute (and not as the value of an attribute named `lower-temperature`). + ## Error Responses If present, the error payload is a JSON object including the following fields: @@ -2021,23 +2053,22 @@ In case of `mqttCustom`: * `payload`, `json` and `ngsi` (all them payload related fields) * `topic` -Macro substitution for templates is based on the syntax `${..}`. In particular: +Macro substitution for templates is based on the syntax `${}`. The support to JEXL +is explained in [JEXL Support](#jexl-support) section. The following identifiers are included in +the context evaluated by the JEXL expression: -* `${id}` is replaced by the `id` of the entity -* `${type}` is replaced by the `type` of the entity -* `${service}` is replaced by the service (i.e. `fiware-service` header value) in the +* `id`: for the `id` of the entity +* `type`: for the `type` of the entity +* `service`: for the service (i.e. `fiware-service` header value) in the update request triggering the subscription. -* `${servicePath}` is replaced by the service path (i.e. `fiware-servicepath` header value) in the +* `servicePath`: for the service path (i.e. `fiware-servicepath` header value) in the update request triggering the subscription. -* `${authToken}` is replaced by the authorization token (i.e. `x-auth-token` header value) in the +* `authToken: for the authorization token (i.e. `x-auth-token` header value) in the update request triggering the subscription. -* Any other `${token}` is replaced by the value of the attribute whose name is `token` or with - an empty string if the attribute is not included in the notification. If the value is a number, - a bool or null then its string representation is used. If the value is a JSON array or object - then its JSON representation as string is used. +* All the attributes in the entity triggering the notification (included in the update triggering the notification or not) -In the rare case an attribute was named in the same way of the `${service}`, `${servicePath}` or -`${authToken}` (e.g. an attribute which name is `service`) then the attribute value takes precedence. +In the rare case an attribute was named in the same way of the `service`, `servicePath` or +`authToken` (e.g. an attribute which name is `service`) then the attribute value takes precedence. Example: @@ -2295,6 +2326,676 @@ Some considerations to take into account when using custom notifications: (i.e. `ngsi` field) then `Ngsiv2-AttrsFormat: normalized` is used, as in a regular notification (given that the notification format is actually the same). +## JEXL Support + +Orion Context Broker supports [JEXL expressions](https://github.com/TomFrost/Jexl) in custom notification [macro replacement](#macro-substitution). Thus, subscriptions like this can be defined: + +``` +"httpCustom": { + ... + "ngsi": { + "relativeHumidity": { + "value": "${humidity/100}", + "type": "Calculated" + } + } +} +``` + +So, if a given update sets entity `humidity` attribute to `84.4` then the notification will include a `relativeHumidity` attribute with value `0.844`. + +A particular case of expressions are the ones in which the expression is a given context identifier, without an actual expression using it. For instance: + +``` +"httpCustom": { + ... + "ngsi": { + "originalHumidity": { + "value": "${humidity}", + "type": "Calculated" + } + } +} +``` + +We also refers to this case as *basic replacement*. + +An useful resource to test JEXL expressions is the [JEXL playground](https://czosel.github.io/jexl-playground). However, take into account the differences between the original JEXL implementation in JavaScript and the one included in Orion, described in the [known limitations](#known-limitations) section. + +Orion relies on cjexl library to provide this functionality. If Orion binary is build without using cjexl, then only basic replacement functionality is available. + +### JEXL usage example + +As example, let's consider a subscription like this: + +``` +"httpCustom": { + ... + "ngsi": { + "speed": { + "value": "${(speed|split(' '))[0]|parseInt}", + "type": "Calculated" + }, + "ratio": { + "value": "${count.sum/count.count}", + "type": "Calculated" + }, + "code": { + "value": "${code||'invalid'}", + "type": "Calculated" + }, + "alert": { + "value": "${(value>max)?'nok':'ok'}", + "type": "Calculated" + }, + "count": { + "value": "${{count:count.count+1, sum:count.sum+((speed|split(' '))[0]|parseInt)}}", + "type": "Calculated" + } +} +``` + +A entity update like this: + +``` +{ + ... + "speed": { + "value": "10 m/s", + "type": "Text" + }, + "count": { + "value": { + "count": 5, + "sum": 100 + }, + "type": "StructuredValue" + }, + "code": { + "value": null, + "type": "Number" + }, + "value": { + "value": 14, + "type": "Number" + }, + "max": { + "value": 50, + "type": "Number" + } +} +``` + +will trigger a notification like this: + +``` +"data": [ + { + ... + "speed": { + "metadata": {}, + "type": "Calculated", + "value": 10 + }, + "ratio": { + "metadata": {}, + "type": "Calculated", + "value": 20 + }, + "code": { + "metadata": {}, + "type": "Calculated", + "value": "invalid" + }, + "alert": { + "metadata": {}, + "type": "Calculated", + "value": "ok" + }, + "count": { + "metadata": {}, + "type": "Calculated", + "value": { + "count": 6, + "sum": 110 + } + } + } +] +``` + +A new entity update like this: + +``` +{ + ... + "speed": { + "value": "30 m/s", + "type": "Text" + }, + "count": { + "value": { + "count": 5, + "sum": 500 + }, + "type": "StructuredValue" + }, + "code": { + "value": 456, + "type": "Number" + }, + "value": { + "value": 75, + "type": "Number" + }, + "max": { + "value": 50, + "type": "Number" + } +} +``` + +will trigger a notification like this: + +``` +"data": [ + { + ... + "speed": { + "metadata": {}, + "type": "Calculated", + "value": 30 + }, + "ratio": { + "metadata": {}, + "type": "Calculated", + "value": 100 + }, + "code": { + "metadata": {}, + "type": "Calculated", + "value": 456 + }, + "alert": { + "metadata": {}, + "type": "Calculated", + "value": "nok" + }, + "count": { + "metadata": {}, + "type": "Calculated", + "value": { + "count": 6, + "sum": 530 + } + } + } +] +``` + +### Available Transformations + +#### `uppercase` + +Convert a string into uppercase. + +Extra arguments: none + +Example (being context `{"c": "fooBAR"}`): + +``` +c|uppercase +``` + +results in + +``` +"FOOBAR" +``` + +#### lowercase + +Convert a string into lowercase. + +Extra arguments: none + +Example (being context `{"c": "fooBAR"}`): + +``` +c|lowercase +``` + +results in + +``` +"foobar" +``` + +#### split + +Split the input string into array items. + +Extra arguments: delimiter to use for the split. + +Example (being context `{"c": "foo,bar,zzz"}`): + +``` +c|split(',') +``` + +results in + +``` +[ "foo", "bar", "zzz" ] +``` + +#### indexOf + +Provides the position of a given string within the input string. In the string is not found, returns `null`. + +Extra arguments: the input string to search. + +Note this function doesn't work if the input is an array (it only works for strings). + +Example (being context `{"c": "fooxybar"}`): + +``` +c|indexOf('xy') +``` + +results in + +``` +3 +``` + +#### len + +Provides the length of a string. + +Extra arguments: none. + +Note this function doesn't work if the input is an array (it only works for strings). + +Example (being context `{"c": "foobar"}`): + +``` +c|len +``` + +results in + +``` +6 +``` + +#### trim + +Removes starting and trailing whitespaces. + +Extra arguments: none. + +Example (being context `{"c": " foo bar "}`): + +``` +c|trim +``` + +results in + +``` +foo bar +``` + +#### substring + +Returns a substring between two positions. + +Extra arguments: +* Initial position +* Final position + +Example (being context `{"c": "foobar"}`): + +``` +c|substring(3,5) +``` + +results in + +``` +ba +``` + +#### includes + +Returns `true` if a given string is contained in the input string, `false` otherwise. + +Extra arguments: the input string to search. + +Example (being context `{"c": "foobar"}`): + +``` +c|includes('ba') +``` + +results in + +``` +true +``` + +#### isNaN + +Returns `true` if the input is not a number, `false` otherwise. + +Extra arguments: none. + +Example (being context `{"c": "foobar"}`): + +``` +c|isNaN +``` + +results in + +``` +true +``` + +#### parseInt + +Parses a string and return the corresponding integer number. + +Extra arguments: none. + +Example (being context `{"c": "25"}`): + +``` +c|parseInt +``` + +results in + +``` +25 +``` + +#### parseFloat + +Parses a string and return the corresponding float number + +Extra arguments: none. + +Example (being context `{"c": "25.33"}`): + +``` +c|parseFloat +``` + +results in + +``` +25.33 +``` + +#### typeOf + +Return a string with the type of the input. + +Extra arguments: none. + +Example (being context `{"c": 23}`): + +``` +c|typeOf +``` + +results in + +``` +"Number" +``` + +#### toString + +Return a string representation of the input. + +Extra arguments: none. + +Example (being context `{"c": 23}`): + +``` +c|toString +``` + +results in + +``` +"23" +``` + +#### floor + +Return the closest lower integer of a given number. + +Extra arguments: none. + +Example (being context `{"c": 3.14}`): + +``` +c|floor +``` + +results in + +``` +3 +``` + +#### ceil + +Return the closest upper integer of a given number. + +Extra arguments: none. + +Example (being context `{"c": 3.14}`): + +``` +c|ceil +``` + +results in + +``` +4 +``` + +#### round + +Return the closest integer (either lower or upper) of a given number. + +Extra arguments: none. + +Example (being context `{"c": 3.14}`): + +``` +c|round +``` + +results in + +``` +3 +``` + +#### toFixed + +Rounds a number to a number of decimal digits. + +Extra arguments: number of decimal digits. + +Example (being context `{"c": 3.18}`): + +``` +c|toFixed(1) +``` + +results in + +``` +3.2 +``` + +#### log + +Return the natural logarithm of a given number + +Extra arguments: none. + +Example (being context `{"c": 3.14}`): + +``` +c|log +``` + +results in + +``` +1.144222799920162 +``` + +#### log10 + +Return the base 10 logarithm of a given number + +Extra arguments: none. + +Example (being context `{"c": 3.14}`): + +``` +c|log10 +``` + +results in + +``` +0.49692964807321494 +``` + +#### log2 + +Return the base 2 logarithm of a given number + +Extra arguments: none. + +Example (being context `{"c": 3.14}`): + +``` +c|log2 +``` + +results in + +``` +1.6507645591169025 +``` + +#### sqrt + +Return the square root of a given number + +Extra arguments: none. + +Example (being context `{"c": 3.14}`): + +``` +c|sqrt +``` + +results in + +``` +1.772004514666935 +``` + +#### replaceStr + +Replace occurrences of a string with another in the input string. + +Extra arguments: +* Source string to replace +* Destination string to replace + +Example (being context `{"c": "foobar"}`): + +``` +c|replaceStr('o','u') +``` + +results in + +``` +"fuubar" +``` + +#### mapper + +Returns a value among several choices based in one to one mapping. This function is based in an array of *values* and an array of *choices* (which length is exactly the same). Thus, if the input value is equal to the *i*-th item of *values*, then *i*-th item of *choices* is returned. + +This transformation returns `null` if some problem with the arguments is found (i.e. input is not found among the values, choices length is not exacly the same as values, the input is not an string, etc.) + +Extra arguments: +* values array +* choices array + +Example (being context `{"c": "fr", "values": ["es", "fr", "de"], "choices": ["Spain", "France", "Germany"]}`): + +``` +c|mapper(values,choices) +``` + +results in + +``` +"France" +``` + +#### thMapper + +Returns a value among several choices based in threshold values. This function is based in an array of *values* and an array of *choices* (which length is exactly the same as values plus one). Thus, if the input value is between the *i*-th and the *i+1*-th item of *values*, then *i*+1-th item of *choices* is returned. + +This transformation returns `null` if some problem with the arguments is found (i.e. choices length is not exacly the same as values plus one, some of the items in the values array is not a number, etc.) + +Extra arguments: +* values array +* choices array + +Example (being context `{"c": 0.5, "values": [-1, 1], "choices": ["low", "medium", "high"]}`): + +``` +c|thMapper(values,choices) +``` + +results in + +``` +"medium" +``` + +### Failsafe cases + +As failsafe behaviour, evaluation returns `null` in the following cases: + +* Some of the transformation used in the expression is unknown (e.g. `A|undefinedExpression`) +* Operations with identifiers that are not defined in the context are used. For instance, `(A==null)?0:A` will result in `null` (and not `0`) if `A` is not in the context, due to `==` is an operation that cannot be done on undefined identifiers. However, `A||0` will work (i.e. `0` will result if `A` is not in the context), as `||` is not considered an operation on `A`. +* Syntax error in the JEXL expression (e.g. `A[0|uppercase`) + +### Known limitations + +- The unitary minus operator is not working properly, e.g. the following expression doesn't work (it failsafes to `null`): `A||-1`. However, the following alternatives are working: `A||0-1` and `A||'-1'|parseInt)` +- Negation operator `!` (supported in original JavaScript JEXL) is not supported + ## Oneshot Subscriptions Oneshot subscription provides an option to subscribe an entity only for one time notification. When consumer creates a subscription @@ -2394,10 +3095,6 @@ in which case all attributes are included in the notification, no matter if they entity. For these attributes that don't exist (`brightness` in this example) the `null` value (of type `"None"`) is used. -In the case of custom notifications, if `covered` is set to `true` then `null` will be used to replace `${...}` -for non existing attributes (the default behavior when `covered` is not set to `true` is to replace by the -empty string the non existing attributes). - We use the term "covered" in the sense the notification "covers" completely all the attributes in the `notification.attrs` field. It can be useful for those notification endpoints that are not flexible enough for a variable set of attributes and needs always the same set of incoming attributes diff --git a/docker/Dockerfile b/docker/Dockerfile index b5b80157d4..e6c7846b56 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -29,11 +29,13 @@ ARG GITHUB_REPOSITORY=fiware-orion ARG GIT_NAME ARG GIT_REV_ORION ARG CLEAN_DEV_TOOLS +ARG REPO_ACCESS_TOKEN ENV ORION_USER ${ORION_USER:-orion} ENV GIT_NAME ${GIT_NAME:-telefonicaid} ENV GIT_REV_ORION ${GIT_REV_ORION:-master} ENV CLEAN_DEV_TOOLS ${CLEAN_DEV_TOOLS:-1} +ENV REPO_ACCESS_TOKEN ${REPO_ACCESS_TOKEN:-""} SHELL ["/bin/bash", "-o", "pipefail", "-c"] @@ -102,6 +104,7 @@ RUN \ git clone https://github.com/${GIT_NAME}/fiware-orion && \ cd fiware-orion && \ git checkout ${GIT_REV_ORION} && \ + bash get_cjexl.sh 0.2.0 ${REPO_ACCESS_TOKEN} && \ make && \ make install && \ # reduce size of installed binaries @@ -125,6 +128,7 @@ RUN \ /opt/mongo-c-driver-1.24.3 \ /usr/local/include/mongo \ /usr/local/lib/libmongoclient.a \ + /usr/local/lib/libcjexl.a \ /opt/rapidjson-1.1.0 \ /opt/v1.1.0.tar.gz \ /usr/local/include/rapidjson \ diff --git a/docker/Dockerfile.alpine b/docker/Dockerfile.alpine index 524335f677..9e35eb3aa9 100644 --- a/docker/Dockerfile.alpine +++ b/docker/Dockerfile.alpine @@ -32,11 +32,13 @@ ARG GITHUB_REPOSITORY=fiware-orion ARG GIT_NAME ARG GIT_REV_ORION ARG CLEAN_DEV_TOOLS +ARG REPO_ACCESS_TOKEN ENV ORION_USER ${ORION_USER:-orion} ENV GIT_NAME ${GIT_NAME:-telefonicaid} ENV GIT_REV_ORION ${GIT_REV_ORION:-master} ENV CLEAN_DEV_TOOLS ${CLEAN_DEV_TOOLS:-1} +ENV REPO_ACCESS_TOKEN ${REPO_ACCESS_TOKEN:-""} SHELL ["/bin/ash", "-o", "pipefail", "-c"] @@ -108,6 +110,7 @@ RUN \ git clone https://github.com/${GIT_NAME}/fiware-orion && \ cd fiware-orion && \ git checkout ${GIT_REV_ORION} && \ + bash get_cjexl.sh 0.2.0 ${REPO_ACCESS_TOKEN} && \ # patch bash and mktemp statement in build script, as in alpine is slightly different sed -i 's/mktemp \/tmp\/compileInfo.h.XXXX/mktemp/g' scripts/build/compileInfo.sh && \ sed -i 's/bash/ash/g' scripts/build/compileInfo.sh && \ @@ -133,6 +136,7 @@ RUN \ /opt/mongo-c-driver-1.24.3 \ /usr/local/include/mongo \ /usr/local/lib/libmongoclient.a \ + /usr/local/lib/libcjexl.a \ /opt/rapidjson-1.1.0 \ /opt/v1.1.0.tar.gz \ /usr/local/include/rapidjson \ diff --git a/get_cjexl.sh b/get_cjexl.sh new file mode 100644 index 0000000000..efd360756e --- /dev/null +++ b/get_cjexl.sh @@ -0,0 +1,33 @@ +# 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 + +CJEXL_VERSION=$1 +TOKEN=$2 + +res_code=$(curl -I -s -o /dev/null -w "%{http_code}" -H "Authorization: token $TOKEN" https://api.github.com/repos/telefonicasc/cjexl/releases/tags/$CJEXL_VERSION) +if [ "$res_code" -eq 200 ]; then + echo "get_cjexl: downloading cjexl lib $CJEXL_VERSION" + ASSET_ID=$(curl -s -S -H "Authorization: token $TOKEN" https://api.github.com/repos/telefonicasc/cjexl/releases/tags/$CJEXL_VERSION | grep -C3 libcjexl.a | grep '"id"' | awk -F ' ' '{print $2}' | awk -F ',' '{print $1}') + curl -L -s -o /usr/local/lib/libcjexl.a -H "Authorization: token $TOKEN" -H "Accept: application/octet-stream" https://api.github.com/repos/telefonicasc/cjexl/releases/assets/$ASSET_ID + MD5SUM=$(md5sum /usr/local/lib/libcjexl.a | awk -F ' ' '{print$1}') + echo "get_cjexl: cjexl lib md5sum is $MD5SUM" +else + echo "get_cjexl: error $res_code accessing cjexl release. Maybe token is invalid?" +fi \ No newline at end of file diff --git a/src/app/contextBroker/contextBroker.cpp b/src/app/contextBroker/contextBroker.cpp index d1cc8a57b2..413c63a85b 100644 --- a/src/app/contextBroker/contextBroker.cpp +++ b/src/app/contextBroker/contextBroker.cpp @@ -105,6 +105,7 @@ #include "alarmMgr/alarmMgr.h" #include "mqtt/mqttMgr.h" #include "metricsMgr/metricsMgr.h" +#include "expressions/exprMgr.h" #include "logSummary/logSummary.h" #include "contextBroker/orionRestServices.h" @@ -597,6 +598,8 @@ void exitFunc(void) curl_context_cleanup(); curl_global_cleanup(); + exprMgr.release(); + #ifdef DEBUG // valgrind pass is done using DEBUG compilation, so we have to take care with // the cache releasing to avoid false positives. In production this is not really @@ -1042,6 +1045,12 @@ int main(int argC, char* argV[]) std::string versionString = std::string(ORION_VERSION) + " (git version: " + GIT_HASH + ")"; + #ifdef EXPR_BASIC + versionString += " flavours: basic-expr"; + #else + versionString += " flavours: jexl-expr"; + #endif + paConfig("man synopsis", (void*) "[options]"); paConfig("man shortdescription", (void*) "Options:"); paConfig("man description", (void*) description); @@ -1219,6 +1228,7 @@ int main(int argC, char* argV[]) SemOpType policy = policyGet(reqMutexPolicy); alarmMgr.init(relogAlarms); mqttMgr.init(mqttTimeout); + exprMgr.init(); orionInit(orionExit, ORION_VERSION, policy, statCounters, statSemWait, statTiming, statNotifQueue, strictIdv1); mongoInit(dbURI, dbHost, rplSet, dbName, user, pwd, authMech, authDb, dbSSL, dbDisableRetryWrites, mtenant, dbTimeout, writeConcern, dbPoolSize, statSemWait); metricsMgr.init(!disableMetrics, statSemWait); diff --git a/src/lib/apiTypesV2/Entity.cpp b/src/lib/apiTypesV2/Entity.cpp index 7c3fd7abba..f51cbd6b35 100644 --- a/src/lib/apiTypesV2/Entity.cpp +++ b/src/lib/apiTypesV2/Entity.cpp @@ -41,6 +41,7 @@ #include "rest/OrionError.h" #include "apiTypesV2/Entity.h" +#include "expressions/ExprContext.h" @@ -307,7 +308,7 @@ std::string Entity::toJson bool blacklist, const std::vector& metadataFilter, bool renderNgsiField, - std::map* replacementsP + ExprContextObject* exprContextObjectP ) { std::vector orderedAttrs; @@ -326,7 +327,7 @@ std::string Entity::toJson out = toJsonKeyvalues(orderedAttrs); break; default: // NGSI_V2_NORMALIZED - out = toJsonNormalized(orderedAttrs, metadataFilter, renderNgsiField, replacementsP); + out = toJsonNormalized(orderedAttrs, metadataFilter, renderNgsiField, exprContextObjectP); break; } @@ -443,7 +444,7 @@ std::string Entity::toJsonNormalized const std::vector& orderedAttrs, const std::vector& metadataFilter, bool renderNgsiField, - std::map* replacementsP + ExprContextObject* exprContextObjectP ) { JsonObjectHelper jh; @@ -475,7 +476,7 @@ std::string Entity::toJsonNormalized for (unsigned int ix = 0; ix < orderedAttrs.size(); ix++) { ContextAttribute* caP = orderedAttrs[ix]; - jh.addRaw(caP->name, caP->toJson(metadataFilter, renderNgsiField, replacementsP)); + jh.addRaw(caP->name, caP->toJson(metadataFilter, renderNgsiField, exprContextObjectP)); } return jh.str(); diff --git a/src/lib/apiTypesV2/Entity.h b/src/lib/apiTypesV2/Entity.h index e4906bb998..9d534d52ed 100644 --- a/src/lib/apiTypesV2/Entity.h +++ b/src/lib/apiTypesV2/Entity.h @@ -32,6 +32,7 @@ #include "ngsi/ContextAttributeVector.h" #include "ngsi/EntityId.h" #include "rest/OrionError.h" +#include "expressions/ExprContext.h" @@ -90,8 +91,8 @@ class Entity const std::vector& attrsFilter, bool blacklist, const std::vector& metadataFilter, - bool renderNgsiField = false, - std::map* replacementsP = NULL); + bool renderNgsiField = false, + ExprContextObject* exprContextObjectP = NULL); std::string toJson(RenderFormat renderFormat, bool renderNgsiField = false); @@ -144,8 +145,8 @@ class Entity std::string toJsonKeyvalues(const std::vector& orderedAttrs); std::string toJsonNormalized(const std::vector& orderedAttrs, const std::vector& metadataFilter, - bool renderNgsiField = false, - std::map* replacementsP = NULL); + bool renderNgsiField = false, + ExprContextObject* exprContextObject = NULL); }; #endif // SRC_LIB_APITYPESV2_ENTITY_H_ diff --git a/src/lib/apiTypesV2/HttpInfo.cpp b/src/lib/apiTypesV2/HttpInfo.cpp index 4f5e1fd80e..d4eb24114c 100644 --- a/src/lib/apiTypesV2/HttpInfo.cpp +++ b/src/lib/apiTypesV2/HttpInfo.cpp @@ -131,7 +131,7 @@ void HttpInfo::fill(const orion::BSONObj& bo) if (bo.hasField(CSUB_NGSI)) n++; if (n > 1) { - LM_E(("custom notification must not have more than one payload related field")); + LM_E(("Runtime Error (custom notification must not have more than one payload related field)")); return; } diff --git a/src/lib/apiTypesV2/MqttInfo.cpp b/src/lib/apiTypesV2/MqttInfo.cpp index d46b9e4507..e604f21e1a 100644 --- a/src/lib/apiTypesV2/MqttInfo.cpp +++ b/src/lib/apiTypesV2/MqttInfo.cpp @@ -143,7 +143,7 @@ void MqttInfo::fill(const orion::BSONObj& bo) if (bo.hasField(CSUB_NGSI)) n++; if (n > 1) { - LM_E(("custom notification must not have more than one payload related field")); + LM_E(("Runtime Error (custom notification must not have more than one payload related field)")); return; } diff --git a/src/lib/common/JsonHelper.cpp b/src/lib/common/JsonHelper.cpp index 812e9ee1f1..3bd6fd5d2a 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) +JsonObjectHelper::JsonObjectHelper(): empty(true), closed(false) { ss += '{'; } @@ -282,7 +282,12 @@ void JsonObjectHelper::addNull(const std::string& key) */ std::string JsonObjectHelper::str() { - ss += '}'; + // This check allows to call str() several times (needed when this is used in ExprContext) + if (!closed) + { + ss += '}'; + closed = true; + } return ss; } @@ -292,7 +297,7 @@ std::string JsonObjectHelper::str() * * JsonVectorHelper - */ -JsonVectorHelper::JsonVectorHelper(): empty(true) +JsonVectorHelper::JsonVectorHelper(): empty(true), closed(false) { ss += '['; } @@ -406,12 +411,28 @@ void JsonVectorHelper::addBool(bool b) +/* **************************************************************************** +* +* JsonObjectHelper::addNull - +*/ +void JsonVectorHelper::addNull(void) +{ + addRaw("null"); +} + + + /* **************************************************************************** * * JsonVectorHelper::str - */ std::string JsonVectorHelper::str() { - ss += ']'; + // This check allows to call str() several times (needed when this is used in ExprContext) + if (!closed) + { + ss += ']'; + closed = true; + } return ss; } diff --git a/src/lib/common/JsonHelper.h b/src/lib/common/JsonHelper.h index 52e633c14d..bcb8aaeaf2 100644 --- a/src/lib/common/JsonHelper.h +++ b/src/lib/common/JsonHelper.h @@ -50,6 +50,7 @@ class JsonObjectHelper private: std::string ss; bool empty; + bool closed; }; @@ -64,6 +65,7 @@ class JsonVectorHelper void addNumber(double value); void addDate(double timestamp); void addBool(bool b); + void addNull(void); std::string str(); @@ -71,6 +73,7 @@ class JsonVectorHelper private: std::string ss; bool empty; + bool closed; }; diff --git a/src/lib/common/macroSubstitute.cpp b/src/lib/common/macroSubstitute.cpp index b17ac6487c..28dd9c156a 100644 --- a/src/lib/common/macroSubstitute.cpp +++ b/src/lib/common/macroSubstitute.cpp @@ -32,6 +32,7 @@ #include "common/JsonHelper.h" #include "common/macroSubstitute.h" +#include "expressions/exprMgr.h" /* **************************************************************************** @@ -39,37 +40,50 @@ * smartStringValue - * * Returns the effective string value, taking into account replacements +* */ -std::string smartStringValue(const std::string stringValue, std::map* replacementsP, const std::string notFoundDefault) +std::string smartStringValue(const std::string stringValue, ExprContextObject* exprContextObjectP, const std::string notFoundDefault) { // This code is pretty similar to the one in CompoundValueNode::toJson() // The program logic branching is the same, but the result at the end of each if-else // is different, which makes difficult to unify both them - if ((replacementsP != NULL) && (stringValue.rfind("${") == 0) && (stringValue.rfind("}", stringValue.size()) == stringValue.size() - 1)) + if ((exprContextObjectP != NULL) && (stringValue.rfind("${") == 0) && (stringValue.rfind("}", stringValue.size()) == stringValue.size() - 1)) { // "Full replacement" case. In this case, the result is not always a string // len("${") + len("}") = 3 std::string macroName = stringValue.substr(2, stringValue.size() - 3); - std::map::iterator iter = replacementsP->find(macroName); - if (iter == replacementsP->end()) + + ExprResult r = exprMgr.evaluate(exprContextObjectP, macroName); + std::string result; + if (r.valueType == orion::ValueTypeNull) { - // macro doesn't exist in the replacement map, so we use null as failsafe - return notFoundDefault; + result = notFoundDefault; } else { - return iter->second; + // in basic mode an extra remove quotes step is needed, to avoid "1" instead of 1 for number, etc. + if (exprContextObjectP->isBasic()) + { + result = removeQuotes(r.toString()); + } + else + { + result = r.toString(); + } } + r.release(); + return result; } - else if (replacementsP != NULL) + else if (exprContextObjectP != NULL) { // "Partial replacement" case. In this case, the result is always a string std::string effectiveValue; - if (!macroSubstitute(&effectiveValue, stringValue, replacementsP, "null")) + if (!macroSubstitute(&effectiveValue, stringValue, exprContextObjectP, "null", true)) { // error already logged in macroSubstitute, using stringValue itself as failsafe effectiveValue = stringValue; } + // toJsonString will stringfly JSON values in macros return '"' + toJsonString(effectiveValue) + '"'; } @@ -84,58 +98,42 @@ std::string smartStringValue(const std::string stringValue, std::map* replacementsP -) +static std::string stringValueOrNothing(ExprContextObject* exprContextObjectP, const std::string key, const std::string& notFoundDefault, bool raw) { - replacementsP->insert(std::pair("id", "\"" + en.id + "\"")); - replacementsP->insert(std::pair("type", "\"" + en.type + "\"")); - replacementsP->insert(std::pair("service", "\"" + service + "\"")); - replacementsP->insert(std::pair("servicePath", "\"" + en.servicePath + "\"")); - replacementsP->insert(std::pair("authToken", "\"" + token + "\"")); - for (unsigned int ix = 0; ix < en.attributeVector.size(); ix++) - { - // Note that if some attribute is named service, servicePath or authToken (although it would be - // an anti-pattern), the attribute takes precedence - (*replacementsP)[en.attributeVector[ix]->name] = en.attributeVector[ix]->toJsonValue(); - } -} - + ExprResult r = exprMgr.evaluate(exprContextObjectP, key); + std::string result; - -/* **************************************************************************** -* -* stringValueOrNothing - -*/ -static std::string stringValueOrNothing(std::map* replacementsP, const std::string key, const std::string& notFoundDefault) -{ - std::map::iterator iter = replacementsP->find(key); - if (iter == replacementsP->end()) + if (r.valueType == orion::ValueTypeNull) { - return notFoundDefault; + result = notFoundDefault; } else { - // replacementP contents are prepared for "full replacement" case, so string values use - // double quotes. But in this case we are in a "partial replacement" case, so we have - // to remove them if we find them - std::string value = iter->second; - if (value[0] == '"') + std::string s = r.toString(); + + // in basic mode an extra remove quotes step is needed, to avoid "1" instead of 1 for number, etc. + if (exprContextObjectP->isBasic()) { - return value.substr(1, value.size()-2); + s = removeQuotes(s); + } + + if (raw) + { + // This means that the expression is in the middle of the string (i.e. partial replacement and not full replacement), + // so double quotes have to be be removed + result = removeQuotes(s); } else { - return value; + result = s; } } + + r.release(); + return result; } @@ -165,7 +163,7 @@ static std::string stringValueOrNothing(std::map* repl * Date: Mon Jun 19 16:33:29 2017 +0200 * */ -bool macroSubstitute(std::string* to, const std::string& from, std::map* replacementsP, const std::string& notFoundDefault) +bool macroSubstitute(std::string* to, const std::string& from, ExprContextObject* exprContextObjectP, const std::string& notFoundDefault, bool raw) { // Initial size check: is the string to convert too big? // @@ -228,7 +226,7 @@ bool macroSubstitute(std::string* to, const std::string& from, std::map outReqMsgMaxSize) @@ -246,7 +244,7 @@ bool macroSubstitute(std::string* to, const std::string& from, std::mapsecond; std::string macro = "${" + macroName + "}"; - std::string value = stringValueOrNothing(replacementsP, macroName, notFoundDefault); + std::string value = stringValueOrNothing(exprContextObjectP, macroName, notFoundDefault, raw); // We have to do the replace operation as many times as macro occurrences for (unsigned int ix = 0; ix < times; ix++) diff --git a/src/lib/common/macroSubstitute.h b/src/lib/common/macroSubstitute.h index 288c25540b..58f2aac2e5 100644 --- a/src/lib/common/macroSubstitute.h +++ b/src/lib/common/macroSubstitute.h @@ -34,22 +34,7 @@ * smartStringValue - * */ -extern std::string smartStringValue(const std::string stringValue, std::map* replacementsP, const std::string notFoundDefault); - - - -/* **************************************************************************** -* -* buildReplacementMap - -* -*/ -extern void buildReplacementsMap -( - const Entity& en, - const std::string& service, - const std::string& token, - std::map* replacementsP -); +extern std::string smartStringValue(const std::string stringValue, ExprContextObject* exprContextObjectP, const std::string notFoundDefault); @@ -58,6 +43,6 @@ extern void buildReplacementsMap * macroSubstitute - * */ -extern bool macroSubstitute(std::string* sP, const std::string& in, std::map* replacementsP, const std::string& notFoundDefault); +extern bool macroSubstitute(std::string* sP, const std::string& in, ExprContextObject* exprContextObjectP, const std::string& notFoundDefault, bool raw = false); #endif // SRC_LIB_COMMON_MACROSUBSTITUTE_H_ diff --git a/src/lib/common/statistics.cpp b/src/lib/common/statistics.cpp index 794eafee2b..10dd8b1631 100644 --- a/src/lib/common/statistics.cpp +++ b/src/lib/common/statistics.cpp @@ -195,6 +195,10 @@ std::string renderTimingStatistics(void) bool accMongoReadWaitTime = (accTimeStat.mongoReadWaitTime.tv_sec != 0) || (accTimeStat.mongoReadWaitTime.tv_nsec != 0); bool accMongoWriteWaitTime = (accTimeStat.mongoWriteWaitTime.tv_sec != 0) || (accTimeStat.mongoWriteWaitTime.tv_nsec != 0); bool accMongoCommandWaitTime = (accTimeStat.mongoCommandWaitTime.tv_sec != 0) || (accTimeStat.mongoCommandWaitTime.tv_nsec != 0); + bool accExprBasicCtxBldTime = (accTimeStat.exprBasicCtxBldTime.tv_sec != 0) || (accTimeStat.exprBasicCtxBldTime.tv_nsec != 0); + bool accExprBasicEvalTime = (accTimeStat.exprBasicEvalTime.tv_sec != 0) || (accTimeStat.exprBasicEvalTime.tv_nsec != 0); + bool accExprJexlCtxBldTime = (accTimeStat.exprJexlCtxBldTime.tv_sec != 0) || (accTimeStat.exprJexlCtxBldTime.tv_nsec != 0); + bool accExprJexlEvalTime = (accTimeStat.exprJexlEvalTime.tv_sec != 0) || (accTimeStat.exprJexlEvalTime.tv_nsec != 0); bool accRenderTime = (accTimeStat.renderTime.tv_sec != 0) || (accTimeStat.renderTime.tv_nsec != 0); bool accReqTime = (accTimeStat.reqTime.tv_sec != 0) || (accTimeStat.reqTime.tv_nsec != 0); @@ -204,6 +208,10 @@ std::string renderTimingStatistics(void) bool lastMongoReadWaitTime = (lastTimeStat.mongoReadWaitTime.tv_sec != 0) || (lastTimeStat.mongoReadWaitTime.tv_nsec != 0); bool lastMongoWriteWaitTime = (lastTimeStat.mongoWriteWaitTime.tv_sec != 0) || (lastTimeStat.mongoWriteWaitTime.tv_nsec != 0); bool lastMongoCommandWaitTime = (lastTimeStat.mongoCommandWaitTime.tv_sec != 0) || (lastTimeStat.mongoCommandWaitTime.tv_nsec != 0); + bool lastExprBasicCtxBldTime = (lastTimeStat.exprBasicCtxBldTime.tv_sec != 0) || (lastTimeStat.exprBasicCtxBldTime.tv_nsec != 0); + bool lastExprBasicEvalTime = (lastTimeStat.exprBasicEvalTime.tv_sec != 0) || (lastTimeStat.exprBasicEvalTime.tv_nsec != 0); + bool lastExprJexlCtxBldTime = (lastTimeStat.exprJexlCtxBldTime.tv_sec != 0) || (lastTimeStat.exprJexlCtxBldTime.tv_nsec != 0); + bool lastExprJexlEvalTime = (lastTimeStat.exprJexlEvalTime.tv_sec != 0) || (lastTimeStat.exprJexlEvalTime.tv_nsec != 0); bool lastRenderTime = (lastTimeStat.renderTime.tv_sec != 0) || (lastTimeStat.renderTime.tv_nsec != 0); bool lastReqTime = (lastTimeStat.reqTime.tv_sec != 0) || (lastTimeStat.reqTime.tv_nsec != 0); @@ -228,6 +236,10 @@ std::string renderTimingStatistics(void) if (accMongoReadWaitTime) accJh.addNumber("mongoReadWait", timeSpecToFloat(accTimeStat.mongoReadWaitTime)); if (accMongoWriteWaitTime) accJh.addNumber("mongoWriteWait", timeSpecToFloat(accTimeStat.mongoWriteWaitTime)); if (accMongoCommandWaitTime) accJh.addNumber("mongoCommandWait", timeSpecToFloat(accTimeStat.mongoCommandWaitTime)); + if (accExprBasicCtxBldTime) accJh.addNumber("exprBasicCtxBld", timeSpecToFloat(accTimeStat.exprBasicCtxBldTime)); + if (accExprBasicEvalTime) accJh.addNumber("exprBasicEval", timeSpecToFloat(accTimeStat.exprBasicEvalTime)); + if (accExprJexlCtxBldTime) accJh.addNumber("exprJexlCtxBld", timeSpecToFloat(accTimeStat.exprJexlCtxBldTime)); + if (accExprJexlEvalTime) accJh.addNumber("exprJexlEval", timeSpecToFloat(accTimeStat.exprJexlEvalTime)); if (accRenderTime) accJh.addNumber("render", timeSpecToFloat(accTimeStat.renderTime)); if (accReqTime) accJh.addNumber("total", timeSpecToFloat(accTimeStat.reqTime)); @@ -243,6 +255,10 @@ std::string renderTimingStatistics(void) if (lastMongoReadWaitTime) lastJh.addNumber("mongoReadWait", timeSpecToFloat(lastTimeStat.mongoReadWaitTime)); if (lastMongoWriteWaitTime) lastJh.addNumber("mongoWriteWait", timeSpecToFloat(lastTimeStat.mongoWriteWaitTime)); if (lastMongoCommandWaitTime) lastJh.addNumber("mongoCommandWait", timeSpecToFloat(lastTimeStat.mongoCommandWaitTime)); + if (lastExprBasicCtxBldTime) lastJh.addNumber("exprBasicCtxBld", timeSpecToFloat(lastTimeStat.exprBasicCtxBldTime)); + if (lastExprBasicEvalTime) lastJh.addNumber("exprBasicEval", timeSpecToFloat(lastTimeStat.exprBasicEvalTime)); + if (lastExprJexlCtxBldTime) lastJh.addNumber("exprJexlCtxBld", timeSpecToFloat(lastTimeStat.exprJexlCtxBldTime)); + if (lastExprJexlEvalTime) lastJh.addNumber("exprJexlEval", timeSpecToFloat(lastTimeStat.exprJexlEvalTime)); if (lastRenderTime) lastJh.addNumber("render", timeSpecToFloat(lastTimeStat.renderTime)); if (lastReqTime) lastJh.addNumber("total", timeSpecToFloat(lastTimeStat.reqTime)); diff --git a/src/lib/common/statistics.h b/src/lib/common/statistics.h index ab4034f5f2..a5e1195781 100644 --- a/src/lib/common/statistics.h +++ b/src/lib/common/statistics.h @@ -208,6 +208,103 @@ +/* **************************************************************************** +* +* TIME_EXPR_CTXBLD_START - +*/ +#define TIME_EXPR_CTXBLD_START() \ + struct timespec exprCtxBldStart; \ + struct timespec exprCtxBldEnd; \ + \ + if (timingStatistics) \ + { \ + clock_gettime(CLOCK_REALTIME, &exprCtxBldStart); \ + } + + + +/* **************************************************************************** +* +* TIME_EXPR_CTXBLD_STOP - +*/ +#define TIME_EXPR_CTXBLD_STOP() \ + if (timingStatistics) \ + { \ + struct timespec diff; \ + clock_gettime(CLOCK_REALTIME, &exprCtxBldEnd); \ + clock_difftime(&exprCtxBldEnd, &exprCtxBldStart, &diff); \ + if (basic) \ + { \ + clock_addtime(&threadLastTimeStat.exprBasicCtxBldTime, &diff); \ + } \ + else \ + { \ + clock_addtime(&threadLastTimeStat.exprJexlCtxBldTime, &diff); \ + } \ + } + + + +/* **************************************************************************** +* +* TIME_EXPR_BASIC_EVAL_START - +*/ +#define TIME_EXPR_BASIC_EVAL_START() \ + struct timespec exprBasicEvalStart; \ + struct timespec exprBasicEvalEnd; \ + \ + if (timingStatistics) \ + { \ + clock_gettime(CLOCK_REALTIME, &exprBasicEvalStart); \ + } + + + +/* **************************************************************************** +* +* TIME_EXPR_BASIC_EVAL_STOP - +*/ +#define TIME_EXPR_BASIC_EVAL_STOP() \ + if (timingStatistics) \ + { \ + struct timespec diff; \ + clock_gettime(CLOCK_REALTIME, &exprBasicEvalEnd); \ + clock_difftime(&exprBasicEvalEnd, &exprBasicEvalStart, &diff); \ + clock_addtime(&threadLastTimeStat.exprBasicEvalTime, &diff); \ + } + + + +/* **************************************************************************** +* +* TIME_EXPR_JEXL_EVAL_START - +*/ +#define TIME_EXPR_JEXL_EVAL_START() \ + struct timespec exprJexlEvalStart; \ + struct timespec exprJexlEvalEnd; \ + \ + if (timingStatistics) \ + { \ + clock_gettime(CLOCK_REALTIME, &exprJexlEvalStart); \ + } + + + +/* **************************************************************************** +* +* TIME_EXPR_JEXL_EVAL_STOP - +*/ +#define TIME_EXPR_JEXL_EVAL_STOP() \ + if (timingStatistics) \ + { \ + struct timespec diff; \ + clock_gettime(CLOCK_REALTIME, &exprJexlEvalEnd); \ + clock_difftime(&exprJexlEvalEnd, &exprJexlEvalStart, &diff); \ + clock_addtime(&threadLastTimeStat.exprJexlEvalTime, &diff); \ + } + + + /* **************************************************************************** * * TimeStat - @@ -220,6 +317,10 @@ typedef struct TimeStat struct timespec mongoReadWaitTime; struct timespec mongoWriteWaitTime; struct timespec mongoCommandWaitTime; + struct timespec exprBasicCtxBldTime; + struct timespec exprBasicEvalTime; + struct timespec exprJexlCtxBldTime; + struct timespec exprJexlEvalTime; struct timespec renderTime; struct timespec reqTime; } TimeStat; diff --git a/src/lib/common/string.h b/src/lib/common/string.h index 886362d709..f3abcee416 100644 --- a/src/lib/common/string.h +++ b/src/lib/common/string.h @@ -216,4 +216,25 @@ extern std::string offuscatePassword(const std::string& uri, const std::string& */ extern bool regComp(regex_t* re, const char* pattern, int flags); + + +/* **************************************************************************** +* +* removeQuotes - +* +*/ +inline std::string removeQuotes(std::string s) +{ + if (s[0] == '"') + { + return s.substr(1, s.size()-2); + } + else + { + return s; + } +} + + + #endif // SRC_LIB_COMMON_STRING_H_ diff --git a/src/lib/expressions/CMakeLists.txt b/src/lib/expressions/CMakeLists.txt new file mode 100644 index 0000000000..27346743a0 --- /dev/null +++ b/src/lib/expressions/CMakeLists.txt @@ -0,0 +1,46 @@ +# 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 + +CMAKE_MINIMUM_REQUIRED(VERSION 3.0) + +SET (SOURCES + ExprManager.cpp + ExprContext.cpp + ExprResult.cpp + exprMgr.cpp +) + +SET (HEADERS + ExprManager.h + ExprContext.h + ExprResult.h + exprMgr.h +) + + + +# Include directories +# ----------------------------------------------------------------- +include_directories("${PROJECT_SOURCE_DIR}/src/lib") + + +# Library declaration +# ----------------------------------------------------------------- +ADD_LIBRARY(expressions STATIC ${SOURCES} ${HEADERS}) diff --git a/src/lib/expressions/ExprContext.cpp b/src/lib/expressions/ExprContext.cpp new file mode 100644 index 0000000000..80148399b3 --- /dev/null +++ b/src/lib/expressions/ExprContext.cpp @@ -0,0 +1,284 @@ +/* +* +* 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 +* +* Author: Fermin Galan +*/ + +#include + +#include "common/string.h" +#include "logMsg/logMsg.h" +#include "expressions/ExprContext.h" + + +/* **************************************************************************** +* +* ExprContextObject::ExprContextObject - +*/ +ExprContextObject::ExprContextObject(bool _basic) +{ + basic = _basic; +} + + + +/* **************************************************************************** +* +* ExprContextObject::getJexlContext - +*/ +std::string ExprContextObject::getJexlContext(void) +{ + return jh.str(); +} + + + +/* **************************************************************************** +* +* ExprContextObject::getMap - +*/ +std::map* ExprContextObject::getMap(void) +{ + return &repl; +} + + + +/* **************************************************************************** +* +* ExprContextObject::add - +*/ +void ExprContextObject::add(const std::string &key, const std::string &_value, bool raw) +{ + if (basic) + { + std::string value = _value; + if (!raw) + { + // This is the case of regular string. The raw case is for JSON generated from compound values + value = '"' + _value + '"'; + } + LM_T(LmtExpr, ("adding to basic expression context object (string): %s=%s", key.c_str(), value.c_str())); + repl.insert(std::pair(key, value)); + } + else + { + LM_T(LmtExpr, ("adding to JEXL expression context object (string): %s=%s", key.c_str(), _value.c_str())); + jh.addString(key, _value); + } +} + + + +/* **************************************************************************** +* +* ExprContextObject::add - +*/ +void ExprContextObject::add(const std::string &key, double _value) +{ + if (basic) + { + LM_T(LmtExpr, ("adding to basic expression context object (double): %s=%f", key.c_str(), _value)); + repl.insert(std::pair(key, double2string(_value))); + } + else + { + LM_T(LmtExpr, ("adding to JEXL expression context object (double): %s=%f", key.c_str(), _value)); + jh.addNumber(key, _value); + } +} + + + +/* **************************************************************************** +* +* ExprContextObject::add - +*/ +void ExprContextObject::add(const std::string &key, bool _value) +{ + if (basic) + { + LM_T(LmtExpr, ("adding to basic expression context object (bool): %s=%s", key.c_str(), _value ? "true" : "false")); + repl.insert(std::pair(key, _value? "true": "false")); + } + else + { + LM_T(LmtExpr, ("adding to JEXL expression context object (bool): %s=%s", key.c_str(), _value ? "true" : "false")); + jh.addBool(key, _value); + } +} + + + +/* **************************************************************************** +* +* ExprContextObject::add - +*/ +void ExprContextObject::add(const std::string &key) +{ + if (basic) + { + LM_T(LmtExpr, ("adding to basic expression context object (none): %s", key.c_str())); + repl.insert(std::pair(key, "null")); + } + else + { + LM_T(LmtExpr, ("adding to JEXL expression context object (none): %s", key.c_str())); + jh.addNull(key); + } +} + + + +/* **************************************************************************** +* +* ExprContextObject::add - +*/ +void ExprContextObject::add(const std::string &key, ExprContextObject exprContextObject) +{ + if (basic) + { + LM_E(("Runtime Error (this method must not be invoked in basic mode)")); + } + else + { + std::string s = exprContextObject.getJexlContext(); + LM_T(LmtExpr, ("adding to JEXL expression context object (object): %s=%s", key.c_str(), s.c_str())); + jh.addRaw(key, s); + } +} + + + +/* **************************************************************************** +* +* ExprContextObject::add - +*/ +void ExprContextObject::add(const std::string &key, ExprContextList exprContextList) +{ + if (basic) + { + LM_E(("Runtime Error (this method must not be invoked in basic mode)")); + } + else + { + std::string s = exprContextList.get(); + LM_T(LmtExpr, ("adding to JEXL expression context object (list): %s=%s", key.c_str(), s.c_str())); + jh.addRaw(key, s); + } +} + + + +/* **************************************************************************** +* +* ExprContextObject::isBasic - +*/ +bool ExprContextObject::isBasic(void) +{ + return basic; +} + + + +/* **************************************************************************** +* +* ExprContextList::get - +*/ +std::string ExprContextList::get(void) +{ + return jh.str(); +} + + + +/* **************************************************************************** +* +* ExprContextList::add - +*/ +void ExprContextList::add(const std::string &_value) +{ + LM_T(LmtExpr, ("adding to JEXL expression context list (string): %s", _value.c_str())); + jh.addString(_value); +} + + + +/* **************************************************************************** +* +* ExprContextList::add - +*/ +void ExprContextList::add(double _value) +{ + LM_T(LmtExpr, ("adding to JEXL expression context list (double): %f", _value)); + jh.addNumber(_value); +} + + + +/* **************************************************************************** +* +* ExprContextList::add - +*/ +void ExprContextList::add(bool _value) +{ + LM_T(LmtExpr, ("adding to JEXL expression context list (bool): %s", _value ? "true" : "false")); + jh.addBool(_value); +} + + + +/* **************************************************************************** +* +* ExprContextList::add - +*/ +void ExprContextList::add(void) +{ + LM_T(LmtExpr, ("adding to JEXL expression context list (none)")); + jh.addNull(); +} + + + +/* **************************************************************************** +* +* ExprContextList::add - +*/ +void ExprContextList::add(ExprContextObject exprContextObject) +{ + std::string s = exprContextObject.getJexlContext(); + LM_T(LmtExpr, ("adding to JEXL expression context list (object): %s", s.c_str())); + jh.addRaw(s); +} + + + +/* **************************************************************************** +* +* ExprContextList::add - +*/ +void ExprContextList::add(ExprContextList exprContextList) +{ + std::string s = exprContextList.get(); + LM_T(LmtExpr, ("adding to JEXL expression context list (list): %s", s.c_str())); + jh.addRaw(s); +} diff --git a/src/lib/expressions/ExprContext.h b/src/lib/expressions/ExprContext.h new file mode 100644 index 0000000000..0e994be3b2 --- /dev/null +++ b/src/lib/expressions/ExprContext.h @@ -0,0 +1,79 @@ +#ifndef SRC_LIB_EXPRESSIONS_EXPRCONTEXT_H_ +#define SRC_LIB_EXPRESSIONS_EXPRCONTEXT_H_ + +/* +* +* 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 +* +* Author: Fermin Galan +*/ + +#include +#include + +#include "common/JsonHelper.h" + +class ExprContextList; // forward declaration + +/* **************************************************************************** +* +* ExprContext - +*/ +class ExprContextObject +{ +private: + bool basic; + JsonObjectHelper jh; // used in regular (i.e. not basic) mode + std::map repl; // used in basic mode + +public: + ExprContextObject(bool basic = false); + + std::string getJexlContext(void); + std::map* getMap(void); + + void add(const std::string& key, const std::string& value, bool raw = false); + void add(const std::string& key, double value); + void add(const std::string& key, bool value); + void add(const std::string& key); + void add(const std::string& key, ExprContextObject exprContextObject); + void add(const std::string& key, ExprContextList exprContextList); + + bool isBasic(void); +}; + +class ExprContextList +{ +private: + JsonVectorHelper jh; + +public: + std::string get(void); + void add(const std::string& value); + void add(double value); + void add(bool value); + void add(void); + void add(ExprContextObject exprContextObject); + void add(ExprContextList exprContextList); +}; + + +#endif // SRC_LIB_EXPRESSIONS_EXPRCONTEXT_H_ diff --git a/src/lib/expressions/ExprManager.cpp b/src/lib/expressions/ExprManager.cpp new file mode 100644 index 0000000000..c9bbf0299e --- /dev/null +++ b/src/lib/expressions/ExprManager.cpp @@ -0,0 +1,147 @@ +/* +* +* 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 +* +* Author: Fermin Galan +*/ + +#include "expressions/ExprManager.h" +#include "expressions/ExprResult.h" +#include "logMsg/logMsg.h" + +#include "orionTypes/OrionValueType.h" +#include "common/statistics.h" + +#ifdef EXPR_BASIC +// Never called, but need to be defined to avoid compilation errors +static void* cjexl_new_engine() +{ + return NULL; +} + +static void cjexl_free_engine(void* ptr) +{ +} + +static const char* cjexl_eval(void* ptr, const char* script_ptr, const char* context_ptr) +{ + return ""; +} +#else +// Interface to use libcjexl +extern "C" { + void* cjexl_new_engine(); +} + +extern "C" { + void cjexl_free_engine(void* ptr); +} + +extern "C" { + const char* cjexl_eval(void* ptr, const char* script_ptr, const char* context_ptr); +} +#endif + + +/* **************************************************************************** +* +* ExprManager::init - +*/ +void ExprManager::init(void) +{ + jexlEngine = cjexl_new_engine(); +} + + + +/* **************************************************************************** +* +* ExprManager::evaluate - +*/ +ExprResult ExprManager::evaluate(ExprContextObject* exprContextObjectP, const std::string& _expression) +{ + ExprResult r; + r.valueType = orion::ValueTypeNull; + + if (exprContextObjectP->isBasic()) + { + // std::map based evaluation. Only pure replacement is supported + + TIME_EXPR_BASIC_EVAL_START(); + LM_T(LmtExpr, ("evaluating basic expression: <%s>", _expression.c_str())); + + std::map* replacementsP = exprContextObjectP->getMap(); + + std::map::iterator iter = replacementsP->find(_expression); + if (iter != replacementsP->end()) + { + r.stringValue = iter->second; + + // To have the same behaviour than in JEXL case + if (r.stringValue == "null") + { + r.valueType = orion::ValueTypeNull; + } + else + { + r.valueType = orion::ValueTypeString; + } + + LM_T(LmtExpr, ("basic evaluation result: <%s>", r.stringValue.c_str())); + } + TIME_EXPR_BASIC_EVAL_STOP(); + } + else + { + // JEXL based evaluation + + TIME_EXPR_JEXL_EVAL_START(); + std::string context = exprContextObjectP->getJexlContext(); + LM_T(LmtExpr, ("evaluating JEXL expression <%s> with context <%s>", _expression.c_str(), context.c_str())); + const char* result = cjexl_eval(jexlEngine, _expression.c_str(), context.c_str()); + LM_T(LmtExpr, ("JEXL evaluation result: <%s>", result)); + + // The ExprResult::fill() method allocates dynamic memory. So, the callers to evaluate() are supposed to invoke ExprResult::release() + // method to free it + r.fill(result); + +#ifndef EXPR_BASIC + // cjexl_eval() allocated memory for us. We have to release it in order to avoid a leak + // (disbled with EXPR_BASIC because in that case result is static memory) + free((char*)result); +#endif + + TIME_EXPR_JEXL_EVAL_STOP(); + } + + return r; +} + + + +/* **************************************************************************** +* +* ExprManager::release - +*/ +void ExprManager::release(void) +{ + cjexl_free_engine(jexlEngine); +} \ No newline at end of file diff --git a/src/lib/expressions/ExprManager.h b/src/lib/expressions/ExprManager.h new file mode 100644 index 0000000000..5d0892c078 --- /dev/null +++ b/src/lib/expressions/ExprManager.h @@ -0,0 +1,47 @@ +#ifndef SRC_LIB_EXPRESSIONS_EXPRMANAGER_H_ +#define SRC_LIB_EXPRESSIONS_EXPRMANAGER_H_ + +/* +* +* 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 +* +* Author: Fermin Galan +*/ + +#include "expressions/ExprContext.h" +#include "expressions/ExprResult.h" + +/* **************************************************************************** +* +* ExprManager - +*/ +class ExprManager +{ +private: + void* jexlEngine; + +public: + void init(void); + ExprResult evaluate(ExprContextObject* exprContextObjectP, const std::string& expression); + void release(void); +}; + +#endif // SRC_LIB_EXPRESSIONS_EXPRMANAGER_H_ \ No newline at end of file diff --git a/src/lib/expressions/ExprResult.cpp b/src/lib/expressions/ExprResult.cpp new file mode 100644 index 0000000000..eec2e415d6 --- /dev/null +++ b/src/lib/expressions/ExprResult.cpp @@ -0,0 +1,314 @@ +/* +* +* 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 +* +* Author: Fermin Galan +*/ + +// The ExprResult class is used as return value in ExprManager::evaluate(). We could return std::string +// in that function and simplify (so avoiding the ExprResult class). But in that case float rounding is +// problematic (e.g. 1.999999 instead of 2), as they don't take advantage of the ad hoc logic implemented +// in ContextAttribute rendering + +#include "rapidjson/document.h" + +#include "expressions/ExprResult.h" + +#include "common/string.h" +#include "common/JsonHelper.h" +#include "logMsg/logMsg.h" + +#include "jsonParseV2/jsonParseTypeNames.h" +#include "jsonParseV2/utilsParse.h" + + + +static void processDictItem(orion::CompoundValueNode* parentP, const rapidjson::Value::ConstMemberIterator& iter); // forward declaration + + + +/* **************************************************************************** +* +* processListItem - +* +*/ +static void processListItem(orion::CompoundValueNode* parentP, const rapidjson::Value::ConstValueIterator& iter) +{ + orion::CompoundValueNode* nodeP; + + std::string type = jsonParseTypeNames[iter->GetType()]; + + if (type == "String") + { + nodeP = new orion::CompoundValueNode("", iter->GetString(), orion::ValueTypeString); + parentP->add(nodeP); + } + else if (type == "Number") + { + nodeP = new orion::CompoundValueNode("", iter->GetDouble(), orion::ValueTypeNumber); + parentP->add(nodeP); + } + else if (type == "True") + { + nodeP = new orion::CompoundValueNode("", true, orion::ValueTypeBoolean); + parentP->add(nodeP); + } + else if (type == "False") + { + nodeP = new orion::CompoundValueNode("", false, orion::ValueTypeBoolean); + parentP->add(nodeP); + } + else if (type == "Null") + { + nodeP = new orion::CompoundValueNode("", "", orion::ValueTypeNull); + parentP->add(nodeP); + } + else if (type == "Array") + { + nodeP = new orion::CompoundValueNode("", "", orion::ValueTypeVector); + for (rapidjson::Value::ConstValueIterator iter2 = iter->Begin(); iter2 != iter->End(); ++iter2) + { + processListItem(nodeP, iter2); + } + parentP->add(nodeP); + } + else if (type == "Object") + { + nodeP = new orion::CompoundValueNode("", "", orion::ValueTypeObject); + for (rapidjson::Value::ConstMemberIterator iter2 = iter->MemberBegin(); iter2 != iter->MemberEnd(); ++iter2) + { + processDictItem(nodeP, iter2); + } + parentP->add(nodeP); + } + else + { + LM_E(("Runtime Error (unknown type: %s)", type.c_str())); + } +} + + + +/* **************************************************************************** +* +* processDictItem - +* +*/ +static void processDictItem(orion::CompoundValueNode* parentP, const rapidjson::Value::ConstMemberIterator& iter) +{ + orion::CompoundValueNode* nodeP; + + std::string name = iter->name.GetString(); + std::string type = jsonParseTypeNames[iter->value.GetType()]; + + if (type == "String") + { + nodeP = new orion::CompoundValueNode(name, iter->value.GetString(), orion::ValueTypeString); + parentP->add(nodeP); + } + else if (type == "Number") + { + nodeP = new orion::CompoundValueNode(name, iter->value.GetDouble(), orion::ValueTypeNumber); + parentP->add(nodeP); + } + else if (type == "True") + { + nodeP = new orion::CompoundValueNode(name, true, orion::ValueTypeBoolean); + parentP->add(nodeP); + } + else if (type == "False") + { + nodeP = new orion::CompoundValueNode(name, false, orion::ValueTypeBoolean); + parentP->add(nodeP); + } + else if (type == "Null") + { + nodeP = new orion::CompoundValueNode(name, "", orion::ValueTypeNull); + parentP->add(nodeP); + } + else if (type == "Array") + { + nodeP = new orion::CompoundValueNode(name, "", orion::ValueTypeVector); + for (rapidjson::Value::ConstValueIterator iter2 = iter->value.Begin(); iter2 != iter->value.End(); ++iter2) + { + processListItem(nodeP, iter2); + } + parentP->add(nodeP); + } + else if (type == "Object") + { + nodeP = new orion::CompoundValueNode(name, "", orion::ValueTypeObject); + for (rapidjson::Value::ConstMemberIterator iter2 = iter->value.MemberBegin(); iter2 != iter->value.MemberEnd(); ++iter2) + { + processDictItem(nodeP, iter2); + } + parentP->add(nodeP); + } + else + { + LM_E(("Runtime Error (unknown type: %s)", type.c_str())); + } +} + + + +/* **************************************************************************** +* +* ExprResult::ExprResult - +* +*/ +ExprResult::ExprResult() +{ + compoundValueP = NULL; +} + + + +/* **************************************************************************** +* +* ExprResult::fill - +* +*/ +void ExprResult::fill(const std::string& result) +{ + // If nothing changes, the returned value would be null (failsafe) + valueType = orion::ValueTypeNull; + + rapidjson::Document document; + + document.Parse(result.c_str()); + + if (document.HasParseError()) + { + LM_E(("Runtime Error (parsing ExprResult: %s)", parseErrorString(document.GetParseError()).c_str())); + return; + } + + std::string type = jsonParseTypeNames[document.GetType()]; + + if (type == "String") + { + stringValue = document.GetString(); + valueType = orion::ValueTypeString; + } + else if (type == "Number") + { + numberValue = document.GetDouble(); + valueType = orion::ValueTypeNumber; + } + else if (type == "True") + { + boolValue = true; + valueType = orion::ValueTypeBoolean; + } + else if (type == "False") + { + boolValue = false; + valueType = orion::ValueTypeBoolean; + } + else if (type == "Null") + { + valueType = orion::ValueTypeNull; + } + else if (type == "Array") + { + valueType = orion::ValueTypeVector; + compoundValueP = new orion::CompoundValueNode(orion::ValueTypeVector); + for (rapidjson::Value::ConstValueIterator iter = document.Begin(); iter != document.End(); ++iter) + { + processListItem(compoundValueP, iter); + } + } + else if (type == "Object") + { + valueType = orion::ValueTypeObject; + compoundValueP = new orion::CompoundValueNode(orion::ValueTypeObject); + for (rapidjson::Value::ConstMemberIterator iter = document.MemberBegin(); iter != document.MemberEnd(); ++iter) + { + processDictItem(compoundValueP, iter); + } + } + else + { + LM_E(("Runtime Error (unknown type: %s)", type.c_str())); + } +} + + + +/* **************************************************************************** +* +* ExprResult::toString - +* +* Pretty similar to ContextAttribute::toJsonValue() +* +*/ +std::string ExprResult::toString(void) +{ + if (valueType == orion::ValueTypeNumber) + { + return double2string(numberValue); + } + else if (valueType == orion::ValueTypeBoolean) + { + return boolValue ? "true" : "false"; + } + else if (valueType == orion::ValueTypeString) + { + return "\"" + stringValue + "\""; + } + else if ((valueType == orion::ValueTypeObject)||(valueType == orion::ValueTypeVector)) + { + if (compoundValueP != NULL) + { + return compoundValueP->toJson(); + } + else + { + LM_E(("Runtime Error (result is vector/object but compountValue is NULL)")); + return ""; + } + } + else if (valueType == orion::ValueTypeNull) + { + return "null"; + } + else + { + LM_E(("Runtime Error (not allowed type in ExprResult: %s)", valueTypeName(valueType))); + return ""; + } +} + + + +/* **************************************************************************** +* +* ExprResult::release - +*/ +void ExprResult::release(void) +{ + if (compoundValueP != NULL) + { + delete compoundValueP; + compoundValueP = NULL; + } +} \ No newline at end of file diff --git a/src/lib/expressions/ExprResult.h b/src/lib/expressions/ExprResult.h new file mode 100644 index 0000000000..9814d3073b --- /dev/null +++ b/src/lib/expressions/ExprResult.h @@ -0,0 +1,62 @@ +#ifndef SRC_LIB_EXPRESSIONS_EXPRRESULT_H_ +#define SRC_LIB_EXPRESSIONS_EXPRRESULT_H_ + +/* +* +* 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 +* +* Author: Fermin Galan +*/ + +#include "orionTypes/OrionValueType.h" + +#include "parse/CompoundValueNode.h" + +#include + +/* **************************************************************************** +* +* ExprResult - +*/ +class ExprResult +{ +public: + // Similar to the fields used in ContextAttribute.h + + orion::ValueType valueType; // Type of value + std::string stringValue; // "value" as a String + double numberValue; // "value" as a Number + bool boolValue; // "value" as a Boolean + + // Use only when valueType is object or vector + orion::CompoundValueNode* compoundValueP; + + ExprResult(void); + + void fill(const std::string& result); + + std::string toString(void); + void release(void); +}; + + + +#endif // SRC_LIB_EXPRESSIONS_EXPRRESULT_H_ diff --git a/src/lib/expressions/exprMgr.cpp b/src/lib/expressions/exprMgr.cpp new file mode 100644 index 0000000000..f56d51706b --- /dev/null +++ b/src/lib/expressions/exprMgr.cpp @@ -0,0 +1,33 @@ +/* +* +* 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 +* +* Author: Fermin Galan +*/ +#include "expressions/ExprManager.h" + + + +/* **************************************************************************** +* +* exprMgr - +*/ +ExprManager exprMgr; diff --git a/src/lib/expressions/exprMgr.h b/src/lib/expressions/exprMgr.h new file mode 100644 index 0000000000..5b57b3feb8 --- /dev/null +++ b/src/lib/expressions/exprMgr.h @@ -0,0 +1,38 @@ +#ifndef SRC_LIB_EXPRESSIONS_EXPRMGR_H_ +#define SRC_LIB_EXPRESSIONS_EXPRMGR_H_ + +/* +* +* 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 +* +* Author: Fermin Galan +*/ +#include "expressions/ExprManager.h" + + + +/* **************************************************************************** +* +* metricsMgr - +*/ +extern ExprManager exprMgr; + +#endif // SRC_LIB_EXPRESSIONS_EXPRMGR_H_ diff --git a/src/lib/jsonParseV2/parseContextAttribute.cpp b/src/lib/jsonParseV2/parseContextAttribute.cpp index 751dfb66d3..caf6ac190a 100644 --- a/src/lib/jsonParseV2/parseContextAttribute.cpp +++ b/src/lib/jsonParseV2/parseContextAttribute.cpp @@ -254,7 +254,8 @@ std::string parseContextAttribute ConnectionInfo* ciP, const rapidjson::Value::ConstMemberIterator& iter, ContextAttribute* caP, - bool checkAttrSpecialTypes + bool checkAttrSpecialTypes, + bool relaxForbiddenCheck ) { std::string name = iter->name.GetString(); @@ -383,7 +384,7 @@ std::string parseContextAttribute caP->type = (compoundVector)? defaultType(orion::ValueTypeVector) : defaultType(caP->valueType); } - std::string r = caP->check(ciP->apiVersion, ciP->requestType); + std::string r = caP->check(ciP->apiVersion, ciP->requestType, relaxForbiddenCheck); if (r != "OK") { alarmMgr.badInput(clientIp, "JSON Parse Error", r); diff --git a/src/lib/jsonParseV2/parseContextAttribute.h b/src/lib/jsonParseV2/parseContextAttribute.h index e3210e9403..2ab0ed39e6 100644 --- a/src/lib/jsonParseV2/parseContextAttribute.h +++ b/src/lib/jsonParseV2/parseContextAttribute.h @@ -42,7 +42,8 @@ extern std::string parseContextAttribute ConnectionInfo* ciP, const rapidjson::Value::ConstMemberIterator& iter, ContextAttribute* caP, - bool checkAttrSpecialTypes + bool checkAttrSpecialTypes, + bool relaxForbiddenCheck = false ); diff --git a/src/lib/jsonParseV2/parseSubscription.cpp b/src/lib/jsonParseV2/parseSubscription.cpp index 4207dfa023..593161b202 100644 --- a/src/lib/jsonParseV2/parseSubscription.cpp +++ b/src/lib/jsonParseV2/parseSubscription.cpp @@ -525,11 +525,6 @@ static std::string parseCustomPayload } ngsi->id = iter->value.GetString(); - - if (forbiddenIdChars(V2, ngsi->id.c_str(), "")) - { - return badInput(ciP, ERROR_DESC_BAD_REQUEST_INVALID_CHAR_ENTID); - } } else if (name == "type") { @@ -539,11 +534,6 @@ static std::string parseCustomPayload } ngsi->type = iter->value.GetString(); - - if (forbiddenIdChars(V2, ngsi->type.c_str(), "")) - { - return badInput(ciP, ERROR_DESC_BAD_REQUEST_INVALID_CHAR_ENTTYPE); - } } else // attribute { @@ -551,7 +541,9 @@ static std::string parseCustomPayload ngsi->attributeVector.push_back(caP); - std::string r = parseContextAttribute(ciP, iter, caP, false); + // Note we are using relaxForbiddenCheck true in this case, as JEXL expressions typically use forbidden + // chars and we don't want to fail in that case + std::string r = parseContextAttribute(ciP, iter, caP, false, true); if (r == "max deep reached") { diff --git a/src/lib/logMsg/traceLevels.h b/src/lib/logMsg/traceLevels.h index d0df21e423..84a40bc6f8 100644 --- a/src/lib/logMsg/traceLevels.h +++ b/src/lib/logMsg/traceLevels.h @@ -131,6 +131,7 @@ typedef enum TraceLevels LmtNotImplemented, LmtCurlContext, LmtThreadpool, + LmtExpr, LmtOldInfo = 240, // old INFO traces moved to DEBUG in Orion 2.5.0 diff --git a/src/lib/ngsi/ContextAttribute.cpp b/src/lib/ngsi/ContextAttribute.cpp index 88681d879a..79ce3f57fc 100644 --- a/src/lib/ngsi/ContextAttribute.cpp +++ b/src/lib/ngsi/ContextAttribute.cpp @@ -1052,7 +1052,7 @@ void ContextAttribute::filterAndOrderMetadata * renderNgsiField true is used in custom notification payloads, which have some small differences * with regards to conventional rendering */ -std::string ContextAttribute::toJson(const std::vector& metadataFilter, bool renderNgsiField, std::map* replacementsP) +std::string ContextAttribute::toJson(const std::vector& metadataFilter, bool renderNgsiField, ExprContextObject* exprContextObjectP) { JsonObjectHelper jh; @@ -1085,7 +1085,7 @@ std::string ContextAttribute::toJson(const std::vector& metadataFi // of DB entities) may lead to NULL, so the check is needed if (childToRenderP != NULL) { - jh.addRaw("value", childToRenderP->toJson(replacementsP)); + jh.addRaw("value", childToRenderP->toJson(exprContextObjectP)); } } else if (valueType == orion::ValueTypeNumber) @@ -1101,7 +1101,7 @@ std::string ContextAttribute::toJson(const std::vector& metadataFi } else if (valueType == orion::ValueTypeString) { - jh.addRaw("value", smartStringValue(stringValue, replacementsP, "null")); + jh.addRaw("value", smartStringValue(stringValue, exprContextObjectP, "null")); } else if (valueType == orion::ValueTypeBoolean) { @@ -1290,11 +1290,80 @@ std::string ContextAttribute::toJsonAsValue +/* **************************************************************************** +* +* addToContext - +* +* Pretty similar in structure to toJsonValue +*/ +void ContextAttribute::addToContext(ExprContextObject* exprContextObjectP, bool legacy) +{ + if (compoundValueP != NULL) + { + // In legacy expression, objects are vector are strings to be stored in a std::map + if (valueType == orion::ValueTypeObject) + { + if (legacy) + { + exprContextObjectP->add(name, compoundValueP->toJson(), true); + } + else + { + exprContextObjectP->add(name, compoundValueP->toExprContextObject()); + } + } + else // valueType == orion::ValueTypeVector + { + if (legacy) + { + exprContextObjectP->add(name, compoundValueP->toJson(), true); + } + else + { + exprContextObjectP->add(name, compoundValueP->toExprContextList()); + } + } + } + else if (valueType == orion::ValueTypeNumber) + { + if ((type == DATE_TYPE) || (type == DATE_TYPE_ALT)) + { + exprContextObjectP->add(name, toJsonString(isodate2str(numberValue))); + } + else // regular number + { + exprContextObjectP->add(name, numberValue); + } + } + else if (valueType == orion::ValueTypeString) + { + exprContextObjectP->add(name, toJsonString(stringValue)); + } + else if (valueType == orion::ValueTypeBoolean) + { + exprContextObjectP->add(name, boolValue); + } + else if (valueType == orion::ValueTypeNull) + { + exprContextObjectP->add(name); + } + else if (valueType == orion::ValueTypeNotGiven) + { + LM_E(("Runtime Error (value not given for attribute %s)", name.c_str())); + } + else + { + LM_E(("Runtime Error (invalid value type %s for attribute %s)", valueTypeName(valueType), name.c_str())); + } +} + + + /* **************************************************************************** * * ContextAttribute::check - */ -std::string ContextAttribute::check(ApiVersion apiVersion, RequestType requestType) +std::string ContextAttribute::check(ApiVersion apiVersion, RequestType requestType, bool relaxForbiddenCheck) { size_t len; char errorMsg[128]; @@ -1345,14 +1414,14 @@ std::string ContextAttribute::check(ApiVersion apiVersion, RequestType requestTy return "Invalid characters in attribute type"; } - if ((compoundValueP != NULL) && (compoundValueP->childV.size() != 0) && (type != TEXT_UNRESTRICTED_TYPE)) + if ((!relaxForbiddenCheck) && (compoundValueP != NULL) && (compoundValueP->childV.size() != 0) && (type != TEXT_UNRESTRICTED_TYPE)) { return compoundValueP->check(""); } if (valueType == orion::ValueTypeString) { - if ((type != TEXT_UNRESTRICTED_TYPE) && (forbiddenChars(stringValue.c_str()))) + if ((!relaxForbiddenCheck) && (type != TEXT_UNRESTRICTED_TYPE) && (forbiddenChars(stringValue.c_str()))) { alarmMgr.badInput(clientIp, "found a forbidden character in the value of an attribute", stringValue); return "Invalid characters in attribute value"; diff --git a/src/lib/ngsi/ContextAttribute.h b/src/lib/ngsi/ContextAttribute.h index 58d7ed39f1..f242bf101d 100644 --- a/src/lib/ngsi/ContextAttribute.h +++ b/src/lib/ngsi/ContextAttribute.h @@ -37,6 +37,7 @@ #include "parse/CompoundValueNode.h" #include "rest/HttpStatusCode.h" #include "mongoDriver/BSONObjBuilder.h" +#include "expressions/ExprContext.h" @@ -107,7 +108,7 @@ typedef struct ContextAttribute std::string toJsonV1AsNameString(bool comma); - std::string toJson(const std::vector& metadataFilter, bool renderNgsiField = false, std::map* replacementsP = NULL); + std::string toJson(const std::vector& metadataFilter, bool renderNgsiField = false, ExprContextObject* exprContextObjectP = NULL); std::string toJsonValue(void); @@ -118,6 +119,8 @@ typedef struct ContextAttribute MimeType* outMimeTypeP, HttpStatusCode* scP); + void addToContext(ExprContextObject* exprContextObjectP, bool legacy); + void release(void); std::string getName(void); @@ -131,7 +134,7 @@ typedef struct ContextAttribute /* Helper method to be use in some places wher '%s' is needed */ std::string getValue(void) const; - std::string check(ApiVersion apiVersion, RequestType requestType); + std::string check(ApiVersion apiVersion, RequestType requestType, bool relaxForbiddenCheck = false); ContextAttribute* clone(); bool compoundItemExists(const std::string& compoundPath, orion::CompoundValueNode** compoundItemPP = NULL); diff --git a/src/lib/ngsi/ContextElementResponse.cpp b/src/lib/ngsi/ContextElementResponse.cpp index ba9d3ae0b2..8f5dace5ca 100644 --- a/src/lib/ngsi/ContextElementResponse.cpp +++ b/src/lib/ngsi/ContextElementResponse.cpp @@ -215,12 +215,12 @@ std::string ContextElementResponse::toJson const std::vector& attrsFilter, bool blacklist, const std::vector& metadataFilter, - std::map* replacementsP + ExprContextObject* exprContextObjectP ) { std::string out; - out = entity.toJson(renderFormat, attrsFilter, blacklist, metadataFilter, false, replacementsP); + out = entity.toJson(renderFormat, attrsFilter, blacklist, metadataFilter, false, exprContextObjectP); return out; } diff --git a/src/lib/ngsi/ContextElementResponse.h b/src/lib/ngsi/ContextElementResponse.h index 87b3bc17b1..2394183081 100644 --- a/src/lib/ngsi/ContextElementResponse.h +++ b/src/lib/ngsi/ContextElementResponse.h @@ -33,6 +33,7 @@ #include "ngsi/StringList.h" #include "ngsi/ContextAttribute.h" #include "apiTypesV2/Entity.h" +#include "expressions/ExprContext.h" #include "mongoDriver/BSONObj.h" @@ -82,7 +83,7 @@ typedef struct ContextElementResponse const std::vector& attrsFilter, bool blacklist, const std::vector& metadataFilter, - std::map* replacementsP); + ExprContextObject* exprContextObjectP); void applyUpdateOperators(void); diff --git a/src/lib/ngsi/ContextElementResponseVector.cpp b/src/lib/ngsi/ContextElementResponseVector.cpp index e044e9779d..24f40121f4 100644 --- a/src/lib/ngsi/ContextElementResponseVector.cpp +++ b/src/lib/ngsi/ContextElementResponseVector.cpp @@ -119,14 +119,14 @@ std::string ContextElementResponseVector::toJson const std::vector& attrsFilter, bool blacklist, const std::vector& metadataFilter, - std::map* replacementsP + ExprContextObject* exprContextObjectP ) { JsonVectorHelper jvh; for (unsigned int ix = 0; ix < vec.size(); ++ix) { - jvh.addRaw(vec[ix]->toJson(renderFormat, attrsFilter, blacklist, metadataFilter, replacementsP)); + jvh.addRaw(vec[ix]->toJson(renderFormat, attrsFilter, blacklist, metadataFilter, exprContextObjectP)); } return jvh.str(); diff --git a/src/lib/ngsi/ContextElementResponseVector.h b/src/lib/ngsi/ContextElementResponseVector.h index 92f74e5ff6..9a6102b990 100644 --- a/src/lib/ngsi/ContextElementResponseVector.h +++ b/src/lib/ngsi/ContextElementResponseVector.h @@ -31,6 +31,7 @@ #include "ngsi/ContextElementResponse.h" #include "apiTypesV2/EntityVector.h" #include "common/RenderFormat.h" +#include "expressions/ExprContext.h" @@ -60,7 +61,7 @@ typedef struct ContextElementResponseVector const std::vector& attrsFilter, bool blacklist, const std::vector& metadataFilter, - std::map* replacementsP); + ExprContextObject* exprContextObjectP); void push_back(ContextElementResponse* item); unsigned int size(void) const; ContextElementResponse* lookup(Entity* eP, HttpStatusCode code = SccNone); diff --git a/src/lib/ngsi10/NotifyContextRequest.cpp b/src/lib/ngsi10/NotifyContextRequest.cpp index b9b4ac5af7..f1ae7167d9 100644 --- a/src/lib/ngsi10/NotifyContextRequest.cpp +++ b/src/lib/ngsi10/NotifyContextRequest.cpp @@ -80,7 +80,7 @@ std::string NotifyContextRequest::toJson const std::vector& attrsFilter, bool blacklist, const std::vector& metadataFilter, - std::map* replacementsP + ExprContextObject* exprContextObjectP ) { if ((renderFormat != NGSI_V2_NORMALIZED) && (renderFormat != NGSI_V2_KEYVALUES) && (renderFormat != NGSI_V2_VALUES) && (renderFormat != NGSI_V2_SIMPLIFIEDKEYVALUES) && (renderFormat != NGSI_V2_SIMPLIFIEDNORMALIZED)) @@ -100,7 +100,7 @@ std::string NotifyContextRequest::toJson else { std::string out; - out += contextElementResponseVector[0]->toJson(NGSI_V2_NORMALIZED, attrsFilter, blacklist, metadataFilter, replacementsP); + out += contextElementResponseVector[0]->toJson(NGSI_V2_NORMALIZED, attrsFilter, blacklist, metadataFilter, exprContextObjectP); return out; } } @@ -114,7 +114,7 @@ std::string NotifyContextRequest::toJson else { std::string out; - out += contextElementResponseVector[0]->toJson(NGSI_V2_KEYVALUES, attrsFilter, blacklist, metadataFilter, replacementsP); + out += contextElementResponseVector[0]->toJson(NGSI_V2_KEYVALUES, attrsFilter, blacklist, metadataFilter, exprContextObjectP); return out; } } @@ -123,7 +123,7 @@ std::string NotifyContextRequest::toJson JsonObjectHelper jh; jh.addString("subscriptionId", subscriptionId.get()); - jh.addRaw("data", contextElementResponseVector.toJson(renderFormat, attrsFilter, blacklist, metadataFilter, replacementsP)); + jh.addRaw("data", contextElementResponseVector.toJson(renderFormat, attrsFilter, blacklist, metadataFilter, exprContextObjectP)); return jh.str(); } } diff --git a/src/lib/ngsi10/NotifyContextRequest.h b/src/lib/ngsi10/NotifyContextRequest.h index c4043704d5..c0d179a1f3 100644 --- a/src/lib/ngsi10/NotifyContextRequest.h +++ b/src/lib/ngsi10/NotifyContextRequest.h @@ -53,7 +53,7 @@ typedef struct NotifyContextRequest const std::vector& attrsFilter, bool blacklist, const std::vector& metadataFilter, - std::map* replacementsP = NULL); + ExprContextObject* exprContextObjectP = NULL); std::string check(ApiVersion apiVersion, const std::string& predetectedError); void release(void); NotifyContextRequest* clone(void); diff --git a/src/lib/ngsiNotify/Notifier.cpp b/src/lib/ngsiNotify/Notifier.cpp index aae1ecb34f..e26fc31e37 100644 --- a/src/lib/ngsiNotify/Notifier.cpp +++ b/src/lib/ngsiNotify/Notifier.cpp @@ -121,7 +121,7 @@ static bool setPayload const std::string& notifPayload, const SubscriptionId& subscriptionId, Entity& en, - std::map* replacementsP, + ExprContextObject* exprContextObjectP, const std::vector& attrsFilter, bool blacklist, const std::vector& metadataFilter, @@ -170,7 +170,7 @@ static bool setPayload } else { - if (!macroSubstitute(payloadP, notifPayload, replacementsP, "")) + if (!macroSubstitute(payloadP, notifPayload, exprContextObjectP, "null", true)) { return false; } @@ -194,37 +194,17 @@ static bool setPayload static bool setJsonPayload ( orion::CompoundValueNode* json, - std::map* replacementsP, + ExprContextObject* exprContextObjectP, std::string* payloadP, std::string* mimeTypeP ) { - *payloadP = json->toJson(replacementsP); + *payloadP = json->toJson(exprContextObjectP); *mimeTypeP = "application/json"; // this can be overriden by headers field return true; } -/* **************************************************************************** -* -* removeQuotes - -* -* Entity id and type are special. Different from a attribute, they are always -* strings and cannot take a number, boolean, etc. as value. -*/ -inline std::string removeQuotes(std::string s) -{ - if (s[0] == '"') - { - return s.substr(1, s.size()-2); - } - else - { - return s; - } -} - - /* **************************************************************************** * @@ -237,7 +217,7 @@ static bool setNgsiPayload const Entity& ngsi, const SubscriptionId& subscriptionId, Entity& en, - std::map* replacementsP, + ExprContextObject* exprContextObjectP, const std::vector& attrsFilter, bool blacklist, const std::vector& metadataFilter, @@ -256,7 +236,7 @@ static bool setNgsiPayload else { // If id is not found in the replacements macro, we use en.id. - effectiveId = removeQuotes(smartStringValue(ngsi.id, replacementsP, '"' + en.id + '"')); + effectiveId = removeQuotes(smartStringValue(ngsi.id, exprContextObjectP, '"' + en.id + '"')); } std::string effectiveType; @@ -267,7 +247,7 @@ static bool setNgsiPayload else { // If type is not found in the replacements macro, we use en.type. - effectiveType = removeQuotes(smartStringValue(ngsi.type, replacementsP, '"' + en.type + '"')); + effectiveType = removeQuotes(smartStringValue(ngsi.type, exprContextObjectP, '"' + en.type + '"')); } cer.entity.fill(effectiveId, effectiveType, en.isPattern, en.servicePath); @@ -293,11 +273,11 @@ static bool setNgsiPayload if ((renderFormat == NGSI_V2_SIMPLIFIEDNORMALIZED) || (renderFormat == NGSI_V2_SIMPLIFIEDKEYVALUES)) { - *payloadP = ncr.toJson(renderFormat, attrsFilter, blacklist, metadataFilter, replacementsP); + *payloadP = ncr.toJson(renderFormat, attrsFilter, blacklist, metadataFilter, exprContextObjectP); } else { - *payloadP = ncr.toJson(NGSI_V2_NORMALIZED, attrsFilter, blacklist, metadataFilter, replacementsP); + *payloadP = ncr.toJson(NGSI_V2_NORMALIZED, attrsFilter, blacklist, metadataFilter, exprContextObjectP); } return true; @@ -335,9 +315,40 @@ static SenderThreadParams* buildSenderParamsCustom std::map headers; Entity& en = notifyCerP->entity; +#ifdef EXPR_BASIC + bool basic = true; +#else + bool basic = false; +#endif + // Used by several macroSubstitute() calls along this function - std::map replacements; - buildReplacementsMap(en, tenant, xauthToken, &replacements); + ExprContextObject exprContext(basic); + + // It seems that add() semantics are different in basic and jexl mode. In jexl mode, if the key already exists, it is + // updated (in other words, the last added keys is the one that takes precedence). In basic model, if the key already + // exists, the operation is ignored (in other words, the first added key is the one that takes precedence). Taking + // into account that in the case of an attribute with name "service", "servicePath" or "authToken", it must have precedence + // over the ones comming from headers of the same name, we conditionally add them depending the case + TIME_EXPR_CTXBLD_START(); + exprContext.add("id", en.id); + exprContext.add("type", en.type); + if (!basic) + { + exprContext.add("service", tenant); + exprContext.add("servicePath", en.servicePath); + exprContext.add("authToken", xauthToken); + } + for (unsigned int ix = 0; ix < en.attributeVector.size(); ix++) + { + en.attributeVector[ix]->addToContext(&exprContext, basic); + } + if (basic) + { + exprContext.add("service", tenant); + exprContext.add("servicePath", en.servicePath); + exprContext.add("authToken", xauthToken); + } + TIME_EXPR_CTXBLD_STOP(); // // 1. Verb/Method @@ -363,7 +374,7 @@ static SenderThreadParams* buildSenderParamsCustom // 2. URL // std::string notifUrl = (notification.type == ngsiv2::HttpNotification ? notification.httpInfo.url : notification.mqttInfo.url); - if (macroSubstitute(&url, notifUrl, &replacements, "") == false) + if (macroSubstitute(&url, notifUrl, &exprContext, "", true) == false) { // Warning already logged in macroSubstitute() return NULL; @@ -379,7 +390,7 @@ static SenderThreadParams* buildSenderParamsCustom { bool includePayload = (notification.type == ngsiv2::HttpNotification ? notification.httpInfo.includePayload : notification.mqttInfo.includePayload); std::string notifPayload = (notification.type == ngsiv2::HttpNotification ? notification.httpInfo.payload : notification.mqttInfo.payload); - if (!setPayload(includePayload, notifPayload, subscriptionId, en, &replacements, attrsFilter, blacklist, metadataFilter, &payload, &mimeType, &renderFormat)) + if (!setPayload(includePayload, notifPayload, subscriptionId, en, &exprContext, attrsFilter, blacklist, metadataFilter, &payload, &mimeType, &renderFormat)) { // Warning already logged in macroSubstitute() return NULL; @@ -388,14 +399,14 @@ static SenderThreadParams* buildSenderParamsCustom else if (customPayloadType == ngsiv2::CustomPayloadType::Json) { orion::CompoundValueNode* json = (notification.type == ngsiv2::HttpNotification ? notification.httpInfo.json : notification.mqttInfo.json); - setJsonPayload(json, &replacements, &payload, &mimeType); + setJsonPayload(json, &exprContext, &payload, &mimeType); renderFormat = NGSI_V2_CUSTOM; } else // customPayloadType == ngsiv2::CustomPayloadType::Ngsi { // 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, &replacements, attrsFilter, blacklist, metadataFilter, &payload, renderFormat)) + if (!setNgsiPayload(ngsi, subscriptionId, en, &exprContext, attrsFilter, blacklist, metadataFilter, &payload, renderFormat)) { // Warning already logged in macroSubstitute() return NULL; @@ -414,7 +425,7 @@ static SenderThreadParams* buildSenderParamsCustom std::string key = it->first; std::string value = it->second; - if ((macroSubstitute(&key, it->first, &replacements, "") == false) || (macroSubstitute(&value, it->second, &replacements, "") == false)) + if ((macroSubstitute(&key, it->first, &exprContext, "", true) == false) || (macroSubstitute(&value, it->second, &exprContext, "", true) == false)) { // Warning already logged in macroSubstitute() return NULL; @@ -440,7 +451,7 @@ static SenderThreadParams* buildSenderParamsCustom std::string key = it->first; std::string value = it->second; - if ((macroSubstitute(&key, it->first, &replacements, "") == false) || (macroSubstitute(&value, it->second, &replacements, "") == false)) + if ((macroSubstitute(&key, it->first, &exprContext, "", true) == false) || (macroSubstitute(&value, it->second, &exprContext, "", true) == false)) { // Warning already logged in macroSubstitute() return NULL; @@ -505,7 +516,7 @@ static SenderThreadParams* buildSenderParamsCustom // 8. Topic (only in the case of MQTT notifications) if (notification.type == ngsiv2::MqttNotification) { - if (macroSubstitute(&topic, notification.mqttInfo.topic, &replacements, "") == false) + if (macroSubstitute(&topic, notification.mqttInfo.topic, &exprContext, "", true) == false) { // Warning already logged in macroSubstitute() return NULL; diff --git a/src/lib/parse/CompoundValueNode.cpp b/src/lib/parse/CompoundValueNode.cpp index 454c239383..fdf0fa2008 100644 --- a/src/lib/parse/CompoundValueNode.cpp +++ b/src/lib/parse/CompoundValueNode.cpp @@ -612,7 +612,7 @@ bool CompoundValueNode::equal(const orion::BSONElement& be) * CompoundValueNode:toJson * */ -std::string CompoundValueNode::toJson(std::map* replacementsP) +std::string CompoundValueNode::toJson(ExprContextObject* exprContextObjectP) { std::string out; JsonVectorHelper jvh; @@ -621,7 +621,7 @@ std::string CompoundValueNode::toJson(std::map* replac switch (valueType) { case orion::ValueTypeString: - return smartStringValue(stringValue, replacementsP, "null"); + return smartStringValue(stringValue, exprContextObjectP, "null"); case orion::ValueTypeNumber: return double2string(numberValue); @@ -635,14 +635,14 @@ std::string CompoundValueNode::toJson(std::map* replac case orion::ValueTypeVector: for (unsigned int ix = 0; ix < childV.size(); ix++) { - jvh.addRaw(childV[ix]->toJson(replacementsP)); + jvh.addRaw(childV[ix]->toJson(exprContextObjectP)); } return jvh.str(); case orion::ValueTypeObject: for (unsigned int ix = 0; ix < childV.size(); ix++) { - joh.addRaw(childV[ix]->name, childV[ix]->toJson(replacementsP)); + joh.addRaw(childV[ix]->name, childV[ix]->toJson(exprContextObjectP)); } return joh.str(); @@ -658,6 +658,106 @@ std::string CompoundValueNode::toJson(std::map* replac +/* **************************************************************************** +* +* CompoundValueNode:toExprContextObject +* +*/ +ExprContextObject CompoundValueNode::toExprContextObject(void) +{ + ExprContextObject co; + for (uint64_t ix = 0; ix < childV.size(); ++ix) + { + CompoundValueNode* child = childV[ix]; + switch (child->valueType) + { + case orion::ValueTypeString: + co.add(child->name, child->stringValue); + break; + + case orion::ValueTypeNumber: + co.add(child->name, child->numberValue); + break; + + case orion::ValueTypeBoolean: + co.add(child->name, child->boolValue); + break; + + case orion::ValueTypeNull: + co.add(child->name); + break; + + case orion::ValueTypeVector: + co.add(child->name, child->toExprContextList()); + break; + + case orion::ValueTypeObject: + co.add(child->name, child->toExprContextObject()); + break; + + case orion::ValueTypeNotGiven: + LM_E(("Runtime Error (value type not given (%s))", name.c_str())); + break; + + default: + LM_E(("Runtime Error (value type unknown (%s))", name.c_str())); + } + } + return co; +} + + + +/* **************************************************************************** +* +* CompoundValueNode:toExprContextList +* +*/ +ExprContextList CompoundValueNode::toExprContextList(void) +{ + ExprContextList cl; + for (uint64_t ix = 0; ix < childV.size(); ++ix) + { + CompoundValueNode* child = childV[ix]; + switch (child->valueType) + { + case orion::ValueTypeString: + cl.add(child->stringValue); + break; + + case orion::ValueTypeNumber: + cl.add(child->numberValue); + break; + + case orion::ValueTypeBoolean: + cl.add(child->boolValue); + break; + + case orion::ValueTypeNull: + cl.add(); + break; + + case orion::ValueTypeVector: + cl.add(child->toExprContextList()); + break; + + case orion::ValueTypeObject: + cl.add(child->toExprContextObject()); + break; + + case orion::ValueTypeNotGiven: + LM_E(("Runtime Error (value type not given)")); + break; + + default: + LM_E(("Runtime Error (value type unknown)")); + } + } + return cl; +} + + + /* **************************************************************************** * * clone - diff --git a/src/lib/parse/CompoundValueNode.h b/src/lib/parse/CompoundValueNode.h index b46aa2c461..bd15aefe64 100644 --- a/src/lib/parse/CompoundValueNode.h +++ b/src/lib/parse/CompoundValueNode.h @@ -35,6 +35,8 @@ #include "mongoDriver/BSONElement.h" +#include "expressions/ExprContext.h" + namespace orion { @@ -117,7 +119,10 @@ class CompoundValueNode bool equal(const orion::BSONElement& be); std::string finish(void); - std::string toJson(std::map* replacementsP = NULL); + std::string toJson(ExprContextObject* exprContextObjectP = NULL); + + ExprContextObject toExprContextObject(void); + ExprContextList toExprContextList(void); void shortShow(const std::string& indent); void show(const std::string& indent); diff --git a/src/lib/rest/rest.cpp b/src/lib/rest/rest.cpp index cc35ff57fd..f0ce3fc4c0 100644 --- a/src/lib/rest/rest.cpp +++ b/src/lib/rest/rest.cpp @@ -618,6 +618,10 @@ static void requestCompleted clock_addtime(&accTimeStat.mongoWriteWaitTime, &threadLastTimeStat.mongoWriteWaitTime); clock_addtime(&accTimeStat.mongoReadWaitTime, &threadLastTimeStat.mongoReadWaitTime); clock_addtime(&accTimeStat.mongoCommandWaitTime, &threadLastTimeStat.mongoCommandWaitTime); + clock_addtime(&accTimeStat.exprBasicCtxBldTime, &threadLastTimeStat.exprBasicCtxBldTime); + clock_addtime(&accTimeStat.exprBasicEvalTime, &threadLastTimeStat.exprBasicEvalTime); + clock_addtime(&accTimeStat.exprJexlCtxBldTime, &threadLastTimeStat.exprJexlCtxBldTime); + clock_addtime(&accTimeStat.exprJexlEvalTime, &threadLastTimeStat.exprJexlEvalTime); clock_addtime(&accTimeStat.renderTime, &threadLastTimeStat.renderTime); clock_addtime(&accTimeStat.reqTime, &threadLastTimeStat.reqTime); diff --git a/src/lib/serviceRoutines/versionTreat.cpp b/src/lib/serviceRoutines/versionTreat.cpp index f4b070f378..1b71e28125 100644 --- a/src/lib/serviceRoutines/versionTreat.cpp +++ b/src/lib/serviceRoutines/versionTreat.cpp @@ -47,6 +47,13 @@ #include #include +#ifndef EXPR_BASIC +// Interface to use libcjexl +extern "C" { + const char* cjexl_version(); +} +#endif + /* **************************************************************************** * * version - @@ -70,6 +77,7 @@ std::string libVersions(void) std::string mhd = " \"libmicrohttpd\": "; std::string ssl = " \"openssl\": "; std::string rjson = " \"rapidjson\": "; + std::string cjexl = " \"libcjexl\": "; std::string mongo = " \"mongoc\": "; std::string bson = " \"bson\": "; @@ -88,6 +96,9 @@ std::string libVersions(void) total += curl + "\"" + curlVersion + "\"" + ",\n"; total += mosq + "\"" + mosqVersion + "\"" + ",\n"; total += mhd + "\"" + MHD_get_version() + "\"" + ",\n"; +#ifndef EXPR_BASIC + total += cjexl + "\"" + cjexl_version() + "\"" + ",\n"; +#endif #ifdef OLD_SSL_VERSION_FORMAT // Needed by openssl 1.1.1n in Debian 11 and before total += ssl + "\"" + SHLIB_VERSION_NUMBER "\"" + ",\n"; diff --git a/test/functionalTest/cases/0000_cli/version.test b/test/functionalTest/cases/0000_cli/version.test index bac1621329..8d034ea9f2 100644 --- a/test/functionalTest/cases/0000_cli/version.test +++ b/test/functionalTest/cases/0000_cli/version.test @@ -30,7 +30,7 @@ broker version contextBroker --version --REGEXPECT-- -REGEX(\d+\.\d+\.\d+.* \(git version: .*\)) +REGEX(\d+\.\d+\.\d+.* \(git version: .*\) flavours:.*) Copyright 2013-2024 Telefonica Investigacion y Desarrollo, S.A.U 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 diff --git a/test/functionalTest/cases/2015_notification_templates/notification_templates.test b/test/functionalTest/cases/2015_notification_templates/notification_templates.test index 23df76a2fe..705c99c8bc 100644 --- a/test/functionalTest/cases/2015_notification_templates/notification_templates.test +++ b/test/functionalTest/cases/2015_notification_templates/notification_templates.test @@ -539,7 +539,7 @@ Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234567]) POST http://127.0.0.1:REGEX(\d+)/notify?a1=a1&a2=a2&id=E1&step=07&type=Thing Fiware-Servicepath: / Entity-Id: E1 -Content-Length: 36 +Content-Length: 40 User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) Step: 07 Ngsiv2-Attrsformat: custom @@ -554,7 +554,7 @@ Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234567]) { "A1": "a1", "A2": "a2", - "A4": "" + "A4": "null" } ======================================= #SORT_END diff --git a/test/functionalTest/cases/2015_notification_templates/notification_templates_many_notifications.test b/test/functionalTest/cases/2015_notification_templates/notification_templates_many_notifications.test index 67f39b8667..4ffa8c252e 100644 --- a/test/functionalTest/cases/2015_notification_templates/notification_templates_many_notifications.test +++ b/test/functionalTest/cases/2015_notification_templates/notification_templates_many_notifications.test @@ -280,7 +280,7 @@ Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[123]) PUT http://127.0.0.1:REGEX(\d+)/notify?a1=true&id=E1&type=T1 Fiware-Servicepath: / Entity-Id: E1 -Content-Length: 36 +Content-Length: 44 User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) Ngsiv2-Attrsformat: custom Host: 127.0.0.1:REGEX(\d+) @@ -292,8 +292,8 @@ Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[123]) { "A1": "true", - "A2": "", - "A3": "" + "A2": "null", + "A3": "null" } #SORT_END #SORT_START @@ -348,23 +348,22 @@ Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[123]) "subscriptionId": "REGEX([0-9a-f]{24})" } ======================================= -PUT http://127.0.0.1:REGEX(\d+)/notify?a2=null&id=E1&type=T2 +PUT http://127.0.0.1:REGEX(\d+)/notify?id=E1&type=T2 Fiware-Servicepath: / Entity-Id: E1 -Content-Length: 36 +Content-Length: 44 User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) Ngsiv2-Attrsformat: custom Host: 127.0.0.1:REGEX(\d+) Accept: application/json -A2: null Entity-Type: T2 Content-Type: text/plain; charset=utf-8 Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[123]) { - "A1": "", + "A1": "null", "A2": "null", - "A3": "" + "A3": "null" } #SORT_END #SORT_START @@ -423,7 +422,7 @@ PUT http://127.0.0.1:REGEX(\d+)/notify?a3=3&id=E1&type=T3 Fiware-Servicepath: / A3: 3 Entity-Id: E1 -Content-Length: 33 +Content-Length: 41 User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) Ngsiv2-Attrsformat: custom Host: 127.0.0.1:REGEX(\d+) @@ -433,8 +432,8 @@ Content-Type: text/plain; charset=utf-8 Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[123]) { - "A1": "", - "A2": "", + "A1": "null", + "A2": "null", "A3": "3" } ======================================= diff --git a/test/functionalTest/cases/2560_custom_notification_json/custom_notification_mqtt_json_constants.test b/test/functionalTest/cases/2560_custom_notification_json/custom_notification_mqtt_json_constants.test index d8fe2d436c..9428ed0e64 100644 --- a/test/functionalTest/cases/2560_custom_notification_json/custom_notification_mqtt_json_constants.test +++ b/test/functionalTest/cases/2560_custom_notification_json/custom_notification_mqtt_json_constants.test @@ -223,7 +223,7 @@ Content-Length: 946 "y1", "y2" ] - }, + }, "text": "foo" }, "qos": 0, diff --git a/test/functionalTest/cases/3693_covered_notifications_in_subscriptions/covered_custom_notification.test b/test/functionalTest/cases/3693_covered_notifications_in_subscriptions/covered_custom_notification.test index 460ffe731a..14fffa6e0f 100644 --- a/test/functionalTest/cases/3693_covered_notifications_in_subscriptions/covered_custom_notification.test +++ b/test/functionalTest/cases/3693_covered_notifications_in_subscriptions/covered_custom_notification.test @@ -36,7 +36,7 @@ accumulatorStart # 03. Create E1 with attribute A1=1 # 04. Dump & reset, see notifications A1=1 and A2=null in custom payload # 05. Create E2 with attribute A1=1 -# 06. Dump & reset, see notifications A1=1 and A2= in custom payload (invalid JSON) +# 06. Dump & reset, see notifications A1=1 and A2=null in custom payload # echo "01. Create covered custom subscriptions for E1 covering attributes A1 and A2" @@ -131,8 +131,8 @@ echo echo -echo "06. Dump & reset, see notifications A1=1 and A2= in custom payload (invalid JSON)" -echo "========================================================================================" +echo "06. Dump & reset, see notifications A1=1 and A2=null in custom payload" +echo "======================================================================" accumulatorDump accumulatorReset echo @@ -195,11 +195,11 @@ Content-Length: 0 -06. Dump & reset, see notifications A1=1 and A2= in custom payload (invalid JSON) -======================================================================================== +06. Dump & reset, see notifications A1=1 and A2=null in custom payload +====================================================================== POST http://127.0.0.1:REGEX(\d+)/notify Fiware-Servicepath: / -Content-Length: 31 +Content-Length: 35 User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) Ngsiv2-Attrsformat: custom Host: 127.0.0.1:REGEX(\d+) @@ -207,7 +207,7 @@ Accept: application/json Content-Type: application/json; charset=utf-8 Fiware-Correlator: REGEX([0-9a-f\-]{36}); cbnotif=1 -{ "A1-value": 1, "A2-value": }======================================= +{ "A1-value": 1, "A2-value": null }======================================= --TEARDOWN-- diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_array_filtering.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_array_filtering.test new file mode 100644 index 0000000000..7762f53a9a --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_array_filtering.test @@ -0,0 +1,309 @@ +# 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-- +JEXL expression in custom notification (array filtering) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression sum: a[.b<5] +# 02. Create entity E1 with a=[{b:1},{b:34},{b:4}] +# 03. Update entity E1 with a=[{c:1},{b:-1},{b:2}] +# 04. Update entity E1 with a=[{b:-1},{b:foo}] +# 05. Update entity E1 with a=[{b:-1, c:1},{b:20, d:3}] +# 06. Dump accumulator and see notifications (cal=[{b:1},{b:4}], cal=[{b:-1},{b:2}], cal=[{b:-1}], cal=[{b:-1, c:1}]) +# + + +echo "01. Create custom sub with custom expression sum: a[.b<5]" +echo "=========================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "cal": { + "value": "${a[.b<5]}", + "type": "Calculated" + } + } + }, + "attrs": [ "cal" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with a=[{b:1},{b:34},{b:4}]" +echo "================================================" +payload='{ + "id": "E1", + "type": "T", + "a": { + "value": [{"b": 1}, {"b": 34}, {"b": 4}], + "type": "Array" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E1 with a=[{c:1},{b:-1},{b:2}]" +echo "================================================" +payload='{ + "a": { + "value": [{"c": 1}, {"b": -1}, {"b": 2}], + "type": "Text" + } +}' +orionCurl --url /v2/entities/E1/attrs -X POST --payload "$payload" +echo +echo + + +echo "04. Update entity E1 with a=[{b:-1},{b:foo}]" +echo "============================================" +payload='{ + "a": { + "value": [{"b": -1}, {"b": "foo"}], + "type": "Text" + } +}' +orionCurl --url /v2/entities/E1/attrs -X POST --payload "$payload" +echo +echo + + +echo "05. Update entity E1 with a=[{b:-1, c:1},{b:20, d:3}]" +echo "=====================================================" +payload='{ + "a": { + "value": [{"b": -1, "c": 1}, {"b": 20, "d": 3}], + "type": "Text" + } +}' +orionCurl --url /v2/entities/E1/attrs -X POST --payload "$payload" +echo +echo + + +echo "06. Dump accumulator and see notifications (cal=[{b:1},{b:4}], cal=[{b:-1},{b:2}], cal=[{b:-1}], cal=[{b:-1, c:1}])" +echo "===================================================================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression sum: a[.b<5] +========================================================= +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=[{b:1},{b:34},{b:4}] +================================================ +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=[{c:1},{b:-1},{b:2}] +================================================ +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +04. Update entity E1 with a=[{b:-1},{b:foo}] +============================================ +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +05. Update entity E1 with a=[{b:-1, c:1},{b:20, d:3}] +===================================================== +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +06. Dump accumulator and see notifications (cal=[{b:1},{b:4}], cal=[{b:-1},{b:2}], cal=[{b:-1}], cal=[{b:-1, c:1}]) +=================================================================================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 145 +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": [ + { + "cal": { + "metadata": {}, + "type": "Calculated", + "value": [ + { + "b": 1 + }, + { + "b": 4 + } + ] + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 146 +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": [ + { + "cal": { + "metadata": {}, + "type": "Calculated", + "value": [ + { + "b": -1 + }, + { + "b": 2 + } + ] + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 138 +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": [ + { + "cal": { + "metadata": {}, + "type": "Calculated", + "value": [ + { + "b": -1 + } + ] + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 144 +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": [ + { + "cal": { + "metadata": {}, + "type": "Calculated", + "value": [ + { + "b": -1, + "c": 1 + } + ] + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_basic_attrs_in_update.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_basic_attrs_in_update.test new file mode 100644 index 0000000000..3bc2121392 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_basic_attrs_in_update.test @@ -0,0 +1,280 @@ +# 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-- +JEXL basic expression in custom notification (source attributes in udpate) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression sum: A+B +# 02. Create entity E1 with A=1 and B=2 +# 03. Update entity E1 with A=foo and B=bar +# 04. Update entity E1 with A=2.1 and B=-3.8 +# 05. Dump accumulator and see three notifications (sum: 3, sum: foobar, sum: -1.7) +# + + +echo "01. Create custom sub with custom expression sum: A+B" +echo "=====================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "sum": { + "value": "${A+B}", + "type": "Calculated" + } + } + } + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A=1 and B=2" +echo "=====================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": 1, + "type": "Number" + }, + "B": { + "value": 2, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E1 with A=foo and B=bar" +echo "=========================================" +payload='{ + "A": { + "value": "foo", + "type": "Text" + }, + "B": { + "value": "bar", + "type": "Text" + } +}' +orionCurl --url /v2/entities/E1/attrs -X PATCH --payload "$payload" +echo +echo + + +echo "04. Update entity E1 with A=2.1 and B=-3.8" +echo "==========================================" +payload='{ + "A": { + "value": 2.1, + "type": "Number" + }, + "B": { + "value": -3.8, + "type": "Number" + } +}' +orionCurl --url /v2/entities/E1/attrs -X PATCH --payload "$payload" +echo +echo + + +echo "05. Dump accumulator and see three notifications (sum: 3, sum: foobar, sum: -1.7)" +echo "=================================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression sum: A+B +===================================================== +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 and B=2 +===================================== +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=foo and B=bar +========================================= +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +04. Update entity E1 with A=2.1 and B=-3.8 +========================================== +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +05. Dump accumulator and see three notifications (sum: 3, sum: foobar, sum: -1.7) +================================================================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 221 +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": [ + { + "A": { + "metadata": {}, + "type": "Number", + "value": 1 + }, + "B": { + "metadata": {}, + "type": "Number", + "value": 2 + }, + "id": "E1", + "sum": { + "metadata": {}, + "type": "Calculated", + "value": 3 + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 232 +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": [ + { + "A": { + "metadata": {}, + "type": "Text", + "value": "foo" + }, + "B": { + "metadata": {}, + "type": "Text", + "value": "bar" + }, + "id": "E1", + "sum": { + "metadata": {}, + "type": "Calculated", + "value": "foobar" + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 229 +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": [ + { + "A": { + "metadata": {}, + "type": "Number", + "value": 2.1 + }, + "B": { + "metadata": {}, + "type": "Number", + "value": -3.8 + }, + "id": "E1", + "sum": { + "metadata": {}, + "type": "Calculated", + "value": -1.7 + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_basic_attrs_not_in_update.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_basic_attrs_not_in_update.test new file mode 100644 index 0000000000..7a284c23aa --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_basic_attrs_not_in_update.test @@ -0,0 +1,174 @@ +# 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-- +JEXL basic expression in custom notification (source attributes not in udpate) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression sum: A+B +# 02. Create entity E1 with A=1 and B=2 +# 03. Update entity E1 with C=foo to trigger notification +# 04. Dump accumulator and see notifications (sum: 3) +# + + +echo "01. Create custom sub with custom expression sum: A+B" +echo "=====================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ], + "condition": { + "attrs": [ "C" ] + } + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "sum": { + "value": "${A+B}", + "type": "Calculated" + } + } + }, + "attrs": [ "sum" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A=1 and B=2" +echo "=====================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": 1, + "type": "Number" + }, + "B": { + "value": 2, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E1 with C=foo to trigger notification" +echo "=======================================================" +payload='{ + "C": { + "value": "foo", + "type": "Text" + } +}' +orionCurl --url /v2/entities/E1/attrs -X POST --payload "$payload" +echo +echo + + +echo "04. Dump accumulator and see notifications (sum: 3)" +echo "===================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression sum: A+B +===================================================== +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 and B=2 +===================================== +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 C=foo to trigger notification +======================================================= +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +04. Dump accumulator and see notifications (sum: 3) +=================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 129 +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": [ + { + "id": "E1", + "sum": { + "metadata": {}, + "type": "Calculated", + "value": 3 + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_expr_attrs_weird_syntax.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_expr_attrs_weird_syntax.test new file mode 100644 index 0000000000..862f671a54 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_expr_attrs_weird_syntax.test @@ -0,0 +1,214 @@ +# 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-- +JEXL expression using attributes with weird syntax + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression: A-B and A:B +# 02. Create entity E1 with A-B 1 and A:B 2 +# 03. Update entity E1 with A-B 3 and A:B 4 +# 04. Dump accumulator and see notifications (R1 null, R2 null and R1 null, R2 null) +# + + +echo "01. Create custom sub with custom expression: A-B and A:B" +echo "=========================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "R1": { + "value": "${A-B}", + "type": "Calculated" + }, + "R2": { + "value": "${A:B}", + "type": "Calculated" + } + } + }, + "attrs": [ "R1", "R2" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A-B 1 and A:B 2" +echo "=========================================" +payload='{ + "id": "E1", + "type": "T", + "A-B": { + "value": 1, + "type": "Number" + }, + "A:B": { + "value": 2, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E1 with A-B 3 and A:B 4" +echo "=========================================" +payload='{ + "A-B": { + "value": 3, + "type": "Number" + }, + "A:B": { + "value": 4, + "type": "Number" + } +}' +orionCurl --url /v2/entities/E1/attrs -X PATCH --payload "$payload" +echo +echo + + +echo "04. Dump accumulator and see notifications (R1 null, R2 null and R1 null, R2 null)" +echo "==================================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression: A-B and A:B +========================================================= +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-B 1 and A:B 2 +========================================= +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-B 3 and A:B 4 +========================================= +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +04. Dump accumulator and see notifications (R1 null, R2 null and R1 null, R2 null) +================================================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 185 +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": [ + { + "R1": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "R2": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 185 +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": [ + { + "R1": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "R2": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_json_navigation.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_json_navigation.test new file mode 100644 index 0000000000..f286e7f2c8 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_json_navigation.test @@ -0,0 +1,200 @@ +# 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-- +JEXL expression in custom notification (JSON navigation in objects and arrays) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression cal: A[0] + A[1].A1 + B.B1[2].C +# 02. Create entity E1 with A and B so cal result in 60 +# 03. Update entity E1 with A and B so cal result in 6 +# 04. Dump accumulator and see notifications (cal 60 and cal 6) +# + + +echo "01. Create custom sub with custom expression cal: A[0] + A[1].A1 + B.B1[2].C" +echo "============================================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "cal": { + "value": "${A[0] + A[1].A1 + B.B1[2].C}", + "type": "Calculated" + } + } + }, + "attrs": [ "cal" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A and B so cal result in 60" +echo "=====================================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": [ 10, { "A1": 20 } ], + "type": "StructuredValue" + }, + "B": { + "value": { "B1": [ 11, 22, { "C": 30 }]}, + "type": "StructuredValue" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E1 with A and B so cal result in 6" +echo "====================================================" +payload='{ + "A": { + "value": [ 1, { "A1": 2 } ], + "type": "StructuredValue" + }, + "B": { + "value": { "B1": [ 11, 22, { "C": 3 }]}, + "type": "StructuredValue" + } +}' +orionCurl --url /v2/entities/E1/attrs -X PATCH --payload "$payload" +echo +echo + + +echo "04. Dump accumulator and see notifications (cal 60 and cal 6)" +echo "=============================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression cal: A[0] + A[1].A1 + B.B1[2].C +============================================================================ +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 and B so cal result in 60 +===================================================== +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 and B so cal result in 6 +==================================================== +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +04. Dump accumulator and see notifications (cal 60 and cal 6) +============================================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 130 +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": [ + { + "cal": { + "metadata": {}, + "type": "Calculated", + "value": 60 + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 129 +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": [ + { + "cal": { + "metadata": {}, + "type": "Calculated", + "value": 6 + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_missing_attribute_as_null.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_missing_attribute_as_null.test new file mode 100644 index 0000000000..d90db1f687 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_missing_attribute_as_null.test @@ -0,0 +1,343 @@ +# 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-- +JEXL expression in custom notification (missing attribute replaced as null) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression B0: (A==null)?-1:A, B1: A||'is null', B2: A||-1, B3: A||0-1||, B4: A||('-1'|parseInt), B5: A||0 +# 02. Create entity E1 with A=1 +# 03. Create entity E2 with A=null +# 04. Create entity E3 without A +# 05. Dump accumulator and see three notifications (<1, 1, null, 1, 1, 1>, <'is null', 'is null', null, -1, -1, 0>, ) +# + + +echo "01. Create custom sub with custom expression B0: (A==null)?-1:A, B1: A||'is null', B2: A||-1, B3: A||0-1||, B4: A||'-1'||parseInt, B5: A||0" +echo "===========================================================================================================================================" +# NOTE: '\'' is the way of scaping a ' in the payload variable below (see https://stackoverflow.com/a/1250279/1485926) +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "B0": { + "value": "${(A==null)?'\''is null'\'':A}", + "type": "Calculated" + }, + "B1": { + "value": "${A||'\''is null'\''}", + "type": "Calculated" + }, + "B2": { + "value": "${A||-1}", + "type": "Calculated" + }, + "B3": { + "value": "${A||0-1}", + "type": "Calculated" + }, + "B4": { + "value": "${A||('\''-1'\''|parseInt)}", + "type": "Calculated" + }, + "B5": { + "value": "${A||0}", + "type": "Calculated" + } + } + }, + "attrs": [ "B0", "B1", "B2", "B3", "B4", "B5" ] + } +}' +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. Create entity E2 with A=null" +echo "================================" +payload='{ + "id": "E2", + "type": "T", + "A": { + "value": null, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "04. Create entity E3 without A" +echo "==============================" +payload='{ + "id": "E3", + "type": "T", + "C": { + "value": 2, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "05. Dump accumulator and see three notifications (<1, 1, null, 1, 1, 1>, <'is null', 'is null', null, -1, -1, 0>, )" +echo "=====================================================================================================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression B0: (A==null)?-1:A, B1: A||'is null', B2: A||-1, B3: A||0-1||, B4: A||'-1'||parseInt, B5: A||0 +=========================================================================================================================================== +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. Create entity E2 with A=null +================================ +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E2?type=T +Content-Length: 0 + + + +04. Create entity E3 without A +============================== +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E3?type=T +Content-Length: 0 + + + +05. Dump accumulator and see three notifications (<1, 1, null, 1, 1, 1>, <'is null', 'is null', null, -1, -1, 0>, ) +===================================================================================================================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 386 +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": [ + { + "B0": { + "metadata": {}, + "type": "Calculated", + "value": 1 + }, + "B1": { + "metadata": {}, + "type": "Calculated", + "value": 1 + }, + "B2": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "B3": { + "metadata": {}, + "type": "Calculated", + "value": 1 + }, + "B4": { + "metadata": {}, + "type": "Calculated", + "value": 1 + }, + "B5": { + "metadata": {}, + "type": "Calculated", + "value": 1 + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 404 +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": [ + { + "B0": { + "metadata": {}, + "type": "Calculated", + "value": "is null" + }, + "B1": { + "metadata": {}, + "type": "Calculated", + "value": "is null" + }, + "B2": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "B3": { + "metadata": {}, + "type": "Calculated", + "value": -1 + }, + "B4": { + "metadata": {}, + "type": "Calculated", + "value": -1 + }, + "B5": { + "metadata": {}, + "type": "Calculated", + "value": 0 + }, + "id": "E2", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 399 +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": [ + { + "B0": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "B1": { + "metadata": {}, + "type": "Calculated", + "value": "is null" + }, + "B2": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "B3": { + "metadata": {}, + "type": "Calculated", + "value": -1 + }, + "B4": { + "metadata": {}, + "type": "Calculated", + "value": -1 + }, + "B5": { + "metadata": {}, + "type": "Calculated", + "value": 0 + }, + "id": "E3", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_missing_attribute_as_null2.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_missing_attribute_as_null2.test new file mode 100644 index 0000000000..2d8d503914 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_missing_attribute_as_null2.test @@ -0,0 +1,251 @@ +# 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-- +JEXL expression in custom notification (missing attribute replaced as null additional case) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression S: (A||0-1)+(B||0-2) +# 02. Create entity E1 with A=1, B=2 +# 03. Create entity E2 with B=1 +# 04. Create entity E3 without A or B +# 05. Dump accumulator and see three notifications (S: 3, S: 0, S: -3) +# + + +echo "01. Create custom sub with custom expression S: (A||0-1)+(B||0-2)" +echo "=================================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "S": { + "value": "${(A||0-1)+(B||0-2)}", + "type": "Calculated" + } + } + }, + "attrs": [ "S" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A=1, B=2" +echo "==================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": 1, + "type": "Number" + }, + "B": { + "value": 2, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Create entity E2 with B=1" +echo "=============================" +payload='{ + "id": "E2", + "type": "T", + "B": { + "value": 1, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "04. Create entity E3 without A or B" +echo "===================================" +payload='{ + "id": "E3", + "type": "T", + "C": { + "value": 2, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "05. Dump accumulator and see three notifications (S: 3, S: 0, S: -3)" +echo "====================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression S: (A||0-1)+(B||0-2) +================================================================= +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, B=2 +================================== +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E1?type=T +Content-Length: 0 + + + +03. Create entity E2 with B=1 +============================= +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E2?type=T +Content-Length: 0 + + + +04. Create entity E3 without A or B +=================================== +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E3?type=T +Content-Length: 0 + + + +05. Dump accumulator and see three notifications (S: 3, S: 0, S: -3) +==================================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 127 +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": [ + { + "S": { + "metadata": {}, + "type": "Calculated", + "value": 3 + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 127 +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": [ + { + "S": { + "metadata": {}, + "type": "Calculated", + "value": 0 + }, + "id": "E2", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 128 +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": [ + { + "S": { + "metadata": {}, + "type": "Calculated", + "value": -3 + }, + "id": "E3", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_not_defined_attrs.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_not_defined_attrs.test new file mode 100644 index 0000000000..3af9b1bdef --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_not_defined_attrs.test @@ -0,0 +1,192 @@ +# 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-- +JEXL expression in custom notification (not defined attributes) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression cal: A+1 +# 02. Create entity E1 with B=1 +# 03. Update entity E1 with B=2 +# 04. Dump accumulator and see notifications (cal: null, cal: null) +# + + +echo "01. Create custom sub with custom expression cal: A+1" +echo "=====================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "cal": { + "value": "${A+1}", + "type": "Calculated" + } + } + }, + "attrs": [ "cal" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with B=1" +echo "=============================" +payload='{ + "id": "E1", + "type": "T", + "B": { + "value": 1, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E1 with B=2" +echo "=============================" +payload='{ + "B": { + "value": 2, + "type": "Number" + } +}' +orionCurl --url /v2/entities/E1/attrs -X POST --payload "$payload" +echo +echo + + +echo "04. Dump accumulator and see notifications (cal: null, cal: null)" +echo "=================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression cal: A+1 +===================================================== +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 B=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 B=2 +============================= +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +04. Dump accumulator and see notifications (cal: null, cal: null) +================================================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 132 +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": [ + { + "cal": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 132 +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": [ + { + "cal": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_null_values.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_null_values.test new file mode 100644 index 0000000000..070ab89ce8 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_null_values.test @@ -0,0 +1,192 @@ +# 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-- +JEXL expression in custom notification (null values) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression cal: A+1 +# 02. Create entity E1 with A=null +# 03. Update entity E1 with A=null again (with forcedUpdate) +# 04. Dump accumulator and see notifications (cal: null, cal: null) +# + + +echo "01. Create custom sub with custom expression cal: A+1" +echo "=====================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "cal": { + "value": "${A+1}", + "type": "Calculated" + } + } + }, + "attrs": [ "cal" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A=null" +echo "================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": null, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E1 with A=null again (with forcedUpdate)" +echo "==========================================================" +payload='{ + "A": { + "value": null, + "type": "Text" + } +}' +orionCurl --url /v2/entities/E1/attrs?options=forcedUpdate -X POST --payload "$payload" +echo +echo + + +echo "04. Dump accumulator and see notifications (cal: null, cal: null)" +echo "=================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression cal: A+1 +===================================================== +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=null +================================ +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=null again (with forcedUpdate) +========================================================== +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +04. Dump accumulator and see notifications (cal: null, cal: null) +================================================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 132 +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": [ + { + "cal": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 132 +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": [ + { + "cal": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_null_values_all_cases.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_null_values_all_cases.test new file mode 100644 index 0000000000..aba513b32e --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_null_values_all_cases.test @@ -0,0 +1,282 @@ +# 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-- +Expression in custom notification (null values in all cases with JEXL expression fail) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart + +--SHELL-- + +# +# 01. Create custom sub using payload: "${A|patata}" +# 02. Create custom sub using payload: "X:${A|patata}:Y" +# 03. Create custom sub using json: "${A|patata}" and "X:${A|patata}:Y" +# 04. Create custom sub using ngsi: "${A|patata}" and "X:${A|patata}:Y" +# 05. Create entity E1 with A=1 +# 06. Dump & reset accumulator and see 4 notifications +# + + +echo '01. Create custom sub using payload: "${A|patata}"' +echo "==================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "headers": { + "content-type": "text/plain" + }, + "payload": "${A|patata}" + } + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '02. Create custom sub using payload: "X:${A|patata}:Y"' +echo "======================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "headers": { + "content-type": "text/plain" + }, + "payload": "X:${A|patata}:Y" + } + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '03. Create custom sub using json: "${A|patata}" and "X:${A|patata}:Y"' +echo "=====================================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "json": { + "B1": "${A|patata}", + "B2": "X:${A|patata}:Y" + } + } + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '04. Create custom sub with using ngsi: "${A|patata}" and "X:${A|patata}:Y"' +echo "==========================================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "B1": { + "value": "${A|patata}", + "type": "Calculated" + }, + "B2": { + "value": "X:${A|patata}:Y", + "type": "Calculated" + } + } + }, + "attrs": [ "B1", "B2" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "05. 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 "06. Dump & reset accumulator and see 4 notifications" +echo "====================================================" +accumulatorDump +accumulatorReset +echo +echo + + +--REGEXPECT-- +01. Create custom sub using payload: "${A|patata}" +================================================== +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 custom sub using payload: "X:${A|patata}:Y" +====================================================== +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 + + + +03. Create custom sub using json: "${A|patata}" and "X:${A|patata}:Y" +===================================================================== +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 + + + +04. Create custom sub with using ngsi: "${A|patata}" and "X:${A|patata}:Y" +========================================================================== +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 + + + +05. 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 + + + +06. Dump & reset accumulator and see 4 notifications +==================================================== +#SORT_START +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 4 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +null======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 8 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +X:null:Y======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 27 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +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=[1234]) + +{"B1":null,"B2":"X:null:Y"}======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 191 +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=[1234]) + +{"subscriptionId":"REGEX([0-9a-f]{24})","data":[{"id":"E1","type":"T","B1":{"type":"Calculated","value":null,"metadata":{}},"B2":{"type":"Calculated","value":"X:null:Y","metadata":{}}}]}======================================= +#SORT_END + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_null_values_all_cases_covered_sub.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_null_values_all_cases_covered_sub.test new file mode 100644 index 0000000000..6cf375ceae --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_null_values_all_cases_covered_sub.test @@ -0,0 +1,289 @@ +# 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-- +Expression in custom notification (null values in all cases with JEXL expression fail with covered subs) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart + +--SHELL-- + +# +# 01. Create custom sub with covered using payload: "${A|patata}" +# 02. Create custom sub with covered using payload: "X:${A|patata}:Y" +# 03. Create custom sub with covered using json: "${A|patata}" and "X:${A|patata}:Y" +# 04. Create custom sub with covered using ngsi: "${A|patata}" and "X:${A|patata}:Y" +# 05. Create entity E1 with A=1 +# 06. Dump & reset accumulator and see 4 notifications +# + + +echo '01. Create custom sub with covered using payload: "${A|patata}"' +echo "===============================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "headers": { + "content-type": "text/plain" + }, + "payload": "${A|patata}" + }, + "covered": true, + "attrs": [ "A" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '02. Create custom sub with covered using payload: "X:${A|patata}:Y"' +echo "===================================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "headers": { + "content-type": "text/plain" + }, + "payload": "X:${A|patata}:Y" + }, + "covered": true, + "attrs": [ "A" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '03. Create custom sub with covered using json: "${A|patata}" and "X:${A|patata}:Y"' +echo "==================================================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "json": { + "B1": "${A|patata}", + "B2": "X:${A|patata}:Y" + } + }, + "covered": true, + "attrs": [ "A" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '04. Create custom sub with covered using ngsi: "${A|patata}" and "X:${A|patata}:Y"' +echo "==================================================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "B1": { + "value": "${A|patata}", + "type": "Calculated" + }, + "B2": { + "value": "X:${A|patata}:Y", + "type": "Calculated" + } + } + }, + "attrs": [ "B1", "B2" ], + "covered": true + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "05. 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 "06. Dump & reset accumulator and see 4 notifications" +echo "====================================================" +accumulatorDump +accumulatorReset +echo +echo + + +--REGEXPECT-- +01. Create custom sub with covered using payload: "${A|patata}" +=============================================================== +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 custom sub with covered using payload: "X:${A|patata}:Y" +=================================================================== +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 + + + +03. Create custom sub with covered using json: "${A|patata}" and "X:${A|patata}:Y" +================================================================================== +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 + + + +04. Create custom sub with covered using ngsi: "${A|patata}" and "X:${A|patata}:Y" +================================================================================== +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 + + + +05. 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 + + + +06. Dump & reset accumulator and see 4 notifications +==================================================== +#SORT_START +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 4 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +null======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 8 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +X:null:Y======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 27 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +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=[1234]) + +{"B1":null,"B2":"X:null:Y"}======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 191 +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=[1234]) + +{"subscriptionId":"REGEX([0-9a-f]{24})","data":[{"id":"E1","type":"T","B1":{"type":"Calculated","value":null,"metadata":{}},"B2":{"type":"Calculated","value":"X:null:Y","metadata":{}}}]}======================================= +#SORT_END + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_several_expressions.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_several_expressions.test new file mode 100644 index 0000000000..b1b06e070a --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_several_expressions.test @@ -0,0 +1,233 @@ +# 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-- +JEXL expression in custom notification (several expressions in same subscription) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression sum: A+B, mul: A*B +# 02. Create entity E1 with A=2 and B=3 +# 03. Update entity E1 with A=2.1 and B=-3.8 +# 04. Dump accumulator and see two notifications ({sum: 5, mul: 6}, {sum: -1.7, mul: -7.98}) +# + + +echo "01. Create custom sub with custom expression sum: A+B, mul: A*B" +echo "===============================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "sum": { + "value": "${A+B}", + "type": "Calculated" + }, + "mul": { + "value": "${A*B}", + "type": "Calculated" + } + } + } + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A=2 and B=3" +echo "=====================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": 2, + "type": "Number" + }, + "B": { + "value": 3, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E1 with A=2.1 and B=-3.8" +echo "==========================================" +payload='{ + "A": { + "value": 2.1, + "type": "Number" + }, + "B": { + "value": -3.8, + "type": "Number" + } +}' +orionCurl --url /v2/entities/E1/attrs -X PATCH --payload "$payload" +echo +echo + + +echo "Dump accumulator and see two notifications ({sum: 5, mul: 6}, {sum: -1.7, mul: -7.98})" +echo "======================================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression sum: A+B, mul: A*B +=============================================================== +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=2 and B=3 +===================================== +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 and B=-3.8 +========================================== +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +Dump accumulator and see two notifications ({sum: 5, mul: 6}, {sum: -1.7, mul: -7.98}) +====================================================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 273 +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": [ + { + "A": { + "metadata": {}, + "type": "Number", + "value": 2 + }, + "B": { + "metadata": {}, + "type": "Number", + "value": 3 + }, + "id": "E1", + "mul": { + "metadata": {}, + "type": "Calculated", + "value": 6 + }, + "sum": { + "metadata": {}, + "type": "Calculated", + "value": 5 + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 285 +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": [ + { + "A": { + "metadata": {}, + "type": "Number", + "value": 2.1 + }, + "B": { + "metadata": {}, + "type": "Number", + "value": -3.8 + }, + "id": "E1", + "mul": { + "metadata": {}, + "type": "Calculated", + "value": -7.98 + }, + "sum": { + "metadata": {}, + "type": "Calculated", + "value": -1.7 + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_syntax_errors.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_syntax_errors.test new file mode 100644 index 0000000000..0804b22c8d --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_syntax_errors.test @@ -0,0 +1,179 @@ +# 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-- +JEXL expression in custom notification with syntax errors + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression with several JEXL expressions with syntax errors +# 02. Create entity E1 with A=[ foobar ] +# 03. Dump accumulator and see notification with expressions resolved to null (except e0 = TRUE) +# + + +echo "01. Create custom sub with custom expression with several JEXL expressions with syntax errors" +echo "=============================================================================================" +# NOTE: '\'' is the way of scaping a ' in the payload variable below (see https://stackoverflow.com/a/1250279/1485926) +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "e0": { + "value": "${A[0]|includes('\''foo'\'')|toString|uppercase}", + "type": "Calculated" + }, + "e1": { + "value": "${A[0|includes('\''foo'\'')|toString|uppercase}", + "type": "Calculated" + }, + "e2": { + "value": "${A[0]|includes('\''foo)|toString|uppercase}", + "type": "Calculated" + }, + "e3": { + "value": "${A[0]|includes('\''foo'\''|toString|uppercase}", + "type": "Calculated" + } + } + } + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A=[ foobar ]" +echo "======================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": [ "foobar" ], + "type": "StructuredValue" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Dump accumulator and see notification with expressions resolved to null (except e0 = TRUE)" +echo "==============================================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression with several JEXL expressions with syntax errors +============================================================================================= +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=[ foobar ] +====================================== +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E1?type=T +Content-Length: 0 + + + +03. Dump accumulator and see notification with expressions resolved to null (except e0 = TRUE) +============================================================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 359 +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": [ + { + "A": { + "metadata": {}, + "type": "StructuredValue", + "value": [ + "foobar" + ] + }, + "e0": { + "metadata": {}, + "type": "Calculated", + "value": "TRUE" + }, + "e1": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "e2": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "e3": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_full.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_full.test new file mode 100644 index 0000000000..977301f192 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_full.test @@ -0,0 +1,450 @@ +# 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-- +JEXL expression in custom notification (using all transformations) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub using all transformations +# 02. Create entity E1 with A to V attributes +# 03. Dump accumulator and see expected notification +# + + +echo "01. Create custom sub using all transformations" +echo "===============================================" +# NOTE: '\'' is the way of scaping a ' in the payload variable below (see https://stackoverflow.com/a/1250279/1485926) +payload='{ + "subject": { + "entities": [ + { + "idPattern": ".*", + "type": "T" + } + ] + }, + "notification": { + "attrs": [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V" + ], + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "A": { + "type": "Text", + "value": "${A+(3|toString)}" + }, + "B": { + "type": "Text", + "value": "${B|replaceStr('\''A'\'','\''B'\'')}" + }, + "C": { + "type": "Number", + "value": "${C+1}" + }, + "D": { + "type": "Text", + "value": "${D|uppercase}" + }, + "E": { + "type": "TextUnrestricted", + "value": "${E|lowercase}" + }, + "F": { + "type": "Text", + "value": "${F[1]|trim}" + }, + "G": { + "type": "Number", + "value": "${G|toFixed(1)}" + }, + "H": { + "type": "Text", + "value": "${H|includes('\''N'\'')}" + }, + "I": { + "type": "Text", + "value": "${I|indexOf('\''test'\'')}" + }, + "J": { + "type": "Text", + "value": "${(J|isNaN)}" + }, + "K": { + "type": "Text", + "value": "${K|typeOf}" + }, + "L": { + "type": "TextUnrestricted", + "value": "${(L|split('\'' '\''))}" + }, + "M": { + "type": "Number", + "value": "${M|round}" + }, + "N": { + "type": "Text", + "value": "${N|substring(1,2)}" + }, + "O": { + "type": "Text", + "value": "${Z||'\''Is null'\''}" + }, + "P": { + "type": "Number", + "value": "${P|len}" + }, + "Q": { + "type": "Number", + "value": "${Q|log}" + }, + "R": { + "type": "Number", + "value": "${R|log10}" + }, + "S": { + "type": "Number", + "value": "${S|log2}" + }, + "T": { + "type": "Number", + "value": "${T|sqrt}" + }, + "U": { + "type": "Text", + "value": "${U|mapper(['\''es'\'','\''fr'\''],['\''Spain'\'','\''France'\''])}" + }, + "V": { + "type": "Text", + "value": "${V|thMapper([1,2],['\''low'\'','\''medium'\'','\''high'\''])}" + } + } + } + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A to V attributes" +echo "===========================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "type": "Text", + "value": "NA" + }, + "B": { + "type": "Text", + "value": "NA" + }, + "C": { + "type": "Number", + "value": 0.17 + }, + "D": { + "type": "Text", + "value": "rice" + }, + "E": { + "type": "TextUnrestricted", + "value": "NA" + }, + "F": { + "type": "Json", + "value": [ + "Dolot sequitud", + " trimable" + ], + "metadata": {} + }, + "G": { + "type": "Number", + "value": 0.85 + }, + "H": { + "type": "Text", + "value": "NA" + }, + "I": { + "type": "Text", + "value": "Ipsum test", + "metadata": {} + }, + "J": { + "type": "Text", + "value": "NA" + }, + "K": { + "type": "Text", + "value": "NA" + }, + "L": { + "type": "Json", + "value": "Lorem limoso" + }, + "M": { + "type": "Number", + "value": 0.3 + }, + "N": { + "type": "Text", + "value": "NA" + }, + "O": { + "type": "Text", + "value": null + }, + "P": { + "type": "Text", + "value": "NA" + }, + "Q": { + "type": "Number", + "value": 2.80 + }, + "R": { + "type": "Number", + "value": 100 + }, + "S": { + "type": "Number", + "value": 32 + }, + "T": { + "type": "Number", + "value": 25 + }, + "U": { + "type": "Text", + "value": "fr" + }, + "V": { + "type": "Number", + "value": 1.5 + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Dump accumulator and see expected notification" +echo "==================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub using all transformations +=============================================== +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 to V attributes +=========================================== +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E1?type=T +Content-Length: 0 + + + +03. Dump accumulator and see expected notification +================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 1178 +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": [ + { + "A": { + "metadata": {}, + "type": "Text", + "value": "NA3" + }, + "B": { + "metadata": {}, + "type": "Text", + "value": "NB" + }, + "C": { + "metadata": {}, + "type": "Number", + "value": 1.17 + }, + "D": { + "metadata": {}, + "type": "Text", + "value": "RICE" + }, + "E": { + "metadata": {}, + "type": "TextUnrestricted", + "value": "na" + }, + "F": { + "metadata": {}, + "type": "Text", + "value": "trimable" + }, + "G": { + "metadata": {}, + "type": "Number", + "value": 0.8 + }, + "H": { + "metadata": {}, + "type": "Text", + "value": true + }, + "I": { + "metadata": {}, + "type": "Text", + "value": 6 + }, + "J": { + "metadata": {}, + "type": "Text", + "value": true + }, + "K": { + "metadata": {}, + "type": "Text", + "value": "String" + }, + "L": { + "metadata": {}, + "type": "TextUnrestricted", + "value": [ + "Lorem", + "limoso" + ] + }, + "M": { + "metadata": {}, + "type": "Number", + "value": 0 + }, + "N": { + "metadata": {}, + "type": "Text", + "value": "A" + }, + "O": { + "metadata": {}, + "type": "Text", + "value": "Is null" + }, + "P": { + "metadata": {}, + "type": "Number", + "value": 2 + }, + "Q": { + "metadata": {}, + "type": "Number", + "value": 1.029619417 + }, + "R": { + "metadata": {}, + "type": "Number", + "value": 2 + }, + "S": { + "metadata": {}, + "type": "Number", + "value": 5 + }, + "T": { + "metadata": {}, + "type": "Number", + "value": 5 + }, + "U": { + "metadata": {}, + "type": "Text", + "value": "France" + }, + "V": { + "metadata": {}, + "type": "Text", + "value": "medium" + }, + "id": "E1", + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_in_id_and_type.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_in_id_and_type.test new file mode 100644 index 0000000000..d1a0fca669 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_in_id_and_type.test @@ -0,0 +1,198 @@ +# 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-- +JEXL expression in custom notification (using transformation in id and type) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression id=S|substring(0,2), type=S|substring(3,5) +# 02. Create entity E with S=E1:T1, A:1 +# 03. Update entity E with S=E2:T2, A:2 +# 04. Dump accumulator and see notifications (E1/T1 A:1, E2/T2 A:2) +# + + +echo "01. Create custom sub with custom expression id=S|substring(0,2), type=S|substring(3,5)" +echo "=======================================================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "id": "${S|substring(0,2)}", + "type": "${S|substring(3,5)}" + } + }, + "attrs": [ "A" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E with S=E1:T1, A:1" +echo "=====================================" +payload='{ + "id": "E", + "type": "T", + "S": { + "value": "E1:T1", + "type": "Text" + }, + "A": { + "value": 1, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E with S=E2:T2, A:2" +echo "=====================================" +payload='{ + "S": { + "value": "E2:T2", + "type": "Text" + }, + "A": { + "value": 2, + "type": "Number" + } +}' +orionCurl --url /v2/entities/E/attrs --payload "$payload" +echo +echo + + +echo "04. Dump accumulator and see notifications (E1/T1 A:1, E2/T2 A:2)" +echo "=================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression id=S|substring(0,2), type=S|substring(3,5) +======================================================================================= +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 E with S=E1:T1, A:1 +===================================== +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E?type=T +Content-Length: 0 + + + +03. Update entity E with S=E2:T2, A:2 +===================================== +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +04. Dump accumulator and see notifications (E1/T1 A:1, E2/T2 A:2) +================================================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 124 +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": [ + { + "A": { + "metadata": {}, + "type": "Number", + "value": 1 + }, + "id": "E1", + "type": "T1" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 124 +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": [ + { + "A": { + "metadata": {}, + "type": "Number", + "value": 2 + }, + "id": "E2", + "type": "T2" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_multiple.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_multiple.test new file mode 100644 index 0000000000..ec3f7f0fff --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_multiple.test @@ -0,0 +1,294 @@ +# 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-- +JEXL basic expression in custom notification (multiple transformations) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with several attributes with transformations +# 02. Create entity E1 +# 03. Update entity E1 +# 05. Dump accumulator and see two expected transformations +# + + +echo "01. Create custom sub with several attributes with transformations" +echo "==================================================================" +# NOTE: '\'' is the way of scaping a ' in the payload variable below (see https://stackoverflow.com/a/1250279/1485926) +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "speed": { + "value": "${(speed|split('\'' '\''))[0]|parseInt}", + "type": "Calculated" + }, + "ratio": { + "value": "${count.sum/count.count}", + "type": "Calculated" + }, + "code": { + "value": "${code||'\''invalid'\''}", + "type": "Calculated" + }, + "alert": { + "value": "${(value>max)?'\''nok'\'':'\''ok'\''}", + "type": "Calculated" + }, + "count": { + "value": "${{count:count.count+1, sum:count.sum+((speed|split('\'' '\''))[0]|parseInt)}}", + "type": "Calculated" + } + } + }, + "attrs": [ "speed", "ratio", "code", "alert", "count" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + +#"value": "${{count:count.count+1,sum:count.sum+speed|split('\'' '\'')[0]|parseInt}}", + +echo "02. Create entity E1" +echo "====================" +payload='{ + "id": "E1", + "type": "T", + "speed": { + "value": "10 m/s", + "type": "Text" + }, + "count": { + "value": { + "count": 5, + "sum": 100 + }, + "type": "StructuredValue" + }, + "code": { + "value": null, + "type": "Number" + }, + "value": { + "value": 14, + "type": "Number" + }, + "max": { + "value": 50, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E1" +echo "====================" +payload='{ + "speed": { + "value": "30 m/s", + "type": "Text" + }, + "count": { + "value": { + "count": 5, + "sum": 500 + }, + "type": "StructuredValue" + }, + "code": { + "value": 456, + "type": "Number" + }, + "value": { + "value": 75, + "type": "Number" + }, + "max": { + "value": 50, + "type": "Number" + } +}' +orionCurl --url /v2/entities/E1/attrs -X PATCH --payload "$payload" +echo +echo + + +echo "05. Dump accumulator and see two expected transformations" +echo "=========================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with several attributes with transformations +================================================================== +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 +==================== +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 +==================== +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +05. Dump accumulator and see two expected transformations +========================================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 379 +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": [ + { + "alert": { + "metadata": {}, + "type": "Calculated", + "value": "ok" + }, + "code": { + "metadata": {}, + "type": "Calculated", + "value": "invalid" + }, + "count": { + "metadata": {}, + "type": "Calculated", + "value": { + "count": 6, + "sum": 110 + } + }, + "id": "E1", + "ratio": { + "metadata": {}, + "type": "Calculated", + "value": 20 + }, + "speed": { + "metadata": {}, + "type": "Calculated", + "value": 10 + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 375 +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": [ + { + "alert": { + "metadata": {}, + "type": "Calculated", + "value": "nok" + }, + "code": { + "metadata": {}, + "type": "Calculated", + "value": 456 + }, + "count": { + "metadata": {}, + "type": "Calculated", + "value": { + "count": 6, + "sum": 530 + } + }, + "id": "E1", + "ratio": { + "metadata": {}, + "type": "Calculated", + "value": 100 + }, + "speed": { + "metadata": {}, + "type": "Calculated", + "value": 30 + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_simple.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_simple.test new file mode 100644 index 0000000000..05ffeb6471 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_simple.test @@ -0,0 +1,145 @@ +# 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-- +JEXL expression in custom notification (using transformation) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression low: A|lowercase +# 02. Create entity E1 with A=IwantAllInLOWERCase +# 03. Dump accumulator and see notifications (low: iwantallinlowercase) +# + + +echo "01. Create custom sub with custom expression low: A|lowercase" +echo "=============================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "low": { + "value": "${A|lowercase}", + "type": "Calculated" + } + } + }, + "attrs": [ "low" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A=IwantAllInLOWERCase" +echo "===============================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": "IwantAllInLOWERCase", + "type": "Text" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Dump accumulator and see notifications (low: iwantallinlowercase)" +echo "=====================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression low: A|lowercase +============================================================= +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=IwantAllInLOWERCase +=============================================== +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E1?type=T +Content-Length: 0 + + + +03. Dump accumulator and see notifications (low: iwantallinlowercase) +===================================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 149 +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": [ + { + "id": "E1", + "low": { + "metadata": {}, + "type": "Calculated", + "value": "iwantallinlowercase" + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_unknown.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_unknown.test new file mode 100644 index 0000000000..c4ad0adf67 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_unknown.test @@ -0,0 +1,145 @@ +# 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-- +JEXL expression in custom notification (using unknown transformation) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression low: A|patatacase +# 02. Create entity E1 with A=IwantAllInLOWERCase +# 03. Dump accumulator and see notifications (low: null) +# + + +echo "01. Create custom sub with custom expression low: A|patatacase" +echo "==============================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "low": { + "value": "${A|patatacase}", + "type": "Calculated" + } + } + }, + "attrs": [ "low" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with A=IwantAllInLOWERCase" +echo "===============================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": "IwantAllInLOWERCase", + "type": "Text" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Dump accumulator and see notifications (low: null)" +echo "======================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression low: A|patatacase +============================================================== +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=IwantAllInLOWERCase +=============================================== +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E1?type=T +Content-Length: 0 + + + +03. Dump accumulator and see notifications (low: null) +====================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 132 +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": [ + { + "id": "E1", + "low": { + "metadata": {}, + "type": "Calculated", + "value": null + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_with_arguments.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_with_arguments.test new file mode 100644 index 0000000000..2bc3422ef1 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/jexl_transformation_with_arguments.test @@ -0,0 +1,251 @@ +# 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-- +JEXL expression in custom notification (using transformation with arguments) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart --pretty-print + +--SHELL-- + +# +# 01. Create custom sub with custom expression included: S|includes(R) +# 02. Create entity E1 with S=a given string, R=given +# 03. Update entity E1 with S=anotherstring, R=strong +# 04. Update entity E1 with S=morestringarecoming, R=string +# 05. Dump accumulator and see notifications (included: true, included: false, include: true) +# + + +echo "01. Create custom sub with custom expression included: S|includes(R)" +echo "====================================================================" +payload='{ + "subject": { + "entities": [ + { + "id" : "E1", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "includes": { + "value": "${S|includes(R)}", + "type": "Boolean" + } + } + }, + "attrs": [ "includes" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "02. Create entity E1 with S=a given string, R=given" +echo "===================================================" +payload='{ + "id": "E1", + "type": "T", + "S": { + "value": "a given string", + "type": "Text" + }, + "R": { + "value": "given", + "type": "Text" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "03. Update entity E1 with S=anotherstring, R=strong" +echo "===================================================" +payload='{ + "S": { + "value": "anotherstring", + "type": "Text" + }, + "R": { + "value": "strong", + "type": "Text" + } +}' +orionCurl --url /v2/entities/E1/attrs --payload "$payload" +echo +echo + + +echo "04. Update entity E1 with S=morestringarecoming, R=string" +echo "=========================================================" +payload='{ + "S": { + "value": "morestringarecoming", + "type": "Text" + }, + "R": { + "value": "string", + "type": "Text" + } +}' +orionCurl --url /v2/entities/E1/attrs --payload "$payload" +echo +echo + + +echo "05. Dump accumulator and see notifications (included: true, included: false, include: true)" +echo "===========================================================================================" +accumulatorDump +echo +echo + + +--REGEXPECT-- +01. Create custom sub with custom expression included: S|includes(R) +==================================================================== +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 S=a given string, R=given +=================================================== +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 S=anotherstring, R=strong +=================================================== +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +04. Update entity E1 with S=morestringarecoming, R=string +========================================================= +HTTP/1.1 204 No Content +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) + + + +05. Dump accumulator and see notifications (included: true, included: false, include: true) +=========================================================================================== +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 134 +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": [ + { + "id": "E1", + "includes": { + "metadata": {}, + "type": "Boolean", + "value": true + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 135 +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": [ + { + "id": "E1", + "includes": { + "metadata": {}, + "type": "Boolean", + "value": false + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 134 +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": [ + { + "id": "E1", + "includes": { + "metadata": {}, + "type": "Boolean", + "value": true + }, + "type": "T" + } + ], + "subscriptionId": "REGEX([0-9a-f]{24})" +} +======================================= + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/null_values_all_cases.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/null_values_all_cases.test new file mode 100644 index 0000000000..3c7fe0ad03 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/null_values_all_cases.test @@ -0,0 +1,366 @@ +# 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-- +Expression in custom notification (null values in all cases) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart + +--SHELL-- + +# +# 01. Create custom sub using payload: "${A}" +# 02. Create custom sub using payload: "X:${A}:Y" +# 03. Create custom sub using json: "${A}" and "X:${A}:Y" +# 04. Create custom sub using ngsi: "${A}" and "X:${A}:Y" +# 05. Create entity E1 with A=null +# 06. Dump & reset accumulator and see 4 notifications +# 07. Create entity E2 without A +# 08. Dump & reset accumulator and see 4 notifications +# + + +echo '01. Create custom sub using payload: "${A}"' +echo "===========================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "headers": { + "content-type": "text/plain" + }, + "payload": "${A}" + } + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '02. Create custom sub using payload: "X:${A}:Y"' +echo "===============================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "headers": { + "content-type": "text/plain" + }, + "payload": "X:${A}:Y" + } + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '03. Create custom sub using json: "${A}" and "X:${A}:Y"' +echo "=======================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "json": { + "B1": "${A}", + "B2": "X:${A}:Y" + } + } + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '04. Create custom sub with using ngsi: "${A}" and "X:${A}:Y"' +echo "============================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "B1": { + "value": "${A}", + "type": "Calculated" + }, + "B2": { + "value": "X:${A}:Y", + "type": "Calculated" + } + } + }, + "attrs": [ "B1", "B2" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "05. Create entity E1 with A=null" +echo "================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": null, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "06. Dump & reset accumulator and see 4 notifications" +echo "====================================================" +accumulatorDump +accumulatorReset +echo +echo + + +echo "07. Create entity E2 without A" +echo "==============================" +payload='{ + "id": "E2", + "type": "T", + "B": { + "value": 1, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "08. Dump & reset accumulator and see 4 notifications" +echo "====================================================" +accumulatorDump +accumulatorReset +echo +echo + + +--REGEXPECT-- +01. Create custom sub using payload: "${A}" +=========================================== +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 custom sub using payload: "X:${A}:Y" +=============================================== +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 + + + +03. Create custom sub using json: "${A}" and "X:${A}:Y" +======================================================= +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 + + + +04. Create custom sub with using ngsi: "${A}" and "X:${A}:Y" +============================================================ +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 + + + +05. Create entity E1 with A=null +================================ +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E1?type=T +Content-Length: 0 + + + +06. Dump & reset accumulator and see 4 notifications +==================================================== +#SORT_START +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 4 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +null======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 8 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +X:null:Y======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 27 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +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=[1234]) + +{"B1":null,"B2":"X:null:Y"}======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 191 +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=[1234]) + +{"subscriptionId":"REGEX([0-9a-f]{24})","data":[{"id":"E1","type":"T","B1":{"type":"Calculated","value":null,"metadata":{}},"B2":{"type":"Calculated","value":"X:null:Y","metadata":{}}}]}======================================= +#SORT_END + + +07. Create entity E2 without A +============================== +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E2?type=T +Content-Length: 0 + + + +08. Dump & reset accumulator and see 4 notifications +==================================================== +#SORT_START +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 4 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +null======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 8 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +X:null:Y======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 27 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +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=[1234]) + +{"B1":null,"B2":"X:null:Y"}======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 191 +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=[1234]) + +{"subscriptionId":"REGEX([0-9a-f]{24})","data":[{"id":"E2","type":"T","B1":{"type":"Calculated","value":null,"metadata":{}},"B2":{"type":"Calculated","value":"X:null:Y","metadata":{}}}]}======================================= +#SORT_END + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/cases/4004_jexl_expressions_in_subs/null_values_all_cases_covered_sub.test b/test/functionalTest/cases/4004_jexl_expressions_in_subs/null_values_all_cases_covered_sub.test new file mode 100644 index 0000000000..562fb86085 --- /dev/null +++ b/test/functionalTest/cases/4004_jexl_expressions_in_subs/null_values_all_cases_covered_sub.test @@ -0,0 +1,373 @@ +# 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-- +Expression in custom notification (null values in all cases with covered subs) + +--SHELL-INIT-- +dbInit CB +brokerStart CB +accumulatorStart + +--SHELL-- + +# +# 01. Create custom sub with covered using payload: "${A}" +# 02. Create custom sub with covered using payload: "X:${A}:Y" +# 03. Create custom sub with covered using json: "${A}" and "X:${A}:Y" +# 04. Create custom sub with covered using ngsi: "${A}" and "X:${A}:Y" +# 05. Create entity E1 with A=null +# 06. Dump & reset accumulator and see 4 notifications +# 07. Create entity E2 without A +# 08. Dump & reset accumulator and see 4 notifications +# + + +echo '01. Create custom sub with covered using payload: "${A}"' +echo "========================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "headers": { + "content-type": "text/plain" + }, + "payload": "${A}" + }, + "covered": true, + "attrs": [ "A" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '02. Create custom sub with covered using payload: "X:${A}:Y"' +echo "============================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "headers": { + "content-type": "text/plain" + }, + "payload": "X:${A}:Y" + }, + "covered": true, + "attrs": [ "A" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '03. Create custom sub with covered using json: "${A}" and "X:${A}:Y"' +echo "====================================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "json": { + "B1": "${A}", + "B2": "X:${A}:Y" + } + }, + "covered": true, + "attrs": [ "A" ] + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo '04. Create custom sub with covered using json: "${A}" and "X:${A}:Y"' +echo "====================================================================" +payload='{ + "subject": { + "entities": [ + { + "idPattern" : ".*", + "type": "T" + } + ] + }, + "notification": { + "httpCustom": { + "url": "http://127.0.0.1:'${LISTENER_PORT}'/notify", + "ngsi": { + "B1": { + "value": "${A}", + "type": "Calculated" + }, + "B2": { + "value": "X:${A}:Y", + "type": "Calculated" + } + } + }, + "attrs": [ "B1", "B2" ], + "covered": true + } +}' +orionCurl --url /v2/subscriptions --payload "$payload" +echo +echo + + +echo "05. Create entity E1 with A=null" +echo "================================" +payload='{ + "id": "E1", + "type": "T", + "A": { + "value": null, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "06. Dump & reset accumulator and see 4 notifications" +echo "====================================================" +accumulatorDump +accumulatorReset +echo +echo + + +echo "07. Create entity E2 without A" +echo "==============================" +payload='{ + "id": "E2", + "type": "T", + "B": { + "value": 1, + "type": "Number" + } +}' +orionCurl --url /v2/entities --payload "$payload" +echo +echo + + +echo "08. Dump & reset accumulator and see 4 notifications" +echo "====================================================" +accumulatorDump +accumulatorReset +echo +echo + + +--REGEXPECT-- +01. Create custom sub with covered using payload: "${A}" +======================================================== +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 custom sub with covered using payload: "X:${A}:Y" +============================================================ +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 + + + +03. Create custom sub with covered using json: "${A}" and "X:${A}:Y" +==================================================================== +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 + + + +04. Create custom sub with covered using json: "${A}" and "X:${A}:Y" +==================================================================== +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 + + + +05. Create entity E1 with A=null +================================ +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E1?type=T +Content-Length: 0 + + + +06. Dump & reset accumulator and see 4 notifications +==================================================== +#SORT_START +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 4 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +null======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 8 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +X:null:Y======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 27 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +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=[1234]) + +{"B1":null,"B2":"X:null:Y"}======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 191 +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=[1234]) + +{"subscriptionId":"REGEX([0-9a-f]{24})","data":[{"id":"E1","type":"T","B1":{"type":"Calculated","value":null,"metadata":{}},"B2":{"type":"Calculated","value":"X:null:Y","metadata":{}}}]}======================================= +#SORT_END + + +07. Create entity E2 without A +============================== +HTTP/1.1 201 Created +Date: REGEX(.*) +Fiware-Correlator: REGEX([0-9a-f\-]{36}) +Location: /v2/entities/E2?type=T +Content-Length: 0 + + + +08. Dump & reset accumulator and see 4 notifications +==================================================== +#SORT_START +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 4 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +null======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 8 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +Host: 127.0.0.1:REGEX(\d+) +Accept: application/json +Content-Type: text/plain +Fiware-Correlator: REGEX([0-9a-f\-]{36}; cbnotif=[1234]) + +X:null:Y======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 27 +User-Agent: orion/REGEX(\d+\.\d+\.\d+.*) +Ngsiv2-Attrsformat: custom +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=[1234]) + +{"B1":null,"B2":"X:null:Y"}======================================= +POST http://127.0.0.1:REGEX(\d+)/notify +Fiware-Servicepath: / +Content-Length: 191 +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=[1234]) + +{"subscriptionId":"REGEX([0-9a-f]{24})","data":[{"id":"E2","type":"T","B1":{"type":"Calculated","value":null,"metadata":{}},"B2":{"type":"Calculated","value":"X:null:Y","metadata":{}}}]}======================================= +#SORT_END + + +--TEARDOWN-- +brokerStop CB +dbDrop CB +accumulatorStop diff --git a/test/functionalTest/testHarness.sh b/test/functionalTest/testHarness.sh index c309c4e8c2..9b30488480 100755 --- a/test/functionalTest/testHarness.sh +++ b/test/functionalTest/testHarness.sh @@ -96,6 +96,8 @@ declare -A skipV typeset -i skips declare -A disabledTestV typeset -i disabledTests +declare -A notInFlavourTestV +typeset -i notInFlavourTests export DIFF=$SCRIPT_HOME/testDiff.py testError=0 @@ -104,6 +106,7 @@ okOnThird=0 okOnPlus3=0 skips=0 disabledTests=0 +notInFlavourTests=0 # ----------------------------------------------------------------------------- @@ -937,6 +940,30 @@ function testDisabled +# ----------------------------------------------------------------------------- +# +# testMatchExprFlavour +# +function testMatchExprFlavour +{ + testcase=$1 + + if grep -q JEXL_EXPR_FLAVOUR $testcase + then + if $(contextBroker --version | grep -q jexl-expr) + then + echo NOT Disabled + else + echo "Disabled" + echo "Disabled" > /tmp/valgrind.out + fi + else + echo NOT Disabled + fi +} + + + # ------------------------------------------------------------------------------ # # Main loop @@ -973,6 +1000,17 @@ do continue fi + # + # Should the test be skipped due to it doesn't mach in the contextBroker flavour? + # + notInFlavour=$(testMatchExprFlavour $testFile) + if [ "$notInFlavour" == "Disabled" ] + then + notInFlavourTestV[$notInFlavourTests]=$testNo': '$testFile + notInFlavourTests=$notInFlavourTests+1 + continue + fi + if [ "$ixList" != "" ] then hit=$(echo ' '$ixList' ' | grep ' '$testNo' ') @@ -1190,4 +1228,16 @@ then done fi +if [ $notInFlavourTests != 0 ] +then + echo + echo WARNING: $notInFlavourTests test cases were not executed due to contexBroker not matching flavour: + ix=0 + while [ $ix -lt $notInFlavourTests ] + do + echo " o " ${notInFlavourTestV[$ix]} + ix=$ix+1 + done +fi + exit $exitCode diff --git a/test/unittests/common/commonMacroSubstitute_test.cpp b/test/unittests/common/commonMacroSubstitute_test.cpp index 40812fb6fb..be51a1e8af 100644 --- a/test/unittests/common/commonMacroSubstitute_test.cpp +++ b/test/unittests/common/commonMacroSubstitute_test.cpp @@ -49,9 +49,12 @@ TEST(commonMacroSubstitute, simple) const char* correct = "Entity E1/T1, attribute 'attr1'"; std::string result; - std::map replacements; - buildReplacementsMap(en, "", "", &replacements); - b = macroSubstitute(&result, s1, &replacements, ""); + ExprContextObject exprContext; + exprContext.add("id", en.id); + exprContext.add("type", en.type); + exprContext.add(caP->name, caP->stringValue); + + b = macroSubstitute(&result, s1, &exprContext, "", true); EXPECT_TRUE(b); EXPECT_STREQ(correct, result.c_str()); } @@ -88,9 +91,12 @@ TEST(commonMacroSubstitute, withRealloc) std::string correct = std::string(base) + "Now, finally something to substitute: Entity E1/T1, attribute 'attr1'"; std::string result; - std::map replacements; - buildReplacementsMap(en, "", "", &replacements); - b = macroSubstitute(&result, s1, &replacements, ""); + ExprContextObject exprContext; + exprContext.add("id", en.id); + exprContext.add("type", en.type); + exprContext.add(caP->name, caP->stringValue); + + b = macroSubstitute(&result, s1, &exprContext, "", true); EXPECT_TRUE(b); EXPECT_STREQ(correct.c_str(), result.c_str()); } @@ -120,9 +126,12 @@ TEST(commonMacroSubstitute, bufferTooBigInitially) // correct = std::string(base) + "EntityId000001/EntityType000001"; std::string result; - std::map replacements; - buildReplacementsMap(en, "", "", &replacements); - b = macroSubstitute(&result, s1, &replacements, ""); + ExprContextObject exprContext; + exprContext.add("id", en.id); + exprContext.add("type", en.type); + exprContext.add(caP->name, caP->stringValue); + + b = macroSubstitute(&result, s1, &exprContext, "", true); EXPECT_FALSE(b); EXPECT_STREQ("", result.c_str()); @@ -158,9 +167,12 @@ TEST(commonMacroSubstitute, bufferTooBigAfterSubstitution) // correct = std::string(base) + "EntityId000001/EntityType000001"; // > 8MB after substitutions std::string result; - std::map replacements; - buildReplacementsMap(en, "", "", &replacements); - b = macroSubstitute(&result, s1, &replacements, ""); + ExprContextObject exprContext; + exprContext.add("id", en.id); + exprContext.add("type", en.type); + exprContext.add(caP->name, caP->stringValue); + + b = macroSubstitute(&result, s1, &exprContext, "", true); EXPECT_FALSE(b); EXPECT_STREQ("", result.c_str()); diff --git a/test/unittests/main_UnitTest.cpp b/test/unittests/main_UnitTest.cpp index 3b7718b0c1..d5b4660025 100644 --- a/test/unittests/main_UnitTest.cpp +++ b/test/unittests/main_UnitTest.cpp @@ -42,6 +42,7 @@ #include "mongoBackend/MongoGlobal.h" #include "ngsiNotify/Notifier.h" #include "alarmMgr/alarmMgr.h" +#include "expressions/exprMgr.h" #include "logSummary/logSummary.h" #include "unittests/unittest.h" @@ -162,6 +163,7 @@ int main(int argC, char** argV) // Note that disableRetryTries, multitenancy and mutex time stats are disabled for unit test mongo init mongoInit(dbURI, dbHost, rplSet, dbName, user, pwd, authMech, authDb, dbSSL, false, false, dbTimeout, writeConcern, dbPoolSize, false); alarmMgr.init(false); + exprMgr.init(); logSummaryInit(&lsPeriod); // setupDatabase(); FIXME #3775: pending on mongo unit test re-enabling