From 4a101338acd789dfd78f143c7c81560392db4544 Mon Sep 17 00:00:00 2001 From: albertopq Date: Tue, 17 Jan 2017 10:33:03 +0100 Subject: [PATCH] [Issue #204] Only create 1 FeatureOfInterest per Thing (#216) --- src/models/association.js | 69 ++++++++++++++++++++++++++++++++------- test/common.js | 4 ++- test/test_observations.js | 33 +++++++++++++++++++ 3 files changed, 94 insertions(+), 12 deletions(-) diff --git a/src/models/association.js b/src/models/association.js index 7c934d5..2ae2f83 100644 --- a/src/models/association.js +++ b/src/models/association.js @@ -15,6 +15,7 @@ import { featuresOfInterest, iotId, locations, + observations, observedProperties, sensors, things @@ -32,17 +33,24 @@ import { /* * Converts a Location into a FeatureOfInterest */ -const featureOfInterestFromLocation = location => { - if (!location) { +const featureOfInterestFromLocation = locationObject => { + if (!locationObject) { return null; } + const options = locationObject.$modelOptions && locationObject.$modelOptions; + if (options && options.name.plural === featuresOfInterest) { + return { + '@iot.id': locationObject.id + }; + } + + const location = locationObject.dataValues || locationObject; const feature = Object.assign({}, { name: location.name, description: location.description, encodingType: location.encodingType }); - feature.feature = Object.assign({}, location.location); Reflect.deleteProperty(feature, 'location'); return feature; @@ -358,19 +366,35 @@ const maybeCreate = (transaction, instance, req, exclude, thingLocation) => { }); } +const sameLocation = (loc1, loc2) => { + if (!loc1 || !loc2) { + return false; + } + return JSON.stringify(loc1) === JSON.stringify(loc2); +} + const getLocationFromDatastream = (datastreamEntity) => { return db().then(models => { + const includeDatastreamsAndLocations = [ + models[locations], { + model: models[datastreams], + include: { + model: models[observations], + include: [models[featuresOfInterest]] + } + } + ]; if (datastreamEntity[iotId]) { return findLocation(datastreams, datastreamEntity[iotId], { model: models[things], - include: models[locations] + include: includeDatastreamsAndLocations }); } if (datastreamEntity.Thing) { if (datastreamEntity.Thing[iotId]) { return findLocation(things, datastreamEntity.Thing[iotId], - models[locations]); + includeDatastreamsAndLocations); } if (datastreamEntity.Thing.Locations) { @@ -387,31 +411,54 @@ const getLocationFromDatastream = (datastreamEntity) => { }); } + const findLocation = (modelName, id, include = []) => { return db().then(models => { return models[modelName].findOne({ where: { id: id }, include: include }).then(instance => { + let _currentThing, loc; try { - let _currentLoc; switch (modelName) { case datastreams: - _currentLoc = instance.Thing.Locations[0]; + _currentThing = instance.Thing; + loc = _currentThing.Locations[0]; break; case things: - _currentLoc = instance.Locations[0]; + _currentThing = instance; + loc = instance.Locations[0]; break; case locations: - _currentLoc = instance; + loc = instance; break; default: break; } - return _currentLoc.dataValues; } catch(e) { - return null; + loc = null; + } + + if (!_currentThing) { + return loc; + } + + // We need to check if for any of the Datastreams of that Thing, + // already exists an Observation which FeatureOfInterest matches + // the last location of the Thing. + const thingDatastreams = _currentThing.Datastreams || []; + for (let i = 0; i < thingDatastreams.length; i++) { + const currentDatastream = thingDatastreams[i]; + for (let j = 0; j < currentDatastream.Observations.length; j++) { + const currentObservation = currentDatastream.Observations[j]; + const foi = currentObservation.FeatureOfInterest; + if (sameLocation(foi && foi.feature, loc && loc.location)) { + return foi; + } + } } + + return loc; }); }); } diff --git a/test/common.js b/test/common.js index 50425b1..041735f 100644 --- a/test/common.js +++ b/test/common.js @@ -423,7 +423,9 @@ module.exports = (endpoint, port, mandatory, optional = []) => { res.header.location.should.be.equal(fullPrepath + path); const expectedModels = Object.keys(expected); Promise.all(expectedModels.map(name => { - return models[name].findAndCountAll().then(result => { + return models[name].findAndCountAll({ + order: 'id desc' + }).then(result => { const resultObject = {}; resultObject[name] = result; return Promise.resolve(resultObject); diff --git a/test/test_observations.js b/test/test_observations.js index 3aead34..058a6fc 100644 --- a/test/test_observations.js +++ b/test/test_observations.js @@ -113,6 +113,39 @@ commonTests(observations, 8885, mandatory, optional).then(tester => { }); }); + describe('More than one Observation without FeatureOfInterest', () => { + let body, countObject; + beforeEach(done => { + db().then(models => { + const datastreamEntity = Object.assign({}, DatastreamsEntity); + datastreamEntity.Thing.Locations = LocationsEntity; + models[datastreams].create(datastreamEntity, { + include: { + model: models.Things, include: { model: models.Locations } + } + }).then(relation => { + body = Object.assign({}, ObservationsEntity); + body.Datastream = { + '@iot.id': relation.id + }; + Reflect.deleteProperty(body, featureOfInterest); + countObject = { + 'Observations': { count: 1 }, + 'Datastreams': { count: 1 }, + 'FeaturesOfInterest': { count: 1 } + }; + tester.postSuccess(done, body, countObject); + }); + }); + }); + + it('should create only 1 FeatureOfInterest for a Datastream with ' + + 'Observations and a linked FeatureOfInterest', done => { + countObject.Observations.count++; + tester.postSuccess(done, body, countObject); + }); + }); + it('should return 201 when linking a Datastream entity with a linked ' + 'Thing', done => { db().then(models => {