/* ****************************************************************************
*
* attributeDomainName - 
*/
static std::string attributeDomainName(std::string path, std::string value, ParseData* reqData)
{
   LM_T(LmtParse, ("Got an attributeDomainName"));
   reqData->ucer.res.attributeDomainName.set(value);
   return "OK";
}
/* ****************************************************************************
*
* errorCodeCode - 
*/
static int errorCodeCode(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a errorCode code: '%s'", node->value()));
  reqData->rcrs.res.errorCode.code = (HttpStatusCode) atoi(node->value());
  return 0;
}
/* ****************************************************************************
*
* errorCodeDetails - 
*/
static int errorCodeDetails(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got an errorCode details string: '%s'", node->value()));
  reqData->rcrs.res.errorCode.details = node->value();
  return 0;
}
/* ****************************************************************************
*
* mongoConnect -
*
* Default value for writeConcern == 1 (0: unacknowledged, 1: acknowledged)
*/
static DBClientBase* mongoConnect
(
    const char*  host,
    const char*  db,
    const char*  rplSet,
    const char*  username,
    const char*  passwd,
    bool         multitenant,
    int          writeConcern,
    double       timeout
)
{
    std::string   err;
    DBClientBase* connection = NULL;

    LM_T(LmtMongo, ("Connection info: dbName='%s', rplSet='%s', timeout=%f", db, rplSet, timeout));

    bool connected     = false;
    int  retries       = RECONNECT_RETRIES;

    if (strlen(rplSet) == 0)
    {
        /* Setting the first argument to true is to use autoreconnect */
        connection = new DBClientConnection(true);

        /* Not sure of how to generalize the following code, given that DBClientBase class doesn't have a common connect() method (surprisingly) */
        for (int tryNo = 0; tryNo < retries; ++tryNo)
        {
            if ( ((DBClientConnection*)connection)->connect(host, err))
            {
                connected = true;
                break;
            }

            if (tryNo == 0)
            {
                LM_E(("Database Startup Error (cannot connect to mongo - doing %d retries with a %d microsecond interval)", retries, RECONNECT_DELAY));
            }
            else
            {
                LM_T(LmtMongo, ("Try %d connecting to mongo failed", tryNo));
            }

            usleep(RECONNECT_DELAY * 1000); // usleep accepts microseconds
        }
    }
    else
    {
        LM_T(LmtMongo, ("Using replica set %s", rplSet));

        // autoReconnect is always on for DBClientReplicaSet connections.
        std::vector<std::string>  hostTokens;
        int components = stringSplit(host, ',', hostTokens);

        std::vector<HostAndPort> rplSetHosts;
        for (int ix = 0; ix < components; ix++)
        {
            LM_T(LmtMongo, ("rplSet host <%s>", hostTokens[ix].c_str()));
            rplSetHosts.push_back(HostAndPort(hostTokens[ix]));
        }

        connection = new DBClientReplicaSet(rplSet, rplSetHosts, timeout);

        /* Not sure of to generalize the following code, given that DBClientBase class hasn't a common connect() method (surprisingly) */
        for (int tryNo = 0; tryNo < retries; ++tryNo)
        {
            if ( ((DBClientReplicaSet*)connection)->connect())
            {
                connected = true;
                break;
            }

            if (tryNo == 0)
            {
                LM_E(("Database Startup Error (cannot connect to mongo - doing %d retries with a %d microsecond interval)", retries, RECONNECT_DELAY));
            }
            else
            {
                LM_T(LmtMongo, ("Try %d connecting to mongo failed", tryNo));
            }

            usleep(RECONNECT_DELAY * 1000); // usleep accepts microseconds
        }
    }

    if (connected == false)
    {
        LM_E(("Database Error (connection failed, after %d retries: '%s')", retries, err.c_str()));
        return NULL;
    }

    LM_I(("Successful connection to database"));

    //
    // WriteConcern
    //
    mongo::WriteConcern writeConcernCheck;

    // In legacy driver writeConcern is no longer an int, but a class. We need a small
    // conversion step here
    mongo::WriteConcern wc = writeConcern == 1 ? mongo::WriteConcern::acknowledged : mongo::WriteConcern::unacknowledged;

    connection->setWriteConcern((mongo::WriteConcern) wc);
    writeConcernCheck = (mongo::WriteConcern) connection->getWriteConcern();

    if (writeConcernCheck.nodes() != wc.nodes())
    {
        LM_E(("Database Error (Write Concern not set as desired)"));
        return NULL;
    }
    LM_T(LmtMongo, ("Active DB Write Concern mode: %d", writeConcern));

    /* Authentication is different depending if multiservice is used or not. In the case of not
     * using multiservice, we authenticate in the single-service database. In the case of using
     * multiservice, it isn't a default database that we know at contextBroker start time (when
     * this connection function is invoked) so we authenticate on the admin database, which provides
     * access to any database */
    if (multitenant)
    {
        if (strlen(username) != 0 && strlen(passwd) != 0)
        {
            if (!connection->auth("admin", std::string(username), std::string(passwd), err))
            {
                LM_E(("Database Startup Error (authentication: db='admin', username='******', password='******': %s)", username, err.c_str()));
                return NULL;
            }
        }
    }
    else
    {
        if (strlen(db) != 0 && strlen(username) != 0 && strlen(passwd) != 0)
        {
            if (!connection->auth(std::string(db), std::string(username), std::string(passwd), err))
            {
                LM_E(("Database Startup Error (authentication: db='%s', username='******', password='******': %s)", db, username, err.c_str()));
                return NULL;
            }
        }
    }

    /* Get mongo version with the 'buildinfo' command */
    BSONObj result;
    std::string extra;
    connection->runCommand("admin", BSON("buildinfo" << 1), result);
    std::string versionString = std::string(result.getStringField("version"));
    if (!versionParse(versionString, mongoVersionMayor, mongoVersionMinor, extra))
    {
        LM_E(("Database Startup Error (invalid version format: %s)", versionString.c_str()));
        return NULL;
    }
    LM_T(LmtMongo, ("mongo version server: %s (mayor: %d, minor: %d, extra: %s)", versionString.c_str(), mongoVersionMayor, mongoVersionMinor, extra.c_str()));

    return connection;
}
/* ****************************************************************************
*
* duration - 
*/
static int duration(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a duration: '%s'", node->value()));
  reqData->rcrs.res.duration.set(node->value());
  return 0;
}
/* ****************************************************************************
*
* contextMetadataType - 
*/
static int contextMetadataType(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a metadata type '%s'", node->value()));
  reqData->acer.metadataP->type = node->value();
  return 0;
}
/* ****************************************************************************
*
* attributeDomainName - 
*/
static int attributeDomainName(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got an attributeDomainName"));
  reqData->acer.res.attributeDomainName.set(node->value());
  return 0;
}
/* ****************************************************************************
*
* polygon -
*/
static int polygon(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a polygon"));
  reqData->scr.scopeP->areaType = orion::PolygonType;
  return 0;
}
/* ****************************************************************************
*
* polygonVertexList -
*/
static int polygonVertexList(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a polygonVertexList"));
  return 0;
}
/* ****************************************************************************
*
* circleCenterLongitude -
*/
static int circleCenterLongitude(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a circleCenterLongitude: %s", node->value()));
  reqData->scr.scopeP->circle.center.longitudeSet(node->value());
  return 0;
}
/* ****************************************************************************
*
* circleRadius -
*/
static int circleRadius(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a circleRadius: %s", node->value()));
  reqData->scr.scopeP->circle.radiusSet(node->value());
  return 0;
}
/* ****************************************************************************
*
* circle -
*/
static int circle(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a circle"));
  reqData->scr.scopeP->areaType = orion::CircleType;
  return 0;
}
/* ****************************************************************************
*
* scopeType -
*/
static int scopeType(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a scopeType: '%s'", node->value()));
  reqData->scr.scopeP->type = node->value();
  return 0;
}
/* ****************************************************************************
*
* contextAttributeValue - 
*/
static std::string contextAttributeValue(std::string path, std::string value, ParseData* reqData)
{
   LM_T(LmtParse, ("Got an attribute value: %s", value.c_str()));
   reqData->ucer.attributeP->value = value;
   return "OK";
}
/* ****************************************************************************
*
* mongoAttributesForEntityType -
*/
HttpStatusCode mongoAttributesForEntityType
(
  std::string                           entityType,
  EntityTypeResponse*         responseP,
  const std::string&                    tenant,
  const std::vector<std::string>&       servicePathV,
  std::map<std::string, std::string>&   uriParams
)
{  
  unsigned int   offset         = atoi(uriParams[URI_PARAM_PAGINATION_OFFSET].c_str());
  unsigned int   limit          = atoi(uriParams[URI_PARAM_PAGINATION_LIMIT].c_str());
  std::string    detailsString  = uriParams[URI_PARAM_PAGINATION_DETAILS];
  bool           details        = (strcasecmp("on", detailsString.c_str()) == 0)? true : false;
  bool           reqSemTaken    = false;

  // Setting the name of the entity type for the response
  responseP->entityType.type = entityType;

  LM_T(LmtMongo, ("Query Types Attribute for <%s>", entityType.c_str()));
  LM_T(LmtPagination, ("Offset: %d, Limit: %d, Details: %s", offset, limit, (details == true)? "true" : "false"));

  reqSemTake(__FUNCTION__, "query types attributes request", SemReadOp, &reqSemTaken);


  /* Compose query based on this aggregation command:   
   *
   * db.runCommand({aggregate: "entities",
   *                pipeline: [ {$match: { "_id.type": "TYPE" , "_id.servicePath": /.../ } },
   *                            {$project: {_id: 1, "attrNames": 1} },
   *                            {$unwind: "$attrNames"},
   *                            {$group: {_id: "$_id.type", attrs: {$addToSet: "$attrNames"}} },
   *                            {$unwind: "$attrs"},
   *                            {$group: {_id: "$attrs" }},
   *                            {$sort: {_id: 1}}
   *                          ]
   *                })
   *
   */

  BSONObj result;
  BSONObj cmd = BSON("aggregate" << COL_ENTITIES <<
                     "pipeline" << BSON_ARRAY(
                                              BSON("$match" << BSON(C_ID_ENTITY << entityType << C_ID_SERVICEPATH << fillQueryServicePath(servicePathV))) <<
                                              BSON("$project" << BSON("_id" << 1 << ENT_ATTRNAMES << 1)) <<
                                              BSON("$unwind" << S_ATTRNAMES) <<
                                              BSON("$group" << BSON("_id" << CS_ID_ENTITY << "attrs" << BSON("$addToSet" << S_ATTRNAMES))) <<
                                              BSON("$unwind" << "$attrs") <<
                                              BSON("$group" << BSON("_id" << "$attrs")) <<
                                              BSON("$sort" << BSON("_id" << 1))
                                             )
                    );

  std::string err;
  if (!runCollectionCommand(composeDatabaseName(tenant), cmd, &result, &err))
  {
    responseP->statusCode.fill(SccReceiverInternalError, err);
    reqSemGive(__FUNCTION__, "query types request", reqSemTaken);
    return SccOk;
  }

  /* Processing result to build response */
  LM_T(LmtMongo, ("aggregation result: %s", result.toString().c_str()));

  std::vector<BSONElement> resultsArray = getField(result, "result").Array();

  responseP->entityType.count = countEntities(tenant, servicePathV, entityType);

  if (resultsArray.size() == 0)
  {
    responseP->statusCode.fill(SccContextElementNotFound);
    reqSemGive(__FUNCTION__, "query types request", reqSemTaken);
    return SccOk;
  }

  /* See comment above in the other method regarding this strategy to implement pagination */
  for (unsigned int ix = offset; ix < MIN(resultsArray.size(), offset + limit); ++ix)
  {
    BSONElement  idField    = resultsArray[ix].embeddedObject().getField("_id");

    //
    // BSONElement::eoo returns true if 'not found', i.e. the field "_id" doesn't exist in 'sub'
    //
    // Now, if 'resultsArray[ix].embeddedObject().getField("_id")' is not found, if we continue,
    // calling embeddedObject() on it, then we get an exception and the broker crashes.
    //
    if (idField.eoo() == true)
    {
      LM_E(("Database Error (error retrieving _id field in doc: %s)", resultsArray[ix].embeddedObject().toString().c_str()));
      continue;
    }

    /* Note that we need and extra query() to the database (inside attributeType() function) to get each attribute type.
     * This could be unefficient, specially if the number of attributes is large */
    std::string attrType = attributeType(tenant, servicePathV, entityType , idField.str());

    ContextAttribute*  ca = new ContextAttribute(idField.str(), attrType, "");
    responseP->entityType.contextAttributeVector.push_back(ca);
  }

  char detailsMsg[256];
  if (responseP->entityType.contextAttributeVector.size() > 0)
  {
    if (details)
    {
      snprintf(detailsMsg, sizeof(detailsMsg), "Count: %d", (int) resultsArray.size());
      responseP->statusCode.fill(SccOk, detailsMsg);
    }
    else
    {
      responseP->statusCode.fill(SccOk);
    }
  }
  else
  {
    if (details)
    {
      snprintf(detailsMsg, sizeof(detailsMsg), "Number of attributes: %d. Offset is %d", (int) resultsArray.size(), offset);
      responseP->statusCode.fill(SccContextElementNotFound, detailsMsg);
    }
    else
    {
      responseP->statusCode.fill(SccContextElementNotFound);
    }
  }

  reqSemGive(__FUNCTION__, "query types request", reqSemTaken);

  return SccOk;
}
/* ****************************************************************************
*
* polygonVertexLongitude -
*/
static int polygonVertexLongitude(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a polygonVertexLongitude: %s", node->value()));
  reqData->scr.vertexP->longitudeSet(node->value());
  return 0;
}
Exemple #17
0
/* ****************************************************************************
*
* xmlNullTreat -
*
* Some of the nodes in the XML tree are known but no action needs to ne taken.
* Especially the start of a container.
*
* We need this to distinguish between XML nodes that are 'ok but no treatment needed' and
* XML nodes that are just 'not recognized' and should give a parse error (or they belong
* to a Compound).
*/
int nullTreat(xml_node<>* node, ParseData* parseDataP)
{
  LM_T(LmtNullNode, ("Node '%s' is recognized but no action is needed", node->name()));

  return 0;
}
/* ****************************************************************************
*
* reference -
*/
static int reference(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a reference: '%s'", node->value()));
  reqData->scr.res.reference.set(node->value());
  return 0;
}
/* ****************************************************************************
*
* domainMetadataValue - 
*/
static int domainMetadataValue(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a metadata value '%s'", node->value()));
  reqData->acer.domainMetadataP->stringValue = node->value();
  return 0;
}
/* ****************************************************************************
*
* notifyConditionType -
*/
static int notifyConditionType(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a Notify Condition Type: '%s'", node->value()));
  reqData->scr.notifyConditionP->type = node->value();
  return 0;
}
/* ****************************************************************************
*
* contextAttributeType - 
*/
static int contextAttributeType(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got an attribute type: %s", node->value()));
  reqData->acer.attributeP->type = node->value();
  return 0;
}
/* ****************************************************************************
*
* condValue -
*/
static int condValue(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a Cond Value: '%s'", node->value()));
  reqData->scr.notifyConditionP->condValueList.push_back(node->value());
  return 0;
}
/* ****************************************************************************
*
* mongoSubscribeContext - 
*/
HttpStatusCode mongoSubscribeContext
(
  SubscribeContextRequest*             requestP,
  SubscribeContextResponse*            responseP,
  const std::string&                   tenant,
  std::map<std::string, std::string>&  uriParam,
  const std::string&                   xauthToken,
  const std::vector<std::string>&      servicePathV
)
{
    const std::string  notifyFormatAsString  = uriParam[URI_PARAM_NOTIFY_FORMAT];
    Format             notifyFormat          = stringToFormat(notifyFormatAsString);
    std::string        servicePath           = (servicePathV.size() == 0)? "" : servicePathV[0];    
    bool               reqSemTaken           = false;

    LM_T(LmtMongo, ("Subscribe Context Request: notifications sent in '%s' format", notifyFormatAsString.c_str()));

    reqSemTake(__FUNCTION__, "ngsi10 subscribe request", SemWriteOp, &reqSemTaken);

    //
    // Calculate expiration (using the current time and the duration field in the request).
    // If expiration is not present, use a default value
    //
    long long expiration = -1;
    if (requestP->expires > 0)
    {
      expiration = requestP->expires;
    }
    else
    {
      if (requestP->duration.isEmpty())
      {
        requestP->duration.set(DEFAULT_DURATION);
      }

      expiration = getCurrentTime() + requestP->duration.parse();
    }
    LM_T(LmtMongo, ("Subscription expiration: %lu", expiration));

    /* Create the mongoDB subscription document */
    BSONObjBuilder  sub;
    OID             oid;

    oid.init();

    sub.append("_id", oid);
    sub.append(CSUB_EXPIRATION, expiration);
    sub.append(CSUB_REFERENCE,  requestP->reference.get());


    /* Throttling */
    long long throttling = 0;
    if (requestP->throttling.seconds != -1)
    {
      throttling = (long long) requestP->throttling.seconds;
      sub.append(CSUB_THROTTLING, throttling);
    }
    else if (!requestP->throttling.isEmpty())
    {
      throttling = (long long) requestP->throttling.parse();
      sub.append(CSUB_THROTTLING, throttling);
    }

    if (servicePath != "")
    {
      sub.append(CSUB_SERVICE_PATH, servicePath);
    }

    
    /* Build entities array */
    BSONArrayBuilder entities;
    for (unsigned int ix = 0; ix < requestP->entityIdVector.size(); ++ix)
    {
        EntityId* en = requestP->entityIdVector[ix];

        if (en->type == "")
        {
          entities.append(BSON(CSUB_ENTITY_ID << en->id <<
                               CSUB_ENTITY_ISPATTERN << en->isPattern));
        }
        else
        {
          entities.append(BSON(CSUB_ENTITY_ID << en->id <<
                               CSUB_ENTITY_TYPE << en->type <<
                               CSUB_ENTITY_ISPATTERN << en->isPattern));
        }
    }
    sub.append(CSUB_ENTITIES, entities.arr());

    /* Build attributes array */
    BSONArrayBuilder attrs;
    for (unsigned int ix = 0; ix < requestP->attributeList.size(); ++ix) {
        attrs.append(requestP->attributeList[ix]);
    }
    sub.append(CSUB_ATTRS, attrs.arr());

    /* Build conditions array (including side-effect notifications and threads creation) */
    bool notificationDone = false;
    BSONArray conds = processConditionVector(&requestP->notifyConditionVector,
                                             requestP->entityIdVector,
                                             requestP->attributeList, oid.toString(),
                                             requestP->reference.get(),
                                             &notificationDone,
                                             notifyFormat,
                                             tenant,
                                             xauthToken,
                                             servicePathV,
                                             requestP->expression.q);
    sub.append(CSUB_CONDITIONS, conds);

    /* Build expression */
    BSONObjBuilder expression;

    expression << CSUB_EXPR_Q << requestP->expression.q
               << CSUB_EXPR_GEOM << requestP->expression.geometry
               << CSUB_EXPR_COORDS << requestP->expression.coords
               << CSUB_EXPR_GEOREL << requestP->expression.georel;
    sub.append(CSUB_EXPR, expression.obj());

    /* Last notification */
    long long lastNotificationTime = 0;
    if (notificationDone)
    {
      lastNotificationTime = (long long) getCurrentTime();

      sub.append(CSUB_LASTNOTIFICATION, lastNotificationTime);
      sub.append(CSUB_COUNT, 1);
    }

    /* Adding format to use in notifications */
    sub.append(CSUB_FORMAT, notifyFormatAsString);

    /* Insert document in database */
    std::string err;
    if (!collectionInsert(getSubscribeContextCollectionName(tenant), sub.obj(), &err))
    {
      reqSemGive(__FUNCTION__, "ngsi10 subscribe request (mongo db exception)", reqSemTaken);
      responseP->subscribeError.errorCode.fill(SccReceiverInternalError, err);
      return SccOk;
    }

    //
    // 3. Create Subscription for the cache
    //
    std::string oidString = oid.toString();

    LM_T(LmtSubCache, ("inserting a new sub in cache (%s)", oidString.c_str()));

    cacheSemTake(__FUNCTION__, "Inserting subscription in cache");
    subCacheItemInsert(tenant.c_str(),
                       servicePath.c_str(),
                       requestP,
                       oidString.c_str(),
                       expiration,
                       throttling,
                       notifyFormat,
                       notificationDone,
                       lastNotificationTime,
                       requestP->expression.q,
                       requestP->expression.geometry,
                       requestP->expression.coords,
                       requestP->expression.georel);

    cacheSemGive(__FUNCTION__, "Inserting subscription in cache");

    reqSemGive(__FUNCTION__, "ngsi10 subscribe request", reqSemTaken);

    /* Fill int the response element */
    responseP->subscribeResponse.duration = requestP->duration;
    responseP->subscribeResponse.subscriptionId.set(oid.toString());
    responseP->subscribeResponse.throttling = requestP->throttling;

    return SccOk;
}
/* ****************************************************************************
*
* throttling -
*/
static int throttling(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a throttling: '%s'", node->value()));
  reqData->scr.res.throttling.set(node->value());
  return 0;
}
/* ****************************************************************************
*
* registrationId - 
*/
static int registrationId(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got a registration id: '%s'", node->value()));
  reqData->rcrs.res.registrationId.set(node->value());
  return 0;
}
/* ****************************************************************************
*
* entityIdId -
*/
static int entityIdId(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got an entityId:id: '%s'", node->value()));
  reqData->scr.entityIdP->id = node->value();
  return 0;
}
/* ****************************************************************************
*
* errorCodeReasonPhrase - 
*/
static int errorCodeReasonPhrase(xml_node<>* node, ParseData* reqData)
{
  LM_T(LmtParse, ("Got an errorCode reason phrase: '%s'", node->value()));
  reqData->rcrs.res.errorCode.reasonPhrase = node->value();
  return 0;
}
/* ****************************************************************************
*
* mongoEntityTypes -
*/
HttpStatusCode mongoEntityTypes
(
  EntityTypeVectorResponse*                  responseP,
  const std::string&                    tenant,
  const std::vector<std::string>&       servicePathV,
  std::map<std::string, std::string>&   uriParams
)
{
  unsigned int   offset         = atoi(uriParams[URI_PARAM_PAGINATION_OFFSET].c_str());
  unsigned int   limit          = atoi(uriParams[URI_PARAM_PAGINATION_LIMIT].c_str());
  std::string    detailsString  = uriParams[URI_PARAM_PAGINATION_DETAILS];
  bool           details        = (strcasecmp("on", detailsString.c_str()) == 0)? true : false;  
  bool           reqSemTaken    = false;

  LM_T(LmtMongo, ("Query Entity Types"));
  LM_T(LmtPagination, ("Offset: %d, Limit: %d, Details: %s", offset, limit, (details == true)? "true" : "false"));

  reqSemTake(__FUNCTION__, "query types request", SemReadOp, &reqSemTaken);

  /* Compose query based on this aggregation command:  
   *
   * db.runCommand({aggregate: "entities",
   *                pipeline: [ {$match: { "_id.servicePath": /.../ } },
   *                            {$project: {_id: 1, "attrNames": 1} },
   *                            {$project: { "attrNames"
   *                                  {$cond: [ {$eq: [ "$attrNames", [ ] ] }, [null], "$attrNames"] }
   *                               }
   *                            },
   *                            {$unwind: "$attrNames"},
   *                            {$group: {_id: "$_id.type", attrs: {$addToSet: "$attrNames"}} },
   *                            {$sort: {_id: 1} }
   *                          ]
   *                })   
   *
   * The $cond part is hard... more information at http://stackoverflow.com/questions/27510143/empty-array-prevents-document-to-appear-in-query
   * As a consequence, some "null" values may appear in the resulting attrs vector, which are pruned by the result processing logic.
   *
   * FIXME P6: in the future, we can interpret the collapse parameter at this layer. If collapse=true so we don't need attributes, the
   * following command can be used:
   *
   * db.runCommand({aggregate: "entities", pipeline: [ {$group: {_id: "$_id.type"} }]})
   *
   *
   *
   */

  BSONObj result;

  //
  // Building the projection part of the query that includes types that have no attributes
  // See bug: https://github.com/telefonicaid/fiware-orion/issues/686
  //
  BSONArrayBuilder  emptyArrayBuilder;
  BSONArrayBuilder  nulledArrayBuilder;

  nulledArrayBuilder.appendNull();

  // We are using the $cond: [ .. ] and not the $cond: { .. } one, as the former is the only one valid in MongoDB 2.4
  BSONObj projection = BSON(
    "$project" << BSON(
      ENT_ATTRNAMES << BSON(
        "$cond" << BSON_ARRAY(
          BSON("$eq" << BSON_ARRAY(S_ATTRNAMES << emptyArrayBuilder.arr()) ) <<
          nulledArrayBuilder.arr() <<
          S_ATTRNAMES
        )
      )
    )
  );

  BSONObj cmd = BSON("aggregate" << COL_ENTITIES <<
                     "pipeline" << BSON_ARRAY(
                                              BSON("$match" << BSON(C_ID_SERVICEPATH << fillQueryServicePath(servicePathV))) <<
                                              BSON("$project" << BSON("_id" << 1 << ENT_ATTRNAMES << 1)) <<
                                              projection <<
                                              BSON("$unwind" << S_ATTRNAMES) <<
                                              BSON("$group" << BSON("_id" << CS_ID_ENTITY << "attrs" << BSON("$addToSet" << S_ATTRNAMES))) <<
                                              BSON("$sort" << BSON("_id" << 1))
                                             )
                     );

  std::string err;
  if (!runCollectionCommand(composeDatabaseName(tenant), cmd, &result, &err))
  {
    responseP->statusCode.fill(SccReceiverInternalError, err);
    reqSemGive(__FUNCTION__, "query types request", reqSemTaken);
    return SccOk;
  }

  // Processing result to build response
  LM_T(LmtMongo, ("aggregation result: %s", result.toString().c_str()));

  std::vector<BSONElement> resultsArray = getField(result, "result").Array();

  if (resultsArray.size() == 0)
  {
    responseP->statusCode.fill(SccContextElementNotFound);
    reqSemGive(__FUNCTION__, "query types request", reqSemTaken);

    return SccOk;
  }

  /* Another strategy to implement pagination is to use the $skip and $limit operators in the
   * aggregation framework. However, doing so, we don't know the total number of results, which can
   * be needed in the case of details=on (using that approach, we need to do two queries: one to get
   * the count and other to get the actual results with $skip and $limit, in the same "transaction" to
   * avoid incoherence between both if some entity type is created or deleted in the process).
   *
   * However, considering that the number of types will be small compared with the number of entities,
   * the current approach seems to be ok
   */
  for (unsigned int ix = offset; ix < MIN(resultsArray.size(), offset + limit); ++ix)
  {
    BSONObj                   resultItem  = resultsArray[ix].embeddedObject();
    EntityType*               entityType  = new EntityType(getStringField(resultItem, "_id"));
    std::vector<BSONElement>  attrsArray  = getField(resultItem, "attrs").Array();

    entityType->count = countEntities(tenant, servicePathV, entityType->type);

    if (!attrsArray[0].isNull())
    {
      for (unsigned int jx = 0; jx < attrsArray.size(); ++jx)
      {
        /* This is where NULL elements in the resulting attrs vector are pruned */
        if (attrsArray[jx].isNull())
        {
          continue;
        }

        /* Note that we need and extra query() to the database (inside attributeType() function) to get each attribute type.
         * This could be unefficient, specially if the number of attributes is large */
        std::string attrType = attributeType(tenant, servicePathV, entityType->type , attrsArray[jx].str());

        ContextAttribute* ca = new ContextAttribute(attrsArray[jx].str(), attrType, "");
        entityType->contextAttributeVector.push_back(ca);
      }
    }

    responseP->entityTypeVector.push_back(entityType);
  }

  char detailsMsg[256];
  if (responseP->entityTypeVector.size() > 0)
  {
    if (details)
    {
      snprintf(detailsMsg, sizeof(detailsMsg), "Count: %d", (int) resultsArray.size());
      responseP->statusCode.fill(SccOk, detailsMsg);
    }
    else
    {
      responseP->statusCode.fill(SccOk);
    }
  }
  else
  {
    if (details)
    {      
      snprintf(detailsMsg, sizeof(detailsMsg), "Number of types: %d. Offset is %d", (int) resultsArray.size(), offset);
      responseP->statusCode.fill(SccContextElementNotFound, detailsMsg);
    }
    else
    {
      responseP->statusCode.fill(SccContextElementNotFound);
    }
  }

  reqSemGive(__FUNCTION__, "query types request", reqSemTaken);

  return SccOk;

}
/* ****************************************************************************
*
* attributeDomainName - 
*/
static std::string attributeDomainName(std::string path, std::string value, ParseData* parseDataP)
{
  LM_T(LmtParse, ("Got an attributeDomainName: '%s'", value.c_str()));
  parseDataP->ncr.cerP->contextElement.attributeDomainName.set(value);
  return "OK";
}
/* ****************************************************************************
*
* contextMetadataValue - 
*/
static std::string contextMetadataValue(std::string path, std::string value, ParseData* reqData)
{
   LM_T(LmtParse, ("Got a metadata value '%s'", value.c_str()));
   reqData->ucer.metadataP->value = value;
   return "OK";
}