From 3b2afb265db5fbb9284a9004d93d6663ea3e060a Mon Sep 17 00:00:00 2001 From: German Toro del Valle Date: Tue, 25 Oct 2016 11:50:40 +0200 Subject: [PATCH] Include support for entity types in the attribute-function-interpolator --- CHANGES_NEXT_RELEASE | 1 + README.md | 2 +- .../attributeFunctionInterpolator.js | 47 ++++++- .../attributeFunctionInterpolator_test.js | 125 ++++++++++++++++-- 4 files changed, 159 insertions(+), 16 deletions(-) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index e69de29..9836365 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -0,0 +1 @@ +- [FEATURE] Include support for entity types in the attribute-function-interpolator diff --git a/README.md b/README.md index 9433737..e93e16e 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,7 @@ The simulation configuration file accepts the following JSON properties or entri * `units`: It is a string which affects the `text` property detailed below. It accepts the following values: `seconds`, `minutes`, `hours`, `days` (day of the week), `dates` (day of the month), `months` and `years`. * `text`: It is an array of 2 elements arrays. The first element is the number of `seconds` (from 0 to 59), `minutes` (from 0 to 59), `hours` (from 0 to 23), `days` (from 0 to 6), `dates` (from 1 to 31), `months` (from 0 to 11) and `years` (full year) (according to the `units` property) from which the specified text will be returned for the current date and time. The second element can be a string corresponding to the text to be returned or an array of 2 elements arrays. The first element of this second 2 elements array is the probability (from 0 to 100) of the occurrence of the text specified as the second element of the array. The addition of the first elements array must be 100. * A valid attribute value using the `text-rotation-interpolator` is: `"text-rotation-interpolator({\"units\": \"seconds\", \"text\": [[0,\"PENDING\"],[15,\"REQUESTED\"],[30,[[50,\"COMPLETED\"],[50,\"ERROR\"]]],[45,\"REMOVED\"]]})"`. For example, according to this text rotation interpolation specification, if the current time seconds is between 0 and 15 it will return the value `PENDING`, if it is between 15 and 30 it will return the value `REQUESTED`, if it is between 30 and 45 it will return the value `COMPLETED` with a probability of 50% and `ERROR` with a probability of 50%. - 8. **`attribute-function-interpolator`**: It returns the result of the evaluation of some Javascript code. This code may include references to any entity's attributes values stored in the Context Broker. This interpolator accepts a string (properly escaped) with the Javascript code to evaluate. In this Javascript code, references to entity's attribute values may be included using the notation: `${{}{}}`, substituting the `` and `:#:}{}}`, substituting the ``, `` and ``, inluding the `:#:` separator) is optional and can be omited, in which case the entity type will not be considered when retrieving the entity and the corresponding attribute value from the Context Broker. * A valid attribute value using the `attribute-function-interpolator` is: `"attribute-function-interpolator(${{Entity:001}{active:001}} + Math.pow(${{Entity:002}{active:001}},2))"`. * **metadata**: Array of metadata information to be associated to the attribute on the update. Each metadata array entry is an object including 3 properties: * **name**: The metadata name. diff --git a/lib/interpolators/attributeFunctionInterpolator.js b/lib/interpolators/attributeFunctionInterpolator.js index 6c1e060..60d3b9e 100644 --- a/lib/interpolators/attributeFunctionInterpolator.js +++ b/lib/interpolators/attributeFunctionInterpolator.js @@ -69,6 +69,34 @@ function sendRequest(token, requestOptions, callback) { request(requestOptions, callback); } +/** + * Returns the entity name from an entity name and possible type pair + * @param {String} entity The entity name and possible type pair + * @return {String} The entity name + */ +function getEntityName(entity) { + var entityName; + if (entity.indexOf(':#:') === -1) { + entityName = entity; + } else { + entityName = entity.substring(0, entity.indexOf(':#:')); + } + return entityName; +} + +/** + * Returns the entity type (if any) from an entity name and possible type pair + * @param {String} entity The entity name and possible type pair + * @return {String} The entity type or undefined if no type + */ +function getEntityType(entity) { + var entityType; + if (entity.indexOf(':#:') !== -1) { + entityType = entity.substring(entity.indexOf(':#:') + 3); + } + return entityType; +} + /** * Returns the request options from a entity-attribute map * @param {Object} domainConf The domain configuration @@ -78,19 +106,26 @@ function sendRequest(token, requestOptions, callback) { */ function getRequestOptions(domainConf, contextBrokerConf, entityAttributeMap) { var body, - requestOptions = []; + requestOptions = [], + entityName, + entityType; var entities = Object.getOwnPropertyNames(entityAttributeMap); entities.forEach(function(entity) { + entityName = getEntityName(entity); + entityType = getEntityType(entity); body = { entities: [ { - id: entity, + id: entityName, isPattern: 'false' } ] }; + if (entityType) { + body.entities[0].type = entityType; + } entityAttributeMap[entity].forEach(function(attribute) { body.attributes = body.attributes || []; body.attributes.push(attribute); @@ -119,13 +154,17 @@ function getRequestOptions(domainConf, contextBrokerConf, entityAttributeMap) { /** * Returns the attribute value from the received responses * @param {Array} responses The array of responses - * @param {String} entity The entity name + * @param {String} entity The entity name and optional type * @param {String} attribute The attribute name * @return {Object} The attribute value */ function getAttributeValue(responses, entity, attribute) { + var entityName = getEntityName(entity); + var entityType = getEntityType(entity); + for (var ii = 0; ii < responses.length; ii++) { - if (responses[ii].body.contextResponses[0].contextElement.id === entity) { + if (responses[ii].body.contextResponses[0].contextElement.id === entityName && + (entityType ? responses[ii].body.contextResponses[0].contextElement.type === entityType : true)) { for (var jj = 0; jj < responses[ii].body.contextResponses[0].contextElement.attributes.length; jj++) { if (responses[ii].body.contextResponses[0].contextElement.attributes[jj].name === attribute) { return responses[ii].body.contextResponses[0].contextElement.attributes[jj].value; diff --git a/test/unit/interpolators/attributeFunctionInterpolator_test.js b/test/unit/interpolators/attributeFunctionInterpolator_test.js index 5f456f4..7be839a 100644 --- a/test/unit/interpolators/attributeFunctionInterpolator_test.js +++ b/test/unit/interpolators/attributeFunctionInterpolator_test.js @@ -31,7 +31,8 @@ var fdsErrors = require(ROOT_PATH + '/lib/errors/fdsErrors'); var attributeFunctionInterpolator = require(ROOT_PATH + '/lib/interpolators/attributeFunctionInterpolator'); var ATTRIBUTE_VALUE_1 = 111; -var ATTRIBUTE_VALUE_2 = 333; +var ATTRIBUTE_VALUE_2 = 222; +var ATTRIBUTE_VALUE_3 = 333; describe('attributeFunctionInterpolator tests', function() { var attributeFunctionInterpolatorFunction, @@ -86,12 +87,12 @@ describe('attributeFunctionInterpolator tests', function() { } ] }; - } else if (requestBody.entities[0].id === 'EntityId2') { + } else if (requestBody.entities[0].id === 'EntityId2' && requestBody.entities[0].type === 'Entity2') { return { contextResponses: [ { contextElement: { - type: 'Entity', + type: 'Entity2', isPattern: 'false', id: 'EntityId2', attributes: [ @@ -101,7 +102,7 @@ describe('attributeFunctionInterpolator tests', function() { value: ATTRIBUTE_VALUE_2 }, { - name: 'AttributeNam21', + name: 'AttributeName22', type: 'Number', value: ATTRIBUTE_VALUE_2 * 2 } @@ -114,6 +115,34 @@ describe('attributeFunctionInterpolator tests', function() { } ] }; + } else if (requestBody.entities[0].id === 'EntityId2') { + return { + contextResponses: [ + { + contextElement: { + type: 'Entity', + isPattern: 'false', + id: 'EntityId2', + attributes: [ + { + name: 'AttributeName21', + type: 'Number', + value: ATTRIBUTE_VALUE_3 + }, + { + name: 'AttributeName22', + type: 'Number', + value: ATTRIBUTE_VALUE_3 * 2 + } + ] + }, + statusCode: { + code: 200, + reasonPhrase: 'OK' + } + } + ] + }; } }); }); @@ -189,7 +218,8 @@ describe('attributeFunctionInterpolator tests', function() { } }); - it('should interpolate if a reference to an entity attribute is passed as the interpolation specification', + it('should interpolate if a reference to an entity attribute (without entity type) is passed as the ' + + 'interpolation specification', function(done) { try { attributeFunctionInterpolatorFunction = @@ -202,7 +232,21 @@ describe('attributeFunctionInterpolator tests', function() { } ); - it('should interpolate if an addition to a reference to an entity attribute is passed as the ' + + it('should interpolate if a reference to an entity attribute (with entity type) is passed as the ' + + 'interpolation specification', + function(done) { + try { + attributeFunctionInterpolatorFunction = + attributeFunctionInterpolator('${{EntityId2:#:Entity2}{AttributeName21}}', domain, contextBroker); + should(attributeFunctionInterpolatorFunction(token)).equal(ATTRIBUTE_VALUE_2); + done(); + } catch(exception) { + done(exception); + } + } + ); + + it('should interpolate if an addition to a reference to an entity attribute (without entity type) is passed as the ' + 'interpolation specification', function(done) { try { @@ -216,8 +260,22 @@ describe('attributeFunctionInterpolator tests', function() { } ); - it('should interpolate if a function invocation on a reference to an entity attribute is passed as the ' + + it('should interpolate if an addition to a reference to an entity attribute (with entity type) is passed as the ' + 'interpolation specification', + function(done) { + try { + attributeFunctionInterpolatorFunction = + attributeFunctionInterpolator('${{EntityId2:#:Entity2}{AttributeName21}} + 111', domain, contextBroker); + should(attributeFunctionInterpolatorFunction(token)).equal(ATTRIBUTE_VALUE_2 + 111); + done(); + } catch(exception) { + done(exception); + } + } + ); + + it('should interpolate if a function invocation on a reference to an entity attribute (without entity type) ' + + 'is passed as the interpolation specification', function(done) { try { attributeFunctionInterpolatorFunction = @@ -230,8 +288,23 @@ describe('attributeFunctionInterpolator tests', function() { } ); - it('should interpolate if the addition of references to distinct entity\'s attributes is passed as the ' + - 'interpolation specification', + it('should interpolate if a function invocation on a reference to an entity attribute (with entity type) ' + + 'is passed as the interpolation specification', + function(done) { + try { + attributeFunctionInterpolatorFunction = + attributeFunctionInterpolator('Math.pow(${{EntityId2:#:Entity2}{AttributeName21}}, 2);', + domain, contextBroker); + should(attributeFunctionInterpolatorFunction(token)).equal(Math.pow(ATTRIBUTE_VALUE_2, 2)); + done(); + } catch(exception) { + done(exception); + } + } + ); + + it('should interpolate if the addition of references to distinct entity\'s attributes (without entity type) ' + + 'is passed as the interpolation specification', function(done) { try { attributeFunctionInterpolatorFunction = @@ -245,13 +318,43 @@ describe('attributeFunctionInterpolator tests', function() { } ); - it('should interpolate if the addition of references to attributes of distinct entities attributes is passed ' + - 'as the interpolation specification', + it('should interpolate if the addition of references to distinct entity\'s attributes (with entity type) ' + + 'is passed as the interpolation specification', + function(done) { + try { + attributeFunctionInterpolatorFunction = + attributeFunctionInterpolator( + '${{EntityId1}{AttributeName11}} + ${{EntityId2:#:Entity2}{AttributeName22}}', domain, contextBroker); + should(attributeFunctionInterpolatorFunction(token)).equal(ATTRIBUTE_VALUE_1 + (ATTRIBUTE_VALUE_2 * 2)); + done(); + } catch(exception) { + done(exception); + } + } + ); + + it('should interpolate if the addition of references to attributes of distinct entities attributes ' + + '(without entity type) is passed as the interpolation specification', function(done) { try { attributeFunctionInterpolatorFunction = attributeFunctionInterpolator( '${{EntityId1}{AttributeName11}} + ${{EntityId2}{AttributeName21}}', domain, contextBroker); + should(attributeFunctionInterpolatorFunction(token)).equal(ATTRIBUTE_VALUE_1 + ATTRIBUTE_VALUE_3); + done(); + } catch(exception) { + done(exception); + } + } + ); + + it('should interpolate if the addition of references to attributes of distinct entities attributes ' + + '(with entity type) is passed as the interpolation specification', + function(done) { + try { + attributeFunctionInterpolatorFunction = + attributeFunctionInterpolator( + '${{EntityId1}{AttributeName11}} + ${{EntityId2:#:Entity2}{AttributeName21}}', domain, contextBroker); should(attributeFunctionInterpolatorFunction(token)).equal(ATTRIBUTE_VALUE_1 + ATTRIBUTE_VALUE_2); done(); } catch(exception) {