UNPKG

fiware-orion-client

Version:

A client Library that makes it easier to get access to Orion Context Broker (part of the FIWARE platform)

766 lines (625 loc) 18.8 kB
/** * * FIWARE-sdk: Orion Context Broker Client Library and Utilities * * var Orion = require('fiware-orion-client'), * OrionHelper = Orion.NgsiHelper; * * This library is intended to work both in Web Browsers and Node * * Copyright (c) 2015 Telefónica Investigación y Desarrollo S.A.U. * * LICENSE: MIT (See LICENSE file) * */ 'use strict'; const PROPERTY_MAP = { 'type': 'type', 'id' : 'id' }; const EXT_PROPERTY_MAP = { 'attributes': 'attributes', 'pattern': 'pattern' }; function Attribute(value, type, metadata) { this.value = value; this.type = type; this.metadata = metadata; } // Converts a NGSI object to XML function ngsiObj2XML() { return ngsiObj2XMLTree.bind(this)().build(); } function ngsiObj2XMLTree() { var XmlBuilder = require('./xml-builder.js'); var root = new XmlBuilder('contextElement'); var entityId = root.child('entityId').attr('type', this.type); entityId.attr('isPattern', this.isPattern); entityId.child('id').text(this.id); var attrList = root.child('contextAttributeList'); this.attributes.forEach(function(aAttr) { var attrNode = attrList.child('contextAttribute'); attrNode.child('name').text(aAttr.name); if (aAttr.type) { attrNode.child('type').text(aAttr.type); } attrNode.child('contextValue').text(aAttr.value); }); return root; } // Converts a NGSI Response to XML function ngsiResponse2XMLTree() { var XmlBuilder = require('./xml-builder.js'); var root = new XmlBuilder('contextResponseList'); this.contextResponses.forEach(function(aResponse) { var responseElement = root.child('contextElementResponse'); responseElement.child(aResponse.contextElement.toXMLTree()); var statusCode = responseElement.child('statusCode'); statusCode.child('code').text(aResponse.statusCode.code); statusCode.child('reasonPhrase').text(aResponse.statusCode.reasonPhrase); }); return root; } function ngsiResponse2XML() { return ngsiResponse2XMLTree.bind(this)().build(); } var NgsiHelper = { parse: function(chunk, options) { var self = this; var ngsiData = chunk; if (typeof chunk === 'string') { try { ngsiData = JSON.parse(chunk); } catch (e) { // If cannot be parsed as JSON then should XML return this._parseXML(chunk); } } var responses = ngsiData.contextResponses; // In this case the response has not been wrapped around contextReponses if (!responses && !ngsiData.errorCode && !ngsiData.attributes) { responses = [ngsiData]; } if (ngsiData.attributes) { responses = [{ contextElement: { attributes: ngsiData.attributes } }]; } var statusCode = (ngsiData.errorCode && ngsiData.errorCode.code) || (ngsiData.statusCode && ngsiData.statusCode.code) || (Array.isArray(responses) && responses[0].statusCode && responses[0].statusCode.code); if (statusCode != 200) { if (statusCode == 404) { return null; } return { inError: true, errorCode: statusCode }; } else if (!ngsiData.contextResponses) { return null; } if (responses.length === 1) { return this._toObject(responses[0].contextElement, options); } else { var out = []; responses.forEach(function(aResponse) { out.push(self._toObject(aResponse.contextElement, options)); }); return out; } }, toNgsi: function(contextData) { var ctxElements = Array.isArray(contextData) ? contextData : [contextData]; var ngsiElements = []; ctxElements.forEach(function(aElement) { ngsiElements.push(NgsiHelper.toNgsiObject(aElement)); }); return { contextElements: ngsiElements }; }, // Converts an object to a NGSI Object toNgsiObject: function(object) { var self = this; if (!object) { return null; } var out = { isPattern: object.pattern ? true : false, id: object.pattern }; var keys = Object.keys(object); keys.forEach(function(aKey) { var mapped = PROPERTY_MAP[aKey]; var extProperty = EXT_PROPERTY_MAP[aKey]; if (mapped && !extProperty) { out[mapped] = object[aKey]; return; } if (extProperty) { return; } out.attributes = out.attributes || []; var value = self._getAttrValue(object[aKey]); var attributeData = { name: aKey, value: value, type: self._typeOf(object[aKey]) }; // Now dealing with metadata (if present) var metadata = object[aKey] && object[aKey].metadata; if (metadata) { attributeData.metadatas = []; for (var p in metadata) { var metaValue = metadata[p]; attributeData.metadatas.push({ name: p, value: metaValue, type: self._typeOf(metaValue) }); } } // Geo-referenced data special case. Adding metadata. if (attributeData.type === 'coords') { attributeData.metadatas = attributeData.metadatas || []; attributeData.metadatas.push({ name: 'location', type: 'string', value: 'WGS84' }); } out.attributes.push(attributeData); }); out.toXMLTree = ngsiObj2XMLTree; out.toXML = ngsiObj2XML; return out; }, _getAttrValue: function(attrValue) { if (attrValue instanceof Attribute) { return attrValue.value; } return attrValue; }, _toValue: function(ngsiAttr) { var out = ngsiAttr.value; switch (ngsiAttr.type) { case 'number': case 'integer': case 'float': out = Number(out); break; case 'date': case 'datetime': case 'urn:x-ogc:def:trs:IDAS:1.0:ISO8601': out = new Date(out); if (isNaN(out.getTime())) { out = null; } break; } return out; }, _typeOf: function(attrData) { if (typeof attrData === 'undefined') { return null; } if (typeof attrData !== 'object') { return typeof attrData; } if (attrData.type && attrData.type === 'geo:point') { return 'coords'; } if (attrData.type) { return attrData.type; } var value = attrData.value; if (!value) { value = attrData; } var out = typeof value; if (out === 'object') { if (typeof value.getDate === 'function') { out = 'date'; } } return out; }, _attrList2Obj: function(attrList, options) { var self = this; var out = Object.create(null); if (!attrList) { return out; } var geoJson = false; if (options && options.GeoJSON) { geoJson = true; } if (Array.isArray(attrList)) { attrList.forEach(function(aAttr) { var value = self._toValue(aAttr); var geoJsonValue = null; if (Array.isArray(aAttr.metadatas)) { var metaObj = Object.create(null); aAttr.metadatas.forEach(function(aMeta) { if (aMeta.name === 'location' && aMeta.value === 'WGS84') { if (geoJson) { var coords = value.split(','); geoJsonValue = { type: 'Point', coordinates: [parseFloat(coords[1]), parseFloat(coords[0])] }; } else { aAttr.type = 'geo:point'; } } else { metaObj[aMeta.name] = self._toValue(aMeta); } }); // If finally there is no other metadata reset the metaObj if (Object.keys(metaObj).length === 0) { metaObj = undefined; } // If the attribute has metadata, then the value is a compound one // represented by the Attribute object value = new Attribute(value, aAttr.type, metaObj); } out[aAttr.name] = geoJsonValue || value; }); } return out; }, _toObject: function(contextElement, options) { if (!contextElement) { return null; } var out = this._attrList2Obj(contextElement.attributes, options); out.type = contextElement.type; out.id = contextElement.id; return out; }, buildSubscription: function(entity, subscriptionParams) { var entities = Array.isArray(entity) ? entity : [entity]; var subscription = { entities: [], notifyConditions: [] }; var entityList = subscription.entities; entities.forEach(function(aEntity) { if (Array.isArray(aEntity.attributes)) { subscription.attributes = aEntity.attributes; } delete aEntity.attributes; entityList.push(NgsiHelper.toNgsiObject(aEntity)); }); var subscribedAttrs = []; if (Array.isArray(subscriptionParams.attributes)) { subscriptionParams.attributes.forEach(function(aAttr) { subscribedAttrs.push(aAttr); }); } subscription.notifyConditions[0] = { type: subscriptionParams.type || 'ONCHANGE', condValues: subscribedAttrs }; for (var option in subscriptionParams) { if (option === 'attributes' || option === 'type') { continue; } if (option === 'callback') { subscription.reference = subscriptionParams.callback; continue; } subscription[option] = subscriptionParams[option]; } // Infinite duration by default subscription.duration = subscription.duration || 'P10Y'; return subscription; }, buildRegistration: function(entity, registrationParams) { var entities = Array.isArray(entity) ? entity : [entity]; var registration = { contextRegistrations: [] }; entities.forEach(function(aEntity) { var aRegistration = { entities: [], attributes: [], providingApplication: aEntity.callback || registrationParams.callback }; var entityList = aRegistration.entities; var attrList = aRegistration.attributes; if (Array.isArray(aEntity.attributes)) { aEntity.attributes.forEach(function(aAttr) { attrList.push(NgsiHelper._toAttr(aAttr)); }); delete aEntity.attributes; } entityList.push(NgsiHelper.toNgsiObject(aEntity)); registration.contextRegistrations.push(aRegistration); // We are not checking for attribute duplicity if (Array.isArray(registrationParams.attributes)) { registrationParams.attributes.forEach(function(aAttr) { attrList.push(NgsiHelper._toAttr(aAttr)); }); } }); for (var option in registrationParams) { if (option === 'providingApplication' || option === 'attributes' || option === 'callback') { continue; } registration[option] = registrationParams[option]; } return registration; }, _toAttr: function(attrData) { if (typeof attrData === 'string') { return { name: attrData, type: 'string', isDomain: false }; } attrData.isDomain = false; return attrData; }, buildUpdate: function(contextData, action) { var request = this.toNgsi(contextData); request.updateAction = action; return request; }, buildQuery: function(queryParameters, options) { // TODO: Consider better approach for dealing with multi query if (Array.isArray(queryParameters)) { var out = { entities: [] } var self = this; queryParameters.forEach(function(aParameter) { out.entities.push(self.toNgsiObject(aParameter)) }); return out; } // If no id is provided then it is assumed any var params = JSON.parse(JSON.stringify(queryParameters)); if (!params.id && !params.pattern) { params.pattern = '.*'; } var out = { entities: [ this.toNgsiObject(params) ], attributes: queryParameters.attributes }; if (options && options.q) { out.restriction = { scopes: [ { type: 'FIWARE::StringQuery', value: options.q } ] }; } if (options && options.location) { var location = options.location; var scopeValue = Object.create(null); var theCoords = location.coords.split(','); if (!out.restriction) { out.restriction = { scopes: [] }; } out.restriction.scopes.push({ type: 'FIWARE::Location', value: scopeValue }); var geometryData = location.geometry.split(';'); var geometry = geometryData[0].trim(); switch (geometry) { case 'Circle': scopeValue.circle = { centerLatitude: theCoords[0].trim(), centerLongitude: theCoords[1].trim(), radius: String(location.radius) }; break; case 'Polygon': var vertices = []; scopeValue.polygon = { 'vertices': vertices }; for (var j = 0; j < theCoords.length; j += 2) { vertices.push({ latitude: theCoords[j].trim(), longitude: theCoords[j + 1].trim() }); } break; } if (geometryData[1] && geometryData[1].trim() === 'external') { scopeValue[geometry.toLowerCase()].inverted = 'true'; } } return out; }, buildNgsiResponse: function(data) { var dataAsNgsi = NgsiHelper.toNgsiObject(data); var out = { contextResponses: [] }; out.contextResponses[0] = { contextElement: dataAsNgsi, statusCode: { code: 200, reasonPhrase: 'OK' } }; out.toXMLTree = ngsiResponse2XMLTree; out.toXML = ngsiResponse2XML; return out; }, parseNgsiRequest: function(chunk) { var out = { entities: [] }; var parsedChunk = chunk; if (typeof chunk === 'string') { try { parsedChunk = JSON.parse(chunk); } catch (e) { return this._parseNgsiRequestXML(chunk); } } var entities = parsedChunk.entities || parsedChunk.contextElements; entities.forEach(function(aEntity) { var obj = NgsiHelper._toObject(aEntity); out.entities.push(obj); }); out.attributes = parsedChunk.attributes; return out; }, toURL: function(queryParameters) { var resourceName = 'contextEntities'; var path = queryParameters.id; if (!queryParameters.id && queryParameters.type) { resourceName = 'contextEntityTypes'; path = queryParameters.type; } var out = [resourceName]; out.push(encodeURIComponent(path)); if (queryParameters.attributes) { out.push('attributes'); out.push(encodeURIComponent(queryParameters.attributes[0])); } return out.join('/'); }, _parseNgsiRequestXML: function(chunk) { var out = { entities: [], attributes: [] }; var regExp1 = /<entityId type=\"(\w+)\"\s+isPattern=\"(\w+)\">/g; var regExp2 = /<id>(\S+)<\/id>/g; var regExp3 = /<attribute>(\w+)<\/attribute>/g; var match1 = regExp1.exec(chunk); while (match1 !== null) { var entity = { type: match1[1] }; if (match1[2] === 'true') { entity.pattern = 'yes'; } else { entity.id = 'yes'; } out.entities.push(entity); match1 = regExp1.exec(chunk); } var match2 = regExp2.exec(chunk); var index = 0; while (match2 !== null) { if (out.entities[index].pattern === 'yes') { out.entities[index].pattern = match2[1]; } else { out.entities[index].id = match2[1]; } match2 = regExp2.exec(chunk); index++; } var match3 = regExp3.exec(chunk); while (match3 !== null) { out.attributes.push(match3[1]); match3 = regExp3.exec(chunk); } return out; }, _parseXML: function(chunk) { var objs = []; var START_ELEMENT = '<contextElement>'; var END_ELEMENT = '</contextElement>'; var START_ATTR = '<contextAttribute>'; var END_ATTR = '</contextAttribute>'; var entityRegExp = /<entityId type=\"(\w+)\"\s+isPattern=\"(\w+)\">/; var idRegExp = /<id>(\S+)<\/id>/; var attrNameRegExp = /<name>(\w+)<\/name>/; var typeRegExp = /<type>(\w+)<\/type>/; var valueRegExp = /<contextValue>(.+)<\/contextValue>/; var remainingStr = chunk; while (true) { var startElement = remainingStr.indexOf(START_ELEMENT); var endElement = remainingStr.indexOf(END_ELEMENT); if (startElement === -1) { break; } var elementChunk = remainingStr.substring( startElement + START_ELEMENT.length, endElement); var entityMatching = entityRegExp.exec(elementChunk); if (!Array.isArray(entityMatching)) { return null; } var type = entityMatching[1]; var isPattern = entityMatching[2]; var id = idRegExp.exec(elementChunk)[1]; var attrList = []; var remainingAttrs = elementChunk; while (true) { var startAttr = remainingAttrs.indexOf(START_ATTR); var endAttr = remainingAttrs.indexOf(END_ATTR); if (startAttr === -1) { break; } var attrChunk = remainingAttrs.substring( startAttr + START_ATTR.length, endAttr); var attrName = attrNameRegExp.exec(attrChunk)[1]; var attrType; var typeMatch = typeRegExp.exec(attrChunk); if (Array.isArray(typeMatch)) { attrType = typeMatch[1]; } var attrVal = valueRegExp.exec(attrChunk)[1]; attrList.push({ name: attrName, type: attrType, value: attrVal }); remainingAttrs = remainingAttrs.substring(endAttr + END_ATTR.length); } var myObj = this._attrList2Obj(attrList); myObj.type = type; if (isPattern === 'true') { myObj.pattern = id; } else { myObj.id = id; } objs.push(myObj); remainingStr = remainingStr.substring(endElement + END_ELEMENT.length); } if (objs.length === 1) { return objs[0]; } else if (objs.length === 0) { return null; } return objs; } }; var theWindow = this.window || null; if (!theWindow) { exports.NgsiHelper = NgsiHelper; exports.Attribute = Attribute; exports.XmlBuilder = require('./xml-builder.js'); }