/** * @file lleventdispatcher.cpp * @author Nat Goodspeed * @date 2009-06-18 * @brief Implementation for lleventdispatcher. * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #if LL_WINDOWS #pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally #endif // Precompiled header #include "linden_common.h" // associated header #include "lleventdispatcher.h" // STL headers // std headers // external library headers // other Linden headers #include "llevents.h" #include "llerror.h" #include "llexception.h" #include "llsdutil.h" #include "stringize.h" #include // std::quoted() #include // std::auto_ptr /***************************************************************************** * LLSDArgsMapper *****************************************************************************/ /** * From a formal parameters description and a map of arguments, construct an * arguments array. * * That is, given: * - an LLSD array of length n containing parameter-name strings, * corresponding to the arguments of a function of interest * - an LLSD collection specifying default parameter values, either: * - an LLSD array of length m <= n, matching the rightmost m params, or * - an LLSD map explicitly stating default name=value pairs * - an LLSD map of parameter names and actual values for a particular * function call * construct an LLSD array of actual argument values for this function call. * * The parameter-names array and the defaults collection describe the function * being called. The map might vary with every call, providing argument values * for the described parameters. * * The array of parameter names must match the number of parameters expected * by the function of interest. * * If you pass a map of default parameter values, it provides default values * as you might expect. It is an error to specify a default value for a name * not listed in the parameters array. * * If you pass an array of default parameter values, it is mapped to the * rightmost m of the n parameter names. It is an error if the default-values * array is longer than the parameter-names array. Consider the following * parameter names: ["a", "b", "c", "d"]. * * - An empty array of default values (or an isUndefined() value) asserts that * every one of the above parameter names is required. * - An array of four default values [1, 2, 3, 4] asserts that every one of * the above parameters is optional. If the current parameter map is empty, * they will be passed to the function as [1, 2, 3, 4]. * - An array of two default values [11, 12] asserts that parameters "a" and * "b" are required, while "c" and "d" are optional, having default values * "c"=11 and "d"=12. * * The arguments array is constructed as follows: * * - Arguments-map keys not found in the parameter-names array are ignored. * - Entries from the map provide values for an improper subset of the * parameters named in the parameter-names array. This results in a * tentative values array with "holes." (size of map) + (number of holes) = * (size of names array) * - Holes are filled with the default values. * - Any remaining holes constitute an error. */ class LL_COMMON_API LLEventDispatcher::LLSDArgsMapper { public: /// Accept description of function: function name, param names, param /// default values LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults); /// Given arguments map, return LLSD::Array of parameter values, or /// trigger error. LLSD map(const LLSD& argsmap) const; private: static std::string formatlist(const LLSD&); template void callFail(ARGS&&... args) const; // The function-name string is purely descriptive. We want error messages // to be able to indicate which function's LLSDArgsMapper has the problem. std::string _function; // Store the names array pretty much as given. LLSD _names; // Though we're handed an array of name strings, it's more useful to us to // store it as a map from name string to position index. Of course that's // easy to generate from the incoming names array, but why do it more than // once? typedef std::map IndexMap; IndexMap _indexes; // Generated array of default values, aligned with the array of param names. LLSD _defaults; // Indicate whether we have a default value for each param. typedef std::vector FilledVector; FilledVector _has_dft; }; LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults): _function(function), _names(names), _has_dft(names.size()) { if (! (_names.isUndefined() || _names.isArray())) { callFail(" names must be an array, not ", names); } auto nparams(_names.size()); // From _names generate _indexes. for (size_t ni = 0, nend = _names.size(); ni < nend; ++ni) { _indexes[_names[ni]] = ni; } // Presize _defaults() array so we don't have to resize it more than once. // All entries are initialized to LLSD(); but since _has_dft is still all // 0, they're all "holes" for now. if (nparams) { _defaults[nparams - 1] = LLSD(); } if (defaults.isUndefined() || defaults.isArray()) { auto ndefaults = defaults.size(); // defaults is a (possibly empty) array. Right-align it with names. if (ndefaults > nparams) { callFail(" names array ", names, " shorter than defaults array ", defaults); } // Offset by which we slide defaults array right to right-align with // _names array auto offset = nparams - ndefaults; // Fill rightmost _defaults entries from defaults, and mark them as // filled for (size_t i = 0, iend = ndefaults; i < iend; ++i) { _defaults[i + offset] = defaults[i]; _has_dft[i + offset] = 1; } } else if (defaults.isMap()) { // defaults is a map. Use it to populate the _defaults array. LLSD bogus; for (LLSD::map_const_iterator mi(defaults.beginMap()), mend(defaults.endMap()); mi != mend; ++mi) { IndexMap::const_iterator ixit(_indexes.find(mi->first)); if (ixit == _indexes.end()) { bogus.append(mi->first); continue; } auto pos = ixit->second; // Store default value at that position in the _defaults array. _defaults[pos] = mi->second; // Don't forget to record the fact that we've filled this // position. _has_dft[pos] = 1; } if (bogus.size()) { callFail(" defaults specified for nonexistent params ", formatlist(bogus)); } } else { callFail(" defaults must be a map or an array, not ", defaults); } } LLSD LLEventDispatcher::LLSDArgsMapper::map(const LLSD& argsmap) const { if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray())) { callFail(" map() needs a map or array, not ", argsmap); } // Initialize the args array. Indexing a non-const LLSD array grows it // to appropriate size, but we don't want to resize this one on each // new operation. Just make it as big as we need before we start // stuffing values into it. LLSD args(LLSD::emptyArray()); if (_defaults.size() == 0) { // If this function requires no arguments, fast exit. (Don't try to // assign to args[-1].) return args; } args[_defaults.size() - 1] = LLSD(); // Get a vector of chars to indicate holes. It's tempting to just scan // for LLSD::isUndefined() values after filling the args array from // the map, but it's plausible for caller to explicitly pass // isUndefined() as the value of some parameter name. That's legal // since isUndefined() has well-defined conversions (default value) // for LLSD data types. So use a whole separate array for detecting // holes. (Avoid std::vector which is known to be odd -- can we // iterate?) FilledVector filled(args.size()); if (argsmap.isArray()) { // Fill args from array. If there are too many args in passed array, // ignore the rest. auto size(argsmap.size()); if (size > args.size()) { // We don't just use std::min() because we want to sneak in this // warning if caller passes too many args. LL_WARNS("LLSDArgsMapper") << _function << " needs " << args.size() << " params, ignoring last " << (size - args.size()) << " of passed " << size << ": " << argsmap << LL_ENDL; size = args.size(); } for (LLSD::Integer i(0); i < size; ++i) { // Copy the actual argument from argsmap args[i] = argsmap[i]; // Note that it's been filled filled[i] = 1; } } else { // argsmap is in fact a map. Walk the map. for (LLSD::map_const_iterator mi(argsmap.beginMap()), mend(argsmap.endMap()); mi != mend; ++mi) { // mi->first is a parameter-name string, with mi->second its // value. Look up the name's position index in _indexes. IndexMap::const_iterator ixit(_indexes.find(mi->first)); if (ixit == _indexes.end()) { // Allow for a map containing more params than were passed in // our names array. Caller typically receives a map containing // the function name, cruft such as reqid, etc. Ignore keys // not defined in _indexes. LL_DEBUGS("LLSDArgsMapper") << _function << " ignoring " << mi->first << "=" << mi->second << LL_ENDL; continue; } auto pos = ixit->second; // Store the value at that position in the args array. args[pos] = mi->second; // Don't forget to record the fact that we've filled this // position. filled[pos] = 1; } } // Fill any remaining holes from _defaults. LLSD unfilled(LLSD::emptyArray()); for (size_t i = 0, iend = args.size(); i < iend; ++i) { if (! filled[i]) { // If there's no default value for this parameter, that's an // error. if (! _has_dft[i]) { unfilled.append(_names[i]); } else { args[i] = _defaults[i]; } } } // If any required args -- args without defaults -- were left unfilled // by argsmap, that's a problem. if (unfilled.size()) { callFail(" missing required arguments ", formatlist(unfilled), " from ", argsmap); } // done return args; } std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list) { std::ostringstream out; const char* delim = ""; for (LLSD::array_const_iterator li(list.beginArray()), lend(list.endArray()); li != lend; ++li) { out << delim << li->asString(); delim = ", "; } return out.str(); } template void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const { LLEventDispatcher::sCallFail (_function, std::forward(args)...); } /***************************************************************************** * LLEventDispatcher *****************************************************************************/ LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): LLEventDispatcher(desc, key, "args") {} LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key, const std::string& argskey): mDesc(desc), mKey(key), mArgskey(argskey) {} LLEventDispatcher::~LLEventDispatcher() { } LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): mDesc(desc) {} /** * DispatchEntry subclass used for callables accepting(const LLSD&) */ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry { LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required): DispatchEntry(desc), mFunc(func), mRequired(required) {} Callable mFunc; LLSD mRequired; LLSD call(const std::string& desc, const LLSD& event, bool, const std::string&) const override { // Validate the syntax of the event itself. std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { LLEventDispatcher::sCallFail (desc, ": bad request: ", mismatch); } // Event syntax looks good, go for it! mFunc(event); return {}; } LLSD addMetadata(LLSD meta) const override { meta["required"] = mRequired; return meta; } }; /** * DispatchEntry subclass for passing LLSD to functions accepting * arbitrary argument types (convertible via LLSDParam) */ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry { ParamsDispatchEntry(const std::string& name, const std::string& desc, const invoker_function& func): DispatchEntry(desc), mName(name), mInvoker(func) {} std::string mName; invoker_function mInvoker; LLSD call(const std::string&, const LLSD& event, bool, const std::string&) const override { try { return mInvoker(event); } catch (const LL::apply_error& err) { // could hit runtime errors with LL::apply() return callFail(err.what()); } } template LLSD callFail(ARGS&&... args) const { LLEventDispatcher::sCallFail(mName, ": ", std::forward(args)...); // pacify the compiler return {}; } }; /** * DispatchEntry subclass for dispatching LLSD::Array to functions accepting * arbitrary argument types (convertible via LLSDParam) */ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry { ArrayParamsDispatchEntry(const std::string& name, const std::string& desc, const invoker_function& func, LLSD::Integer arity): ParamsDispatchEntry(name, desc, func), mArity(arity) {} LLSD::Integer mArity; LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override { // std::string context { stringize(desc, "(", event, ") with argskey ", std::quoted(argskey), ": ") }; // Whether we try to extract arguments from 'event' depends on whether // the LLEventDispatcher consumer called one of the (name, event) // methods (! fromMap) or one of the (event) methods (fromMap). If we // were called with (name, event), the passed event must itself be // suitable to pass to the registered callable, no args extraction // required or even attempted. Only if called with plain (event) do we // consider extracting args from that event. Initially assume 'event' // itself contains the arguments. LLSD args{ event }; if (fromMap) { if (! mArity) { // When the target function is nullary, and we're called from // an (event) method, just ignore the rest of the map entries. args.clear(); } else { // We only require/retrieve argskey if the target function // isn't nullary. For all others, since we require an LLSD // array, we must have an argskey. if (argskey.empty()) { return callFail("LLEventDispatcher has no args key"); } if ((! event.has(argskey))) { return callFail("missing required key ", std::quoted(argskey)); } args = event[argskey]; } } return ParamsDispatchEntry::call(desc, args, fromMap, argskey); } LLSD addMetadata(LLSD meta) const override { LLSD array(LLSD::emptyArray()); // Resize to number of arguments required if (mArity) array[mArity - 1] = LLSD(); llassert_always(array.size() == mArity); meta["required"] = array; return meta; } }; /** * DispatchEntry subclass for dispatching LLSD::Map to functions accepting * arbitrary argument types (convertible via LLSDParam) */ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry { MapParamsDispatchEntry(const std::string& name, const std::string& desc, const invoker_function& func, const LLSD& params, const LLSD& defaults): ParamsDispatchEntry(name, desc, func), mMapper(name, params, defaults), mRequired(LLSD::emptyMap()) { // Build the set of all param keys, then delete the ones that are // optional. What's left are the ones that are required. for (LLSD::array_const_iterator pi(params.beginArray()), pend(params.endArray()); pi != pend; ++pi) { mRequired[pi->asString()] = LLSD(); } if (defaults.isArray() || defaults.isUndefined()) { // Right-align the params and defaults arrays. auto offset = params.size() - defaults.size(); // Now the name of every defaults[i] is at params[i + offset]. for (size_t i(0), iend(defaults.size()); i < iend; ++i) { // Erase this optional param from mRequired. mRequired.erase(params[i + offset].asString()); // Instead, make an entry in mOptional with the default // param's name and value. mOptional[params[i + offset].asString()] = defaults[i]; } } else if (defaults.isMap()) { // if defaults is already a map, then it's already in the form we // intend to deliver in metadata mOptional = defaults; // Just delete from mRequired every key appearing in mOptional. for (LLSD::map_const_iterator mi(mOptional.beginMap()), mend(mOptional.endMap()); mi != mend; ++mi) { mRequired.erase(mi->first); } } } LLSDArgsMapper mMapper; LLSD mRequired; LLSD mOptional; LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override { // by default, pass the whole event as the arguments map LLSD args{ event }; // Were we called by one of the (event) methods (instead of the (name, // event) methods), do we have an argskey, and does the incoming event // have that key? if (fromMap && (! argskey.empty()) && event.has(argskey)) { // if so, extract the value of argskey from the incoming event, // and use that as the arguments map args = event[argskey]; } // Now convert args from LLSD map to LLSD array using mMapper, then // pass to base-class call() method. return ParamsDispatchEntry::call(desc, mMapper.map(args), fromMap, argskey); } LLSD addMetadata(LLSD meta) const override { meta["required"] = mRequired; meta["optional"] = mOptional; return meta; } }; void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, const std::string& desc, const invoker_function& invoker, LLSD::Integer arity) { // The first parameter to ArrayParamsDispatchEntry is solely for error // messages. Identify our instance and this entry. mDispatch.emplace( name, new ArrayParamsDispatchEntry(stringize(*this, '[', name, ']'), desc, invoker, arity)); } void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, const std::string& desc, const invoker_function& invoker, const LLSD& params, const LLSD& defaults) { // Pass instance info as well as this entry name for error messages. mDispatch.emplace( name, new MapParamsDispatchEntry(stringize(*this, '[', name, ']'), desc, invoker, params, defaults)); } /// Register a callable by name void LLEventDispatcher::add(const std::string& name, const std::string& desc, const Callable& callable, const LLSD& required) { mDispatch.emplace(name, new LLSDDispatchEntry(desc, callable, required)); } /// Unregister a callable bool LLEventDispatcher::remove(const std::string& name) { DispatchMap::iterator found = mDispatch.find(name); if (found == mDispatch.end()) { return false; } mDispatch.erase(found); return true; } /// Call a registered callable with an explicitly-specified name. It is an /// error if no such callable exists. LLSD LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const { return try_call(std::string(), name, event); } bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const { try { try_call(std::string(), name, event); return true; } // Note that we don't catch the generic DispatchError, only the specific // DispatchMissing. try_call() only promises to return false if the // specified callable name isn't found -- not for general errors. catch (const DispatchMissing&) { return false; } } /// Extract the @a key value from the incoming @a event, and call the callable /// whose name is specified by that map @a key. It is an error if no such /// callable exists. LLSD LLEventDispatcher::operator()(const LLSD& event) const { return try_call(mKey, event[mKey], event); } bool LLEventDispatcher::try_call(const LLSD& event) const { try { try_call(mKey, event[mKey], event); return true; } catch (const DispatchMissing&) { return false; } } LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name, const LLSD& event) const { if (name.empty()) { if (key.empty()) { callFail("attempting to call with no name"); } else { callFail("no ", key); } } DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { // Here we were passed a valid name, but there's no registered // callable with that name. This is the one case in which we throw // DispatchMissing instead of the generic DispatchError. // Distinguish the public method by which our caller reached here: // key.empty() means the name was passed explicitly, non-empty means // we extracted the name from the incoming event using that key. if (key.empty()) { callFail(std::quoted(name), " not found"); } else { callFail("bad ", key, " value ", std::quoted(name)); } } // Found the name, so it's plausible to even attempt the call. return found->second->call(stringize(*this, " calling ", std::quoted(name)), event, (! key.empty()), mArgskey); } template //static void LLEventDispatcher::sCallFail(ARGS&&... args) { auto error = stringize(std::forward(args)...); LL_WARNS("LLEventDispatcher") << error << LL_ENDL; LLTHROW(EXCEPTION(error)); } template void LLEventDispatcher::callFail(ARGS&&... args) const { // Describe this instance in addition to the error itself. sCallFail(*this, ": ", std::forward(args)...); } LLSD LLEventDispatcher::getMetadata(const std::string& name) const { DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { return LLSD(); } LLSD meta; meta["name"] = name; meta["desc"] = found->second->mDesc; return found->second->addMetadata(meta); } std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) { // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that. return out << LLError::Log::classname(self) << '(' << self.mDesc << ')'; } /***************************************************************************** * LLDispatchListener *****************************************************************************/ std::string LLDispatchListener::mReplyKey{ "reply" }; /*==========================================================================*| TODO: * When process() finds name.isArray(), construct response array from dispatching each call -- args must also be (array of args structures) (could also construct response map, IF array contains unique names) * When process() finds name.isMap(), construct response map from dispatching each call -- value of each key is its args struct -- argskey ignored -- note, caller can't care about order * Possible future transactional behavior: look up all names before calling any |*==========================================================================*/ bool LLDispatchListener::process(const LLSD& event) const { // Collecting errors is only meaningful with a reply key. Without one, if // an error occurs, let the exception propagate. auto returned = call("", event, (! event.has(mReplyKey))); std::string& error{ returned.first }; LLSD& result{ returned.second }; if (! error.empty()) { // Here there was an error and the incoming event has mReplyKey -- // else DispatchError would already have propagated out of the call() // above. Reply with a map containing an "error" key explaining the // problem. reply(llsd::map("error", error), event); return false; } // We seem to have gotten a valid result. But we don't know whether the // registered callable is void or non-void. If it's void, // LLEventDispatcher returned isUndefined(). Otherwise, try to send it // back to our invoker. if (result.isDefined()) { if (! result.isMap()) { // wrap the result in a map as the "data" key result = llsd::map("data", result); } reply(result, event); } return false; } // Pass empty name to call LLEventDispatcher::operator()(const LLSD&), // non-empty name to call operator()(const std::string&, const LLSD&). // Returns (empty string, return value) on successful call. // Returns (error message, undefined) if error and 'exception' is false. // Throws DispatchError if error and 'exception' is true. std::pair LLDispatchListener::call(const std::string& name, const LLSD& event, bool exception) const { try { if (name.empty()) { // unless this throws, return (empty string, real return value) return { {}, (*this)(event) }; } else { // unless this throws, return (empty string, real return value) return { {}, (*this)(name, event) }; } } catch (const DispatchError& err) { if (exception) { // Caller asked for an exception on error. Oblige. throw; } // Caller does NOT want an exception: return (error message, undefined) return { err.what(), LLSD() }; } } void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const { // Call sendReply() unconditionally: sendReply() itself tests whether the // specified reply key is present in the incoming request, and does // nothing if there's no such key. sendReply(reply, request, mReplyKey); }