ErrorPtr PropertyContainer::accessProperty(PropertyAccessMode aMode, ApiValuePtr aQueryObject, ApiValuePtr aResultObject, int aDomain, PropertyDescriptorPtr aParentDescriptor) { ErrorPtr err; #if DEBUGFOCUSLOGGING FOCUSLOG("\naccessProperty: entered with query = %s\n", aQueryObject->description().c_str()); if (aParentDescriptor) { FOCUSLOG("- parentDescriptor '%s' (%s), fieldKey=%u, objectKey=%u\n", aParentDescriptor->name(), aParentDescriptor->isStructured() ? "structured" : "scalar", aParentDescriptor->fieldKey(), aParentDescriptor->objectKey()); } #endif // for reading, NULL query is like query { "":NULL } if (aQueryObject->isNull() && aMode==access_read) { aQueryObject->setType(apivalue_object); aQueryObject->add("", aQueryObject->newValue(apivalue_null)); } // aApiObject must be of type apivalue_object if (!aQueryObject->isType(apivalue_object)) return ErrorPtr(new VdcApiError(415, "Query or Value written must be object")); if (aMode==access_read) { if (!aResultObject) return ErrorPtr(new VdcApiError(415, "accessing property for read must provide result object")); aResultObject->setType(apivalue_object); // must be object } // Iterate trough elements of query object aQueryObject->resetKeyIteration(); string queryName; ApiValuePtr queryValue; string errorMsg; while (aQueryObject->nextKeyValue(queryName, queryValue)) { FOCUSLOG("- starting to process query element named '%s' : %s\n", queryName.c_str(), queryValue->description().c_str()); if (aMode==access_read && queryName=="#") { // asking for number of elements at this level -> generate and return int value queryValue = queryValue->newValue(apivalue_int64); // integer queryValue->setInt32Value(numProps(aDomain, aParentDescriptor)); aResultObject->add(queryName, queryValue); } else { // accessing an element or series of elements at this level bool wildcard = isMatchAll(queryName); // - find all descriptor(s) for this queryName PropertyDescriptorPtr propDesc; int propIndex = 0; bool foundone = false; do { propDesc = getDescriptorByName(queryName, propIndex, aDomain, aParentDescriptor); if (propDesc) { foundone = true; // found at least one descriptor for this query element FOCUSLOG(" - processing descriptor '%s' (%s), fieldKey=%u, objectKey=%u\n", propDesc->name(), propDesc->isStructured() ? "structured" : "scalar", propDesc->fieldKey(), propDesc->objectKey()); // actually access by descriptor if (propDesc->isStructured()) { ApiValuePtr subQuery; // property is a container. Now check the value if (queryValue->isType(apivalue_object)) { subQuery = queryValue; // query specifies next level, just use it } else if (queryName!="*" && (!wildcard || propDesc->isWildcardAddressable())) { // don't recurse deeper when query name is "*" or property is not wildcard-adressable // special case is "*" as leaf in query - only recurse if it is not present // - autocreate subquery subQuery = queryValue->newValue(apivalue_object); subQuery->add("", queryValue->newValue(apivalue_null)); } if (subQuery) { // addressed property is a container by itself -> recurse // - get the PropertyContainer int containerDomain = aDomain; // default to same, but getContainer may modify it PropertyDescriptorPtr containerPropDesc = propDesc; PropertyContainerPtr container = getContainer(containerPropDesc, containerDomain); if (container) { FOCUSLOG(" - container for '%s' is 0x%p\n", propDesc->name(), container.get()); FOCUSLOG(" >>>> RECURSING into accessProperty()\n"); if (aMode==access_read) { // read needs a result object ApiValuePtr resultValue = queryValue->newValue(apivalue_object); err = container->accessProperty(aMode, subQuery, resultValue, containerDomain, containerPropDesc); if (Error::isOK(err)) { // add to result with actual name (from descriptor) FOCUSLOG("\n <<<< RETURNED from accessProperty() recursion\n"); FOCUSLOG(" - accessProperty of container for '%s' returns %s\n", propDesc->name(), resultValue->description().c_str()); aResultObject->add(propDesc->name(), resultValue); } } else { // for write, just pass the query value err = container->accessProperty(aMode, subQuery, ApiValuePtr(), containerDomain, containerPropDesc); FOCUSLOG(" <<<< RETURNED from accessProperty() recursion\n", propDesc->name(), container.get()); } if ((aMode!=access_read) && Error::isOK(err)) { // give this container a chance to post-process write access err = writtenProperty(aMode, propDesc, aDomain, container); } // 404 errors are collected, but dont abort the query if (Error::isError(err, VdcApiError::domain(), 404)) { if (!errorMsg.empty()) errorMsg += "; "; errorMsg += string_format("Error(s) accessing subproperties of '%s' : { %s }", queryName.c_str(), err->description().c_str()); err.reset(); // forget the error on this level } } } } else { // addressed (and known by descriptor!) property is a simple value field -> access it if (aMode==access_read) { // read access: create a new apiValue and have it filled ApiValuePtr fieldValue = queryValue->newValue(propDesc->type()); // create a value of correct type to get filled bool accessOk = accessField(aMode, fieldValue, propDesc); // read // for read, not getting an OK from accessField means: property does not exist (even if known per descriptor), // so it will not be added to the result if (accessOk) { // add to result with actual name (from descriptor) aResultObject->add(propDesc->name(), fieldValue); } FOCUSLOG(" - accessField for '%s' returns %s\n", propDesc->name(), fieldValue->description().c_str()); } else { // write access: just pass the value if (!accessField(aMode, queryValue, propDesc)) { // write err = ErrorPtr(new VdcApiError(403,string_format("Write access to '%s' denied", propDesc->name()))); } } } } else { // no descriptor found for this query element // Note: this means that property is not KNOWN, which IS not the same as getting false from accessField // (latter means that property IS known, but has no value in the context it was queried) // HOWEVER, for the vdc API it was decided that for reading, these cases should NOT be // distinguished for getProperty. If a property does not exist for either reason, the return tree just does // no contain that property. // Also note that not having a property is not the same as not having a property VALUE: // latter case will explicitly return a NULL value if (!wildcard && !foundone && aMode!=access_read) { // query did address a specific property but it is unknown -> report as error (for read AND write!) // - collect error message, but do not abort query processing if (!errorMsg.empty()) errorMsg += "; "; errorMsg += string_format("Unknown property '%s' -> ignored", queryName.c_str()); } } } while (Error::isOK(err) && propIndex!=PROPINDEX_NONE); } // now generate error if we have collected a non-empty error message if (!errorMsg.empty()) { err = ErrorPtr(new VdcApiError(404,errorMsg)); } #if DEBUGLOGGING if (aMode==access_read) { FOCUSLOG("- query element named '%s' now has result object: %s\n", queryName.c_str(), aResultObject->description().c_str()); } #endif } return err; }
ErrorPtr DaliDeviceContainer::handleMethod(VdcApiRequestPtr aRequest, const string &aMethod, ApiValuePtr aParams) { ErrorPtr respErr; if (aMethod=="x-p44-groupDevices") { // create a composite device out of existing single-channel ones ApiValuePtr components; long long collectionID = -1; DeviceVector groupedDevices; respErr = checkParam(aParams, "members", components); if (Error::isOK(respErr)) { if (components->isType(apivalue_object)) { components->resetKeyIteration(); string dimmerType; ApiValuePtr o; while (components->nextKeyValue(dimmerType, o)) { DsUid memberUID; memberUID.setAsBinary(o->binaryValue()); bool deviceFound = false; // search for this device for (DeviceVector::iterator pos = devices.begin(); pos!=devices.end(); ++pos) { // only non-grouped DALI devices can be grouped DaliDevicePtr dev = boost::dynamic_pointer_cast<DaliDevice>(*pos); if (dev && dev->getDsUid() == memberUID) { deviceFound = true; // found this device, create DB entry for it db.executef( "INSERT OR REPLACE INTO compositeDevices (dimmerUID, dimmerType, collectionID) VALUES ('%s','%s',%lld)", memberUID.getString().c_str(), dimmerType.c_str(), collectionID ); if (collectionID<0) { // use rowid of just inserted item as collectionID collectionID = db.last_insert_rowid(); // - update already inserted first record db.executef( "UPDATE compositeDevices SET collectionID=%lld WHERE ROWID=%lld", collectionID, collectionID ); } // remember groupedDevices.push_back(dev); // done break; } } if (!deviceFound) { respErr = ErrorPtr(new WebError(404, "some devices of the group could not be found")); break; } } if (Error::isOK(respErr) && groupedDevices.size()>0) { // all components inserted into DB // - remove grouped single devices for (DeviceVector::iterator pos = groupedDevices.begin(); pos!=groupedDevices.end(); ++pos) { (*pos)->hasVanished(false); // vanish, but keep settings } // - re-collect devices to find grouped composite now, but only in a second starting from main loop, not from here CompletedCB cb = boost::bind(&DaliDeviceContainer::groupCollected, this, aRequest); MainLoop::currentMainLoop().executeOnce(boost::bind(&DaliDeviceContainer::collectDevices, this, cb, false, false), 1*Second); } } } } else { respErr = inherited::handleMethod(aRequest, aMethod, aParams); } return respErr; }