/** * @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::auto_ptr /***************************************************************************** * DispatchError *****************************************************************************/ struct DispatchError: public LLException { // template constructor involving strings passes all arguments to // stringize() to construct LLException's what() string template DispatchError(const std::string& arg0, ARGS&&... args): LLException(stringize(arg0, std::forward(args)...)) {} }; /***************************************************************************** * 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 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&); // 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; }; 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())) { LLTHROW(DispatchError(function, " 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) { LLTHROW(DispatchError(function, " 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()) { LLTHROW(DispatchError(function, " defaults specified for nonexistent params ", formatlist(bogus))); } } else { LLTHROW(DispatchError(function, " defaults must be a map or an array, not ", defaults)); } } LLSD LLSDArgsMapper::map(const LLSD& argsmap) const { if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray())) { LLTHROW(DispatchError(_function, " 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()) { LLTHROW(DispatchError(_function, " missing required arguments ", formatlist(unfilled), " from ", argsmap)); } // done return args; } std::string 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(); } /***************************************************************************** * LLEventDispatcher *****************************************************************************/ LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): mDesc(desc), mKey(key) { } 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; virtual LLSD call(const std::string& desc, const LLSD& event) const { // Validate the syntax of the event itself. std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { LLTHROW(DispatchError(desc, ": bad request: ", mismatch)); } // Event syntax looks good, go for it! mFunc(event); return {}; } virtual LLSD addMetadata(LLSD meta) const { 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& desc, const invoker_function& func): DispatchEntry(desc), mInvoker(func) {} invoker_function mInvoker; virtual LLSD call(const std::string&, const LLSD& event) const { return mInvoker(event); } }; /** * 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& desc, const invoker_function& func, LLSD::Integer arity): ParamsDispatchEntry(desc, func), mArity(arity) {} LLSD::Integer mArity; virtual LLSD addMetadata(LLSD meta) const { 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(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; virtual LLSD call(const std::string& desc, const LLSD& event) const { // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass // to base-class call() method. return ParamsDispatchEntry::call(desc, mMapper.map(event)); } virtual LLSD addMetadata(LLSD meta) const { 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) { mDispatch.emplace(name, new ArrayParamsDispatchEntry(desc, invoker, arity)); } void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, const std::string& desc, const invoker_function& invoker, const LLSD& params, const LLSD& defaults) { mDispatch.emplace(name, new MapParamsDispatchEntry(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. void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const { std::string error{ try_call_log(std::string(), name, event) }; if (! error.empty()) { callFail(event, error); } } /// 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. void LLEventDispatcher::operator()(const LLSD& event) const { std::string error{ try_call_log(mKey, event[mKey], event) }; if (! error.empty()) { callFail(event, error); } } void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const { // pass back a response that includes an "error" key with the message. reply(llsd::map("error", msg), event); } void LLEventDispatcher::reply(const LLSD& response, const LLSD& event) const { static LLSD::String key{ "reply" }; if (event.has(key)) { // Oh good, the incoming event specifies a reply pump -- pass back // our response. sendReply(response, event, key); } } bool LLEventDispatcher::try_call(const LLSD& event) const { return try_call_log(mKey, event[mKey], event).empty(); } /*==========================================================================*| TODO: * When mInvoker returns result.isDefined(), sendReply(llsd::map("data", result)) * When try_call finds name.isArray(), construct response array from dispatching each call, sendReply() as above * When try_call finds name.isMap(), construct response map from dispatching each call, sendReply() as above -- note, caller can't care about order * Possible future transactional behavior: look up all names before calling any |*==========================================================================*/ bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const { return try_call_log(std::string(), name, event).empty(); } std::string LLEventDispatcher::try_call_log(const std::string& key, const LLSD& name, const LLSD& event) const { std::string error{ try_call(key, name, event) }; if (! error.empty()) { // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that. error = stringize(LLError::Log::classname(this), "(", mDesc, "): ", error); LL_WARNS("LLEventDispatcher") << error << LL_ENDL; } return error; } // This internal method returns empty string if the call succeeded, else // non-empty error message. std::string LLEventDispatcher::try_call(const std::string& key, const LLSD& name, const LLSD& event) const { if (name.isUndefined()) { if (key.empty()) { return "attempting to call with no name"; } else { return stringize("no ", key); } } else if (name.isArray()) { return stringize(key, " array dispatch ", name, " not yet implemented"); } else if (name.isMap()) { return stringize(key, " map dispatch ", name, " not yet implemented"); } else if (! name.isString()) { return stringize(key, " bad type ", LLSD::typeString(name.type()), ' ', name, " -- function names are String"); } else // name is an LLSD::String { auto success{ try_call_one(key, name, event) }; // pretend to unpack std::string& error{ success.first }; LLSD& result{ success.second }; // did try_call_one() report an error? if (! error.empty()) { // if we have a reply key, respond to invoker reply(llsd::map("error", error), event); // now tell caller return error; } // try_call_one() succeeded in calling the target function -- // should we reply to invoker? if (result.isUndefined()) { // We would get result.isUndefined() if the target function has // void return. In any case, even if the target function returns // LLSD, isUndefined() means "don't bother sending response." return {}; } // result.isDefined(): the target function returned something. // Respond to invoker if we have a "reply" key. if (! result.isMap()) { // wrap result in a map to play well with sendReply() result = llsd::map("data", result); } reply(result, event); return {}; } } std::pair LLEventDispatcher::try_call_one(const std::string& key, const std::string& name, const LLSD& event) const { DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { if (key.empty()) { return { stringize("'", name, "' not found"), {} }; } else { return { stringize("bad ", key, " value '", name, "'"), {} }; } } try { // Found the name, so it's plausible to even attempt the call. return { {}, found->second->call(stringize("calling '", name, "'"), event) }; } catch (const DispatchError& err) { // trouble preparing arguments return { err.what(), {} }; } catch (const LL::apply_error& err) { // could also hit runtime errors with LL::apply() return { err.what(), {} }; } } 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); } /***************************************************************************** * LLDispatchListener *****************************************************************************/ LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): LLEventDispatcher(pumpname, key), // Do NOT tweak the passed pumpname. In practice, when someone // instantiates a subclass of our LLEventAPI subclass, they intend to // claim that LLEventPump name in the global LLEventPumps namespace. It // would be mysterious and distressing if we allowed name tweaking, and // someone else claimed pumpname first for a completely unrelated // LLEventPump. Posted events would never reach our subclass listener // because we would have silently changed its name; meanwhile listeners // (if any) on that other LLEventPump would be confused by the events // intended for our subclass. LLEventStream(pumpname, false), mBoundListener(listen("self", [this](const LLSD& event){ return process(event); })) { } bool LLDispatchListener::process(const LLSD& event) { (*this)(event); return false; }