Created
April 28, 2014 17:17
-
-
Save fgalan/11378281 to your computer and use it in GitHub Desktop.
mongoDiscoverContextAvailability.cpp @ IoT integration meeting Madrid April 2014
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* | |
* Copyright 2013 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 | |
* fermin at tid dot es | |
* | |
* Author: Ken Zangelin | |
*/ | |
#include <string> | |
#include "logMsg/logMsg.h" | |
#include "logMsg/traceLevels.h" | |
#include "common/statistics.h" | |
#include "common/sem.h" | |
#include "mongoBackend/mongoDiscoverContextAvailability.h" | |
#include "rest/HttpStatusCode.h" | |
#include "mongoBackend/MongoGlobal.h" | |
#include "ngsi/StatusCode.h" | |
#include "ngsi9/DiscoverContextAvailabilityRequest.h" | |
#include "ngsi9/DiscoverContextAvailabilityResponse.h" | |
#include "mongo/client/dbclient.h" | |
using namespace mongo; | |
/* **************************************************************************** | |
* | |
* associationsQuery - | |
*/ | |
bool associationsQuery(EntityIdVector enV, AttributeList attrL, std::string scope, MetadataVector* mdV, std::string* err) { | |
DBClientConnection* connection = getMongoConnection(); | |
/* Note that SCOPE_VALUE_ASSOC_SOURCE means that the argument is a target (so we use ASSOC_TARGET_ENT and | |
* ASSOC_ATTRS_TARGET in the query), while SCOPE_VALUE_ASSOC_TARGET means that the argument is a source (so we | |
* use ASSOC_SOURCE_ENT and ASSOC_ATTRS_source in the query) */ | |
BSONObjBuilder queryB; | |
/* Build query (entity part) */ | |
BSONArrayBuilder enArray; | |
for (unsigned int ix = 0; ix < enV.size() ; ++ix) { | |
enArray.append(BSON(ASSOC_ENT_ID << enV.get(ix)->id << ASSOC_ENT_TYPE << enV.get(ix)->type)); | |
} | |
BSONObj queryEn; | |
if (scope == SCOPE_VALUE_ASSOC_SOURCE) { | |
queryB.append(ASSOC_TARGET_ENT, BSON("$in" << enArray.arr())); | |
} | |
else { // SCOPE_VALUE_ASSOC_TARGET | |
queryB.append(ASSOC_SOURCE_ENT, BSON("$in" << enArray.arr())); | |
} | |
/* Build query (attribute part) */ | |
BSONArrayBuilder attrArray; | |
for (unsigned int ix = 0; ix < attrL.size() ; ++ix) { | |
attrArray.append(attrL.get(ix)); | |
} | |
std::string attrField; | |
if (scope == SCOPE_VALUE_ASSOC_SOURCE) { | |
attrField = std::string(ASSOC_ATTRS) + "." + ASSOC_ATTRS_TARGET; | |
} | |
else { // SCOPE_VALUE_ASSOC_TARGET | |
attrField = std::string(ASSOC_ATTRS) + "." + ASSOC_ATTRS_SOURCE; | |
} | |
queryB.append(attrField, BSON("$in" << attrArray.arr())); | |
/* Do query in MongoDB */ | |
BSONObj query = queryB.obj(); | |
auto_ptr<DBClientCursor> cursor; | |
try { | |
LM_T(LmtMongo, ("query() in '%s' collection: '%s'", getAssociationsCollectionName(), query.toString().c_str())); | |
mongoSemTake(__FUNCTION__, "query in AssociationsCollection"); | |
cursor = connection->query(getAssociationsCollectionName(), query); | |
/* | |
* We have observed that in some cases of DB errors (e.g. the database daemon is down) instead of | |
* raising an exception, the query() method sets the cursor to NULL. In this case, we raise the | |
* exception ourselves | |
*/ | |
if (cursor.get() == NULL) { | |
throw DBException("Null cursor from mongo (details on this is found in the source code)", 0); | |
} | |
mongoSemGive(__FUNCTION__, "query in AssociationsCollection"); | |
} | |
catch( const DBException &e ) { | |
mongoSemGive(__FUNCTION__, "query in AssociationsCollection (DBException)"); | |
*err = std::string("collection: ") + getAssociationsCollectionName() + | |
" - query(): " + query.toString() + | |
" - exception: " + e.what(); | |
return false; | |
} | |
catch(...) { | |
mongoSemGive(__FUNCTION__, "query in AssociationsCollection (Generic Exception)"); | |
*err = std::string("collection: ") + getAssociationsCollectionName() + | |
" - query(): " + query.toString() + | |
" - exception: " + "generic"; | |
return false; | |
} | |
/* Process query result */ | |
while (cursor->more()) { | |
BSONObj r = cursor->next(); | |
LM_T(LmtMongo, ("retrieved document: '%s'", r.toString().c_str())); | |
std::string name = STR_FIELD(r, "_id"); | |
std::string srcEnId = STR_FIELD(r.getField(ASSOC_SOURCE_ENT).embeddedObject(), ASSOC_ENT_ID); | |
std::string srcEnType = STR_FIELD(r.getField(ASSOC_SOURCE_ENT).embeddedObject(), ASSOC_ENT_TYPE); | |
std::string tgtEnId = STR_FIELD(r.getField(ASSOC_TARGET_ENT).embeddedObject(), ASSOC_ENT_ID); | |
std::string tgtEnType = STR_FIELD(r.getField(ASSOC_TARGET_ENT).embeddedObject(), ASSOC_ENT_TYPE); | |
Metadata* md = new Metadata(name, "Association"); | |
md->association.entityAssociation.source.id = srcEnId; | |
md->association.entityAssociation.source.type = srcEnType; | |
md->association.entityAssociation.source.isPattern = "false"; | |
md->association.entityAssociation.target.id = tgtEnId; | |
md->association.entityAssociation.target.type = tgtEnType; | |
md->association.entityAssociation.target.isPattern = "false"; | |
std::vector<BSONElement> attrs = r.getField(ASSOC_ATTRS).Array(); | |
for (unsigned int ix = 0; ix < attrs.size(); ++ix) { | |
std::string srcAttr = STR_FIELD(attrs[ix].embeddedObject(), ASSOC_ATTRS_SOURCE); | |
std::string tgtAttr = STR_FIELD(attrs[ix].embeddedObject(), ASSOC_ATTRS_TARGET); | |
AttributeAssociation* attrAssoc = new AttributeAssociation(); | |
attrAssoc->source = srcAttr; | |
attrAssoc->target = tgtAttr; | |
md->association.attributeAssociationList.push_back(attrAssoc); | |
} | |
mdV->push_back(md); | |
} | |
return true; | |
} | |
/* **************************************************************************** | |
* | |
* associationsDiscoverConvextAvailability - | |
*/ | |
static HttpStatusCode associationsDiscoverConvextAvailability(DiscoverContextAvailabilityRequest* requestP, DiscoverContextAvailabilityResponse* responseP, std::string scope) { | |
if (scope == SCOPE_VALUE_ASSOC_ALL) { | |
LM_W(("%s scope not supported", SCOPE_VALUE_ASSOC_ALL)); | |
responseP->errorCode.fill(SccNotImplemented, std::string("Not supported scope: '") + SCOPE_VALUE_ASSOC_ALL + "'"); | |
return SccOk; | |
} | |
MetadataVector mdV; | |
std::string err; | |
if (!associationsQuery(requestP->entityIdVector, requestP->attributeList, scope, &mdV, &err)) { | |
responseP->errorCode.fill(SccReceiverInternalError, std::string("Database error: ") + err); | |
LM_RE(SccOk,(responseP->errorCode.details.c_str())); | |
} | |
/* Query for associated entities */ | |
for (unsigned int ix = 0; ix < mdV.size(); ++ix) { | |
/* Each association involves a registrationsQuery() operation, accumulating the answer in | |
* responseP->responseVector */ | |
Metadata* md = mdV.get(ix); | |
EntityIdVector enV; | |
AttributeList attrL; | |
EntityId en; | |
if (scope == SCOPE_VALUE_ASSOC_SOURCE) { | |
en = EntityId(md->association.entityAssociation.source.id, md->association.entityAssociation.source.type); | |
} | |
else { // SCOPE_VALUE_ASSOC_TARGET | |
en = EntityId(md->association.entityAssociation.target.id, md->association.entityAssociation.target.type); | |
} | |
enV.push_back(&en); | |
for (unsigned int jx = 0; jx < md->association.attributeAssociationList.size(); ++jx) { | |
if (scope == SCOPE_VALUE_ASSOC_SOURCE) { | |
attrL.push_back(md->association.attributeAssociationList.get(jx)->source); | |
} | |
else { | |
attrL.push_back(md->association.attributeAssociationList.get(jx)->target); | |
} | |
} | |
ContextRegistrationResponseVector crrV; | |
if (!registrationsQuery(enV, attrL, &crrV, &err)) { | |
responseP->errorCode.fill(SccReceiverInternalError, err); | |
LM_RE(SccOk,(responseP->errorCode.details.c_str())); | |
} | |
/* Accumulate in responseP */ | |
for (unsigned int jx = 0; jx < crrV.size(); ++jx) { | |
responseP->responseVector.push_back(crrV.get(jx)); | |
} | |
} | |
if (responseP->responseVector.size() != 0) | |
{ | |
/* Set association metadata as final ContextRegistrationResponse*/ | |
ContextRegistrationResponse* crrMd = new ContextRegistrationResponse(); | |
crrMd->contextRegistration.providingApplication.set("http://www.fi-ware.eu/NGSI/association"); | |
crrMd->contextRegistration.registrationMetadataVector = mdV; | |
responseP->responseVector.push_back(crrMd); | |
} | |
else | |
{ | |
// | |
// FIXME P10: Two problems with associations discovered during Integration Meeting in Madrid 2014 | |
// | |
// The two problems are (both discoveries with associations): | |
// 1. When nothing is found the response lacks the ErrorCode but it has that 'default' response | |
// 2. When the discovery has an empty attributeList, nothing is found | |
// | |
// It works just fine when something is found and the request contains a valid non-empty attributeList. | |
// | |
// Also, of course, the string "Include Associations" has to be "IncludeAssociations" - | |
// just change the define SCOPE_TYPE_ASSOC in Scope.h | |
// | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
// | |
// I needed these two lines (errorCode.fill + LM_RE) to give a better reply for nothing found, but in the case | |
// of something found, I cannot have these lines present. | |
// | |
// We need a harness test to take care of the following situations: | |
// | |
// 1. Association found, but no entity responds to the association | |
// 2. Association found and all ok, but the discovery has an empty AttributeList | |
// 3. Association found and the discovery has a 'correct' AttributeList | |
// 4. Association not found | |
// | |
// Point 3. is already covered, and perhaps point 4 also, but the other two | |
// I don't think have any harness test. | |
// | |
// Actually, there are 12 combinations here (2*2*3): | |
// 1. Association FOUND / NOT FOUND | |
// 2. Responding entity FOUND /NOT FOUND | |
// 3. AttributeList NOT THERE / THERE WITH OK ATTR / THERE WITH 'BAD' ATTR | |
// | |
// | |
// Finally, here are the two lines that I needed for 'nothing found' but that breaks 'something found' | |
// responseP->errorCode.fill(SccContextElementNotFound, "Just Testing"); | |
// LM_RE(SccOk, (responseP->errorCode.details.c_str())); | |
// | |
// Another thing we should fix: | |
// Uppercase 'A' in a few XML fields about associations: | |
// - AttributeAssociationList | |
// - AttributeAssociation | |
// These are probably/hopefully THE ONLY xml tags we have that start in uppercase ... | |
// | |
// I checked the tagnames in JSON also, but they aren't even there! | |
// WE DON'T SUPPORT associations in JSON ! | |
} | |
return SccOk; | |
} | |
/* **************************************************************************** | |
* | |
* conventionalDiscoverContextAvailability - | |
*/ | |
static HttpStatusCode conventionalDiscoverContextAvailability(DiscoverContextAvailabilityRequest* requestP, DiscoverContextAvailabilityResponse* responseP) { | |
std::string err; | |
if (!registrationsQuery(requestP->entityIdVector, requestP->attributeList, &responseP->responseVector, &err)) { | |
responseP->errorCode.fill(SccReceiverInternalError, err); | |
LM_RE(SccOk,(responseP->errorCode.details.c_str())); | |
} | |
if (responseP->responseVector.size() == 0) { | |
/* If the responseV is empty, we haven't found any entity and have to fill the status code part in the | |
* response */ | |
responseP->errorCode.fill(SccContextElementNotFound); | |
return SccOk; | |
} | |
return SccOk; | |
} | |
/* **************************************************************************** | |
* | |
* mongoDiscoverContextAvailability - | |
*/ | |
HttpStatusCode mongoDiscoverContextAvailability(DiscoverContextAvailabilityRequest* requestP, DiscoverContextAvailabilityResponse* responseP) | |
{ | |
reqSemTake(__FUNCTION__, "mongo ngsi9 discovery request"); | |
LM_T(LmtMongo, ("DiscoverContextAvailability Request")); | |
/* Depending on the scope used, we invoke one function or other. DiscoverContextAvailability may behave | |
* differently depending on the scope. Although OperationScope is a list in NGSI, we only support one | |
* scope at the same time */ | |
int nScopes = requestP->restriction.scopeVector.size(); | |
if (nScopes > 0) { | |
if (nScopes > 1) { | |
LM_W(("Using %d scopes: only the first one will be used", nScopes)); | |
} | |
std::string scopeType = requestP->restriction.scopeVector.get(0)->type; | |
std::string scopeValue = requestP->restriction.scopeVector.get(0)->value; | |
if (scopeType == SCOPE_TYPE_ASSOC) { | |
HttpStatusCode ms = associationsDiscoverConvextAvailability(requestP, responseP, scopeValue); | |
reqSemGive(__FUNCTION__, "mongo ngsi9 discovery request (association)"); | |
if (responseP->responseVector.size() != 0) | |
return ms; | |
else | |
LM_W(("Associations: Nothing found - doing conventional discoverContextAvailability")); | |
} | |
else { | |
LM_W(("Unsupported scope (%s, %s), doing conventional discoverContextAvailability", scopeType.c_str(), scopeValue.c_str())); | |
} | |
} | |
HttpStatusCode hsCode = conventionalDiscoverContextAvailability(requestP, responseP); | |
if (hsCode != SccOk) | |
++noOfDiscoveryErrors; | |
reqSemGive(__FUNCTION__, "mongo ngsi9 discovery request"); | |
return hsCode; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment