972 lines
33 KiB
C++
972 lines
33 KiB
C++
/**
|
|
* @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 <iomanip> // std::quoted()
|
|
#include <memory> // 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(LLEventDispatcher* parent, 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 <typename... ARGS>
|
|
void callFail(ARGS&&... args) const;
|
|
|
|
// store a plain dumb back-pointer because we don't have to manage the
|
|
// parent LLEventDispatcher's lifespan
|
|
LLEventDispatcher* _parent;
|
|
// 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<LLSD::String, size_t> 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<char> FilledVector;
|
|
FilledVector _has_dft;
|
|
};
|
|
|
|
LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(LLEventDispatcher* parent,
|
|
const std::string& function,
|
|
const LLSD& names,
|
|
const LLSD& defaults):
|
|
_parent(parent),
|
|
_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<bool> 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 <typename... ARGS>
|
|
void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const
|
|
{
|
|
_parent->callFail<LLEventDispatcher::DispatchError>
|
|
(_function, std::forward<ARGS>(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(LLEventDispatcher* parent, const std::string& desc):
|
|
mParent(parent),
|
|
mDesc(desc)
|
|
{}
|
|
|
|
/**
|
|
* DispatchEntry subclass used for callables accepting(const LLSD&)
|
|
*/
|
|
struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry
|
|
{
|
|
LLSDDispatchEntry(LLEventDispatcher* parent, const std::string& desc,
|
|
const Callable& func, const LLSD& required):
|
|
DispatchEntry(parent, 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())
|
|
{
|
|
return callFail(desc, ": bad request: ", mismatch);
|
|
}
|
|
// Event syntax looks good, go for it!
|
|
return mFunc(event);
|
|
}
|
|
|
|
LLSD getMetadata() const override
|
|
{
|
|
return llsd::map("required", mRequired);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* DispatchEntry subclass for passing LLSD to functions accepting
|
|
* arbitrary argument types (convertible via LLSDParam)
|
|
*/
|
|
struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry
|
|
{
|
|
ParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
|
|
const std::string& desc, const invoker_function& func):
|
|
DispatchEntry(parent, 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());
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* DispatchEntry subclass for dispatching LLSD::Array to functions accepting
|
|
* arbitrary argument types (convertible via LLSDParam)
|
|
*/
|
|
struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
|
|
{
|
|
ArrayParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
|
|
const std::string& desc, const invoker_function& func,
|
|
LLSD::Integer arity):
|
|
ParamsDispatchEntry(parent, 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 getMetadata() const override
|
|
{
|
|
LLSD array(LLSD::emptyArray());
|
|
// Resize to number of arguments required
|
|
if (mArity)
|
|
array[mArity - 1] = LLSD();
|
|
llassert_always(array.size() == mArity);
|
|
return llsd::map("required", array);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* DispatchEntry subclass for dispatching LLSD::Map to functions accepting
|
|
* arbitrary argument types (convertible via LLSDParam)
|
|
*/
|
|
struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
|
|
{
|
|
MapParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
|
|
const std::string& desc, const invoker_function& func,
|
|
const LLSD& params, const LLSD& defaults):
|
|
ParamsDispatchEntry(parent, name, desc, func),
|
|
mMapper(parent, 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 getMetadata() const override
|
|
{
|
|
return llsd::map("required", mRequired, "optional", mOptional);
|
|
}
|
|
};
|
|
|
|
void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name,
|
|
const std::string& desc,
|
|
const invoker_function& invoker,
|
|
LLSD::Integer arity)
|
|
{
|
|
mDispatch.emplace(
|
|
name,
|
|
new ArrayParamsDispatchEntry(this, "", 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(this, "", desc, invoker, params, defaults));
|
|
}
|
|
|
|
/// Register a callable by name
|
|
void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc,
|
|
const Callable& callable, const LLSD& required)
|
|
{
|
|
mDispatch.emplace(name, new LLSDDispatchEntry(this, desc, callable, required));
|
|
}
|
|
|
|
void LLEventDispatcher::addFail(const std::string& name, const char* classname) const
|
|
{
|
|
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name
|
|
<< "): " << LLError::Log::demangle(classname)
|
|
<< " is not a subclass of LLEventDispatcher"
|
|
<< LL_ENDL;
|
|
}
|
|
|
|
/// 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<DispatchError>("attempting to call with no name");
|
|
}
|
|
else
|
|
{
|
|
callFail<DispatchError>("no ", key);
|
|
}
|
|
}
|
|
|
|
DispatchMap::const_iterator found = mDispatch.find(name);
|
|
if (found == mDispatch.end())
|
|
{
|
|
// Here we were passed a non-empty 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<DispatchMissing>(std::quoted(name), " not found");
|
|
}
|
|
else
|
|
{
|
|
callFail<DispatchMissing>("bad ", key, " value ", std::quoted(name));
|
|
}
|
|
}
|
|
|
|
// Found the name, so it's plausible to even attempt the call.
|
|
const char* delim = (key.empty()? "" : "=");
|
|
// append either "[key=name]" or just "[name]"
|
|
SetState transient(this, '[', key, delim, name, ']');
|
|
return found->second->call("", event, (! key.empty()), mArgskey);
|
|
}
|
|
|
|
template <typename EXCEPTION, typename... ARGS>
|
|
//static
|
|
void LLEventDispatcher::sCallFail(ARGS&&... args)
|
|
{
|
|
auto error = stringize(std::forward<ARGS>(args)...);
|
|
LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
|
|
LLTHROW(EXCEPTION(error));
|
|
}
|
|
|
|
template <typename EXCEPTION, typename... ARGS>
|
|
void LLEventDispatcher::callFail(ARGS&&... args) const
|
|
{
|
|
// Describe this instance in addition to the error itself.
|
|
sCallFail<EXCEPTION>(*this, ": ", std::forward<ARGS>(args)...);
|
|
}
|
|
|
|
LLSD LLEventDispatcher::getMetadata(const std::string& name) const
|
|
{
|
|
DispatchMap::const_iterator found = mDispatch.find(name);
|
|
if (found == mDispatch.end())
|
|
{
|
|
return LLSD();
|
|
}
|
|
LLSD meta{ found->second->getMetadata() };
|
|
meta["name"] = name;
|
|
meta["desc"] = found->second->mDesc;
|
|
return meta;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self)
|
|
{
|
|
// If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that.
|
|
// Also report whatever transient state is active.
|
|
return out << LLError::Log::classname(self) << '(' << self.mDesc << ')'
|
|
<< self.getState();
|
|
}
|
|
|
|
std::string LLEventDispatcher::getState() const
|
|
{
|
|
// default value of fiber_specific_ptr is nullptr, and ~SetState() reverts
|
|
// to that; infer empty string
|
|
if (! mState.get())
|
|
return {};
|
|
else
|
|
return *mState;
|
|
}
|
|
|
|
bool LLEventDispatcher::setState(SetState&, const std::string& state) const
|
|
{
|
|
// If SetState is instantiated at multiple levels of function call, ignore
|
|
// the lower-level call because the outer call presumably provides more
|
|
// context.
|
|
if (mState.get())
|
|
return false;
|
|
|
|
// Pass us empty string (a la ~SetState()) to reset to nullptr, else take
|
|
// a heap copy of the passed state string so we can delete it on
|
|
// subsequent reset().
|
|
mState.reset(state.empty()? nullptr : new std::string(state));
|
|
return true;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* LLDispatchListener
|
|
*****************************************************************************/
|
|
std::string LLDispatchListener::mReplyKey{ "reply" };
|
|
|
|
bool LLDispatchListener::process(const LLSD& event) const
|
|
{
|
|
// Decide what to do based on the incoming value of the specified dispatch
|
|
// key.
|
|
LLSD name{ event[getDispatchKey()] };
|
|
if (name.isMap())
|
|
{
|
|
call_map(name, event);
|
|
}
|
|
else if (name.isArray())
|
|
{
|
|
call_array(name, event);
|
|
}
|
|
else
|
|
{
|
|
call_one(name, event);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const
|
|
{
|
|
LLSD result;
|
|
try
|
|
{
|
|
result = (*this)(event);
|
|
}
|
|
catch (const DispatchError& err)
|
|
{
|
|
if (! event.has(mReplyKey))
|
|
{
|
|
// Without a reply key, let the exception propagate.
|
|
throw;
|
|
}
|
|
|
|
// Here there was an error and the incoming event has mReplyKey. Reply
|
|
// with a map containing an "error" key explaining the problem.
|
|
return reply(llsd::map("error", err.what()), event);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const
|
|
{
|
|
// LLSD map containing returned values
|
|
LLSD result;
|
|
// cache dispatch key
|
|
std::string key{ getDispatchKey() };
|
|
// collect any error messages here
|
|
std::ostringstream errors;
|
|
const char* delim = "";
|
|
|
|
for (const auto& pair : llsd::inMap(reqmap))
|
|
{
|
|
const LLSD::String& name{ pair.first };
|
|
const LLSD& args{ pair.second };
|
|
try
|
|
{
|
|
// in case of errors, tell user the dispatch key, the fact that
|
|
// we're processing a request map and the current key in that map
|
|
SetState(this, '[', key, '[', name, "]]");
|
|
// With this form, capture return value even if undefined:
|
|
// presence of the key in the response map can be used to detect
|
|
// which request keys succeeded.
|
|
result[name] = (*this)(name, args);
|
|
}
|
|
catch (const std::exception& err)
|
|
{
|
|
// Catch not only DispatchError, but any C++ exception thrown by
|
|
// the target callable. Collect exception name and message in
|
|
// 'errors'.
|
|
errors << delim << LLError::Log::classname(err) << ": " << err.what();
|
|
delim = "\n";
|
|
}
|
|
}
|
|
|
|
// so, were there any errors?
|
|
std::string error = errors.str();
|
|
if (! error.empty())
|
|
{
|
|
if (! event.has(mReplyKey))
|
|
{
|
|
// can't send reply, throw
|
|
sCallFail<DispatchError>(error);
|
|
}
|
|
else
|
|
{
|
|
// reply key present
|
|
result["error"] = error;
|
|
}
|
|
}
|
|
|
|
reply(result, event);
|
|
}
|
|
|
|
void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) const
|
|
{
|
|
// LLSD array containing returned values
|
|
LLSD results;
|
|
// cache the dispatch key
|
|
std::string key{ getDispatchKey() };
|
|
// arguments array, if present -- const because, if it's shorter than
|
|
// reqarray, we don't want to grow it
|
|
const LLSD argsarray{ event[getArgsKey()] };
|
|
// error message, if any
|
|
std::string error;
|
|
|
|
// classic index loop because we need the index
|
|
for (size_t i = 0, size = reqarray.size(); i < size; ++i)
|
|
{
|
|
const auto& reqentry{ reqarray[i] };
|
|
std::string name;
|
|
LLSD args;
|
|
if (reqentry.isString())
|
|
{
|
|
name = reqentry.asString();
|
|
args = argsarray[i];
|
|
}
|
|
else if (reqentry.isArray() && reqentry.size() == 2 && reqentry[0].isString())
|
|
{
|
|
name = reqentry[0].asString();
|
|
args = reqentry[1];
|
|
}
|
|
else
|
|
{
|
|
// reqentry isn't in either of the documented forms
|
|
error = stringize(*this, ": ", getDispatchKey(), '[', i, "] ",
|
|
reqentry, " unsupported");
|
|
break;
|
|
}
|
|
|
|
// reqentry is one of the valid forms, got name and args
|
|
try
|
|
{
|
|
// in case of errors, tell user the dispatch key, the fact that
|
|
// we're processing a request array, the current entry in that
|
|
// array and the corresponding callable name
|
|
SetState(this, '[', key, '[', i, "]=", name, ']');
|
|
// With this form, capture return value even if undefined
|
|
results.append((*this)(name, args));
|
|
}
|
|
catch (const std::exception& err)
|
|
{
|
|
// Catch not only DispatchError, but any C++ exception thrown by
|
|
// the target callable. Report the exception class as well as the
|
|
// error string.
|
|
error = stringize(LLError::Log::classname(err), ": ", err.what());
|
|
break;
|
|
}
|
|
}
|
|
|
|
LLSD result;
|
|
// was there an error?
|
|
if (! error.empty())
|
|
{
|
|
if (! event.has(mReplyKey))
|
|
{
|
|
// can't send reply, throw
|
|
sCallFail<DispatchError>(error);
|
|
}
|
|
else
|
|
{
|
|
// reply key present
|
|
result["error"] = error;
|
|
}
|
|
}
|
|
|
|
// wrap the results array as response map "data" key, as promised
|
|
if (results.isDefined())
|
|
{
|
|
result["data"] = results;
|
|
}
|
|
|
|
reply(result, event);
|
|
}
|
|
|
|
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);
|
|
}
|