diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2009-06-19 00:17:30 +0000 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2009-06-19 00:17:30 +0000 |
commit | dc3833f31b8a20220ddb1775e1625c016c397435 (patch) | |
tree | 166ce57be2578ea03d1e20976977e4ca6c1f1dba | |
parent | 8a8edfd98c7fced23f4c1f8dd5e4f65e1cadfce8 (diff) |
DEV-31980: extract dispatch-by-string-name logic from LLAresListener to new
LLEventDispatcher and LLDispatchListener classes. See LLAresListener for
example usage.
-rw-r--r-- | indra/llcommon/CMakeLists.txt | 2 | ||||
-rw-r--r-- | indra/llcommon/lleventdispatcher.cpp | 123 | ||||
-rw-r--r-- | indra/llcommon/lleventdispatcher.h | 108 | ||||
-rw-r--r-- | indra/llmessage/llareslistener.cpp | 46 | ||||
-rw-r--r-- | indra/llmessage/llareslistener.h | 14 | ||||
-rw-r--r-- | indra/llmessage/tests/llareslistener_test.cpp | 2 |
6 files changed, 242 insertions, 53 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index e920475f54..88acb70b8a 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -37,6 +37,7 @@ set(llcommon_SOURCE_FILES llerrorthread.cpp llevent.cpp lleventcoro.cpp + lleventdispatcher.cpp lleventfilter.cpp llevents.cpp llfasttimer.cpp @@ -126,6 +127,7 @@ set(llcommon_HEADER_FILES llerrorthread.h llevent.h lleventcoro.h + lleventdispatcher.h lleventfilter.h llevents.h lleventemitter.h diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp new file mode 100644 index 0000000000..2dbd59b156 --- /dev/null +++ b/indra/llcommon/lleventdispatcher.cpp @@ -0,0 +1,123 @@ +/** + * @file lleventdispatcher.cpp + * @author Nat Goodspeed + * @date 2009-06-18 + * @brief Implementation for lleventdispatcher. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/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 "llsdutil.h" + +LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): + mDesc(desc), + mKey(key) +{ +} + +LLEventDispatcher::~LLEventDispatcher() +{ +} + +/// Register a callable by name +void LLEventDispatcher::add(const std::string& name, const Callable& callable, const LLSD& required) +{ + mDispatch[name] = DispatchMap::mapped_type(callable, required); +} + +void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const +{ + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name + << "): " << 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. If no +/// such callable exists, die with LL_ERRS. +void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const +{ + if (! attemptCall(name, event)) + { + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name + << "' not found" << LL_ENDL; + } +} + +/// Extract the @a key value from the incoming @a event, and call the +/// callable whose name is specified by that map @a key. If no such +/// callable exists, die with LL_ERRS. +void LLEventDispatcher::operator()(const LLSD& event) const +{ + // This could/should be implemented in terms of the two-arg overload. + // However -- we can produce a more informative error message. + std::string name(event[mKey]); + if (! attemptCall(name, event)) + { + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey + << " value '" << name << "'" << LL_ENDL; + } +} + +bool LLEventDispatcher::attemptCall(const std::string& name, const LLSD& event) const +{ + DispatchMap::const_iterator found = mDispatch.find(name); + if (found == mDispatch.end()) + { + // The reason we only return false, leaving it up to our caller to die + // with LL_ERRS, is that different callers have different amounts of + // available information. + return false; + } + // Found the name, so it's plausible to even attempt the call. But first, + // validate the syntax of the event itself. + std::string mismatch(llsd_matches(found->second.second, event)); + if (! mismatch.empty()) + { + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ") calling '" << name + << "': bad request: " << mismatch << LL_ENDL; + } + // Event syntax looks good, go for it! + (found->second.first)(event); + return true; // tell caller we were able to call +} + +LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): + LLEventDispatcher(pumpname, key), + mPump(pumpname, true), // allow tweaking for uniqueness + mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1))) +{ +} + +bool LLDispatchListener::process(const LLSD& event) +{ + (*this)(event); + return false; +} diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h new file mode 100644 index 0000000000..d75055fd6f --- /dev/null +++ b/indra/llcommon/lleventdispatcher.h @@ -0,0 +1,108 @@ +/** + * @file lleventdispatcher.h + * @author Nat Goodspeed + * @date 2009-06-18 + * @brief Central mechanism for dispatching events by string name. This is + * useful when you have a single LLEventPump listener on which you can + * request different operations, vs. instantiating a different + * LLEventPump for each such operation. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTDISPATCHER_H) +#define LL_LLEVENTDISPATCHER_H + +#include <string> +#include <map> +#include <boost/function.hpp> +#include <boost/bind.hpp> +#include <typeinfo> +#include "llevents.h" + +class LLSD; + +/** + * Given an LLSD map, examine a string-valued key and call a corresponding + * callable. This class is designed to be contained by an LLEventPump + * listener class that will register some of its own methods, though any + * callable can be used. + */ +class LLEventDispatcher +{ +public: + LLEventDispatcher(const std::string& desc, const std::string& key); + virtual ~LLEventDispatcher(); + + /// Accept any C++ callable, typically a boost::bind() expression + typedef boost::function<void(const LLSD&)> Callable; + + /// Register a @a callable by @a name. The optional @a required parameter + /// is used to validate the structure of each incoming event (see + /// llsd_matches()). + void add(const std::string& name, const Callable& callable, const LLSD& required=LLSD()); + + /// Special case: a subclass of this class can register a @a method + /// without explicitly specifying the <tt>boost::bind()</tt> expression. + /// The optional @a required parameter is used to validate the structure + /// of each incoming event (see llsd_matches()). + template <class CLASS> + void add(const std::string& name, void (CLASS::*method)(const LLSD&), + const LLSD& required=LLSD()) + { + CLASS* downcast = dynamic_cast<CLASS*>(this); + if (! downcast) + { + addFail(name, typeid(CLASS).name()); + } + else + { + add(name, boost::bind(method, downcast, _1), required); + } + } + + /// Unregister a callable + bool remove(const std::string& name); + + /// Call a registered callable with an explicitly-specified name. If no + /// such callable exists, die with LL_ERRS. If the @a event fails to match + /// the @a required prototype specified at add() time, die with LL_ERRS. + void operator()(const std::string& name, const LLSD& event) const; + + /// Extract the @a key value from the incoming @a event, and call the + /// callable whose name is specified by that map @a key. If no such + /// callable exists, die with LL_ERRS. If the @a event fails to match the + /// @a required prototype specified at add() time, die with LL_ERRS. + void operator()(const LLSD& event) const; + +private: + void addFail(const std::string& name, const std::string& classname) const; + /// try to dispatch, return @c true if success + bool attemptCall(const std::string& name, const LLSD& event) const; + + std::string mDesc, mKey; + typedef std::map<std::string, std::pair<Callable, LLSD> > DispatchMap; + DispatchMap mDispatch; +}; + +/** + * Bundle an LLEventPump and a listener with an LLEventDispatcher. A class + * that contains (or derives from) LLDispatchListener need only specify the + * LLEventPump name and dispatch key, and add() its methods. Incoming events + * will automatically be dispatched. + */ +class LLDispatchListener: public LLEventDispatcher +{ +public: + LLDispatchListener(const std::string& pumpname, const std::string& key); + +private: + bool process(const LLSD& event); + + LLEventStream mPump; + LLTempBoundListener mBoundListener; +}; + +#endif /* ! defined(LL_LLEVENTDISPATCHER_H) */ diff --git a/indra/llmessage/llareslistener.cpp b/indra/llmessage/llareslistener.cpp index 4bf375069d..a8beb8cbde 100644 --- a/indra/llmessage/llareslistener.cpp +++ b/indra/llmessage/llareslistener.cpp @@ -9,10 +9,6 @@ * $/LicenseInfo$ */ -#if LL_WINDOWS -#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally -#endif - // Precompiled header #include "linden_common.h" // associated header @@ -27,35 +23,13 @@ #include "llsdutil.h" LLAresListener::LLAresListener(const std::string& pumpname, LLAres* llares): - mAres(llares), - mBoundListener(LLEventPumps::instance(). - obtain(pumpname). - listen("LLAresListener", boost::bind(&LLAresListener::process, this, _1))) -{ - // Insert an entry into our mDispatch map for every method we want to be - // able to invoke via this event API. - mDispatch["rewriteURI"] = boost::bind(&LLAresListener::rewriteURI, this, _1); -} - -bool LLAresListener::process(const LLSD& command) + LLDispatchListener(pumpname, "op"), + mAres(llares) { - const std::string op(command["op"]); - // Look up the requested operation. - DispatchMap::const_iterator found = mDispatch.find(op); - if (found == mDispatch.end()) - { - // There's no feedback other than our own reply. If somebody asks - // for an operation that's not supported (perhaps because of a - // typo?), unless we holler loudly, the request will be silently - // ignored. Throwing a tantrum on such errors will hopefully make - // this product more robust. - LL_ERRS("LLAresListener") << "Unsupported request " << op << LL_ENDL; - return false; - } - // Having found the operation, call it. - found->second(command); - // Conventional LLEventPump listener return - return false; + // add() every method we want to be able to invoke via this event API. + // Optional third parameter validates expected LLSD request structure. + add("rewriteURI", &LLAresListener::rewriteURI, + LLSD().insert("uri", LLSD()).insert("reply", LLSD())); } /// This UriRewriteResponder subclass packages returned URIs as an LLSD @@ -97,13 +71,5 @@ private: void LLAresListener::rewriteURI(const LLSD& data) { - static LLSD required(LLSD().insert("uri", LLSD()).insert("reply", LLSD())); - // Validate that the request is well-formed - std::string mismatch(llsd_matches(required, data)); - if (! mismatch.empty()) - { - LL_ERRS("LLAresListener") << "bad rewriteURI request: " << mismatch << LL_ENDL; - } - // Looks as though we have what we need; issue the request mAres->rewriteURI(data["uri"], new UriRewriteResponder(data)); } diff --git a/indra/llmessage/llareslistener.h b/indra/llmessage/llareslistener.h index 8835440c5d..bf093b3d3d 100644 --- a/indra/llmessage/llareslistener.h +++ b/indra/llmessage/llareslistener.h @@ -14,33 +14,23 @@ #if ! defined(LL_LLARESLISTENER_H) #define LL_LLARESLISTENER_H -#include <string> -#include <map> -#include <boost/function.hpp> -#include "llevents.h" +#include "lleventdispatcher.h" class LLAres; class LLSD; /// Listen on an LLEventPump with specified name for LLAres request events. -class LLAresListener +class LLAresListener: public LLDispatchListener { public: /// Specify the pump name on which to listen, and bind the LLAres instance /// to use (e.g. gAres) LLAresListener(const std::string& pumpname, LLAres* llares); - /// Handle request events on the event pump specified at construction time - bool process(const LLSD& command); - private: /// command["op"] == "rewriteURI" void rewriteURI(const LLSD& data); - typedef boost::function<void(const LLSD&)> Callable; - typedef std::map<std::string, Callable> DispatchMap; - DispatchMap mDispatch; - LLTempBoundListener mBoundListener; LLAres* mAres; }; diff --git a/indra/llmessage/tests/llareslistener_test.cpp b/indra/llmessage/tests/llareslistener_test.cpp index 215a3806f8..ac4886ccf4 100644 --- a/indra/llmessage/tests/llareslistener_test.cpp +++ b/indra/llmessage/tests/llareslistener_test.cpp @@ -130,7 +130,7 @@ namespace tut { threw = e.what(); } - ensure_contains("LLAresListener bad op", threw, "Unsupported"); + ensure_contains("LLAresListener bad op", threw, "bad"); } template<> template<> |